matssocket
Version:
MatsSocket client library
1,039 lines (935 loc) • 251 kB
JavaScript
(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>