UNPKG

matssocket

Version:
1,039 lines (935 loc) 251 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.matssocket = {})); })(this, (function (exports) { 'use strict'; /** * Sent by the MatsSocket, via the {@link MatsSocket#setAuthorizationExpiredCallback}, when it requires new or * revalidated authentication by the client. * * @param {AuthorizationRequiredEventType} type - {@link AuthorizationRequiredEvent#type} * @param {Timestamp} currentExpirationTimestamp - {@link AuthorizationRequiredEvent#currentExpirationTimestamp} * @class */ function AuthorizationRequiredEvent(type, currentExpirationTimestamp) { /** * Type of the event, one of {@link AuthorizationRequiredEventType}. * * @type {AuthorizationRequiredEventType} */ this.type = type; /** * Millis-since-epoch when the current Authorization expires - note that this might well still be in the future, * but the "slack" left before expiration is used up. * * @type {Timestamp} */ this.currentExpirationTimestamp = currentExpirationTimestamp; } /** * Type of {@link AuthorizationRequiredEvent}. * * @enum {string} * @readonly */ const AuthorizationRequiredEventType = { /** * Initial state, if auth not already set by app. */ NOT_PRESENT: "notpresent", /** * The authentication is expired - note that this might well still be in the future, * but the "slack" left before expiration is not long enough. */ EXPIRED: "expired", /** * The server has requested that the app provides fresh auth to proceed - this needs to be fully fresh, even * though there might still be "slack" enough left on the current authorization to proceed. (The server side * might want the full expiry to proceed, or wants to ensure that the app can still produce new auth - i.e. * it might suspect that the current authentication session has been invalidated, and need proof that the app * can still produce new authorizations/tokens). */ REAUTHENTICATE: "reauthenticate" }; Object.freeze(AuthorizationRequiredEventType); /** * States for MatsSocket's {@link MatsSocket#state state}. * * @enum {string} * @readonly */ const ConnectionState = { /** * This is the initial State of a MatsSocket. Also, the MatsSocket is re-set back to this State in a * Session-Closed-from-Server situation (which is communicated via listeners registered with * {@link MatsSocket#addSessionClosedEventListener}), OR if you have explicitly performed a * {@link MatsSocket#close}. * <p/> * Only transition out of this state is into {@link ConnectionState.CONNECTING}. */ NO_SESSION: "nosession", /** * Read doc at {@link ConnectionEventType.CONNECTING}. */ CONNECTING: "connecting", /** * Read doc at {@link ConnectionEventType.WAITING}. */ WAITING: "waiting", /** * Read doc at {@link ConnectionEventType.CONNECTED}. */ CONNECTED: "connected", /** * Read doc at {@link ConnectionEventType.SESSION_ESTABLISHED}. */ SESSION_ESTABLISHED: "sessionestablished" }; Object.freeze(ConnectionState); /** * Event object for {@link MatsSocket#addConnectionEventListener}. * <p /> * <b>Note on event ordering</b>: {@link ConnectionEvent}s are delivered ASAP. This means that for events that the * client controls, they are issued <i/>before</i> the operation they describe commences: * {@link ConnectionEventType#CONNECTING CONNECTING} and * {@link ConnectionEventType#SESSION_ESTABLISHED SESSION_ESTABLISHED}. However, for events where the client is * "reacting", e.g. when the WebSocket connects, or abruptly closes, they are issued ASAP when the Client gets to know about it: * {@link ConnectionEventType#CONNECTED CONNECTED}, {@link ConnectionEventType#LOST_CONNECTION LOST_CONNECTION}, * {@link ConnectionEventType#CONNECTION_ERROR CONNECTION_ERROR} and {@link ConnectionEventType#WAITING WAITING}. * For {@link ConnectionEventType#COUNTDOWN COUNTDOWN}, there is not much to say wrt. timing, other than you won't typically * get a 'countdown'-event with 0 seconds left, as that is when we transition into 'connecting' again. For events * that also describe {@link ConnectionState}s, the {@link MatsSocket.state} is updated before the event is fired. * * @param {ConnectionEventType} type - {@link ConnectionEvent#type} * @param {string} webSocketUrl - {@link ConnectionEvent#webSocketUrl} * @param {Event} webSocketEvent - {@link ConnectionEvent#webSocketEvent} * @param {number} timeoutSeconds - {@link ConnectionEvent#timeoutSeconds} * @param {number} countdownSeconds - {@link ConnectionEvent#countdownSeconds} * @param {number} connectionAttempt - {@link ConnectionEvent#connectionAttempt} * @class */ function ConnectionEvent(type, webSocketUrl, webSocketEvent, timeoutSeconds, countdownSeconds, connectionAttempt) { /** * The type of the <code>ConnectionEvent</code>, returns an enum value of {@link ConnectionEventType}. * * @type {ConnectionEventType} */ this.type = type; /** * Holds the current URL we're either connected to, was connected to, or trying to connect to. * * @type {string} */ this.webSocketUrl = webSocketUrl; /** * For several of the events (enumerated in {@link ConnectionEventType}), there is an underlying WebSocket event * that caused it. This field holds that. * <ul> * <li>{@link ConnectionEventType#WAITING}: WebSocket {@link CloseEvent} that caused this transition.</li> * <li>{@link ConnectionEventType#CONNECTED}: WebSocket {@link Event} that caused this transition.</li> * <li>{@link ConnectionEventType#CONNECTION_ERROR}: WebSocket {@link Event} that caused this transition.</li> * <li>{@link ConnectionEventType#LOST_CONNECTION}: WebSocket {@link CloseEvent} that caused it.</li> * </ul> * * @type {Event} */ this.webSocketEvent = webSocketEvent; /** * For {@link ConnectionEventType#CONNECTING}, {@link ConnectionEventType#WAITING} and {@link ConnectionEventType#COUNTDOWN}, * tells how long the timeout for this attempt is, i.e. what the COUNTDOWN events start out with. Together with * {@link #countdownSeconds} of the COUNTDOWN events, this can be used to calculate a fraction if you want to * make a "progress bar" of sorts. * <p/> * The timeouts starts at 500 ms (unless there is only 1 URL configured, in which case 5 seconds), and then * increases exponentially, but maxes out at 15 seconds. * * @type {number} */ this.timeoutSeconds = timeoutSeconds; /** * For {@link ConnectionEventType#CONNECTING}, {@link ConnectionEventType#WAITING} and {@link ConnectionEventType#COUNTDOWN}, * tells how many seconds there are left for this attempt (of the {@link #timeoutSeconds} it started with), * with a tenth of a second as precision. With the COUNTDOWN events, these come in each 100 ms (1/10 second), * and show how long time there is left before trying again (if MatsSocket is configured with multiple URLs, * the next attempt will be a different URL). * <p/> * The countdown is started when the state transitions to {@link ConnectionEventType#CONNECTING}, and * stops either when {@link ConnectionEventType#CONNECTED} or the timeout reaches zero. If the * state is still CONNECTING when the countdown reaches zero, implying that the "new WebSocket(..)" call still * has not either opened or closed, the connection attempt is aborted by calling webSocket.close(). It then * tries again, possibly with a different URL - and the countdown starts over. * <p/> * Notice that the countdown is not affected by any state transition into {@link ConnectionEventType#WAITING} - * such transition only means that the "new WebSocket(..)" call failed and emitted a close-event, but we will * still wait out the countdown before trying again. * <p/> * Notice that you will most probably not get an event with 0 seconds, as that is when we transition into * {@link ConnectionEventType#CONNECTING} and the countdown starts over (possibly with a larger timeout). * <p/> * Truncated exponential backoff: The timeouts starts at 500 ms (unless there is only 1 URL configured, in which * case 5 seconds), and then increases exponentially, but maxes out at 15 seconds. * * @type {number} */ this.countdownSeconds = countdownSeconds; /** * The connection attempt count, starts at 0th attempt and increases for each time the connection attempt fails. * * @type {number} */ this.connectionAttempt = connectionAttempt; } /** * The event types of {@link ConnectionEvent} - four of the event types are state-transitions into different states * of {@link ConnectionState}. * * @enum {string} * @readonly */ const ConnectionEventType = { /** * State, and fires as ConnectionEvent when we transition into this state, which is when the WebSocket is literally trying to connect. * This is between <code>new WebSocket(url)</code> (or the {@link MatsSocket#preconnectoperation "PreConnectOperation"} if configured), * and either webSocket.onopen or webSocket.onclose is fired, or countdown reaches 0. If webSocket.onopen, * we transition into {@link ConnectionEventType.CONNECTED}, if webSocket.onclose, we transition into * {@link ConnectionEventType.WAITING}. If we reach countdown 0 while in CONNECTING, we will "re-transition" to the same state, and * thus get one more event of CONNECTING. * <p/> * User Info Tip: Show a info-box, stating "Connecting! <4.0 seconds..>", countdown in "grayed out" style, box is * some neutral information color, e.g. yellow (fading over to this color if already red or orange due to * {@link ConnectionEventType.CONNECTION_ERROR} or {@link ConnectionEventType.LOST_CONNECTION}). * Each time it transitions into CONNECTING, it will start a new countdown. Let's say it starts from say 4 * seconds: If this connection attempt fails after 1 second, it will transition into WAITING and continue the * countdown with 3 seconds remaining. */ CONNECTING: ConnectionState.CONNECTING, /** * State, and fires as ConnectionEvent when we transition into this state, which is when {@link ConnectionEventType.CONNECTING} fails. * The only transition out of this state is {@link ConnectionEventType.CONNECTING}, when the {@link ConnectionEventType.COUNTDOWN} reaches 0. * <p/> * Notice that the {@link ConnectionEvent} contains the {@link Event} that came with webSocket.close (while CONNECTING). * <p/> * User Info Tip: Show a info-box, stating "Waiting! <2.9 seconds..>", countdown in normal visibility, box is * some neutral information color, e.g. yellow (keeping the box color fading if in progress). * It will come into this state from {@link ConnectionEventType.CONNECTING}, and have the time remaining from the initial countdown. * So if the attempt countdown started from 4 seconds, and it took 1 second before the connection attempt failed, * then there will be 3 seconds left in WAITING state. */ WAITING: ConnectionState.WAITING, /** * State, and fires as ConnectionEvent when we transition into this state, which is when WebSocket.onopen is fired. * Notice that the MatsSocket is still not fully established, as we have not yet exchanged HELLO and WELCOME - * the MatsSocket is fully established at {@link ConnectionEventType.SESSION_ESTABLISHED}. * <p/> * Notice that the {@link ConnectionEvent} contains the WebSocket 'onopen' {@link Event} that was issued when * the WebSocket opened. * <p/> * User Info Tip: Show a info-box, stating "Connected!", happy-color, e.g. green, with no countdown. */ CONNECTED: ConnectionState.CONNECTED, /** * State, and fires as ConnectionEvent when we transition into this state, which is when when the WELCOME MatsSocket message comes * from the Server, also implying that it has been authenticated: The MatsSocket is now fully established, and * actual messages can be exchanged. * <p/> * User Info Tip: Show a info-box, stating "Session OK!", happy-color, e.g. green, with no countdown - and the * entire info-box fades away fast, e.g. after 1 second. */ SESSION_ESTABLISHED: ConnectionState.SESSION_ESTABLISHED, /** * This is a pretty worthless event. It comes from WebSocket.onerror. It will <i>always</i> be trailed by a * WebSocket.onclose, which gives the event {@link ConnectionEventType.LOST_CONNECTION}. * <p/> * Notice that the {@link ConnectionEvent} contains the {@link Event} that caused the error. * <p/> * 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). */ CONNECTION_ERROR: "connectionerror", /** * This comes when WebSocket.onclose is fired "unexpectedly", <b>and the reason for this close is NOT a SessionClosed Event</b> (The latter will * instead invoke the listeners registered with {@link MatsSocket#addSessionClosedEventListener}). * A LOST_CONNECTION will start a reconnection attempt after a very brief delay (couple of hundred milliseconds), * and the next state transition and thus event is {@link ConnectionEventType.CONNECTING}. * <p/> * Notice that the {@link ConnectionEvent} contains the {@link CloseEvent} that caused the lost connection. * <p/> * User Info Tip: Show a info-box, stating "Connection broken!", which is some orange color (unless it already * is red due to {@link ConnectionEventType.CONNECTION_ERROR}), fading over to the next color when next event ({@link ConnectionEventType.CONNECTING} * comes in. */ LOST_CONNECTION: "lostconnection", /** * Events fired every 100ms while in state {@link ConnectionEventType.CONNECTING}, possibly continuing over to {@link ConnectionEventType.WAITING}. * Notice that you will most probably not get an event with 0 seconds left, as that is when we (re-)transition to * {@link ConnectionEventType.CONNECTING} and the countdown starts over (possibly with a larger timeout). Read more at * {@link ConnectionEvent#countdownSeconds}. * <p/> * User Info Tip: Read more at {@link ConnectionEventType.CONNECTING} and {@link ConnectionEventType.WAITING}. */ COUNTDOWN: "countdown" }; Object.freeze(ConnectionEventType); /** * <b>Copied directly from MatsSocketServer.java</b>: * All Message Types (aka MatsSocket Envelope Types) used in the wire-protocol of MatsSocket. * * @enum {string} * @readonly */ const MessageType = { /** * A HELLO message must be part of the first Pipeline of messages, preferably alone. One of the messages in the * first Pipeline must have the "auth" field set, and it might as well be the HELLO. */ HELLO: "HELLO", /** * The reply to a {@link #HELLO}, where the MatsSocketSession is established, and the MatsSocketSessionId is * returned. If you included a MatsSocketSessionId in the HELLO, signifying that you want to reconnect to an * existing session, and you actually get a WELCOME back, it will be the same as what you provided - otherwise * the connection is closed with {@link MatsSocketCloseCodes#SESSION_LOST}. */ WELCOME: "WELCOME", /** * The sender sends a "fire and forget" style message. */ SEND: "SEND", /** * The sender initiates a request, to which a {@link #RESOLVE} or {@link #REJECT} message is expected. */ REQUEST: "REQUEST", /** * The sender should retry the message (the receiver could not handle it right now, but a Retry might fix it). */ RETRY: "RETRY", /** * The specified message was Received, and acknowledged positively - i.e. the other party has decided to process * it. * <p/> * The sender of the ACK has now taken over responsibility of the specified message, put it (at least the * reference ClientMessageId) in its <i>Inbox</i>, and possibly started processing it. The reason for the Inbox * is so that if it Receives the message again, it may just insta-ACK/NACK it and toss this copy out the window * (since it has already handled it). * <p/> * When an ACK is received, the receiver may safely delete the acknowledged message from its <i>Outbox</i>. */ ACK: "ACK", /** * The specified message was Received, but it did not acknowledge it - i.e. the other party has decided to NOT * process it. * <p/> * The sender of the NACK has now taken over responsibility of the specified message, put it (at least the * reference Client/Server MessageId) in its <i>Inbox</i> - but has evidently decided not to process it. The * reason for the Inbox is so that if it Receives the message again, it may just insta-ACK/NACK it and toss this * copy out the window (since it has already handled it). * <p/> * When an NACK is received, the receiver may safely delete the acknowledged message from its <i>Outbox</i>. */ NACK: "NACK", /** * An "Acknowledge ^ 2", i.e. an acknowledge of the {@link #ACK} or {@link #NACK}. When the receiver gets this, * it may safely delete the entry it has for the specified message from its <i>Inbox</i>. * <p/> * The message is now fully transferred from one side to the other, and both parties again has no reference to * this message in their Inbox and Outbox. */ ACK2: "ACK2", /** * A RESOLVE-reply to a previous {@link #REQUEST} - if the Client did the {@code REQUEST}, the Server will * answer with either a RESOLVE or {@link #REJECT}. */ RESOLVE: "RESOLVE", /** * A REJECT-reply to a previous {@link #REQUEST} - if the Client did the {@code REQUEST}, the Server will answer * with either a REJECT or {@link #RESOLVE}. */ REJECT: "REJECT", /** * Request from Client: The Client want to subscribe to a Topic, the TopicId is specified in 'eid'. */ SUB: "SUB", /** * Request from Client: The Client want to unsubscribe to a Topic, the TopicId is specified in 'eid'. */ UNSUB: "UNSUB", /** * Reply from Server: Subscription was OK. If this is a reconnect, this indicates that any messages that was * lost "while offline" will now be delivered/"replayed". */ SUB_OK: "SUB_OK", /** * Reply from Server: Subscription went OK, but you've lost messages: The messageId that was referenced in the * {@link #SUB} was not known to the server, implying that there are at least one message that has expired, and * as such it can be many - so you won't get any "replayed". */ SUB_LOST: "SUB_LOST", /** * Reply from Server: Subscription was not authorized - no messages for this Topic will be delivered. */ SUB_NO_AUTH: "SUB_NO_AUTH", /** * Topic message from Server: A message is issued on Topic, the TopicId is specified in 'eid', while the message * is in 'msg'. */ PUB: "PUB", /** * The server requests that the Client re-authenticates, where the Client should immediately get a fresh * authentication and send it back using either any message it has pending, or in a separate {@link #AUTH} * message. Message processing - both processing of received messages, and sending of outgoing messages (i.e. * Replies to REQUESTs, or Server-initiated SENDs and REQUESTs) will be stalled until such auth is gotten. */ REAUTH: "REAUTH", /** * From Client: The client can use a separate AUTH message to send over the requested {@link #REAUTH} (it could * just as well put the 'auth' in a PING or any other message it had pending). */ AUTH: "AUTH", /** * A PING, to which a {@link #PONG} is expected. */ PING: "PING", /** * A Reply to a {@link #PING}. */ PONG: "PONG" }; Object.freeze(MessageType); /** * Message Received on Server event: "acknowledge" or "negative acknowledge" - these are the events which the * returned Promise of a send(..) is settled with (i.e. then() and catch()), and which * {@link MatsSocket#request request}'s receivedCallback function are invoked with. * * @param {ReceivedEventType} type - {@link ReceivedEvent#type} * @param {string} traceId - {@link ReceivedEvent#traceId} * @param {Timestamp} sentTimestamp - {@link ReceivedEvent#sentTimestamp} * @param {Timestamp} receivedTimestamp - {@link ReceivedEvent#receivedTimestamp} * @param {Timestamp} roundTripMillis - {@link ReceivedEvent#roundTripMillis} * @param {string} description - {@link ReceivedEvent#description} * @class */ function ReceivedEvent(type, traceId, sentTimestamp, receivedTimestamp, roundTripMillis, description) { /** * Values are from {@link ReceivedEventType}: Type of received event, either {@link ReceivedEventType#ACK "ack"}, * {@link ReceivedEventType#NACK "nack"} - <b>or {@link ReceivedEventType#SESSION_CLOSED "sessionclosed"} if the * session was closed with outstanding initiations and MatsSocket therefore "clears out" these initiations.</b> *s * @type {ReceivedEventType} */ this.type = type; /** * TraceId for this call / message. * * @type {string} */ this.traceId = traceId; /** * Millis-since-epoch when the message was sent from the Client. * * @type {Timestamp} */ this.sentTimestamp = sentTimestamp; /** * Millis-since-epoch when the ACK or NACK was received on the Client, millis-since-epoch. * * @type {Timestamp} */ this.receivedTimestamp = receivedTimestamp; /** * Round-trip time in milliseconds from Initiation of flow (send, request, requestReplyTo) to Received * acknowledgement (ACK/NACK) was received, basically <code>{@link #receivedTimestamp} * - {@link #sentTimestamp}</code>, but depending on the browser/runtime, you might get higher resolution * than integer milliseconds (i.e. fractions of milliseconds, a floating point number) - it depends on * the resolution of <code>performance.now()</code>. * <p/> * Notice that Received-events might be de-prioritized on the Server side (batched up, with micro-delays * to get multiple into the same batch), so this number should not be taken as the "ping time". * * @type {FractionalMillis} */ this.roundTripMillis = roundTripMillis; /** * Sometimes, typically on Server NACKs (e.g. targetting non-existing Endpoint), the Server supplies a * description to why this was no good. * * @type {string} */ this.description = description; } /** * Types of {@link ReceivedEvent}. * * @enum {string} * @readonly */ const ReceivedEventType = { /** * If the Server-side MatsSocketEndpoint/Terminator accepted the message for handling (and if relevant, * forwarded it to the Mats fabric). The returned Promise of send() is <i>resolved</i> with this type of event. * The 'receivedCallback' of a request() will get both "ack" and {@link #NACK "nack"}, thus must check on * the type if it makes a difference. */ ACK: "ack", /** * If the Server-side MatsSocketEndpoint/Terminator dit NOT accept the message, either explicitly with * context.deny(), or by failing with Exception. The returned Promise of send() is <i>rejected</i> with this * type of event. The 'receivedCallback' of a request() will get both "nack" and {@link #ACK "ack"}, thus must * check on the type if it makes a difference. * <p/> * Notice that a for a Client-initiated Request which is insta-rejected in the incomingHandler by invocation of * context.reject(..), this implies <i>acknowledge</i> of the <i>reception</i> of the message, but <i>reject</i> * as with regard to the </i>reply</i> (the Promise returned from request(..)). */ NACK: "nack", /** * "Synthetic" event in that it is not a message from Server: A Client-to-Server * {@link MatsSocket#request() Request} was not ACKed or NACKed by the server within the * {@link MatsSocket#requestTimeoutMillis default request timeout} - or a specific timeout specified in the request * invocation. In these situations, any nack- or receivedCallback will be invoked with a {@link ReceivedEvent} * of this type. */ TIMEOUT: "timeout", /** * "Synthetic" event in that it is not a message from Server: This only happens if the MatsSocketSession is * closed with outstanding Initiations not yet Received on Server. In these situations, any nack- or * receivedCallback will be invoked with a {@link ReceivedEvent} of this type. */ SESSION_CLOSED: "sessionclosed" }; Object.freeze(ReceivedEventType); /** * Message Event - the event emitted for a {@link MatsSocket#request() Requests}'s Promise resolve() and reject() * (i.e. then() and catch()), and to a {@link MatsSocket#terminator() Terminator}'s resolveCallback and * rejectCallback functions for replies due to {@link MatsSocket#requestReplyTo() requestReplyTo}, and for Server * initiated Sends (to Terminators), and for the event to a {@link MatsSocket#endpoint() Endpoint} upon a Server * initiated Request, and for the event sent to a {@link MatsSocket#subscribe() Subscription}. * * @param {MessageEventType} type - {@link MessageEvent#type} * @param {object} data - {@link MessageEvent#data} * @param {string} traceId - {@link MessageEvent#traceId} * @param {string} messageId - {@link MessageEvent#messageId} * @param {Timestamp} receivedTimestamp - {@link MessageEvent#receivedTimestamp} * @class */ function MessageEvent(type, data, traceId, messageId, receivedTimestamp) { /** * Values are from {@link MessageEventType}: Either {@link MessageEventType#SEND "send"} (for a Client * Terminator when targeted for a Server initiated Send); {@link MessageEventType#REQUEST "request"} (for a * Client Endpoint when targeted for a Server initiated Request); or {@link MessageEventType#RESOLVE "resolve"} * or {@link MessageEventType#REJECT "reject"} (for settling of Promise from a Client-initiated Request, and * for a Client Terminator when targeted as the reply-endpoint for a Client initiated Request) - <b>or * {@link MessageEventType#SESSION_CLOSED "sessionclosed"} if the session was closed with outstanding Requests * and MatsSocket therefore "clears out" these Requests.</b> * <p/> * Notice: In the face of {@link MessageType#SESSION_CLOSED "sessionclosed"} or {@link MessageType#TIMEOUT "timeout"}, * the {@link #data} property (i.e. the actual message from the server) will be <code>undefined</code>. * Wrt. "sessionclosed", this is <i>by definition</i>: The Request was outstanding, meaning that an answer from the * Server had yet to come. This is opposed to a normal REJECT settling from the Server-side MatsSocketEndpoint, * which may choose to include data with a rejection. The same basically goes wrt. "timeout", as the Server * has not replied yet. * * @type {MessageEventType} */ this.type = type; /** * The actual data from the other peer. * <p/> * Notice: In the face of {@link MessageType#SESSION_CLOSED "sessionclosed"} or {@link MessageType#TIMEOUT "timeout"}, * this value will be <code>undefined</code>. * Wrt. "sessionclosed", this is <i>by definition</i>: The Request was outstanding, meaning that an answer from the * Server had yet to come. This is opposed to a normal REJECT settling from the Server-side MatsSocketEndpoint, * which may choose to include data with a rejection. The same basically goes wrt. "timeout", as the Server * has not replied yet. * * @type {object} */ this.data = data; /** * When a Terminator gets invoked to handle a Reply due to a Client initiated {@link MatsSocket#requestReplyTo}, * this holds the 'correlationInformation' object that was supplied in the requestReplyTo(..) invocation. * * @type {object} */ this.correlationInformation = undefined; /** * The TraceId for this call / message. * * @type {string} */ this.traceId = traceId; /** * Either the ClientMessageId if this message is a Reply to a Client-initiated Request (i.e. this message is a * RESOLVE or REJECT), or ServerMessageId if this originated from the Server (i.e. SEND or REQUEST); * * @type {string} */ this.messageId = messageId; /** * millis-since-epoch when the Request, for which this message is a Reply, was sent from the * Client. If this message is not a Reply to a Client-initiated Request, it is undefined. * * @type {Timestamp} */ this.clientRequestTimestamp = undefined; /** * When the message was received on the Client, millis-since-epoch. * * @type {Timestamp} */ this.receivedTimestamp = receivedTimestamp; /** * For {@link MatsSocket#request()} and {@link MatsSocket#requestReplyTo()} Requests: Round-trip time in * milliseconds from Request was performed to Reply was received, basically <code>{@link #receivedTimestamp} - * {@link #clientRequestTimestamp}</code>, but depending on the browser/runtime, you might get higher resolution * than integer milliseconds (i.e. fractions of milliseconds, a floating point number) - it depends on the * resolution of <code>performance.now()</code>. * @type {FractionalMillis} */ this.roundTripMillis = undefined; /** * If debugging is requested, by means of {@link MatsSocket#debug} or the config object in the send, request and * requestReplyTo, this will contain a {@link DebugInformation} instance. However, the contents of that object * is decided by what you request, and what the authorized user is allowed to get as decided by the * AuthenticationPlugin when authenticating the user. */ this.debug = undefined; } /** * Types of {@link MessageEvent}. * * @enum {string} * @readonly */ const MessageEventType = { /** Message sent from Server to Client as a resolve for a previous request. */ RESOLVE: "resolve", /** Message sent from Server to Client as a reject for a previous request. */ REJECT: "reject", /** Message sent from Server to a Client Terminator. */ SEND: "send", /** Message sent from Server to a Client Endpoint (expecting a reply). */ REQUEST: "request", /** Message sent from Server to a Client Topic. */ PUB: "pub", /** * "Synthetic" event in that it is not a message from Server: A Client-to-Server * {@link MatsSocket#request() Request} was not replied to by the server within the * {@link MatsSocket#requestTimeout default request timeout} - or a specific timeout specified in the request * invocation. In these situations, the Request Promise is rejected with a {@link MessageEvent} of this type, * and the {@link MessageEvent#data} value is undefined. */ TIMEOUT: "timeout", /** * "Synthetic" event in that it is not a message from Server: This only happens if the MatsSocketSession is * closed with outstanding Client-to-Server {@link MatsSocket#request() Requests} not yet replied to by the * server. In these situations, the Request Promise is rejected with a {@link MessageEvent} of this type, and * the {@link MessageEvent#data} value is undefined. */ SESSION_CLOSED: "sessionclosed" }; Object.freeze(MessageEventType); /** * Information about how a subscription went on the server side. If you do two subscriptions to the same Topic, * you will still only get one such message - thus if you want one for each, you'd better add two listeners too, * <i>before</i> doing any of the subscribes. * <p /> * Note: this also fires upon every reconnect. <b>Make note of the {@link SubscriptionEventType#LOST_MESSAGES}!</b> * * @param type {SubscriptionEventType} - {@link SubscriptionEvent#type} * @param topicId {string} - {@link SubscriptionEvent#topicId} * @class */ function SubscriptionEvent(type, topicId) { /** * How the subscription fared. * * @type {SubscriptionEventType} */ this.type = type; /** * What TopicIc this relates to. * * @type {string} */ this.topicId = topicId; } /** * Type of {@link SubscriptionEvent}. * * @enum {string} * @readonly */ const SubscriptionEventType = { /** * The subscription on the server side went ok. If reconnect, any missing messages are now being sent. */ OK: "ok", /** * You were not authorized to subscribe to this Topic. */ NOT_AUTHORIZED: "notauthorized", /** * Upon reconnect, the "last message Id" was not known to the server, implying that there are lost messages. * Since you will now have to handle this situation by other means anyway (e.g. do a request for all stock ticks * between the last know timestamp and now), you will thus not get any of the lost messages even if the server * has some. */ LOST_MESSAGES: "lostmessages" }; Object.freeze(SubscriptionEventType); /** * (Metrics) Information about Client-to-Server SENDs and REQUESTs (aka <i>Client Initiations</i>), including * experienced round-trip times for both Received acknowledgement, and for Requests, the Request-to-Reply time. * <p /> * For each message that, for sends, has been acknowledged received, and for requests, has been replied to, gives * this information: * <ul> * <li>Client MessageId (envelope's 'cmid').</li> * <li>Timestamp of when message was sent.</li> * <li>Target MatsSocket Endpoint or Terminator Id (envelope's 'eid').</li> * <li>TraceId for the SEND or REQUEST (envelope's 'tid').</li> * <li>The outgoing message, i.e. the SEND or the REQUEST message (envelope's 'msg').</li> * <li>Experienced Received Acknowledge round-trip time.</li> * <li>For {@link MatsSocket#request() Requests}, the Reply's {@link MessageEventType}</li> * <li>For {@link MatsSocket#requestReplyTo() requestReplyTo} Requests, the replyToTerminatorId.</li> * <li>For Requests, the total experienced Request-to-Reply time.</li> * <li>For Requests, the Reply {@link MessageEvent} object.</li> * </ul> * You may "subscribe" to <code>InitiationProcessedEvents</code> using * {@link MatsSocket#addInitiationProcessedEventListener()}, and you may get the latest such events from the * property {@link MatsSocket#initiations}. * <p /> * <b>Note on event ordering</b>: * <ul> * <li>send: First {@link ReceivedEvent} is issued. Then an {@link InitiationProcessedEvent} is added to * {@link MatsSocket#initiations}, and then all {@link InitiationProcessedEvent} listeners are invoked</li> * <li>request/requestReplyTo: First {@link ReceivedEvent} is issued (i.e. ack/nack), then when the reply * comes back to the server, an {@link InitiationProcessedEvent} is added to {@link MatsSocket#initiations}, and * then all {@link InitiationProcessedEvent} listeners are invoked, and finally the {@link MessageEvent} is * delivered, either as settling of the return Reply-Promise (for 'request'), or invocation of the Terminator's * message- or rejectCallbacks (for 'requestReplyTo'). * </ul> * * @param {string} endpointId * @param {string} clientMessageId * @param {Timestamp} sentTimestamp * @param {FractionalMillis} sessionEstablishedOffsetMillis * @param {string} traceId * @param {Object} initiationMessage * @param {FractionalMillis} acknowledgeRoundTripMillis * @param {MessageEventType} replyMessageEventType * @param {string} replyToTerminatorId * @param {FractionalMillis} requestRoundTripMillis * @param {MessageEvent} replyMessageEvent * @class */ function InitiationProcessedEvent(endpointId, clientMessageId, sentTimestamp, sessionEstablishedOffsetMillis, traceId, initiationMessage, acknowledgeRoundTripMillis, replyMessageEventType, replyToTerminatorId, requestRoundTripMillis, replyMessageEvent) { /** * Which initiation type of this flow, enum of {@link InitiationProcessedEventType}. * * @type {InitiationProcessedEventType} */ this.type = ((replyToTerminatorId ? InitiationProcessedEventType.REQUEST_REPLY_TO : (replyMessageEventType ? InitiationProcessedEventType.REQUEST : InitiationProcessedEventType.SEND))); /** * Target Server MatsSocket Endpoint or Terminator Id (envelope's 'eid'). * * @type {string} */ this.endpointId = endpointId; /** * The Client MessageId of the Initiation (envelope's 'cmid'). For this particular MatsSocket library, this * is currently an integer sequence id. * * @type {string} */ this.clientMessageId = clientMessageId; /** * Millis-from-epoch when this initiation was sent. * * @type {Timestamp} */ this.sentTimestamp = sentTimestamp; /** * The number of milliseconds offset for sending this message from the initial {@link ConnectionEventType#SESSION_ESTABLISHED} event for * this MatsSocket - <b>this number will typically be negative for the first messages</b>: A negative number * implies that the message was sent before the WELCOME was received, which again implies that the very first * message will by definition have a negative offset since it is this message that starts the HELLO/WELCOME * handshake and is thus enqueued before the WELCOME has been received. This is desirable: Upon application * startup, stack up all requests that you need answer for to show the initial screen, and they will all be * sent in a single pipeline, directly trailing the HELLO, their answers coming in as soon as possible after * the WELCOME. * * @type {FractionalMillis} */ this.sessionEstablishedOffsetMillis = sessionEstablishedOffsetMillis; /** * TraceId for the initiation - which follows through all parts of the processing (envelope's 'tid'). * * @type {string} */ this.traceId = traceId; /** * The message object that was sent with the initiation, i.e. on send(), request() or requestReplyTo() (outgoing envelope's 'msg'). * * @type {Object} */ this.initiationMessage = initiationMessage; /** * The experienced round-trip time for the Received Acknowledgement - this is the time back-and-forth. * * <b>Note that this number can be a float, not necessarily integer</b>. * * @type {FractionalMillis} */ this.acknowledgeRoundTripMillis = acknowledgeRoundTripMillis; // === For Requests. /** * The {@link MessageEventType} for Replies to Request Initiations. * * @type {string} */ this.replyMessageEventType = replyMessageEventType; /** * The 'replyToTerminatorId' for {@link MatsSocket#requestReplyTo()}-Requests. * * @type {string} */ this.replyToTerminatorId = replyToTerminatorId; /** * The experienced round-trip time from a Request initiation to the Reply (RESOLVE or REJECT) comes back. * * @type {FractionalMillis} */ this.requestReplyRoundTripMillis = requestRoundTripMillis; /** * The Reply {@link MessageEvent} that was supplied to the Promise (on resolve/then or reject/catch) or ReplyTo * Client {@link #terminator() Terminator}. * * @type {MessageEvent} */ this.replyMessageEvent = replyMessageEvent; } /** * Type of {@link InitiationProcessedEvent} - the type of the <i>initiation</i> of a flow, which also * determines which fields of the <code>InitiationProcessedEvent</code> are set. * * @enum {string} * @readonly */ const InitiationProcessedEventType = { /** * Flow initiated with {@link MatsSocket#send()}. Fields whose name does not start with "reply" or "request" * will be set. */ SEND: "send", /** * Flow initiated with {@link MatsSocket#request()}. Will have all fields except * {@link InitiationProcessedEvent#replyToTerminatorId} set. */ REQUEST: "request", /** * Flow initiated with {@link MatsSocket#requestReplyTo()}. Will have <i>all</i> fields set. */ REQUEST_REPLY_TO: "requestreplyto" }; Object.freeze(InitiationProcessedEventType); /** * (Metric) A "holding struct" for pings and their experienced round-trip times - you may "subscribe" to ping results * using {@link MatsSocket#addPingPongListener()}, and you may get the latest pings from the property * {@link MatsSocket#pings}. * * @param {number} pingId * @param {Timestamp} sentTimestamp * @class */ function PingPong(pingId, sentTimestamp) { /** * Sequence of the ping. * * @type {number} */ this.pingId = pingId; /** * Millis-from-epoch when this ping was sent. * * @type {Timestamp} */ this.sentTimestamp = sentTimestamp; /** * The experienced round-trip time for this ping-pong - this is the time back-and-forth. * * @type {FractionalMillis} */ this.roundTripMillis = undefined; } /** * <b>Copied directly from MatsSocketServer.java</b>: * WebSocket CloseCodes used in MatsSocket, and for what. Using both standard codes, and MatsSocket-specific/defined * codes. * <p/> * Note: Plural "Codes" since that is what the JSR 356 Java WebSocket API {@link CloseCodes does..!} * * @enum {number} * @readonly */ const MatsSocketCloseCodes = { /** * Standard code 1008 - From Server side, Client should REJECT all outstanding and "crash"/reboot application: * used when the we cannot authenticate. */ VIOLATED_POLICY: 1008, /** * Standard code 1011 - From Server side, Client should REJECT all outstanding and "crash"/reboot application. * This is the default close code if the MatsSocket "onMessage"-handler throws anything, and may also explicitly * be used by the implementation if it encounters a situation it cannot recover from. */ UNEXPECTED_CONDITION: 1011, /** * Standard code 1012 - From Server side, Client should REISSUE all outstanding upon reconnect: used when * {@link MatsSocketServer#stop(int)} is invoked. Please reconnect. */ SERVICE_RESTART: 1012, /** * Standard code 1001 - From Client/Browser side, client should have REJECTed all outstanding: Synonym for * {@link #CLOSE_SESSION}, as the WebSocket documentation states <i>"indicates that an endpoint is "going away", * such as a server going down <b>or a browser having navigated away from a page.</b>"</i>, the latter point * being pretty much exactly correct wrt. when to close a session. So, if a browser decides to use this code * when the user navigates away and the client MatsSocket library or employing application does not catch it, * we'd want to catch this as a Close Session. Notice that I've not experienced a browser that actually utilizes * this close code yet, though! * <p/> * <b>Notice that if a close with this close code <i>is initiated from the Server-side</i>, this should NOT be * considered a CLOSE_SESSION by the neither the client nor the server!</b> At least Jetty's implementation of * JSR 356 WebSocket API for Java sends GOING_AWAY upon socket close due to timeout. Since a timeout can happen * if we loose connection and thus can't convey PINGs, the MatsSocketServer must not interpret Jetty's * timeout-close as Close Session. Likewise, if the client just experienced massive lag on the connection, and * thus didn't get the PING over to the server in a timely fashion, but then suddenly gets Jetty's timeout close * with GOING_AWAY, this should not be interpreted by the client as the server wants to close the session. */ GOING_AWAY: 1001, /** * 4000: Both from Server side and Client/Browser side, client should REJECT all outstanding: * <ul> * <li>From Browser: Used when the browser closes WebSocket "on purpose", wanting to close the session - * typically when the user explicitly logs out, or navigates away from web page. All traces of the * MatsSocketSession are effectively deleted from the server, including any undelivered replies and messages * ("push") from server.</li> * <li>From Server: {@link MatsSocketServer#closeSession(String)} was invoked, and the WebSocket to that client * was still open, so we close it.</li> * </ul> */ CLOSE_SESSION: 4000, /** * 4001: From Server side, Client should REJECT all outstanding and "crash"/reboot application: A * HELLO:RECONNECT was attempted, but the session was gone. A considerable amount of time has probably gone by * since it last was connected. The client application must get its state synchronized with the server side's * view of the world, thus the suggestion of "reboot". */ SESSION_LOST: 4001, /** * 4002: Both from Server side and from Client/Browser side: REISSUE all outstanding upon reconnect: * <ul> * <li>From Client: The client just fancied a little break (just as if lost connection in a tunnel), used from * integration tests.</li> * <li>From Server: We ask that the client reconnects. This gets us a clean state and in particular new * authentication (In case of using OAuth/OIDC tokens, the client is expected to fetch a fresh token from token * server).</li> * </ul>