"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebhookEmitter = void 0;
const node_crypto_1 = require("node:crypto");
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const jose_1 = require("jose");
const global_logger_factory_1 = require("global-logger-factory");
const NotImplementedHttpError_1 = require("../../../util/errors/NotImplementedHttpError");
const PathUtil_1 = require("../../../util/PathUtil");
const StreamUtil_1 = require("../../../util/StreamUtil");
const NotificationEmitter_1 = require("../NotificationEmitter");
const WebhookChannel2023Type_1 = require("./WebhookChannel2023Type");
/**
 * Emits a notification representation using the WebhookChannel2023 specification.
 *
 * At the time of writing it is not specified how exactly a notification sender should make its requests verifiable,
 * so for now we use a token similar to those from Solid-OIDC, signed by the server itself.
 *
 * Generates a DPoP token and proof, and adds those to the HTTP request that is sent to the target.
 *
 * The `expiration` input parameter is how long the generated token should be valid in minutes.
 * Default is 20.
 */
class WebhookEmitter extends NotificationEmitter_1.NotificationEmitter {
    logger = (0, global_logger_factory_1.getLoggerFor)(this);
    issuer;
    webId;
    jwkGenerator;
    expiration;
    constructor(baseUrl, webIdRoute, jwkGenerator, expiration = 20) {
        super();
        this.issuer = (0, PathUtil_1.trimTrailingSlashes)(baseUrl);
        this.webId = webIdRoute.getPath();
        this.jwkGenerator = jwkGenerator;
        this.expiration = expiration * 60 * 1000;
    }
    async canHandle({ channel }) {
        if (!(0, WebhookChannel2023Type_1.isWebhook2023Channel)(channel)) {
            throw new NotImplementedHttpError_1.NotImplementedHttpError(`${channel.id} is not a WebhookChannel2023 channel.`);
        }
    }
    async handle({ channel, representation }) {
        // Cast was checked in `canHandle`
        const webhookChannel = channel;
        this.logger.debug(`Emitting Webhook notification with target ${webhookChannel.sendTo}`);
        const privateKey = await this.jwkGenerator.getPrivateKey();
        const publicKey = await this.jwkGenerator.getPublicKey();
        const privateKeyObject = await (0, jose_1.importJWK)(privateKey);
        // Make sure both header and proof have the same timestamp
        const time = Date.now();
        // Currently the spec does not define how the notification sender should identify.
        // The format used here has been chosen to be similar
        // to how ID tokens are described in the Solid-OIDC specification for consistency.
        const dpopToken = await new jose_1.SignJWT({
            webid: this.webId,
            azp: this.webId,
            sub: this.webId,
            cnf: {
                jkt: await (0, jose_1.calculateJwkThumbprint)(publicKey, 'sha256'),
            },
        }).setProtectedHeader({ alg: privateKey.alg })
            .setIssuedAt(time)
            .setExpirationTime(time + this.expiration)
            .setAudience([this.webId, 'solid'])
            .setIssuer(this.issuer)
            .setJti((0, node_crypto_1.randomUUID)())
            .sign(privateKeyObject);
        // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop#section-4.2
        const dpopProof = await new jose_1.SignJWT({
            htu: webhookChannel.sendTo,
            htm: 'POST',
        }).setProtectedHeader({ alg: privateKey.alg, jwk: publicKey, typ: 'dpop+jwt' })
            .setIssuedAt(time)
            .setJti((0, node_crypto_1.randomUUID)())
            .sign(privateKeyObject);
        const response = await (0, cross_fetch_1.default)(webhookChannel.sendTo, {
            method: 'POST',
            headers: {
                // eslint-disable-next-line @typescript-eslint/naming-convention
                'content-type': representation.metadata.contentType,
                authorization: `DPoP ${dpopToken}`,
                dpop: dpopProof,
            },
            body: await (0, StreamUtil_1.readableToString)(representation.data),
        });
        if (response.status >= 400) {
            this.logger.error(`There was an issue emitting a Webhook notification with target ${webhookChannel.sendTo}: ${await response.text()}`);
        }
    }
}
exports.WebhookEmitter = WebhookEmitter;
//# sourceMappingURL=WebhookEmitter.js.map