var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
/* eslint-disable import/prefer-default-export */
import cloneDeep from 'lodash/cloneDeep';
import { validateResponse } from '@darajs/ui-utils';
import { notifySessionLoggedOut, runSessionRefresh, waitForOngoingSessionRefresh } from '../auth/session-state';
import { SESSION_REFRESHED_EVENT } from './events';
/**
 * Serializable form of RequestExtras.
 * Required to use for e.g. recoil params as they need to be serializable.
 */
export class RequestExtrasSerializable {
    constructor(extras) {
        this.extras = extras;
    }
    /**
     * Get the serializable form of the extras.
     * Transforms the headers into a serializable format rather than a Headers object.
     */
    toSerializable() {
        if (!this.extras) {
            return null;
        }
        if (typeof this.extras === 'string') {
            return this.extras;
        }
        const serializable = cloneDeep(this.extras);
        // Make headers serializable
        if (serializable === null || serializable === void 0 ? void 0 : serializable.headers) {
            const headers = new Headers(serializable.headers);
            serializable.headers = Object.fromEntries(headers.entries());
        }
        return serializable;
    }
    /**
     * Serialize the extras into a string.
     */
    toJSON() {
        const serializable = this.toSerializable();
        if (!serializable) {
            return null;
        }
        return JSON.stringify(serializable);
    }
}
/**
 * Light wrapper around fetch.
 *
 * @param url URL
 * @param options optional fetch options. Multiple option sets can be provided, they will be merged in order with later options overriding earlier ones.
 */
export function request(url, ...options) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b;
        // If another request/tab is currently refreshing the session, wait for it first
        // so we don't send avoidable 401/403 requests with stale credentials.
        yield waitForOngoingSessionRefresh();
        const mergedOptions = options.reduce((acc, opt) => (Object.assign(Object.assign({}, acc), opt)), {});
        const { headers, credentials: mergedCredentials } = mergedOptions, other = __rest(mergedOptions, ["headers", "credentials"]);
        const credentials = mergedCredentials !== null && mergedCredentials !== void 0 ? mergedCredentials : 'include';
        const headersInterface = new Headers(headers);
        // default to json accept header
        if (!headersInterface.has('Accept')) {
            headersInterface.set('Accept', 'application/json');
        }
        // default to json content type header unless body is formdata
        if (!headersInterface.has('Content-Type') && (mergedOptions === null || mergedOptions === void 0 ? void 0 : mergedOptions.body) && !((mergedOptions === null || mergedOptions === void 0 ? void 0 : mergedOptions.body) instanceof FormData)) {
            headersInterface.set('Content-Type', 'application/json');
        }
        const baseUrl = (_b = (_a = window.dara) === null || _a === void 0 ? void 0 : _a.base_url) !== null && _b !== void 0 ? _b : '';
        const urlString = url instanceof URL ? url.pathname + url.search : url;
        const response = yield fetch(baseUrl + urlString, Object.assign({ credentials, headers: headersInterface }, other));
        // in case of an auth error, attempt to refresh the token and retry the request
        if (response.status === 401 || response.status === 403) {
            try {
                // Ensure only one tab refreshes at a time; others wait and reuse the refreshed token.
                yield runSessionRefresh(() => __awaiter(this, void 0, void 0, function* () {
                    const refreshHeaders = new Headers({ Accept: 'application/json' });
                    const refreshResponse = yield fetch(`${baseUrl}/api/auth/refresh-token`, {
                        credentials,
                        headers: refreshHeaders,
                        method: 'POST',
                    });
                    if (refreshResponse.ok) {
                        window.dispatchEvent(new Event(SESSION_REFRESHED_EVENT));
                        return;
                    }
                    notifySessionLoggedOut();
                    // this will throw an error with the error content
                    yield validateResponse(refreshResponse, 'Request auth error, failed to refresh the session token');
                    throw new Error('Request auth error, failed to refresh the session token');
                }));
                // retry the request after refresh
                return fetch(baseUrl + urlString, Object.assign({ credentials, headers: headersInterface }, other));
            }
            catch (e) {
                // refresh failed - return the original request, the caller is supposed to handle the error
                // however they wish
                // eslint-disable-next-line no-console
                console.error('Failed to refresh token', e);
                return response;
            }
        }
        return response;
    });
}
//# sourceMappingURL=http.js.map