"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const grpc_1 = require("grpc");
const util_1 = require("util");
const ENVOY_DEFAULT_EGRESS_PORT = 12345;
const ENVOY_DEFAULT_EGRESS_ADDR = "127.0.0.1";
const ENVOY_EGRESS_PORT = parseInt(process.env.ENVOY_EGRESS_PORT || `${ENVOY_DEFAULT_EGRESS_PORT}`, 10);
const ENVOY_EGRESS_ADDR = process.env.ENVOY_EGRESS_ADDR || ENVOY_DEFAULT_EGRESS_ADDR;
const X_B3_TRACEID = "x-b3-traceid";
const X_B3_SPANID = "x-b3-spanid";
const X_B3_PARENTSPANID = "x-b3-parentspanid";
const X_B3_SAMPLED = "x-b3-sampled";
const X_B3_FLAGS = "x-b3-flags";
const X_OT_SPAN_CONTEXT = "x-ot-span-context";
const X_REQUEST_ID = "x-request-id";
const X_CLIENT_TRACE_ID = "x-client-trace-id";
const X_ENVOY_EXPECTED_RQ_TIMEOUT_MS = "x-envoy-expected-rq-timeout-ms";
/**
 * the header returned by envoy telling upstream is overloaded
 * @internal
 */
exports.X_ENVOY_OVERLOADED = "x-envoy-overloaded";
/**
 * the header returned by envoy telling upstream duration
 * @internal
 */
exports.X_ENVOY_UPSTREAM_SERVICE_TIME = "x-envoy-upstream-service-time";
/**
 * the header set in envoy config for telling this library egress port
 */
exports.X_TUBI_ENVOY_EGRESS_PORT = "x-tubi-envoy-egress-port";
/**
 * the header set in envoy config for telling this library egress address
 */
exports.X_TUBI_ENVOY_EGRESS_ADDR = "x-tubi-envoy-egress-addr";
/**
 * the optional header set in envoy config for telling a host is managed by envoy
 * so that this library can route envoy or call directly accordingly
 */
exports.X_TUBI_ENVOY_MANAGED_HOST = "x-tubi-envoy-managed-host";
/**
 * read value of the key from meata
 * return undefined if not found or empty
 * return first one if multiple values
 * @param meta metadata
 * @param key key
 */
function readMetaAsStringOrUndefined(meta, key) {
    const value = meta.get(key);
    if (value.length > 0) {
        return value[0].toString();
    }
    return undefined;
}
exports.readMetaAsStringOrUndefined = readMetaAsStringOrUndefined;
function alwaysReturnArray(input) {
    if (Array.isArray(input))
        return input;
    return [input];
}
/**
 * read value of the key from header
 * return undefined if not found or empty
 * return first one if multiple values
 * @param header the header
 * @param key the key
 */
function readHeaderOrUndefined(header, key) {
    const value = header[key];
    if (!value) {
        return undefined;
    }
    return alwaysReturnArray(value)[0];
}
exports.readHeaderOrUndefined = readHeaderOrUndefined;
/**
 * assign key value to header, skip empty value
 * @param header the http header
 * @param key the key
 * @param value the value
 */
function assignHeader(header, key, value) {
    if (value === undefined || value === null)
        return;
    if (util_1.isNumber(value)) {
        if (isNaN(value)) {
            return;
        }
        header[key] = `${value}`;
    }
    else {
        header[key] = value;
    }
}
exports.assignHeader = assignHeader;
/**
 * some HTTP framework will do a tricky thing: the merge the headers into one string
 * fixing it here
 * @param hosts a list of array
 */
function refineManagedHostArray(hosts) {
    return hosts.reduce((acc, host) => {
        if (host.indexOf(",") >= 0) {
            return acc.concat(host
                .split(",")
                .map(value => value.trim())
                .filter(value => value));
        }
        acc.push(host);
        return acc;
    }, []);
}
exports.refineManagedHostArray = refineManagedHostArray;
function ensureItsEnvoyContextInit(param) {
    // test if this is a grpc.Metadata
    if (param instanceof grpc_1.Metadata) {
        return {
            meta: param
        };
    }
    // this if this is a HttpHeader
    const asInit = param;
    if (!asInit.meta || typeof asInit.meta === "string" || Array.isArray(asInit.meta)) {
        return {
            meta: param
        };
    }
    return asInit;
}
exports.ensureItsEnvoyContextInit = ensureItsEnvoyContextInit;
/**
 * EnvoyContext is where all information related to the current envoy environment.
 */
class EnvoyContext {
    /**
     * initialize an EnvoyContext
     * @param options options for init envoy context
     */
    constructor(options) {
        const { meta, envoyEgressPort, envoyEgressAddr, envoyManagedHosts, directMode } = ensureItsEnvoyContextInit(options);
        let expectedRequestTimeoutString;
        let envoyEgressAddrFromHeader;
        let envoyEgressPortStringFromHeader;
        let envoyManagedHostsFromHeader;
        if (meta instanceof grpc_1.Metadata) {
            const metadata = meta;
            this.traceId = readMetaAsStringOrUndefined(metadata, X_B3_TRACEID);
            this.spanId = readMetaAsStringOrUndefined(metadata, X_B3_SPANID);
            this.parentSpanId = readMetaAsStringOrUndefined(metadata, X_B3_PARENTSPANID);
            this.sampled = readMetaAsStringOrUndefined(metadata, X_B3_SAMPLED);
            this.flags = readMetaAsStringOrUndefined(metadata, X_B3_FLAGS);
            this.otSpanContext = readMetaAsStringOrUndefined(metadata, X_OT_SPAN_CONTEXT);
            this.requestId = readMetaAsStringOrUndefined(metadata, X_REQUEST_ID);
            this.clientTraceId = readMetaAsStringOrUndefined(metadata, X_CLIENT_TRACE_ID);
            expectedRequestTimeoutString = readMetaAsStringOrUndefined(metadata, X_ENVOY_EXPECTED_RQ_TIMEOUT_MS);
            envoyEgressAddrFromHeader = readMetaAsStringOrUndefined(metadata, exports.X_TUBI_ENVOY_EGRESS_ADDR);
            envoyEgressPortStringFromHeader = readMetaAsStringOrUndefined(metadata, exports.X_TUBI_ENVOY_EGRESS_PORT);
            const managedHosts = metadata.get(exports.X_TUBI_ENVOY_MANAGED_HOST);
            if (managedHosts && managedHosts.length > 0) {
                envoyManagedHostsFromHeader = managedHosts.map(v => v.toString());
            }
        }
        else {
            const httpHeader = meta;
            this.traceId = readHeaderOrUndefined(httpHeader, X_B3_TRACEID);
            this.spanId = readHeaderOrUndefined(httpHeader, X_B3_SPANID);
            this.parentSpanId = readHeaderOrUndefined(httpHeader, X_B3_PARENTSPANID);
            this.sampled = readHeaderOrUndefined(httpHeader, X_B3_SAMPLED);
            this.flags = readHeaderOrUndefined(httpHeader, X_B3_FLAGS);
            this.otSpanContext = readHeaderOrUndefined(httpHeader, X_OT_SPAN_CONTEXT);
            this.requestId = readHeaderOrUndefined(httpHeader, X_REQUEST_ID);
            this.clientTraceId = readHeaderOrUndefined(httpHeader, X_CLIENT_TRACE_ID);
            expectedRequestTimeoutString = readHeaderOrUndefined(httpHeader, X_ENVOY_EXPECTED_RQ_TIMEOUT_MS);
            envoyEgressAddrFromHeader = readHeaderOrUndefined(httpHeader, exports.X_TUBI_ENVOY_EGRESS_ADDR);
            envoyEgressPortStringFromHeader = readHeaderOrUndefined(httpHeader, exports.X_TUBI_ENVOY_EGRESS_PORT);
            const managedHosts = httpHeader[exports.X_TUBI_ENVOY_MANAGED_HOST];
            if (managedHosts) {
                envoyManagedHostsFromHeader = alwaysReturnArray(managedHosts);
            }
        }
        if (expectedRequestTimeoutString !== undefined && expectedRequestTimeoutString !== "") {
            this.expectedRequestTimeout = parseInt(expectedRequestTimeoutString, 10);
        }
        this.envoyEgressPort =
            envoyEgressPort ||
                (envoyEgressPortStringFromHeader && parseInt(envoyEgressPortStringFromHeader, 10)) ||
                ENVOY_EGRESS_PORT;
        this.envoyEgressAddr = envoyEgressAddr || envoyEgressAddrFromHeader || ENVOY_EGRESS_ADDR;
        if (directMode === undefined) {
            this.directMode =
                process.env.ENVOY_DIRECT_MODE === "true" || process.env.ENVOY_DIRECT_MODE === "1";
        }
        else {
            this.directMode = directMode;
        }
        if (this.directMode) {
            this.envoyManagedHosts = new Set();
        }
        else if (envoyManagedHosts !== undefined) {
            this.envoyManagedHosts = envoyManagedHosts;
        }
        else if (envoyManagedHostsFromHeader !== undefined) {
            this.envoyManagedHosts = new Set(refineManagedHostArray(envoyManagedHostsFromHeader));
        }
    }
    /**
     * Assemble the required tracing headers that required for propagation.
     * See more here <https://www.envoyproxy.io/docs/envoy/v1.5.0/intro/arch_overview/tracing.html#trace-context-propagation>
     */
    assembleTracingHeader() {
        const header = {};
        assignHeader(header, X_B3_TRACEID, this.traceId);
        assignHeader(header, X_B3_SPANID, this.spanId);
        assignHeader(header, X_B3_PARENTSPANID, this.parentSpanId);
        assignHeader(header, X_B3_SAMPLED, this.sampled);
        assignHeader(header, X_B3_FLAGS, this.flags);
        assignHeader(header, X_OT_SPAN_CONTEXT, this.otSpanContext);
        assignHeader(header, X_REQUEST_ID, this.requestId);
        assignHeader(header, X_CLIENT_TRACE_ID, this.clientTraceId);
        return header;
    }
    shouldCallWithoutEnvoy(host) {
        return (this.directMode || (this.envoyManagedHosts !== undefined && !this.envoyManagedHosts.has(host)));
    }
}
exports.default = EnvoyContext;
//# sourceMappingURL=envoy-context.js.map