"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("./errors");
const util_1 = require("./util");
/**
 * OAuthTokens is the handle for OAuth access and refresh tokens granted by
 * the user.
 */
class OAuthTokens {
    constructor(data) {
        this.data = data;
    }
    /**
     * Returns OAuth tokens created from a response from the token endpoint as
     * described in https://tools.ietf.org/html/rfc6749#section-5.1.
     */
    static fromTokenResponse(res, scopes) {
        return new OAuthTokens({
            accessToken: res.access_token,
            refreshToken: res.refresh_token,
            expiresAt: new Date(Date.now() + res.expires_in * 1000),
            scopes,
        });
    }
    /**
     * Returns a header that can be used in making http requests.
     */
    header() {
        return { Authorization: `Bearer ${this.data.accessToken}` };
    }
    /**
     * Returns whether the oauth tokens are expired.
     */
    expired() {
        return this.data.expiresAt.getTime() < Date.now();
    }
    /**
     * Returns whether all of the provided scopes have been granted by this set
     * of OAuth tokens.
     */
    granted(scopes) {
        return !scopes.some(s => this.data.scopes.indexOf(s) === -1);
    }
}
exports.OAuthTokens = OAuthTokens;
/**
 * OAuthShortCode is the shortcode handle returned by the `OAuthClient`. See
 * documentation on that class for more information and usage examples.
 */
class OAuthShortCode {
    constructor(clientId, scopes, shortcode, fetcher) {
        this.clientId = clientId;
        this.scopes = scopes;
        this.shortcode = shortcode;
        this.fetcher = fetcher;
        /**
         * The OAuth shortcode to display to the user.
         */
        this.code = this.shortcode.code;
        this.expired = util_1.delay(this.shortcode.expires_in * 1000);
    }
    waitForAccept() {
        return __awaiter(this, void 0, void 0, function* () {
            const res = yield this.fetcher.json('get', `/oauth/shortcode/check/${this.shortcode.handle}`);
            switch (res.status) {
                case 200:
                    return this.getTokens(yield res.json());
                case 204:
                    break;
                case 403:
                    throw new errors_1.ShortCodeAccessDeniedError();
                case 404:
                    throw new errors_1.ShortCodeExpireError();
                default:
                    throw new errors_1.UnexpectedHttpError(res, yield res.text());
            }
            yield Promise.race([
                util_1.delay(OAuthShortCode.checkInterval),
                this.expired.then(() => {
                    throw new errors_1.ShortCodeExpireError();
                }),
            ]);
            return this.waitForAccept();
        });
    }
    getTokens({ code }) {
        return __awaiter(this, void 0, void 0, function* () {
            const res = yield this.fetcher.json('post', '/oauth/token', {
                client_id: this.clientId,
                grant_type: 'authorization_code',
                code,
            });
            if (res.status >= 300) {
                throw new errors_1.UnexpectedHttpError(res, yield res.text());
            }
            return OAuthTokens.fromTokenResponse(yield res.json(), this.scopes);
        });
    }
}
/**
 * Interval on which to poll to see if the shortcode was accepted.
 */
OAuthShortCode.checkInterval = 2000;
exports.OAuthShortCode = OAuthShortCode;
/**
 * OAuthClient is a clien interface for handling shortcode grants and
 * reauthorizing access tokens.
 *
 * ```
 * const client = new OAuthClient(options);
 * const attempt = () => {
 *   return client.getCode()
 *     .then(code => {
 *       console.log(`Go to mixer.com/go and enter ${code.code}`);
 *       return code.waitForAccept();
 *     })
 *     .catch(err => {
 *       if (err instanceof ShortCodeExpireError) {
 *         return attempt(); // loop!
 *       }
 *
 *       throw err;
 *     });
 * };
 *
 * attempt().then(tokens => console.log(`Access token: ${tokens.access}`));
 * ```
 */
class OAuthClient {
    constructor(options, fetcher = new util_1.Fetcher([], options.host)) {
        this.options = options;
        this.fetcher = fetcher;
    }
    /**
     * Starts a flow to get an oauth code.
     */
    getCode() {
        return __awaiter(this, void 0, void 0, function* () {
            const results = yield this.fetcher.json('post', '/oauth/shortcode', {
                client_id: this.options.clientId,
                client_secret: this.options.clientSecret,
                scope: this.options.scopes.join(' '),
            });
            if (results.status >= 300) {
                throw new errors_1.UnexpectedHttpError(results, yield results.text());
            }
            const json = yield results.json();
            return new OAuthShortCode(this.options.clientId, this.options.scopes, json, this.fetcher);
        });
    }
    /**
     * Refreshes the token set and returns a new set of tokens.
     */
    refresh(tokens) {
        return __awaiter(this, void 0, void 0, function* () {
            const res = yield this.fetcher.json('post', '/oauth/token', {
                grant_type: 'refresh_token',
                refresh_token: tokens.data.refreshToken,
                client_id: this.options.clientId,
            });
            if (res.status >= 300) {
                throw new errors_1.UnexpectedHttpError(res, yield res.text());
            }
            return OAuthTokens.fromTokenResponse(yield res.json(), tokens.data.scopes);
        });
    }
}
exports.OAuthClient = OAuthClient;
