matssocket
Version:
MatsSocket client library
1 lines • 282 kB
Source Map (JSON)
{"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":["export { 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 {AuthorizationRequiredEvent} type - {@link AuthorizationRequiredEvent#type}\n * @param {number} currentExpirationTimestamp - {@link AuthorizationRequiredEvent#currentExpirationTimestamp}\n * @class\n */\nfunction AuthorizationRequiredEvent(type, currentExpirationTimestamp) {\n /**\n * Type of the event, one of {@link AuthorizationRequiredEvent}.\n *\n * @type {AuthorizationRequiredEvent}\n */\n this.type = type;\n\n /**\n * Millis-from-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 {number}\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 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","export { 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 {number} sentTimestamp - {@link ReceivedEvent#sentTimestamp}\n * @param {number} receivedTimestamp - {@link ReceivedEvent#receivedTimestamp}\n * @param {number} 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 {number}\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 {number}\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 {number}\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","export { 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 {number} 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 {number}\n */\n this.clientRequestTimestamp = undefined;\n\n /**\n * When the message was received on the Client, millis-since-epoch.\n *\n * @type {number}\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 * <b>Note that this number can be a float, not necessarily integer</b>.\n\n * @type {number}\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 RESOLVE: \"resolve\",\n\n REJECT: \"reject\",\n\n SEND: \"send\",\n\n REQUEST: \"request\",\n\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","export { InitiationProcessedEvent, InitiationProcessedEventType }\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 {int} sentTimestamp\n * @param {float} sessionEstablishedOffsetMillis\n * @param {string} traceId\n * @param {Object} initiationMessage\n * @param {float} acknowledgeRoundTripMillis\n * @param {MessageEventType} replyMessageEventType\n * @param {string} replyToTerminatorId\n * @param {float} 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 {int}\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 * <b>Note that this number can be a float, not necessarily integer</b>.\n *\n * @type {float}\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 {float}\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 * <b>Note that this number can be a float, not necessarily integer</b>.\n *\n * @type {float}\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","export { 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 {number} 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 {number}\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 * <b>Note that this number can be a float, not necessarily integer</b>.\n *\n * @type {number}\n */\n this.roundTripMillis = undefined;\n}\n","export { MatsSocketCloseCodes }\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 {int}\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\n /**\n * Resolves the numeric \"close code\" to the String key name of this enum, or <code>\"UNKNOWN(\"+closeCode+\")\"</code>\n * for unknown numeric codes.\n *\n * @param closeCode the numeric \"close code\" of the WebSocket.\n * @returns {string} String key name of this enum, or <code>\"UNKNOWN(\"+closeCode+\")\"</code> for unknown numeric codes.\n */\n nameFor: function (closeCode) {\n let keys = Object.keys(MatsSocketCloseCodes).filter(function (key) {\n return MatsSocketCloseCodes[key] === closeCode;\n });\n if (keys.length === 1) {\n return keys[0];\n }\n return \"UNKNOWN(\" + closeCode + \")\";\n }\n};\nObject.freeze(MatsSocketCloseCodes);\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","export { 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 * @class\n */\nfunction DebugInformation(clientMessageSent, requestedDebugOptions, envelope, receivedTimestamp) {\n\n /**\n * From client: When the message was sent, millis-from-epoch.\n * @type {number}\n */\n this.clientMessageSent = clientMessageSent;\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 * 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 * From server, only if debug:\n * @type {number}\n */\n this.clientMessageReceived = undefined;\n this.clientMessageReceivedNodename = undefined;\n\n this.matsMessageSent = undefined;\n this.matsMessageReplyReceived = undefined;\n this.matsMessageReplyReceivedNodename = undefined;\n\n this.serverMessageCreated = undefined;\n this.serverMessageCreatedNodename = undefined;\n\n this.messageSentToClient = undefined;\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 this.messageReceived = receivedTimestamp;\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 {string}\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} 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/**\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) {\n let clientLibNameAndVersion = \"MatsSocket.js,1.0.0-rc0-2025-10-04\";\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 = function (url, protocol) {\n return new WebSocket(url, protocol);\n };\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 import('ws')\n .then((ws) => {\n log(\"Constructor: NodeJs import('ws') went OK: Got WebSocket module\");\n const {default: WebSocket} = ws;\n setTimeout(() => {\n webSocketFactory = function (url, protocol) {\n return new WebSocket(url, protocol);\n };\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 // NOTE!! There is an \"implicit\"/\"hidden\" 'sessionId' field too, but we do not make it explicit.\n // 'sessionId' is set when we get the SessionId from WELCOME, cleared (deleted) upon SessionClose\n // (along with _matsSocketOpen = false)\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 * 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 '\" + MatsSocketCloseCodes.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 \" + MatsSocketCloseCodes.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 function _isNodeJs() {\n return process && process.versions && (typeof process.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(clientLibNameAndVersion + \" 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 delete (that.sessionId);\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: clientLibNameAndVersion + \"; 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 ('maxConnectionAttempts' in that && 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 [\" + MatsSocketCloseCodes.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 = MatsSocketCloseCodes.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 [\" + MatsSocketCloseCodes.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 ? that.initialPingDelay : 3000 + Math.random() * 4000);\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 if (that.logging) log(\"Ping-'thread': About to send ping. ConnectionState:[\" + that.state + \"], matsSocketOpen:[\" + _matsSocketOpen + \"].\");\n if ((that.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}"],"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","nameFor","closeCode","keys","filter","key","length","ErrorEvent","message","object","reference","referenceAsString","maxLength","result","JSON","stringify","err","substring","DebugInformation","clientMessageSent","requestedDebugOptions","envelope","desc","resolvedDebugOptions","clientMessageReceived","clientMessageReceivedNodename","matsMessageSent","matsMessageReplyReceived","matsMessageReplyReceivedNodename","serverMessageCreated","serverMessageCreatedNodename","messageSentToClient","messageSentToClientNodename","resd","cmrts","cmrnn","mmsts","mmrrts","mmrrnn","smcts","smcnn","mscts","mscnn","messageReceived","DebugOption","TIMESTAMPS","NODES","CUSTOM_A","CUSTOM_B","appName","appVersion","urls","config","clientLibNameAndVersion","Error","String","Array","isArray","_nodeJsCallbackOnceWebSocketModuleResolved","webSocketFactory","_nodeJsTryingToImportWebSocketModule","webSocket","url","protocol","WebSocket","process","versions","node","import","then","ws","log","default","setTimeout","catch","reason","error","performance","window","now","Date","that","userAgent","self","navigator","concat","logging","errorLogging","outofbandclose","preconnectoperation","requestTimeout","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","get","_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","sessionId","_closeSessionAndClearStateAndPipelineAndFuturesAndOutstandingMessages","closeSesionUrl","replace","sendBeacon","reconnect","disconnect","onmessage","randomId","_alphabet","Math","floor","random","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","_notifySessionClosedEventListeners","code","codeName","messageEvent","_","_connectionAttempt","_urlIndexCurrentlyConnecting","_connectionAttemptRound","_connectionTimeoutBase","_connectionTimeoutMin","_connectionTimeoutMax","_increaseReconnectStateVars","_secondsTenths","milliseconds","round","_updateStateAndNotifyConnectionEventListeners","connectionEvent","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","maxConnectionAttempts","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","initialPingDelay","_pingId","state"],"mappings":"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"}