"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppServiceRegistration = void 0;
const crypto_1 = require("crypto");
const js_yaml_1 = __importDefault(require("js-yaml"));
const fs_1 = __importDefault(require("fs"));
class AppServiceRegistration {
    constructor(url) {
        this.url = url;
        /**
         * Construct a new application service registration.
         * @constructor
         * @param {string} appServiceUrl The base URL the AS can be reached via.
         */
        this.id = null;
        this.hsToken = null;
        this.asToken = null;
        this.senderLocalpart = null;
        this.rateLimited = undefined;
        /**
         * **Experimental**
         * Signal to the homeserver that this appservice will accept ephemeral events.
         */
        this.pushEphemeral = undefined;
        this.namespaces = {};
        this.protocols = null;
        this.cachedRegex = {};
    }
    /**
     * Generate a random token.
     * @return {string} A randomly generated token.
     */
    static generateToken() {
        return (0, crypto_1.randomBytes)(32).toString('hex');
    }
    /**
 * Convert a JSON object to an AppServiceRegistration object.
 * @static
 * @param obj The registration object
 * @return The registration.
 */
    static fromObject(obj) {
        const reg = new AppServiceRegistration(obj.url);
        reg.setId(obj.id);
        reg.setHomeserverToken(obj.hs_token);
        reg.setAppServiceToken(obj.as_token);
        reg.setSenderLocalpart(obj.sender_localpart);
        if (obj.rate_limited !== undefined)
            reg.setRateLimited(obj.rate_limited);
        if (obj.protocols)
            reg.setProtocols(obj.protocols);
        reg.pushEphemeral = obj["de.sorunome.msc2409.push_ephemeral"];
        if (obj.namespaces) {
            const kinds = ["users", "aliases", "rooms"];
            for (const kind of kinds) {
                const namespace = obj.namespaces[kind];
                if (!namespace) {
                    continue;
                }
                namespace.forEach((regexObj) => {
                    reg.addRegexPattern(kind, regexObj.regex, regexObj.exclusive);
                });
            }
        }
        return reg;
    }
    /**
     * Set the URL which the home server will hit in order to talk to the AS.
     * @param {string} url The application service url
     */
    setAppServiceUrl(url) {
        this.url = url;
    }
    /**
     * Get the URL which the home server will hit in order to talk to the AS.
     */
    getAppServiceUrl() {
        return this.url;
    }
    /**
     * Set the ID of the appservice; must be unique across the homeserver and never change.
     * @param {string} id The ID
     */
    setId(id) {
        this.id = id;
    }
    /**
     * Get the ID of the appservice.
     * @return {?string} The ID
     */
    getId() {
        return this.id;
    }
    /**
     * Set the list of protocols that this appservice will serve for third party lookups.
     * @param {string[]} protocols The protocols
     */
    setProtocols(protocols) {
        this.protocols = protocols;
    }
    /**
     * Get the list of protocols that this appservice will serve for third party lookups.
     * Will return null if no protocols have been set.
     * @return {string[]} The protocols.
     */
    getProtocols() {
        return this.protocols;
    }
    /**
     * Set the token the homeserver will use to communicate with the app service.
     * @param {string} token The token
     */
    setHomeserverToken(token) {
        this.hsToken = token;
    }
    /**
     * Get the token the homeserver will use to communicate with the app service.
     * @return {?string} The token
     */
    getHomeserverToken() {
        return this.hsToken;
    }
    /**
     * Set the token the app service will use to communicate with the homeserver.
     * @param {string} token The token
     */
    setAppServiceToken(token) {
        this.asToken = token;
    }
    /**
     * Get the token the app service will use to communicate with the homeserver.
     * @return {?string} The token
     */
    getAppServiceToken() {
        return this.asToken;
    }
    /**
     * Set the desired user_id localpart for the app service itself.
     * @param {string} localpart The user_id localpart ("alice" in "@alice:domain")
     */
    setSenderLocalpart(localpart) {
        this.senderLocalpart = localpart;
    }
    /**
     * Get whether requests from this AS are rate-limited by the home server.
     */
    isRateLimited() {
        return this.rateLimited === undefined ? true : this.rateLimited;
    }
    /**
     * Set whether requests from this AS are rate-limited by the home server.
     * @param {boolean} isRateLimited The flag which is set to true to enable rate
     * rate limiting, false to disable.
     */
    setRateLimited(isRateLimited) {
        this.rateLimited = isRateLimited;
    }
    /**
     * **Experimental**
     *
     * Should the appservice receive ephemeral events. Note this requires
     * a homeserver implementing MSC2409.
     */
    pushEphemeralEnabled() {
        return this.pushEphemeral || false;
    }
    /**
     * Get the desired user_id localpart for the app service itself.
     * @return {?string} The user_id localpart ("alice" in "@alice:domain")
     */
    getSenderLocalpart() {
        return this.senderLocalpart;
    }
    /**
     * Add a regex pattern to be registered.
     * @param {String} type : The type of regex pattern. Must be 'users', 'rooms', or
     * 'aliases'.
     * @param {String} regex : The regex pattern.
     * @param {Boolean} exclusive : True to reserve the matched namespace.
     * @throws If given an invalid type or regex.
     */
    addRegexPattern(type, regex, exclusive) {
        if (typeof regex != "string") {
            throw new Error("Regex must be a string");
        }
        if (["users", "aliases", "rooms"].indexOf(type) == -1) {
            throw new Error("'type' must be 'users', 'rooms' or 'aliases'");
        }
        const regexObject = {
            exclusive: Boolean(exclusive),
            regex: regex
        };
        const namespace = this.namespaces[type];
        if (namespace) {
            namespace.push(regexObject);
        }
        else {
            this.namespaces[type] = [regexObject];
        }
    }
    /**
     * Output this registration to the given file name.
     * @param {String} filename The file name to write the yaml to.
     * @throws If required fields hs_token, as_token, url are missing.
     */
    outputAsYaml(filename) {
        const reg = this.getOutput();
        fs_1.default.writeFileSync(filename, js_yaml_1.default.dump(reg));
    }
    /**
     * Get the key-value output which should be written to a YAML file.
     * @throws If required fields hs_token, as-token, url, sender_localpart are missing.
     */
    getOutput() {
        // Typescript will default any string array to a set of strings, even if it's a static array.
        const requiredFields = [
            "id", "hsToken", "asToken", "senderLocalpart"
        ];
        const missingFields = requiredFields.filter((key) => !this[key]);
        if (missingFields.length) {
            throw new Error(`Missing required field(s): ${missingFields}`);
        }
        const responseFormat = {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            id: this.id,
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            hs_token: this.hsToken,
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            as_token: this.asToken,
            url: this.url,
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            sender_localpart: this.senderLocalpart,
        };
        if (this.pushEphemeral !== undefined) {
            responseFormat["de.sorunome.msc2409.push_ephemeral"] = this.pushEphemeral;
        }
        if (this.protocols) {
            responseFormat.protocols = this.protocols;
        }
        if (Object.keys(this.namespaces).length > 0) {
            responseFormat.namespaces = this.namespaces;
        }
        if (this.rateLimited !== undefined) {
            responseFormat.rate_limited = this.rateLimited;
        }
        return responseFormat;
    }
    /**
     * Check if a user_id meets this registration regex.
     * @param {string} userId The user ID
     * @param {boolean} onlyExclusive True to restrict matching to only exclusive
     * regexes. False to allow exclusive or non-exlusive regexes to match.
     * @return {boolean} True if there is a match.
     */
    isUserMatch(userId, onlyExclusive) {
        return this._isMatch(this.namespaces.users, userId, onlyExclusive);
    }
    /**
     * Check if a room alias meets this registration regex.
     * @param {string} alias The room alias
     * @param {boolean} onlyExclusive True to restrict matching to only exclusive
     * regexes. False to allow exclusive or non-exlusive regexes to match.
     * @return {boolean} True if there is a match.
     */
    isAliasMatch(alias, onlyExclusive) {
        return this._isMatch(this.namespaces.aliases, alias, onlyExclusive);
    }
    /**
     * Check if a room ID meets this registration regex.
     * @param {string} roomId The room ID
     * @param {boolean} onlyExclusive True to restrict matching to only exclusive
     * regexes. False to allow exclusive or non-exlusive regexes to match.
     * @return {boolean} True if there is a match.
     */
    isRoomMatch(roomId, onlyExclusive) {
        return this._isMatch(this.namespaces.rooms, roomId, onlyExclusive);
    }
    _isMatch(regexList, sample, onlyExclusive) {
        if (!regexList) {
            return false;
        }
        onlyExclusive = Boolean(onlyExclusive);
        for (const regexObj of regexList) {
            let regex = this.cachedRegex[regexObj.regex];
            if (!regex) {
                regex = new RegExp(regexObj.regex);
                this.cachedRegex[regexObj.regex] = regex;
            }
            if (regex.test(sample)) {
                if (onlyExclusive && !regexObj.exclusive) {
                    continue;
                }
                return true;
            }
        }
        return false;
    }
}
exports.AppServiceRegistration = AppServiceRegistration;
