{"version":3,"file":"MatsSocket.umd.min.cjs","sources":["../lib/AuthorizationRequiredEvent.js","../lib/ConnectionState.js","../lib/ConnectionEvent.js","../lib/MessageType.js","../lib/ReceivedEvent.js","../lib/MessageEvent.js","../lib/SubscriptionEvent.js","../lib/InitiationProcessedEvent.js","../lib/PingPong.js","../lib/MatsSocketCloseCodes.js","../lib/ErrorEvent.js","../lib/DebugInformation.js","../lib/MatsSocket.js"],"sourcesContent":["import './typedefs.js';\n// Repeating typedefs here, since 'tsc' otherwise don't create the 'export type FractionalMillis = number;' line.\n/**\n * Fractional milliseconds for high-res timing.\n * @typedef {number} FractionalMillis\n */\n\n/**\n * Timestamp, millis-since-epoch.\n * @typedef {number} Timestamp\n */\n\nexport { AuthorizationRequiredEvent, AuthorizationRequiredEventType }\n\n/**\n * Sent by the MatsSocket, via the {@link MatsSocket#setAuthorizationExpiredCallback}, when it requires new or\n * revalidated authentication by the client.\n *\n * @param {AuthorizationRequiredEventType} type - {@link AuthorizationRequiredEvent#type}\n * @param {Timestamp} currentExpirationTimestamp - {@link AuthorizationRequiredEvent#currentExpirationTimestamp}\n * @class\n */\nfunction AuthorizationRequiredEvent(type, currentExpirationTimestamp) {\n    /**\n     * Type of the event, one of {@link AuthorizationRequiredEventType}.\n     *\n     * @type {AuthorizationRequiredEventType}\n     */\n    this.type = type;\n\n    /**\n     * Millis-since-epoch when the current Authorization expires - note that this might well still be in the future,\n     * but the \"slack\" left before expiration is used up.\n     *\n     * @type {Timestamp}\n     */\n    this.currentExpirationTimestamp = currentExpirationTimestamp;\n}\n\n/**\n * Type of {@link AuthorizationRequiredEvent}.\n *\n * @enum {string}\n * @readonly\n */\nconst AuthorizationRequiredEventType = {\n    /**\n     * Initial state, if auth not already set by app.\n     */\n    NOT_PRESENT: \"notpresent\",\n\n    /**\n     * The authentication is expired - note that this might well still be in the future,\n     * but the \"slack\" left before expiration is not long enough.\n     */\n    EXPIRED: \"expired\",\n\n    /**\n     * The server has requested that the app provides fresh auth to proceed - this needs to be fully fresh, even\n     * though there might still be \"slack\" enough left on the current authorization to proceed. (The server side\n     * might want the full expiry to proceed, or wants to ensure that the app can still produce new auth - i.e.\n     * it might suspect that the current authentication session has been invalidated, and need proof that the app\n     * can still produce new authorizations/tokens).\n     */\n    REAUTHENTICATE: \"reauthenticate\"\n};\nObject.freeze(AuthorizationRequiredEventType);","export { ConnectionState }\n\n/**\n * States for MatsSocket's {@link MatsSocket#state state}.\n *\n * @enum {string}\n * @readonly\n */\nconst ConnectionState = {\n    /**\n     * This is the initial State of a MatsSocket. Also, the MatsSocket is re-set back to this State in a\n     * Session-Closed-from-Server situation (which is communicated via listeners registered with\n     * {@link MatsSocket#addSessionClosedEventListener}), OR if you have explicitly performed a\n     * {@link MatsSocket#close}.\n     * <p/>\n     * Only transition out of this state is into {@link ConnectionState.CONNECTING}.\n     */\n    NO_SESSION: \"nosession\",\n\n    /**\n     * Read doc at {@link ConnectionEventType.CONNECTING}.\n     */\n    CONNECTING: \"connecting\",\n\n    /**\n     * Read doc at {@link ConnectionEventType.WAITING}.\n     */\n    WAITING: \"waiting\",\n\n    /**\n     * Read doc at {@link ConnectionEventType.CONNECTED}.\n     */\n    CONNECTED: \"connected\",\n\n    /**\n     * Read doc at {@link ConnectionEventType.SESSION_ESTABLISHED}.\n     */\n    SESSION_ESTABLISHED: \"sessionestablished\"\n};\nObject.freeze(ConnectionState);\n","import { ConnectionState } from \"./ConnectionState.js\";\n\nexport { ConnectionEvent, ConnectionEventType }\n\n/**\n * Event object for {@link MatsSocket#addConnectionEventListener}.\n * <p />\n * <b>Note on event ordering</b>: {@link ConnectionEvent}s are delivered ASAP. This means that for events that the\n * client controls, they are issued <i/>before</i> the operation they describe commences:\n * {@link ConnectionEventType#CONNECTING CONNECTING} and\n * {@link ConnectionEventType#SESSION_ESTABLISHED SESSION_ESTABLISHED}. However, for events where the client is\n * \"reacting\", e.g. when the WebSocket connects, or abruptly closes, they are issued ASAP when the Client gets to know about it:\n * {@link ConnectionEventType#CONNECTED CONNECTED}, {@link ConnectionEventType#LOST_CONNECTION LOST_CONNECTION},\n * {@link ConnectionEventType#CONNECTION_ERROR CONNECTION_ERROR} and {@link ConnectionEventType#WAITING WAITING}.\n * For {@link ConnectionEventType#COUNTDOWN COUNTDOWN}, there is not much to say wrt. timing, other than you won't typically\n * get a 'countdown'-event with 0 seconds left, as that is when we transition into 'connecting' again. For events\n * that also describe {@link ConnectionState}s, the {@link MatsSocket.state} is updated before the event is fired.\n *\n * @param {ConnectionEventType} type - {@link ConnectionEvent#type}\n * @param {string} webSocketUrl - {@link ConnectionEvent#webSocketUrl}\n * @param {Event} webSocketEvent - {@link ConnectionEvent#webSocketEvent}\n * @param {number} timeoutSeconds - {@link ConnectionEvent#timeoutSeconds}\n * @param {number} countdownSeconds - {@link ConnectionEvent#countdownSeconds}\n * @param {number} connectionAttempt - {@link ConnectionEvent#connectionAttempt}\n * @class\n */\nfunction ConnectionEvent(type, webSocketUrl, webSocketEvent, timeoutSeconds, countdownSeconds, connectionAttempt) {\n    /**\n     * The type of the <code>ConnectionEvent</code>, returns an enum value of {@link ConnectionEventType}.\n     *\n     * @type {ConnectionEventType}\n     */\n    this.type = type;\n\n    /**\n     * Holds the current URL we're either connected to, was connected to, or trying to connect to.\n     *\n     * @type {string}\n     */\n    this.webSocketUrl = webSocketUrl;\n\n    /**\n     * For several of the events (enumerated in {@link ConnectionEventType}), there is an underlying WebSocket event\n     * that caused it. This field holds that.\n     * <ul>\n     *     <li>{@link ConnectionEventType#WAITING}: WebSocket {@link CloseEvent} that caused this transition.</li>\n     *     <li>{@link ConnectionEventType#CONNECTED}: WebSocket {@link Event} that caused this transition.</li>\n     *     <li>{@link ConnectionEventType#CONNECTION_ERROR}: WebSocket {@link Event} that caused this transition.</li>\n     *     <li>{@link ConnectionEventType#LOST_CONNECTION}: WebSocket {@link CloseEvent} that caused it.</li>\n     * </ul>\n     *\n     * @type {Event}\n     */\n    this.webSocketEvent = webSocketEvent;\n\n    /**\n     * For {@link ConnectionEventType#CONNECTING}, {@link ConnectionEventType#WAITING} and {@link ConnectionEventType#COUNTDOWN},\n     * tells how long the timeout for this attempt is, i.e. what the COUNTDOWN events start out with. Together with\n     * {@link #countdownSeconds} of the COUNTDOWN events, this can be used to calculate a fraction if you want to\n     * make a \"progress bar\" of sorts.\n     * <p/>\n     * The timeouts starts at 500 ms (unless there is only 1 URL configured, in which case 5 seconds), and then\n     * increases exponentially, but maxes out at 15 seconds.\n     *\n     * @type {number}\n     */\n    this.timeoutSeconds = timeoutSeconds;\n\n    /**\n     * For {@link ConnectionEventType#CONNECTING}, {@link ConnectionEventType#WAITING} and {@link ConnectionEventType#COUNTDOWN},\n     * tells how many seconds there are left for this attempt (of the {@link #timeoutSeconds} it started with),\n     * with a tenth of a second as precision. With the COUNTDOWN events, these come in each 100 ms (1/10 second),\n     * and show how long time there is left before trying again (if MatsSocket is configured with multiple URLs,\n     * the next attempt will be a different URL).\n     * <p/>\n     * The countdown is started when the state transitions to {@link ConnectionEventType#CONNECTING}, and\n     * stops either when {@link ConnectionEventType#CONNECTED} or the timeout reaches zero. If the\n     * state is still CONNECTING when the countdown reaches zero, implying that the \"new WebSocket(..)\" call still\n     * has not either opened or closed, the connection attempt is aborted by calling webSocket.close(). It then\n     * tries again, possibly with a different URL - and the countdown starts over.\n     * <p/>\n     * Notice that the countdown is not affected by any state transition into {@link ConnectionEventType#WAITING} -\n     * such transition only means that the \"new WebSocket(..)\" call failed and emitted a close-event, but we will\n     * still wait out the countdown before trying again.\n     * <p/>\n     * Notice that you will most probably not get an event with 0 seconds, as that is when we transition into\n     * {@link ConnectionEventType#CONNECTING} and the countdown starts over (possibly with a larger timeout).\n     * <p/>\n     * Truncated exponential backoff: The timeouts starts at 500 ms (unless there is only 1 URL configured, in which\n     * case 5 seconds), and then increases exponentially, but maxes out at 15 seconds.\n     *\n     * @type {number}\n     */\n    this.countdownSeconds = countdownSeconds;\n\n    /**\n     * The connection attempt count, starts at 0th attempt and increases for each time the connection attempt fails.\n     *\n     * @type {number}\n     */\n    this.connectionAttempt = connectionAttempt;\n}\n\n\n/**\n * The event types of {@link ConnectionEvent} - four of the event types are state-transitions into different states\n * of {@link ConnectionState}.\n *\n * @enum {string}\n * @readonly\n */\nconst ConnectionEventType = {\n    /**\n     * State, and fires as ConnectionEvent when we transition into this state, which is when the WebSocket is literally trying to connect.\n     * This is between <code>new WebSocket(url)</code> (or the {@link MatsSocket#preconnectoperation \"PreConnectOperation\"} if configured),\n     * and either webSocket.onopen or webSocket.onclose is fired, or countdown reaches 0. If webSocket.onopen,\n     * we transition into {@link ConnectionEventType.CONNECTED}, if webSocket.onclose, we transition into\n     * {@link ConnectionEventType.WAITING}. If we reach countdown 0 while in CONNECTING, we will \"re-transition\" to the same state, and\n     * thus get one more event of CONNECTING.\n     * <p/>\n     * User Info Tip: Show a info-box, stating \"Connecting! <4.0 seconds..>\", countdown in \"grayed out\" style, box is\n     * some neutral information color, e.g. yellow (fading over to this color if already red or orange due to\n     * {@link ConnectionEventType.CONNECTION_ERROR} or {@link ConnectionEventType.LOST_CONNECTION}).\n     * Each time it transitions into CONNECTING, it will start a new countdown. Let's say it starts from say 4\n     * seconds: If this connection attempt fails after 1 second, it will transition into WAITING and continue the\n     * countdown with 3 seconds remaining.\n     */\n    CONNECTING: ConnectionState.CONNECTING,\n\n    /**\n     * State, and fires as ConnectionEvent when we transition into this state, which is when {@link ConnectionEventType.CONNECTING} fails.\n     * The only transition out of this state is {@link ConnectionEventType.CONNECTING}, when the {@link ConnectionEventType.COUNTDOWN} reaches 0.\n     * <p/>\n     * Notice that the {@link ConnectionEvent} contains the {@link Event} that came with webSocket.close (while CONNECTING).\n     * <p/>\n     * User Info Tip: Show a info-box, stating \"Waiting! <2.9 seconds..>\", countdown in normal visibility, box is\n     * some neutral information color, e.g. yellow (keeping the box color fading if in progress).\n     * It will come into this state from {@link ConnectionEventType.CONNECTING}, and have the time remaining from the initial countdown.\n     * So if the attempt countdown started from 4 seconds, and it took 1 second before the connection attempt failed,\n     * then there will be 3 seconds left in WAITING state.\n     */\n    WAITING: ConnectionState.WAITING,\n\n    /**\n     * State, and fires as ConnectionEvent when we transition into this state, which is when WebSocket.onopen is fired.\n     * Notice that the MatsSocket is still not fully established, as we have not yet exchanged HELLO and WELCOME -\n     * the MatsSocket is fully established at {@link ConnectionEventType.SESSION_ESTABLISHED}.\n     * <p/>\n     * Notice that the {@link ConnectionEvent} contains the WebSocket 'onopen' {@link Event} that was issued when\n     * the WebSocket opened.\n     * <p/>\n     * User Info Tip: Show a info-box, stating \"Connected!\", happy-color, e.g. green, with no countdown.\n     */\n    CONNECTED: ConnectionState.CONNECTED,\n\n    /**\n     * State, and fires as ConnectionEvent when we transition into this state, which is when when the WELCOME MatsSocket message comes\n     * from the Server, also implying that it has been authenticated: The MatsSocket is now fully established, and\n     * actual messages can be exchanged.\n     * <p/>\n     * User Info Tip: Show a info-box, stating \"Session OK!\", happy-color, e.g. green, with no countdown - and the\n     * entire info-box fades away fast, e.g. after 1 second.\n     */\n    SESSION_ESTABLISHED: ConnectionState.SESSION_ESTABLISHED,\n\n    /**\n     * This is a pretty worthless event. It comes from WebSocket.onerror. It will <i>always</i> be trailed by a\n     * WebSocket.onclose, which gives the event {@link ConnectionEventType.LOST_CONNECTION}.\n     * <p/>\n     * Notice that the {@link ConnectionEvent} contains the {@link Event} that caused the error.\n     * <p/>\n     * User Info Tip: Show a info-box, which is some reddish color (no need for text since next event {@link ConnectionEventType.LOST_CONNECTION}) comes immediately).\n     */\n    CONNECTION_ERROR: \"connectionerror\",\n\n    /**\n     * This comes when WebSocket.onclose is fired \"unexpectedly\", <b>and the reason for this close is NOT a SessionClosed Event</b> (The latter will\n     * instead invoke the listeners registered with {@link MatsSocket#addSessionClosedEventListener}).\n     * A LOST_CONNECTION will start a reconnection attempt after a very brief delay (couple of hundred milliseconds),\n     * and the next state transition and thus event is {@link ConnectionEventType.CONNECTING}.\n     * <p/>\n     * Notice that the {@link ConnectionEvent} contains the {@link CloseEvent} that caused the lost connection.\n     * <p/>\n     * User Info Tip: Show a info-box, stating \"Connection broken!\", which is some orange color (unless it already\n     * is red due to {@link ConnectionEventType.CONNECTION_ERROR}), fading over to the next color when next event ({@link ConnectionEventType.CONNECTING}\n     * comes in.\n     */\n    LOST_CONNECTION: \"lostconnection\",\n\n    /**\n     * Events fired every 100ms while in state {@link ConnectionEventType.CONNECTING}, possibly continuing over to {@link ConnectionEventType.WAITING}.\n     * Notice that you will most probably not get an event with 0 seconds left, as that is when we (re-)transition to\n     * {@link ConnectionEventType.CONNECTING} and the countdown starts over (possibly with a larger timeout). Read more at\n     * {@link ConnectionEvent#countdownSeconds}.\n     * <p/>\n     * User Info Tip: Read more at {@link ConnectionEventType.CONNECTING} and {@link ConnectionEventType.WAITING}.\n     */\n    COUNTDOWN: \"countdown\"\n\n};\nObject.freeze(ConnectionEventType);\n","export { MessageType }\n\n/**\n * <b>Copied directly from MatsSocketServer.java</b>:\n * All Message Types (aka MatsSocket Envelope Types) used in the wire-protocol of MatsSocket.\n *\n * @enum {string}\n * @readonly\n */\nconst MessageType = {\n    /**\n     * A HELLO message must be part of the first Pipeline of messages, preferably alone. One of the messages in the\n     * first Pipeline must have the \"auth\" field set, and it might as well be the HELLO.\n     */\n    HELLO: \"HELLO\",\n\n    /**\n     * The reply to a {@link #HELLO}, where the MatsSocketSession is established, and the MatsSocketSessionId is\n     * returned. If you included a MatsSocketSessionId in the HELLO, signifying that you want to reconnect to an\n     * existing session, and you actually get a WELCOME back, it will be the same as what you provided - otherwise\n     * the connection is closed with {@link MatsSocketCloseCodes#SESSION_LOST}.\n     */\n    WELCOME: \"WELCOME\",\n\n    /**\n     * The sender sends a \"fire and forget\" style message.\n     */\n    SEND: \"SEND\",\n\n    /**\n     * The sender initiates a request, to which a {@link #RESOLVE} or {@link #REJECT} message is expected.\n     */\n    REQUEST: \"REQUEST\",\n\n    /**\n     * The sender should retry the message (the receiver could not handle it right now, but a Retry might fix it).\n     */\n    RETRY: \"RETRY\",\n\n    /**\n     * The specified message was Received, and acknowledged positively - i.e. the other party has decided to process\n     * it.\n     * <p/>\n     * The sender of the ACK has now taken over responsibility of the specified message, put it (at least the\n     * reference ClientMessageId) in its <i>Inbox</i>, and possibly started processing it. The reason for the Inbox\n     * is so that if it Receives the message again, it may just insta-ACK/NACK it and toss this copy out the window\n     * (since it has already handled it).\n     * <p/>\n     * When an ACK is received, the receiver may safely delete the acknowledged message from its <i>Outbox</i>.\n     */\n    ACK: \"ACK\",\n\n    /**\n     * The specified message was Received, but it did not acknowledge it - i.e. the other party has decided to NOT\n     * process it.\n     * <p/>\n     * The sender of the NACK has now taken over responsibility of the specified message, put it (at least the\n     * reference Client/Server MessageId) in its <i>Inbox</i> - but has evidently decided not to process it. The\n     * reason for the Inbox is so that if it Receives the message again, it may just insta-ACK/NACK it and toss this\n     * copy out the window (since it has already handled it).\n     * <p/>\n     * When an NACK is received, the receiver may safely delete the acknowledged message from its <i>Outbox</i>.\n     */\n    NACK: \"NACK\",\n\n    /**\n     * An \"Acknowledge ^ 2\", i.e. an acknowledge of the {@link #ACK} or {@link #NACK}. When the receiver gets this,\n     * it may safely delete the entry it has for the specified message from its <i>Inbox</i>.\n     * <p/>\n     * The message is now fully transferred from one side to the other, and both parties again has no reference to\n     * this message in their Inbox and Outbox.\n     */\n    ACK2: \"ACK2\",\n\n    /**\n     * A RESOLVE-reply to a previous {@link #REQUEST} - if the Client did the {@code REQUEST}, the Server will\n     * answer with either a RESOLVE or {@link #REJECT}.\n     */\n    RESOLVE: \"RESOLVE\",\n\n    /**\n     * A REJECT-reply to a previous {@link #REQUEST} - if the Client did the {@code REQUEST}, the Server will answer\n     * with either a REJECT or {@link #RESOLVE}.\n     */\n    REJECT: \"REJECT\",\n\n    /**\n     * Request from Client: The Client want to subscribe to a Topic, the TopicId is specified in 'eid'.\n     */\n    SUB: \"SUB\",\n\n    /**\n     * Request from Client: The Client want to unsubscribe to a Topic, the TopicId is specified in 'eid'.\n     */\n    UNSUB: \"UNSUB\",\n\n    /**\n     * Reply from Server: Subscription was OK. If this is a reconnect, this indicates that any messages that was\n     * lost \"while offline\" will now be delivered/\"replayed\".\n     */\n    SUB_OK: \"SUB_OK\",\n\n    /**\n     * Reply from Server: Subscription went OK, but you've lost messages: The messageId that was referenced in the\n     * {@link #SUB} was not known to the server, implying that there are at least one message that has expired, and\n     * as such it can be many - so you won't get any \"replayed\".\n     */\n    SUB_LOST: \"SUB_LOST\",\n\n    /**\n     * Reply from Server: Subscription was not authorized - no messages for this Topic will be delivered.\n     */\n    SUB_NO_AUTH: \"SUB_NO_AUTH\",\n\n    /**\n     * Topic message from Server: A message is issued on Topic, the TopicId is specified in 'eid', while the message\n     * is in 'msg'.\n     */\n    PUB: \"PUB\",\n\n    /**\n     * The server requests that the Client re-authenticates, where the Client should immediately get a fresh\n     * authentication and send it back using either any message it has pending, or in a separate {@link #AUTH}\n     * message. Message processing - both processing of received messages, and sending of outgoing messages (i.e.\n     * Replies to REQUESTs, or Server-initiated SENDs and REQUESTs) will be stalled until such auth is gotten.\n     */\n    REAUTH: \"REAUTH\",\n\n    /**\n     * From Client: The client can use a separate AUTH message to send over the requested {@link #REAUTH} (it could\n     * just as well put the 'auth' in a PING or any other message it had pending).\n     */\n    AUTH: \"AUTH\",\n\n    /**\n     * A PING, to which a {@link #PONG} is expected.\n     */\n    PING: \"PING\",\n\n    /**\n     * A Reply to a {@link #PING}.\n     */\n    PONG: \"PONG\"\n};\nObject.freeze(MessageType);\n","import './typedefs.js';\n// Repeating typedefs here, since 'tsc' otherwise don't create the 'export type FractionalMillis = number;' line.\n/**\n * Fractional milliseconds for high-res timing.\n * @typedef {number} FractionalMillis\n */\n\n/**\n * Timestamp, millis-since-epoch.\n * @typedef {number} Timestamp\n */\n\nexport { ReceivedEvent, ReceivedEventType }\n\n/**\n * Message Received on Server event: \"acknowledge\" or \"negative acknowledge\" - these are the events which the\n * returned Promise of a send(..) is settled with (i.e. then() and catch()), and which\n * {@link MatsSocket#request request}'s receivedCallback function are invoked with.\n *\n * @param {ReceivedEventType} type - {@link ReceivedEvent#type}\n * @param {string} traceId - {@link ReceivedEvent#traceId}\n * @param {Timestamp} sentTimestamp - {@link ReceivedEvent#sentTimestamp}\n * @param {Timestamp} receivedTimestamp - {@link ReceivedEvent#receivedTimestamp}\n * @param {Timestamp} roundTripMillis - {@link ReceivedEvent#roundTripMillis}\n * @param {string} description - {@link ReceivedEvent#description}\n * @class\n */\nfunction ReceivedEvent(type, traceId, sentTimestamp, receivedTimestamp, roundTripMillis, description) {\n    /**\n     * Values are from {@link ReceivedEventType}: Type of received event, either {@link ReceivedEventType#ACK \"ack\"},\n     * {@link ReceivedEventType#NACK \"nack\"} - <b>or {@link ReceivedEventType#SESSION_CLOSED \"sessionclosed\"} if the\n     * session was closed with outstanding initiations and MatsSocket therefore \"clears out\" these initiations.</b>\n     *s\n     * @type {ReceivedEventType}\n     */\n    this.type = type;\n\n    /**\n     * TraceId for this call / message.\n     *\n     * @type {string}\n     */\n    this.traceId = traceId;\n\n    /**\n     * Millis-since-epoch when the message was sent from the Client.\n     *\n     * @type {Timestamp}\n     */\n    this.sentTimestamp = sentTimestamp;\n\n    /**\n     * Millis-since-epoch when the ACK or NACK was received on the Client, millis-since-epoch.\n     *\n     * @type {Timestamp}\n     */\n    this.receivedTimestamp = receivedTimestamp;\n\n    /**\n     * Round-trip time in milliseconds from Initiation of flow (send, request, requestReplyTo) to Received\n     * acknowledgement (ACK/NACK) was received, basically <code>{@link #receivedTimestamp}\n     * - {@link #sentTimestamp}</code>, but depending on the browser/runtime, you might get higher resolution\n     * than integer milliseconds (i.e. fractions of milliseconds, a floating point number) - it depends on\n     * the resolution of <code>performance.now()</code>.\n     * <p/>\n     * Notice that Received-events might be de-prioritized on the Server side (batched up, with micro-delays\n     * to get multiple into the same batch), so this number should not be taken as the \"ping time\".\n     *\n     * @type {FractionalMillis}\n     */\n    this.roundTripMillis = roundTripMillis;\n\n    /**\n     * Sometimes, typically on Server NACKs (e.g. targetting non-existing Endpoint), the Server supplies a\n     * description to why this was no good.\n     *\n     * @type {string}\n     */\n    this.description = description;\n}\n\n/**\n * Types of {@link ReceivedEvent}.\n *\n * @enum {string}\n * @readonly\n */\nconst ReceivedEventType = {\n    /**\n     * If the Server-side MatsSocketEndpoint/Terminator accepted the message for handling (and if relevant,\n     * forwarded it to the Mats fabric). The returned Promise of send() is <i>resolved</i> with this type of event.\n     * The 'receivedCallback' of a request() will get both \"ack\" and {@link #NACK \"nack\"}, thus must check on\n     * the type if it makes a difference.\n     */\n    ACK: \"ack\",\n\n    /**\n     * If the Server-side MatsSocketEndpoint/Terminator dit NOT accept the message, either explicitly with\n     * context.deny(), or by failing with Exception. The returned Promise of send() is <i>rejected</i> with this\n     * type of event. The 'receivedCallback' of a request() will get both \"nack\" and {@link #ACK \"ack\"}, thus must\n     * check on the type if it makes a difference.\n     * <p/>\n     * Notice that a for a Client-initiated Request which is insta-rejected in the incomingHandler by invocation of\n     * context.reject(..), this implies <i>acknowledge</i> of the <i>reception</i> of the message, but <i>reject</i>\n     * as with regard to the </i>reply</i> (the Promise returned from request(..)).\n     */\n    NACK: \"nack\",\n\n    /**\n     * \"Synthetic\" event in that it is not a message from Server: A Client-to-Server\n     * {@link MatsSocket#request() Request} was not ACKed or NACKed by the server within the\n     * {@link MatsSocket#requestTimeoutMillis default request timeout} - or a specific timeout specified in the request\n     * invocation. In these situations, any nack- or receivedCallback will be invoked with a {@link ReceivedEvent}\n     * of this type.\n     */\n    TIMEOUT: \"timeout\",\n\n    /**\n     * \"Synthetic\" event in that it is not a message from Server: This only happens if the MatsSocketSession is\n     * closed with outstanding Initiations not yet Received on Server. In these situations, any nack- or\n     * receivedCallback will be invoked with a {@link ReceivedEvent} of this type.\n     */\n    SESSION_CLOSED: \"sessionclosed\"\n};\nObject.freeze(ReceivedEventType);\n","import './typedefs.js';\n// Repeating typedefs here, since 'tsc' otherwise don't create the 'export type FractionalMillis = number;' line.\n/**\n * Fractional milliseconds for high-res timing.\n * @typedef {number} FractionalMillis\n */\n\n/**\n * Timestamp, millis-since-epoch.\n * @typedef {number} Timestamp\n */\n\nexport { MessageEvent, MessageEventType }\n\n/**\n * Message Event - the event emitted for a {@link MatsSocket#request() Requests}'s Promise resolve() and reject()\n * (i.e. then() and catch()), and to a {@link MatsSocket#terminator() Terminator}'s resolveCallback and\n * rejectCallback functions for replies due to {@link MatsSocket#requestReplyTo() requestReplyTo}, and for Server\n * initiated Sends (to Terminators), and for the event to a {@link MatsSocket#endpoint() Endpoint} upon a Server\n * initiated Request, and for the event sent to a {@link MatsSocket#subscribe() Subscription}.\n *\n * @param {MessageEventType} type - {@link MessageEvent#type}\n * @param {object} data - {@link MessageEvent#data}\n * @param {string} traceId - {@link MessageEvent#traceId}\n * @param {string} messageId - {@link MessageEvent#messageId}\n * @param {Timestamp} receivedTimestamp - {@link MessageEvent#receivedTimestamp}\n * @class\n */\nfunction MessageEvent(type, data, traceId, messageId, receivedTimestamp) {\n    /**\n     * Values are from {@link MessageEventType}: Either {@link MessageEventType#SEND \"send\"} (for a Client\n     * Terminator when targeted for a Server initiated Send); {@link MessageEventType#REQUEST \"request\"} (for a\n     * Client Endpoint when targeted for a Server initiated Request); or {@link MessageEventType#RESOLVE \"resolve\"}\n     * or {@link MessageEventType#REJECT \"reject\"} (for settling of Promise from a Client-initiated Request, and\n     * for a Client Terminator when targeted as the reply-endpoint for a Client initiated Request) - <b>or\n     * {@link MessageEventType#SESSION_CLOSED \"sessionclosed\"} if the session was closed with outstanding Requests\n     * and MatsSocket therefore \"clears out\" these Requests.</b>\n     * <p/>\n     * Notice: In the face of {@link MessageType#SESSION_CLOSED \"sessionclosed\"} or {@link MessageType#TIMEOUT \"timeout\"},\n     * the {@link #data} property (i.e. the actual message from the server) will be <code>undefined</code>.\n     * Wrt. \"sessionclosed\", this is <i>by definition</i>: The Request was outstanding, meaning that an answer from the\n     * Server had yet to come. This is opposed to a normal REJECT settling from the Server-side MatsSocketEndpoint,\n     * which may choose to include data with a rejection. The same basically goes wrt. \"timeout\", as the Server\n     * has not replied yet.\n     *\n     * @type {MessageEventType}\n     */\n    this.type = type;\n\n    /**\n     * The actual data from the other peer.\n     * <p/>\n     * Notice: In the face of {@link MessageType#SESSION_CLOSED \"sessionclosed\"} or {@link MessageType#TIMEOUT \"timeout\"},\n     * this value will be <code>undefined</code>.\n     * Wrt. \"sessionclosed\", this is <i>by definition</i>: The Request was outstanding, meaning that an answer from the\n     * Server had yet to come. This is opposed to a normal REJECT settling from the Server-side MatsSocketEndpoint,\n     * which may choose to include data with a rejection. The same basically goes wrt. \"timeout\", as the Server\n     * has not replied yet.\n     *\n     * @type {object}\n     */\n    this.data = data;\n\n    /**\n     * When a Terminator gets invoked to handle a Reply due to a Client initiated {@link MatsSocket#requestReplyTo},\n     * this holds the 'correlationInformation' object that was supplied in the requestReplyTo(..) invocation.\n     *\n     * @type {object}\n     */\n    this.correlationInformation = undefined;\n\n    /**\n     * The TraceId for this call / message.\n     *\n     * @type {string}\n     */\n    this.traceId = traceId;\n\n    /**\n     * Either the ClientMessageId if this message is a Reply to a Client-initiated Request (i.e. this message is a\n     * RESOLVE or REJECT), or ServerMessageId if this originated from the Server (i.e. SEND or REQUEST);\n     *\n     * @type {string}\n     */\n    this.messageId = messageId;\n\n    /**\n     * millis-since-epoch when the Request, for which this message is a Reply, was sent from the\n     * Client. If this message is not a Reply to a Client-initiated Request, it is undefined.\n     *\n     * @type {Timestamp}\n     */\n    this.clientRequestTimestamp = undefined;\n\n    /**\n     * When the message was received on the Client, millis-since-epoch.\n     *\n     * @type {Timestamp}\n     */\n    this.receivedTimestamp = receivedTimestamp;\n\n    /**\n     * For {@link MatsSocket#request()} and {@link MatsSocket#requestReplyTo()} Requests: Round-trip time in\n     * milliseconds from Request was performed to Reply was received, basically <code>{@link #receivedTimestamp} -\n     * {@link #clientRequestTimestamp}</code>, but depending on the browser/runtime, you might get higher resolution\n     * than integer milliseconds (i.e. fractions of milliseconds, a floating point number) - it depends on the\n     * resolution of <code>performance.now()</code>.\n\n     * @type {FractionalMillis}\n     */\n    this.roundTripMillis = undefined;\n\n    /**\n     * If debugging is requested, by means of {@link MatsSocket#debug} or the config object in the send, request and\n     * requestReplyTo, this will contain a {@link DebugInformation} instance. However, the contents of that object\n     * is decided by what you request, and what the authorized user is allowed to get as decided by the\n     * AuthenticationPlugin when authenticating the user.\n     */\n    this.debug = undefined;\n}\n\n/**\n * Types of {@link MessageEvent}.\n *\n * @enum {string}\n * @readonly\n */\nconst MessageEventType = {\n    /** Message sent from Server to Client as a resolve for a previous request. */\n    RESOLVE: \"resolve\",\n\n    /** Message sent from Server to Client as a reject for a previous request. */\n    REJECT: \"reject\",\n\n    /** Message sent from Server to a Client Terminator. */\n    SEND: \"send\",\n\n    /** Message sent from Server to a Client Endpoint (expecting a reply). */\n    REQUEST: \"request\",\n\n    /** Message sent from Server to a Client Topic. */\n    PUB: \"pub\",\n\n    /**\n     * \"Synthetic\" event in that it is not a message from Server: A Client-to-Server\n     * {@link MatsSocket#request() Request} was not replied to by the server within the\n     * {@link MatsSocket#requestTimeout default request timeout} - or a specific timeout specified in the request\n     * invocation. In these situations, the Request Promise is rejected with a {@link MessageEvent} of this type,\n     * and the {@link MessageEvent#data} value is undefined.\n     */\n    TIMEOUT: \"timeout\",\n\n    /**\n     * \"Synthetic\" event in that it is not a message from Server: This only happens if the MatsSocketSession is\n     * closed with outstanding Client-to-Server {@link MatsSocket#request() Requests} not yet replied to by the\n     * server. In these situations, the Request Promise is rejected with a {@link MessageEvent} of this type, and\n     * the {@link MessageEvent#data} value is undefined.\n     */\n    SESSION_CLOSED: \"sessionclosed\"\n};\nObject.freeze(MessageEventType);\n","export { SubscriptionEvent, SubscriptionEventType }\n\n/**\n * Information about how a subscription went on the server side. If you do two subscriptions to the same Topic,\n * you will still only get one such message - thus if you want one for each, you'd better add two listeners too,\n * <i>before</i> doing any of the subscribes.\n * <p />\n * Note: this also fires upon every reconnect. <b>Make note of the {@link SubscriptionEventType#LOST_MESSAGES}!</b>\n *\n * @param type {SubscriptionEventType} - {@link SubscriptionEvent#type}\n * @param topicId {string} - {@link SubscriptionEvent#topicId}\n * @class\n */\nfunction SubscriptionEvent(type, topicId) {\n    /**\n     * How the subscription fared.\n     *\n     * @type {SubscriptionEventType}\n     */\n    this.type = type;\n\n    /**\n     * What TopicIc this relates to.\n     *\n     * @type {string}\n     */\n    this.topicId = topicId;\n\n}\n\n/**\n * Type of {@link SubscriptionEvent}.\n *\n * @enum {string}\n * @readonly\n */\nconst SubscriptionEventType = {\n    /**\n     * The subscription on the server side went ok. If reconnect, any missing messages are now being sent.\n     */\n    OK: \"ok\",\n\n    /**\n     * You were not authorized to subscribe to this Topic.\n     */\n    NOT_AUTHORIZED: \"notauthorized\",\n\n    /**\n     * Upon reconnect, the \"last message Id\" was not known to the server, implying that there are lost messages.\n     * Since you will now have to handle this situation by other means anyway (e.g. do a request for all stock ticks\n     * between the last know timestamp and now), you will thus not get any of the lost messages even if the server\n     * has some.\n     */\n    LOST_MESSAGES: \"lostmessages\"\n};\nObject.freeze(SubscriptionEventType);\n","import './typedefs.js';\n// Repeating typedefs here, since 'tsc' otherwise don't create the 'export type FractionalMillis = number;' line.\n/**\n * Fractional milliseconds for high-res timing.\n * @typedef {number} FractionalMillis\n */\n\n/**\n * Timestamp, millis-since-epoch.\n * @typedef {number} Timestamp\n */\n\nimport { MessageEvent, MessageEventType } from './MessageEvent.js';\n\nexport { InitiationProcessedEvent, InitiationProcessedEventType }\n\n\n/**\n * (Metrics) Information about Client-to-Server SENDs and REQUESTs (aka <i>Client Initiations</i>), including\n * experienced round-trip times for both Received acknowledgement, and for Requests, the Request-to-Reply time.\n * <p />\n * For each message that, for sends, has been acknowledged received, and for requests, has been replied to, gives\n * this information:\n * <ul>\n *     <li>Client MessageId  (envelope's 'cmid').</li>\n *     <li>Timestamp of when message was sent.</li>\n *     <li>Target MatsSocket Endpoint or Terminator Id  (envelope's 'eid').</li>\n *     <li>TraceId for the SEND or REQUEST  (envelope's 'tid').</li>\n *     <li>The outgoing message, i.e. the SEND or the REQUEST message  (envelope's 'msg').</li>\n *     <li>Experienced Received Acknowledge round-trip time.</li>\n *     <li>For {@link MatsSocket#request() Requests}, the Reply's {@link MessageEventType}</li>\n *     <li>For {@link MatsSocket#requestReplyTo() requestReplyTo} Requests, the replyToTerminatorId.</li>\n *     <li>For Requests, the total experienced Request-to-Reply time.</li>\n *     <li>For Requests, the Reply {@link MessageEvent} object.</li>\n * </ul>\n * You may \"subscribe\" to <code>InitiationProcessedEvents</code> using\n * {@link MatsSocket#addInitiationProcessedEventListener()}, and you may get the latest such events from the\n * property {@link MatsSocket#initiations}.\n * <p />\n * <b>Note on event ordering</b>:\n * <ul>\n *     <li>send: First {@link ReceivedEvent} is issued. Then an {@link InitiationProcessedEvent} is added to\n *         {@link MatsSocket#initiations}, and then all {@link InitiationProcessedEvent} listeners are invoked</li>\n *     <li>request/requestReplyTo: First {@link ReceivedEvent} is issued (i.e. ack/nack), then when the reply\n *     comes back to the server, an {@link InitiationProcessedEvent} is added to {@link MatsSocket#initiations}, and\n *     then all {@link InitiationProcessedEvent} listeners are invoked, and finally the {@link MessageEvent} is\n *     delivered, either as settling of the return Reply-Promise (for 'request'), or invocation of the Terminator's\n *     message- or rejectCallbacks (for 'requestReplyTo').\n * </ul>\n *\n * @param {string} endpointId\n * @param {string} clientMessageId\n * @param {Timestamp} sentTimestamp\n * @param {FractionalMillis} sessionEstablishedOffsetMillis\n * @param {string} traceId\n * @param {Object} initiationMessage\n * @param {FractionalMillis} acknowledgeRoundTripMillis\n * @param {MessageEventType} replyMessageEventType\n * @param {string} replyToTerminatorId\n * @param {FractionalMillis} requestRoundTripMillis\n * @param {MessageEvent} replyMessageEvent\n * @class\n */\nfunction InitiationProcessedEvent(endpointId, clientMessageId, sentTimestamp, sessionEstablishedOffsetMillis, traceId, initiationMessage, acknowledgeRoundTripMillis, replyMessageEventType, replyToTerminatorId, requestRoundTripMillis, replyMessageEvent) {\n    /**\n     * Which initiation type of this flow, enum of {@link InitiationProcessedEventType}.\n     *\n     * @type {InitiationProcessedEventType}\n     */\n    this.type = ((replyToTerminatorId ? InitiationProcessedEventType.REQUEST_REPLY_TO : (replyMessageEventType ? InitiationProcessedEventType.REQUEST : InitiationProcessedEventType.SEND)));\n\n    /**\n     * Target Server MatsSocket Endpoint or Terminator Id  (envelope's 'eid').\n     *\n     * @type {string}\n     */\n    this.endpointId = endpointId;\n\n    /**\n     * The Client MessageId of the Initiation  (envelope's 'cmid'). For this particular MatsSocket library, this\n     * is currently an integer sequence id.\n     *\n     * @type {string}\n     */\n    this.clientMessageId = clientMessageId;\n\n    /**\n     * Millis-from-epoch when this initiation was sent.\n     *\n     * @type {Timestamp}\n     */\n    this.sentTimestamp = sentTimestamp;\n\n    /**\n     * The number of milliseconds offset for sending this message from the initial {@link ConnectionEventType#SESSION_ESTABLISHED} event for\n     * this MatsSocket - <b>this number will typically be negative for the first messages</b>: A negative number\n     * implies that the message was sent before the WELCOME was received, which again implies that the very first\n     * message will by definition have a negative offset since it is this message that starts the HELLO/WELCOME\n     * handshake and is thus enqueued before the WELCOME has been received. This is desirable: Upon application\n     * startup, stack up all requests that you need answer for to show the initial screen, and they will all be\n     * sent in a single pipeline, directly trailing the HELLO, their answers coming in as soon as possible after\n     * the WELCOME.\n     *\n     * @type {FractionalMillis}\n     */\n    this.sessionEstablishedOffsetMillis = sessionEstablishedOffsetMillis;\n\n    /**\n     * TraceId for the initiation - which follows through all parts of the processing  (envelope's 'tid').\n     *\n     * @type {string}\n     */\n    this.traceId = traceId;\n\n    /**\n     * The message object that was sent with the initiation, i.e. on send(), request() or requestReplyTo()  (outgoing envelope's 'msg').\n     *\n     * @type {Object}\n     */\n    this.initiationMessage = initiationMessage;\n\n    /**\n     * The experienced round-trip time for the Received Acknowledgement - this is the time back-and-forth.\n     *\n     * <b>Note that this number can be a float, not necessarily integer</b>.\n     *\n     * @type {FractionalMillis}\n     */\n    this.acknowledgeRoundTripMillis = acknowledgeRoundTripMillis;\n\n    // === For Requests.\n\n    /**\n     * The {@link MessageEventType} for Replies to Request Initiations.\n     *\n     * @type {string}\n     */\n    this.replyMessageEventType = replyMessageEventType;\n\n    /**\n     * The 'replyToTerminatorId' for {@link MatsSocket#requestReplyTo()}-Requests.\n     *\n     * @type {string}\n     */\n    this.replyToTerminatorId = replyToTerminatorId;\n\n    /**\n     * The experienced round-trip time from a Request initiation to the Reply (RESOLVE or REJECT) comes back.\n     *\n     * @type {FractionalMillis}\n     */\n    this.requestReplyRoundTripMillis = requestRoundTripMillis;\n\n    /**\n     * The Reply {@link MessageEvent} that was supplied to the Promise (on resolve/then or reject/catch) or ReplyTo\n     * Client {@link #terminator() Terminator}.\n     *\n     * @type {MessageEvent}\n     */\n    this.replyMessageEvent = replyMessageEvent;\n}\n\n/**\n * Type of {@link InitiationProcessedEvent} - the type of the <i>initiation</i> of a flow, which also\n * determines which fields of the <code>InitiationProcessedEvent</code> are set.\n *\n * @enum {string}\n * @readonly\n */\nconst InitiationProcessedEventType = {\n    /**\n     * Flow initiated with {@link MatsSocket#send()}. Fields whose name does not start with \"reply\" or \"request\"\n     * will be set.\n     */\n    SEND: \"send\",\n\n    /**\n     * Flow initiated with {@link MatsSocket#request()}. Will have all fields except\n     * {@link InitiationProcessedEvent#replyToTerminatorId} set.\n     */\n    REQUEST: \"request\",\n\n    /**\n     * Flow initiated with {@link MatsSocket#requestReplyTo()}. Will have <i>all</i> fields set.\n     */\n    REQUEST_REPLY_TO: \"requestreplyto\"\n};\nObject.freeze(InitiationProcessedEventType);\n","import './typedefs.js';\n// Repeating typedefs here, since 'tsc' otherwise don't create the 'export type FractionalMillis = number;' line.\n/**\n * Fractional milliseconds for high-res timing.\n * @typedef {number} FractionalMillis\n */\n\n/**\n * Timestamp, millis-since-epoch.\n * @typedef {number} Timestamp\n */\n\nexport { PingPong }\n\n/**\n * (Metric) A \"holding struct\" for pings and their experienced round-trip times - you may \"subscribe\" to ping results\n * using {@link MatsSocket#addPingPongListener()}, and you may get the latest pings from the property\n * {@link MatsSocket#pings}.\n *\n * @param {number} pingId\n * @param {Timestamp} sentTimestamp\n * @class\n */\nfunction PingPong(pingId, sentTimestamp) {\n    /**\n     * Sequence of the ping.\n     *\n     * @type {number}\n     */\n    this.pingId = pingId;\n\n    /**\n     * Millis-from-epoch when this ping was sent.\n     *\n     * @type {Timestamp}\n     */\n    this.sentTimestamp = sentTimestamp;\n\n    /**\n     * The experienced round-trip time for this ping-pong - this is the time back-and-forth.\n     *\n     * @type {FractionalMillis}\n     */\n    this.roundTripMillis = undefined;\n}\n","export { MatsSocketCloseCodes, MatsSocketCloseCodesUtil }\n\n/**\n * <b>Copied directly from MatsSocketServer.java</b>:\n * WebSocket CloseCodes used in MatsSocket, and for what. Using both standard codes, and MatsSocket-specific/defined\n * codes.\n * <p/>\n * Note: Plural \"Codes\" since that is what the JSR 356 Java WebSocket API {@link CloseCodes does..!}\n *\n * @enum {number}\n * @readonly\n */\nconst MatsSocketCloseCodes = {\n    /**\n     * Standard code 1008 - From Server side, Client should REJECT all outstanding and \"crash\"/reboot application:\n     * used when the we cannot authenticate.\n     */\n    VIOLATED_POLICY: 1008,\n\n    /**\n     * Standard code 1011 - From Server side, Client should REJECT all outstanding and \"crash\"/reboot application.\n     * This is the default close code if the MatsSocket \"onMessage\"-handler throws anything, and may also explicitly\n     * be used by the implementation if it encounters a situation it cannot recover from.\n     */\n    UNEXPECTED_CONDITION: 1011,\n\n    /**\n     * Standard code 1012 - From Server side, Client should REISSUE all outstanding upon reconnect: used when\n     * {@link MatsSocketServer#stop(int)} is invoked. Please reconnect.\n     */\n    SERVICE_RESTART: 1012,\n\n    /**\n     * Standard code 1001 - From Client/Browser side, client should have REJECTed all outstanding: Synonym for\n     * {@link #CLOSE_SESSION}, as the WebSocket documentation states <i>\"indicates that an endpoint is \"going away\",\n     * such as a server going down <b>or a browser having navigated away from a page.</b>\"</i>, the latter point\n     * being pretty much exactly correct wrt. when to close a session. So, if a browser decides to use this code\n     * when the user navigates away and the client MatsSocket library or employing application does not catch it,\n     * we'd want to catch this as a Close Session. Notice that I've not experienced a browser that actually utilizes\n     * this close code yet, though!\n     * <p/>\n     * <b>Notice that if a close with this close code <i>is initiated from the Server-side</i>, this should NOT be\n     * considered a CLOSE_SESSION by the neither the client nor the server!</b> At least Jetty's implementation of\n     * JSR 356 WebSocket API for Java sends GOING_AWAY upon socket close due to timeout. Since a timeout can happen\n     * if we loose connection and thus can't convey PINGs, the MatsSocketServer must not interpret Jetty's\n     * timeout-close as Close Session. Likewise, if the client just experienced massive lag on the connection, and\n     * thus didn't get the PING over to the server in a timely fashion, but then suddenly gets Jetty's timeout close\n     * with GOING_AWAY, this should not be interpreted by the client as the server wants to close the session.\n     */\n    GOING_AWAY: 1001,\n\n    /**\n     * 4000: Both from Server side and Client/Browser side, client should REJECT all outstanding:\n     * <ul>\n     * <li>From Browser: Used when the browser closes WebSocket \"on purpose\", wanting to close the session -\n     * typically when the user explicitly logs out, or navigates away from web page. All traces of the\n     * MatsSocketSession are effectively deleted from the server, including any undelivered replies and messages\n     * (\"push\") from server.</li>\n     * <li>From Server: {@link MatsSocketServer#closeSession(String)} was invoked, and the WebSocket to that client\n     * was still open, so we close it.</li>\n     * </ul>\n     */\n    CLOSE_SESSION: 4000,\n\n    /**\n     * 4001: From Server side, Client should REJECT all outstanding and \"crash\"/reboot application: A\n     * HELLO:RECONNECT was attempted, but the session was gone. A considerable amount of time has probably gone by\n     * since it last was connected. The client application must get its state synchronized with the server side's\n     * view of the world, thus the suggestion of \"reboot\".\n     */\n    SESSION_LOST: 4001,\n\n    /**\n     * 4002: Both from Server side and from Client/Browser side: REISSUE all outstanding upon reconnect:\n     * <ul>\n     * <li>From Client: The client just fancied a little break (just as if lost connection in a tunnel), used from\n     * integration tests.</li>\n     * <li>From Server: We ask that the client reconnects. This gets us a clean state and in particular new\n     * authentication (In case of using OAuth/OIDC tokens, the client is expected to fetch a fresh token from token\n     * server).</li>\n     * </ul>\n     */\n    RECONNECT: 4002,\n\n    /**\n     * 4003: From Server side: Currently used in the specific situation where a MatsSocket client connects with\n     * the same MatsSocketSessionId as an existing WebSocket connection. This could happen if the client has\n     * realized that a connection is wonky and wants to ditch it, but the server has not realized the same yet.\n     * When the server then gets the new connect, it'll see that there is an active WebSocket already. It needs\n     * to close that. But the client \"must not do anything\" other than what it already is doing - reconnecting.\n     */\n    DISCONNECT: 4003,\n\n    /**\n     * 4004: From Server side: Client should REJECT all outstanding and \"crash\"/reboot application: Used when the\n     * client does not speak the MatsSocket protocol correctly. Session is closed.\n     */\n    MATS_SOCKET_PROTOCOL_ERROR: 4004,\n};\nObject.freeze(MatsSocketCloseCodes);\n\n/**\n * Utility functions for {@link MatsSocketCloseCodes}.\n * @namespace\n */\nconst MatsSocketCloseCodesUtil = (() => {\n    // Build a reverse lookup once, O(1) lookups later.\n    const REVERSE = new Map(\n        Object.entries(MatsSocketCloseCodes).map(([k, v]) => [v, k])\n    );\n\n    return {\n        /**\n         * Resolve numeric code -> enum key name, or \"UNKNOWN(<code>)\".\n         * @param {number} code\n         * @returns {string}\n         */\n        nameFor(code) {\n            const name = REVERSE.get(code);\n            return name ?? `UNKNOWN(${code})`;\n        },\n    };\n})();","export { ErrorEvent }\n\n/**\n * The Event object supplied to listeners added via {@link MatsSocket#addErrorEventListener()}.\n *\n * @param type {string}\n * @param message {string}\n * @param object {Object}\n * @class\n */\nfunction ErrorEvent(type, message, object) {\n    /**\n     * Type of error - describes the situation where the error occurred. Currently has no event-type enum.\n     *\n     * @type {string}\n     */\n    this.type = type;\n\n    /**\n     * The error message\n     *\n     * @type {string}\n     */\n    this.message = message;\n\n    /**\n     * Some errors supply a relevant object: Event for WebSocket errors, The attempted processed MatsSocket Envelope\n     * when envelope processing fails, or the caught Error in a try-catch (typically when invoking event listeners).\n     * <b>For submitting errors back to the home server, check out {@link ErrorEvent#referenceAsString}.</b>\n     *\n     * @type {Object}\n     */\n    this.reference = object;\n\n    /**\n     * Makes a string (<b>chopped the specified max number of characters, default 1024 chars</b>) out of the\n     * {@link ErrorEvent#reference} Object, which might be useful if you want to send this back over HTTP - consider\n     * <code>encodeURIComponent(referenceAsString)</code> if you want to send it over the URL. First tries to use\n     * <code>JSON.stringify(reference)</code>, failing that, it uses <code>\"\"+reference</code>. Then chops to max\n     * 1024, using \"...\" to denote the chop.\n     *\n     * @param {number} maxLength the max number of characters that will be returned, with any chop denoted by \"...\".\n     * @returns {string}\n     */\n    this.referenceAsString = function (maxLength = 1024) {\n        let result;\n        try {\n            result = JSON.stringify(this.reference);\n        } catch (err) {\n            /* no-op */\n        }\n        if (typeof result !== 'string') {\n            result = \"\" + this.reference;\n        }\n        return (result.length > maxLength ? result.substring(0, maxLength - 3) + \"...\" : result);\n    };\n}\n","import './typedefs.js';\n// Repeating typedefs here, since 'tsc' otherwise don't create the 'export type FractionalMillis = number;' line.\n/**\n * Fractional milliseconds for high-res timing.\n * @typedef {number} FractionalMillis\n */\n\n/**\n * Timestamp, millis-since-epoch.\n * @typedef {number} Timestamp\n */\n\n\nexport { DebugInformation, DebugOption }\n\n/**\n * Meta-information for the call, availability depends on the allowed debug options for the authenticated user,\n * and which information is requested in client. Notice that Client side and Server side might have wildly differing\n * ideas of what the time is, which means that timestamps comparison between Server and Client must be evaluated\n * with massive interpretation.\n *\n * @param {Timestamp} clientMessageSent\n * @param {number} requestedDebugOptions\n * @param {object} envelope\n * @param {Timestamp} receivedTimestamp\n * @class\n */\nfunction DebugInformation(clientMessageSent, requestedDebugOptions, envelope, receivedTimestamp) {\n    /**\n     * From client: When the message was sent, millis-from-epoch.\n     * @type {Timestamp}\n     */\n    this.clientMessageSent = clientMessageSent;\n\n    /**\n     * From client: What {@link DebugOption}s (bitfield) was requested by the client of when message was sent.\n     * @type {number}\n     */\n    this.requestedDebugOptions = requestedDebugOptions;\n\n    /**\n     * From server: Description if anything didn't go as expected.\n     * @type {string}\n     */\n    this.description = envelope.desc;\n\n    /**\n     * When this message was received by the client.\n     * @type {Timestamp}\n     */\n    this.messageReceived = receivedTimestamp;\n\n    /**\n     * (Only if debug) From server: What {@link DebugOption}s (bitfield) was resolved/given by the server, based on the\n     * {@link DebugInformation#requestedDebugOptions} and authorization.\n     * @type {number}\n     */\n    this.resolvedDebugOptions = 0; // default to 0 if no debug object in the envelope\n\n    /**\n     * (Only if debug) When the MatsSocket message from the client was received by the MatsSocketServer.\n     * @type {Timestamp}\n     */\n    this.clientMessageReceived = undefined;\n    /**\n     * (Only if debug) Which MatsSocketServer node received (and thus initial-processed) the MatsSocket message.\n     * @type {string}\n     */\n    this.clientMessageReceivedNodename = undefined;\n\n    /**\n     * (Only if debug) When the Mats3 message was sent onto the Mats3 Fabric on server side.\n     * @type {Timestamp}\n     */\n    this.matsMessageSent = undefined;\n\n    /**\n     * (Only if debug) When the Mats3 reply was received by the MatsSocketServer.\n     * @type {Timestamp}\n     */\n    this.matsMessageReplyReceived = undefined;\n    /**\n     * (Only if debug) Which MatsSocketServer node received the reply for the Mats3 message (might not be the same that sent it).\n     * @type {string}\n     */\n    this.matsMessageReplyReceivedNodename = undefined;\n\n    /**\n     * (Only if debug) When the MatsSocket message was produced on the server side.\n     * @type {Timestamp}\n     */\n    this.serverMessageCreated = undefined;\n    /**\n     * (Only if debug) Which MatsSocketServer node created the MatsSocket message.\n     * @type {string}\n     */\n    this.serverMessageCreatedNodename = undefined;\n\n    /**\n     * (Only if debug) When the MatsSocket message was sent from server to client.\n     * @type {Timestamp}\n     */\n    this.messageSentToClient = undefined;\n    /**\n     * (Only if debug) Which MatsSocketServer node sent the MatsSocket message to the client (the one that held the\n     * MatsSocket session at the sending time. This might be different from the MatsSocketServer node that held the\n     * session at the receiving time).\n     * @type {string}\n     */\n    this.messageSentToClientNodename = undefined;\n\n    if (envelope.debug) {\n        this.resolvedDebugOptions = envelope.debug.resd;\n\n        this.clientMessageReceived = envelope.debug.cmrts;\n        this.clientMessageReceivedNodename = envelope.debug.cmrnn;\n\n        this.matsMessageSent = envelope.debug.mmsts;\n        this.matsMessageReplyReceived = envelope.debug.mmrrts;\n        this.matsMessageReplyReceivedNodename = envelope.debug.mmrrnn;\n\n        this.serverMessageCreated = envelope.debug.smcts;\n        this.serverMessageCreatedNodename = envelope.debug.smcnn;\n\n        this.messageSentToClient = envelope.debug.mscts;\n        this.messageSentToClientNodename = envelope.debug.mscnn;\n    }\n}\n\n/**\n * <b>Copied directly from AuthenticationPlugin.java</b>:\n * Types of debug information you can request, read more at {@link MatsSocket#debug} and {@link MessageEvent#debug}.\n *\n * @enum {number}\n * @readonly\n */\nconst DebugOption = {\n    /**\n     * Timing info of the separate phases. Note that time-skew between different nodes must be taken into account.\n     */\n    TIMESTAMPS: 1, // was 0b0000_0001 (changed due to underscores and possibly binary being a bit too edgy for JS in 2021)\n\n    /**\n     * Node-name of the handling nodes of the separate phases.\n     */\n    NODES: 2, // was 0b0000_0010\n\n    /**\n     * <code>AuthenticationPlugin</code>-specific \"Option A\" - this is not used by MatsSocket itself, but can be employed\n     * and given a meaning by the <code>AuthenticationPlugin</code>.\n     * <p/>\n     * Notice: You might be just as well off by implementing such functionality on the <code>Principal</code> returned by\n     * the <code>AuthenticationPlugin</code> (\"this user is allowed to request these things\") - and on the request DTOs\n     * from the Client (\"I would like to request these things\").\n     */\n    CUSTOM_A: 64, // was 0b0100_0000\n\n    /**\n     * <code>AuthenticationPlugin</code>-specific \"Option B\" - this is not used by MatsSocket itself, but can be employed\n     * and given a meaning by the <code>AuthenticationPlugin</code>.\n     * <p/>\n     * Notice: You might be just as well off by implementing such functionality on the <code>Principal</code> returned by\n     * the <code>AuthenticationPlugin</code> (\"this user is allowed to request these things\") - and on the request DTOs\n     * from the Client (\"I would like to request these things\").\n     */\n    CUSTOM_B: 128 // was 0b1000_0000\n};\nObject.freeze(DebugOption);\n","import {AuthorizationRequiredEvent, AuthorizationRequiredEventType} from './AuthorizationRequiredEvent.js';\nimport {ConnectionState} from './ConnectionState.js';\nimport {ConnectionEvent, ConnectionEventType} from './ConnectionEvent.js';\nimport {MessageType} from './MessageType.js';\nimport {ReceivedEvent, ReceivedEventType} from './ReceivedEvent.js';\nimport {MessageEvent, MessageEventType} from './MessageEvent.js';\nimport {SubscriptionEvent, SubscriptionEventType} from \"./SubscriptionEvent.js\";\nimport {InitiationProcessedEvent, InitiationProcessedEventType} from './InitiationProcessedEvent.js';\nimport {PingPong} from './PingPong.js';\nimport {MatsSocketCloseCodes, MatsSocketCloseCodesUtil} from './MatsSocketCloseCodes.js';\nimport {ErrorEvent} from './ErrorEvent.js';\nimport {DebugInformation, DebugOption} from \"./DebugInformation.js\";\n\nexport {\n    MatsSocket,\n    AuthorizationRequiredEvent, AuthorizationRequiredEventType,\n    ConnectionState,\n    ConnectionEvent, ConnectionEventType,\n    MessageType,\n    ReceivedEvent, ReceivedEventType,\n    MessageEvent, MessageEventType,\n    SubscriptionEvent, SubscriptionEventType,\n    InitiationProcessedEvent, InitiationProcessedEventType,\n    PingPong,\n    MatsSocketCloseCodes,\n    ErrorEvent,\n    DebugOption, DebugInformation\n}\n\n/**\n * Creates a MatsSocket, requiring the using Application's name and version, and which URLs to connect to.\n * <p/>\n * Note: Public, Private and Privileged modelled after\n * <a href=\"http://crockford.com/javascript/private.html\">http://crockford.com/javascript/private.html</a>\n *\n * @param {string} appName the name of the application using this MatsSocket.js client library\n * @param {string} appVersion the version of the application using this MatsSocket.js client library\n * @param {array} urls an array of WebSocket URLs speaking 'matssocket' protocol, or a single string URL.\n * @param {object} config an optional object carrying extra configuration. Current sole key: 'webSocketFactory': how to\n * make WebSockets, not required in a browser setting as it will use window.WebSocket if not set.\n * @class\n */\nfunction MatsSocket(appName, appVersion, urls, config = null) {\n    let CLIENT_LIB_VERSION = \"1.0.0-2025-10-27\";\n    let CLIENT_LIB_NAME_AND_VERSION = \"MatsSocket.js,\" + CLIENT_LIB_VERSION;\n\n    // :: Validate primary arguments\n    if (typeof appName !== \"string\") {\n        throw new Error(\"appName must be a string, was: [\" + appName + \"]\");\n    }\n    if (typeof appVersion !== \"string\") {\n        throw new Error(\"appVersion must be a string, was: [\" + appVersion + \"]\");\n    }\n    // 'urls' must either be a string, String, or an Array that is not 0 elements.\n    let urlsOk = ((typeof urls === 'string') || (urls instanceof String)) || (Array.isArray(urls) && urls.length > 0);\n    if (!urlsOk) {\n        throw new Error(\"urls must have at least 1 url set, got: [\" + urls + \"]\");\n    }\n\n    // If we haven't gotten 'webSocketFactory', and we're in Node.js env, this field is set to true, and an attempt to\n    // import('ws') is performed. When the import has gone either OK or not OK, this field is set to false, and the the\n    // next field's value will be invoked.\n    let _nodeJsTryingToImportWebSocketModule = false;\n    // If !undefined, will be invoked once import of WebSocket module is either OK or Not OK.\n    // If the 'webSocketFactory' is !undefined, it went OK. If the above field is false, it went to hell.\n    let _nodeJsCallbackOnceWebSocketModuleResolved = undefined;\n    // Note: Alternative would be main vs. browser in package.json, e.g. here: https://stackoverflow.com/a/67393553/39334\n\n    // :: Provide default for socket factory if not defined.\n    let webSocketFactory = undefined;\n    if (config) {\n        if (config.webSocketFactory) {\n            if (typeof config.webSocketFactory !== \"function\") {\n                throw new Error(\"config.webSocketFactory should be a function, instead got [\" + (typeof config.webSocketFactory) + \"].\");\n            }\n            webSocketFactory = config.webSocketFactory;\n        } else if (config.webSocket) {\n            if (typeof config.webSocket !== \"function\") {\n                throw new Error(\"config.webSocket should be a function (constructor), instead got [\" + (typeof config.webSocket) + \"].\");\n            }\n            webSocketFactory = function (url, protocol) {\n                return new config.webSocket(url, protocol);\n            };\n        }\n    }\n    // ?: Did we get webSocketFactory from 'config'?\n    if (!webSocketFactory) {\n        // -> No, so try for global WebSocket\n        if (typeof WebSocket === \"function\") {\n            webSocketFactory = (url, protocol) => new WebSocket(url, protocol);\n        } else if (_isNodeJs()) {\n            _nodeJsTryingToImportWebSocketModule = true;\n            // -> Seemingly Node.js environment, try to dynamically import the 'ws' library.\n            // NOTE: Such import is specified as an asynchronous operation, albeit it seems synchronous when running in Node.\n            // However, we'll have to treat it as async, so a bit of handling both here and in the WebSocket creation\n            // code below, by means of \"stop process and restart once import resolved\" logic.\n\n            _importWsLazy()\n                .then((ws) => {\n                    log(\"Constructor: NodeJs import('ws') went OK: Got WebSocket module\");\n                    const {default: WebSocket} = ws;\n                    setTimeout(() => {\n                        webSocketFactory = (url, protocol) => new WebSocket(url, protocol);\n                        log(\"Constructor: 'webSocketFactory' is now set.\");\n                        _nodeJsTryingToImportWebSocketModule = false;\n                        if (typeof _nodeJsCallbackOnceWebSocketModuleResolved === 'function') {\n                            log(\"Constructor: Invoking callback-method to restart WebSocket creation attempt.\");\n                            _nodeJsCallbackOnceWebSocketModuleResolved();\n                        }\n                    }, 0)\n                })\n                .catch(reason => {\n                    _nodeJsTryingToImportWebSocketModule = false;\n                    error(\"Import of WebSocket module failed\", \"In Node.js environment, the import('ws') failed\", reason);\n                });\n        }\n        else {\n            throw new Error(\"Missing config.webSocket, config.webSocketFactory, global WebSocket (window.WebSocket),\" +\n                \" and seemingly not Node.js so cannot dynamically import 'ws' module: Cannot create MatsSocket.\");\n        }\n    }\n\n    // :: Polyfill performance.now() for Node.js: if window.performance is present, use this.\n    let performance = ((typeof (window) === \"object\" && window.performance) || {\n        now: function now() {\n            return Date.now();\n        }\n    });\n\n    const that = this;\n    const userAgent = (typeof (self) === 'object' && typeof (self.navigator) === 'object') ? self.navigator.userAgent : \"Unknown\";\n\n    // Ensure that 'urls' is an array or 1 or several URLs.\n    urls = [].concat(urls);\n\n\n    // ==============================================================================================\n    // PUBLIC:\n    // ==============================================================================================\n\n    /**\n     * 'sessionId' is set when we get the SessionId from WELCOME, set back to undefined on SessionClose\n     * (along with _matsSocketOpen = false)\n     *\n     * @type {string}\n     */\n    this.sessionId = undefined;\n\n    /**\n     * Whether to log via console.log. The logging is quite extensive. <b>Default <code>false</code></b>.\n     *\n     * @type {boolean}\n     */\n    this.logging = false;\n\n    /**\n     * Whether to log errors via console.error. <b>Default <code>true</code></b>.\n     *\n     * @type {boolean}\n     */\n    this.errorLogging = true;\n\n    /**\n     * \"Out-of-band Close\" refers to a small hack to notify the server about a MatsSocketSession being Closed even\n     * if the WebSocket is not live anymore: When {@link MatsSocket#close} is invoked, an attempt is done to close\n     * the WebSocket with CloseCode {@link MatsSocketCloseCodes.CLOSE_SESSION} - but whether the WebSocket is open\n     * or not, this \"Out-of-band Close\" will also be invoked if enabled and MatsSocket SessionId is present.\n     * <p/>\n     * Values:\n     * <ul>\n     *     <li>\"Falsy\", e.g. <code>false</code>: Disables this functionality</li>\n     *     <li>A <code>function</code>: The function is invoked when close(..) is invoked, the\n     *         single parameter being an object with two keys: <code>'webSocketUrl'</code> is the current WebSocket\n     *         url, i.e. the URL that the WebSocket was connected to, e.g. \"wss://example.com/matssocket\".\n     *         <code>'sessionId'</code> is the current MatsSocket SessionId - the one we're trying to close.</li>\n     *     <li>\"Truthy\", e.g. <code>true</code> <b>(default)</b>: When this MatsSocket library is used in\n     *         a web browser context, the following code is executed:\n     *         <code>navigator.sendBeacon(webSocketUrl.replace('ws', 'http')+\"/close_session?sessionId={sessionId}\")</code>.\n     *         Note that replace is replace-first, and that an extra 's' in 'wss' thus results in 'https'.</li>\n     * </ul>\n     * The default is <code>true</code>.\n     * <p/>\n     * Note: A 'beforeunload' listener invoking {@link MatsSocket#close} is attached when running in a web browser,\n     * so that if the user navigates away, the current MatsSocketSession is closed.\n     *\n     * @type {(function|boolean)}\n     */\n    this.outofbandclose = true;\n\n    /**\n     * \"Pre Connection Operation\" refers to a hack whereby the MatsSocket performs a specified operation - by default\n     * a {@link XMLHttpRequest} to the same URL as the WebSocket will be connected to - before initiating the\n     * WebSocket connection. The goal of this solution is to overcome a deficiency with the WebSocket Web API\n     * where it is impossible to add headers, in particular \"Authorization\": The XHR adds the Authorization header\n     * as normal, and the server side can transfer this header value over to a Cookie (e.g. named \"MatsSocketAuthCookie\").\n     * When the WebSocket connect is performed, the cookies will be transferred along with the initial \"handshake\"\n     * HTTP Request - and the AuthenticationPlugin on the server side can then validate the Authorization header -\n     * now present in a cookie. <i>Note: One could of course have supplied it in the URL of the WebSocket HTTP Handshake,\n     * but this is very far from ideal, as a live authentication then could be stored in several ACCESS LOG style\n     * logging systems along the path of the WebSocket HTTP Handshake Request call.</i>\n     * <p/>\n     * Values:\n     * <ul>\n     *     <li>\"Falsy\", e.g. <code>false</code> <b>(default)</b>: Disables this functionality.</li>\n     *     <li>A <code>string</code>: Performs a <code>XMLHttpRequest</code> with the URL set to the specified string, with the\n     *     HTTP Header \"<code>Authorization</code>\" set to the current AuthorizationValue. Expects 200, 202 or 204\n     *     as returned status code to go on.</li>\n     *     <li>A <code>function</code>: Invokes the function with a parameter object containing <code>'webSocketUrl'</code>,\n     *     which is the current WebSocket URL that we will connect to when this PreConnectionOperation has gone through,\n     *     and <code>'authorization'</code>, which is the current Authorization Value. <b>Expects\n     *     a two-element array returned</b>: [abortFunction, requestPromise]. The abortFunction is invoked when\n     *     the connection-retry system deems the current attempt to have taken too long time. The requestPromise must\n     *     be resolved by your code when the request has been successfully performed, or rejected if it didn't go through.\n     *     In the latter case, a new invocation of the 'preconnectoperation' will be performed after a countdown,\n     *     possibly with a different 'webSocketUrl' value if the MatsSocket is configured with multiple URLs.</li>\n     *     <li>\"Truthy\", e.g. <code>true</code>: Performs a <code>XMLHttpRequest</code> to the same URL as\n     *     the WebSocket URL, with \"ws\" replaced with \"http\", similar to {@link MatsSocket#outofbandclose}, and the HTTP\n     *     Header \"<code>Authorization</code>\" set to the current Authorization Value. Expects 200, 202 or 204 as\n     *     returned status code to go on.</li>\n     * </ul>\n     * The default is <code>false</code>.\n     * <p/>\n     * Note: For inspiration for the function-style value of this config, look in the source for the method\n     * <code>w_defaultXhrPromiseFactory(params)</code>.\n     * <p/>\n     * Note: A WebSocket is set up with a single HTTP Request, called the \"Upgrade\" or \"Handshake\" request. The\n     * point about being able to send Authorization along with the WebSocket connect only refers to this initial\n     * HTTP Request. Subsequent updates of the Authorization by means of invocation of\n     * {@link MatsSocket#setCurrentAuthorization} will not result in new HTTP calls - these new Authorization\n     * strings are sent in-band with WebSocket messages (MatsSocket envelopes).\n     *\n     * @type {(boolean|string|function)}\n     */\n    this.preconnectoperation = false;\n\n    /**\n     * A bit field requesting different types of debug information from the server - the flags/bits are defined in\n     * {@link DebugOption}. The information concerns timings and which server nodes have handled the messages.\n     * <p/>\n     * This field is used as the default for requests sent to the server, but individual requests may also set\n     * the debug flags explicitly (i.e. override) by use of the optional \"config\" object on\n     * {@link MatsSocket#requestReplyTo} or {@link MatsSocket#request}.\n     * <p/>\n     * To facilitate debug information also on Server initiated messages, the <i>last sent</i> debug flags is\n     * also stored on the server and used when messages originate there (i.e. Server-to-Client SENDs and REQUESTs).\n     * This goes both if the default was used (this flag), or overridden-per-request config: The last flag sent over\n     * is used for any subsequent server-initiated message. This is arguably a pretty annoying way to control the server\n     * initiated debug flags - vote for <a href=\"https://github.com/centiservice/matssocket/issues/13\">Issue 13</a>\n     * if you want something more explicit.\n     * <p/>\n     * The value is a bit field (values in {@link DebugOption}), so you bitwise-or (or simply add) together the\n     * different things you want.\n     * <p/>\n     * The value from the client is bitwise-and'ed together with the debug capabilities the authenticated user has\n     * gotten by the AuthenticationPlugin on the Server side. This means that the AuthenticationPlugin ultimately\n     * controls how much info the accessing user is allowed to get.\n     * <p/>\n     * Default is <code>0</code>, i.e. no debug.\n     *\n     * @type {number}\n     */\n    this.debug = 0;\n\n    /**\n     * When performing a {@link MatsSocket#request Request} and {@link MatsSocket#requestReplyTo RequestReplyTo},\n     * you may not always get a (timely) answer: Either you can lose the connection, thus lagging potentially forever -\n     * or, depending on the Mats message handling on the server (i.e. using \"non-persistent messaging\" for blazing fast\n     * performance for non-state changing operations), there is a minuscule chance that the message may be lost - or, if\n     * there is a massive backlog of messages for the particular Mats endpoint that is interfaced, you might not get an\n     * answer for 20 minutes. This setting controls the default timeout in milliseconds for Requests, and is default\n     * 45000 milliseconds (45 seconds), but you may override this per Request by specifying a different timeout in the\n     * config object for the request. When the timeout is hit, the Promise of a {@link MatsSocket#request} - or the\n     * specified ReplyTo Terminator for a {@link MatsSocket#requestReplyTo} - will be rejected with a\n     * {@link MessageEvent} of type {@link MessageEventType.TIMEOUT}. In addition, if the Received acknowledgement has\n     * not gotten in either, this will also (<i>before</i> the Promise reject!) be NACK'ed with\n     * {@link ReceivedEventType.TIMEOUT}\n     *\n     * @type {number}\n     */\n    this.requestTimeout = 45000;\n\n    /**\n     * Way to let integration tests checking failed connections take a bit less time..! Default is 'undefined', which\n     * yields a small number (60ish x 15 seconds) when we do not have SessionId (i.e. trying to connect, we have still\n     * not started the app), and a rather large one (a full day) if we do have a SessionId (implying that we're trying\n     * to reconnect).\n     *\n     * @type {number}\n     */\n    this.maxConnectionAttempts = undefined;\n\n    /**\n     * Default is 3-7 seconds for the initial ping delay, and then 15 seconds for subsequent pings. Can be overridden\n     * for tests.\n     *\n     * @type {number}\n     */\n    this.initialPingDelay = 3000 + Math.random() * 4000\n\n    /**\n     * Callback function for {@link MatsSocket#addSessionClosedEventListener}.\n     *\n     * @callback sessionClosedEventCallback\n     * @param {CloseEvent} closeEvent the WebSocket's {@link CloseEvent}.\n     */\n\n    /**\n     * <b>Note: You <i>should</i> register a SessionClosedEvent listener, as any invocation of this listener by this\n     * client library means that you've either not managed to do initial authentication, or lost sync with the\n     * server, and you should crash or \"reboot\" the application employing the library to regain sync.</b>\n     * <p />\n     * The registered event listener functions are called when the Server kicks us off the socket and the session is\n     * closed due to a multitude of reasons, where most should never happen if you use the library correctly, in\n     * particular wrt. authentication. <b>It is NOT invoked when you explicitly invoke matsSocket.close() from\n     * the client yourself!</b>\n     * <p />\n     * The event object is the WebSocket's {@link CloseEvent}, adorned with properties 'codeName', giving the\n     * <i>key name</i> of the {@link MatsSocketCloseCodes} (as provided by {@link MatsSocketCloseCodes#nameFor}),\n     * and 'outstandingInitiations', giving the number of outstanding initiations when the session was closed.\n     * You can use the 'code' to \"enum-compare\" to <code>MatsSocketCloseCodes</code>, the enum keys are listed here:\n     * <ul>\n     *   <li>{@link MatsSocketCloseCodes.UNEXPECTED_CONDITION UNEXPECTED_CONDITION}: Error on the Server side,\n     *   typically that the data store (DB) was unavailable, and the MatsSocketServer could not reliably recover\n     *   the processing of your message.</li>\n     *   <li>{@link MatsSocketCloseCodes.MATS_SOCKET_PROTOCOL_ERROR MATS_SOCKET_PROTOCOL_ERROR}: This client library\n     *   has a bug!</li>\n     *   <li>{@link MatsSocketCloseCodes.VIOLATED_POLICY VIOLATED_POLICY}: Initial Authorization was wrong. Always\n     *   supply a correct and non-expired Authorization value, which has sufficient 'roomForLatency' wrt.\n     *   the expiry time.</li>\n     *   <li>{@link MatsSocketCloseCodes.CLOSE_SESSION CLOSE_SESSION}:\n     *   <code>MatsSocketServer.closeSession(sessionId)</code> was invoked Server side for this MatsSocketSession</li>\n     *   <li>{@link MatsSocketCloseCodes.SESSION_LOST SESSION_LOST}: A reconnect attempt was performed, but the\n     *   MatsSocketSession was timed out on the Server. The Session will never time out if the WebSocket connection\n     *   is open. Only if the Client has lost connection, the timer will start. The Session timeout is measured in\n     *   hours or days. This could conceivably happen if you close the lid of a laptop, and open it again days later\n     *   - but one would think that the Authentication session (the one giving you Authorization headers) had timed\n     *   out long before.</li>\n     * </ul>\n     * Again, note: No such error should happen if this client is used properly, and the server does not get\n     * problems with its data store.\n     * <p />\n     * Note that when this event listener is invoked, the MatsSocketSession is just as closed as if you invoked\n     * {@link MatsSocket#close} on it: All outstanding send/requests are NACK'ed (with\n     * {@link ReceivedEventType.SESSION_CLOSED}), all request Promises are rejected\n     * (with {@link MessageEventType.SESSION_CLOSED}), and the MatsSocket object is as if just constructed and\n     * configured. You may \"boot it up again\" by sending a new message where you then will get a new MatsSocket\n     * SessionId. However, you should consider restarting the application if this happens, or otherwise \"reboot\"\n     * it as if it just started up (gather all required state and null out any other that uses lazy fetching).\n     * Realize that any outstanding \"addOrder\" request's Promise will now have been rejected - and you don't really\n     * know whether the order was placed or not, so you should get the entire order list. On the received event,\n     * the property 'outstandingInitiations' details the number of outstanding send/requests and Promises that was\n     * rejected: If this is zero, you <i>might</i> actually be in sync (barring failed/missing Server-to-Client\n     * SENDs or REQUESTs), and could <i>consider</i> to just \"act as if nothing happened\" - by sending a new message\n     * and thus get a new MatsSocket Session going.\n     *\n     * @param {sessionClosedEventCallback} sessionClosedEventListener a function that is invoked when the library gets the current\n     * MatsSocketSession closed from the server. The event object is the WebSocket's {@link CloseEvent}.\n     */\n    this.addSessionClosedEventListener = function (sessionClosedEventListener) {\n        if (!(typeof sessionClosedEventListener === 'function')) {\n            throw Error(\"SessionClosedEvent listener must be a function\");\n        }\n        _sessionClosedEventListeners.push(sessionClosedEventListener);\n    };\n\n    /**\n     * Callback function for {@link MatsSocket#addConnectionEventListener}.\n     *\n     * @callback connectionEventCallback\n     * @param {ConnectionEvent} connectionEvent giving information about what happened.\n     */\n\n    /**\n     * <b>Note: You <i>could</i> register a ConnectionEvent listener, as these are only informational messages\n     * about the state of the Connection.</b> It is nice if the user gets a small notification about <i>\"Connection\n     * Lost, trying to reconnect in 2 seconds\"</i> to keep him in the loop of why the application's data fetching\n     * seems to be lagging. There are suggestions of how to approach this with each of the enum values of\n     * {@link ConnectionEventType}.\n     * <p />\n     * The registered event listener functions are called when this client library performs WebSocket connection\n     * operations, including connection closed events that are not \"Session Close\" style. This includes the simple\n     * situation of \"lost connection, reconnecting\" because you passed through an area with limited or no\n     * connectivity.\n     * <p />\n     * Read more at {@link ConnectionEvent} and {@link ConnectionEventType}.\n     *\n     * @param {connectionEventCallback} connectionEventListener a function that is invoked when the library issues\n     * {@link ConnectionEvent}s.\n     */\n    this.addConnectionEventListener = function (connectionEventListener) {\n        if (!(typeof connectionEventListener === 'function')) {\n            throw Error(\"SessionClosedEvent listener must be a function\");\n        }\n        _connectionEventListeners.push(connectionEventListener);\n    };\n\n    /**\n     * Callback function for {@link MatsSocket#addSubscriptionEventListener}.\n     *\n     * @callback subscriptionEventCallback\n     * @param {SubscriptionEvent} subscriptionEvent giving information about what the server had to say about\n     * subscriptions.\n     */\n\n    /**\n     * <b>Note: If you use {@link #subscribe subscriptions}, you <i>should</i> register a\n     * {@link SubscriptionEvent} listener, as you should be concerned about {@link SubscriptionEventType.NOT_AUTHORIZED}\n     * and {@link SubscriptionEventType.LOST_MESSAGES}.</b>\n     * <p />\n     * Read more at {@link SubscriptionEvent} and {@link SubscriptionEventType}.\n     *\n     * @param {subscriptionEventCallback} subscriptionEventListener a function that is invoked when the library\n     * gets information from the Server wrt. subscriptions.\n     */\n    this.addSubscriptionEventListener = function (subscriptionEventListener) {\n        if (!(typeof subscriptionEventListener === 'function')) {\n            throw Error(\"SubscriptionEvent listener must be a function\");\n        }\n        _subscriptionEventListeners.push(subscriptionEventListener);\n    };\n\n    /**\n     * Callback function for {@link MatsSocket#addErrorEventListener}.\n     *\n     * @callback errorEventCallback\n     * @param {ErrorEvent} errorEvent information about what error happened.\n     */\n\n    /**\n     * Some 25 places within the MatsSocket client catches errors of different kinds, typically where listeners\n     * cough up errors, or if the library catches mistakes with the protocol, or if the WebSocket emits an error.\n     * Add a ErrorEvent listener to get hold of these, and send them back to your server for\n     * inspection - it is best to do this via out-of-band means, e.g. via HTTP. For browsers, consider\n     * <code>navigator.sendBeacon(..)</code>.\n     * <p />\n     * The event object is {@link ErrorEvent}.\n     *\n     * @param {errorEventCallback} errorEventListener\n     */\n    this.addErrorEventListener = function (errorEventListener) {\n        if (!(typeof errorEventListener === 'function')) {\n            throw Error(\"ErrorEvent listener must be a function\");\n        }\n        _errorEventListeners.push(errorEventListener);\n    };\n\n    /**\n     * Callback function for {@link MatsSocket#setAuthorizationExpiredCallback}.\n     *\n     * @callback authorizationExpiredCallback\n     * @param {AuthorizationRequiredEvent} authorizationRequiredEvent information about why authorization information\n     * is requested.\n     */\n\n    /**\n     * If this MatsSockets client realizes that the expiration time (minus the room for latency) of the authorization\n     * has passed when about to send a message, it will invoke this callback function. A new authorization must then\n     * be provided by invoking the 'setCurrentAuthorization' function - only when this is invoked, the MatsSocket\n     * will send messages. The MatsSocket will queue up any messages that are initiated while waiting for new\n     * authorization, and send them all at once in a single pipeline when the new authorization is in.\n     *\n     * @param {authorizationExpiredCallback} authorizationExpiredCallback function which will be invoked\n     * when about to send a new message <i>if</i>\n     * '<code>Date.now() > (expirationTimeMillisSinceEpoch - roomForLatencyMillis)</code>' from the paramaters of\n     * the last invocation of {@link MatsSocket#setCurrentAuthorization}.\n     */\n    this.setAuthorizationExpiredCallback = function (authorizationExpiredCallback) {\n        if (!(typeof authorizationExpiredCallback === 'function')) {\n            throw Error(\"AuthorizationExpiredCallback must be a function\");\n        }\n        _authorizationExpiredCallback = authorizationExpiredCallback;\n\n        // Evaluate whether there are stuff in the pipeline that should be sent now.\n        // (Not-yet-sent HELLO does not count..)\n        that.flush();\n    };\n\n    /**\n     * Sets an authorization String, which for several types of authorization must be invoked on a regular basis with\n     * fresh authorization - this holds for a OAuth/JWT/OIDC-type system where an access token will expire within a short time\n     * frame (e.g. expires within minutes). For an Oauth2-style authorization scheme, this could be \"Bearer: ......\".\n     * This must correspond to what the server side authorization plugin expects.\n     * <p />\n     * <b>NOTE: This SHALL NOT be used to CHANGE the user!</b> It should only refresh an existing authorization for the\n     * initially authenticated user. One MatsSocket (Session) shall only be used by a single user: If changing\n     * user, you should ditch the existing MatsSocket after invoking {@link MatsSocket#close} to properly clean up the\n     * current MatsSocketSession on the server side too, and then make a new MatsSocket thus getting a new Session.\n     * <p />\n     * Note: If the underlying WebSocket has not been established and HELLO sent, then invoking this method will NOT\n     * do that - only the first actual MatsSocket message will start the WebSocket and perform the HELLO/WELCOME\n     * handshake.\n     *\n     * @param {string} authorizationValue the string Value which will be transfered to the Server and there resolved\n     *        to a Principal and UserId on the server side by the AuthorizationPlugin. Note that this value potentially\n     *        also will be forwarded to other resources that requires authorization.\n     * @param {number} expirationTimestamp the millis-since-epoch at which this authorization expires\n     *        (in case of OAuth-style tokens), or -1 if it never expires or otherwise has no defined expiration mechanism.\n     *        <i>Notice that in a JWT token, the expiration time is in seconds, not millis: Multiply by 1000.</i>\n     * @param {number} roomForLatencyMillis the number of millis which is subtracted from the 'expirationTimestamp' to\n     *        find the point in time where the MatsSocket will refuse to use the authorization and instead invoke the\n     *        {@link #setAuthorizationExpiredCallback AuthorizationExpiredCallback} and wait for a new authorization\n     *        being set by invocation of the present method. Depending on what the usage of the Authorization string\n     *        is on server side is, this should probably <b>at least</b> be 10000, i.e. 10 seconds - but if the Mats\n     *        endpoints uses the Authorization string to do further accesses, both latency and queue time must be\n     *        taken into account (e.g. for calling into another API that also needs a valid token). If\n     *        expirationTimestamp is '-1', then this parameter is not used. <i>Default value is 30000 (30 seconds).</i>\n     */\n    this.setCurrentAuthorization = function (authorizationValue, expirationTimestamp= -1, roomForLatencyMillis = 30000) {\n        if (this.logging) log(\"Got Authorization which \"\n            + (expirationTimestamp !== -1 ? \"Expires in [\" + (expirationTimestamp - Date.now()) + \" ms]\" : \"[Never expires]\")\n            + \", roomForLatencyMillis: \" + roomForLatencyMillis);\n\n        _authorization = authorizationValue;\n        _expirationTimestamp = expirationTimestamp;\n        _roomForLatencyMillis = roomForLatencyMillis;\n        // ?: Should we send it now?\n        if (_authExpiredCallbackInvoked_EventType === AuthorizationRequiredEventType.REAUTHENTICATE) {\n            log(\"Immediate send of new authentication due to REAUTHENTICATE\");\n            _forcePipelineProcessing = true;\n        }\n        // We're now back to \"normal\", i.e. not outstanding authorization request.\n        _authExpiredCallbackInvoked_EventType = undefined;\n\n        // Evaluate whether there are stuff in the pipeline that should be sent now.\n        // (Not-yet-sent HELLO does not count..)\n        that.flush();\n    };\n\n    /**\n     * Millis-since-epoch of last message enqueued. This can be used by the mechanism invoking\n     * {@link MatsSocket#setCurrentAuthorization} to decide whether it should keep the\n     * authorization fresh (i.e. no latency waiting for new authorization is introduced when a new message is\n     * enqueued), or fall back to relying on the 'authorizationExpiredCallback' being invoked when a new message needs\n     * it (thus introducing latency while waiting for authorization). One could envision keeping fresh auth for 5\n     * minutes, but if the user has not done anything requiring authentication (i.e. sending information bearing\n     * messages SEND, REQUEST or Replies) in that timespan, you stop doing continuous authentication refresh, falling\n     * back to the \"on demand\" based logic, where when a message is enqueued, the\n     * {@link MatsSocket#setAuthorizationExpiredCallback} is invoked if the authentication is expired.\n     *\n     * @member {number} lastMessageEnqueuedTimestamp\n     * @memberOf MatsSocket\n     * @readonly\n     */\n    Object.defineProperty(this, \"lastMessageEnqueuedTimestamp\", {\n        get: function () {\n            return _lastMessageEnqueuedTimestamp;\n        }\n    });\n\n    /**\n     * Returns whether this MatsSocket <i>currently</i> have a WebSocket connection open. It can both go down\n     * by lost connection (driving through a tunnel), where it will start to do reconnection attempts, or because\n     * you (the Client) have {@link MatsSocket#close closed} this MatsSocketSession, or because the <i>Server</i> has\n     * closed the MatsSocketSession.\n     * <p/>\n     * Pretty much the same as <code>({@link MatsSocket.state} === {@link ConnectionState.CONNECTED})\n     * || ({@link MatsSocket.state} === {@link ConnectionState.SESSION_ESTABLISHED})</code> - however, in the face of\n     * {@link MessageType.DISCONNECT}, the state will not change, but the connection is dead ('connected' returns\n     * false).\n     *\n     * @member {string} connected\n     * @memberOf MatsSocket\n     * @readonly\n     */\n    Object.defineProperty(this, \"connected\", {\n        get: function () {\n            return _webSocket != null;\n        }\n    });\n\n    /**\n     * Returns which one of the {@link ConnectionState} state enums the MatsSocket is in.\n     * <ul>\n     *     <li>NO_SESSION - initial state, and after Session Close (both from client and server side)</li>\n     *     <li>CONNECTING - when we're actively trying to connect, i.e. \"new WebSocket(..)\" has been invoked, but not yet either opened or closed.</li>\n     *     <li>WAITING - if the \"new WebSocket(..)\" invocation ended in the socket closing, i.e. connection failed, but we're still counting down to next (re)connection attempt.</li>\n     *     <li>CONNECTED - if the \"new WebSocket(..)\" resulted in the socket opening. We still have not established the MatsSocketSession with the server, though.</li>\n     *     <li>SESSION_ESTABLISHED - when we're open for business: Connected, authenticated, and established MatsSocketSession with the server.</li>\n     * </ul>\n     *\n     * @member {string} state\n     * @memberOf MatsSocket\n     * @readonly\n     */\n    Object.defineProperty(this, \"state\", {\n        get: function () {\n            return _state;\n        }\n    });\n\n    /**\n     * Metrics/Introspection: Returns an array of the 100 latest {@link PingPong}s. Note that a PingPong entry\n     * is added to this array <i>before</i> it gets the Pong, thus the latest may not have its\n     * {@link PingPong#roundTripMillis} set yet. Also, if a ping is performed right before the connection goes down,\n     * it will never get the Pong, thus there might be entries in the middle of the list too that does not have\n     * roundTripMillis set. This is opposed to the {@link #addPingPongListener}, which only gets invoked when\n     * the pong has arrived.\n     *\n     * @see MatsSocket#addPingPongListener\n     *\n     * @member {array<PingPong>}\n     * @memberOf MatsSocket\n     * @readonly\n     */\n    Object.defineProperty(this, \"pings\", {\n        get: function () {\n            return _pings;\n        }\n    });\n\n    /**\n     * Callback function for {@link MatsSocket#addPingPongListener}.\n     *\n     * @callback addPingPongCallback\n     * @param {PingPong} pingPong information about the ping and the pong.\n     */\n\n    /**\n     * A {@link PingPong} listener is invoked each time a {@link MessageType#PONG} message comes in, giving you\n     * information about the experienced {@link PingPong#roundTripMillis round-trip time}. The PINGs and PONGs are\n     * handled slightly special in that they always are handled ASAP with short-path code routes, and should thus\n     * give a good indication about experienced latency from the network. That said, they are sent on the same\n     * connection as all data, so if there is a gigabyte document \"in the pipe\", the PING will come behind that\n     * and thus get a big hit. Thus, you should consider this when interpreting the results - a high outlier should\n     * be seen in conjunction with a message that was sent at the same time.\n     *\n     * @param {addPingPongCallback} pingPongListener a function that is invoked when the library issues\n     */\n    this.addPingPongListener = function (pingPongListener) {\n        if (!(typeof pingPongListener === 'function')) {\n            throw Error(\"PingPong listener must be a function\");\n        }\n        _pingPongListeners.push(pingPongListener);\n    };\n\n    /**\n     * Metrics/Introspection: Returns an array of the {@link #numberOfInitiationsKept} latest\n     * {@link InitiationProcessedEvent}s.\n     * <p />\n     * Note: These objects will always have the {@link InitiationProcessedEvent#initiationMessage} and (if Request)\n     * {@link InitiationProcessedEvent#replyMessageEvent} set, as opposed to the events issued to\n     * {@link #addInitiationProcessedEventListener}, which can decide whether to include them.\n     *\n     * @see MatsSocket#addInitiationProcessedEventListener\n     *\n     * @member {InitiationProcessedEvent<InitiationProcessedEvent>}\n     * @memberOf MatsSocket\n     * @readonly\n     */\n    Object.defineProperty(this, \"initiations\", {\n        get: function () {\n            return _initiationProcessedEvents;\n        }\n    });\n\n    /**\n     * Metrics/Introspection: How many {@link InitiationProcessedEvent}s to keep in {@link #initiations}.\n     * If the current number of initiations is more than what you set it to, it will be culled.\n     * You can use this to \"reset\" the {@link #initiations array of initiations} by setting it to 0, then right\n     * back up to whatever you fancy.\n     * <p />\n     * Default is 10.\n     *\n     * @member {number}\n     * @memberOf MatsSocket\n     * @readonly\n     */\n    Object.defineProperty(this, \"numberOfInitiationsKept\", {\n        get: function () {\n            return _numberOfInitiationsKept;\n        },\n        set: function (numberOfInitiationsKept) {\n            if (numberOfInitiationsKept < 0) {\n                throw new Error(\"numberOfInitiationsKept must be >= 0\");\n            }\n            _numberOfInitiationsKept = numberOfInitiationsKept;\n            while (_initiationProcessedEvents.length > numberOfInitiationsKept) {\n                _initiationProcessedEvents.shift();\n            }\n        }\n    });\n\n    /**\n     * Callback function for {@link MatsSocket#addInitiationProcessedEventListener}.\n     *\n     * @callback initiationProcessedEventCallback\n     * @param {InitiationProcessedEvent} initiationProcessedEvent information about the processing of the initiation.\n     */\n\n    /**\n     * Registering an {@link InitiationProcessedEvent} listener will give you meta information about each Send\n     * and Request that is performed through the library when it is fully processed, thus also containing\n     * information about experienced round-trip times. The idea is that you thus can gather metrics of\n     * performance as experienced out on the client, by e.g. periodically sending this gathering to the Server.\n     * <b>Make sure that you understand that if you send to the server each time this listener is invoked, using\n     * the MatsSocket itself, you WILL end up in a tight loop!</b> This is because the sending of the statistics\n     * message itself will again trigger a new invocation of this listener. This can be avoided in two ways: Either\n     * instead send periodically - in which case you can include the statistics message itself, OR specify that\n     * you do NOT want a listener-invocation of these messages by use of the config object on the send, request\n     * and requestReplyTo methods.\n     * <p />\n     * Note: Each listener gets its own instance of {@link InitiationProcessedEvent}, which also is different from\n     * the ones in the {@link MatsSocket.initiations} array.\n     *\n     * @param {initiationProcessedEventCallback} initiationProcessedEventListener a function that is invoked when\n     * the library issues {@link InitiationProcessedEvent}s.\n     * @param {boolean} includeInitiationMessage whether to include the {@link InitiationProcessedEvent#initiationMessage}\n     * @param {boolean} includeReplyMessageEvent whether to include the {@link InitiationProcessedEvent#replyMessageEvent}\n     * Reply {@link MessageEvent}s.\n     */\n    this.addInitiationProcessedEventListener = function (initiationProcessedEventListener, includeInitiationMessage, includeReplyMessageEvent) {\n        if (!(typeof initiationProcessedEventListener === 'function')) {\n            throw Error(\"InitiationProcessedEvent listener must be a function\");\n        }\n        _initiationProcessedEventListeners.push({\n            listener: initiationProcessedEventListener,\n            includeInitiationMessage: includeInitiationMessage,\n            includeReplyMessageEvent: includeReplyMessageEvent\n        });\n    };\n\n    // ========== Terminator and Endpoint registration ==========\n\n    /**\n     * Registers a Terminator, on the specified terminatorId, and with the specified callbacks. A Terminator is\n     * the target for Server-to-Client SENDs, and the Server's REPLYs from invocations of\n     * <code>requestReplyTo(terminatorId ..)</code> where the terminatorId points to this Terminator.\n     * <p />\n     * Note: You cannot register any Terminators, Endpoints or Subscriptions starting with \"MatsSocket\".\n     *\n     * @param terminatorId the id of this client side Terminator.\n     * @param messageCallback receives an Event when everything went OK, containing the message on the \"data\" property.\n     * @param rejectCallback is relevant if this endpoint is set as the replyTo-target on a requestReplyTo(..) invocation, and will\n     * get invoked with the Event if the corresponding Promise-variant would have been rejected.\n     */\n    this.terminator = function (terminatorId, messageCallback, rejectCallback) {\n        // :: Assert for double-registrations\n        if (_terminators[terminatorId] !== undefined) {\n            throw new Error(\"Cannot register more than one Terminator to same terminatorId [\" + terminatorId + \"], existing: \" + _terminators[terminatorId]);\n        }\n        if (_endpoints[terminatorId] !== undefined) {\n            throw new Error(\"Cannot register a Terminator to same terminatorId [\" + terminatorId + \"] as an Endpoint's endpointId, existing: \" + _endpoints[terminatorId]);\n        }\n        // :: Assert that the namespace \"MatsSocket\" is not used\n        if (terminatorId.startsWith(\"MatsSocket\")) {\n            throw new Error('The namespace \"MatsSocket\" is reserved, terminatorId [' + terminatorId + '] is illegal.');\n        }\n        // :: Assert that the messageCallback is a function\n        if (typeof messageCallback !== 'function') {\n            throw new Error(\"The 'messageCallback' must be a function.\");\n        }\n        // :: Assert that the rejectCallback is either undefined or a function\n        if ((rejectCallback !== undefined) && (typeof rejectCallback !== 'function')) {\n            throw new Error(\"The 'rejectCallback' must either be undefined or a function.\");\n        }\n        log(\"Registering Terminator on id [\" + terminatorId + \"]:\\n #messageCallback: \" + messageCallback + \"\\n #rejectCallback: \" + rejectCallback);\n        _terminators[terminatorId] = {\n            resolve: messageCallback,\n            reject: rejectCallback\n        };\n    };\n\n    /**\n     * Registers an Endpoint, on the specified endpointId, with the specified \"promiseProducer\". An Endpoint is\n     * the target for Server-to-Client REQUESTs. The promiseProducer is a function that takes a message event\n     * (the incoming REQUEST) and produces a Promise, whose return (resolve or reject) is the return value of the\n     * endpoint.\n     * <p />\n     * Note: You cannot register any Terminators, Endpoints or Subscriptions starting with \"MatsSocket\".\n     *\n     * @param endpointId the id of this client side Endpoint.\n     * @param {function} promiseProducer a function that takes a Message Event and returns a Promise which when\n     * later either Resolve or Reject will be the return value of the endpoint call.\n     */\n    this.endpoint = function (endpointId, promiseProducer) {\n        // :: Assert for double-registrations\n        if (_endpoints[endpointId] !== undefined) {\n            throw new Error(\"Cannot register more than one Endpoint to same endpointId [\" + endpointId + \"], existing: \" + _endpoints[endpointId]);\n        }\n        if (_terminators[endpointId] !== undefined) {\n            throw new Error(\"Cannot register an Endpoint to same endpointId [\" + endpointId + \"] as a Terminator, existing: \" + _terminators[endpointId]);\n        }\n        // :: Assert that the namespace \"MatsSocket\" is not used\n        if (endpointId.startsWith(\"MatsSocket\")) {\n            throw new Error('The namespace \"MatsSocket\" is reserved, EndpointId [' + endpointId + '] is illegal.');\n        }\n        // :: Assert that the promiseProducer is a function\n        if (typeof promiseProducer !== 'function') {\n            throw new Error(\"The 'promiseProducer' must be a function.\");\n        }\n        log(\"Registering Endpoint on id [\" + endpointId + \"]:\\n #promiseProducer: \" + promiseProducer);\n        _endpoints[endpointId] = promiseProducer;\n    };\n\n    /**\n     * Subscribes to a Topic. The Server may do an authorization check for the subscription. If you are not allowed,\n     * a {@link SubscriptionEvent} of type {@link SubscriptionEventType.NOT_AUTHORIZED} is issued, and the callback\n     * will not get any messages. Otherwise, the event type is {@link SubscriptionEventType.OK}.\n     * <p />\n     * Note: If the 'messageCallback' was already registered, an error is emitted, but the method otherwise returns\n     * silently.\n     * <p />\n     * Note: You will not get messages that was issued before the subscription initially is registered with the\n     * server, which means that you by definition cannot get any messages issued earlier than the initial\n     * {@link ConnectionEventType.SESSION_ESTABLISHED}. Code accordingly. <i>Tip for a \"ticker stream\" or \"cache\n     * update stream\" or similar: Make sure you have some concept of event sequence number on updates. Do the MatsSocket\n     * connect with the Subscription in place, but for now just queue up any updates. Do the request for \"full initial load\", whose reply\n     * contains the last applied sequence number. Now process the queued events that arrived while getting the\n     * initial load (i.e. in front, or immediately after), taking into account which event sequence numbers that\n     * already was applied in the initial load: Discard the earlier and same, apply the later. Finally, go over to\n     * immediate processing of the events. If you get a reconnect telling you that messages was lost (next \"Note\"!),\n     * you could start this process over.</i>\n     * <p />\n     * Note: Reconnects are somewhat catered for, in that a \"re-subscription\" after re-establishing the session will\n     * contain the latest messageId the client has received, and the server will then send along all the messages\n     * <i>after</i> this that was lost - up to some limit specified on the server. If the messageId is not known by the server,\n     * implying that the client has been gone for too long time, a {@link SubscriptionEvent} of type\n     * {@link SubscriptionEventType.LOST_MESSAGES} is issued. Otherwise, the event type is\n     * {@link SubscriptionEventType.OK}.\n     * <p />\n     * Note: You should preferably add all \"static\" subscriptions in the \"configuration phase\" while setting up\n     * your MatsSocket, before starting it (i.e. sending first message). However, dynamic adding and\n     * {@link MatsSocket#deleteSubscription deleting} is also supported.\n     * <p />\n     * Note: Pub/sub is not designed to be as reliable as send/request - but it should be pretty ok anyway!\n     * <p />\n     * Wrt. to how many topics a client can subscribe to: Mainly bandwidth constrained wrt. to the total number of\n     * messages, although there is a slight memory and CPU usage to consider too (several hundred should not really\n     * be a problem). In addition, the client needs to send over the actual subscriptions, and if these number in\n     * the thousands, the connect and any reconnects could end up with tens or hundreds of kilobytes of \"system\n     * information\" passed over the WebSocket.\n     * <p />\n     * Wrt. to how many topics that can exist: Mainly memory constrained on the server based on the number of topics\n     * multiplied by the number of subscriptions per topic, in addition to the number of messages passed in total\n     * as each node in the cluster will have to listen to either the full total of messages, or at least a\n     * substantial subset of the messages - and it will also retain these messages for hours to allow for client\n     * reconnects.\n     * <p />\n     * Note: You cannot register any Terminators, Endpoints or Subscriptions starting with \"MatsSocket\".\n     */\n    this.subscribe = function (topicId, messageCallback) {\n        // :: Assert that the namespace \"MatsSocket\" is not used\n        if (topicId.startsWith(\"MatsSocket\")) {\n            throw new Error('The namespace \"MatsSocket\" is reserved, Topic [' + topicId + '] is illegal.');\n        }\n        if (topicId.startsWith(\"!\")) {\n            throw new Error('Topic cannot start with \"!\" (and why would you use chars like that anyway?!), Topic [' + topicId + '] is illegal.');\n        }\n        // :: Assert that the messageCallback is a function\n        if (typeof messageCallback !== 'function') {\n            throw new Error(\"The 'messageCallback' must be a function.\");\n        }\n        log(\"Registering Subscription on Topic [\" + topicId + \"]:\\n #messageCallback: \" + messageCallback);\n        // ?: Check if we have an active subscription holder here already\n        let subs = _subscriptions[topicId];\n        if (!subs) {\n            // -> No, we do not have subscription holder going\n            // Add a holder\n            subs = {\n                listeners: [],\n                lastSmid: undefined,\n                subscriptionSentToServer: false\n            };\n            _subscriptions[topicId] = subs;\n        }\n        // :: Assert that the messageCallback is not already there\n        for (let i = 0; i < subs.listeners.length; i++) {\n            if (subs.listeners[i] === messageCallback) {\n                error(\"subscription_already_exists\", \"The specified messageCallback [\" + messageCallback + \"] was already subscribed to Topic [\" + topicId + \"].\");\n                return;\n            }\n        }\n        // Add the present messageCallback to the subscription holder\n        subs.listeners.push(messageCallback);\n\n        // ?: Have we NOT already subscribed with Server?\n        if (!subs.subscriptionSentToServer) {\n            // ?: Has HELLO already been sent?\n            // (If socket is NOT hello'ed, subs will be done when doing HELLO.)\n            if (_helloSent) {\n                // -> Yes, HELLO sent, so handle \"dynamic subscription\", i.e. subscribing while the socket is open.\n                // Send message to subscribe this TopicId with the server\n                // - using PRE-pipeline to get it done in front of any e.g send or request that potentially could\n                // trigger a publish (on the server side) which we should now get.\n                _addEnvelopeToPipeline_EvaluatePipelineLater({\n                    t: MessageType.SUB,\n                    eid: topicId\n                }, true);\n                // The subscription must now be assumed sent to the server (ref unsubscription)\n                subs.subscriptionSentToServer = true;\n            }\n            else {\n                // -> No, HELLO not yet sent. Make it happen Real Soon Now.\n                // HELLO handling will do the subscription\n                // We must however force pipeline processing since there might be nothing in the pipelines.\n                _forcePipelineProcessing = true;\n                // We must also \"force open\" the MatsSocket, i.e. \"emulate\" that an information bearing message is enqueued.\n                _matsSocketOpen = true;\n                // Run the pipeline (use \"later\", there might be more subs or messages to come from client)\n                _evaluatePipelineLater();\n            }\n        }\n    };\n\n    /**\n     * Removes a previously added {@link MatsSocket#subscribe subscription}. If there are no more listeners for this topic,\n     * it is de-subscribed from the server. If the 'messageCallback' was not already registered, an error is\n     * emitted, but the method otherwise returns silently.\n     *\n     * @param topicId\n     * @param messageCallback\n     */\n    this.deleteSubscription = function (topicId, messageCallback) {\n        let subs = _subscriptions[topicId];\n        if (!subs) {\n            throw new Error(\"The topicId [\" + topicId + \"] had no subscriptions! (thus this message callback is not subscribed! [\" + messageCallback + \"].\");\n        }\n        let found = false;\n        for (let i = 0; i < subs.listeners.length; i++) {\n            if (subs.listeners[i] === messageCallback) {\n                found = true;\n                subs.listeners = subs.listeners.splice(i, 1);\n                break;\n            }\n        }\n        if (!found) {\n            error(\"subscription_not_found\", \"The specified messageCallback [\" + messageCallback + \"] was not subscribed with Topic [\" + topicId + \"].\");\n            return;\n        }\n\n        // :: Only need to send unsubscription if we already are subscribed\n\n        // ?: Are we empty of listeners, AND we are already subscribed with Server?\n        if ((subs.listeners.length === 0) && subs.subscriptionSentToServer) {\n            // -> Yes, we are empty of listeners, and the subscription is already sent\n            // Send message to unsubscribe this TopicId with the server\n            // - using PRE-pipeline since subscriptions are using that, and we need subs and de-subs in sequential correct order\n            _addEnvelopeToPipeline_EvaluatePipelineLater({\n                t: MessageType.UNSUB,\n                eid: topicId\n            }, true);\n            // Remove locally\n            delete _subscriptions[topicId];\n        }\n    };\n\n    /**\n     * \"Fire-and-forget\"-style send-a-message. The returned promise is Resolved when the Server receives and accepts\n     * the message for processing, while it is Rejected if the Server denies it.\n     * <p/>\n     * The config object has a single key - <i>which is optional</i>:\n     * <ul>\n     *     <li>suppressInitiationProcessedEvent: If <code>true</code>, no event will be sent to listeners added\n     *         using {@link MatsSocket#addInitiationProcessedEventListener}.</li>\n     * </ul>\n     *\n     * @param endpointId the Server MatsSocket Endpoint/Terminator that this message should go to.\n     * @param traceId the TraceId for this message - will go through all parts of the call, including the Mats flow.\n     * @param message the actual message for the Server MatsSocket Endpoint.\n     * @param {object} config an optional configuration object - read JSDoc.\n     * @returns {Promise<ReceivedEvent>}\n     */\n    this.send = function (endpointId, traceId, message, config = undefined) {\n        return new Promise(function (resolve, reject) {\n            // Make lambda for what happens when it has been RECEIVED on server.\n            let initiation = Object.create(null);\n            // Set the Sends's returned Promise's settle functions for ACK and NACK.\n            initiation.ack = resolve;\n            initiation.nack = reject;\n\n            // Parse config object\n            if (config) {\n                if (typeof (config) !== 'object') {\n                    throw new Error(\"The 'config' parameter wasn't an object.\");\n                }\n                // ?: 'suppressInitiationProcessedEvent' setting?\n                if (config.suppressInitiationProcessedEvent) {\n                    initiation.suppressInitiationProcessedEvent = true;\n                }\n            }\n\n            let envelope = Object.create(null);\n            envelope.t = MessageType.SEND;\n            envelope.eid = endpointId;\n            envelope.msg = message;\n\n            _addInformationBearingEnvelopeToPipeline(envelope, traceId, initiation, undefined);\n        });\n    };\n\n\n    /**\n     * Perform a Request, and have the reply come back via the returned Promise. As opposed to Send, where the\n     * returned Promise is resolved when the server accepts the message, the Promise is now resolved by the Reply.\n     * To get information of whether the server accepted or did not accept the message, you can provide either\n     * a receivedCallback function (set the 'config' parameter to this function) or set the two config properties\n     * 'ackCallback' and 'nackCallback' to functions. If you supply the single function variant, this is equivalent\n     * to setting both ack- and nackCallback to the same function. The {@link ReceivedEvent}'s type will distinguish\n     * between {@link ReceivedEventType.ACK ACK} or {@link ReceivedEventType.NACK NACK}.\n     * <p/>\n     * The config object has keys as such - <i>all are optional</i>:\n     * <ul>\n     *     <li><b><code>receivedCallback</code></b>: {function} invoked when the Server receives the event and either ACK or NACKs it\n     *         - or when {@link MessageEventType.TIMEOUT} or {@link MessageEventType.SESSION_CLOSED} happens.\n     *         This overrides the ack- and nackCallbacks.</li>\n     *     <li><b><code>ackCallback</code></b>: {function} invoked when the Server receives the event and ACKs it.</li>\n     *     <li><b><code>nackCallback</code></b>: {function} invoked when the Server receives the event and NACKs it\n     *         - or when {@link MessageEventType.TIMEOUT} or {@link MessageEventType.SESSION_CLOSED} happens.</li>\n     *     <li><b><code>timeout</code></b>: number of milliseconds before the Client times out the Server reply. When this happens,\n     *         the 'nackCallback' (or receivedCallback if this is used) is invoked with a {@link ReceivedEvent} of\n     *         type {@link ReceivedEventType.TIMEOUT}, and the Request's Promise will be <i>rejected</i> with a\n     *         {@link MessageEvent} of type {@link MessageEventType.TIMEOUT}.</li>\n     *     <li><b><code>suppressInitiationProcessedEvent</code></b>: if <code>true</code>, no event will be sent to listeners added\n     *         using {@link MatsSocket#addInitiationProcessedEventListener}.</li>\n     *     <li><b><code>debug</code></b>: If set, this specific call flow overrides the global {@link MatsSocket#debug} setting, read\n     *         more about debug and {@link DebugOption}s there.</li>\n     * </ul>\n     * <p />\n     * <b>Note on event ordering:</b> {@link ReceivedEvent}s shall always be delivered <i>before</i> {@link MessageEvent}s.\n     * This means that for a <i>request</i>, if receivedCallback (or ack- or nackCallback) is provided, it shall be\n     * invoked <i>before</i> the return Reply-Promise will be settled. For more on event ordering wrt. message\n     * processing, read {@link InitiationProcessedEvent}.\n     *\n     * @param endpointId the Server MatsSocket Endpoint that this message should go to.\n     * @param traceId the TraceId for this message - will go through all parts of the call, including the Mats flow.\n     * @param message the actual message for the Server MatsSocket Endpoint.\n     * @param {function|object} configOrCallback (optional) either directly a \"receivedCallback\" function as\n     *        described in the config object, or a config object - read JSDoc above.\n     * @returns {Promise<MessageEvent>}\n     */\n    this.request = function (endpointId, traceId, message, configOrCallback = undefined) {\n        return new Promise(function (resolve, reject) {\n            // Default Timeout is MatsSocket's default\n            let timeout = that.requestTimeout;\n\n            // Make lambda for what happens when it has been RECEIVED on server.\n            let initiation = Object.create(null);\n\n            // :: Handle the different configOrCallback situations\n\n            // ?: Is the 'configOrCallback' a function?\n            if (typeof (configOrCallback) === 'function') {\n                // -> Yes, function, so then it is a receivedCallback: Set both ACN and NACK to the this function\n                initiation.ack = configOrCallback;\n                initiation.nack = configOrCallback;\n\n                // ?: Is the 'configOrCallback' an object?\n            } else if (typeof (configOrCallback) === 'object') {\n                // -> Yes, object, so then it is a config object\n\n                // ?: Do we have a 'receivedCallback' defined?\n                if (configOrCallback.receivedCallback) {\n                    // -> Yes, we have a 'receivedCallback': Set both ACN and NACK to the this function\n                    if (typeof (configOrCallback.receivedCallback) !== 'function') {\n                        throw new Error(\"The 'configOrCallback.receivedCallback' is not a function.\");\n                    }\n                    // Set both ACN and NACK to the 'receivedCallback'\n                    initiation.ack = configOrCallback.receivedCallback;\n                    initiation.nack = configOrCallback.receivedCallback;\n                } else {\n                    // -> No, no 'receivedCallback', so then handle if we have ack- or nackCallback\n                    // ?: Do we have 'ackCallback'?\n                    if (configOrCallback.ackCallback) {\n                        // -> Yes, 'ackCallback' present - assert that it is a function, and then set it.\n                        if (typeof (configOrCallback.ackCallback) !== 'function') {\n                            throw new Error(\"The 'configOrCallback.ackCallback' is not a function.\");\n                        }\n                        initiation.ack = configOrCallback.ackCallback;\n                    }\n                    // ?: Do we have 'nackCallback'?\n                    if (configOrCallback.nackCallback) {\n                        // -> Yes, 'nackCallback' present - assert that it is a function, and then set it.\n                        if (typeof (configOrCallback.nackCallback) !== 'function') {\n                            throw new Error(\"The 'configOrCallback.nackCallback' is not a function.\");\n                        }\n                        initiation.nack = configOrCallback.nackCallback;\n                    }\n                }\n\n                // ?: Do we have a timeout configured?\n                if (configOrCallback.timeout !== undefined) {\n                    // -> Yes, there was a timeout configured - assert that it is a number, then override default.\n                    if (typeof (configOrCallback.timeout) !== 'number') {\n                        throw new Error(\"The 'configOrCallback.timeout' is not a number.\");\n                    }\n                    timeout = configOrCallback.timeout;\n                }\n\n                // ?: 'suppressInitiationProcessedEvent' setting?\n                if (configOrCallback.suppressInitiationProcessedEvent) {\n                    initiation.suppressInitiationProcessedEvent = true;\n                }\n\n                // ?: 'debug' setting?\n                if (configOrCallback.debug !== undefined) {\n                    initiation.debug = configOrCallback.debug;\n                }\n\n                // ?: Is the 'configOrCallback' /undefined/?\n            } else if (typeof (configOrCallback) === 'undefined') {\n                // -> Yes, undefined - which is default, and legal!\n                /* n/a */\n\n            } else {\n                // -> It is something else than 'function', 'object' or /undefined/ -> illegal!\n                throw new Error(\"The 'configOrCallback' parameter was neither a function or an object.\");\n            }\n\n            // Make Request for the Reply\n            let request = Object.create(null);\n            request.resolve = resolve;\n            request.reject = reject;\n            request.timeout = timeout;\n\n            // Make the MatsSocket Envelope that will be sent.\n            let envelope = Object.create(null);\n            envelope.t = MessageType.REQUEST;\n            envelope.eid = endpointId;\n            envelope.msg = message;\n            envelope.to = timeout;\n\n            _addInformationBearingEnvelopeToPipeline(envelope, traceId, initiation, request);\n        });\n    };\n\n    /**\n     * Perform a Request, but send the reply to a specific client terminator registered on this MatsSocket instance.\n     * The returned Promise functions as for Send, since the reply will not go to the Promise, but to the\n     * terminator. Notice that you can set any CorrelationInformation object which will be available for the Client\n     * terminator when it receives the reply - this is kept on the client (not serialized and sent along with\n     * request and reply), so it can be any object: An identifier, some object to apply the result on, or even a\n     * function.\n     * <p/>\n     * The config object has keys as such - <i>all are optional</i>:\n     * <ul>\n     *     <li><b><code>timeout</code></b>: number of milliseconds before the Client times out the Server reply. When this happens,\n     *         the returned Promise is <i>rejected</i> with a {@link ReceivedEvent} of\n     *         type {@link ReceivedEventType.TIMEOUT}, and the specified Client Terminator will have its\n     *         rejectCallback invoked with a {@link MessageEvent} of type {@link MessageEventType.TIMEOUT}.</li>\n     *     <li><b><code>suppressInitiationProcessedEvent</code></b>: if <code>true</code>, no event will be sent to listeners added\n     *         using {@link MatsSocket#addInitiationProcessedEventListener}.</li>\n     *     <li><b><code>debug</code></b>: If set, this specific call flow overrides the global {@link MatsSocket#debug} setting, read\n     *         more about debug and {@link DebugOption}s there.</li>\n     * </ul>\n     * <p />\n     * <b>Note on event ordering:</b> {@link ReceivedEvent}s shall always be delivered before {@link MessageEvent}s. This means\n     * that for a <i>requestReplyTo</i>, the returned Received-Promise shall be settled <i>before</i> the\n     * Terminator gets its resolve- or rejectCallback invoked. For more on event ordering wrt. message\n     * processing, read {@link InitiationProcessedEvent}.\n     *\n     * @param endpointId the Server MatsSocket Endpoint that this message should go to.\n     * @param traceId the TraceId for this message - will go through all parts of the call, including the Mats flow.\n     * @param message the actual message for the Server MatsSocket Endpoint.\n     * @param replyToTerminatorId which Client Terminator the reply should go to\n     * @param correlationInformation information that will be available to the Client Terminator\n     *        (in {@link MessageEvent#correlationInformation}) when the reply comes back.\n     * @param {object} config an optional configuration object - the one parameter you can set is 'timeout', which\n     *        works like it does for {@link MatsSocket#request}.\n     * @returns {Promise<ReceivedEvent>}\n     */\n    this.requestReplyTo = function (endpointId, traceId, message, replyToTerminatorId, correlationInformation, config = undefined) {\n        // ?: Do we have the Terminator the client requests reply should go to?\n        if (!_terminators[replyToTerminatorId]) {\n            // -> No, we do not have this. Programming error from app.\n            throw new Error(\"The Client Terminator [\" + replyToTerminatorId + \"] is not present, !\");\n        }\n\n        return new Promise(function (resolve, reject) {\n            // Make lambda for what happens when it has been RECEIVED on server.\n            let initiation = Object.create(null);\n            // Set the RequestReplyTop's returned Promise's settle functions for ACK and NACK.\n            initiation.ack = resolve;\n            initiation.nack = reject;\n\n            // :: Find which timeout to use\n            let timeout = that.requestTimeout;\n\n            // Parse config object\n            if (config) {\n                if (typeof (config) !== 'object') {\n                    throw new Error(\"The 'config' parameter wasn't an object.\");\n                }\n\n                // ?: Do we have a timeout configured?\n                if (config.timeout !== undefined) {\n                    // -> Yes, there was a timeout configured - assert that it is a number, then override default.\n                    if (typeof (config.timeout) !== 'number') {\n                        throw new Error(\"The 'config.timeout' is not a number.\");\n                    }\n                    timeout = config.timeout;\n                }\n\n                // ?: 'suppressInitiationProcessedEvent' setting?\n                if (config.suppressInitiationProcessedEvent) {\n                    initiation.suppressInitiationProcessedEvent = true;\n                }\n\n                // ?: 'debug' setting?\n                if (config.debug !== undefined) {\n                    initiation.debug = config.debug;\n                }\n            }\n\n            // Make Request for the Reply\n            let request = Object.create(null);\n            request.replyToTerminatorId = replyToTerminatorId;\n            request.correlationInformation = correlationInformation;\n            request.timeout = timeout;\n\n            // Make the MatsSocket Envelope that will be sent.\n            let envelope = Object.create(null);\n            envelope.t = MessageType.REQUEST;\n            envelope.eid = endpointId;\n            envelope.msg = message;\n            envelope.to = timeout;\n\n            _addInformationBearingEnvelopeToPipeline(envelope, traceId, initiation, request);\n        });\n    };\n\n    /**\n     * Synchronously flush any pipelined messages, i.e. when the method exits, webSocket.send(..) has been invoked\n     * with the serialized pipelined messages, <i>unless</i> the authorization had expired (read more at\n     * {@link MatsSocket#setCurrentAuthorization} and {@link MatsSocket#setAuthorizationExpiredCallback}).\n     */\n    this.flush = function () {\n        // ?: Are we currently doing \"auto-pipelining\"?\n        if (_evaluatePipelineLater_timeoutId) {\n            // -> Yes, so clear this timeout, since we're flushing now.\n            clearTimeout(_evaluatePipelineLater_timeoutId);\n            _evaluatePipelineLater_timeoutId = undefined;\n        }\n        // Do flush\n        _evaluatePipelineSend();\n    };\n\n    /**\n     * Closes any currently open WebSocket with MatsSocket-specific CloseCode CLOSE_SESSION (4000). Depending\n     * of the value of {@link MatsSocket#outofbandclose}, it <i>also</i> uses <code>navigator.sendBeacon(..)</code>\n     * (if present, i.e. web browser context) to send an out-of-band Close Session HTTP POST, or, if\n     * 'outofbancclose' is a function, this is invoked (if 'outofbandclose' is <code>false</code>, this\n     * functionality is disabled). Upon receiving the WebSocket close, the server terminates the MatsSocketSession.\n     * The MatsSocket instance's SessionId is made undefined. If there currently is a pipeline,\n     * this will be dropped (i.e. messages deleted), any outstanding receiveCallbacks\n     * (from Requests) are invoked, and received Promises (from sends) are rejected, with type\n     * {@link ReceivedEventType.SESSION_CLOSED}, outstanding Reply Promises (from Requests)\n     * are rejected with {@link MessageEventType.SESSION_CLOSED}. The effect is to cleanly shut down the\n     * MatsSocketSession (all session data removed from server), and also clean the MatsSocket instance.\n     * <p />\n     * Afterwards, the MatsSocket can be started up again by sending a message - keeping its configuration wrt.\n     * terminators, endpoints and listeners. As The SessionId on this client MatsSocket was cleared (and the\n     * previous Session on the server is deleted), this will result in a new server side Session. If you want a\n     * totally clean MatsSocket instance, then just ditch the current instance and make a new one (which then will\n     * have to be configured with terminators etc).\n     * <p />\n     * <b>Note: A 'beforeunload' event handler is automatically registered on 'window' (if present, i.e. MatsSocket is\n     * running in a web browser), which invokes this method</b>, so that if the user navigates away, the session will\n     * be closed.\n     *\n     * @param {string} reason short descriptive string. Will be supplied with the webSocket close reason string,\n     * and must therefore be quite short (max 123 chars).\n     */\n    this.close = function (reason) {\n        // Fetch properties we need before clearing state\n        let existingWebSocketUrl = _currentWebSocketUrl;\n        let existingSessionId = that.sessionId;\n        log(\"close(): Closing MatsSocketSession, id:[\" + existingSessionId + \"] due to [\" + reason\n            + \"], currently connected: [\" + (_webSocket ? _webSocket.url : \"not connected\") + \"]\");\n\n        // :: In-band Session Close: Close the WebSocket itself with CLOSE_SESSION Close Code.\n        // ?: Do we have _webSocket?\n        if (_webSocket) {\n            // -> Yes, so close WebSocket with MatsSocket-specific CloseCode CLOSE_SESSION 4000.\n            log(\" \\\\-> WebSocket is open, so we perform in-band Session Close by closing the WebSocket with MatsSocketCloseCode.CLOSE_SESSION (4000).\");\n            // Perform the close\n            _webSocket.close(MatsSocketCloseCodes.CLOSE_SESSION, \"From client: \" + reason);\n        }\n\n        // Close Session and clear all state of this MatsSocket.\n        _closeSessionAndClearStateAndPipelineAndFuturesAndOutstandingMessages();\n\n        // :: Out-of-band Session Close\n        // ?: Do we have a sessionId?\n        if (existingSessionId) {\n            // ?: Is the out-of-band close function defined?\n            if (typeof (this.outofbandclose) === \"function\") {\n                // -> Yes, function, so invoke it\n                this.outofbandclose({\n                    webSocketUrl: existingWebSocketUrl,\n                    sessionId: existingSessionId\n                });\n                // ?: Is the out-of-band close 'truthy'?\n            } else if (this.outofbandclose) {\n                // -> Yes, truthy, so perform default logic\n                // ?: Do we have 'navigator'?\n                if ((typeof window !== 'undefined') && (typeof window.navigator !== 'undefined')) {\n                    // -> Yes, navigator present, so then we can fire off the out-of-band close too\n                    // Fire off a \"close\" over HTTP using navigator.sendBeacon(), so that even if the socket is closed, it is possible to terminate the MatsSocket SessionId.\n                    let closeSesionUrl = existingWebSocketUrl.replace(\"ws\", \"http\") + \"/close_session?session_id=\" + existingSessionId;\n                    log(\"  \\\\- Send an out-of-band (i.e. HTTP) close_session, using navigator.sendBeacon('\" + closeSesionUrl + \"').\");\n                    let success = window.navigator.sendBeacon(closeSesionUrl);\n                    log(\"    \\\\- Result: \" + (success ? \"Enqueued POST, but do not know whether anyone received it - check Network tab of Dev Tools.\" : \"Did NOT manage to enqueue a POST.\"));\n                }\n            }\n        }\n    };\n\n    /**\n     * Effectively emulates \"lost connection\". Used in testing.\n     * <p />\n     * If the \"disconnect\" parameter is true, it will disconnect with {@link MatsSocketCloseCodes.DISCONNECT}\n     * instead of {@link MatsSocketCloseCodes.RECONNECT}, which will result in the MatsSocket not immediately\n     * starting the reconnection procedure until a new message is added.\n     *\n     * @param reason {String} a string saying why.\n     * @param disconnect {Boolean} whether to close with {@link MatsSocketCloseCodes.DISCONNECT} instead of\n     * {@link MatsSocketCloseCodes.RECONNECT} - default <code>false</code>. AFAIK, only useful in testing..!\n     */\n    this.reconnect = function (reason, disconnect = false) {\n        let closeCode = disconnect ? MatsSocketCloseCodes.DISCONNECT : MatsSocketCloseCodes.RECONNECT;\n        if (that.logging) log(\"reconnect(): Closing WebSocket with CloseCode '\" + MatsSocketCloseCodesUtil.nameFor(closeCode) + \" (\" + closeCode + \")',\" +\n            \" MatsSocketSessionId:[\" + that.sessionId + \"] due to [\" + reason + \"], currently connected: [\" + (_webSocket ? _webSocket.url : \"not connected\") + \"]\");\n        if (!_webSocket) {\n            throw new Error(\"There is no live WebSocket to close with \" + MatsSocketCloseCodesUtil.nameFor(closeCode) + \" closeCode!\");\n        }\n        // Hack for Node: Node is too fast wrt. handling the reply message, so one of the integration tests fails.\n        // The test in question reconnect in face of having the test RESOLVE in the incomingHandler, which exercises\n        // the double-delivery catching when getting a direct RESOLVE instead of an ACK from the server.\n        // However, the following RECONNECT-close is handled /after/ the RESOLVE message comes in, and ok's the test.\n        // So then, MatsSocket dutifully starts reconnecting - /after/ the test is finished. Thus, Node sees the\n        // outstanding timeout \"thread\" which pings, and never exits. To better emulate an actual lost connection,\n        // we /first/ unset the 'onmessage' handler (so that any pending messages surely will be lost), before we\n        // close the socket. Notice that a \"_matsSocketOpen\" guard was also added, so that it shall explicitly stop\n        // such reconnecting in face of an actual .close(..) invocation.\n\n        // First unset message handler so that we do not receive any more WebSocket messages (but NOT unset 'onclose', nor 'onerror')\n        _webSocket.onmessage = undefined;\n        // Now closing the WebSocket (thus getting the 'onclose' handler invoked - just as if we'd lost connection, or got this RECONNECT close from Server).\n        _webSocket.close(closeCode, reason);\n    };\n\n    /**\n     * Convenience method for making random strings meant for user reading, e.g. as a part of a good TraceIds, since this\n     * alphabet only consists of lower and upper case letters, and digits. To make a traceId \"unique enough\" for\n     * finding it in a log system, a length of 6 should be plenty. The alphabet is 62 chars.\n     *\n     * @param {number} length how long the string should be, default is 6.\n     * @returns {string} a random string consisting of characters from from digits, lower and upper case letters\n     * (62 chars).\n     */\n    this.randomId = function (length= 6) {\n        let result = '';\n        for (let i = 0; i < length; i++) {\n            result += _alphabet[Math.floor(Math.random() * _alphabet.length)];\n        }\n        return result;\n    };\n\n    /**\n     * Convenience method for making random strings for correlationIds, not meant for human reading as the alphabet\n     * consist of all visible ACSII chars that won't be quoted in a JSON string. Should you want to make actual Session\n     * cookies or similar, that is, ids being very unique and hard to brute force, you would want to have a longer length,\n     * use e.g. length=16. The alphabet is 92 chars.\n     * @param {number} length how long the string should be, default is 10.\n     * @returns {string} a random string consisting of characters from all visible and non-JSON-quoted chars of\n     * ASCII (92 chars).\n     */\n    this.randomCId = function (length= 10) {\n        let result = '';\n        for (let i = 0; i < length; i++) {\n            result += _jsonAlphabet[Math.floor(Math.random() * _jsonAlphabet.length)];\n        }\n        return result;\n    };\n\n    // ==============================================================================================\n    // PRIVATE\n    // ==============================================================================================\n\n    const start = Date.now();\n\n    function log(msg, object) {\n        if (that.logging) {\n            if (object) {\n                console.log(_matsSocketInstanceId + \"/\" + that.sessionId + \"{\" + (Date.now() - start) + \"}: \" + msg, object);\n            } else {\n                console.log(_matsSocketInstanceId + \"/\" + that.sessionId + \"{\" + (Date.now() - start) + \"}: \" + msg);\n            }\n        }\n    }\n\n    function error(type, msg, err) {\n        let event = new ErrorEvent(type, msg, err);\n        // Notify ErrorEvent listeners, synchronously.\n        for (let i = 0; i < _errorEventListeners.length; i++) {\n            try {\n                _errorEventListeners[i](event);\n            } catch (err) {\n                // NOTICE! NOT using error(..) - THIS method - to notify about errors, in fear of ending up with infinite recursion\n                console.error(\"Caught error when notifying one of the [\" + _errorEventListeners.length + \"] ErrorEvent listeners - NOT notifying using ErrorEvent in fear of creating infinite recursion.\", err);\n            }\n        }\n\n        if (that.errorLogging) {\n            if (err) {\n                console.error(type + \": \" + msg, err);\n            } else {\n                console.error(type + \": \" + msg);\n            }\n        }\n    }\n\n    // ==== Fields\n\n    // Simple Alphabet: All digits, lower and upper ASCII chars: 10 + 26 x 2 = 62 chars.\n    const _alphabet = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n    // Alphabet of JSON-non-quoted and visible chars: 92 chars.\n    const _jsonAlphabet = \"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~\";\n\n    // The URLs to use - will be shuffled. Can be reset to not randomized by this.disableUrlRandomize()\n    let _useUrls = [].concat(urls);\n    // Shuffle the URLs\n    _shuffleArray(_useUrls);\n\n    const _matsSocketInstanceId = that.randomId(3);\n    let _lastMessageEnqueuedTimestamp = Date.now(); // Start by assuming that it was just used.\n    let _initialSessionEstablished_PerformanceNow = undefined;\n\n    // If true, we're currently already trying to get a WebSocket\n    let _webSocketConnecting = false;\n    // If NOT undefined, we have an open WebSocket available.\n    let _webSocket = undefined; // NOTE: It is set upon \"onopen\", and unset upon \"onclose\".\n    // If false, we should not accidentally try to reconnect or similar\n    let _matsSocketOpen = false; // NOTE: Set to true upon enqueuing of information-bearing message.\n\n    let _prePipeline = [];\n    let _pipeline = [];\n    let _terminators = Object.create(null);\n    let _endpoints = Object.create(null);\n    let _subscriptions = Object.create(null);\n\n    let _sessionClosedEventListeners = [];\n    let _connectionEventListeners = [];\n    let _subscriptionEventListeners = [];\n    let _pingPongListeners = [];\n    let _initiationProcessedEventListeners = [];\n    let _errorEventListeners = [];\n\n    let _state = ConnectionState.NO_SESSION;\n\n    let _helloSent = false;\n    let _forcePipelineProcessing = false;\n\n    let _authorization = undefined;\n    let _lastAuthorizationSentToServer = undefined;\n    let _lastDebugOptionsSentToServer = undefined;\n    let _expirationTimestamp = undefined;\n    let _roomForLatencyMillis = undefined;\n    let _authorizationExpiredCallback = undefined;\n\n    let _messageSequenceId = 0; // Increases for each SEND, REQUEST and REPLY\n\n    // When we've informed the app that we need auth, we do not need to do it again until it has set it.\n    let _authExpiredCallbackInvoked_EventType = undefined;\n\n    // Outstanding Pings\n    const _outstandingPings = Object.create(null);\n    // Outstanding Request \"futures\", i.e. the resolve() and reject() functions of the returned Promise.\n    const _outstandingRequests = Object.create(null);\n    // Outbox for SEND and REQUEST messages waiting for Received ACK/NACK\n    const _outboxInitiations = Object.create(null);\n    // .. \"guard object\" to avoid having to retransmit messages sent /before/ the WELCOME is received for the HELLO handshake\n    let _outboxInitiations_RetransmitGuard = this.randomCId(5);\n    // Outbox for REPLYs\n    const _outboxReplies = Object.create(null);\n    // The Inbox - to be able to catch double deliveries from Server\n    let _inbox = Object.create(null);\n\n    // :: STATS\n    // Last 100 PingPong instances\n    const _pings = [];\n    // Last X InitationProcessedEvent instances.\n    let _numberOfInitiationsKept = 10;\n    const _initiationProcessedEvents = [];\n\n    // ==== Register \"system\" endpoints\n\n    _endpoints['MatsSocket.renewAuth'] = function (messageEvent) {\n        return new Promise(function (resolve, _) {\n            // Immediately ask for new Authorization\n            _requestNewAuthorizationFromApp(\"MatsSocket.renewAuth was invoked\", new AuthorizationRequiredEvent(AuthorizationRequiredEventType.REAUTHENTICATE, undefined));\n            // .. then immediately resolve the server side request.\n            // This will add a message to the pipeline, but the pipeline will not be sent until new auth present.\n            // When the new auth comes in, the message will be sent, and resolve on the server side - and new auth\n            // is then \"magically\" present on the incoming-context.\n            resolve({});\n        });\n    };\n\n    // ==== Implementation ====\n\n    // Hack to get past Next's server-side compilation error: \"Module not found: Can't resolve 'ws'\".\n    function _importWsLazy() {\n        // Note: Never executed in the browser\n        return import(\n            /* webpackIgnore: true */\n            /* @vite-ignore */\n            // @ts-expect-error TS2307: (Cannot find module) it is up to the user to provide 'ws' when used in node.\n            'ws'\n            );\n    }\n\n    function _isNodeJs() {\n        // @ts-expect-error TS2580: (Cannot find name process): This is node-only, and this is the point.\n        const proc = process;\n        return proc && proc.versions && (typeof proc.versions.node !== 'undefined');\n    }\n\n    function _invokeLater(that) {\n        setTimeout(that, 0);\n    }\n\n    // https://stackoverflow.com/a/12646864/39334\n    function _shuffleArray(array) {\n        for (let i = array.length - 1; i > 0; i--) {\n            let j = Math.floor(Math.random() * (i + 1));\n            let temp = array[i];\n            array[i] = array[j];\n            array[j] = temp;\n        }\n    }\n\n    function _beforeunloadHandler() {\n        that.close(CLIENT_LIB_NAME_AND_VERSION + \" window.onbeforeunload close\");\n    }\n\n    function _registerBeforeunload() {\n        // ?: Is the self object an EventTarget (ie. window in a Browser context)\n        if (typeof (self) === 'object' && typeof (self.addEventListener) === 'function') {\n            // Yes -> Add \"onbeforeunload\" event listener to shut down the MatsSocket cleanly (closing session) when user navigates away from page.\n            self.addEventListener(\"beforeunload\", _beforeunloadHandler);\n        }\n    }\n\n    function _deregisterBeforeunload() {\n        if (typeof (self) === 'object' && typeof (self.addEventListener) === 'function') {\n            self.removeEventListener(\"beforeunload\", _beforeunloadHandler);\n        }\n    }\n\n    function _clearWebSocketStateAndInfrastructure() {\n        log(\"clearWebSocketStateAndInfrastructure(). Current WebSocket:\" + _webSocket);\n        // Stop pinger\n        _stopPinger();\n        // Remove beforeunload eventlistener\n        _deregisterBeforeunload();\n        // Reset Reconnect state vars\n        _resetReconnectStateVars();\n        // Make new RetransmitGuard - so that any previously \"guarded\" messages now will be retransmitted.\n        _outboxInitiations_RetransmitGuard = that.randomCId(5);\n        // :: Clear out _webSocket;\n        if (_webSocket) {\n            // We don't want the onclose callback invoked from this event that we initiated ourselves.\n            _webSocket.onclose = undefined;\n            // We don't want any messages either\n            _webSocket.onmessage = undefined;\n            // Also drop onerror for good measure.\n            _webSocket.onerror = undefined;\n        }\n        _webSocket = undefined;\n    }\n\n    function _closeSessionAndClearStateAndPipelineAndFuturesAndOutstandingMessages() {\n        log(\"closeSessionAndClearStateAndPipelineAndFuturesAndOutstandingMessages(). Current WebSocket:\" + _webSocket);\n        // Clear state\n        that.sessionId = undefined;\n        _state = ConnectionState.NO_SESSION;\n\n        _clearWebSocketStateAndInfrastructure();\n\n        // :: Clear pipeline\n        _pipeline.length = 0;\n\n        // Were now also NOT open, until a new message is enqueued.\n        _matsSocketOpen = false;\n\n        // :: NACK all outstanding messages\n        for (let cmid in _outboxInitiations) {\n            let initiation = _outboxInitiations[cmid];\n            delete _outboxInitiations[cmid];\n\n            if (that.logging) log(\"Close Session: Clearing outstanding Initiation [\" + initiation.envelope.t + \"] to [\" + initiation.envelope.eid + \"], cmid:[\" + initiation.envelope.cmid + \"], TraceId:[\" + initiation.envelope.tid + \"].\");\n            _completeReceived(ReceivedEventType.SESSION_CLOSED, initiation, Date.now());\n        }\n\n        // :: Reject all Requests\n        for (let cmid in _outstandingRequests) {\n            let request = _outstandingRequests[cmid];\n            delete _outstandingRequests[cmid];\n\n            if (that.logging) log(\"Close Session: Clearing outstanding REQUEST to [\" + request.envelope.eid + \"], cmid:[\" + request.envelope.cmid + \"], TraceId:[\" + request.envelope.tid + \"].\", request);\n            _completeRequest(request, MessageEventType.SESSION_CLOSED, {}, Date.now());\n        }\n    }\n\n    function _addInformationBearingEnvelopeToPipeline(envelope, traceId, initiation, request) {\n        // This is an information-bearing message, so now this MatsSocket instance is open.\n        _matsSocketOpen = true;\n        let now = Date.now();\n        let performanceNow = performance.now();\n\n        // Add the traceId to the message\n        envelope.tid = traceId;\n        // Add next message Sequence Id\n        let thisMessageSequenceId = _messageSequenceId++;\n        envelope.cmid = thisMessageSequenceId;\n\n        // :: Debug\n        // Set to either specific override, or global default.\n        initiation.debug = (initiation.debug !== undefined ? initiation.debug : (that.debug ? that.debug : 0));\n        // ?: Is the debug not 0?\n        if (initiation.debug !== 0) {\n            // -> Yes, not 0 - so send it in request.\n            envelope.rd = initiation.debug;\n        }\n        // ?: If the last transferred was /something/, while now it is 0, then we must send it over to reset\n        if ((initiation.debug === 0) && (_lastDebugOptionsSentToServer !== 0)) {\n            // -> Yes, was reset to 0 - so must send to server.\n            envelope.rd = 0;\n        }\n        // This is the last debug option the server knows.\n        _lastDebugOptionsSentToServer = initiation.debug;\n\n        // Update timestamp of last \"information bearing message\" sent.\n        _lastMessageEnqueuedTimestamp = now;\n\n        // Make lambda for what happens when it has been RECEIVED on server.\n        // Store the outgoing envelope\n        initiation.envelope = envelope;\n        // Store which \"retransmitGuard\" we were at when sending this (look up usage for why)\n        initiation.retransmitGuard = _outboxInitiations_RetransmitGuard;\n        // Start the attempt counter. We start at 1, as we immediately will enqueue this envelope..\n        initiation.attempt = 1;\n        // Store the sent timestamp.\n        initiation.sentTimestamp = now;\n        // Store performance.now for round-trip timing\n        initiation.messageSent_PerformanceNow = performanceNow;\n        // Make a (mental) slot for the messageAcked_PerformanceNow (set at ACK processing)\n        initiation.messageAcked_PerformanceNow = undefined;\n\n        // Initiation state created - store the outstanding Send or Request\n        _outboxInitiations[thisMessageSequenceId] = initiation;\n\n        // ?: Do we have a request?\n        if (request) {\n            // -> Yes, this is a REQUEST!\n            // Store the initiation\n            request.initiation = initiation;\n            // Store the outgoing envelope (could have gotten it through request.initiation, but too much hassle too many places)\n            request.envelope = envelope;\n\n            // Make timeouter\n            request.timeoutId = setTimeout(function () {\n                // :: The Request timeout was hit.\n                let performanceNow = performance.now();\n                if (that.logging) log(\"TIMEOUT! Request with TraceId:[\" + request.envelope.tid + \"], cmid:[\" + request.envelope.cmid + \"] overshot timeout [\" + (performanceNow - request.initiation.messageSent_PerformanceNow) + \" ms of \" + request.timeout + \"]\");\n                // Check if we've gotten the ACK/NACK\n                let initiation = _outboxInitiations[thisMessageSequenceId];\n                // ?: Was the initiation still present?\n                if (initiation) {\n                    // -> Yes, still present, so we have not gotten the ACK/NACK from Server yet, thus NACK it with ReceivedEventType.TIMEOUT\n                    delete _outboxInitiations[thisMessageSequenceId];\n                    _completeReceived(ReceivedEventType.TIMEOUT, initiation, Date.now());\n                }\n\n                // :: Complete the Request with MessageEventType.TIMEOUT\n\n                /*\n                 * NOTICE!! HACK-ALERT! The ordering of events wrt. Requests is as such:\n                 * 1. ReceivedEvent (receivedCallback for requests, and Received-Promise for requestReplyTo)\n                 * 2. InitiationProcessedEvent stored on matsSocket.initiations\n                 * 3. InitiationProcessedEvent listeners\n                 * 4. MessageEvent (Reply-Promise for requests, Terminator callbacks for requestReplyTo)\n                 *\n                 * WITH a requestReplyTo, the ReceivedEvent becomes async in nature, since requestReplyTo returns\n                 * a Promise<ReceivedEvent>. Also, with a requestReplyTo, the completing of the requestReplyTo is\n                 * then done on a Terminator, using its specified callbacks - and this is done using\n                 * setTimeout(.., 0) to \"emulate\" the same async-ness as a Reply-Promise with ordinary requests.\n                 * However, the timing between the ReceivedEvent and InitiationProcessedEvent then becomes\n                 * rather shaky. Therefore, IF the initiation is still in place (ReceivedEvent not yet issued),\n                 * AND this is a requestReplyTo, THEN we delay the completion of the Request (i.e. issue\n                 * InitiationProcessedEvent and MessageEvent) to be more certain that the ReceivedEvent is\n                 * processed before the rest.\n                 */\n                // ?: Did we still have the initiation in place, AND this is a requestReplyTo?\n                if (initiation && request.replyToTerminatorId) {\n                    // -> Yes, the initiation was still in place (i.e. ReceivedEvent not issued), and this was\n                    // a requestReplyTo:\n                    // Therefore we delay the entire completion of the request (InitiationProcessedEvent and\n                    // MessageEvent), to be sure that they happen AFTER the ReceivedEvent issued above.\n                    setTimeout(function () {\n                        _completeRequest(request, MessageEventType.TIMEOUT, {}, Date.now());\n                    }, 50);\n                } else {\n                    // -> No, either the initiation was already gone (ReceivedEvent already issued), OR it was\n                    // not a requestReplyTo:\n                    // Therefore, we run the completion right away (InitiationProcessedEvent is sync, while\n                    // MessageEvent is a Promise settling).\n                    _completeRequest(request, MessageEventType.TIMEOUT, {}, Date.now());\n                }\n            }, request.timeout);\n\n            // Request state created - store this outstanding Request.\n            _outstandingRequests[thisMessageSequenceId] = request;\n        }\n\n        _addEnvelopeToPipeline_EvaluatePipelineLater(envelope);\n    }\n\n    /**\n     * Unconditionally adds the supplied envelope to the pipeline, and then evaluates the pipeline,\n     * invokeLater-style so as to get \"auth-pipelining\". Use flush() to get sync send.\n     */\n    function _addEnvelopeToPipeline_EvaluatePipelineLater(envelope, prePipeline = false) {\n        // ?: Should we add it to the pre-pipeline, or the ordinary?\n        if (prePipeline) {\n            // -> To pre-pipeline\n            if (that.logging) log(\"ENQUEUE: Envelope of type [\" + envelope.t + \"] enqueued to PRE-pipeline: \" + JSON.stringify(envelope));\n            _prePipeline.push(envelope);\n        } else {\n            // -> To ordinary pipeline.\n            if (that.logging) log(\"ENQUEUE: Envelope of type [\" + envelope.t + \"] enqueued to pipeline: \" + JSON.stringify(envelope));\n            _pipeline.push(envelope);\n        }\n        // Perform \"auto-pipelining\", by waiting a minimal amount of time before actually sending.\n        _evaluatePipelineLater();\n    }\n\n    let _evaluatePipelineLater_timeoutId = undefined;\n\n    function _evaluatePipelineLater() {\n        if (_evaluatePipelineLater_timeoutId) {\n            clearTimeout(_evaluatePipelineLater_timeoutId);\n        }\n        _evaluatePipelineLater_timeoutId = setTimeout(function () {\n            _evaluatePipelineLater_timeoutId = undefined;\n            _evaluatePipelineSend();\n        }, 10);\n    }\n\n    /**\n     * Sends pipelined messages\n     */\n    function _evaluatePipelineSend() {\n        // ?: Are there any messages in pipeline or PRE-pipeline,\n        // or should we force pipeline processing (either to get HELLO, SUB or AUTH over)\n        if ((_pipeline.length === 0) && (_prePipeline.length === 0) && !_forcePipelineProcessing) {\n            // -> No, no message in pipeline, and we should not force processing to get HELLO or AUTH over\n            // Nothing to do, drop out.\n            return;\n        }\n        // ?: Is the MatsSocket open yet? (I.e. an information-bearing message has been enqueued)\n        if (!_matsSocketOpen) {\n            // -> No, so ignore this invocation - come back when there is something to send!\n            log(\"evaluatePipelineSend(), but MatsSocket is not open - ignoring.\");\n            return;\n        }\n        // ?: Do we have authorization?!\n        if (_authorization === undefined) {\n            // -> No, authorization not present.\n            _requestNewAuthorizationFromApp(\"Authorization not present\", new AuthorizationRequiredEvent(AuthorizationRequiredEventType.NOT_PRESENT, undefined));\n            return;\n        }\n        // ?: Check whether we have expired authorization\n        if ((_expirationTimestamp !== undefined) && (_expirationTimestamp !== -1)\n            && ((_expirationTimestamp - _roomForLatencyMillis) < Date.now())) {\n            // -> Yes, authorization is expired.\n            _requestNewAuthorizationFromApp(\"Authorization is expired\", new AuthorizationRequiredEvent(AuthorizationRequiredEventType.EXPIRED, _expirationTimestamp));\n            return;\n        }\n        // ?: Check that we are not already waiting for new auth\n        // (This is needed here since we might actually have valid authentication, but server has still asked us, via \"REAUTH\", to get new)\n        if (_authExpiredCallbackInvoked_EventType) {\n            log(\"We have asked app for new authorization, and still waiting for it.\");\n            return;\n        }\n\n        // ----- We have good authentication, and should send pipeline.\n\n        // ?: Are we trying to open websocket?\n        if (_webSocketConnecting) {\n            log(\"evaluatePipelineSend(): WebSocket is currently connecting. Cannot send yet.\");\n            // -> Yes, so then the socket is not open yet, but we are in the process.\n            // Return now, as opening is async. When the socket opens, it will re-run 'evaluatePipelineSend()'.\n            return;\n        }\n\n        // ?: Is the WebSocket present?\n        if (_webSocket === undefined) {\n            log(\"evaluatePipelineSend(): WebSocket is not present, so initiate creation. Cannot send yet.\");\n            // -> No, so go get it.\n            _initiateWebSocketCreation();\n            // Returning now, as opening is async. When the socket opens, it will re-run 'evaluatePipelineSend()'.\n            return;\n        }\n\n        // ----- WebSocket is open, so send any outstanding messages!!\n\n        // ?: Have we sent HELLO?\n        if (!_helloSent) {\n            // -> No, HELLO not sent, so we create it now (auth is present, check above)\n            let helloMessage = {\n                t: MessageType.HELLO,\n                clv: CLIENT_LIB_NAME_AND_VERSION + \"; User-Agent: \" + userAgent,\n                ts: Date.now(),\n                an: appName,\n                av: appVersion,\n                auth: _authorization,  // This is guaranteed to be in place and valid, see above\n                cid: that.randomId(10)\n            };\n            // ?: Have we requested a reconnect?\n            if (that.sessionId !== undefined) {\n                log(\"HELLO not send, adding to pre-pipeline. HELLO (\\\"Reconnect\\\") to MatsSocketSessionId:[\" + that.sessionId + \"]\");\n                // -> Evidently yes, so add the requested reconnect-to-sessionId.\n                helloMessage.sid = that.sessionId;\n            } else {\n                // -> We want a new session (which is default anyway)\n                log(\"HELLO not sent, adding to pre-pipeline. HELLO (\\\"New\\\"), we will get assigned a MatsSocketSessionId upon WELCOME.\");\n            }\n            // Add the HELLO to the prePipeline\n            _prePipeline.unshift(helloMessage);\n            // We will now have sent the HELLO, so do not send it again.\n            _helloSent = true;\n            // We've sent the current auth\n            _lastAuthorizationSentToServer = _authorization;\n\n            // :: Handle subscriptions\n            for (let topicId in _subscriptions) {\n                let subs = _subscriptions[topicId];\n                // ?: Do we have subscribers, but not sent to server?\n                if ((subs.listeners.length > 0) && (!subs.subscriptionSentToServer)) {\n                    // -> Yes, so we need to subscribe\n                    _prePipeline.push({\n                        t: MessageType.SUB,\n                        eid: topicId,\n                        smid: subs.lastSmid\n                    });\n                }\n                // ?: Do we NOT have subscribers, but sub is sent to Server?\n                if ((subs.listeners.length === 0) && (subs.subscriptionSentToServer)) {\n                    // -> Yes, so we need to unsubscribe - and delete the subscription\n                    _prePipeline.push({\n                        t: MessageType.UNSUB,\n                        eid: topicId\n                    });\n                    // Delete this local subscription.\n                    delete _subscriptions[topicId];\n                }\n            }\n        }\n\n        // ?: Have we sent HELLO, i.e. session is \"active\", but the authorization has changed since last we sent over authorization?\n        if (_helloSent && (_lastAuthorizationSentToServer !== _authorization)) {\n            // -> Yes, it has changed, so add it to some envelope - either last in pipeline, or if empty pipe, then make an AUTH message.\n            if (_pipeline.length > 0) {\n                let lastEnvelope = _pipeline[_pipeline.length - 1];\n                if (that.logging) log(\"Authorization has changed, and there is a message in pipeline of type [\" + lastEnvelope.t + \"], so so we add 'auth' to it.\");\n                lastEnvelope.auth = _authorization;\n            } else {\n                if (that.logging) log(\"Authorization has changed, but there is no message in pipeline, so we add an AUTH message now.\");\n                _pipeline.push({t: MessageType.AUTH, auth: _authorization});\n            }\n            // The current authorization is now sent\n            _lastAuthorizationSentToServer = _authorization;\n        }\n\n        // We're now doing a round of pipeline processing, so turn of forcing.\n        _forcePipelineProcessing = false;\n\n        // :: Send PRE-pipeline messages, if there are any\n        // (Before the HELLO is sent and sessionId is established, the max size of message is low on the server)\n        if (_prePipeline.length > 0) {\n            if (that.logging) log(\"Flushing prePipeline of [\" + _prePipeline.length + \"] messages: \" + JSON.stringify(_prePipeline));\n            _webSocket.send(JSON.stringify(_prePipeline));\n            // Clear prePipeline\n            _prePipeline.length = 0;\n        }\n        // :: Send any pipelined messages.\n        if (_pipeline.length > 0) {\n            if (that.logging) log(\"Flushing pipeline of [\" + _pipeline.length + \"] messages: \" + JSON.stringify(_pipeline));\n            _webSocket.send(JSON.stringify(_pipeline));\n            // Clear pipeline\n            _pipeline.length = 0;\n        }\n    }\n\n    function _requestNewAuthorizationFromApp(what, event) {\n        // ?: Have we already asked app for new auth?\n        if (_authExpiredCallbackInvoked_EventType) {\n            // -> Yes, so just return.\n            log(what + \", but we've already asked app for it due to: [\" + _authExpiredCallbackInvoked_EventType + \"].\");\n            return;\n        }\n        // E-> No, not asked for auth - so do it.\n        log(what + \". Will not send pipeline until gotten. Invoking 'authorizationExpiredCallback', type:[\" + event.type + \"].\");\n        // We will have asked for auth after this.\n        _authExpiredCallbackInvoked_EventType = event.type;\n        // Assert that we have callback\n        if (!_authorizationExpiredCallback) {\n            // -> We do not have callback! This is actually disaster.\n            let reason = \"From Client: Need new authorization, but missing 'authorizationExpiredCallback'. This is fatal, cannot continue.\";\n            error(\"missingauthcallback\", reason);\n            // !! We need to close down\n            if (_webSocket) {\n                // -> Yes, so close WebSocket with MatsSocket-specific CloseCode CLOSE_SESSION 4000.\n                log(\" \\\\-> WebSocket is open, so we perform in-band Session Close by closing the WebSocket with MatsSocketCloseCode.CLOSE_SESSION (4000).\");\n                // Perform the close\n                _webSocket.close(MatsSocketCloseCodes.CLOSE_SESSION, reason);\n            }\n            let outstandingInitiations = Object.keys(_outboxInitiations).length;\n            // Close Session and clear all state of this MatsSocket.\n            _closeSessionAndClearStateAndPipelineAndFuturesAndOutstandingMessages();\n            // Notify SessionClosedEventListeners - with a fake CloseEvent\n            _notifySessionClosedEventListeners({\n                type: \"close\",\n                code: MatsSocketCloseCodes.VIOLATED_POLICY,\n                codeName: \"VIOLATED_POLICY\",\n                reason: reason,\n                outstandingInitiations: outstandingInitiations\n            });\n\n            return;\n        }\n        // E-> We do have 'authorizationExpiredCallback', so ask app for new auth\n        _authorizationExpiredCallback(event);\n    }\n\n    let _connectionAttempt = 0; // A counter of how many times a connection attempt has been performed, starts at 0th attempt.\n\n    let _urlIndexCurrentlyConnecting = 0; // Cycles through the URLs\n    let _connectionAttemptRound = 0; // When cycled one time through URLs, this increases.\n    let _currentWebSocketUrl = undefined;\n\n    let _connectionTimeoutBase = 500; // Base timout, milliseconds. Doubles, up to max defined below.\n    let _connectionTimeoutMinIfSingleUrl = 5000; // Min timeout if single-URL configured, milliseconds.\n    // Based on whether there is multiple URLs, or just a single one, we choose the short \"timeout base\", or a longer one, as minimum.\n    let _connectionTimeoutMin = _useUrls.length > 1 ? _connectionTimeoutBase : _connectionTimeoutMinIfSingleUrl;\n    let _connectionTimeoutMax = 15000; // Milliseconds max between connection attempts.\n\n    function _maxConnectionAttempts() {\n        // ?: Have maxConnectionAttempts been set?\n        if (that.maxConnectionAttempts !== undefined) {\n            // -> Yes, so use this.\n            return that.maxConnectionAttempts;\n        }\n        return that.sessionId !== null\n            ? 5760 // The default should be about a day..! 15 sec per attempt: 5760*15 sec = 60*60*24 sec\n            : 60; // Way fewer attempts if no session ID is present.\n    }\n\n    function _increaseReconnectStateVars() {\n        _connectionAttempt++;\n        _urlIndexCurrentlyConnecting++;\n        if (_urlIndexCurrentlyConnecting >= _useUrls.length) {\n            _urlIndexCurrentlyConnecting = 0;\n            _connectionAttemptRound++;\n        }\n        _currentWebSocketUrl = _useUrls[_urlIndexCurrentlyConnecting];\n        log(\"## _increaseReconnectStateVars(): round:[\" + _connectionAttemptRound + \"], urlIndex:[\" + _urlIndexCurrentlyConnecting + \"] = \" + _currentWebSocketUrl);\n    }\n\n    function _resetReconnectStateVars() {\n        _connectionAttempt = 0;\n        _urlIndexCurrentlyConnecting = 0;\n        _connectionAttemptRound = 0;\n        _currentWebSocketUrl = _useUrls[_urlIndexCurrentlyConnecting];\n        log(\"## _resetReconnectStateVars(): round:[\" + _connectionAttemptRound + \"], urlIndex:[\" + _urlIndexCurrentlyConnecting + \"] = \" + _currentWebSocketUrl);\n    }\n\n    // .. Invoke resetConnectStateVars() right away to get URL set.\n    _resetReconnectStateVars();\n\n    function _secondsTenths(milliseconds) {\n        // Rounds to tenth of second, e.g. 2730 -> 2.7.\n        return Math.round(milliseconds / 100) / 10;\n    }\n\n    function _updateStateAndNotifyConnectionEventListeners(connectionEvent) {\n        // ?: Should we log? Logging is on, AND (either NOT CountDown, OR CountDown == initialSeconds, OR Countdown == whole second).\n        if (that.logging && ((connectionEvent.type !== ConnectionEventType.COUNTDOWN)\n            || (connectionEvent.countdownSeconds === connectionEvent.timeoutSeconds)\n            || (Math.round(connectionEvent.countdownSeconds) === connectionEvent.countdownSeconds))) {\n            log(\"Sending ConnectionEvent to listeners\", connectionEvent);\n        }\n        // ?: Is this a state?\n        let result = Object.keys(ConnectionState).filter(function (key) {\n            return ConnectionState[key] === connectionEvent.type;\n        });\n        if (result.length === 1) {\n            // -> Yes, this is a state - so update the state..!\n            _state = ConnectionState[result[0]];\n            log(\"The ConnectionEventType [\" + result[0] + \"] is also a ConnectionState - setting MatsSocket state [\" + _state + \"].\");\n        }\n\n        // :: Notify all ConnectionEvent listeners.\n        for (let i = 0; i < _connectionEventListeners.length; i++) {\n            try {\n                _connectionEventListeners[i](connectionEvent);\n            } catch (err) {\n                error(\"notify ConnectionEvent listeners\", \"Caught error when notifying one of the [\" + _connectionEventListeners.length + \"] ConnectionEvent listeners about [\" + connectionEvent.type + \"].\", err);\n            }\n        }\n    }\n\n    function _notifySessionClosedEventListeners(closeEvent) {\n        if (that.logging) log(\"Sending SessionClosedEvent to listeners\", closeEvent);\n        for (let i = 0; i < _sessionClosedEventListeners.length; i++) {\n            try {\n                _sessionClosedEventListeners[i](closeEvent);\n            } catch (err) {\n                error(\"notify SessionClosedEvent listeners\", \"Caught error when notifying one of the [\" + _sessionClosedEventListeners.length + \"] SessionClosedEvent listeners.\", err);\n            }\n        }\n    }\n\n    function _initiateWebSocketCreation() {\n        // ?: Assert that we do not have the WebSocket already\n        if (_webSocket !== undefined) {\n            // -> Damn, we did have a WebSocket. Why are we here?!\n            throw (new Error(\"Should not be here, as WebSocket is already in place!\"));\n        }\n        // ?: Verify that we are actually open - we should not be trying to connect otherwise.\n        if (!_matsSocketOpen) {\n            // -> We've been asynchronously closed - bail out from opening WebSocket\n            throw (new Error(\"The MatsSocket instance is closed, so we should not open WebSocket\"));\n        }\n\n        // ----- We do not already have a WebSocket and the MatsSocket instance is Open!\n\n        // :: First need to check whether we have OK Authorization - if not, we must terminate entire connection procedure, ask for new, and start over.\n        // Note: The start-over will happen when new auth comes in, _evaluatePipelineSend(..) is invoked, and there is no WebSocket there.\n        // ?: Check whether we have expired authorization\n        if ((_expirationTimestamp !== undefined) && (_expirationTimestamp !== -1)\n            && ((_expirationTimestamp - _roomForLatencyMillis) < Date.now())) {\n            // -> Yes, authorization is expired.\n            log(\"InitiateWebSocketCreation: Authorization is expired, we need new to continue.\");\n            // We are not connecting anymore\n            _webSocketConnecting = false;\n            // Request new auth\n            _requestNewAuthorizationFromApp(\"Authorization is expired\", new AuthorizationRequiredEvent(AuthorizationRequiredEventType.EXPIRED, _expirationTimestamp));\n            return;\n        }\n\n        // ------ We have a valid, unexpired authorization token ready to use for connection\n\n        if (! webSocketFactory) {\n            if (_nodeJsTryingToImportWebSocketModule) {\n                log(\"InitiateWebSocketCreation: Missing matsSocketFactory, but import in progress! Wait for result of import attempt.\")\n                _nodeJsCallbackOnceWebSocketModuleResolved = function() {\n                    log(\"InitiateWebSocketCreation: Was missing matsSocketFactory, but matsSocketFactory should now be in place. Attempting again.\")\n                    _initiateWebSocketCreation();\n                }\n                return;\n            }\n            else {\n                error(\"Missing WebSocket implementation\", \"InitiateWebSocketCreation: Missing matsSocketFactory, and there is no attempt in progress to get it. Fatal.\")\n                throw new Error(\"Missing matsSocketFactory!\");\n            }\n        }\n\n        // :: We are currently trying to connect! (This will be set to true repeatedly while in the process of opening)\n        _webSocketConnecting = true;\n\n        // Timeout: LESSER of \"max\" and \"timeoutBase * (2^round)\", which should lead to timeoutBase x1, x2, x4, x8 - but capped at max.\n        // .. but at least '_connectionTimeoutMin', which also handles the special case of longer minimum if just 1 URL.\n        // Shortcut exponential if we guaranteed would be at max, to avoid pow-overflow\n        let timeout = _connectionAttemptRound > 10\n            ? _connectionTimeoutMax\n            : Math.max(_connectionTimeoutMin, Math.min(_connectionTimeoutMax, _connectionTimeoutBase * Math.pow(2, _connectionAttemptRound)));\n        let currentCountdownTargetTimestamp = Date.now();\n        let targetTimeoutTimestamp = currentCountdownTargetTimestamp + timeout;\n        let secondsLeft = function () {\n            return Math.round(((targetTimeoutTimestamp - Date.now()) / 100)) / 10;\n        };\n\n        // About to create WebSocket, so notify our listeners about this.\n        _updateStateAndNotifyConnectionEventListeners(new ConnectionEvent(ConnectionEventType.CONNECTING, _currentWebSocketUrl, undefined, _secondsTenths(timeout), secondsLeft(), _connectionAttempt));\n\n        let preConnectOperationAbortFunction = undefined;\n        let websocketAttempt = undefined;\n        let countdownId = undefined;\n\n        /*\n         * Make a \"connection timeout\" countdown,\n         * This will re-invoke itself every 100 ms to create the COUNTDOWN events - until either cancelled by connect going through,\n         *  or it reaches targetTimeoutTimestamp (timeout), where it aborts the attempt, bumps the state vars,\n         *  and then re-runs the '_initiateWebSocketCreation' method.\n         */\n        function w_countDownTimer() {\n            // ?: Assert that we're still open\n            if (!_matsSocketOpen) {\n                log(\"When doing countdown rounds, we realize that this MatsSocket instance is closed! - stopping right here.\");\n                w_abortAttempt();\n                return;\n            }\n            // :: Find next target\n            while (currentCountdownTargetTimestamp <= Date.now()) {\n                currentCountdownTargetTimestamp += 100;\n            }\n            // ?: Have we now hit or overshot the target?\n            if (currentCountdownTargetTimestamp >= targetTimeoutTimestamp) {\n                // -> Yes, we've hit target, so this did not work out - abort attempt, bump state vars, and reschedule the entire show.\n                w_connectTimeout_AbortAttemptAndReschedule();\n            } else {\n                // -> No, we've NOT hit timeout-target, so sleep till next countdown-target, where we re-invoke ourselves (this w_countDownTimer())\n                // Notify ConnectionEvent listeners about this COUNTDOWN event.\n                _updateStateAndNotifyConnectionEventListeners(new ConnectionEvent(ConnectionEventType.COUNTDOWN, _currentWebSocketUrl, undefined, _secondsTenths(timeout), secondsLeft(), _connectionAttempt));\n                let sleep = Math.max(5, currentCountdownTargetTimestamp - Date.now());\n                countdownId = setTimeout(function () {\n                    w_countDownTimer();\n                }, sleep);\n            }\n        }\n\n        function w_abortAttempt() {\n            // ?: Are we in progress with the PreConnectionOperation?\n            if (preConnectOperationAbortFunction) {\n                // -> Evidently still doing PreConnectionRequest - kill it.\n                log(\"  \\\\- Within PreConnectionOperation phase - invoking preConnectOperationFunction's abort() function.\");\n                // null out the 'preConnectOperationAbortFunction', as this is used for indication for whether the preConnectRequestPromise's resolve&reject should act.\n                let abortFunctionTemp = preConnectOperationAbortFunction;\n                // Clear out\n                preConnectOperationAbortFunction = undefined;\n                // Invoke the abort.\n                abortFunctionTemp();\n            }\n\n            // ?: Are we in progress with opening WebSocket?\n            if (websocketAttempt) {\n                // -> Evidently still trying to connect WebSocket - kill it.\n                log(\"  \\\\- Within WebSocket connect phase - clearing handlers and invoking webSocket.close().\");\n                websocketAttempt.onopen = undefined;\n                websocketAttempt.onerror = function (closeEvent) {\n                    if (that.logging) log(\"!! websocketAttempt.onerror: Forced close by timeout, instanceId:[\" + closeEvent.target.webSocketInstanceId + \"]\", closeEvent);\n                };\n                websocketAttempt.onclose = function (closeEvent) {\n                    if (that.logging) log(\"!! websocketAttempt.onclose: Forced close by timeout, instanceId:[\" + closeEvent.target.webSocketInstanceId + \"]\", closeEvent);\n                };\n                // Close the current WebSocket connection attempt (i.e. abort connect if still trying).\n                websocketAttempt.close(MatsSocketCloseCodes.CLOSE_SESSION, \"WebSocket connect aborted\");\n                // Clear out the attempt\n                websocketAttempt = undefined;\n            }\n        }\n\n        function w_defaultXhrPromiseFactory(params) {\n            let xhr = new XMLHttpRequest();\n            let abort = function () {\n                xhr.abort();\n            };\n            let preConnectPromise = new Promise(function (resolve, reject) {\n                    xhr.addEventListener(\"loadend\", function (event) {\n                        // Get XHR's status\n                        let status = xhr.status;\n                        // ?: Was it a GOOD return?\n                        if ((status === 200) || (status === 202) || (status === 204)) {\n                            // -> Yes, it was good - supplying the status code\n                            resolve(status);\n                        } else {\n                            // -> Not, it was BAD - supplying the status code\n                            reject(status);\n                        }\n                    });\n                    xhr.withCredentials = true;\n                    // Note: 'params.directUrl' is only relevant for this internal code, look at the usage of this method.\n                    xhr.open(\"GET\", (params.directUrl ? params.directUrl : params.webSocketUrl.replace(\"ws\", \"http\")));\n                    xhr.setRequestHeader(\"Authorization\", params.authorization);\n                    xhr.send();\n                }\n            );\n            return [abort, preConnectPromise];\n        }\n\n        function w_attemptPreConnectionOperation() {\n            // :: Decide based on type of 'preconnectoperation' how to do the .. PreConnectOperation..!\n            let abortAndPromise;\n            let params = {\n                webSocketUrl: _currentWebSocketUrl,\n                authorization: _authorization\n            };\n            if (typeof (that.preconnectoperation) === 'function') {\n                // -> Function, so invoke it, with contract-specified params object containing 'webSocketUrl'.\n                abortAndPromise = that.preconnectoperation(params);\n            } else if (typeof (that.preconnectoperation) === 'string') {\n                // -> String, so invoke our default promise factory with special 'directUrl' property, which overrides 'webSocketUrl'.\n                params.directUrl = that.preconnectoperation;\n                abortAndPromise = w_defaultXhrPromiseFactory(params);\n            } else {\n                // -> Truthy, so invoke our default promise factory with params object containing 'webSocketUrl'.\n                abortAndPromise = w_defaultXhrPromiseFactory(params);\n            }\n\n            // Deconstruct the return\n            preConnectOperationAbortFunction = abortAndPromise[0];\n            let preConnectRequestPromise = abortAndPromise[1];\n\n            // Handle the resolve or reject from the preConnectionOperation\n            preConnectRequestPromise\n                .then(function (statusMessage) {\n                    // -> Yes, good return - so go onto next phase, which is creating the WebSocket\n                    // ?: Are we still trying to perform the preConnectOperation? (not timed out)\n                    if (preConnectOperationAbortFunction) {\n                        // -> Yes, not timed out, so then we're good to go with the next phase\n                        log(\"PreConnectionOperation went OK [\" + statusMessage + \"], going on to create WebSocket.\");\n                        // Create the WebSocket\n                        w_attemptWebSocket();\n                    }\n                })\n                .catch(function (statusMessage) {\n                    // -> No, bad return - so go for next\n                    // ?: Are we still trying to perform the preConnectOperation? (not timed out)\n                    if (preConnectOperationAbortFunction) {\n                        // -> Yes, not timed out, so then we'll notify about our failed attempt\n                        log(\"PreConnectionOperation failed [\" + statusMessage + \"] - retrying.\");\n                        // Go for next retry\n                        w_connectFailed_RetryOrWaitForTimeout();\n                    }\n                });\n        }\n\n        function w_attemptWebSocket() {\n            // We're not trying to perform the preConnectOperation anymore, so clear it.\n            preConnectOperationAbortFunction = undefined;\n            // ?: Assert that we're not already trying to make a WebSocket\n            if (websocketAttempt) {\n                throw Error(\"When going for attempt on creating WebSocket, there was already an attempt in place.\");\n            }\n\n            // ?: Assert that we're still open\n            if (!_matsSocketOpen) {\n                log(\"Upon WebSocket.open, we realize that this MatsSocket instance is closed! - stopping right here.\");\n                w_abortAttempt();\n                return;\n            }\n\n            // :: Actually create the WebSocket instance\n            let url = _currentWebSocketUrl + (that.preconnectoperation ? \"?preconnect=true\" : \"\");\n            const webSocketInstanceId = that.randomId(6);\n            if (that.logging) log(\"INSTANTIATING new WebSocket(\\\"\" + url + \"\\\", \\\"matssocket\\\") - InstanceId:[\" + webSocketInstanceId + \"]\");\n            websocketAttempt = webSocketFactory(url, \"matssocket\");\n            websocketAttempt.webSocketInstanceId = webSocketInstanceId;\n\n            // :: Add the handlers for this \"trying to acquire\" procedure.\n\n            // Note: On failure, some environments will call error, then close, and some just error. We set up so that we\n            // handle any order, and both, by removing handlers after getting the first.\n\n            // Error: Log, updateState/notifyListeners, and start retry/wait.\n            websocketAttempt.onerror = function (errorEvent) {\n                if (that.logging) log(\"Create WebSocket: error. InstanceId:[\" + errorEvent.target.webSocketInstanceId + \"]\", errorEvent);\n                // Some environments will call onClose afterward, and some not. We'll remove all handlers to be sure.\n                websocketAttempt.onerror = undefined;\n                websocketAttempt.onclose = undefined;\n                websocketAttempt.onopen = undefined;\n                _updateStateAndNotifyConnectionEventListeners(new ConnectionEvent(ConnectionEventType.WAITING, _currentWebSocketUrl, errorEvent, _secondsTenths(timeout), secondsLeft(), _connectionAttempt));\n                w_connectFailed_RetryOrWaitForTimeout();\n            };\n\n            // Close: .. same as Error\n            websocketAttempt.onclose = function (closeEvent) {\n                if (that.logging) log(\"Create WebSocket: close. InstanceId:[\" + closeEvent.target.webSocketInstanceId + \"], Code:\" + closeEvent.code + \", Reason:\" + closeEvent.reason, closeEvent);\n                // We'll remove all handlers now, since any other event would be bad at this time.\n                websocketAttempt.onerror = undefined;\n                websocketAttempt.onclose = undefined;\n                websocketAttempt.onopen = undefined;\n                _updateStateAndNotifyConnectionEventListeners(new ConnectionEvent(ConnectionEventType.WAITING, _currentWebSocketUrl, closeEvent, _secondsTenths(timeout), secondsLeft(), _connectionAttempt));\n                w_connectFailed_RetryOrWaitForTimeout();\n            };\n\n            // Open: Success! Cancel countdown timer, and set WebSocket in MatsSocket, clear flags, set proper WebSocket event handlers including onMessage.\n            websocketAttempt.onopen = function (openEvent) {\n                // First and foremost: Cancel the \"connection timeout\" thingy - we're done!\n                clearTimeout(countdownId);\n\n                // ?: Assert that we're still open\n                if (!_matsSocketOpen) {\n                    log(\"Upon WebSocket.open, we realize that this MatsSocket instance is closed! - stopping right here.\");\n                    w_abortAttempt();\n                    return;\n                }\n\n                log(\"Create WebSocket: opened! InstanceId:[\" + openEvent.target.webSocketInstanceId + \"].\", openEvent);\n\n                // Store our brand new, soon-ready-for-business WebSocket.\n                _webSocket = websocketAttempt;\n                // We're not /trying/ to connect anymore.. (Because, *hell yeah!*, we /have/ connected!!)\n                _webSocketConnecting = false;\n                // Since we've just established this WebSocket, we have obviously not sent HELLO yet.\n                _helloSent = false;\n\n                // Set our proper handlers\n                _webSocket.onopen = undefined; // No need for 'onopen', it is already open. Also, node.js evidently immediately fires it again, even though it was already fired.\n                _webSocket.onerror = _onerror;\n                _webSocket.onclose = _onclose;\n                _webSocket.onmessage = _onmessage;\n\n                _registerBeforeunload();\n\n                _updateStateAndNotifyConnectionEventListeners(new ConnectionEvent(ConnectionEventType.CONNECTED, _currentWebSocketUrl, openEvent, _secondsTenths(timeout), secondsLeft(), _connectionAttempt));\n\n                // Fire off any waiting messages, next tick\n                _invokeLater(function () {\n                    log(\"WebSocket is open! Running evaluatePipelineSend() to start HELLO/WELCOME handshake.\");\n                    _evaluatePipelineSend();\n                });\n            };\n        }\n\n        function w_connectFailed_RetryOrWaitForTimeout() {\n            // :: Attempt failed, either immediate retry or wait for timeout\n            log(\"Create WebSocket: Attempt failed, URL [\" + _currentWebSocketUrl + \"] didn't work out.\");\n            // ?: Assert that we're still open\n            if (!_matsSocketOpen) {\n                log(\"After failed attempt, we realize that this MatsSocket instance is closed! - stopping right here.\");\n                // Abort connecting\n                w_abortAttempt();\n                // Cancel the \"reconnect scheduler\" thingy.\n                clearTimeout(countdownId);\n                return;\n            }\n            // ?: Have we had WAY too many connection attempts?\n            if (_connectionAttempt >= _maxConnectionAttempts()) {\n                // -> Yes, too much fails or errors - stop nagging server.\n                let reason = \"Trying to create WebSocket: Too many consecutive connection attempts [\" + _connectionAttempt + \"]\";\n                error(\"too many connection attempts\", reason);\n                // Hold on to how many outstanding initiations there are now\n                let outstandingInitiations = Object.keys(_outboxInitiations).length;\n                // Abort connecting\n                w_abortAttempt();\n                // Cancel the \"reconnect scheduler\" thingy.\n                clearTimeout(countdownId);\n                // Close Session\n                _closeSessionAndClearStateAndPipelineAndFuturesAndOutstandingMessages();\n                // Notify SessionClosedEventListeners - with a fake CloseEvent\n                _notifySessionClosedEventListeners({\n                    type: \"close\",\n                    code: MatsSocketCloseCodes.VIOLATED_POLICY,\n                    codeName: \"VIOLATED_POLICY\",\n                    reason: reason,\n                    outstandingInitiations: outstandingInitiations\n                });\n                return;\n            }\n            // Clear out the attempt instances\n            preConnectOperationAbortFunction = undefined;\n            websocketAttempt = undefined;\n            // ?: If we are on the FIRST (0th) round of trying out the different URLs, then immediately try the next\n            // .. But only if there are multiple URLs configured.\n            if ((_connectionAttemptRound === 0) && (_useUrls.length > 1)) {\n                // -> YES, we are on the 0th round of connection attempts, and there are multiple URLs, so immediately try the next.\n                // Cancel the \"reconnect scheduler\" thingy.\n                clearTimeout(countdownId);\n\n                // Invoke on next tick: Bump state vars, re-run _initiateWebSocketCreation\n                _invokeLater(function () {\n                    _increaseReconnectStateVars();\n                    _initiateWebSocketCreation();\n                });\n            }\n            // E-> NO, we are either not on the 0th round of attempts, OR there is just a single URL.\n            // Therefore, let the countdown timer do its stuff.\n        }\n\n        function w_connectTimeout_AbortAttemptAndReschedule() {\n            // :: Attempt timed out, clear this WebSocket out\n            log(\"Create WebSocket: Attempt timeout exceeded [\" + timeout + \" ms], URL [\" + _currentWebSocketUrl + \"] didn't work out.\");\n            // Abort the attempt.\n            w_abortAttempt();\n            // ?: Assert that we're still open\n            if (!_matsSocketOpen) {\n                log(\"After timed out attempt, we realize that this MatsSocket instance is closed! - stopping right here.\");\n                return;\n            }\n            // Invoke after a small random number of millis: Bump reconnect state vars, re-run _initiateWebSocketCreation\n            setTimeout(function () {\n                _increaseReconnectStateVars();\n                _initiateWebSocketCreation();\n            }, Math.round(Math.random() * 200));\n        }\n\n        // Start the countdown-timer.\n        // NOTICE! Order here is important - as the 'w_attemptPreConnectionOperation' right below may cancel it!\n        w_countDownTimer();\n\n        // :: Start the actual connection attempt!\n        // ?: Should we do a PreConnectionOperation?\n        if (that.preconnectoperation) {\n            // -> function, string URL or 'true': Attempt tp perform the PreConnectionOperation - which upon success goes on to invoke 'w_attemptWebSocket()'.\n            w_attemptPreConnectionOperation();\n        } else {\n            // -> Falsy: No PreConnectionOperation, attempt to create the WebSocket directly.\n            w_attemptWebSocket();\n        }\n    }\n\n    function _onerror(errorEvent) {\n        error(\"websocket.onerror\", \"Got 'onerror' event from WebSocket, instanceId:[\" + errorEvent.target.webSocketInstanceId + \"].\", errorEvent);\n        // :: Synchronously notify our ConnectionEvent listeners.\n        _updateStateAndNotifyConnectionEventListeners(new ConnectionEvent(ConnectionEventType.CONNECTION_ERROR, _currentWebSocketUrl, errorEvent, undefined, undefined, _connectionAttempt));\n    }\n\n    function _onclose(closeEvent) {\n        log(\"websocket.onclose, instanceId:[\" + closeEvent.target.webSocketInstanceId + \"]\", closeEvent);\n\n        // Note: Here (as opposed to matsSocket.close()) the WebSocket is already closed, so we don't have to close it..!\n\n        // ?: Special codes, that signifies that we should close (terminate) the MatsSocketSession.\n        if ((closeEvent.code === MatsSocketCloseCodes.UNEXPECTED_CONDITION)\n            || (closeEvent.code === MatsSocketCloseCodes.MATS_SOCKET_PROTOCOL_ERROR)\n            || (closeEvent.code === MatsSocketCloseCodes.VIOLATED_POLICY)\n            || (closeEvent.code === MatsSocketCloseCodes.CLOSE_SESSION)\n            || (closeEvent.code === MatsSocketCloseCodes.SESSION_LOST)) {\n            // -> One of the specific \"Session is closed\" CloseCodes -> Reject all outstanding, this MatsSocket is trashed.\n            error(\"session closed from server\", \"The WebSocket was closed with a CloseCode [\" + MatsSocketCloseCodesUtil.nameFor(closeEvent.code) + \"] signifying that our MatsSocketSession is closed, reason:[\" + closeEvent.reason + \"].\", closeEvent);\n\n            // Hold on to how many outstanding initiations there are now\n            let outstandingInitiations = Object.keys(_outboxInitiations).length;\n\n            // Close Session, Clear all state.\n            _closeSessionAndClearStateAndPipelineAndFuturesAndOutstandingMessages();\n\n            // :: Synchronously notify our SessionClosedEvent listeners\n            // NOTE: This shall only happen if Close Session is from ServerSide (that is, here), otherwise, if the app invoked matsSocket.close(), one would think the app knew about the close itself..!\n            closeEvent.codeName = MatsSocketCloseCodesUtil.nameFor(closeEvent.code);\n            closeEvent.outstandingInitiations = outstandingInitiations;\n            _notifySessionClosedEventListeners(closeEvent);\n\n        } else {\n            // -> NOT one of the specific \"Session is closed\" CloseCodes -> Reconnect and Reissue all outstanding..\n            if (closeEvent.code !== MatsSocketCloseCodes.DISCONNECT) {\n                log(\"We were closed with a CloseCode [\" + MatsSocketCloseCodesUtil.nameFor(closeEvent.code) + \"] that does NOT denote that we should close the session. Initiate reconnect and reissue all outstanding.\");\n            } else {\n                log(\"We were closed with the special DISCONNECT close code - act as we lost connection, but do NOT start to reconnect.\");\n            }\n\n            // Clear out WebSocket \"infrastructure\", i.e. state and \"pinger thread\".\n            _clearWebSocketStateAndInfrastructure();\n\n            // :: This is a reconnect - so we should do pipeline processing right away, to get the HELLO over.\n            _forcePipelineProcessing = true;\n\n            // :: Synchronously notify our ConnectionEvent listeners.\n            _updateStateAndNotifyConnectionEventListeners(new ConnectionEvent(ConnectionEventType.LOST_CONNECTION, _currentWebSocketUrl, closeEvent, undefined, undefined, _connectionAttempt));\n\n            // ?: Is this the special DISCONNECT that asks us to NOT start reconnecting?\n            if (closeEvent.code !== MatsSocketCloseCodes.DISCONNECT) {\n                // -> No, not special DISCONNECT - so start reconnecting.\n                // :: Start reconnecting, but give the server a little time to settle, and a tad randomness to handle any reconnect floods.\n                setTimeout(function () {\n                    // ?: Have we already gotten a new WebSocket, or started the process of creating one (due to a new\n                    // message having been sent in the meantime, having started the WebSocket creation process)?\n                    if ((_webSocket !== undefined) || _webSocketConnecting) {\n                        // -> Yes, so we should not start again (the _initiateWebSocketCreation asserts these states)\n                        log(\"Start reconnect after LOST_CONNECTION: Already gotten WebSocket, or started creation process. Bail out.\");\n                        return;\n                    }\n                    // ?: Has the MatsSocket been closed in the meantime?\n                    if (!_matsSocketOpen) {\n                        // -> We've been asynchronously closed - bail out from creating WebSocket  (the _initiateWebSocketCreation asserts this state)\n                        log(\"Start reconnect after LOST_CONNECTION: MatsSocket is closed. Bail out.\");\n                        return;\n                    }\n                    // E-> We should start creation process.\n                    _initiateWebSocketCreation();\n                }, 250 + Math.random() * 750);\n            }\n        }\n    }\n\n    function _onmessage(messageEvent) {\n        let receivedTimestamp = Date.now();\n        let data = messageEvent.data;\n        let envelopes = JSON.parse(data);\n\n        let numEnvelopes = envelopes.length;\n        if (that.logging) log(\"websocket.onmessage, instanceId:[\" + messageEvent.target.webSocketInstanceId + \"]: Got \" + numEnvelopes + \" messages.\");\n\n        for (let i = 0; i < numEnvelopes; i++) {\n            let envelope = envelopes[i];\n            try {\n                if (that.logging) log(\" \\\\- onmessage: handling message \" + i + \": \" + envelope.t + \", envelope:\" + JSON.stringify(envelope));\n\n                if (envelope.t === MessageType.WELCOME) {\n                    // Fetch our assigned MatsSocketSessionId\n                    that.sessionId = envelope.sid;\n                    if (that.logging) log(\"We're WELCOME! SessionId:\" + that.sessionId + \", there are [\" + Object.keys(_outboxInitiations).length + \"] outstanding sends-or-requests, and [\" + Object.keys(_outboxReplies).length + \"] outstanding replies.\");\n                    // If this is the very first time we get SESSION_ESTABLISHED, then record time (can happen again due to reconnects)\n                    if (!_initialSessionEstablished_PerformanceNow) {\n                        _initialSessionEstablished_PerformanceNow = performance.now();\n                    }\n                    // :: Synchronously notify our ConnectionEvent listeners.\n                    _updateStateAndNotifyConnectionEventListeners(new ConnectionEvent(ConnectionEventType.SESSION_ESTABLISHED, _currentWebSocketUrl, undefined, undefined, undefined, _connectionAttempt));\n                    // Start pinger (AFTER having set ConnectionState to SESSION_ESTABLISHED, otherwise it'll exit!)\n                    _startPinger();\n\n                    // TODO: Test this outstanding-stuff! Both that they are actually sent again, and that server handles the (quite possible) double-delivery.\n\n                    // ::: RETRANSMIT: If we have stuff in our outboxes, we might have to send them again (we send unless \"RetransmitGuard\" tells otherwise).\n\n                    // :: Outstanding SENDs and REQUESTs\n                    for (let key in _outboxInitiations) {\n                        let initiation = _outboxInitiations[key];\n                        let initiationEnvelope = initiation.envelope;\n                        // ?: Is the RetransmitGuard the same as we currently have?\n                        if (initiation.retransmitGuard === _outboxInitiations_RetransmitGuard) {\n                            // -> Yes, so it makes little sense in sending these messages again just yet.\n                            if (that.logging) log(\"RetransmitGuard: The outstanding Initiation [\" + initiationEnvelope.t + \"] with cmid:[\" + initiationEnvelope.cmid + \"] and TraceId:[\" + initiationEnvelope.tid\n                                + \"] was created with the same RetransmitGuard as we currently have [\" + _outboxInitiations_RetransmitGuard + \"] - they were sent directly trailing HELLO, before WELCOME came back in. No use in sending again.\");\n                            continue;\n                        }\n                        initiation.attempt++;\n                        if (initiation.attempt > 10) {\n                            error(\"toomanyretries\", \"Upon reconnect: Too many attempts at sending Initiation [\" + initiationEnvelope.t + \"] with cmid:[\" + initiationEnvelope.cmid + \"], TraceId[\" + initiationEnvelope.tid + \"], size:[\" + JSON.stringify(initiationEnvelope).length + \"].\", initiationEnvelope);\n                            continue;\n                        }\n                        // NOTICE: Won't delete it here - that is done when we process the ACK from server\n                        _addEnvelopeToPipeline_EvaluatePipelineLater(initiationEnvelope);\n                        // Flush for each message, in case the size of the message was of issue why we closed (maybe pipeline was too big).\n                        that.flush();\n                    }\n\n                    // :: Outstanding Replies\n                    // NOTICE: Since we cannot possibly have replied to a Server Request BEFORE we get the WELCOME, we do not need RetransmitGuard for Replies\n                    // (Point is that the RetransmitGuard guards against sending again messages that we sent \"along with\" the HELLO, before we got the WELCOME.\n                    // A Request from the Server cannot possibly come in before WELCOME (as that is by protcol definition the first message we get from the Server),\n                    // so there will \"axiomatically\" not be any outstanding Replies with the same RetransmitGuard as we currently have: Therefore, /all should be retransmitted/).\n                    for (let key in _outboxReplies) {\n                        let reply = _outboxReplies[key];\n                        let replyEnvelope = reply.envelope;\n                        reply.attempt++;\n                        if (reply.attempt > 10) {\n                            error(\"toomanyretries\", \"Upon reconnect: Too many attempts at sending Reply [\" + replyEnvelope.t + \"] with smid:[\" + replyEnvelope.smid + \"], TraceId[\" + replyEnvelope.tid + \"], size:[\" + JSON.stringify(replyEnvelope).length + \"].\", replyEnvelope);\n                            continue;\n                        }\n                        // NOTICE: Won't delete it here - that is done when we process the ACK from server\n                        _addEnvelopeToPipeline_EvaluatePipelineLater(replyEnvelope);\n                        // Flush for each message, in case the size of the message was of issue why we closed (maybe pipeline was too big).\n                        that.flush();\n                    }\n\n                } else if (envelope.t === MessageType.REAUTH) {\n                    // -> Server asks us to get new Authentication, as the one he has \"on hand\" is too old to send us outgoing messages\n                    _requestNewAuthorizationFromApp(\"Server demands new Authorization\", new AuthorizationRequiredEvent(AuthorizationRequiredEventType.REAUTHENTICATE, undefined));\n\n\n                } else if (envelope.t === MessageType.RETRY) {\n                    // -> Server asks us to RETRY the information-bearing-message\n\n                    // TODO: Test RETRY!\n\n                    // ?: Is it an outstanding Send or Request\n                    let initiation = _outboxInitiations[envelope.cmid];\n                    if (initiation) {\n                        let initiationEnvelope = initiation.envelope;\n                        initiation.attempt++;\n                        if (initiation.attempt > 10) {\n                            error(\"toomanyretries\", \"Upon RETRY-request: Too many attempts at sending [\" + initiationEnvelope.t + \"] with cmid:[\" + initiationEnvelope.cmid + \"], TraceId[\" + initiationEnvelope.tid + \"], size:[\" + JSON.stringify(initiationEnvelope).length + \"].\", initiationEnvelope);\n                            continue;\n                        }\n                        // Note: the retry-cycles will start at attempt=2, since we initialize it with 1, and have already increased it by now.\n                        let retryDelay = Math.pow(2, (initiation.attempt - 2)) * 500 + Math.round(Math.random() * 1000);\n                        setTimeout(function () {\n                            _addEnvelopeToPipeline_EvaluatePipelineLater(initiationEnvelope);\n                        }, retryDelay);\n                        continue;\n                    }\n                    // E-> Was not outstanding Send or Request\n\n                    // ?: Is it an outstanding Reply, i.e. Resolve or Reject?\n                    let reply = _outboxReplies[envelope.cmid];\n                    if (reply) {\n                        let replyEnvelope = reply.envelope;\n                        reply.attempt++;\n                        if (reply.attempt > 10) {\n                            error(\"toomanyretries\", \"Upon RETRY-request: Too many attempts at sending [\" + replyEnvelope.t + \"] with smid:[\" + replyEnvelope.smid + \"], TraceId[\" + replyEnvelope.tid + \"], size:[\" + JSON.stringify(replyEnvelope).length + \"].\", replyEnvelope);\n                            continue;\n                        }\n                        // Note: the retry-cycles will start at attempt=2, since we initialize it with 1, and have already increased it by now.\n                        let retryDelay = Math.pow(2, (initiation.attempt - 2)) * 500 + Math.round(Math.random() * 1000);\n                        setTimeout(function () {\n                            _addEnvelopeToPipeline_EvaluatePipelineLater(replyEnvelope);\n                        }, retryDelay);\n                    }\n                } else if ((envelope.t === MessageType.ACK) || (envelope.t === MessageType.NACK)) {\n                    // -> Server Acknowledges information-bearing message from Client.\n\n                    if ((envelope.cmid === undefined) && (envelope.ids === undefined)) {\n                        // -> No, we do not have this. Programming error from Server.\n                        error(\"ack missing ids\", \"The ACK/NACK envelope is missing 'cmid' or 'ids'.\", envelope);\n                        continue;\n                    }\n\n                    let ids = [];\n                    if (envelope.cmid !== undefined) ids.push(envelope.cmid);\n                    if (envelope.ids !== undefined) ids = ids.concat(envelope.ids);\n\n                    _sendAck2Later(ids);\n\n                    // :: Handling if this was an ACK for outstanding SEND or REQUEST\n                    for (let i = 0; i < ids.length; i++) {\n                        let cmid = ids[i];\n                        let initiation = _outboxInitiations[cmid];\n                        // ?: Check that we found it.\n                        if (initiation === undefined) {\n                            // -> No, NOT initiation. Assume it was a for a Reply (RESOLVE or REJECT), delete the outbox entry.\n                            delete _outboxReplies[cmid];\n                            continue;\n                        }\n                        // E-> ----- Yes, we had an outstanding Initiation (SEND or REQUEST).\n\n                        initiation.messageAcked_PerformanceNow = performance.now();\n\n                        // Fetch Request, if any.\n\n                        let receivedEventType = (envelope.t === MessageType.ACK ? ReceivedEventType.ACK : ReceivedEventType.NACK);\n                        _completeReceived(receivedEventType, initiation, receivedTimestamp, envelope.desc);\n\n                        let request = _outstandingRequests[cmid];\n                        // ?: If this was a REQUEST, and it is a !ACK - it will never get a Reply..\n                        if (request && (envelope.t !== MessageType.ACK)) {\n                            // -> Yes, this was a REQUEST that got an !ACK\n                            // We have to reject the REQUEST too - it was never processed, and will thus never get a Reply\n                            // (Note: This is either a reject for a Promise, or errorCallback on Endpoint).\n                            _completeRequest(request, MessageEventType.REJECT, {}, receivedTimestamp);\n                        }\n                    }\n\n                } else if (envelope.t === MessageType.ACK2) {\n                    // -> ACKNOWLEDGE of the RECEIVED: We can delete from our inbox\n                    if ((envelope.smid === undefined) && (envelope.ids === undefined)) {\n                        // -> No, we do not have this. Programming error from Server.\n                        error(\"ack2 missing ids\", \"The ACK2 envelope is missing 'smid' or 'ids'\", envelope);\n                        continue;\n                    }\n                    // Delete it from inbox - that is what ACK2 means: Other side has now deleted it from outbox,\n                    // and can thus not ever deliver it again (so we can delete the guard against double delivery).\n                    if (envelope.smid) {\n                        delete _inbox[envelope.smid];\n                    }\n                    if (envelope.ids) {\n                        for (let i = 0; i < envelope.ids.length; i++) {\n                            delete _inbox[envelope.ids[i]];\n                        }\n                    }\n\n                } else if ((envelope.t === MessageType.SEND) || (envelope.t === MessageType.REQUEST)) {\n                    // -> SEND or REQUEST: Sever-to-Client Send or Request to client terminatorOrEndpoint\n\n                    let termOrEndp = envelope.t === MessageType.SEND ? \"Terminator\" : \"Endpoint\";\n\n                    if (envelope.smid === undefined) {\n                        // -> No, we do not have this. Programming error from Server.\n                        error(envelope.t.toLowerCase() + \" missing smid\", \"The \" + envelope.t + \" envelope is missing 'smid'\", envelope);\n                        continue;\n                    }\n\n                    // Find the (client) Terminator/Endpoint which the Send should go to\n                    let terminatorOrEndpoint = (envelope.t === MessageType.SEND ? _terminators[envelope.eid] : _endpoints[envelope.eid]);\n\n                    // :: Send receipt unconditionally\n                    _sendAckLater((terminatorOrEndpoint ? MessageType.ACK : MessageType.NACK), envelope.smid, terminatorOrEndpoint ? undefined : \"The Client \" + termOrEndp + \" [\" + envelope.eid + \"] does not exist!\");\n\n\n                    // ?: Do we have the desired Terminator?\n                    if (terminatorOrEndpoint === undefined) {\n                        // -> No, we do not have this. Programming error from app.\n                        error(\"client \" + termOrEndp.toLowerCase() + \" does not exist\", \"The Client \" + termOrEndp + \" [\" + envelope.eid + \"] does not exist!!\", envelope);\n                        continue;\n                    }\n                    // E-> We found the Terminator to tell\n\n                    // ?: Have we already gotten this message? (Double delivery)\n                    if (_inbox[envelope.smid]) {\n                        // -> Yes, so this was a double delivery. Drop processing, we've already done it.\n                        if (that.logging) log(\"Caught double delivery of \" + envelope.t + \" with smid:[\" + envelope.smid + \"], sending ACK, but won't process again.\", envelope);\n                        continue;\n                    }\n\n                    // Add the message to inbox\n                    _inbox[envelope.smid] = envelope;\n\n                    // :: Handle the SEND or REQUEST\n\n                    // ?: Is this a SEND?\n                    if (envelope.t === MessageType.SEND) {\n                        // Yes, SEND, so invoke the Terminator\n                        let messageEvent = _createMessageEventForIncoming(envelope, receivedTimestamp);\n                        terminatorOrEndpoint.resolve(messageEvent);\n                    } else {\n                        // No, this is REQUEST - so invoke the Endpoint to get a Promise, and send its settling using RESOLVE or REJECT.\n                        // :: Create a Resolve and Reject handler\n                        let fulfilled = function (resolveReject, msg) {\n                            // Update timestamp of last \"information bearing message\" sent.\n                            _lastMessageEnqueuedTimestamp = Date.now();\n                            // Create the Reply message\n                            let replyEnvelope = {\n                                t: resolveReject,\n                                smid: envelope.smid,\n                                tid: envelope.tid,\n                                msg: msg\n                            };\n                            // Add the message Sequence Id\n                            replyEnvelope.cmid = _messageSequenceId++;\n                            // Add it to outbox\n                            _outboxReplies[replyEnvelope.cmid] = {\n                                attempt: 1,\n                                envelope: replyEnvelope\n                            };\n                            // Send it down the wire\n                            _addEnvelopeToPipeline_EvaluatePipelineLater(replyEnvelope);\n                        };\n\n                        // :: Invoke the Endpoint, getting a Promise back.\n                        let messageEvent = _createMessageEventForIncoming(envelope, receivedTimestamp);\n                        let promise = terminatorOrEndpoint(messageEvent);\n\n                        // :: Finally attach the Resolve and Reject handler\n                        promise.then(function (resolveMessage) {\n                            fulfilled(MessageType.RESOLVE, resolveMessage);\n                        }).catch(function (rejectMessage) {\n                            fulfilled(MessageType.REJECT, rejectMessage);\n                        });\n                    }\n\n                } else if ((envelope.t === MessageType.RESOLVE) || (envelope.t === MessageType.REJECT)) {\n                    // -> Reply to REQUEST\n                    // ?: Do server want receipt, indicated by the message having 'smid' property?\n                    // (NOTE: Reply (RESOLVE/REJECT) directly in IncomingHandler will not set 'smid', as the message has never been in the outbox, so won't need deletion).\n                    if (envelope.smid) {\n                        // -> Yes, so send ACK to server\n                        _sendAckLater(MessageType.ACK, envelope.smid);\n                    }\n                    // It is physically possible that the Reply comes before the ACK (I've observed it!).\n                    // .. Such a situation could potentially be annoying for the using application (Reply before Ack)..\n                    // ALSO, for Replies that are produced in the incomingHandler, there will be no separate ACK message - this is a combined ACK+Reply.\n                    // Handle this by checking whether the initiation is still in place, and handle it as \"ACK Received\" if so.\n                    let initiation = _outboxInitiations[envelope.cmid];\n                    // ?: Was the initiation still present?\n                    if (initiation) {\n                        // -> Yes, still present - this means that this is effectively a /combined/ ACK+Reply, so must also handle the ACK-part.\n                        // Send ACK2 for the \"ACK-part\" of this Reply (the Client-to-Server REQUEST was stored in Server's inbox - he may now delete it).\n                        _sendAck2Later(envelope.cmid);\n                        // Complete any Received-callbacks for the \"ACK-part\" of this Reply.\n                        _completeReceived(ReceivedEventType.ACK, initiation, receivedTimestamp);\n                    }\n\n                    let request = _outstandingRequests[envelope.cmid];\n                    if (!request) {\n                        if (that.logging) log(\"Double delivery: Evidently we've already completed the Request for cmid:[\" + envelope.cmid + \"], traiceId: [\" + envelope.tid + \"], ignoring.\");\n                        continue;\n                    }\n\n                    // Ensure that the timeout is killed now. NOTICE: MUST do this here, since we might delay the delivery even more, check crazy stuff below.\n                    clearTimeout(request.timeoutId);\n\n                    // Complete the Promise on a REQUEST-with-Promise, or messageCallback/errorCallback on Endpoint for REQUEST-with-ReplyTo\n                    let messageEventType = (envelope.t === MessageType.RESOLVE ? MessageEventType.RESOLVE : MessageEventType.REJECT);\n\n                    /*\n                     * NOTICE!! HACK-ALERT! The ordering of events wrt. Requests is as such:\n                     * 1. ReceivedEvent (receivedCallback for requests, and Received-Promise for requestReplyTo)\n                     * 2. InitiationProcessedEvent stored on matsSocket.initiations\n                     * 3. InitiationProcessedEvent listeners\n                     * 4. MessageEvent (Reply-Promise for requests, Terminator callbacks for requestReplyTo)\n                     *\n                     * WITH a requestReplyTo, the ReceivedEvent becomes async in nature, since requestReplyTo returns\n                     * a Promise<ReceivedEvent>. Also, with a requestReplyTo, the completing of the requestReplyTo is\n                     * then done on a Terminator, using its specified callbacks - and this is done using\n                     * setTimeout(.., 0) to \"emulate\" the same async-ness as a Reply-Promise with ordinary requests.\n                     * However, the timing between the ReceivedEvent and InitiationProcessedEvent then becomes\n                     * rather shaky. Therefore, IF the initiation is still in place (ReceivedEvent not yet issued),\n                     * AND this is a requestReplyTo, THEN we delay the completion of the Request (i.e. issue\n                     * InitiationProcessedEvent and MessageEvent) to be more certain that the ReceivedEvent is\n                     * processed before the rest.\n                     */\n                    // ?: Did we still have the initiation in place, AND this is a requestReplyTo?\n                    if (initiation && request.replyToTerminatorId) {\n                        // -> Yes, the initiation was still in place (i.e. ReceivedEvent not issued), and this was\n                        // a requestReplyTo:\n                        // Therefore we delay the entire completion of the request (InitiationProcessedEvent and\n                        // MessageEvent), to be sure that they happen AFTER the ReceivedEvent issued above.\n                        setTimeout(function () {\n                            _completeRequest(request, messageEventType, envelope, receivedTimestamp);\n                        }, 20);\n                    } else {\n                        // -> No, either the initiation was already gone (ReceivedEvent already issued), OR it was\n                        // not a requestReplyTo:\n                        // Therefore, we run the completion right away (InitiationProcessedEvent is sync, while\n                        // MessageEvent is a Promise settling).\n                        _completeRequest(request, messageEventType, envelope, receivedTimestamp);\n                    }\n\n\n                } else if (envelope.t === MessageType.PING) {\n                    // -> PING request, respond with a PONG\n                    // Add the PONG reply to pipeline\n                    _addEnvelopeToPipeline_EvaluatePipelineLater({\n                        t: MessageType.PONG,\n                        x: envelope.x\n                    });\n                    // Send it immediately, since pings might be used to establish roundtrip timings.\n                    that.flush();\n\n                } else if ((envelope.t === MessageType.SUB_OK) || (envelope.t === MessageType.SUB_LOST) || (envelope.t === MessageType.SUB_NO_AUTH)) {\n                    // -> Result of SUB\n                    // Notify PingPong listeners, synchronously.\n                    let eventType;\n                    if (envelope.t === MessageType.SUB_OK) {\n                        eventType = SubscriptionEventType.OK;\n                    } else if (envelope.t === MessageType.SUB_LOST) {\n                        eventType = SubscriptionEventType.LOST_MESSAGES;\n                    } else {\n                        eventType = SubscriptionEventType.NOT_AUTHORIZED;\n                    }\n                    let event = new SubscriptionEvent(eventType, envelope.eid);\n                    for (let i = 0; i < _subscriptionEventListeners.length; i++) {\n                        try {\n                            _subscriptionEventListeners[i](event);\n                        } catch (err) {\n                            error(\"notify SubscriptionEvent listeners\", \"Caught error when notifying one of the [\" + _subscriptionEventListeners.length + \"] SubscriptionEvent listeners.\", err);\n                        }\n                    }\n\n                } else if (envelope.t === MessageType.PUB) {\n                    // -> Server publishes a Topic message\n                    let event = new MessageEvent(MessageEventType.PUB, envelope.msg, envelope.tid, envelope.smid, receivedTimestamp);\n\n                    let subs = _subscriptions[envelope.eid];\n                    // ?: Did we find any listeners?\n                    if (!subs) {\n                        // -> No. Strange.\n                        error(\"message for unwanted topic\", \"We got a PUB message for Topic [\" + envelope.eid + \"], but we have no subscribers for it.\");\n                        continue;\n                    }\n\n                    // Issue message to all listeners for this Topic.\n                    for (let i = 0; i < subs.listeners.length; i++) {\n                        try {\n                            subs.listeners[i](event);\n                        } catch (err) {\n                            error(\"dispatch topic message\", \"Caught error when notifying one of the [\" + subs.listeners.length + \"] subscription listeners for Topic [\" + envelope.eid + \"].\", err);\n                        }\n                    }\n\n                    // Make note of the latest message id processed for this Topic\n                    subs.lastSmid = envelope.smid;\n\n                } else if (envelope.t === MessageType.PONG) {\n                    // -> Response to a PING\n                    let pingPongHolder = _outstandingPings[envelope.x];\n                    delete _outstandingPings[envelope.x];\n                    // Calculate the round-trip time, using performanceNow stored along with the PingPong instance.\n                    let pingPong = pingPongHolder[1];\n                    let performanceThen = pingPongHolder[0];\n                    pingPong.roundTripMillis = _roundTiming(performance.now() - performanceThen);\n\n                    // Notify PingPong listeners, synchronously.\n                    for (let i = 0; i < _pingPongListeners.length; i++) {\n                        try {\n                            _pingPongListeners[i](pingPong);\n                        } catch (err) {\n                            error(\"notify pingpongevent listeners\", \"Caught error when notifying one of the [\" + _pingPongListeners.length + \"] PingPongEvent listeners.\", err);\n                        }\n                    }\n                }\n            } catch (err) {\n                let stringified = JSON.stringify(envelope);\n                error(\"envelope processing\", \"Got unexpected error while handling incoming envelope of type '\" + envelope.t + \"': \" + (stringified.length > 1024 ? stringified.substring(0, 1021) + \"...\" : stringified), err);\n            }\n        }\n    }\n\n    let _laterAcks = [];\n    let _laterNacks = [];\n    let _laterAckTimeoutId = undefined;\n\n    function _sendAckLater(type, smid, description) {\n        // ?: Do we have description?\n        if (description) {\n            // -> Yes, description - so then we need to send it by itself\n            _addEnvelopeToPipeline_EvaluatePipelineLater({\n                t: type,\n                smid: smid,\n                desc: description\n            });\n            return;\n        }\n        // ?: Was it ACK or NACK?\n        if (type === MessageType.ACK) {\n            _laterAcks.push(smid);\n        } else {\n            _laterNacks.push(smid);\n        }\n        // Send them now or later\n        clearTimeout(_laterAckTimeoutId);\n        if ((_laterAcks.length + _laterNacks.length) > 10) {\n            _sendAcksAndNacksNow();\n        } else {\n            _laterAckTimeoutId = setTimeout(_sendAcksAndNacksNow, 20);\n        }\n    }\n\n    function _sendAcksAndNacksNow() {\n        // ACKs\n        if (_laterAcks.length > 1) {\n            _addEnvelopeToPipeline_EvaluatePipelineLater({\n                t: MessageType.ACK,\n                ids: _laterAcks\n            });\n            _laterAcks = [];\n        } else if (_laterAcks.length === 1) {\n            _addEnvelopeToPipeline_EvaluatePipelineLater({\n                t: MessageType.ACK,\n                smid: _laterAcks[0]\n            });\n            _laterAcks.length = 0;\n        }\n        // NACKs\n        if (_laterNacks.length > 1) {\n            _addEnvelopeToPipeline_EvaluatePipelineLater({\n                t: MessageType.NACK,\n                ids: _laterNacks\n            });\n            _laterNacks = [];\n        } else if (_laterNacks.length === 1) {\n            _addEnvelopeToPipeline_EvaluatePipelineLater({\n                t: MessageType.NACK,\n                smid: _laterNacks[0]\n            });\n            _laterNacks.length = 0;\n        }\n    }\n\n    let _laterAck2s = [];\n    let _laterAck2TimeoutId = undefined;\n\n    function _sendAck2Later(ids) {\n        _laterAck2s = _laterAck2s.concat(ids);\n        // Send them now or later\n        clearTimeout(_laterAck2TimeoutId);\n        if (_laterAck2s.length > 10) {\n            _sendAck2sNow();\n        } else {\n            _laterAck2TimeoutId = setTimeout(_sendAck2sNow, 50);\n        }\n    }\n\n    function _sendAck2sNow() {\n        // ACK2s\n        if (_laterAck2s.length > 1) {\n            _addEnvelopeToPipeline_EvaluatePipelineLater({\n                t: MessageType.ACK2,\n                ids: _laterAck2s\n            });\n            _laterAck2s = [];\n        } else if (_laterAck2s.length === 1) {\n            _addEnvelopeToPipeline_EvaluatePipelineLater({\n                t: MessageType.ACK2,\n                cmid: _laterAck2s[0]\n            });\n            _laterAck2s.length = 0;\n        }\n    }\n\n    function _completeReceived(receivedEventType, initiation, receivedTimestamp, description = undefined) {\n        let performanceNow = performance.now();\n        initiation.messageAcked_PerformanceNow = performanceNow;\n\n        // NOTICE! We do this SYNCHRONOUSLY, to ensure that we come in front of Request Promise settling (specifically, Promise /rejection/ if NACK).\n        delete _outboxInitiations[initiation.envelope.cmid];\n        let receivedEvent = new ReceivedEvent(receivedEventType, initiation.envelope.tid, initiation.sentTimestamp, receivedTimestamp, _roundTiming(performanceNow - initiation.messageSent_PerformanceNow), description);\n        // ?: Was it a ACK (not NACK)?\n        if (receivedEventType === ReceivedEventType.ACK) {\n            // -> Yes, it was \"ACK\" - so Server was happy.\n            if (initiation.ack) {\n                try {\n                    initiation.ack(receivedEvent);\n                } catch (err) {\n                    error(\"received ack\", \"When trying to ACK the initiation with ReceivedEvent [\" + receivedEventType + \"], we got error.\", err);\n                }\n            }\n        } else {\n            // -> No, it was !ACK, so message has not been forwarded to Mats\n            if (initiation.nack) {\n                try {\n                    initiation.nack(receivedEvent);\n                } catch (err) {\n                    error(\"received nack\", \"When trying to NACK the initiation with ReceivedEvent [\" + receivedEventType + \"], we got error.\", err);\n                }\n            }\n        }\n\n        // ?: Should we issue InitiationProcessedEvent? (SEND is finished processed at ACK time, while REQUEST waits for REPLY from server before finished processing)\n        if (initiation.envelope.t === MessageType.SEND) {\n            // -> Yes, we should issue - and to get this in a order where \"Received is always invoked before\n            // InitiationProcessedEvents\", we'll have to delay it, as the Promise settling above is async)\n            setTimeout(function () {\n                _issueInitiationProcessedEvent(initiation);\n            }, 50);\n        }\n    }\n\n    function _createMessageEventForIncoming(envelope, receivedTimestamp) {\n        let messageEvent = new MessageEvent(envelope.t, envelope.msg, envelope.tid, envelope.smid, receivedTimestamp);\n        // ?: Do we have a debug object in the envelope?\n        if (envelope.debug) {\n            // -> Yes, so then created it for MessageEvent too.\n            messageEvent.debug = new DebugInformation(undefined, undefined, envelope, receivedTimestamp);\n        }\n        return messageEvent;\n    }\n\n    function _completeRequest(request, messageEventType, incomingEnvelope, receivedTimestamp) {\n        // We're finishing it now, so it shall not be timed out.\n        clearTimeout(request.timeoutId);\n\n        // Make note of performance.now() at this point in time\n        let performanceNow = performance.now();\n\n        delete _outstandingRequests[request.envelope.cmid];\n\n        // Create the event\n        let event = new MessageEvent(messageEventType, incomingEnvelope.msg, request.envelope.tid, request.envelope.cmid, receivedTimestamp);\n        event.clientRequestTimestamp = request.initiation.sentTimestamp;\n        event.roundTripMillis = performanceNow - request.initiation.messageSent_PerformanceNow;\n        // .. add CorrelationInformation from request if requestReplyTo\n        event.correlationInformation = request.correlationInformation;\n        // Add DebugInformation if relevant\n        if (request.initiation.debug !== 0) {\n            event.debug = new DebugInformation(request.initiation.sentTimestamp, request.initiation.debug, incomingEnvelope, receivedTimestamp);\n        }\n\n        // Invoke InitiationProcessedEvent listeners (Both adding to matsSocket.initiations and firing of listeners is done sync, thus done before settling).\n        _issueInitiationProcessedEvent(request.initiation, request.replyToTerminatorId, event);\n\n        // ?: Is this a RequestReplyTo, as indicated by the request having a replyToEndpoint?\n        if (request.replyToTerminatorId) {\n            // -> Yes, this is a REQUEST-with-ReplyTo\n            // Find the (client) Terminator which the Reply should go to\n            let terminator = _terminators[request.replyToTerminatorId];\n            // \"Emulate\" asyncness as if with Promise settling with setTimeout(.., 0).\n            setTimeout(function () {\n                if (messageEventType === MessageEventType.RESOLVE) {\n                    try {\n                        terminator.resolve(event);\n                    } catch (err) {\n                        error(\"replytoterminator resolve\", \"When trying to pass a RESOLVE to Terminator [\" + request.replyToTerminatorId + \"], an exception was raised.\", err);\n                    }\n                } else {\n                    // ?: Do we actually have a Reject-function (not necessarily, app decides whether to register it)\n                    if (terminator.reject) {\n                        // -> Yes, so reject it.\n                        try {\n                            terminator.reject(event);\n                        } catch (err) {\n                            error(\"replytoterminator reject\", \"When trying to pass a [\" + messageEventType + \"] to Terminator [\" + request.replyToTerminatorId + \"], an exception was raised.\", err);\n                        }\n                    }\n                }\n            }, 0);\n        } else {\n            // -> No, this is a REQUEST-with-Promise (missing (client) EndpointId)\n            // Delete the outstanding request, as we will complete it now.\n            delete _outstandingRequests[request.envelope.cmid];\n            // :: Note, resolving/rejecting a Promise is always async (happens \"next tick\").\n            // ?: Was it RESOLVE or REJECT?\n            if (messageEventType === MessageEventType.RESOLVE) {\n                request.resolve(event);\n            } else {\n                request.reject(event);\n            }\n        }\n    }\n\n    function _roundTiming(millis) {\n        return Math.round(millis * 100) / 100;\n    }\n\n    function _issueInitiationProcessedEvent(initiation, replyToTerminatorId = undefined, replyMessageEvent = undefined) {\n        // Handle when initationProcessed /before/ session established: Setting to 0. (Can realistically only happen in testing.)\n        let sessionEstablishedOffsetMillis = (_initialSessionEstablished_PerformanceNow ? _roundTiming(initiation.messageSent_PerformanceNow - _initialSessionEstablished_PerformanceNow) : 0);\n        let acknowledgeRoundTripTime = _roundTiming(initiation.messageAcked_PerformanceNow - initiation.messageSent_PerformanceNow);\n        let requestRoundTripTime = (replyMessageEvent ? _roundTiming(performance.now() - initiation.messageSent_PerformanceNow) : undefined);\n        let replyMessageEventType = (replyMessageEvent ? replyMessageEvent.type : undefined);\n        if (_numberOfInitiationsKept > 0) {\n            let initiationProcessedEvent = new InitiationProcessedEvent(initiation.envelope.eid, initiation.envelope.cmid, initiation.sentTimestamp,\n                sessionEstablishedOffsetMillis, initiation.envelope.tid, initiation.envelope.msg, acknowledgeRoundTripTime, replyMessageEventType, replyToTerminatorId, requestRoundTripTime, replyMessageEvent);\n            _initiationProcessedEvents.push(initiationProcessedEvent);\n            while (_initiationProcessedEvents.length > _numberOfInitiationsKept) {\n                _initiationProcessedEvents.shift();\n            }\n        }\n\n        if (initiation.suppressInitiationProcessedEvent) {\n            log(\"InitiationProcessedEvent is suppressed, so NOT notifying listeners.\");\n            return;\n        }\n\n        // Firing to listeners, synchronous.\n        for (let i = 0; i < _initiationProcessedEventListeners.length; i++) {\n            try {\n                let registration = _initiationProcessedEventListeners[i];\n                let initiationMessageIncluded = (registration.includeInitiationMessage ? initiation.envelope.msg : undefined);\n                let replyMessageEventIncluded = (registration.includeReplyMessageEvent ? replyMessageEvent : undefined);\n                let initiationProcessedEvent = new InitiationProcessedEvent(initiation.envelope.eid, initiation.envelope.cmid, initiation.sentTimestamp, sessionEstablishedOffsetMillis,\n                    initiation.envelope.tid, initiationMessageIncluded, acknowledgeRoundTripTime, replyMessageEventType, replyToTerminatorId, requestRoundTripTime, replyMessageEventIncluded);\n                if (that.logging) log(\"Sending InitiationProcessedEvent to listener [\" + (i + 1) + \"/\" + _initiationProcessedEventListeners.length + \"]\", initiationProcessedEvent);\n                registration.listener(initiationProcessedEvent);\n            } catch (err) {\n                error(\"notify InitiationProcessedEvent listeners\", \"Caught error when notifying one of the [\" + _initiationProcessedEventListeners.length + \"] InitiationProcessedEvent listeners.\", err);\n            }\n        }\n    }\n\n    function _startPinger() {\n        log(\"Starting PING'er!\");\n        // Start the pinger with a random 5 +/-2 seconds, in case of mass reconnect.\n        // Notice the \"magic property\" here, used in integration tests\n        _pingLater(that.initialPingDelay);\n    }\n\n    function _stopPinger() {\n        log(\"Cancelling PINGer\");\n        if (_pinger_TimeoutId) {\n            clearTimeout(_pinger_TimeoutId);\n            _pinger_TimeoutId = undefined;\n        }\n    }\n\n    let _pinger_TimeoutId;\n    let _pingId = 0;\n\n    function _pingLater(initialPingDelay) {\n        _pinger_TimeoutId = setTimeout(function () {\n            // @ts-expect-error TS2339: 'state' is defined via defineProperty; we only read it here.\n            const state= that.state;\n            if (that.logging) log(\"Ping-'thread': About to send ping. ConnectionState:[\" + state + \"], matsSocketOpen:[\" + _matsSocketOpen + \"].\");\n            if ((state === ConnectionState.SESSION_ESTABLISHED) && _matsSocketOpen) {\n                let pingId = _pingId++;\n                let pingPong = new PingPong(pingId, Date.now());\n                _pings.push(pingPong);\n                if (_pings.length > 100) {\n                    _pings.shift();\n                }\n                _outstandingPings[pingId] = [performance.now(), pingPong];\n                _webSocket.send(\"[{\\\"t\\\":\\\"\" + MessageType.PING + \"\\\",\\\"x\\\":\\\"\" + pingId + \"\\\"}]\");\n                // Reschedule\n                _pingLater(15000);\n            } else {\n                log(\"Ping-'thread': NOT sending Ping and NOT Rescheduling due to state!=SESSION_ESTABLISHED or !connected - exiting 'thread'.\");\n            }\n        }, initialPingDelay);\n    }\n}\n"],"names":["AuthorizationRequiredEvent","type","currentExpirationTimestamp","this","AuthorizationRequiredEventType","NOT_PRESENT","EXPIRED","REAUTHENTICATE","Object","freeze","ConnectionState","NO_SESSION","CONNECTING","WAITING","CONNECTED","SESSION_ESTABLISHED","ConnectionEvent","webSocketUrl","webSocketEvent","timeoutSeconds","countdownSeconds","connectionAttempt","ConnectionEventType","CONNECTION_ERROR","LOST_CONNECTION","COUNTDOWN","MessageType","HELLO","WELCOME","SEND","REQUEST","RETRY","ACK","NACK","ACK2","RESOLVE","REJECT","SUB","UNSUB","SUB_OK","SUB_LOST","SUB_NO_AUTH","PUB","REAUTH","AUTH","PING","PONG","ReceivedEvent","traceId","sentTimestamp","receivedTimestamp","roundTripMillis","description","ReceivedEventType","TIMEOUT","SESSION_CLOSED","MessageEvent","data","messageId","correlationInformation","undefined","clientRequestTimestamp","debug","MessageEventType","SubscriptionEvent","topicId","SubscriptionEventType","OK","NOT_AUTHORIZED","LOST_MESSAGES","InitiationProcessedEvent","endpointId","clientMessageId","sessionEstablishedOffsetMillis","initiationMessage","acknowledgeRoundTripMillis","replyMessageEventType","replyToTerminatorId","requestRoundTripMillis","replyMessageEvent","InitiationProcessedEventType","REQUEST_REPLY_TO","requestReplyRoundTripMillis","PingPong","pingId","MatsSocketCloseCodes","VIOLATED_POLICY","UNEXPECTED_CONDITION","SERVICE_RESTART","GOING_AWAY","CLOSE_SESSION","SESSION_LOST","RECONNECT","DISCONNECT","MATS_SOCKET_PROTOCOL_ERROR","MatsSocketCloseCodesUtil","REVERSE","Map","entries","map","k","v","nameFor","code","get","ErrorEvent","message","object","reference","referenceAsString","maxLength","result","JSON","stringify","err","length","substring","DebugInformation","clientMessageSent","requestedDebugOptions","envelope","desc","messageReceived","resolvedDebugOptions","clientMessageReceived","clientMessageReceivedNodename","matsMessageSent","matsMessageReplyReceived","matsMessageReplyReceivedNodename","serverMessageCreated","serverMessageCreatedNodename","messageSentToClient","messageSentToClientNodename","resd","cmrts","cmrnn","mmsts","mmrrts","mmrrnn","smcts","smcnn","mscts","mscnn","DebugOption","TIMESTAMPS","NODES","CUSTOM_A","CUSTOM_B","appName","appVersion","urls","config","CLIENT_LIB_NAME_AND_VERSION","Error","String","Array","isArray","_nodeJsCallbackOnceWebSocketModuleResolved","webSocketFactory","_nodeJsTryingToImportWebSocketModule","webSocket","url","protocol","WebSocket","proc","process","versions","node","_isNodeJs","import","then","ws","log","default","setTimeout","catch","reason","error","performance","window","now","Date","that","userAgent","self","navigator","concat","sessionId","logging","errorLogging","outofbandclose","preconnectoperation","requestTimeout","maxConnectionAttempts","initialPingDelay","Math","random","addSessionClosedEventListener","sessionClosedEventListener","_sessionClosedEventListeners","push","addConnectionEventListener","connectionEventListener","_connectionEventListeners","addSubscriptionEventListener","subscriptionEventListener","_subscriptionEventListeners","addErrorEventListener","errorEventListener","_errorEventListeners","setAuthorizationExpiredCallback","authorizationExpiredCallback","_authorizationExpiredCallback","flush","setCurrentAuthorization","authorizationValue","expirationTimestamp","roomForLatencyMillis","_authorization","_expirationTimestamp","_roomForLatencyMillis","_authExpiredCallbackInvoked_EventType","_forcePipelineProcessing","defineProperty","_lastMessageEnqueuedTimestamp","_webSocket","_state","_pings","addPingPongListener","pingPongListener","_pingPongListeners","_initiationProcessedEvents","_numberOfInitiationsKept","set","numberOfInitiationsKept","shift","addInitiationProcessedEventListener","initiationProcessedEventListener","includeInitiationMessage","includeReplyMessageEvent","_initiationProcessedEventListeners","listener","terminator","terminatorId","messageCallback","rejectCallback","_terminators","_endpoints","startsWith","resolve","reject","endpoint","promiseProducer","subscribe","subs","_subscriptions","listeners","lastSmid","subscriptionSentToServer","i","_helloSent","_addEnvelopeToPipeline_EvaluatePipelineLater","t","eid","_matsSocketOpen","_evaluatePipelineLater","deleteSubscription","found","splice","send","Promise","initiation","create","ack","nack","suppressInitiationProcessedEvent","msg","_addInformationBearingEnvelopeToPipeline","request","configOrCallback","timeout","receivedCallback","ackCallback","nackCallback","to","requestReplyTo","_evaluatePipelineLater_timeoutId","clearTimeout","_evaluatePipelineSend","close","existingWebSocketUrl","_currentWebSocketUrl","existingSessionId","_closeSessionAndClearStateAndPipelineAndFuturesAndOutstandingMessages","closeSesionUrl","replace","sendBeacon","reconnect","disconnect","closeCode","onmessage","randomId","_alphabet","floor","randomCId","_jsonAlphabet","start","console","_matsSocketInstanceId","event","_useUrls","array","j","temp","_shuffleArray","_initialSessionEstablished_PerformanceNow","_lastAuthorizationSentToServer","_lastDebugOptionsSentToServer","_webSocketConnecting","_prePipeline","_pipeline","_messageSequenceId","_outstandingPings","_outstandingRequests","_outboxInitiations","_outboxInitiations_RetransmitGuard","_outboxReplies","_inbox","_invokeLater","_beforeunloadHandler","_clearWebSocketStateAndInfrastructure","_pinger_TimeoutId","removeEventListener","_resetReconnectStateVars","onclose","onerror","cmid","tid","_completeReceived","_completeRequest","performanceNow","thisMessageSequenceId","rd","retransmitGuard","attempt","messageSent_PerformanceNow","messageAcked_PerformanceNow","timeoutId","prePipeline","_requestNewAuthorizationFromApp","_initiateWebSocketCreation","helloMessage","clv","ts","an","av","auth","cid","sid","unshift","smid","lastEnvelope","what","outstandingInitiations","keys","_notifySessionClosedEventListeners","codeName","messageEvent","_","_connectionAttempt","_urlIndexCurrentlyConnecting","_connectionAttemptRound","_connectionTimeoutBase","_connectionTimeoutMin","_connectionTimeoutMax","_increaseReconnectStateVars","_secondsTenths","milliseconds","round","_updateStateAndNotifyConnectionEventListeners","connectionEvent","filter","key","closeEvent","preConnectOperationAbortFunction","websocketAttempt","countdownId","max","min","pow","currentCountdownTargetTimestamp","targetTimeoutTimestamp","secondsLeft","w_abortAttempt","abortFunctionTemp","onopen","target","webSocketInstanceId","w_defaultXhrPromiseFactory","params","xhr","XMLHttpRequest","abort","addEventListener","status","withCredentials","open","directUrl","setRequestHeader","authorization","w_attemptWebSocket","errorEvent","w_connectFailed_RetryOrWaitForTimeout","openEvent","_onerror","_onclose","_onmessage","w_countDownTimer","w_connectTimeout_AbortAttemptAndReschedule","sleep","abortAndPromise","statusMessage","w_attemptPreConnectionOperation","envelopes","parse","numEnvelopes","_startPinger","initiationEnvelope","reply","replyEnvelope","retryDelay","ids","_sendAck2Later","termOrEndp","toLowerCase","terminatorOrEndpoint","_sendAckLater","_createMessageEventForIncoming","fulfilled","resolveReject","resolveMessage","rejectMessage","messageEventType","x","eventType","pingPongHolder","pingPong","performanceThen","_roundTiming","stringified","_laterAckTimeoutId","_laterAcks","_laterNacks","_sendAcksAndNacksNow","_laterAck2TimeoutId","_laterAck2s","_sendAck2sNow","receivedEventType","receivedEvent","_issueInitiationProcessedEvent","incomingEnvelope","millis","acknowledgeRoundTripTime","requestRoundTripTime","initiationProcessedEvent","registration","initiationMessageIncluded","replyMessageEventIncluded","_pingLater","_pingId","state"],"mappings":"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"}