"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PollingFetcher = void 0;
const events_1 = require("events");
const feature_1 = require("../feature");
const request_1 = require("../request");
const url_utils_1 = require("../url-utils");
const events_2 = require("../events");
class PollingFetcher extends events_1.EventEmitter {
    constructor(options) {
        super();
        this.stopped = false;
        this.failures = 0;
        this.options = options;
        this.etag = options.etag;
    }
    timedFetch(interval) {
        if (interval > 0) {
            this.timer = setTimeout(() => this.fetch(), interval);
            if (process.env.NODE_ENV !== 'test' && typeof this.timer.unref === 'function') {
                this.timer.unref();
            }
        }
    }
    async start() {
        this.stopped = false;
        await this.fetch();
    }
    nextFetch() {
        return this.options.refreshInterval + this.failures * this.options.refreshInterval;
    }
    getFailures() {
        return this.failures;
    }
    getEtag() {
        return this.etag;
    }
    setEtag(value) {
        this.etag = value;
    }
    backoff() {
        this.failures = Math.min(this.failures + 1, 10);
        return this.nextFetch();
    }
    countSuccess() {
        this.failures = Math.max(this.failures - 1, 0);
        return this.nextFetch();
    }
    configurationError(url, statusCode) {
        this.failures += 1;
        if (statusCode === 401 || statusCode === 403) {
            this.emit(events_2.UnleashEvents.Error, new Error(
            // eslint-disable-next-line max-len
            `${url} responded ${statusCode} which means your API key is not allowed to connect. Stopping refresh of toggles`));
        }
        return 0;
    }
    recoverableError(url, statusCode) {
        let nextFetch = this.backoff();
        if (statusCode === 429) {
            this.emit(events_2.UnleashEvents.Warn, `${url} responded TOO_MANY_CONNECTIONS (429). Backing off`);
        }
        else if (statusCode === 404) {
            this.emit(events_2.UnleashEvents.Warn, `${url} responded FILE_NOT_FOUND (404). Backing off`);
        }
        else if (statusCode === 500 ||
            statusCode === 502 ||
            statusCode === 503 ||
            statusCode === 504) {
            this.emit(events_2.UnleashEvents.Warn, `${url} responded ${statusCode}. Backing off`);
        }
        return nextFetch;
    }
    handleErrorCases(url, statusCode) {
        if (statusCode === 401 || statusCode === 403) {
            return this.configurationError(url, statusCode);
        }
        else if (statusCode === 404 ||
            statusCode === 429 ||
            statusCode === 500 ||
            statusCode === 502 ||
            statusCode === 503 ||
            statusCode === 504) {
            return this.recoverableError(url, statusCode);
        }
        else {
            const error = new Error(`Response was not statusCode 2XX, but was ${statusCode}`);
            this.emit(events_2.UnleashEvents.Error, error);
            return this.options.refreshInterval;
        }
    }
    async fetch() {
        if (this.stopped || !(this.options.refreshInterval > 0)) {
            return;
        }
        let nextFetch = this.options.refreshInterval;
        try {
            let mergedTags;
            if (this.options.tags) {
                mergedTags = this.mergeTagsToStringArray(this.options.tags);
            }
            const url = (0, url_utils_1.default)(this.options.url, this.options.projectName, this.options.namePrefix, mergedTags, this.options.mode);
            const headers = this.options.customHeadersFunction
                ? await this.options.customHeadersFunction()
                : this.options.headers;
            const res = await (0, request_1.get)({
                url,
                etag: this.etag,
                appName: this.options.appName,
                timeout: this.options.timeout,
                instanceId: this.options.instanceId,
                connectionId: this.options.connectionId,
                interval: this.options.refreshInterval,
                headers,
                httpOptions: this.options.httpOptions,
                supportedSpecVersion: '5.2.0',
            });
            if (res.status === 304) {
                this.emit(events_2.UnleashEvents.Unchanged);
            }
            else if (res.ok) {
                nextFetch = this.countSuccess();
                try {
                    const data = await res.json();
                    if (res.headers.get('etag') !== null) {
                        this.etag = res.headers.get('etag');
                    }
                    else {
                        this.etag = undefined;
                    }
                    const fetchingModeHeader = res.headers.get('fetch-mode');
                    if (fetchingModeHeader === 'streaming' && this.options.onModeChange) {
                        await this.options.onModeChange('streaming');
                        return;
                    }
                    if (this.options.mode.type === 'polling' && this.options.mode.format === 'delta') {
                        await this.options.onSaveDelta((0, feature_1.parseClientFeaturesDelta)(data));
                    }
                    else {
                        await this.options.onSave(data, true);
                    }
                }
                catch (err) {
                    this.emit(events_2.UnleashEvents.Error, err);
                }
            }
            else {
                nextFetch = this.handleErrorCases(url, res.status);
            }
        }
        catch (err) {
            const e = err;
            if (e.code === 'ECONNRESET') {
                nextFetch = Math.max(Math.floor(this.options.refreshInterval / 2), 1000);
                this.emit(events_2.UnleashEvents.Warn, `Socket keep alive error, retrying in ${nextFetch}ms`);
            }
            else {
                this.emit(events_2.UnleashEvents.Error, err);
            }
        }
        finally {
            this.timedFetch(nextFetch);
        }
    }
    mergeTagsToStringArray(tags) {
        return tags.map((tag) => `${tag.name}:${tag.value}`);
    }
    stop() {
        this.stopped = true;
        if (this.timer) {
            clearTimeout(this.timer);
        }
    }
}
exports.PollingFetcher = PollingFetcher;//# sourceMappingURL=https://main.vscode-cdn.net/sourcemaps/94ec85969ebc38d14c35c87a0284fafd84bff116/node_modules/unleash-client/lib/repository/polling-fetcher.js.map