'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var jwtDecode = require('jwt-decode');
var crypto = require('crypto');
var coreUtil = require('@azure/core-util');
var coreRestPipeline = require('@azure/core-rest-pipeline');
var coreAuth = require('@azure/core-auth');

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
const parseToken = (token) => {
    const { exp } = jwtDecode.jwtDecode(token);
    return {
        token,
        expiresOnTimestamp: exp * 1000,
    };
};

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
const expiredToken = { token: "", expiresOnTimestamp: -10 };
const minutesToMs = (minutes) => minutes * 1000 * 60;
const defaultExpiringSoonInterval = minutesToMs(10);
const defaultRefreshAfterLifetimePercentage = 0.5;
class AutoRefreshTokenCredential {
    constructor(refreshArgs) {
        this.expiringSoonIntervalInMs = defaultExpiringSoonInterval;
        this.refreshAfterLifetimePercentage = defaultRefreshAfterLifetimePercentage;
        this.activeTokenFetching = null;
        this.activeTokenUpdating = null;
        this.disposed = false;
        const { tokenRefresher, token, refreshProactively } = refreshArgs;
        this.refresh = tokenRefresher;
        this.currentToken = token ? parseToken(token) : expiredToken;
        this.refreshProactively = refreshProactively !== null && refreshProactively !== void 0 ? refreshProactively : false;
        if (this.refreshProactively) {
            this.scheduleRefresh();
        }
    }
    async getToken(options) {
        if (!this.isTokenExpiringSoon(this.currentToken)) {
            return this.currentToken;
        }
        if (!this.isTokenValid(this.currentToken)) {
            const updatePromise = this.updateTokenAndReschedule(options === null || options === void 0 ? void 0 : options.abortSignal);
            await updatePromise;
        }
        return this.currentToken;
    }
    dispose() {
        this.disposed = true;
        this.activeTokenFetching = null;
        this.activeTokenUpdating = null;
        this.currentToken = expiredToken;
        if (this.activeTimeout) {
            clearTimeout(this.activeTimeout);
        }
    }
    async updateTokenAndReschedule(abortSignal) {
        if (this.activeTokenUpdating) {
            return this.activeTokenUpdating;
        }
        this.activeTokenUpdating = this.refreshTokenAndReschedule(abortSignal);
        try {
            await this.activeTokenUpdating;
        }
        finally {
            this.activeTokenUpdating = null;
        }
    }
    async refreshTokenAndReschedule(abortSignal) {
        const newToken = await this.refreshToken(abortSignal);
        if (!this.isTokenValid(newToken)) {
            throw new Error("The token returned from the tokenRefresher is expired.");
        }
        this.currentToken = newToken;
        if (this.refreshProactively) {
            this.scheduleRefresh();
        }
    }
    async refreshToken(abortSignal) {
        try {
            if (!this.activeTokenFetching) {
                this.activeTokenFetching = this.refresh(abortSignal);
            }
            return parseToken(await this.activeTokenFetching);
        }
        finally {
            this.activeTokenFetching = null;
        }
    }
    scheduleRefresh() {
        if (this.disposed) {
            return;
        }
        if (this.activeTimeout) {
            clearTimeout(this.activeTimeout);
        }
        const tokenTtlInMs = this.currentToken.expiresOnTimestamp - Date.now();
        let timespanInMs = null;
        if (this.isTokenExpiringSoon(this.currentToken)) {
            // Schedule the next refresh for when it reaches a certain percentage of the remaining lifetime.
            timespanInMs = tokenTtlInMs * this.refreshAfterLifetimePercentage;
        }
        else {
            // Schedule the next refresh for when it gets in to the soon-to-expire window.
            timespanInMs = tokenTtlInMs - this.expiringSoonIntervalInMs;
        }
        this.activeTimeout = setTimeout(() => this.updateTokenAndReschedule(), timespanInMs);
    }
    isTokenValid(token) {
        return token && Date.now() < token.expiresOnTimestamp;
    }
    isTokenExpiringSoon(token) {
        return !token || Date.now() >= token.expiresOnTimestamp - this.expiringSoonIntervalInMs;
    }
}

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
 * StaticTokenCredential
 */
class StaticTokenCredential {
    constructor(token) {
        this.token = token;
    }
    async getToken() {
        return this.token;
    }
    dispose() {
        /* intentionally empty */
    }
}

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
 * The CommunicationTokenCredential implementation with support for proactive token refresh.
 */
class AzureCommunicationTokenCredential {
    constructor(tokenOrRefreshOptions) {
        this.disposed = false;
        if (typeof tokenOrRefreshOptions === "string") {
            this.tokenCredential = new StaticTokenCredential(parseToken(tokenOrRefreshOptions));
        }
        else {
            this.tokenCredential = new AutoRefreshTokenCredential(tokenOrRefreshOptions);
        }
    }
    /**
     * Gets an `AccessToken` for the user. Throws if already disposed.
     * @param abortSignal - An implementation of `AbortSignalLike` to cancel the operation.
     */
    async getToken(options) {
        this.throwIfDisposed();
        const token = await this.tokenCredential.getToken(options);
        this.throwIfDisposed();
        return token;
    }
    /**
     * Disposes the CommunicationTokenCredential and cancels any internal auto-refresh operation.
     */
    dispose() {
        this.disposed = true;
        this.tokenCredential.dispose();
    }
    throwIfDisposed() {
        if (this.disposed) {
            throw new Error("User credential is disposed");
        }
    }
}

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
const shaHash = async (content) => crypto.createHash("sha256").update(content).digest("base64");
const shaHMAC = async (secret, content) => {
    const decodedSecret = Buffer.from(secret, "base64");
    return crypto.createHmac("sha256", decodedSecret).update(content).digest("base64");
};

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
 * CommunicationKeyCredentialPolicy provides a means of signing requests made through
 * the SmsClient.
 */
const communicationAccessKeyCredentialPolicy = "CommunicationAccessKeyCredentialPolicy";
/**
 * Creates an HTTP pipeline policy to authenticate a request using a `KeyCredential`.
 * @hidden
 *
 * @param credential - The key credential.
 */
function createCommunicationAccessKeyCredentialPolicy(credential) {
    return {
        name: communicationAccessKeyCredentialPolicy,
        async sendRequest(request, next) {
            var _a;
            const verb = request.method.toUpperCase();
            const utcNow = new Date().toUTCString();
            const contentHash = await shaHash(((_a = request.body) === null || _a === void 0 ? void 0 : _a.toString()) || "");
            const dateHeader = "x-ms-date";
            const signedHeaders = `${dateHeader};host;x-ms-content-sha256`;
            const url = new URL(request.url);
            const query = url.searchParams.toString();
            const urlPathAndQuery = query ? `${url.pathname}?${query}` : url.pathname;
            const port = url.port;
            const hostAndPort = port ? `${url.host}:${port}` : url.host;
            const stringToSign = `${verb}\n${urlPathAndQuery}\n${utcNow};${hostAndPort};${contentHash}`;
            const signature = await shaHMAC(credential.key, stringToSign);
            if (coreUtil.isNode) {
                request.headers.set("Host", hostAndPort || "");
            }
            request.headers.set(dateHeader, utcNow);
            request.headers.set("x-ms-content-sha256", contentHash);
            request.headers.set("Authorization", `HMAC-SHA256 SignedHeaders=${signedHeaders}&Signature=${signature}`);
            return next(request);
        },
    };
}

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
 * Creates a pipeline policy to authenticate request based
 * on the credential passed in.
 * @hidden
 *
 * @param credential - The KeyCredential or TokenCredential.
 */
function createCommunicationAuthPolicy(credential) {
    if (coreAuth.isTokenCredential(credential)) {
        const policyOptions = {
            credential: credential,
            scopes: ["https://communication.azure.com//.default"],
        };
        return coreRestPipeline.bearerTokenAuthenticationPolicy(policyOptions);
    }
    else {
        return createCommunicationAccessKeyCredentialPolicy(credential);
    }
}

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// TODO: update when connection string format is finalized
const CONNECTION_STRING_REGEX = /endpoint=(.*);accesskey=(.*)/i;
const tryParseConnectionString = (s) => {
    const match = s.match(CONNECTION_STRING_REGEX);
    if ((match === null || match === void 0 ? void 0 : match[1]) && match[2]) {
        return { endpoint: match[1], credential: new coreAuth.AzureKeyCredential(match[2]) };
    }
    return undefined;
};
/**
 * Returns an EndpointCredential to easily access properties of the connection string.
 * @hidden
 *
 * @param connectionString - The connection string to parse
 * @returns Object to access the endpoint and the credentials
 */
const parseConnectionString = (connectionString) => {
    const parsedConnectionString = tryParseConnectionString(connectionString);
    if (parsedConnectionString) {
        return parsedConnectionString;
    }
    else {
        throw new Error(`Invalid connection string ${connectionString}`);
    }
};

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
const isValidEndpoint = (host) => {
    var _a;
    const url = new URL(host);
    return (!!((_a = url.protocol) === null || _a === void 0 ? void 0 : _a.match(/^http[s]?/)) &&
        url.host !== undefined &&
        url.host !== "" &&
        (url.pathname === undefined || url.pathname === "" || url.pathname === "/"));
};
const assertValidEndpoint = (host) => {
    if (!isValidEndpoint(host)) {
        throw new Error(`Invalid endpoint url ${host}`);
    }
};
/**
 * Checks whether a value is a KeyCredential.
 *
 * @param credential - The credential being checked.
 */
const isKeyCredential = (credential) => {
    const castCredential = credential;
    return (castCredential &&
        typeof castCredential.key === "string" &&
        castCredential.getToken === undefined);
};
/**
 * Parses arguments passed to a communication client.
 * @hidden
 */
const parseClientArguments = (connectionStringOrUrl, credentialOrOptions) => {
    if (isKeyCredential(credentialOrOptions) || coreAuth.isTokenCredential(credentialOrOptions)) {
        assertValidEndpoint(connectionStringOrUrl);
        return { url: connectionStringOrUrl, credential: credentialOrOptions };
    }
    else {
        const { endpoint: host, credential } = parseConnectionString(connectionStringOrUrl);
        assertValidEndpoint(host);
        return { url: host, credential };
    }
};

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
 * Tests an Identifier to determine whether it implements CommunicationUserIdentifier.
 *
 * @param identifier - The assumed CommunicationUserIdentifier to be tested.
 */
const isCommunicationUserIdentifier = (identifier) => {
    return typeof identifier.communicationUserId === "string";
};
/**
 * Tests an Identifier to determine whether it implements PhoneNumberIdentifier.
 *
 * @param identifier - The assumed PhoneNumberIdentifier to be tested.
 */
const isPhoneNumberIdentifier = (identifier) => {
    return typeof identifier.phoneNumber === "string";
};
/**
 * Tests an Identifier to determine whether it implements MicrosoftTeamsUserIdentifier.
 *
 * @param identifier - The assumed available to be tested.
 */
const isMicrosoftTeamsUserIdentifier = (identifier) => {
    return typeof identifier.microsoftTeamsUserId === "string";
};
/**
 * Tests an Identifier to determine whether it implements MicrosoftTeamsAppIdentifier.
 *
 * @param identifier - The assumed available to be tested.
 */
const isMicrosoftTeamsAppIdentifier = (identifier) => {
    return typeof identifier.teamsAppId === "string";
};
/**
 * Tests an Identifier to determine whether it implements UnknownIdentifier.
 *
 * @param identifier - The assumed UnknownIdentifier to be tested.
 */
const isUnknownIdentifier = (identifier) => {
    return typeof identifier.id === "string";
};
/**
 * Returns the CommunicationIdentifierKind for a given CommunicationIdentifier. Returns undefined if the kind couldn't be inferred.
 *
 * @param identifier - The identifier whose kind is to be inferred.
 */
const getIdentifierKind = (identifier) => {
    if (isCommunicationUserIdentifier(identifier)) {
        return Object.assign(Object.assign({}, identifier), { kind: "communicationUser" });
    }
    if (isPhoneNumberIdentifier(identifier)) {
        return Object.assign(Object.assign({}, identifier), { kind: "phoneNumber" });
    }
    if (isMicrosoftTeamsUserIdentifier(identifier)) {
        return Object.assign(Object.assign({}, identifier), { kind: "microsoftTeamsUser" });
    }
    if (isMicrosoftTeamsAppIdentifier(identifier)) {
        return Object.assign(Object.assign({}, identifier), { kind: "microsoftTeamsApp" });
    }
    return Object.assign(Object.assign({}, identifier), { kind: "unknown" });
};
/**
 * Returns the rawId for a given CommunicationIdentifier. You can use the rawId for encoding the identifier and then use it as a key in a database.
 *
 * @param identifier - The identifier to be translated to its rawId.
 */
const getIdentifierRawId = (identifier) => {
    const identifierKind = getIdentifierKind(identifier);
    switch (identifierKind.kind) {
        case "communicationUser":
            return identifierKind.communicationUserId;
        case "microsoftTeamsUser": {
            const { microsoftTeamsUserId, rawId, cloud, isAnonymous } = identifierKind;
            if (rawId)
                return rawId;
            if (isAnonymous)
                return `8:teamsvisitor:${microsoftTeamsUserId}`;
            switch (cloud) {
                case "dod":
                    return `8:dod:${microsoftTeamsUserId}`;
                case "gcch":
                    return `8:gcch:${microsoftTeamsUserId}`;
                case "public":
                    return `8:orgid:${microsoftTeamsUserId}`;
            }
            return `8:orgid:${microsoftTeamsUserId}`;
        }
        case "microsoftTeamsApp": {
            const { teamsAppId, rawId, cloud } = identifierKind;
            if (rawId)
                return rawId;
            switch (cloud) {
                case "dod":
                    return `28:dod:${teamsAppId}`;
                case "gcch":
                    return `28:gcch:${teamsAppId}`;
            }
            return `28:orgid:${teamsAppId}`;
        }
        case "phoneNumber": {
            const { phoneNumber, rawId } = identifierKind;
            if (rawId)
                return rawId;
            return `4:${phoneNumber}`;
        }
        case "unknown": {
            return identifierKind.id;
        }
    }
};
const buildMicrosoftTeamsAppIdentifier = (teamsAppId, cloud) => {
    return {
        kind: "microsoftTeamsApp",
        teamsAppId: teamsAppId,
        cloud: cloud,
    };
};
const buildMicrosoftTeamsUserIdentifier = (id, cloud, isAnonymous) => {
    return {
        kind: "microsoftTeamsUser",
        microsoftTeamsUserId: id,
        isAnonymous: isAnonymous,
        cloud: cloud,
    };
};
/**
 * Creates a CommunicationIdentifierKind from a given rawId. When storing rawIds use this function to restore the identifier that was encoded in the rawId.
 *
 * @param rawId - The rawId to be translated to its identifier representation.
 */
const createIdentifierFromRawId = (rawId) => {
    if (rawId.startsWith("4:")) {
        return { kind: "phoneNumber", phoneNumber: `${rawId.substring("4:".length)}` };
    }
    const segments = rawId.split(":");
    if (segments.length !== 3) {
        return { kind: "unknown", id: rawId };
    }
    const prefix = `${segments[0]}:${segments[1]}:`;
    const suffix = segments[2];
    switch (prefix) {
        case "8:teamsvisitor:":
            return { kind: "microsoftTeamsUser", microsoftTeamsUserId: suffix, isAnonymous: true };
        case "8:orgid:":
            return buildMicrosoftTeamsUserIdentifier(suffix, "public", false);
        case "8:dod:":
            return buildMicrosoftTeamsUserIdentifier(suffix, "dod", false);
        case "8:gcch:":
            return buildMicrosoftTeamsUserIdentifier(suffix, "gcch", false);
        case "8:acs:":
        case "8:spool:":
        case "8:dod-acs:":
        case "8:gcch-acs:":
            return { kind: "communicationUser", communicationUserId: rawId };
        case "28:orgid:":
            return buildMicrosoftTeamsAppIdentifier(suffix, "public");
        case "28:gcch:":
            return buildMicrosoftTeamsAppIdentifier(suffix, "gcch");
        case "28:dod:":
            return buildMicrosoftTeamsAppIdentifier(suffix, "dod");
    }
    return { kind: "unknown", id: rawId };
};

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
const assertNotNullOrUndefined = (obj, prop) => {
    const subObjName = Object.keys(obj)[0];
    const subObj = obj[subObjName];
    if (prop in subObj) {
        return subObj[prop];
    }
    throw new Error(`Property ${prop} is required for identifier of type ${subObjName}.`);
};
const assertMaximumOneNestedModel = (identifier) => {
    const presentProperties = [];
    if (identifier.communicationUser !== undefined) {
        presentProperties.push("communicationUser");
    }
    if (identifier.microsoftTeamsUser !== undefined) {
        presentProperties.push("microsoftTeamsUser");
    }
    if (identifier.microsoftTeamsApp !== undefined) {
        presentProperties.push("microsoftTeamsApp");
    }
    if (identifier.phoneNumber !== undefined) {
        presentProperties.push("phoneNumber");
    }
    if (presentProperties.length > 1) {
        throw new Error(`Only one of the properties in ${JSON.stringify(presentProperties)} should be present.`);
    }
};
/**
 * @hidden
 * Translates a CommunicationIdentifier to its serialized format for sending a request.
 * @param identifier - The CommunicationIdentifier to be serialized.
 */
const serializeCommunicationIdentifier = (identifier) => {
    var _a, _b, _c, _d, _e, _f;
    const identifierKind = getIdentifierKind(identifier);
    switch (identifierKind.kind) {
        case "communicationUser":
            return {
                rawId: getIdentifierRawId(identifierKind),
                communicationUser: { id: identifierKind.communicationUserId },
            };
        case "phoneNumber":
            return {
                rawId: (_a = identifierKind.rawId) !== null && _a !== void 0 ? _a : getIdentifierRawId(identifierKind),
                phoneNumber: {
                    value: identifierKind.phoneNumber,
                },
            };
        case "microsoftTeamsUser":
            return {
                rawId: (_b = identifierKind.rawId) !== null && _b !== void 0 ? _b : getIdentifierRawId(identifierKind),
                microsoftTeamsUser: {
                    userId: identifierKind.microsoftTeamsUserId,
                    isAnonymous: (_c = identifierKind.isAnonymous) !== null && _c !== void 0 ? _c : false,
                    cloud: (_d = identifierKind.cloud) !== null && _d !== void 0 ? _d : "public",
                },
            };
        case "microsoftTeamsApp":
            return {
                rawId: (_e = identifierKind.rawId) !== null && _e !== void 0 ? _e : getIdentifierRawId(identifierKind),
                microsoftTeamsApp: {
                    appId: identifierKind.teamsAppId,
                    cloud: (_f = identifierKind.cloud) !== null && _f !== void 0 ? _f : "public",
                },
            };
        case "unknown":
            return { rawId: identifierKind.id };
        default:
            throw new Error(`Can't serialize an identifier with kind ${identifierKind.kind}`);
    }
};
const getKind = (serializedIdentifier) => {
    if (serializedIdentifier.communicationUser) {
        return "communicationUser";
    }
    if (serializedIdentifier.phoneNumber) {
        return "phoneNumber";
    }
    if (serializedIdentifier.microsoftTeamsUser) {
        return "microsoftTeamsUser";
    }
    if (serializedIdentifier.microsoftTeamsApp) {
        return "microsoftTeamsApp";
    }
    return "unknown";
};
/**
 * @hidden
 * Translates the serialized format of a communication identifier to CommunicationIdentifier.
 * @param serializedIdentifier - The SerializedCommunicationIdentifier to be deserialized.
 */
const deserializeCommunicationIdentifier = (serializedIdentifier) => {
    var _a;
    assertMaximumOneNestedModel(serializedIdentifier);
    const { communicationUser, microsoftTeamsUser, microsoftTeamsApp, phoneNumber } = serializedIdentifier;
    const kind = (_a = serializedIdentifier.kind) !== null && _a !== void 0 ? _a : getKind(serializedIdentifier);
    if (kind === "communicationUser" && communicationUser) {
        return {
            kind: "communicationUser",
            communicationUserId: assertNotNullOrUndefined({ communicationUser }, "id"),
        };
    }
    if (kind === "phoneNumber" && phoneNumber) {
        return {
            kind: "phoneNumber",
            phoneNumber: assertNotNullOrUndefined({ phoneNumber }, "value"),
            rawId: assertNotNullOrUndefined({ phoneNumber: serializedIdentifier }, "rawId"),
        };
    }
    if (kind === "microsoftTeamsUser" && microsoftTeamsUser) {
        return {
            kind: "microsoftTeamsUser",
            microsoftTeamsUserId: assertNotNullOrUndefined({ microsoftTeamsUser }, "userId"),
            isAnonymous: assertNotNullOrUndefined({ microsoftTeamsUser }, "isAnonymous"),
            cloud: assertNotNullOrUndefined({ microsoftTeamsUser }, "cloud"),
            rawId: assertNotNullOrUndefined({ microsoftTeamsUser: serializedIdentifier }, "rawId"),
        };
    }
    if (kind === "microsoftTeamsApp" && microsoftTeamsApp) {
        return {
            kind: "microsoftTeamsApp",
            teamsAppId: assertNotNullOrUndefined({ microsoftTeamsApp }, "appId"),
            cloud: assertNotNullOrUndefined({ microsoftTeamsApp }, "cloud"),
            rawId: assertNotNullOrUndefined({ microsoftTeamsApp: serializedIdentifier }, "rawId"),
        };
    }
    return {
        kind: "unknown",
        id: assertNotNullOrUndefined({ unknown: serializedIdentifier }, "rawId"),
    };
};

exports.AzureCommunicationTokenCredential = AzureCommunicationTokenCredential;
exports.createCommunicationAccessKeyCredentialPolicy = createCommunicationAccessKeyCredentialPolicy;
exports.createCommunicationAuthPolicy = createCommunicationAuthPolicy;
exports.createIdentifierFromRawId = createIdentifierFromRawId;
exports.deserializeCommunicationIdentifier = deserializeCommunicationIdentifier;
exports.getIdentifierKind = getIdentifierKind;
exports.getIdentifierRawId = getIdentifierRawId;
exports.isCommunicationUserIdentifier = isCommunicationUserIdentifier;
exports.isKeyCredential = isKeyCredential;
exports.isMicrosoftTeamsAppIdentifier = isMicrosoftTeamsAppIdentifier;
exports.isMicrosoftTeamsUserIdentifier = isMicrosoftTeamsUserIdentifier;
exports.isPhoneNumberIdentifier = isPhoneNumberIdentifier;
exports.isUnknownIdentifier = isUnknownIdentifier;
exports.parseClientArguments = parseClientArguments;
exports.parseConnectionString = parseConnectionString;
exports.serializeCommunicationIdentifier = serializeCommunicationIdentifier;
//# sourceMappingURL=index.js.map
