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