/**
 * A central dispatcher is aware of the world.  Basically that means that names are also urls.
 * URL schemes can be mapped to EnvelopePorts, so that e.g. remote:// goes somewhere else.
 *
 * This means that 404s happen when the dispatcher doesn't know about the scheme or port.
 */
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _CentralDispatcher_instances, _CentralDispatcher_clients, _CentralDispatcher_recipientsByHostname, _CentralDispatcher_supervisor, _CentralDispatcher_streamsByID, _CentralDispatcher_bind, _CentralDispatcher_dispatchFiltered, _CentralDispatcher_dispatchFilteredRequest, _CentralDispatcher_dispatchFilteredResponse, _CentralDispatcher_dispatchError404, _CentralDispatcher_evictStreamIfComplete, _CentralDispatcher_getSupervisorHostname, _CentralDispatcher_isFiltered, _CentralDispatcher_isLocalClient, _CentralDispatcher_isSourceDispatcherOrSupervisor, _CentralDispatcher_isWorker, _CentralDispatcher_logEnvelope, _CentralDispatcher_newClientCloseRequestError499, _CentralDispatcher_newFilterError500, _CentralDispatcher_newStreamEvictionError503, _CentralDispatcher_newServiceUnavailableError503, _CentralDispatcher_onStreamEvicting, _CentralDispatcher_postAsync, _CentralDispatcher_unbind;
import { SupervisorDefinition, } from '@devvit/protos/types/devvit/runtime/actor/supervisor.js';
import { Envelope } from '@devvit/protos/types/devvit/runtime/envelope.js';
import { DEBUG_CONFIG } from '@devvit/shared-types/debugConfig.js';
import { Hostname, parentHostname, resolveSystemHostname, SUPERVISOR_CNAME, } from '@devvit/shared-types/HostnameUtil.js';
import { assertNonNull } from '@devvit/shared-types/NonNull.js';
import { isNativeWorker, isPlatformNode, isWorker } from '@devvit/shared-types/PlatformUtil.js';
import { StringUtil } from '@devvit/shared-types/StringUtil.js';
import LRU from 'lru-cache';
import { v4 as UUIDv4 } from 'uuid';
import { filterHeadersForDestination } from '../../../lib/Header.js';
import { Log } from '../../../lib/loggers/Log.js';
import { assertEnvelopePostable, envelopeMethod, EnvelopeUtil } from '../EnvelopeUtil.js';
import { isIDish } from '../IDFactory.js';
// to-do: Rename to GlobalEnvelopeDispatcher since this is the only dispatcher
// that runs in the window / globalThis main thread in production.
export class CentralDispatcher {
    /**
     * All known recipients by hostname excluding local clients. Only meant to be
     * used in a debug mode REPL.
     */
    get debugRecipientsByHostname() {
        return DEBUG_CONFIG.addDebugUtilsToEnv ? __classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f") : undefined;
    }
    /** Local clients. Only meant to be used in a debug mode REPL. */
    get debugClients() {
        return DEBUG_CONFIG.addDebugUtilsToEnv ? __classPrivateFieldGet(this, _CentralDispatcher_clients, "f") : undefined;
    }
    constructor(hostname) {
        _CentralDispatcher_instances.add(this);
        /**
         * Clients local to the dispatcher. Local clients have the same hostname as
         * the dispatcher and do not appear in #recipientsByHostname which does not
         * permit duplicates.
         */
        _CentralDispatcher_clients.set(this, new Set());
        /**
         * All known recipients by hostname excluding local clients. Local clients
         * have the same hostname as the dispatcher. The primary worker server also
         * has the same hostname as the dispatcher (eg, supervisor.system.local) but
         * the runtime actor does not (eg, runtime.system.local).
         */
        _CentralDispatcher_recipientsByHostname.set(this, {});
        /** Nullish when in a worker, nonnullish in runtime. */
        _CentralDispatcher_supervisor.set(this, void 0);
        /**
         * *Response* recipients by stream ID. Never route a request by stream ID.
         * Requests should always be routed by hostname.
         *
         * The recipients may be local clients or external recipients.
         *
         * to-do: never evict local clients.
         */
        _CentralDispatcher_streamsByID.set(this, void 0);
        this.hostname = hostname;
        // Keep capacity aligned to MaxStreams in v8workers/constants.
        __classPrivateFieldSet(this, _CentralDispatcher_streamsByID, new LRU({ max: 8192, dispose: __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_onStreamEvicting).bind(this) }), "f");
    }
    onBind(recipient) {
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_bind).call(this, recipient);
    }
    onUnbind(other) {
        if (other.hostname == null) {
            throw Error('Cannot unbind from unbound dispatcher.');
        }
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_unbind).call(this, other);
    }
    bind(recipient) {
        if (!__classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_isLocalClient).call(this, recipient) && this.isBound(recipient)) {
            throw Error(`Recipient at ${recipient.hostname} already bound.`);
        }
        // onBind() must occur first because sometimes the recipient, such as
        // clients, will dynamically choose a name to match the dispatcher.
        recipient.onBind(this);
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_bind).call(this, recipient);
    }
    /** For runtimes only, bind a supervisor client. */
    bindSupervisor(supervisor) {
        __classPrivateFieldSet(this, _CentralDispatcher_supervisor, supervisor, "f");
    }
    isBound(recipient) {
        return (__classPrivateFieldGet(this, _CentralDispatcher_clients, "f").has(recipient) ||
            (recipient.hostname != null && recipient.hostname in __classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f")));
    }
    unbind(recipient) {
        if (!this.isBound(recipient)) {
            return;
        }
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_unbind).call(this, recipient);
        recipient.onUnbind(this);
    }
    async release(recipient) {
        this.unbind(recipient);
        // Unconditionally dispose. Regardless of whether the recipient was bound,
        // it will at least be disposed. The thinking is that if a client was
        // created but never got bound, releasing is still expected to dispose it.
        await recipient.dispose();
    }
    post(caller, envelope) {
        // Forbid invalid envelopes from being posted. Envelopes are constructed by
        // clients and servers and expected to contain a valid ID, source,
        // destination, etc. RepeatEnvelopeFilter tests for and uses ID but may be
        // several hops out. Keep this check synchronous so errors surface on the
        // same thread for debuggability.
        // to-do: test isIDish(streamId), isIDish(traceID), src != '', etc.
        if (!isIDish(envelope.id)) {
            throw Error(`Missing or invalid ID in envelope ${EnvelopeUtil.toLogString(envelope)}.`);
        }
        // All asynchronous errors are handled in #postAsync().
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_postAsync).call(this, caller, envelope);
    }
    async dispose() {
        // to-do: dispose post filter streams
        for (const recipient of __classPrivateFieldGet(this, _CentralDispatcher_clients, "f")) {
            await this.release(recipient);
        }
        for (const recipient of Object.values(__classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f"))) {
            await this.release(recipient);
        }
        __classPrivateFieldSet(this, _CentralDispatcher_supervisor, undefined, "f");
    }
}
_CentralDispatcher_clients = new WeakMap(), _CentralDispatcher_recipientsByHostname = new WeakMap(), _CentralDispatcher_supervisor = new WeakMap(), _CentralDispatcher_streamsByID = new WeakMap(), _CentralDispatcher_instances = new WeakSet(), _CentralDispatcher_bind = function _CentralDispatcher_bind(recipient) {
    assertNonNull(recipient.hostname, 'Recipient missing hostname.');
    if (__classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_isLocalClient).call(this, recipient)) {
        __classPrivateFieldGet(this, _CentralDispatcher_clients, "f").add(recipient);
        return;
    }
    if (recipient.hostname in __classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f") &&
        __classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f")[recipient.hostname] !== recipient) {
        throw Error(`Server hostname ${recipient.hostname} already bound.`);
    }
    __classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f")[recipient.hostname] = recipient;
}, _CentralDispatcher_dispatchFiltered = function _CentralDispatcher_dispatchFiltered(caller, envelope) {
    __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_logEnvelope).call(this, envelope);
    if (envelope.request) {
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchFilteredRequest).call(this, caller, envelope);
    }
    else {
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchFilteredResponse).call(this, caller, envelope);
    }
    __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_evictStreamIfComplete).call(this, envelope);
}, _CentralDispatcher_dispatchFilteredRequest = function _CentralDispatcher_dispatchFilteredRequest(caller, envelope) {
    if (!envelope.request) {
        throw Error('Expected request envelope.');
    }
    // Record sender (origin) by stream ID. The stream is locked in. It's
    // always correct to route back a *response* to this destination. Pipes may
    // issue multiple requests on the same stream but only the client recipient
    // is recorded for resolution by stream ID. Routing any subsequent request
    // on the same stream ID would erroneously return the request to the client
    // as the server is never recorded by stream ID.
    //
    // Request-response streams (pipes) may be unbalanced.
    if (!this.isBound(caller)) {
        throw Error(`Caller ${caller.hostname} must be bound to dispatcher.`);
    }
    // Refresh the LRU recency.
    const stream = __classPrivateFieldGet(this, _CentralDispatcher_streamsByID, "f").get(envelope.streamId);
    if (stream == null) {
        __classPrivateFieldGet(this, _CentralDispatcher_streamsByID, "f").set(envelope.streamId, { requester: caller, request: envelope });
    }
    else if (stream.requester !== caller) {
        throw Error(`Client recipient ${envelope.src} with request to ${envelope.dst} ` +
            `changed on open stream ${envelope.streamId}.`);
    }
    let dst = envelope.dst;
    let searching = true;
    while (searching) {
        const recipient = __classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f")[dst];
        if (recipient != null) {
            try {
                recipient.post(caller, envelope);
            }
            catch (err) {
                Log.error(`Failed to post request ${EnvelopeUtil.toLogString(envelope)}: ${StringUtil.caughtToString(err)}`);
                if (!isDispatcherErrorEnvelope(envelope)) {
                    __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchFiltered).call(this, this, __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_newServiceUnavailableError503).call(this, envelope, err));
                }
            }
            return;
        }
        searching = dst.length > 0;
        dst = parentHostname(dst);
    }
    if (!isDispatcherErrorEnvelope(envelope)) {
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchError404).call(this, envelope);
    }
}, _CentralDispatcher_dispatchFilteredResponse = function _CentralDispatcher_dispatchFilteredResponse(caller, envelope) {
    if (envelope.request) {
        throw Error('Expected response envelope.');
    }
    // Refresh the LRU recency.
    const stream = __classPrivateFieldGet(this, _CentralDispatcher_streamsByID, "f").get(envelope.streamId);
    if (stream != null) {
        try {
            stream.requester.post(caller, envelope);
        }
        catch (err) {
            Log.error(`Failed to post response ${EnvelopeUtil.toLogString(envelope)}: ${StringUtil.caughtToString(err)}`);
            if (!isDispatcherErrorEnvelope(envelope)) {
                if (err instanceof Error) {
                    __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchFiltered).call(this, this, __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_newClientCloseRequestError499).call(this, envelope, err));
                }
                else {
                    __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchFiltered).call(this, this, __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_newClientCloseRequestError499).call(this, envelope, new Error('unknown error')));
                }
            }
        }
    }
    else if (!isDispatcherErrorEnvelope(envelope)) {
        __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchError404).call(this, envelope);
    }
}, _CentralDispatcher_dispatchError404 = function _CentralDispatcher_dispatchError404(envelope) {
    const recipients = Object.keys(__classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f"))
        .sort((lhs, rhs) => lhs.localeCompare(rhs))
        .join(', ');
    Log.info(`Posting 404: ${this.hostname} cannot post envelope from ` +
        `${envelope.src} for ${envelope.dst} with stream ID ` +
        `${envelope.streamId}; recipients are ${recipients}.`);
    const destination = envelope.src;
    __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchFiltered).call(this, this, Envelope.fromPartial({
        id: UUIDv4(),
        complete: true,
        dst: destination,
        message: `404 ${envelope.dst} not found.`,
        metadata: filterHeadersForDestination(this, envelope.metadata, destination),
        method: envelope.method,
        request: false,
        src: envelope.dst,
        streamId: envelope.streamId,
        success: false,
        traceId: envelope.traceId,
    }));
}, _CentralDispatcher_evictStreamIfComplete = function _CentralDispatcher_evictStreamIfComplete(envelope) {
    if (envelope.complete && !envelope.request) {
        __classPrivateFieldGet(this, _CentralDispatcher_streamsByID, "f").delete(envelope.streamId);
    }
}, _CentralDispatcher_getSupervisorHostname = function _CentralDispatcher_getSupervisorHostname() {
    // to-do: simplify. This calculation is complicated because in the now
    // deleted BrowserRuntime main thread, the dispatcher's hostname is usually
    // "local". Workers, however, are special and their dispatchers have the
    // same hostname as their actors.
    //
    // For example, an actor name foo.local's dispatcher is also foo.local. For
    // the supervisor, this means the dispatcher is often
    // supervisor.system.local and so taking
    // `resolveSystemHostname(SUPERVISOR_CNAME, this)` yields
    // "supervisor.system.supervisor.system.local" instead of the expected
    // "supervisor.system.local".
    //
    // As a workaround, this code takes the root, often just "local", and
    // resolves the supervisor's system hostname from that. That is, the root of
    // "supervisor.system.local" is "local" and the root of "local" is still
    // "local".
    return resolveSystemHostname(SUPERVISOR_CNAME, { hostname: Hostname.getRoot(this.hostname) });
}, _CentralDispatcher_isFiltered = function _CentralDispatcher_isFiltered(caller, envelope) {
    // All workers trust themselves, including the supervisor, so a worker's
    // dispatcher considers every envelope already filtered because it doesn't
    // make sense for it to filter itself.
    //
    // The supervisor never trusts other workers but it's the runtime that's
    // actually responsible for requesting an envelope be filtered. The
    // supervisor never discerns whether to filter an envelope.
    return __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_isWorker).call(this) || __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_isSourceDispatcherOrSupervisor).call(this, caller, envelope);
}, _CentralDispatcher_isLocalClient = function _CentralDispatcher_isLocalClient(recipient) {
    return recipient.type === 'client';
}, _CentralDispatcher_isSourceDispatcherOrSupervisor = function _CentralDispatcher_isSourceDispatcherOrSupervisor(caller, envelope) {
    return (
    // From CentralDispatcher to the supervisor.
    caller === __classPrivateFieldGet(this, _CentralDispatcher_supervisor, "f") ||
        // Already filtered by or from the supervisor.
        envelope.src === __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_getSupervisorHostname).call(this));
}, _CentralDispatcher_isWorker = function _CentralDispatcher_isWorker() {
    return __classPrivateFieldGet(this, _CentralDispatcher_supervisor, "f") == null;
}, _CentralDispatcher_logEnvelope = function _CentralDispatcher_logEnvelope(envelope) {
    const isCommonRuntime = !isWorker() && isPlatformNode();
    const filterEnvelope = envelope.method === envelopeMethod(SupervisorDefinition, 'filter');
    if (DEBUG_CONFIG.logAllEnvelopes &&
        // Only log from the common runtime's global dispatcher and native
        // workers. Native runtimes _only_ execute TypeScript in workers. There's
        // no common runtime and the supervisor pretty much only filters so each
        // user worker must log itself.
        (isCommonRuntime || isNativeWorker()) &&
        // Remove filterEnvelope to log envelope filter request/responses from the
        // runtime to the supervisor and back. This is super noisy and usually
        // interferes with debugging.
        !filterEnvelope) {
        Log.envelope(envelope);
    }
}, _CentralDispatcher_newClientCloseRequestError499 = function _CentralDispatcher_newClientCloseRequestError499(envelope, err) {
    const destination = envelope.src;
    return Envelope.fromPartial({
        id: UUIDv4(),
        complete: true,
        dst: destination,
        message: err.message,
        metadata: filterHeadersForDestination(this, envelope.metadata, destination),
        method: envelope.method,
        src: envelope.dst,
        streamId: envelope.streamId,
        traceId: envelope.traceId,
    });
}, _CentralDispatcher_newFilterError500 = function _CentralDispatcher_newFilterError500(envelope, error) {
    Log.error(StringUtil.caughtToString(error));
    const destination = envelope.src;
    return Envelope.fromPartial({
        id: UUIDv4(),
        complete: true,
        dst: destination,
        message: `500 filter error: ${error}`,
        metadata: filterHeadersForDestination(this, envelope.metadata, destination),
        method: envelope.method,
        request: false,
        src: envelope.dst,
        streamId: envelope.streamId,
        success: false,
        traceId: envelope.traceId,
    });
}, _CentralDispatcher_newStreamEvictionError503 = function _CentralDispatcher_newStreamEvictionError503(envelope) {
    const destination = envelope.src;
    return Envelope.fromPartial({
        id: UUIDv4(),
        complete: true,
        dst: destination,
        message: '503 stream closed by dispatcher due to envelope stream overflow.',
        metadata: filterHeadersForDestination(this, envelope.metadata, destination),
        method: envelope.method,
        src: envelope.dst,
        streamId: envelope.streamId,
        traceId: envelope.traceId,
    });
}, _CentralDispatcher_newServiceUnavailableError503 = function _CentralDispatcher_newServiceUnavailableError503(envelope, reason) {
    const destination = envelope.src;
    return Envelope.fromPartial({
        id: UUIDv4(),
        complete: true,
        dst: destination,
        message: `503 service unavailable: ${reason.message}`,
        metadata: filterHeadersForDestination(this, envelope.metadata, destination),
        method: envelope.method,
        src: envelope.dst,
        streamId: envelope.streamId,
        traceId: envelope.traceId,
    });
}, _CentralDispatcher_onStreamEvicting = function _CentralDispatcher_onStreamEvicting(stream, streamID, reason) {
    // Stream closed naturally, possibly due to an error response. However, the
    // dispatcher does not know whether the recipient will be reused or not so
    // it cannot release the recipient.
    if (reason === 'delete') {
        return;
    }
    // to-do: never evict local clients.
    if (__classPrivateFieldGet(this, _CentralDispatcher_clients, "f").has(stream.requester)) {
        Log.error(`Envelope stream overflow. Evicting least recently used stream ` +
            `${streamID} to local client at ${stream.requester.hostname}.`);
    }
    else {
        Log.warn(`Envelope stream overflow. Evicting least recently used stream ` +
            `${streamID} associated with recipient at ${stream.requester.hostname}.`);
    }
    // to-do: notify server. From the server perspective which routes every
    // request by hostname:
    // - Async requests: as a server, I don't await requests. There's nothing
    //   pending in the dispatcher either. When I do get a request, I await
    //   for my service to generate a response which I then post to the
    //   dispatcher's post(). post() is synchronous so there's nothing the
    //   server waits for. Servers don't have a callback like the clients do
    //   in their post() method.
    // - Sink requests: as a server, I am waiting for the requests to stop
    //   coming in. We do want to send an error response in this case.
    // - Source requests: as a server, I keep sending responses until the
    //   client unsubscribes. However, we don't expose the Observable held by
    //   the server. We'll need to add extra logic to the server post() method
    //   to allow the dispatcher to close the stream, I think.
    // - Pipes: just like sink, as a server, I am waiting for the requests to
    //   stop coming in. We do want to send an error response in this case.
    // The client, which routes every response by stream ID:
    // - Async: as a client, I am awaiting a response. The stream was evicted
    //   so I should and now do get an error response.
    // - Sink requests: as a client, I am issuing request after request.
    //   Requests always route by hostname so the stream will be
    //   reestablished. However, the server could close the stream and issue a
    //   response which wouldn't route so we treat this as an error.
    // - Source requests: as a client, I send one request and just keep
    //   listening for responses until I unsubscribe. I do want to know when
    //   the stream is closed so getting the error response is good.
    // - Pipe: just like sink, as a client, I just keep issuing requests as
    //   long as I like. However, the server could close the stream and I
    //   wouldn't know about it so we treat this as an error.
    const envelope = __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_newStreamEvictionError503).call(this, stream.request);
    __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_logEnvelope).call(this, envelope);
    try {
        stream.requester.post(this, envelope);
    }
    catch (err) {
        Log.warn(`Failed to post stream eviction error ${EnvelopeUtil.toLogString(envelope)}. Giving up: ${StringUtil.caughtToString(err)}`);
    }
}, _CentralDispatcher_postAsync = async function _CentralDispatcher_postAsync(caller, envelope) {
    // Provide better error messages in debug mode for messages that cannot be
    // sent.
    if (DEBUG_CONFIG.assertEnvelopesPostable) {
        assertEnvelopePostable(envelope);
    }
    if (!__classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_isFiltered).call(this, caller, envelope)) {
        assertNonNull(__classPrivateFieldGet(this, _CentralDispatcher_supervisor, "f"), 'Expected the supervisor to be set.');
        try {
            envelope = await __classPrivateFieldGet(this, _CentralDispatcher_supervisor, "f").Filter(envelope);
        }
        catch (err) {
            if (isDispatcherErrorEnvelope(envelope)) {
                Log.info('A dispatch error occurred but the attempted envelope already ' +
                    `contained a dispatcher error. Giving up: ${StringUtil.caughtToString(err)}`);
                __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_evictStreamIfComplete).call(this, envelope);
                return;
            }
            Log.error(`Failed to filter envelope ${EnvelopeUtil.toLogString(envelope)}: ${StringUtil.caughtToString(err)}`);
            envelope = __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_newFilterError500).call(this, envelope, err);
            __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_logEnvelope).call(this, envelope);
            // Post to the caller directly. The request stream will not exist if the
            // first envelope failed.
            try {
                caller.post(caller, envelope);
            }
            catch (err) {
                Log.error(`Failed to post envelope filter error ${EnvelopeUtil.toLogString(envelope)}. Giving up: ${StringUtil.caughtToString(err)}.`);
            }
            __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_evictStreamIfComplete).call(this, envelope);
            return;
        }
    }
    __classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_dispatchFiltered).call(this, caller, envelope);
}, _CentralDispatcher_unbind = function _CentralDispatcher_unbind(recipient) {
    if (!this.isBound(recipient)) {
        return;
    }
    assertNonNull(recipient.hostname, 'Recipient missing hostname.');
    if (__classPrivateFieldGet(this, _CentralDispatcher_instances, "m", _CentralDispatcher_isLocalClient).call(this, recipient)) {
        __classPrivateFieldGet(this, _CentralDispatcher_clients, "f").delete(recipient);
    }
    else {
        delete __classPrivateFieldGet(this, _CentralDispatcher_recipientsByHostname, "f")[recipient.hostname];
    }
};
CentralDispatcher.UNRESOLVED_HOSTNAMES = '';
// to-do: make this check hard to spoof. We also don't know if we're the
// dispatcher this envelope originated from.
function isDispatcherErrorEnvelope(envelope) {
    return envelope.complete === true && envelope.request === false && envelope.success === false;
}
