'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');

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }

  return obj;
}

function ownKeys(object, enumerableOnly) {
  var keys = Object.keys(object);

  if (Object.getOwnPropertySymbols) {
    var symbols = Object.getOwnPropertySymbols(object);
    if (enumerableOnly) symbols = symbols.filter(function (sym) {
      return Object.getOwnPropertyDescriptor(object, sym).enumerable;
    });
    keys.push.apply(keys, symbols);
  }

  return keys;
}

function _objectSpread2(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i] != null ? arguments[i] : {};

    if (i % 2) {
      ownKeys(Object(source), true).forEach(function (key) {
        _defineProperty(target, key, source[key]);
      });
    } else if (Object.getOwnPropertyDescriptors) {
      Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    } else {
      ownKeys(Object(source)).forEach(function (key) {
        Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
      });
    }
  }

  return target;
}

function _objectWithoutPropertiesLoose(source, excluded) {
  if (source == null) return {};
  var target = {};
  var sourceKeys = Object.keys(source);
  var key, i;

  for (i = 0; i < sourceKeys.length; i++) {
    key = sourceKeys[i];
    if (excluded.indexOf(key) >= 0) continue;
    target[key] = source[key];
  }

  return target;
}

function _objectWithoutProperties(source, excluded) {
  if (source == null) return {};

  var target = _objectWithoutPropertiesLoose(source, excluded);

  var key, i;

  if (Object.getOwnPropertySymbols) {
    var sourceSymbolKeys = Object.getOwnPropertySymbols(source);

    for (i = 0; i < sourceSymbolKeys.length; i++) {
      key = sourceSymbolKeys[i];
      if (excluded.indexOf(key) >= 0) continue;
      if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
      target[key] = source[key];
    }
  }

  return target;
}

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(_objectSpread2(_objectSpread2({}, options), {}, {
      clientType: "oauth-app"
    })) : await oauthMethods.exchangeDeviceCode(_objectSpread2(_objectSpread2({}, options), {}, {
      clientType: "github-app"
    }));
    return _objectSpread2({
      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 = "3.1.1";

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
  } = options,
        otherOptions = _objectWithoutProperties(options, ["request"]);

  const state = options.clientType === "github-app" ? _objectSpread2(_objectSpread2({}, otherOptions), {}, {
    clientType: "github-app",
    request: request$1
  }) : _objectSpread2(_objectSpread2({}, 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
