@webex/calling 3.12.0-mobius-socket.9 → 3.12.0-mobius-socket.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CallingClient/CallingClient.test.js +2 -2
- package/dist/CallingClient/CallingClient.test.js.map +1 -1
- package/dist/CallingClient/calling/call.test.js +1 -1
- package/dist/CallingClient/calling/call.test.js.map +1 -1
- package/dist/CallingClient/line/line.test.js +1 -1
- package/dist/CallingClient/line/line.test.js.map +1 -1
- package/dist/CallingClient/registration/register.test.js +1 -1
- package/dist/CallingClient/registration/register.test.js.map +1 -1
- package/dist/CallingClient/utils/request.js +2 -2
- package/dist/CallingClient/utils/request.js.map +1 -1
- package/dist/mobius-socket/config.js +61 -0
- package/dist/mobius-socket/config.js.map +1 -0
- package/dist/mobius-socket/errors.js +106 -0
- package/dist/mobius-socket/errors.js.map +1 -0
- package/dist/mobius-socket/index.js +101 -0
- package/dist/mobius-socket/index.js.map +1 -0
- package/dist/mobius-socket/mobius-socket-events.test.js +511 -0
- package/dist/mobius-socket/mobius-socket-events.test.js.map +1 -0
- package/dist/mobius-socket/mobius-socket.js +1191 -0
- package/dist/mobius-socket/mobius-socket.js.map +1 -0
- package/dist/mobius-socket/mobius-socket.test.js +2107 -0
- package/dist/mobius-socket/mobius-socket.test.js.map +1 -0
- package/dist/mobius-socket/socket/constants.js +20 -0
- package/dist/mobius-socket/socket/constants.js.map +1 -0
- package/dist/mobius-socket/socket/index.js +15 -0
- package/dist/mobius-socket/socket/index.js.map +1 -0
- package/dist/mobius-socket/socket/socket-base.js +632 -0
- package/dist/mobius-socket/socket/socket-base.js.map +1 -0
- package/dist/mobius-socket/socket/socket.js +19 -0
- package/dist/mobius-socket/socket/socket.js.map +1 -0
- package/dist/mobius-socket/socket/socket.shim.js +36 -0
- package/dist/mobius-socket/socket/socket.shim.js.map +1 -0
- package/dist/mobius-socket/socket.test.js +752 -0
- package/dist/mobius-socket/socket.test.js.map +1 -0
- package/dist/mobius-socket/test/mocha-helpers.js +23 -0
- package/dist/mobius-socket/test/mocha-helpers.js.map +1 -0
- package/dist/mobius-socket/test/promise-tick.js +29 -0
- package/dist/mobius-socket/test/promise-tick.js.map +1 -0
- package/dist/module/CallingClient/utils/request.js +1 -1
- package/dist/module/mobius-socket/config.js +18 -0
- package/dist/module/mobius-socket/errors.js +30 -0
- package/dist/module/mobius-socket/index.js +24 -0
- package/dist/module/mobius-socket/mobius-socket.js +761 -0
- package/dist/module/mobius-socket/socket/constants.js +10 -0
- package/dist/module/mobius-socket/socket/index.js +4 -0
- package/dist/module/mobius-socket/socket/socket-base.js +374 -0
- package/dist/module/mobius-socket/socket/socket.js +9 -0
- package/dist/module/mobius-socket/socket/socket.shim.js +24 -0
- package/dist/types/mobius-socket/config.d.ts +15 -0
- package/dist/types/mobius-socket/config.d.ts.map +1 -0
- package/dist/types/mobius-socket/errors.d.ts +13 -0
- package/dist/types/mobius-socket/errors.d.ts.map +1 -0
- package/dist/types/mobius-socket/index.d.ts +9 -0
- package/dist/types/mobius-socket/index.d.ts.map +1 -0
- package/dist/types/mobius-socket/mobius-socket.d.ts +62 -0
- package/dist/types/mobius-socket/mobius-socket.d.ts.map +1 -0
- package/dist/types/mobius-socket/socket/constants.d.ts +11 -0
- package/dist/types/mobius-socket/socket/constants.d.ts.map +1 -0
- package/dist/types/mobius-socket/socket/index.d.ts +2 -0
- package/dist/types/mobius-socket/socket/index.d.ts.map +1 -0
- package/dist/types/mobius-socket/socket/socket-base.d.ts +38 -0
- package/dist/types/mobius-socket/socket/socket-base.d.ts.map +1 -0
- package/dist/types/mobius-socket/socket/socket.d.ts +3 -0
- package/dist/types/mobius-socket/socket/socket.d.ts.map +1 -0
- package/dist/types/mobius-socket/socket/socket.shim.d.ts +3 -0
- package/dist/types/mobius-socket/socket/socket.shim.d.ts.map +1 -0
- package/package.json +17 -5
- package/src/mobius-socket/socket/socket.js +13 -0
- package/src/mobius-socket/socket/socket.shim.js +31 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
import { camelCase, set } from 'lodash';
|
|
6
|
+
import backoff from 'backoff';
|
|
7
|
+
import Socket from './socket';
|
|
8
|
+
import { BadRequest, Forbidden, NotAuthorized, UnknownResponse, } from './errors';
|
|
9
|
+
const normalReconnectReasons = ['idle', 'done (forced)'];
|
|
10
|
+
const DEFAULT_MOBIUS_WEBSOCKET_SESSION = 'mobius-websocket-session';
|
|
11
|
+
const MOBIUS_SOCKET_NAMESPACE = 'MobiusSocket';
|
|
12
|
+
const TOKEN_REFRESH_INTERVAL_MS = 1 * 60 * 60 * 1000;
|
|
13
|
+
function normalizeMobiusAuthToken(token) {
|
|
14
|
+
if (typeof token !== 'string') {
|
|
15
|
+
return token;
|
|
16
|
+
}
|
|
17
|
+
return token.replace(/^Bearer\s+/i, '');
|
|
18
|
+
}
|
|
19
|
+
class MobiusSocket extends EventEmitter {
|
|
20
|
+
constructor(webex, config = {}) {
|
|
21
|
+
super();
|
|
22
|
+
if (!webex) {
|
|
23
|
+
throw new Error('A Webex instance is required when initializing MobiusSocket');
|
|
24
|
+
}
|
|
25
|
+
this.webex = webex;
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.logger = webex.logger || console;
|
|
28
|
+
this.defaultSessionId = DEFAULT_MOBIUS_WEBSOCKET_SESSION;
|
|
29
|
+
this.connected = false;
|
|
30
|
+
this.connecting = false;
|
|
31
|
+
this.hasEverConnected = false;
|
|
32
|
+
this.socket = undefined;
|
|
33
|
+
this.sockets = new Map();
|
|
34
|
+
this.backoffCalls = new Map();
|
|
35
|
+
this._shutdownSwitchoverBackoffCalls = new Map();
|
|
36
|
+
this._seenAsyncEventIdsBySession = new Map();
|
|
37
|
+
this._connectPromises = new Map();
|
|
38
|
+
this.mercuryTimeOffset = undefined;
|
|
39
|
+
this._tokenRefreshTimer = undefined;
|
|
40
|
+
this._tokenRefreshInFlight = undefined;
|
|
41
|
+
this._bindInternalEvents();
|
|
42
|
+
}
|
|
43
|
+
off(eventName, listener) {
|
|
44
|
+
if (listener) {
|
|
45
|
+
return super.off(eventName, listener);
|
|
46
|
+
}
|
|
47
|
+
this.removeAllListeners(eventName);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
_bindInternalEvents() {
|
|
51
|
+
this.on('event:featureToggle_update', (envelope) => {
|
|
52
|
+
if (envelope && envelope.data) {
|
|
53
|
+
this.webex.internal.feature.updateFeature(envelope.data.featureToggle);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
this.on('event:ActiveClusterStatusEvent', (envelope) => {
|
|
57
|
+
if (typeof this.webex.internal.services?.switchActiveClusterIds === 'function' &&
|
|
58
|
+
envelope &&
|
|
59
|
+
envelope.data) {
|
|
60
|
+
this.webex.internal.services.switchActiveClusterIds(envelope.data?.activeClusters);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
this.on('event:u2c.cache-invalidation', (envelope) => {
|
|
64
|
+
if (typeof this.webex.internal.services?.invalidateCache === 'function' &&
|
|
65
|
+
envelope &&
|
|
66
|
+
envelope.data) {
|
|
67
|
+
this.webex.internal.services.invalidateCache(envelope.data?.timestamp);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
_attachSocketEventListeners(socket, sessionId) {
|
|
72
|
+
socket.on('close', (event) => this._onclose(sessionId, event, socket));
|
|
73
|
+
socket.on('message', (...args) => this._onmessage(sessionId, ...args));
|
|
74
|
+
socket.on('pong', (...args) => this._setTimeOffset(sessionId, ...args));
|
|
75
|
+
socket.on('sequence-mismatch', (...args) => this._emit(sessionId, 'sequence-mismatch', ...args));
|
|
76
|
+
socket.on('ping-pong-latency', (...args) => this._emit(sessionId, 'ping-pong-latency', ...args));
|
|
77
|
+
}
|
|
78
|
+
_getSeenAsyncEventIds(sessionId) {
|
|
79
|
+
let seenAsyncEventIds = this._seenAsyncEventIdsBySession.get(sessionId);
|
|
80
|
+
if (!seenAsyncEventIds) {
|
|
81
|
+
seenAsyncEventIds = new Map();
|
|
82
|
+
this._seenAsyncEventIdsBySession.set(sessionId, seenAsyncEventIds);
|
|
83
|
+
}
|
|
84
|
+
return seenAsyncEventIds;
|
|
85
|
+
}
|
|
86
|
+
_clearSeenAsyncEventIds(sessionId) {
|
|
87
|
+
if (sessionId) {
|
|
88
|
+
this._seenAsyncEventIdsBySession.delete(sessionId);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this._seenAsyncEventIdsBySession.clear();
|
|
92
|
+
}
|
|
93
|
+
_trackAsyncEventAndShouldSuppressDuplicate(sessionId, envelope) {
|
|
94
|
+
if (envelope?.type !== 'async_event' || !envelope.eventId) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const seenAsyncEventIds = this._getSeenAsyncEventIds(sessionId);
|
|
98
|
+
if (seenAsyncEventIds.has(envelope.eventId)) {
|
|
99
|
+
const previousValue = seenAsyncEventIds.get(envelope.eventId);
|
|
100
|
+
seenAsyncEventIds.delete(envelope.eventId);
|
|
101
|
+
seenAsyncEventIds.set(envelope.eventId, previousValue);
|
|
102
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: duplicate async_event suppressed for ${sessionId}, eventId=${envelope.eventId}`);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: tracking async_event for ${sessionId}, eventId=${envelope.eventId}`);
|
|
106
|
+
seenAsyncEventIds.set(envelope.eventId, true);
|
|
107
|
+
if (seenAsyncEventIds.size > this.config.dedupCacheMaxSize) {
|
|
108
|
+
const oldestEventId = seenAsyncEventIds.keys().next().value;
|
|
109
|
+
seenAsyncEventIds.delete(oldestEventId);
|
|
110
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: evicted oldest async_event from dedup cache for ${sessionId}, eventId=${oldestEventId}`);
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
_handleImminentShutdown(sessionId) {
|
|
115
|
+
const oldSocket = this.sockets.get(sessionId);
|
|
116
|
+
try {
|
|
117
|
+
if (this._shutdownSwitchoverBackoffCalls.get(sessionId)) {
|
|
118
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover already in progress for ${sessionId}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const switchoverId = `${Date.now()}`;
|
|
122
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover start, id=${switchoverId} for ${sessionId}`);
|
|
123
|
+
this._connectWithBackoff(undefined, sessionId, {
|
|
124
|
+
isShutdownSwitchover: true,
|
|
125
|
+
attemptOptions: {
|
|
126
|
+
isShutdownSwitchover: true,
|
|
127
|
+
onSuccess: (newSocket, webSocketUrl) => {
|
|
128
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover connected, url: ${webSocketUrl} for ${sessionId}`);
|
|
129
|
+
this.socket = this.sockets.get(this.defaultSessionId);
|
|
130
|
+
this.connected = this.hasConnectedSockets();
|
|
131
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_complete', {
|
|
132
|
+
url: webSocketUrl,
|
|
133
|
+
});
|
|
134
|
+
if (oldSocket) {
|
|
135
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] old socket retained; server will close with 4001`);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
.then(() => {
|
|
141
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover completed successfully for ${sessionId}`);
|
|
142
|
+
})
|
|
143
|
+
.catch((err) => {
|
|
144
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover exhausted retries; will fall back to normal reconnection for ${sessionId}: `, err);
|
|
145
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_failed', { reason: err });
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] error during switchover for ${sessionId}`, e);
|
|
150
|
+
this._shutdownSwitchoverBackoffCalls.delete(sessionId);
|
|
151
|
+
this._emit(sessionId, 'event:mercury_shutdown_switchover_failed', { reason: e });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
getLastError() {
|
|
155
|
+
return this.lastError;
|
|
156
|
+
}
|
|
157
|
+
getSockets() {
|
|
158
|
+
return this.sockets;
|
|
159
|
+
}
|
|
160
|
+
getSocket(sessionId = this.defaultSessionId) {
|
|
161
|
+
return this.sockets.get(sessionId);
|
|
162
|
+
}
|
|
163
|
+
getConnectedWebSocketUrl(sessionId = this.defaultSessionId) {
|
|
164
|
+
const socket = this.getSocket(sessionId);
|
|
165
|
+
if (!socket?.connected) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return socket.url;
|
|
169
|
+
}
|
|
170
|
+
send(payload, sessionId = this.defaultSessionId) {
|
|
171
|
+
const socket = this.getSocket(sessionId);
|
|
172
|
+
if (!socket || !socket.connected) {
|
|
173
|
+
return Promise.reject(new Error(`Mobius socket is not connected for session ${sessionId}`));
|
|
174
|
+
}
|
|
175
|
+
return socket.send(payload);
|
|
176
|
+
}
|
|
177
|
+
sendWssRequest(payload, sessionIdOrOptions = this.defaultSessionId, options = {}) {
|
|
178
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
179
|
+
return Promise.reject(new Error('`payload` is required'));
|
|
180
|
+
}
|
|
181
|
+
let sessionId = this.defaultSessionId;
|
|
182
|
+
let requestOptions = options;
|
|
183
|
+
if (typeof sessionIdOrOptions === 'string') {
|
|
184
|
+
sessionId = sessionIdOrOptions;
|
|
185
|
+
}
|
|
186
|
+
else if (sessionIdOrOptions && typeof sessionIdOrOptions === 'object') {
|
|
187
|
+
requestOptions = sessionIdOrOptions;
|
|
188
|
+
}
|
|
189
|
+
const socket = this.getSocket(sessionId);
|
|
190
|
+
if (!socket || !socket.connected) {
|
|
191
|
+
return Promise.reject(new Error(`Mobius socket is not connected for session ${sessionId}`));
|
|
192
|
+
}
|
|
193
|
+
return socket.sendRequest(payload, {
|
|
194
|
+
timeout: requestOptions.timeout,
|
|
195
|
+
matchesResponse: (response, request) => response?.type === 'response_event' &&
|
|
196
|
+
response?.subtype === request.type &&
|
|
197
|
+
response?.trackingId === request.trackingId,
|
|
198
|
+
getStatusCode: (response) => response?.statusCode,
|
|
199
|
+
getStatusMessage: (response) => response?.statusMessage,
|
|
200
|
+
createError: (response, statusCode, statusMessage) => this._createWssResponseError(response, statusCode, statusMessage),
|
|
201
|
+
createTimeoutError: (request) => this._createWssResponseError({
|
|
202
|
+
type: 'response_event',
|
|
203
|
+
subtype: request.type,
|
|
204
|
+
trackingId: request.trackingId,
|
|
205
|
+
}, 408, 'Mobius websocket response timed out'),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
isConnected() {
|
|
209
|
+
return this.connected;
|
|
210
|
+
}
|
|
211
|
+
hasConnectedSockets(sessionId) {
|
|
212
|
+
if (sessionId) {
|
|
213
|
+
return Boolean(this.sockets.get(sessionId)?.connected);
|
|
214
|
+
}
|
|
215
|
+
for (const socket of this.sockets.values()) {
|
|
216
|
+
if (socket?.connected) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
hasConnectingSockets(sessionId = this.defaultSessionId) {
|
|
223
|
+
const socket = this.sockets.get(sessionId || this.defaultSessionId);
|
|
224
|
+
return Boolean(socket?.connecting);
|
|
225
|
+
}
|
|
226
|
+
connect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
227
|
+
if (this._connectPromises.has(sessionId)) {
|
|
228
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connection ${sessionId} already in progress, returning existing promise`);
|
|
229
|
+
return this._connectPromises.get(sessionId);
|
|
230
|
+
}
|
|
231
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
232
|
+
if (sessionSocket?.connected || sessionSocket?.connecting) {
|
|
233
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connection ${sessionId} already connected, will not connect again`);
|
|
234
|
+
return Promise.resolve();
|
|
235
|
+
}
|
|
236
|
+
if (webSocketUrl && this.socketUrl && webSocketUrl !== this.socketUrl) {
|
|
237
|
+
this.hasEverConnected = false;
|
|
238
|
+
}
|
|
239
|
+
const resolvedUrl = webSocketUrl || this.socketUrl;
|
|
240
|
+
if (webSocketUrl) {
|
|
241
|
+
this.socketUrl = webSocketUrl;
|
|
242
|
+
}
|
|
243
|
+
this.connecting = true;
|
|
244
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: starting connection attempt for ${sessionId}${Number(this.config.initialConnectionMaxRetries) === 0 && !this.hasEverConnected
|
|
245
|
+
? ' (initial retries disabled)'
|
|
246
|
+
: ''}`);
|
|
247
|
+
const connectPromise = Promise.resolve(this.webex.internal.device.registered || this.webex.internal.device.register())
|
|
248
|
+
.then(() => {
|
|
249
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connecting ${sessionId}`);
|
|
250
|
+
return this._connectWithBackoff(resolvedUrl, sessionId);
|
|
251
|
+
})
|
|
252
|
+
.finally(() => {
|
|
253
|
+
this._connectPromises.delete(sessionId);
|
|
254
|
+
});
|
|
255
|
+
this._connectPromises.set(sessionId, connectPromise);
|
|
256
|
+
return connectPromise;
|
|
257
|
+
}
|
|
258
|
+
logout() {
|
|
259
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: logout() called`);
|
|
260
|
+
return this.disconnectAll(this.config.beforeLogoutOptionsCloseReason &&
|
|
261
|
+
!normalReconnectReasons.includes(this.config.beforeLogoutOptionsCloseReason)
|
|
262
|
+
? { code: 3050, reason: this.config.beforeLogoutOptionsCloseReason }
|
|
263
|
+
: undefined);
|
|
264
|
+
}
|
|
265
|
+
disconnect(options, sessionId = this.defaultSessionId) {
|
|
266
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}#disconnect: connecting state: ${this.connecting}, connected state: ${this.connected}, socket exists: ${!!this
|
|
267
|
+
.socket}, options: ${JSON.stringify(options)}`);
|
|
268
|
+
const backoffCall = this.backoffCalls.get(sessionId);
|
|
269
|
+
if (backoffCall) {
|
|
270
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: aborting connection ${sessionId}`);
|
|
271
|
+
backoffCall.abort();
|
|
272
|
+
this.backoffCalls.delete(sessionId);
|
|
273
|
+
}
|
|
274
|
+
const shutdownSwitchoverBackoffCall = this._shutdownSwitchoverBackoffCalls.get(sessionId);
|
|
275
|
+
if (shutdownSwitchoverBackoffCall) {
|
|
276
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: aborting shutdown switchover connection ${sessionId}`);
|
|
277
|
+
shutdownSwitchoverBackoffCall.abort();
|
|
278
|
+
this._shutdownSwitchoverBackoffCalls.delete(sessionId);
|
|
279
|
+
}
|
|
280
|
+
this._connectPromises.delete(sessionId);
|
|
281
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
282
|
+
this._clearSeenAsyncEventIds(sessionId);
|
|
283
|
+
if (!sessionSocket) {
|
|
284
|
+
this.connected = this.hasConnectedSockets();
|
|
285
|
+
if (!this.hasConnectedSockets()) {
|
|
286
|
+
this._stopTokenRefreshTimer();
|
|
287
|
+
}
|
|
288
|
+
return Promise.resolve();
|
|
289
|
+
}
|
|
290
|
+
sessionSocket.removeAllListeners('message');
|
|
291
|
+
sessionSocket.connecting = false;
|
|
292
|
+
sessionSocket.connected = false;
|
|
293
|
+
return Promise.resolve(sessionSocket.close(options || undefined)).finally(() => {
|
|
294
|
+
this.connected = this.hasConnectedSockets();
|
|
295
|
+
if (!this.hasConnectedSockets()) {
|
|
296
|
+
this._stopTokenRefreshTimer();
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
disconnectAll(options) {
|
|
301
|
+
const disconnectPromises = [];
|
|
302
|
+
for (const sessionId of this.sockets.keys()) {
|
|
303
|
+
disconnectPromises.push(this.disconnect(options, sessionId));
|
|
304
|
+
}
|
|
305
|
+
return Promise.all(disconnectPromises).then(() => {
|
|
306
|
+
this.connected = false;
|
|
307
|
+
this.socket = undefined;
|
|
308
|
+
this.sockets.clear();
|
|
309
|
+
this.backoffCalls.clear();
|
|
310
|
+
this._shutdownSwitchoverBackoffCalls.clear();
|
|
311
|
+
this._clearSeenAsyncEventIds();
|
|
312
|
+
this._stopTokenRefreshTimer();
|
|
313
|
+
this._connectPromises.clear();
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
processRegistrationStatusEvent(message) {
|
|
317
|
+
this.localClusterServiceUrls = message.localClusterServiceUrls;
|
|
318
|
+
}
|
|
319
|
+
_createWssResponseError(response, statusCode, statusMessage) {
|
|
320
|
+
const error = new Error(statusMessage || `Mobius websocket request failed with status ${statusCode || 'unknown'}`);
|
|
321
|
+
error.name = 'MobiusSocketResponseError';
|
|
322
|
+
error.statusCode = statusCode;
|
|
323
|
+
error.statusMessage = statusMessage;
|
|
324
|
+
error.response = response;
|
|
325
|
+
error.trackingId = response?.trackingId;
|
|
326
|
+
return error;
|
|
327
|
+
}
|
|
328
|
+
_applyOverrides(event) {
|
|
329
|
+
if (!event || !event.headers) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const headerKeys = Object.keys(event.headers);
|
|
333
|
+
headerKeys.forEach((keyPath) => {
|
|
334
|
+
set(event, keyPath, event.headers[keyPath]);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
_prepareUrl(webSocketUrl) {
|
|
338
|
+
if (!webSocketUrl) {
|
|
339
|
+
webSocketUrl = this.webex.internal.device.webSocketUrl;
|
|
340
|
+
}
|
|
341
|
+
return Promise.resolve(webSocketUrl);
|
|
342
|
+
}
|
|
343
|
+
_attemptConnection(socketUrl, sessionId, callback, options = {}) {
|
|
344
|
+
const { isShutdownSwitchover = false, onSuccess = null } = options;
|
|
345
|
+
const socket = new Socket();
|
|
346
|
+
socket.connecting = true;
|
|
347
|
+
let newWSUrl;
|
|
348
|
+
this._attachSocketEventListeners(socket, sessionId);
|
|
349
|
+
const backoffCall = isShutdownSwitchover
|
|
350
|
+
? this._shutdownSwitchoverBackoffCalls.get(sessionId)
|
|
351
|
+
: this.backoffCalls.get(sessionId);
|
|
352
|
+
if (!backoffCall) {
|
|
353
|
+
const mode = isShutdownSwitchover ? 'switchover backoff call' : 'backoffCall';
|
|
354
|
+
const msg = `${MOBIUS_SOCKET_NAMESPACE}: prevent socket open when ${mode} no longer defined for ${sessionId}`;
|
|
355
|
+
const err = new Error(msg);
|
|
356
|
+
this.logger.info(msg);
|
|
357
|
+
callback(err);
|
|
358
|
+
return Promise.reject(err);
|
|
359
|
+
}
|
|
360
|
+
if (!isShutdownSwitchover) {
|
|
361
|
+
this.sockets.set(sessionId, socket);
|
|
362
|
+
}
|
|
363
|
+
return this._prepareAndOpenSocket(socket, socketUrl, sessionId, isShutdownSwitchover)
|
|
364
|
+
.then((webSocketUrl) => {
|
|
365
|
+
newWSUrl = webSocketUrl;
|
|
366
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${isShutdownSwitchover ? '[shutdown] switchover' : ''} connected to mobius socket, success, action: connected for ${sessionId}, url: ${newWSUrl}`);
|
|
367
|
+
if (onSuccess) {
|
|
368
|
+
onSuccess(socket, webSocketUrl);
|
|
369
|
+
callback();
|
|
370
|
+
return Promise.resolve();
|
|
371
|
+
}
|
|
372
|
+
callback();
|
|
373
|
+
return Promise.resolve();
|
|
374
|
+
})
|
|
375
|
+
.catch((reason) => {
|
|
376
|
+
if (isShutdownSwitchover) {
|
|
377
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover attempt failed for ${sessionId}`, reason);
|
|
378
|
+
return callback(reason);
|
|
379
|
+
}
|
|
380
|
+
this.lastError = reason;
|
|
381
|
+
const backoffCallNormal = this.backoffCalls.get(sessionId);
|
|
382
|
+
if (reason.code !== 1006 && backoffCallNormal && backoffCallNormal?.getNumRetries() > 0) {
|
|
383
|
+
this._emit(sessionId, 'connection_failed', reason, {
|
|
384
|
+
sessionId,
|
|
385
|
+
retries: backoffCallNormal?.getNumRetries(),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connection attempt failed for ${sessionId}`, reason, backoffCallNormal?.getNumRetries() === 0 ? reason.stack : '');
|
|
389
|
+
if (reason instanceof UnknownResponse) {
|
|
390
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: received unknown response code for ${sessionId}, refreshing device registration`);
|
|
391
|
+
return this.webex.internal.device.refresh().then(() => callback(reason));
|
|
392
|
+
}
|
|
393
|
+
if (reason instanceof NotAuthorized) {
|
|
394
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: received authorization error for ${sessionId}, reauthorizing`);
|
|
395
|
+
return this.webex.credentials.refresh({ force: true }).then(() => callback(reason));
|
|
396
|
+
}
|
|
397
|
+
if (reason instanceof BadRequest || reason instanceof Forbidden) {
|
|
398
|
+
this.logger.warn(`${MOBIUS_SOCKET_NAMESPACE}: received unrecoverable response from ${MOBIUS_SOCKET_NAMESPACE} for ${sessionId}`);
|
|
399
|
+
backoffCallNormal?.abort();
|
|
400
|
+
return callback(reason);
|
|
401
|
+
}
|
|
402
|
+
return callback(reason);
|
|
403
|
+
})
|
|
404
|
+
.catch((reason) => {
|
|
405
|
+
this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: failed to handle connection failure for ${sessionId}`, reason);
|
|
406
|
+
callback(reason);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
_prepareAndOpenSocket(socket, socketUrl, sessionId, isShutdownSwitchover = false) {
|
|
410
|
+
const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
|
|
411
|
+
return Promise.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()]).then(([webSocketUrl, token]) => {
|
|
412
|
+
let options = {
|
|
413
|
+
forceCloseDelay: this.config.forceCloseDelay,
|
|
414
|
+
wssResponseTimeout: this.config.wssResponseTimeout,
|
|
415
|
+
skipAckEventId: this.config.skipAckEventId,
|
|
416
|
+
skipAckEventType: this.config.skipAckEventType,
|
|
417
|
+
token: normalizeMobiusAuthToken(token.toString()),
|
|
418
|
+
refreshToken: () => this._refreshToken(),
|
|
419
|
+
trackingId: `${this.webex.sessionId}_${Date.now()}`,
|
|
420
|
+
logger: this.logger,
|
|
421
|
+
};
|
|
422
|
+
if (this.webex.config.defaultMobiusSocketOptions) {
|
|
423
|
+
const customOptionsMsg = isShutdownSwitchover
|
|
424
|
+
? 'setting custom options for switchover'
|
|
425
|
+
: 'setting custom options';
|
|
426
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${customOptionsMsg}`);
|
|
427
|
+
options = { ...options, ...this.webex.config.defaultMobiusSocketOptions };
|
|
428
|
+
}
|
|
429
|
+
this.sockets.set(sessionId, socket);
|
|
430
|
+
this.socket = this.sockets.get(this.defaultSessionId);
|
|
431
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE} ${logPrefix} url for ${sessionId}: ${webSocketUrl}`);
|
|
432
|
+
return socket.open(webSocketUrl, options).then(() => webSocketUrl);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
_connectWithBackoff(webSocketUrl, sessionId, context = {}) {
|
|
436
|
+
const { isShutdownSwitchover = false, attemptOptions = {} } = context;
|
|
437
|
+
return new Promise((resolve, reject) => {
|
|
438
|
+
let call;
|
|
439
|
+
const isInitialConnect = !isShutdownSwitchover && !this.hasEverConnected;
|
|
440
|
+
const initialRetryLimit = this.config.initialConnectionMaxRetries == null
|
|
441
|
+
? null
|
|
442
|
+
: Number(this.config.initialConnectionMaxRetries);
|
|
443
|
+
const isInitialConnectWithoutRetries = isInitialConnect && initialRetryLimit === 0;
|
|
444
|
+
const onComplete = (err, sid = sessionId) => {
|
|
445
|
+
if (isShutdownSwitchover) {
|
|
446
|
+
this._shutdownSwitchoverBackoffCalls.delete(sid);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
this.backoffCalls.delete(sid);
|
|
450
|
+
}
|
|
451
|
+
const sessionSocket = this.sockets.get(sid);
|
|
452
|
+
if (err) {
|
|
453
|
+
const msg = isShutdownSwitchover
|
|
454
|
+
? `[shutdown] switchover failed after ${call.getNumRetries()} retries`
|
|
455
|
+
: `failed to connect after ${call.getNumRetries()} retries`;
|
|
456
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${msg}; log statement about next retry was inaccurate; ${err}`);
|
|
457
|
+
if (sessionSocket) {
|
|
458
|
+
sessionSocket.connecting = false;
|
|
459
|
+
sessionSocket.connected = false;
|
|
460
|
+
}
|
|
461
|
+
return reject(err);
|
|
462
|
+
}
|
|
463
|
+
if (sessionSocket) {
|
|
464
|
+
sessionSocket.connecting = false;
|
|
465
|
+
sessionSocket.connected = true;
|
|
466
|
+
}
|
|
467
|
+
if (!isShutdownSwitchover) {
|
|
468
|
+
this.connecting = this.hasConnectingSockets();
|
|
469
|
+
this.connected = this.hasConnectedSockets();
|
|
470
|
+
this.hasEverConnected = true;
|
|
471
|
+
this._startTokenRefreshTimer();
|
|
472
|
+
this._emit(sid, 'online');
|
|
473
|
+
}
|
|
474
|
+
return resolve();
|
|
475
|
+
};
|
|
476
|
+
call = backoff.call((callback) => {
|
|
477
|
+
const attemptNum = call.getNumRetries();
|
|
478
|
+
const attemptLogPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
|
|
479
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: executing ${attemptLogPrefix} attempt ${attemptNum} for ${sessionId}`);
|
|
480
|
+
this._attemptConnection(webSocketUrl, sessionId, callback, attemptOptions);
|
|
481
|
+
}, (err) => onComplete(err, sessionId));
|
|
482
|
+
call.setStrategy(new backoff.ExponentialStrategy({
|
|
483
|
+
initialDelay: this.config.backoffTimeReset,
|
|
484
|
+
maxDelay: this.config.backoffTimeMax,
|
|
485
|
+
}));
|
|
486
|
+
if (isInitialConnectWithoutRetries) {
|
|
487
|
+
call.retryIf(() => false);
|
|
488
|
+
}
|
|
489
|
+
else if (isInitialConnect && initialRetryLimit > 0) {
|
|
490
|
+
call.failAfter(initialRetryLimit);
|
|
491
|
+
}
|
|
492
|
+
else if (this.config.maxRetries) {
|
|
493
|
+
call.failAfter(this.config.maxRetries);
|
|
494
|
+
}
|
|
495
|
+
if (isShutdownSwitchover) {
|
|
496
|
+
this._shutdownSwitchoverBackoffCalls.set(sessionId, call);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
this.backoffCalls.set(sessionId, call);
|
|
500
|
+
}
|
|
501
|
+
call.on('abort', () => {
|
|
502
|
+
const msg = isShutdownSwitchover ? 'Shutdown Switchover' : 'Connection';
|
|
503
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${msg} aborted for ${sessionId}`);
|
|
504
|
+
reject(new Error(`MobiusSocket ${msg} Aborted for ${sessionId}`));
|
|
505
|
+
});
|
|
506
|
+
call.on('callback', (err) => {
|
|
507
|
+
if (err) {
|
|
508
|
+
if (isInitialConnectWithoutRetries) {
|
|
509
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: initial connect failed for ${sessionId}; retries already disabled`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const number = call.getNumRetries();
|
|
513
|
+
const delay = Math.min(call.strategy_.nextBackoffDelay_, this.config.backoffTimeMax);
|
|
514
|
+
const callbackLogPrefix = isShutdownSwitchover ? '[shutdown] switchover' : '';
|
|
515
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${callbackLogPrefix} failed to connect; attempting retry ${number + 1} in ${delay} ms for ${sessionId}`);
|
|
516
|
+
if (process.env.NODE_ENV === 'development') {
|
|
517
|
+
this.logger.debug(`${MOBIUS_SOCKET_NAMESPACE}: `, err, err.stack);
|
|
518
|
+
}
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connected ${sessionId}`);
|
|
522
|
+
});
|
|
523
|
+
call.start();
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
_emit(sessionId, eventName, ...args) {
|
|
527
|
+
try {
|
|
528
|
+
if (!sessionId || !eventName) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const suffix = sessionId === this.defaultSessionId ? '' : `:${sessionId}`;
|
|
532
|
+
this.emit(`${eventName}${suffix}`, ...args);
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
try {
|
|
536
|
+
this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: error occurred in event handler:`, error, ' with args: ', [sessionId, eventName, ...args]);
|
|
537
|
+
}
|
|
538
|
+
catch (logError) {
|
|
539
|
+
console.error('MobiusSocket _emit error handling failed:', logError);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
_getEventHandlers(eventType) {
|
|
544
|
+
if (!eventType) {
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
const [namespace, name] = eventType.split('.');
|
|
548
|
+
const handlers = [];
|
|
549
|
+
if (!this.webex[namespace] && !this.webex.internal[namespace]) {
|
|
550
|
+
return handlers;
|
|
551
|
+
}
|
|
552
|
+
const handlerName = camelCase(`process_${name}_event`);
|
|
553
|
+
if ((this.webex[namespace] || this.webex.internal[namespace])[handlerName]) {
|
|
554
|
+
handlers.push({
|
|
555
|
+
name: handlerName,
|
|
556
|
+
namespace,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return handlers;
|
|
560
|
+
}
|
|
561
|
+
_startTokenRefreshTimer() {
|
|
562
|
+
if (this._tokenRefreshTimer || !this.hasConnectedSockets()) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
this._tokenRefreshTimer = setInterval(() => {
|
|
566
|
+
this._refreshToken().catch((error) => {
|
|
567
|
+
this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: periodic token refresh failed`, error);
|
|
568
|
+
});
|
|
569
|
+
}, TOKEN_REFRESH_INTERVAL_MS);
|
|
570
|
+
}
|
|
571
|
+
_stopTokenRefreshTimer() {
|
|
572
|
+
if (!this._tokenRefreshTimer) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
clearInterval(this._tokenRefreshTimer);
|
|
576
|
+
this._tokenRefreshTimer = undefined;
|
|
577
|
+
}
|
|
578
|
+
_refreshToken() {
|
|
579
|
+
if (this._tokenRefreshInFlight) {
|
|
580
|
+
return this._tokenRefreshInFlight;
|
|
581
|
+
}
|
|
582
|
+
if (!this.hasConnectedSockets()) {
|
|
583
|
+
this._stopTokenRefreshTimer();
|
|
584
|
+
return Promise.resolve();
|
|
585
|
+
}
|
|
586
|
+
const tokenPromise = this.webex.credentials.canRefresh
|
|
587
|
+
? this.webex.credentials
|
|
588
|
+
.refresh({ force: true })
|
|
589
|
+
.then(() => this.webex.credentials.getUserToken())
|
|
590
|
+
: this.webex.credentials.getUserToken();
|
|
591
|
+
this._tokenRefreshInFlight = tokenPromise
|
|
592
|
+
.then((token) => {
|
|
593
|
+
if (!token) {
|
|
594
|
+
throw new Error('Mobius token refresh did not return a token');
|
|
595
|
+
}
|
|
596
|
+
const refreshedToken = normalizeMobiusAuthToken(token.toString());
|
|
597
|
+
const authPayloadPromises = [];
|
|
598
|
+
for (const socket of this.sockets.values()) {
|
|
599
|
+
if (socket?.connected) {
|
|
600
|
+
authPayloadPromises.push(socket.refresh(refreshedToken));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return Promise.all(authPayloadPromises);
|
|
604
|
+
})
|
|
605
|
+
.catch((error) => {
|
|
606
|
+
this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: failed to refresh/re-auth Mobius sockets`, error);
|
|
607
|
+
throw error;
|
|
608
|
+
})
|
|
609
|
+
.finally(() => {
|
|
610
|
+
this._tokenRefreshInFlight = undefined;
|
|
611
|
+
});
|
|
612
|
+
return this._tokenRefreshInFlight;
|
|
613
|
+
}
|
|
614
|
+
_onclose(sessionId, event, sourceSocket) {
|
|
615
|
+
try {
|
|
616
|
+
const reason = event.reason && event.reason.toLowerCase();
|
|
617
|
+
const sessionSocket = this.sockets.get(sessionId);
|
|
618
|
+
let socketUrl;
|
|
619
|
+
event.sessionId = sessionId;
|
|
620
|
+
const isActiveSocket = sourceSocket === sessionSocket;
|
|
621
|
+
if (sourceSocket) {
|
|
622
|
+
socketUrl = sourceSocket.url;
|
|
623
|
+
}
|
|
624
|
+
this.sockets.delete(sessionId);
|
|
625
|
+
if (isActiveSocket) {
|
|
626
|
+
if (sessionSocket) {
|
|
627
|
+
sessionSocket.removeAllListeners();
|
|
628
|
+
if (sessionId === this.defaultSessionId) {
|
|
629
|
+
this.socket = undefined;
|
|
630
|
+
}
|
|
631
|
+
this._emit(sessionId, 'offline', event);
|
|
632
|
+
}
|
|
633
|
+
this.connecting = this.hasConnectingSockets();
|
|
634
|
+
this.connected = this.hasConnectedSockets();
|
|
635
|
+
if (!this.hasConnectedSockets()) {
|
|
636
|
+
this._stopTokenRefreshTimer();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] non-active socket closed, code=${event.code} for ${sessionId}`);
|
|
641
|
+
if (sourceSocket) {
|
|
642
|
+
sourceSocket.removeAllListeners();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
switch (event.code) {
|
|
646
|
+
case 1003:
|
|
647
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: service rejected last message for ${sessionId}; will not reconnect: ${event.reason}`);
|
|
648
|
+
if (isActiveSocket)
|
|
649
|
+
this._emit(sessionId, 'offline.permanent', event);
|
|
650
|
+
break;
|
|
651
|
+
case 4000:
|
|
652
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: socket ${sessionId} replaced; will not reconnect`);
|
|
653
|
+
if (isActiveSocket)
|
|
654
|
+
this._emit(sessionId, 'offline.replaced', event);
|
|
655
|
+
break;
|
|
656
|
+
case 4001:
|
|
657
|
+
if (isActiveSocket) {
|
|
658
|
+
this.logger.warn(`${MOBIUS_SOCKET_NAMESPACE}: active socket closed with 4001; shutdown switchover failed for ${sessionId}`);
|
|
659
|
+
this._emit(sessionId, 'offline.permanent', event);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: old socket closed with 4001 (replaced during shutdown); no reconnect needed for ${sessionId}`);
|
|
663
|
+
this._emit(sessionId, 'offline.replaced', event);
|
|
664
|
+
}
|
|
665
|
+
break;
|
|
666
|
+
case 1001:
|
|
667
|
+
case 1005:
|
|
668
|
+
case 1006:
|
|
669
|
+
case 1011:
|
|
670
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: socket ${sessionId} disconnected; reconnecting`);
|
|
671
|
+
if (isActiveSocket) {
|
|
672
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
673
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] reconnecting active socket to recover for ${sessionId}`);
|
|
674
|
+
this._reconnect(socketUrl, sessionId);
|
|
675
|
+
}
|
|
676
|
+
break;
|
|
677
|
+
case 1000:
|
|
678
|
+
case 3050:
|
|
679
|
+
if (normalReconnectReasons.includes(reason)) {
|
|
680
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: socket ${sessionId} disconnected; reconnecting`);
|
|
681
|
+
if (isActiveSocket) {
|
|
682
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
683
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] reconnecting due to normal close for ${sessionId}`);
|
|
684
|
+
this._reconnect(socketUrl, sessionId);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: socket ${sessionId} disconnected; will not reconnect: ${event.reason}`);
|
|
689
|
+
if (isActiveSocket)
|
|
690
|
+
this._emit(sessionId, 'offline.permanent', event);
|
|
691
|
+
}
|
|
692
|
+
break;
|
|
693
|
+
default:
|
|
694
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: socket ${sessionId} disconnected unexpectedly; will not reconnect`);
|
|
695
|
+
if (isActiveSocket)
|
|
696
|
+
this._emit(sessionId, 'offline.permanent', event);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: error occurred in close handler for ${sessionId}`, error);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
_onmessage(sessionId, event) {
|
|
704
|
+
this._setTimeOffset(sessionId, event);
|
|
705
|
+
const envelope = event.data;
|
|
706
|
+
if (process.env.ENABLE_MERCURY_LOGGING) {
|
|
707
|
+
this.logger.debug(`${MOBIUS_SOCKET_NAMESPACE}: message envelope from ${sessionId}: `, envelope);
|
|
708
|
+
}
|
|
709
|
+
envelope.sessionId = sessionId;
|
|
710
|
+
if (envelope && envelope.type === 'shutdown') {
|
|
711
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] imminent shutdown message received for ${sessionId}`);
|
|
712
|
+
this._emit(sessionId, 'event:mercury_shutdown_imminent', envelope);
|
|
713
|
+
this._handleImminentShutdown(sessionId);
|
|
714
|
+
return Promise.resolve();
|
|
715
|
+
}
|
|
716
|
+
if (this._trackAsyncEventAndShouldSuppressDuplicate(sessionId, envelope)) {
|
|
717
|
+
return Promise.resolve();
|
|
718
|
+
}
|
|
719
|
+
if (envelope.type) {
|
|
720
|
+
this._emit(sessionId, `event:${envelope.type}`, envelope);
|
|
721
|
+
}
|
|
722
|
+
envelope.sessionId = sessionId;
|
|
723
|
+
const data = envelope.data || envelope;
|
|
724
|
+
this._applyOverrides(data);
|
|
725
|
+
const eventType = data?.eventType || envelope.eventType;
|
|
726
|
+
if (!eventType) {
|
|
727
|
+
this._emit(sessionId, 'event', envelope);
|
|
728
|
+
return Promise.resolve();
|
|
729
|
+
}
|
|
730
|
+
return this._getEventHandlers(eventType)
|
|
731
|
+
.reduce((promise, handler) => promise.then(() => {
|
|
732
|
+
const { namespace, name } = handler;
|
|
733
|
+
return new Promise((resolve) => resolve((this.webex[namespace] || this.webex.internal[namespace])[name](data))).catch((reason) => this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: error occurred in autowired event handler for ${eventType} from ${sessionId}`, reason));
|
|
734
|
+
}), Promise.resolve())
|
|
735
|
+
.then(() => {
|
|
736
|
+
this._emit(sessionId, 'event', envelope);
|
|
737
|
+
const [namespace] = eventType.split('.');
|
|
738
|
+
if (namespace === eventType) {
|
|
739
|
+
this._emit(sessionId, `event:${namespace}`, envelope);
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
this._emit(sessionId, `event:${namespace}`, envelope);
|
|
743
|
+
this._emit(sessionId, `event:${eventType}`, envelope);
|
|
744
|
+
}
|
|
745
|
+
})
|
|
746
|
+
.catch((reason) => {
|
|
747
|
+
this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: error occurred processing socket message from ${sessionId}`, reason);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
_setTimeOffset(sessionId, event) {
|
|
751
|
+
const { wsWriteTimestamp } = event.data;
|
|
752
|
+
if (typeof wsWriteTimestamp === 'number' && wsWriteTimestamp > 0) {
|
|
753
|
+
this.mercuryTimeOffset = Date.now() - wsWriteTimestamp;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
_reconnect(webSocketUrl, sessionId = this.defaultSessionId) {
|
|
757
|
+
this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: reconnecting ${sessionId}`);
|
|
758
|
+
return this.connect(webSocketUrl || this.socketUrl, sessionId);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
export default MobiusSocket;
|