@webex/calling 3.12.0-next.4 → 3.12.0-next.41

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.
Files changed (204) hide show
  1. package/dist/CallingClient/CallingClient.js +463 -205
  2. package/dist/CallingClient/CallingClient.js.map +1 -1
  3. package/dist/CallingClient/CallingClient.test.js +170 -77
  4. package/dist/CallingClient/CallingClient.test.js.map +1 -1
  5. package/dist/CallingClient/calling/call.js +589 -445
  6. package/dist/CallingClient/calling/call.js.map +1 -1
  7. package/dist/CallingClient/calling/call.test.js +695 -445
  8. package/dist/CallingClient/calling/call.test.js.map +1 -1
  9. package/dist/CallingClient/calling/callManager.js +53 -30
  10. package/dist/CallingClient/calling/callManager.js.map +1 -1
  11. package/dist/CallingClient/calling/callManager.test.js +35 -0
  12. package/dist/CallingClient/calling/callManager.test.js.map +1 -1
  13. package/dist/CallingClient/calling/types.js +2 -0
  14. package/dist/CallingClient/calling/types.js.map +1 -1
  15. package/dist/CallingClient/constants.js +25 -3
  16. package/dist/CallingClient/constants.js.map +1 -1
  17. package/dist/CallingClient/line/index.js +4 -1
  18. package/dist/CallingClient/line/index.js.map +1 -1
  19. package/dist/CallingClient/line/line.test.js +16 -1
  20. package/dist/CallingClient/line/line.test.js.map +1 -1
  21. package/dist/CallingClient/registration/register.js +712 -406
  22. package/dist/CallingClient/registration/register.js.map +1 -1
  23. package/dist/CallingClient/registration/register.test.js +202 -21
  24. package/dist/CallingClient/registration/register.test.js.map +1 -1
  25. package/dist/CallingClient/registration/types.js.map +1 -1
  26. package/dist/CallingClient/registration/webWorker.js +41 -104
  27. package/dist/CallingClient/registration/webWorker.js.map +1 -1
  28. package/dist/CallingClient/registration/webWorker.test.js +39 -153
  29. package/dist/CallingClient/registration/webWorker.test.js.map +1 -1
  30. package/dist/CallingClient/registration/webWorkerStr.js +1 -1
  31. package/dist/CallingClient/registration/webWorkerStr.js.map +1 -1
  32. package/dist/CallingClient/utils/constants.js +46 -0
  33. package/dist/CallingClient/utils/constants.js.map +1 -0
  34. package/dist/CallingClient/utils/index.js +63 -0
  35. package/dist/CallingClient/utils/index.js.map +1 -0
  36. package/dist/CallingClient/utils/mobiusSocketMapper.js +122 -0
  37. package/dist/CallingClient/utils/mobiusSocketMapper.js.map +1 -0
  38. package/dist/CallingClient/utils/mobiusSocketMapper.test.js +211 -0
  39. package/dist/CallingClient/utils/mobiusSocketMapper.test.js.map +1 -0
  40. package/dist/CallingClient/utils/request.js +354 -0
  41. package/dist/CallingClient/utils/request.js.map +1 -0
  42. package/dist/CallingClient/utils/request.test.js +881 -0
  43. package/dist/CallingClient/utils/request.test.js.map +1 -0
  44. package/dist/CallingClient/utils/types.js +7 -0
  45. package/dist/CallingClient/utils/types.js.map +1 -0
  46. package/dist/CallingClient/utils/wsFeatureFlag.js +70 -0
  47. package/dist/CallingClient/utils/wsFeatureFlag.js.map +1 -0
  48. package/dist/CallingClient/utils/wsFeatureFlag.test.js +139 -0
  49. package/dist/CallingClient/utils/wsFeatureFlag.test.js.map +1 -0
  50. package/dist/Contacts/ContactsClient.js +13 -11
  51. package/dist/Contacts/ContactsClient.js.map +1 -1
  52. package/dist/Contacts/ContactsClient.test.js +3 -8
  53. package/dist/Contacts/ContactsClient.test.js.map +1 -1
  54. package/dist/Events/types.js +1 -11
  55. package/dist/Events/types.js.map +1 -1
  56. package/dist/Metrics/index.js +63 -2
  57. package/dist/Metrics/index.js.map +1 -1
  58. package/dist/Metrics/index.test.js +357 -1
  59. package/dist/Metrics/index.test.js.map +1 -1
  60. package/dist/Metrics/types.js +19 -1
  61. package/dist/Metrics/types.js.map +1 -1
  62. package/dist/SDKConnector/types.js.map +1 -1
  63. package/dist/common/Utils.js +138 -44
  64. package/dist/common/Utils.js.map +1 -1
  65. package/dist/common/testUtil.js +8 -4
  66. package/dist/common/testUtil.js.map +1 -1
  67. package/dist/common/types.js +2 -0
  68. package/dist/common/types.js.map +1 -1
  69. package/dist/mobius-socket/config.js +24 -0
  70. package/dist/mobius-socket/config.js.map +1 -0
  71. package/dist/mobius-socket/errors.js +143 -0
  72. package/dist/mobius-socket/errors.js.map +1 -0
  73. package/dist/mobius-socket/errors.test.js +20 -0
  74. package/dist/mobius-socket/errors.test.js.map +1 -0
  75. package/dist/mobius-socket/index.js +57 -0
  76. package/dist/mobius-socket/index.js.map +1 -0
  77. package/dist/mobius-socket/mobius-socket-events.test.js +492 -0
  78. package/dist/mobius-socket/mobius-socket-events.test.js.map +1 -0
  79. package/dist/mobius-socket/mobius-socket.js +841 -0
  80. package/dist/mobius-socket/mobius-socket.js.map +1 -0
  81. package/dist/mobius-socket/mobius-socket.test.js +1845 -0
  82. package/dist/mobius-socket/mobius-socket.test.js.map +1 -0
  83. package/dist/mobius-socket/socket/constants.js +55 -0
  84. package/dist/mobius-socket/socket/constants.js.map +1 -0
  85. package/dist/mobius-socket/socket/index.js +15 -0
  86. package/dist/mobius-socket/socket/index.js.map +1 -0
  87. package/dist/mobius-socket/socket/socket-base.js +604 -0
  88. package/dist/mobius-socket/socket/socket-base.js.map +1 -0
  89. package/dist/mobius-socket/socket/socket.js +19 -0
  90. package/dist/mobius-socket/socket/socket.js.map +1 -0
  91. package/dist/mobius-socket/socket/socket.shim.js +26 -0
  92. package/dist/mobius-socket/socket/socket.shim.js.map +1 -0
  93. package/dist/mobius-socket/socket/types.js +7 -0
  94. package/dist/mobius-socket/socket/types.js.map +1 -0
  95. package/dist/mobius-socket/socket.test.js +727 -0
  96. package/dist/mobius-socket/socket.test.js.map +1 -0
  97. package/dist/mobius-socket/test/mocha-helpers.js +23 -0
  98. package/dist/mobius-socket/test/mocha-helpers.js.map +1 -0
  99. package/dist/mobius-socket/test/promise-tick.js +28 -0
  100. package/dist/mobius-socket/test/promise-tick.js.map +1 -0
  101. package/dist/mobius-socket/types.js +7 -0
  102. package/dist/mobius-socket/types.js.map +1 -0
  103. package/dist/module/CallingClient/CallingClient.js +141 -10
  104. package/dist/module/CallingClient/calling/call.js +177 -61
  105. package/dist/module/CallingClient/calling/callManager.js +27 -7
  106. package/dist/module/CallingClient/calling/types.js +2 -0
  107. package/dist/module/CallingClient/constants.js +21 -0
  108. package/dist/module/CallingClient/line/index.js +2 -2
  109. package/dist/module/CallingClient/registration/register.js +234 -62
  110. package/dist/module/CallingClient/registration/webWorker.js +42 -61
  111. package/dist/module/CallingClient/registration/webWorkerStr.js +47 -82
  112. package/dist/module/CallingClient/utils/constants.js +30 -0
  113. package/dist/module/CallingClient/utils/index.js +5 -0
  114. package/dist/module/CallingClient/utils/mobiusSocketMapper.js +72 -0
  115. package/dist/module/CallingClient/utils/request.js +165 -0
  116. package/dist/module/CallingClient/utils/types.js +1 -0
  117. package/dist/module/CallingClient/utils/wsFeatureFlag.js +41 -0
  118. package/dist/module/Contacts/ContactsClient.js +1 -1
  119. package/dist/module/Events/types.js +0 -10
  120. package/dist/module/Metrics/index.js +48 -1
  121. package/dist/module/Metrics/types.js +18 -0
  122. package/dist/module/common/Utils.js +52 -12
  123. package/dist/module/common/testUtil.js +5 -1
  124. package/dist/module/common/types.js +2 -0
  125. package/dist/module/mobius-socket/config.js +15 -0
  126. package/dist/module/mobius-socket/errors.js +58 -0
  127. package/dist/module/mobius-socket/index.js +24 -0
  128. package/dist/module/mobius-socket/mobius-socket.js +589 -0
  129. package/dist/module/mobius-socket/socket/constants.js +26 -0
  130. package/dist/module/mobius-socket/socket/index.js +4 -0
  131. package/dist/module/mobius-socket/socket/socket-base.js +368 -0
  132. package/dist/module/mobius-socket/socket/socket.js +9 -0
  133. package/dist/module/mobius-socket/socket/socket.shim.js +12 -0
  134. package/dist/module/mobius-socket/socket/types.js +1 -0
  135. package/dist/module/mobius-socket/types.js +1 -0
  136. package/dist/types/CallingClient/CallingClient.d.ts +7 -0
  137. package/dist/types/CallingClient/CallingClient.d.ts.map +1 -1
  138. package/dist/types/CallingClient/calling/call.d.ts +12 -0
  139. package/dist/types/CallingClient/calling/call.d.ts.map +1 -1
  140. package/dist/types/CallingClient/calling/callManager.d.ts +3 -2
  141. package/dist/types/CallingClient/calling/callManager.d.ts.map +1 -1
  142. package/dist/types/CallingClient/calling/types.d.ts +34 -9
  143. package/dist/types/CallingClient/calling/types.d.ts.map +1 -1
  144. package/dist/types/CallingClient/constants.d.ts +21 -0
  145. package/dist/types/CallingClient/constants.d.ts.map +1 -1
  146. package/dist/types/CallingClient/line/index.d.ts +1 -1
  147. package/dist/types/CallingClient/line/index.d.ts.map +1 -1
  148. package/dist/types/CallingClient/registration/register.d.ts +7 -1
  149. package/dist/types/CallingClient/registration/register.d.ts.map +1 -1
  150. package/dist/types/CallingClient/registration/types.d.ts +4 -2
  151. package/dist/types/CallingClient/registration/types.d.ts.map +1 -1
  152. package/dist/types/CallingClient/registration/webWorker.d.ts.map +1 -1
  153. package/dist/types/CallingClient/registration/webWorkerStr.d.ts +1 -1
  154. package/dist/types/CallingClient/registration/webWorkerStr.d.ts.map +1 -1
  155. package/dist/types/CallingClient/utils/constants.d.ts +30 -0
  156. package/dist/types/CallingClient/utils/constants.d.ts.map +1 -0
  157. package/dist/types/CallingClient/utils/index.d.ts +6 -0
  158. package/dist/types/CallingClient/utils/index.d.ts.map +1 -0
  159. package/dist/types/CallingClient/utils/mobiusSocketMapper.d.ts +5 -0
  160. package/dist/types/CallingClient/utils/mobiusSocketMapper.d.ts.map +1 -0
  161. package/dist/types/CallingClient/utils/request.d.ts +24 -0
  162. package/dist/types/CallingClient/utils/request.d.ts.map +1 -0
  163. package/dist/types/CallingClient/utils/types.d.ts +29 -0
  164. package/dist/types/CallingClient/utils/types.d.ts.map +1 -0
  165. package/dist/types/CallingClient/utils/wsFeatureFlag.d.ts +4 -0
  166. package/dist/types/CallingClient/utils/wsFeatureFlag.d.ts.map +1 -0
  167. package/dist/types/Events/types.d.ts +4 -11
  168. package/dist/types/Events/types.d.ts.map +1 -1
  169. package/dist/types/Metrics/index.d.ts.map +1 -1
  170. package/dist/types/Metrics/types.d.ts +19 -2
  171. package/dist/types/Metrics/types.d.ts.map +1 -1
  172. package/dist/types/SDKConnector/types.d.ts +24 -0
  173. package/dist/types/SDKConnector/types.d.ts.map +1 -1
  174. package/dist/types/common/Utils.d.ts +9 -2
  175. package/dist/types/common/Utils.d.ts.map +1 -1
  176. package/dist/types/common/testUtil.d.ts +4 -1
  177. package/dist/types/common/testUtil.d.ts.map +1 -1
  178. package/dist/types/common/types.d.ts +3 -0
  179. package/dist/types/common/types.d.ts.map +1 -1
  180. package/dist/types/mobius-socket/config.d.ts +17 -0
  181. package/dist/types/mobius-socket/config.d.ts.map +1 -0
  182. package/dist/types/mobius-socket/errors.d.ts +32 -0
  183. package/dist/types/mobius-socket/errors.d.ts.map +1 -0
  184. package/dist/types/mobius-socket/index.d.ts +14 -0
  185. package/dist/types/mobius-socket/index.d.ts.map +1 -0
  186. package/dist/types/mobius-socket/mobius-socket.d.ts +48 -0
  187. package/dist/types/mobius-socket/mobius-socket.d.ts.map +1 -0
  188. package/dist/types/mobius-socket/socket/constants.d.ts +27 -0
  189. package/dist/types/mobius-socket/socket/constants.d.ts.map +1 -0
  190. package/dist/types/mobius-socket/socket/index.d.ts +5 -0
  191. package/dist/types/mobius-socket/socket/index.d.ts.map +1 -0
  192. package/dist/types/mobius-socket/socket/socket-base.d.ts +43 -0
  193. package/dist/types/mobius-socket/socket/socket-base.d.ts.map +1 -0
  194. package/dist/types/mobius-socket/socket/socket.d.ts +6 -0
  195. package/dist/types/mobius-socket/socket/socket.d.ts.map +1 -0
  196. package/dist/types/mobius-socket/socket/socket.shim.d.ts +6 -0
  197. package/dist/types/mobius-socket/socket/socket.shim.d.ts.map +1 -0
  198. package/dist/types/mobius-socket/socket/types.d.ts +61 -0
  199. package/dist/types/mobius-socket/socket/types.d.ts.map +1 -0
  200. package/dist/types/mobius-socket/types.d.ts +21 -0
  201. package/dist/types/mobius-socket/types.d.ts.map +1 -0
  202. package/package.json +20 -5
  203. package/src/mobius-socket/socket/socket.shim.ts +22 -0
  204. package/src/mobius-socket/socket/socket.ts +14 -0
@@ -0,0 +1,589 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file
3
+ */
4
+ import { EventEmitter } from 'events';
5
+ import backoff from 'backoff';
6
+ import Socket from './socket';
7
+ import { BadRequest, Forbidden, NotAuthorized, UnknownResponse } from './errors';
8
+ import { MOBIUS_SOCKET_4001_EVENT } from './socket/constants';
9
+ const normalReconnectReasons = ['idle', 'done (forced)'];
10
+ const MOBIUS_SOCKET_NAMESPACE = 'MobiusSocket';
11
+ const TOKEN_REFRESH_INTERVAL_MS = 1 * 60 * 60 * 1000;
12
+ function normalizeMobiusAuthToken(token) {
13
+ return token.replace(/^Bearer\s+/i, '');
14
+ }
15
+ class MobiusSocket extends EventEmitter {
16
+ webex;
17
+ config;
18
+ logger;
19
+ connected;
20
+ connecting;
21
+ hasEverConnected;
22
+ socket;
23
+ backoffCall;
24
+ shutdownSwitchoverBackoffCall;
25
+ seenAsyncEventIds;
26
+ connectPromise;
27
+ socketUrl;
28
+ tokenRefreshTimer;
29
+ tokenRefreshInFlight;
30
+ constructor(webex, config = {}) {
31
+ super();
32
+ if (!webex) {
33
+ throw new Error('A Webex instance is required when initializing MobiusSocket');
34
+ }
35
+ this.webex = webex;
36
+ this.config = config;
37
+ this.logger = webex.logger || console;
38
+ this.connected = false;
39
+ this.connecting = false;
40
+ this.hasEverConnected = false;
41
+ this.socket = undefined;
42
+ this.backoffCall = undefined;
43
+ this.shutdownSwitchoverBackoffCall = undefined;
44
+ this.seenAsyncEventIds = new Map();
45
+ this.connectPromise = undefined;
46
+ this.tokenRefreshTimer = undefined;
47
+ this.tokenRefreshInFlight = undefined;
48
+ }
49
+ off(eventName, listener) {
50
+ if (listener) {
51
+ return super.off(eventName, listener);
52
+ }
53
+ this.removeAllListeners(eventName);
54
+ return this;
55
+ }
56
+ attachSocketEventListeners(socket) {
57
+ socket.on('close', (event) => this.onclose(event, socket));
58
+ socket.on('message', (event) => this.onmessage(event));
59
+ }
60
+ trackAsyncEventAndShouldSuppressDuplicate(envelope) {
61
+ if (envelope?.type !== 'async_event' || !envelope.eventId) {
62
+ return false;
63
+ }
64
+ if (this.seenAsyncEventIds.has(envelope.eventId)) {
65
+ const previousValue = this.seenAsyncEventIds.get(envelope.eventId) || true;
66
+ this.seenAsyncEventIds.delete(envelope.eventId);
67
+ this.seenAsyncEventIds.set(envelope.eventId, previousValue);
68
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: duplicate async_event suppressed, eventId=${envelope.eventId}`);
69
+ return true;
70
+ }
71
+ this.logger.log(`${MOBIUS_SOCKET_NAMESPACE}: tracking async_event, eventId=${envelope.eventId}`);
72
+ this.seenAsyncEventIds.set(envelope.eventId, true);
73
+ if (this.config.dedupCacheMaxSize &&
74
+ this.seenAsyncEventIds.size > this.config.dedupCacheMaxSize) {
75
+ const oldestEventId = this.seenAsyncEventIds.keys().next().value || '';
76
+ this.seenAsyncEventIds.delete(oldestEventId);
77
+ this.logger.log(`${MOBIUS_SOCKET_NAMESPACE}: evicted oldest async_event from dedup cache, eventId=${oldestEventId}`);
78
+ }
79
+ return false;
80
+ }
81
+ handleImminentShutdown() {
82
+ const oldSocket = this.socket;
83
+ try {
84
+ if (this.shutdownSwitchoverBackoffCall) {
85
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover already in progress`);
86
+ return;
87
+ }
88
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover start`);
89
+ this.connectWithBackoff(undefined, {
90
+ isShutdownSwitchover: true,
91
+ attemptOptions: {
92
+ isShutdownSwitchover: true,
93
+ onSuccess: (newSocket, webSocketUrl) => {
94
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover connected, url: ${webSocketUrl}`);
95
+ newSocket.connecting = false;
96
+ newSocket.connected = true;
97
+ this.socket = newSocket;
98
+ this.connected = true;
99
+ this.emitEvent('event:mobius_shutdown_switchover_complete', {
100
+ url: webSocketUrl,
101
+ });
102
+ if (oldSocket) {
103
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] old socket retained; server will close with 4001`);
104
+ }
105
+ },
106
+ },
107
+ })
108
+ .then(() => {
109
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover completed successfully`);
110
+ })
111
+ .catch((err) => {
112
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover exhausted retries; will fall back to normal reconnection: `, err);
113
+ this.emitEvent('event:mobius_shutdown_switchover_failed', { reason: err });
114
+ });
115
+ }
116
+ catch (e) {
117
+ this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] error during switchover`, e);
118
+ this.shutdownSwitchoverBackoffCall = undefined;
119
+ this.emitEvent('event:mobius_shutdown_switchover_failed', { reason: e });
120
+ }
121
+ }
122
+ getConnectedWebSocketUrl() {
123
+ if (!this.socket?.connected) {
124
+ return undefined;
125
+ }
126
+ return this.socket.url;
127
+ }
128
+ sendWssRequest(payload, options = {}) {
129
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
130
+ return Promise.reject(new Error('`payload` is required'));
131
+ }
132
+ if (!this.socket || !this.socket.connected) {
133
+ return Promise.reject(new Error('Mobius socket is not connected'));
134
+ }
135
+ return this.socket.sendRequest(payload, { timeout: options.timeout });
136
+ }
137
+ isConnected() {
138
+ return this.connected;
139
+ }
140
+ connect(webSocketUrl) {
141
+ if (this.connectPromise) {
142
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connection already in progress, returning existing promise`);
143
+ return this.connectPromise;
144
+ }
145
+ if (this.socket?.connected || this.socket?.connecting) {
146
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: already connected, will not connect again`);
147
+ return Promise.resolve();
148
+ }
149
+ if (webSocketUrl && this.socketUrl && webSocketUrl !== this.socketUrl) {
150
+ this.hasEverConnected = false;
151
+ }
152
+ if (webSocketUrl) {
153
+ this.socketUrl = webSocketUrl;
154
+ }
155
+ this.connecting = true;
156
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: starting connection attempt${Number(this.config.initialConnectionMaxRetries) === 0 && !this.hasEverConnected
157
+ ? ' (initial retries disabled)'
158
+ : ''}`);
159
+ const connectPromise = Promise.resolve(this.webex.internal.device.registered || this.webex.internal.device.register?.())
160
+ .then(() => {
161
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connecting`);
162
+ return this.connectWithBackoff(this.socketUrl);
163
+ })
164
+ .finally(() => {
165
+ this.connectPromise = undefined;
166
+ });
167
+ this.connectPromise = connectPromise;
168
+ return connectPromise;
169
+ }
170
+ disconnect(options) {
171
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}#disconnect: connecting state: ${this.connecting},
172
+ connected state: ${this.connected}, socket exists: ${!!this.socket},
173
+ options: ${JSON.stringify(options)}`);
174
+ if (this.backoffCall) {
175
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: aborting connection`);
176
+ this.backoffCall.abort();
177
+ this.backoffCall = undefined;
178
+ }
179
+ if (this.shutdownSwitchoverBackoffCall) {
180
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: aborting shutdown switchover connection`);
181
+ this.shutdownSwitchoverBackoffCall.abort();
182
+ this.shutdownSwitchoverBackoffCall = undefined;
183
+ }
184
+ this.connectPromise = undefined;
185
+ this.seenAsyncEventIds.clear();
186
+ if (!this.socket) {
187
+ this.connected = false;
188
+ this.stopTokenRefreshTimer();
189
+ return Promise.resolve();
190
+ }
191
+ this.socket.removeAllListeners('message');
192
+ this.socket.connecting = false;
193
+ this.socket.connected = false;
194
+ return Promise.resolve(this.socket.close(options || undefined)).finally(() => {
195
+ this.connected = false;
196
+ this.stopTokenRefreshTimer();
197
+ });
198
+ }
199
+ prepareUrl(webSocketUrl) {
200
+ if (!webSocketUrl) {
201
+ webSocketUrl = this.webex.internal.device.webSocketUrl || '';
202
+ }
203
+ return Promise.resolve(webSocketUrl);
204
+ }
205
+ attemptConnection(socketUrl, callback, options = {}) {
206
+ const { isShutdownSwitchover = false, attemptOptions = {} } = options;
207
+ const { onSuccess = null } = attemptOptions;
208
+ const socket = new Socket();
209
+ socket.connecting = true;
210
+ let newWSUrl;
211
+ this.attachSocketEventListeners(socket);
212
+ const backoffCall = isShutdownSwitchover
213
+ ? this.shutdownSwitchoverBackoffCall
214
+ : this.backoffCall;
215
+ if (!backoffCall) {
216
+ const mode = isShutdownSwitchover ? 'switchover backoff call' : 'backoffCall';
217
+ const msg = `${MOBIUS_SOCKET_NAMESPACE}: prevent socket open when ${mode} no longer defined`;
218
+ const err = new Error(msg);
219
+ this.logger.info(msg);
220
+ callback(err);
221
+ return Promise.reject(err);
222
+ }
223
+ if (!isShutdownSwitchover) {
224
+ this.socket = socket;
225
+ }
226
+ return this.prepareAndOpenSocket(socket, socketUrl, isShutdownSwitchover)
227
+ .then((webSocketUrl) => {
228
+ newWSUrl = webSocketUrl;
229
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${isShutdownSwitchover ? '[shutdown] switchover' : ''} connected to mobius socket, success, url: ${newWSUrl}`);
230
+ if (onSuccess) {
231
+ onSuccess(socket, webSocketUrl);
232
+ callback();
233
+ return Promise.resolve();
234
+ }
235
+ callback();
236
+ return Promise.resolve();
237
+ })
238
+ .catch((reason) => {
239
+ if (isShutdownSwitchover) {
240
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: [shutdown] switchover attempt failed`, reason);
241
+ return callback(reason);
242
+ }
243
+ const backoffCallNormal = this.backoffCall;
244
+ if (reason.code !== 1006 && backoffCallNormal && backoffCallNormal?.getNumRetries() > 0) {
245
+ this.emitEvent('connection_failed', reason, {
246
+ retries: backoffCallNormal?.getNumRetries(),
247
+ });
248
+ }
249
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connection attempt failed`, reason, backoffCallNormal?.getNumRetries() === 0 ? reason.stack : '');
250
+ if (reason instanceof UnknownResponse) {
251
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: received unknown response code, refreshing device registration`);
252
+ return this.webex.internal.device
253
+ .refresh?.()
254
+ .then(() => callback(reason));
255
+ }
256
+ if (reason instanceof NotAuthorized) {
257
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: received authorization error, reauthorizing`);
258
+ return this.webex.credentials
259
+ .refresh?.({ force: true })
260
+ .then(() => callback(reason));
261
+ }
262
+ if (reason instanceof BadRequest || reason instanceof Forbidden) {
263
+ this.logger.warn(`${MOBIUS_SOCKET_NAMESPACE}: received unrecoverable response from ${MOBIUS_SOCKET_NAMESPACE}`);
264
+ backoffCallNormal?.abort();
265
+ return callback(reason);
266
+ }
267
+ return callback(reason);
268
+ })
269
+ .catch((reason) => {
270
+ this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: failed to handle connection failure`, reason);
271
+ callback(reason);
272
+ });
273
+ }
274
+ prepareAndOpenSocket(socket, socketUrl, isShutdownSwitchover = false) {
275
+ const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
276
+ return Promise.all([this.prepareUrl(socketUrl), this.webex.credentials.getUserToken()]).then(([webSocketUrl, token]) => {
277
+ let options = {
278
+ forceCloseDelay: this.config.forceCloseDelay,
279
+ wssResponseTimeout: this.config.wssResponseTimeout,
280
+ token: normalizeMobiusAuthToken(token.toString()),
281
+ refreshToken: () => this.refreshToken(),
282
+ trackingId: `${this.webex.sessionId}_${Date.now()}`,
283
+ logger: this.logger,
284
+ };
285
+ if (this.webex.config.defaultMobiusSocketOptions) {
286
+ const customOptionsMsg = isShutdownSwitchover
287
+ ? 'setting custom options for switchover'
288
+ : 'setting custom options';
289
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${customOptionsMsg}`);
290
+ options = { ...options, ...this.webex.config.defaultMobiusSocketOptions };
291
+ }
292
+ if (!isShutdownSwitchover) {
293
+ this.socket = socket;
294
+ }
295
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE} ${logPrefix} url: ${webSocketUrl}`);
296
+ return socket.open(webSocketUrl, options).then(() => webSocketUrl);
297
+ });
298
+ }
299
+ connectWithBackoff(webSocketUrl, context = {}) {
300
+ const { isShutdownSwitchover = false, attemptOptions = {} } = context;
301
+ return new Promise((resolve, reject) => {
302
+ let call;
303
+ const isInitialConnect = !isShutdownSwitchover && !this.hasEverConnected;
304
+ const initialRetryLimit = this.config.initialConnectionMaxRetries == null
305
+ ? null
306
+ : Number(this.config.initialConnectionMaxRetries);
307
+ const isInitialConnectWithoutRetries = isInitialConnect && initialRetryLimit === 0;
308
+ const onComplete = (err) => {
309
+ if (isShutdownSwitchover) {
310
+ this.shutdownSwitchoverBackoffCall = undefined;
311
+ }
312
+ else {
313
+ this.backoffCall = undefined;
314
+ }
315
+ if (err) {
316
+ const msg = isShutdownSwitchover
317
+ ? `[shutdown] switchover failed after ${call.getNumRetries()} retries`
318
+ : `failed to connect after ${call.getNumRetries()} retries`;
319
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${msg}; ${err}`);
320
+ if (!isShutdownSwitchover && this.socket) {
321
+ this.socket.connecting = false;
322
+ this.socket.connected = false;
323
+ }
324
+ return reject(err);
325
+ }
326
+ if (!isShutdownSwitchover && this.socket) {
327
+ this.socket.connecting = false;
328
+ this.socket.connected = true;
329
+ }
330
+ if (!isShutdownSwitchover) {
331
+ this.connecting = false;
332
+ this.connected = true;
333
+ this.hasEverConnected = true;
334
+ this.startTokenRefreshTimer();
335
+ this.emitEvent('online');
336
+ }
337
+ return resolve();
338
+ };
339
+ call = backoff.call((callback) => {
340
+ const attemptNum = call.getNumRetries();
341
+ const attemptLogPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
342
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: executing ${attemptLogPrefix} attempt ${attemptNum}`);
343
+ this.attemptConnection(webSocketUrl, callback, attemptOptions);
344
+ }, (err) => onComplete(err));
345
+ call.setStrategy(new backoff.ExponentialStrategy({
346
+ initialDelay: this.config.backoffTimeReset,
347
+ maxDelay: this.config.backoffTimeMax,
348
+ }));
349
+ if (isInitialConnectWithoutRetries) {
350
+ call.retryIf(() => false);
351
+ }
352
+ else if (isInitialConnect && initialRetryLimit !== null && initialRetryLimit > 0) {
353
+ call.failAfter(initialRetryLimit);
354
+ }
355
+ else if (this.config.maxRetries) {
356
+ call.failAfter(this.config.maxRetries);
357
+ }
358
+ if (isShutdownSwitchover) {
359
+ this.shutdownSwitchoverBackoffCall = call;
360
+ }
361
+ else {
362
+ this.backoffCall = call;
363
+ }
364
+ call.on('abort', () => {
365
+ const msg = isShutdownSwitchover ? 'Shutdown Switchover' : 'Connection';
366
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${msg} aborted`);
367
+ reject(new Error(`MobiusSocket ${msg} Aborted`));
368
+ });
369
+ call.on('callback', (err) => {
370
+ if (err) {
371
+ if (isInitialConnectWithoutRetries) {
372
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: initial connect failed; retries already disabled`);
373
+ return;
374
+ }
375
+ const number = call.getNumRetries();
376
+ const delay = Math.min(call.strategy_.nextBackoffDelay_, this.config.backoffTimeMax || Infinity);
377
+ const callbackLogPrefix = isShutdownSwitchover ? '[shutdown] switchover' : '';
378
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: ${callbackLogPrefix} failed to connect; attempting retry ${number + 1} in ${delay} ms`);
379
+ if (process.env.NODE_ENV === 'development') {
380
+ this.logger.debug(`${MOBIUS_SOCKET_NAMESPACE}: `, err, err.stack);
381
+ }
382
+ return;
383
+ }
384
+ this.logger.info(`${MOBIUS_SOCKET_NAMESPACE}: connected`);
385
+ });
386
+ call.start();
387
+ });
388
+ }
389
+ emitEvent(eventName, ...args) {
390
+ try {
391
+ if (!eventName) {
392
+ return;
393
+ }
394
+ this.emit(eventName, ...args);
395
+ }
396
+ catch (error) {
397
+ this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: error occurred in event handler:`, error, ' with args: ', [eventName, ...args]);
398
+ }
399
+ }
400
+ startTokenRefreshTimer() {
401
+ if (this.tokenRefreshTimer || !this.connected) {
402
+ return;
403
+ }
404
+ this.tokenRefreshTimer = setInterval(() => {
405
+ this.refreshToken().catch((error) => {
406
+ this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: periodic token refresh failed`, error);
407
+ });
408
+ }, TOKEN_REFRESH_INTERVAL_MS);
409
+ }
410
+ stopTokenRefreshTimer() {
411
+ if (!this.tokenRefreshTimer) {
412
+ return;
413
+ }
414
+ clearInterval(this.tokenRefreshTimer);
415
+ this.tokenRefreshTimer = undefined;
416
+ }
417
+ refreshToken() {
418
+ if (this.tokenRefreshInFlight) {
419
+ return this.tokenRefreshInFlight;
420
+ }
421
+ if (!this.connected) {
422
+ this.stopTokenRefreshTimer();
423
+ return Promise.resolve();
424
+ }
425
+ const tokenPromise = this.webex.credentials.canRefresh
426
+ ? this.webex.credentials
427
+ .refresh?.({ force: true })
428
+ ?.then(() => this.webex.credentials.getUserToken())
429
+ : this.webex.credentials.getUserToken();
430
+ this.tokenRefreshInFlight = tokenPromise
431
+ .then((token) => {
432
+ if (!token) {
433
+ throw new Error('Mobius token refresh did not return a token');
434
+ }
435
+ const refreshedToken = normalizeMobiusAuthToken(token.toString());
436
+ if (!this.socket?.connected) {
437
+ this.logger.warn(`${MOBIUS_SOCKET_NAMESPACE}: socket is not connected, skipping token refresh`);
438
+ return undefined;
439
+ }
440
+ return this.socket.refresh(refreshedToken);
441
+ })
442
+ .catch((error) => {
443
+ this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: failed to refresh/re-auth Mobius socket`, error);
444
+ throw error;
445
+ })
446
+ .finally(() => {
447
+ this.tokenRefreshInFlight = undefined;
448
+ });
449
+ return this.tokenRefreshInFlight;
450
+ }
451
+ async onclose(event, sourceSocket) {
452
+ const loggerContext = {
453
+ file: 'mobius-socket.ts',
454
+ method: 'onclose',
455
+ };
456
+ try {
457
+ const reason = event.reason && event.reason.toLowerCase();
458
+ let socketUrl;
459
+ const isActiveSocket = sourceSocket === this.socket;
460
+ if (sourceSocket) {
461
+ socketUrl = sourceSocket.url;
462
+ }
463
+ if (isActiveSocket) {
464
+ if (this.socket) {
465
+ this.socket.removeAllListeners();
466
+ this.socket = undefined;
467
+ this.emitEvent('offline', event);
468
+ }
469
+ this.connecting = false;
470
+ this.connected = false;
471
+ this.stopTokenRefreshTimer();
472
+ }
473
+ else {
474
+ this.logger.info(`[shutdown] non-active socket closed, code=${event.code}`, loggerContext);
475
+ if (sourceSocket) {
476
+ sourceSocket.removeAllListeners();
477
+ }
478
+ }
479
+ switch (event.code) {
480
+ case 1003:
481
+ this.logger.info(`service rejected last message; will not reconnect: ${event.reason}`, loggerContext);
482
+ if (isActiveSocket)
483
+ this.emitEvent('offline.permanent', event);
484
+ break;
485
+ case 4000:
486
+ this.logger.info(`socket replaced; will not reconnect`, loggerContext);
487
+ if (isActiveSocket)
488
+ this.emitEvent('offline.replaced', event);
489
+ break;
490
+ case 4001:
491
+ this.logger.info(`socket closed with 4001; will not reconnect`, loggerContext);
492
+ this.emitEvent('event:async_event', MOBIUS_SOCKET_4001_EVENT);
493
+ break;
494
+ case 1000:
495
+ case 1001:
496
+ this.logger.info(`socket disconnected; ${event.reason}`, loggerContext);
497
+ if (isActiveSocket)
498
+ this.emitEvent('offline.permanent', event);
499
+ break;
500
+ case 1005:
501
+ case 1006:
502
+ case 1011:
503
+ case 1012:
504
+ this.logger.info(`socket disconnected; reconnecting`, loggerContext);
505
+ if (isActiveSocket) {
506
+ this.emitEvent('offline.transient', event);
507
+ this.reconnect(socketUrl);
508
+ }
509
+ break;
510
+ case 3050:
511
+ if (reason && normalReconnectReasons.includes(reason)) {
512
+ this.logger.info(`socket disconnected; reconnecting`, loggerContext);
513
+ if (isActiveSocket) {
514
+ this.emitEvent('offline.transient', event);
515
+ this.reconnect(socketUrl);
516
+ }
517
+ }
518
+ else {
519
+ this.logger.info(`socket disconnected; will not reconnect: ${event.reason}`, loggerContext);
520
+ if (isActiveSocket)
521
+ this.emitEvent('offline.permanent', event);
522
+ }
523
+ break;
524
+ case 4401:
525
+ case 4403:
526
+ case 4404:
527
+ this.logger.error(`onclose, statusCode=${event.code}`, loggerContext);
528
+ await this.refreshToken().catch((error) => {
529
+ this.logger.error(`${MOBIUS_SOCKET_NAMESPACE}: periodic token refresh failed`, error);
530
+ });
531
+ await this.reconnect(this.socket?.url);
532
+ break;
533
+ case 4429:
534
+ this.logger.error(`too many requests, statusCode=${event.code}`, loggerContext);
535
+ break;
536
+ default:
537
+ this.logger.info(`socket disconnected unexpectedly; will not reconnect`, loggerContext);
538
+ if (isActiveSocket)
539
+ this.emitEvent('offline.permanent', event);
540
+ }
541
+ }
542
+ catch (error) {
543
+ this.logger.error(`error occurred in close handler`, error, loggerContext);
544
+ }
545
+ }
546
+ onmessage(event) {
547
+ const loggerContext = {
548
+ file: 'mobius-socket.ts',
549
+ method: 'onmessage',
550
+ };
551
+ const envelope = event.data;
552
+ this.logger.debug(`message envelope: `, envelope, loggerContext);
553
+ if (envelope && envelope.type === 'shutdown') {
554
+ this.logger.info(`[shutdown] imminent shutdown message received`, loggerContext);
555
+ this.emitEvent('event:mobius_shutdown_imminent', envelope);
556
+ this.handleImminentShutdown();
557
+ return Promise.resolve();
558
+ }
559
+ if (this.trackAsyncEventAndShouldSuppressDuplicate(envelope)) {
560
+ return Promise.resolve();
561
+ }
562
+ if (envelope.type) {
563
+ this.emitEvent(`event:${envelope.type}`, envelope);
564
+ }
565
+ const data = envelope.data || envelope;
566
+ const eventType = data?.eventType || envelope.eventType;
567
+ if (!eventType) {
568
+ this.emitEvent('event', envelope);
569
+ return Promise.resolve();
570
+ }
571
+ try {
572
+ this.emitEvent('event', envelope);
573
+ const [namespace] = eventType.split('.');
574
+ this.emitEvent(`event:${namespace}`, envelope);
575
+ if (namespace !== eventType) {
576
+ this.emitEvent(`event:${eventType}`, envelope);
577
+ }
578
+ }
579
+ catch (reason) {
580
+ this.logger.error(`error occurred processing socket message`, reason, loggerContext);
581
+ }
582
+ return Promise.resolve();
583
+ }
584
+ reconnect(webSocketUrl) {
585
+ this.logger.info(`reconnecting`, { file: 'mobius-socket.ts', method: 'reconnect' });
586
+ return this.connect(webSocketUrl || this.socketUrl);
587
+ }
588
+ }
589
+ export default MobiusSocket;
@@ -0,0 +1,26 @@
1
+ export const SOCKET_READY_STATE = Object.freeze({
2
+ CONNECTING: 0,
3
+ OPEN: 1,
4
+ CLOSING: 2,
5
+ CLOSED: 3,
6
+ });
7
+ export const MESSAGE_TYPES = Object.freeze({
8
+ AUTH: 'auth',
9
+ EVENT_ACK: 'event_ack',
10
+ });
11
+ export const MOBIUS_SOCKET_4001_EVENT = {
12
+ type: 'async_event',
13
+ trackingId: '4001-event',
14
+ eventId: '4001-event-id',
15
+ data: {
16
+ eventType: 'registration.down',
17
+ deviceInfo: {
18
+ userId: '4001-user-id',
19
+ device: {
20
+ deviceId: '4001-device-id',
21
+ uri: '4001-device-uri',
22
+ status: 'inactive',
23
+ },
24
+ },
25
+ },
26
+ };
@@ -0,0 +1,4 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+ export { default } from './socket';