'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var universalUserAgent = require('universal-user-agent');
var request = require('@octokit/request');
var oauthMethods = require('@octokit/oauth-methods');

async function getOAuthAccessToken(state, options) {
  const cachedAuthentication = getCachedAuthentication(state, options.auth);
  if (cachedAuthentication) return cachedAuthentication; // Step 1: Request device and user codes
  // https://docs.github.com/en/developers/apps/authorizing-oauth-apps#step-1-app-requests-the-device-and-user-verification-codes-from-github

  const {
    data: verification
  } = await oauthMethods.createDeviceCode({
    clientType: state.clientType,
    clientId: state.clientId,
    request: options.request || state.request,
    // @ts-expect-error the extra code to make TS happy is not worth it
    scopes: options.auth.scopes || state.scopes
  }); // Step 2: User must enter the user code on https://github.com/login/device
  // See https://docs.github.com/en/developers/apps/authorizing-oauth-apps#step-2-prompt-the-user-to-enter-the-user-code-in-a-browser

  await state.onVerification(verification); // Step 3: Exchange device code for access token
  // See https://docs.github.com/en/developers/apps/authorizing-oauth-apps#step-3-app-polls-github-to-check-if-the-user-authorized-the-device

  const authentication = await waitForAccessToken(options.request || state.request, state.clientId, state.clientType, verification);
  state.authentication = authentication;
  return authentication;
}

function getCachedAuthentication(state, auth) {
  if (auth.refresh === true) return false;
  if (!state.authentication) return false;

  if (state.clientType === "github-app") {
    return state.authentication;
  }

  const authentication = state.authentication;
  const newScope = ("scopes" in auth && auth.scopes || state.scopes).join(" ");
  const currentScope = authentication.scopes.join(" ");
  return newScope === currentScope ? authentication : false;
}

async function wait(seconds) {
  await new Promise(resolve => setTimeout(resolve, seconds * 1000));
}

async function waitForAccessToken(request, clientId, clientType, verification) {
  try {
    const options = {
      clientId,
      request,
      code: verification.device_code
    }; // WHY TYPESCRIPT WHY ARE YOU DOING THIS TO ME

    const {
      authentication
    } = clientType === "oauth-app" ? await oauthMethods.exchangeDeviceCode({ ...options,
      clientType: "oauth-app"
    }) : await oauthMethods.exchangeDeviceCode({ ...options,
      clientType: "github-app"
    });
    return {
      type: "token",
      tokenType: "oauth",
      ...authentication
    };
  } catch (error) {
    // istanbul ignore if
    if (!error.response) throw error;
    const errorType = error.response.data.error;

    if (errorType === "authorization_pending") {
      await wait(verification.interval);
      return waitForAccessToken(request, clientId, clientType, verification);
    }

    if (errorType === "slow_down") {
      await wait(verification.interval + 5);
      return waitForAccessToken(request, clientId, clientType, verification);
    }

    throw error;
  }
}

async function auth(state, authOptions) {
  return getOAuthAccessToken(state, {
    auth: authOptions
  });
}

async function hook(state, request, route, parameters) {
  let endpoint = request.endpoint.merge(route, parameters); // Do not intercept request to retrieve codes or token

  if (/\/login\/(oauth\/access_token|device\/code)$/.test(endpoint.url)) {
    return request(endpoint);
  }

  const {
    token
  } = await getOAuthAccessToken(state, {
    request,
    auth: {
      type: "oauth"
    }
  });
  endpoint.headers.authorization = `token ${token}`;
  return request(endpoint);
}

const VERSION = "4.0.0";

function createOAuthDeviceAuth(options) {
  const requestWithDefaults = options.request || request.request.defaults({
    headers: {
      "user-agent": `octokit-auth-oauth-device.js/${VERSION} ${universalUserAgent.getUserAgent()}`
    }
  });
  const {
    request: request$1 = requestWithDefaults,
    ...otherOptions
  } = options;
  const state = options.clientType === "github-app" ? { ...otherOptions,
    clientType: "github-app",
    request: request$1
  } : { ...otherOptions,
    clientType: "oauth-app",
    request: request$1,
    scopes: options.scopes || []
  };

  if (!options.clientId) {
    throw new Error('[@octokit/auth-oauth-device] "clientId" option must be set (https://github.com/octokit/auth-oauth-device.js#usage)');
  }

  if (!options.onVerification) {
    throw new Error('[@octokit/auth-oauth-device] "onVerification" option must be a function (https://github.com/octokit/auth-oauth-device.js#usage)');
  } // @ts-ignore too much for tsc / ts-jest ¯\_(ツ)_/¯


  return Object.assign(auth.bind(null, state), {
    hook: hook.bind(null, state)
  });
}

exports.createOAuthDeviceAuth = createOAuthDeviceAuth;
//# sourceMappingURL=index.js.map
