/*
 * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
 */
import { stringToBase64 } from "../lib/base64.js";
import { env } from "../lib/env.js";
import { parse } from "../lib/schemas.js";
import * as components from "../models/components/index.js";
export class ClientCredentialsHook {
    constructor() {
        this.sessions = {};
    }
    sdkInit(opts) {
        this.client = opts.client;
        return opts;
    }
    async beforeRequest(hookCtx, request) {
        if (!hookCtx.oAuth2Scopes) {
            // OAuth2 not in use
            return request;
        }
        const credentials = await this.getCredentials(hookCtx);
        if (!credentials || Object.values(credentials).some(v => v === "")) {
            return request;
        }
        const sessionKey = this.getSessionKey(credentials);
        let session = this.sessions[sessionKey];
        if (!session
            || !this.hasRequiredScopes(session.scopes, hookCtx.oAuth2Scopes)
            || this.hasTokenExpired(session.expiresAt)) {
            session = await this.doTokenRequest(hookCtx, credentials, this.getScopes(hookCtx.oAuth2Scopes, session));
            this.sessions[sessionKey] = session;
        }
        request.headers.set("Authorization", `Bearer ${session.token}`);
        return request;
    }
    async afterError(hookCtx, response, error) {
        if (!hookCtx.oAuth2Scopes) {
            // OAuth2 not in use
            return { response, error };
        }
        if (error) {
            return { response, error };
        }
        const credentials = await this.getCredentials(hookCtx);
        if (!credentials || Object.values(credentials).some(v => v === "")) {
            return { response, error };
        }
        if (response && response?.status === 401) {
            const sessionKey = this.getSessionKey(credentials);
            delete this.sessions[sessionKey];
        }
        return { response, error };
    }
    async doTokenRequest(hookCtx, credentials, scopes) {
        const formData = new URLSearchParams();
        formData.append("grant_type", "client_credentials");
        const headers = {
            "Content-Type": "application/x-www-form-urlencoded",
        };
        formData.append("client_id", credentials.clientID);
        formData.append("client_secret", credentials.clientSecret);
        if (scopes.length > 0) {
            formData.append("scope", scopes.join(" "));
        }
        if (!hookCtx.baseURL?.toString()) {
            throw new Error("Assertion error: Base URL cannot be empty");
        }
        const tokenURL = new URL(credentials.tokenURL ?? "", hookCtx.baseURL);
        const request = new Request(tokenURL, {
            method: "POST",
            headers,
            body: formData,
        });
        const res = await this.client?.request(request);
        if (!res) {
            throw new Error("Failed to fetch token");
        }
        if (res.status < 200 || res.status >= 300) {
            throw new Error("Unexpected status code");
        }
        const data = await res.json();
        if (!data || typeof data !== "object") {
            throw new Error("Unexpected response format");
        }
        if (data.token_type !== "Bearer") {
            throw new Error("Unexpected token type");
        }
        let expiresAt;
        if (data.expires_in) {
            expiresAt = Date.now() + data.expires_in * 1000;
        }
        const sess = {
            credentials,
            token: data.access_token,
            scopes,
        };
        if (expiresAt !== undefined) {
            sess.expiresAt = expiresAt;
        }
        return sess;
    }
    async getCredentials(hookCtx) {
        const source = hookCtx.securitySource;
        if (!source) {
            return null;
        }
        let security = source;
        if (typeof source === "function") {
            security = await source();
        }
        return this.getCredentialsGlobal(security);
    }
    async getCredentialsGlobal(security) {
        const out = parse(security, (val) => components.Security$outboundSchema.parse(val), "unexpected security type");
        return {
            clientID: out?.clientID ?? env().ONEROSTER_CLIENT_ID ?? "",
            clientSecret: out?.clientSecret ?? env().ONEROSTER_CLIENT_SECRET ?? "",
            tokenURL: out?.tokenURL ?? env().ONEROSTER_TOKEN_URL ?? "",
        };
    }
    getSessionKey(credentials) {
        const key = `${credentials.clientID}:${credentials.clientSecret}`;
        return stringToBase64(key);
    }
    hasRequiredScopes(scopes, requiredScopes) {
        return requiredScopes.every((scope) => scopes.includes(scope));
    }
    getScopes(requiredScopes, sess) {
        return [...new Set((sess?.scopes ?? []).concat(requiredScopes))];
    }
    hasTokenExpired(expiresAt) {
        return !expiresAt || Date.now() + 60000 > expiresAt;
    }
}
//# sourceMappingURL=clientcredentials.js.map