@ohos-ports/genesys-cloud-streaming-client 20.1.0-beta.1

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 (176) hide show
  1. package/README.md +47 -0
  2. package/dist/cjs/alerting-leader.d.ts +29 -0
  3. package/dist/cjs/alerting-leader.js +163 -0
  4. package/dist/cjs/client.d.ts +84 -0
  5. package/dist/cjs/client.js +680 -0
  6. package/dist/cjs/connection-manager.d.ts +18 -0
  7. package/dist/cjs/connection-manager.js +120 -0
  8. package/dist/cjs/connection-transfer.d.ts +7 -0
  9. package/dist/cjs/connection-transfer.js +20 -0
  10. package/dist/cjs/http-client.d.ts +18 -0
  11. package/dist/cjs/http-client.js +230 -0
  12. package/dist/cjs/index.d.ts +10 -0
  13. package/dist/cjs/index.js +31 -0
  14. package/dist/cjs/messenger.d.ts +37 -0
  15. package/dist/cjs/messenger.js +54 -0
  16. package/dist/cjs/notifications.d.ts +94 -0
  17. package/dist/cjs/notifications.js +495 -0
  18. package/dist/cjs/ping.d.ts +21 -0
  19. package/dist/cjs/ping.js +60 -0
  20. package/dist/cjs/polyfills.d.ts +0 -0
  21. package/dist/cjs/polyfills.js +6 -0
  22. package/dist/cjs/server-monitor.d.ts +16 -0
  23. package/dist/cjs/server-monitor.js +44 -0
  24. package/dist/cjs/stanza-definitions/webrtc-signaling.d.ts +29 -0
  25. package/dist/cjs/stanza-definitions/webrtc-signaling.js +113 -0
  26. package/dist/cjs/stanza-definitions/xep0051.d.ts +19 -0
  27. package/dist/cjs/stanza-definitions/xep0051.js +28 -0
  28. package/dist/cjs/stats-formatter.d.ts +6 -0
  29. package/dist/cjs/stats-formatter.js +63 -0
  30. package/dist/cjs/types/genesys-cloud-media-session.d.ts +64 -0
  31. package/dist/cjs/types/genesys-cloud-media-session.js +353 -0
  32. package/dist/cjs/types/interfaces.d.ts +310 -0
  33. package/dist/cjs/types/interfaces.js +22 -0
  34. package/dist/cjs/types/libs.d.ts +32 -0
  35. package/dist/cjs/types/libs.js +1 -0
  36. package/dist/cjs/types/media-session.d.ts +76 -0
  37. package/dist/cjs/types/media-session.js +2 -0
  38. package/dist/cjs/types/named-agent.d.ts +13 -0
  39. package/dist/cjs/types/named-agent.js +2 -0
  40. package/dist/cjs/types/offline-error.d.ts +2 -0
  41. package/dist/cjs/types/offline-error.js +5 -0
  42. package/dist/cjs/types/sasl-error.d.ts +8 -0
  43. package/dist/cjs/types/sasl-error.js +12 -0
  44. package/dist/cjs/types/stanza-media-session.d.ts +56 -0
  45. package/dist/cjs/types/stanza-media-session.js +255 -0
  46. package/dist/cjs/types/streaming-client-extension.d.ts +14 -0
  47. package/dist/cjs/types/streaming-client-extension.js +2 -0
  48. package/dist/cjs/types/timeout-error.d.ts +3 -0
  49. package/dist/cjs/types/timeout-error.js +9 -0
  50. package/dist/cjs/types/user-cancelled-error.d.ts +2 -0
  51. package/dist/cjs/types/user-cancelled-error.js +5 -0
  52. package/dist/cjs/utils.d.ts +34 -0
  53. package/dist/cjs/utils.js +202 -0
  54. package/dist/cjs/webrtc.d.ts +126 -0
  55. package/dist/cjs/webrtc.js +891 -0
  56. package/dist/deploy-info.json +21 -0
  57. package/dist/es/alerting-leader.d.ts +29 -0
  58. package/dist/es/alerting-leader.js +177 -0
  59. package/dist/es/client.d.ts +84 -0
  60. package/dist/es/client.js +699 -0
  61. package/dist/es/connection-manager.d.ts +18 -0
  62. package/dist/es/connection-manager.js +124 -0
  63. package/dist/es/connection-transfer.d.ts +7 -0
  64. package/dist/es/connection-transfer.js +16 -0
  65. package/dist/es/http-client.d.ts +18 -0
  66. package/dist/es/http-client.js +194 -0
  67. package/dist/es/index.bundle.js +39629 -0
  68. package/dist/es/index.d.ts +10 -0
  69. package/dist/es/index.js +10 -0
  70. package/dist/es/messenger.d.ts +37 -0
  71. package/dist/es/messenger.js +62 -0
  72. package/dist/es/notifications.d.ts +94 -0
  73. package/dist/es/notifications.js +507 -0
  74. package/dist/es/ping.d.ts +21 -0
  75. package/dist/es/ping.js +68 -0
  76. package/dist/es/polyfills.d.ts +0 -0
  77. package/dist/es/polyfills.js +6 -0
  78. package/dist/es/server-monitor.d.ts +16 -0
  79. package/dist/es/server-monitor.js +41 -0
  80. package/dist/es/stanza-definitions/webrtc-signaling.d.ts +29 -0
  81. package/dist/es/stanza-definitions/webrtc-signaling.js +110 -0
  82. package/dist/es/stanza-definitions/xep0051.d.ts +19 -0
  83. package/dist/es/stanza-definitions/xep0051.js +25 -0
  84. package/dist/es/stats-formatter.d.ts +6 -0
  85. package/dist/es/stats-formatter.js +54 -0
  86. package/dist/es/types/genesys-cloud-media-session.d.ts +64 -0
  87. package/dist/es/types/genesys-cloud-media-session.js +379 -0
  88. package/dist/es/types/interfaces.d.ts +310 -0
  89. package/dist/es/types/interfaces.js +19 -0
  90. package/dist/es/types/libs.d.ts +32 -0
  91. package/dist/es/types/libs.js +1 -0
  92. package/dist/es/types/media-session.d.ts +76 -0
  93. package/dist/es/types/media-session.js +1 -0
  94. package/dist/es/types/named-agent.d.ts +13 -0
  95. package/dist/es/types/named-agent.js +1 -0
  96. package/dist/es/types/offline-error.d.ts +2 -0
  97. package/dist/es/types/offline-error.js +2 -0
  98. package/dist/es/types/sasl-error.d.ts +8 -0
  99. package/dist/es/types/sasl-error.js +9 -0
  100. package/dist/es/types/stanza-media-session.d.ts +56 -0
  101. package/dist/es/types/stanza-media-session.js +264 -0
  102. package/dist/es/types/streaming-client-extension.d.ts +14 -0
  103. package/dist/es/types/streaming-client-extension.js +1 -0
  104. package/dist/es/types/timeout-error.d.ts +3 -0
  105. package/dist/es/types/timeout-error.js +5 -0
  106. package/dist/es/types/user-cancelled-error.d.ts +2 -0
  107. package/dist/es/types/user-cancelled-error.js +2 -0
  108. package/dist/es/utils.d.ts +34 -0
  109. package/dist/es/utils.js +192 -0
  110. package/dist/es/webrtc.d.ts +126 -0
  111. package/dist/es/webrtc.js +917 -0
  112. package/dist/manifest.json +19 -0
  113. package/dist/npm/CHANGELOG.md +637 -0
  114. package/dist/npm/README.md +47 -0
  115. package/dist/npm/alerting-leader.d.ts +29 -0
  116. package/dist/npm/alerting-leader.js +163 -0
  117. package/dist/npm/client.d.ts +84 -0
  118. package/dist/npm/client.js +680 -0
  119. package/dist/npm/connection-manager.d.ts +18 -0
  120. package/dist/npm/connection-manager.js +120 -0
  121. package/dist/npm/connection-transfer.d.ts +7 -0
  122. package/dist/npm/connection-transfer.js +20 -0
  123. package/dist/npm/http-client.d.ts +18 -0
  124. package/dist/npm/http-client.js +230 -0
  125. package/dist/npm/index.d.ts +10 -0
  126. package/dist/npm/index.js +31 -0
  127. package/dist/npm/messenger.d.ts +37 -0
  128. package/dist/npm/messenger.js +54 -0
  129. package/dist/npm/module.js +10 -0
  130. package/dist/npm/notifications.d.ts +94 -0
  131. package/dist/npm/notifications.js +495 -0
  132. package/dist/npm/ping.d.ts +21 -0
  133. package/dist/npm/ping.js +60 -0
  134. package/dist/npm/polyfills.d.ts +0 -0
  135. package/dist/npm/polyfills.js +6 -0
  136. package/dist/npm/server-monitor.d.ts +16 -0
  137. package/dist/npm/server-monitor.js +44 -0
  138. package/dist/npm/stanza-definitions/webrtc-signaling.d.ts +29 -0
  139. package/dist/npm/stanza-definitions/webrtc-signaling.js +113 -0
  140. package/dist/npm/stanza-definitions/xep0051.d.ts +19 -0
  141. package/dist/npm/stanza-definitions/xep0051.js +28 -0
  142. package/dist/npm/stats-formatter.d.ts +6 -0
  143. package/dist/npm/stats-formatter.js +63 -0
  144. package/dist/npm/types/genesys-cloud-media-session.d.ts +64 -0
  145. package/dist/npm/types/genesys-cloud-media-session.js +353 -0
  146. package/dist/npm/types/interfaces.d.ts +310 -0
  147. package/dist/npm/types/interfaces.js +22 -0
  148. package/dist/npm/types/libs.d.ts +32 -0
  149. package/dist/npm/types/libs.js +1 -0
  150. package/dist/npm/types/media-session.d.ts +76 -0
  151. package/dist/npm/types/media-session.js +2 -0
  152. package/dist/npm/types/named-agent.d.ts +13 -0
  153. package/dist/npm/types/named-agent.js +2 -0
  154. package/dist/npm/types/offline-error.d.ts +2 -0
  155. package/dist/npm/types/offline-error.js +5 -0
  156. package/dist/npm/types/sasl-error.d.ts +8 -0
  157. package/dist/npm/types/sasl-error.js +12 -0
  158. package/dist/npm/types/stanza-media-session.d.ts +56 -0
  159. package/dist/npm/types/stanza-media-session.js +255 -0
  160. package/dist/npm/types/streaming-client-extension.d.ts +14 -0
  161. package/dist/npm/types/streaming-client-extension.js +2 -0
  162. package/dist/npm/types/timeout-error.d.ts +3 -0
  163. package/dist/npm/types/timeout-error.js +9 -0
  164. package/dist/npm/types/user-cancelled-error.d.ts +2 -0
  165. package/dist/npm/types/user-cancelled-error.js +5 -0
  166. package/dist/npm/utils.d.ts +34 -0
  167. package/dist/npm/utils.js +207 -0
  168. package/dist/npm/webrtc.d.ts +126 -0
  169. package/dist/npm/webrtc.js +893 -0
  170. package/dist/streaming-client.browser.js +2 -0
  171. package/dist/streaming-client.browser.js.LICENSE.txt +12 -0
  172. package/dist/v20/streaming-client.browser.js +2 -0
  173. package/dist/v20/streaming-client.browser.js.LICENSE.txt +12 -0
  174. package/dist/v20.1.0/streaming-client.browser.js +2 -0
  175. package/dist/v20.1.0/streaming-client.browser.js.LICENSE.txt +12 -0
  176. package/package.json +132 -0
@@ -0,0 +1,699 @@
1
+ 'use strict';
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { TokenBucket } from 'limiter';
12
+ import { Logger } from 'genesys-cloud-client-logger';
13
+ import './polyfills';
14
+ import { AlertingLeaderExtension } from './alerting-leader';
15
+ import { Notifications } from './notifications';
16
+ import { WebrtcExtension } from './webrtc';
17
+ import { Ping } from './ping';
18
+ import { ServerMonitor } from './server-monitor';
19
+ import { StreamingClientError, delay, parseJwt, timeoutPromise } from './utils';
20
+ import { HttpClient } from './http-client';
21
+ import { StreamingClientErrorTypes } from './types/interfaces';
22
+ import EventEmitter from 'events';
23
+ import { ConnectionManager } from './connection-manager';
24
+ import { backOff } from 'exponential-backoff';
25
+ import SaslError from './types/sasl-error';
26
+ import { TimeoutError } from './types/timeout-error';
27
+ import { MessengerExtension } from './messenger';
28
+ import { ConnectionTransfer } from './connection-transfer';
29
+ import UserCancelledError from './types/user-cancelled-error';
30
+ const extensions = {
31
+ notifications: Notifications,
32
+ webrtcSessions: WebrtcExtension,
33
+ messenger: MessengerExtension,
34
+ alertingLeader: AlertingLeaderExtension
35
+ };
36
+ const STANZA_DISCONNECTED = 'stanzaDisconnected';
37
+ const NO_LONGER_SUBSCRIBED = 'notify:no_longer_subscribed';
38
+ const DUPLICATE_ID = 'notify:duplicate_id';
39
+ const MAX_CHANNEL_REUSES = 10;
40
+ const SESSION_STORE_KEY = 'sc_connectionData';
41
+ const BACKOFF_DECREASE_DELAY_MULTIPLIER = 5;
42
+ const INITIAL_DELAY = 2000;
43
+ export class Client extends EventEmitter {
44
+ constructor(options) {
45
+ super();
46
+ this.connected = false;
47
+ this.connecting = false;
48
+ this.hardReconnectRequired = true;
49
+ this.isGuest = false;
50
+ this.backgroundAssistantMode = false;
51
+ this.autoReconnect = true;
52
+ this.cancelConnectionAttempt = false;
53
+ this.extensions = [];
54
+ this.channelReuses = 0;
55
+ this.hasMadeInitialAttempt = false;
56
+ this.jidResource = '';
57
+ this.http = new HttpClient({ customHeaders: options.customHeaders });
58
+ this.reconnectOnNoLongerSubscribed = options.reconnectOnNoLongerSubscribed !== false;
59
+ this.useServerSidePings = options.useServerSidePings !== false;
60
+ this.config = {
61
+ host: options.host,
62
+ apiHost: options.apiHost || options.host.replace('wss://streaming.', ''),
63
+ authToken: options.authToken,
64
+ jwt: options.jwt,
65
+ jid: options.jid,
66
+ jidResource: options.jidResource,
67
+ userId: options.userId,
68
+ channelId: null,
69
+ appName: options.appName,
70
+ appVersion: options.appVersion,
71
+ appId: options.appId,
72
+ logLevel: options.logLevel,
73
+ customHeaders: options.customHeaders
74
+ };
75
+ this.backgroundAssistantMode = this.checkIsBackgroundAssistant();
76
+ this.isGuest = !this.backgroundAssistantMode && !options.authToken;
77
+ let loggerAccessToken = options.authToken || '';
78
+ let loggerUrl = `https://api.${this.config.apiHost}/api/v2/diagnostics/trace`;
79
+ if (this.backgroundAssistantMode) {
80
+ loggerAccessToken = options.jwt;
81
+ loggerUrl += '/backgroundassistant';
82
+ }
83
+ this.logger = new Logger({
84
+ accessToken: loggerAccessToken,
85
+ url: loggerUrl,
86
+ uploadDebounceTime: 1000,
87
+ initializeServerLogging: !this.isGuest && !options.optOutOfWebrtcStatsTelemetry,
88
+ /* streaming-client logging info */
89
+ appVersion: Client.version,
90
+ appName: 'streaming-client',
91
+ logLevel: this.config.logLevel || 'info',
92
+ logger: options.logger || console,
93
+ formatters: options.logFormatters,
94
+ useUniqueLogUploader: options.useUniqueLogUploader,
95
+ /* secondary/parent app info */
96
+ originAppName: options.appName,
97
+ originAppVersion: options.appVersion,
98
+ originAppId: options.appId,
99
+ customHeaders: options.customHeaders
100
+ });
101
+ this.connectionManager = new ConnectionManager(this.logger, this.config);
102
+ Object.keys(extensions).forEach((extensionName) => {
103
+ const extension = new extensions[extensionName](this, options);
104
+ this.extensions.push(extension);
105
+ if (!extension.tokenBucket) {
106
+ // default rate limit
107
+ // 20 stanzas per 1000 ms,
108
+ // adding up to 25 stanzas over the course of the 1000ms
109
+ // starting with 20 stanzas
110
+ // = 45 stanzas max per 1000 ms
111
+ // = 70 stanzas max per 2000 ms
112
+ extension.tokenBucket = new TokenBucket(20, 25, 1000);
113
+ extension.tokenBucket.content = 25;
114
+ }
115
+ if (typeof extension.on === 'function') {
116
+ extension.on('send', this.handleSendEventFromExtension.bind(this, extension));
117
+ }
118
+ this[extensionName] = extension.expose;
119
+ this[`_${extensionName}`] = extension;
120
+ });
121
+ }
122
+ handleSendEventFromExtension(extension, data, message = false) {
123
+ return extension.tokenBucket.removeTokens(1, () => {
124
+ const stanza = this.activeStanzaInstance;
125
+ if (!stanza) {
126
+ return this.logger.warn('cannot send message, no active stanza client', { data, message }, { skipServer: true });
127
+ }
128
+ if (message === true) {
129
+ return stanza.sendMessage(data);
130
+ }
131
+ return stanza.sendIQ(data);
132
+ });
133
+ }
134
+ checkIsBackgroundAssistant() {
135
+ if (this.config.jwt) {
136
+ const jwt = parseJwt(this.config.jwt);
137
+ return jwt && jwt.iss === 'urn:purecloud:screenrecording';
138
+ }
139
+ return false;
140
+ }
141
+ addInateEventHandlers(stanza) {
142
+ // make sure we don't stack event handlers. There should only ever be *at most* one handler
143
+ this.removeStanzaBoundEventHandlers();
144
+ this.boundStanzaDisconnect = this.handleStanzaDisconnectedEvent.bind(this, stanza);
145
+ this.boundStanzaNoLongerSubscribed = this.handleNoLongerSubscribed.bind(this, stanza);
146
+ this.boundStanzaDuplicateId = this.handleDuplicateId.bind(this, stanza);
147
+ this.on(STANZA_DISCONNECTED, this.boundStanzaDisconnect);
148
+ this.on(NO_LONGER_SUBSCRIBED, this.boundStanzaNoLongerSubscribed);
149
+ this.on(DUPLICATE_ID, this.boundStanzaDuplicateId);
150
+ this.extensions.forEach(extension => {
151
+ if (typeof extension.handleIq === 'function') {
152
+ stanza.on('iq', extension.handleIq.bind(extension));
153
+ }
154
+ if (typeof extension.handleMessage === 'function') {
155
+ stanza.on('message', extension.handleMessage.bind(extension));
156
+ }
157
+ });
158
+ }
159
+ removeStanzaBoundEventHandlers() {
160
+ if (this.boundStanzaDisconnect) {
161
+ this.off(STANZA_DISCONNECTED, this.boundStanzaDisconnect);
162
+ this.boundStanzaDisconnect = undefined;
163
+ }
164
+ if (this.boundStanzaNoLongerSubscribed) {
165
+ this.off(NO_LONGER_SUBSCRIBED, this.boundStanzaNoLongerSubscribed);
166
+ this.boundStanzaNoLongerSubscribed = undefined;
167
+ }
168
+ if (this.boundStanzaDuplicateId) {
169
+ this.off(DUPLICATE_ID, this.boundStanzaDuplicateId);
170
+ this.boundStanzaDuplicateId = undefined;
171
+ }
172
+ }
173
+ proxyStanzaEvents(stanza) {
174
+ stanza.originalEmitter = stanza.emit;
175
+ stanza.emit = (eventName, ...args) => {
176
+ const hasListeners = stanza.originalEmitter(eventName, ...args);
177
+ // there are a few events that need to be handled specially. stanza emits a `connected` event
178
+ // which means the web socket connected but that doesn't mean it's not going to immediately close.
179
+ // For this reason, we are going to equate the `session:started` event as "connected" which
180
+ // essentially means the websocket connection is stable.
181
+ //
182
+ // we are also going to let streaming client control its own connected and disconnected state so
183
+ // we will emit those events separately "when we are ready".
184
+ // as per block comment, we'll ignore the connected event
185
+ if (eventName === 'connected') {
186
+ return hasListeners;
187
+ }
188
+ else if (eventName === 'disconnected') {
189
+ eventName = STANZA_DISCONNECTED;
190
+ }
191
+ return this.emit(eventName, ...args);
192
+ };
193
+ }
194
+ handleStanzaDisconnectedEvent(disconnectedInstance) {
195
+ var _a, _b;
196
+ return __awaiter(this, void 0, void 0, function* () {
197
+ this.logger.info('stanzaDisconnected event received', { stanzaInstanceId: disconnectedInstance.id, channelId: disconnectedInstance.channelId });
198
+ this.connected = false;
199
+ this.connecting = false;
200
+ (_a = disconnectedInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
201
+ (_b = disconnectedInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
202
+ this.removeAllListeners(STANZA_DISCONNECTED);
203
+ this.removeAllListeners(NO_LONGER_SUBSCRIBED);
204
+ // unproxy events
205
+ if (disconnectedInstance.originalEmitter) {
206
+ disconnectedInstance.emit = disconnectedInstance.originalEmitter;
207
+ }
208
+ this.activeStanzaInstance = undefined;
209
+ this.emit('disconnected', { reconnecting: this.autoReconnect });
210
+ if (this.autoReconnect) {
211
+ return this.connect({ keepTryingOnFailure: true })
212
+ .catch(error => {
213
+ this.logger.error('Failed to auto reconnect', {
214
+ keepTryingOnFailure: true,
215
+ stanzaInstanceId: disconnectedInstance.id,
216
+ channelId: disconnectedInstance.channelId
217
+ });
218
+ this.emit('disconnected', {
219
+ error,
220
+ reconnecting: false
221
+ });
222
+ });
223
+ }
224
+ });
225
+ }
226
+ handleNoLongerSubscribed(stanzaInstance) {
227
+ var _a, _b;
228
+ this.logger.warn('noLongerSubscribed event received', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
229
+ (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
230
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
231
+ this.hardReconnectRequired = true;
232
+ if (!this.reconnectOnNoLongerSubscribed) {
233
+ this.autoReconnect = false;
234
+ }
235
+ }
236
+ handleDuplicateId(stanzaInstance) {
237
+ var _a, _b;
238
+ this.logger.warn('duplicate_id event received, forcing hard reconnect', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
239
+ (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
240
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
241
+ this.hardReconnectRequired = true;
242
+ }
243
+ disconnect() {
244
+ return __awaiter(this, void 0, void 0, function* () {
245
+ this.logger.info('streamingClient.disconnect was called');
246
+ // Clear stored JID on client disconnect.
247
+ this.jidResource = '';
248
+ return timeoutPromise(resolve => {
249
+ this.hardReconnectRequired = true;
250
+ this.autoReconnect = false;
251
+ this.cancelConnectionAttempt = true;
252
+ this.http.stopAllRetries();
253
+ const currentStanza = this.connectionManager.currentStanzaInstance;
254
+ if (currentStanza) {
255
+ return currentStanza.disconnect().then(resolve);
256
+ }
257
+ else {
258
+ resolve();
259
+ }
260
+ }, 5000, 'disconnecting streaming service');
261
+ });
262
+ }
263
+ getSessionStoreKey() {
264
+ const differentiator = this.config.appName || this.logger.clientId;
265
+ return `${SESSION_STORE_KEY}_${differentiator}`;
266
+ }
267
+ getConnectionData() {
268
+ const connectionDataStr = sessionStorage.getItem(this.getSessionStoreKey());
269
+ const defaultValue = {
270
+ currentDelayMs: 0
271
+ };
272
+ if (connectionDataStr) {
273
+ try {
274
+ return JSON.parse(connectionDataStr);
275
+ }
276
+ catch (e) {
277
+ this.logger.warn('failed to parse streaming client connection data');
278
+ return defaultValue;
279
+ }
280
+ }
281
+ return defaultValue;
282
+ }
283
+ setConnectionData(data) {
284
+ sessionStorage.setItem(this.getSessionStoreKey(), JSON.stringify(data));
285
+ }
286
+ increaseBackoff() {
287
+ const connectionData = this.getConnectionData();
288
+ const currentDelay = Math.max(connectionData.currentDelayMs * 2, INITIAL_DELAY * 2);
289
+ const newConnectionData = {
290
+ currentDelayMs: currentDelay,
291
+ delayMsAfterNextReduction: currentDelay / 2,
292
+ nextDelayReductionTime: new Date().getTime() + (currentDelay * BACKOFF_DECREASE_DELAY_MULTIPLIER),
293
+ timeOfTotalReset: new Date().getTime() + 1000 * 60 * 60 // one hour in the future
294
+ };
295
+ this.setConnectionData(newConnectionData);
296
+ return newConnectionData;
297
+ }
298
+ decreaseBackoff(newAmountMs) {
299
+ const data = this.getConnectionData();
300
+ const msUntilNextReduction = newAmountMs * BACKOFF_DECREASE_DELAY_MULTIPLIER;
301
+ const newConnectionData = {
302
+ currentDelayMs: newAmountMs,
303
+ delayMsAfterNextReduction: newAmountMs / 2,
304
+ nextDelayReductionTime: new Date().getTime() + (msUntilNextReduction),
305
+ timeOfTotalReset: data.timeOfTotalReset
306
+ };
307
+ // if we are past the total reset time, do that instead
308
+ if (data.timeOfTotalReset && data.timeOfTotalReset < new Date().getTime() || newAmountMs < INITIAL_DELAY) {
309
+ this.logger.debug('decreaseBackoff() called, but timeOfTotalReset has elasped or next delay is below 2s. Resetting backoff');
310
+ return this.setConnectionData({
311
+ currentDelayMs: 0
312
+ });
313
+ }
314
+ this.setConnectionData(newConnectionData);
315
+ clearTimeout(this.backoffReductionTimer);
316
+ this.logger.debug('Setting timer for next backoff reduction since we haven\'t reached total reset', { msUntilReduction: msUntilNextReduction, delayMsAfterNextReduction: newConnectionData.delayMsAfterNextReduction });
317
+ this.backoffReductionTimer = setTimeout(() => this.decreaseBackoff(newConnectionData.delayMsAfterNextReduction), msUntilNextReduction);
318
+ }
319
+ getStartingDelay(connectionData, maxDelay) {
320
+ // we don't want the delay to ever be less than 2 seconds
321
+ const minDelay = Math.max(connectionData.currentDelayMs, INITIAL_DELAY);
322
+ if (connectionData.timeOfTotalReset && connectionData.timeOfTotalReset < new Date().getTime()) {
323
+ return INITIAL_DELAY;
324
+ }
325
+ return Math.min(minDelay, maxDelay);
326
+ }
327
+ connect(connectOpts) {
328
+ var _a;
329
+ return __awaiter(this, void 0, void 0, function* () {
330
+ this.cancelConnectionAttempt = false;
331
+ if (this.connecting) {
332
+ const error = new Error('Already trying to connect streaming client');
333
+ return this.logger.warn(error);
334
+ }
335
+ this.connecting = true;
336
+ const maxDelay = (connectOpts === null || connectOpts === void 0 ? void 0 : connectOpts.maxDelayBetweenConnectionAttempts) || 90000;
337
+ let maxAttempts = (connectOpts === null || connectOpts === void 0 ? void 0 : connectOpts.maxConnectionAttempts) || 1;
338
+ if (connectOpts === null || connectOpts === void 0 ? void 0 : connectOpts.keepTryingOnFailure) {
339
+ // this maintains the previous functionality
340
+ maxAttempts = Infinity;
341
+ }
342
+ clearTimeout(this.backoffReductionTimer);
343
+ const connectionData = this.getConnectionData();
344
+ const startingDelay = this.getStartingDelay(connectionData, maxDelay);
345
+ const delayFirstAttempt = this.hasMadeInitialAttempt;
346
+ this.hasMadeInitialAttempt = true;
347
+ if (connectionData.currentDelayMs) {
348
+ this.logger.debug('streamingClient.connect was called, but backoff is remembered', { currentDelayMs: connectionData.currentDelayMs, delayingThisAttempt: delayFirstAttempt, clientId: this.logger.clientId, appName: this.config.appName });
349
+ }
350
+ try {
351
+ yield backOff(() => __awaiter(this, void 0, void 0, function* () {
352
+ const connectionData = this.getConnectionData();
353
+ yield this.makeConnectionAttempt();
354
+ if (connectionData.nextDelayReductionTime) {
355
+ const msUntilReduction = connectionData.nextDelayReductionTime - new Date().getTime();
356
+ this.logger.debug('Setting timer for next backoff reduction', { msUntilReduction, delayMsAfterNextReduction: connectionData.delayMsAfterNextReduction });
357
+ this.backoffReductionTimer = setTimeout(() => this.decreaseBackoff(connectionData.delayMsAfterNextReduction || 0), msUntilReduction);
358
+ }
359
+ }), {
360
+ jitter: 'none',
361
+ maxDelay,
362
+ numOfAttempts: maxAttempts,
363
+ startingDelay,
364
+ delayFirstAttempt,
365
+ retry: this.backoffConnectRetryHandler.bind(this, {
366
+ maxConnectionAttempts: maxAttempts
367
+ })
368
+ });
369
+ }
370
+ catch (err) {
371
+ let errorForThrowing;
372
+ let errorForLogging = err;
373
+ // Check `cancelConnectionAttempt` instead of the error type in case a different error occurred
374
+ // around the same time that might mask the cancellation.
375
+ if (this.cancelConnectionAttempt) {
376
+ errorForThrowing = new StreamingClientError(StreamingClientErrorTypes.userCancelled, 'Streaming client connection cancelled', err);
377
+ errorForLogging = errorForThrowing;
378
+ }
379
+ else if (!err) {
380
+ errorForThrowing = new StreamingClientError(StreamingClientErrorTypes.generic, 'Streaming client connection attempted and received an undefined error');
381
+ errorForLogging = errorForThrowing;
382
+ }
383
+ else if (err.name === 'AxiosError') {
384
+ const axiosError = err;
385
+ const config = axiosError.config || { url: undefined, method: undefined };
386
+ // sanitized error for logging
387
+ errorForLogging = {
388
+ config: {
389
+ url: config.url,
390
+ method: config.method
391
+ },
392
+ status: (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.status,
393
+ code: axiosError.code,
394
+ name: axiosError.name,
395
+ message: axiosError.message
396
+ };
397
+ errorForThrowing = new StreamingClientError(StreamingClientErrorTypes.generic, 'Failed to connect streaming client due to network error', err);
398
+ if (this.networkErrorNeedsAuth(err)) {
399
+ errorForThrowing = new StreamingClientError(StreamingClientErrorTypes.invalid_token, 'Failed to connect streaming client due to invalid token', err);
400
+ }
401
+ }
402
+ else if (err instanceof SaslError) {
403
+ errorForThrowing = new StreamingClientError(StreamingClientErrorTypes.invalid_token, 'Failed to connect streaming client due to invalid token', err);
404
+ if (this.saslErrorIsRetryable(err)) {
405
+ errorForThrowing = new StreamingClientError(StreamingClientErrorTypes.generic, 'Streaming client connection attempted and received a SASL error', err);
406
+ }
407
+ }
408
+ else {
409
+ errorForThrowing = new StreamingClientError(StreamingClientErrorTypes.generic, 'Streaming client connection attempted and received an unknown error', err);
410
+ }
411
+ this.logger.error('Failed to connect streaming client', { error: errorForLogging });
412
+ throw errorForThrowing;
413
+ }
414
+ });
415
+ }
416
+ backoffConnectRetryHandler(connectOpts, err, connectionAttempt) {
417
+ var _a, _b, _c, _d;
418
+ return __awaiter(this, void 0, void 0, function* () {
419
+ // Check `cancelConnectionAttempt` instead of the error type in case a different error occurred
420
+ // around the same time that might allow for retries to continue.
421
+ if (this.cancelConnectionAttempt) {
422
+ return false;
423
+ }
424
+ // if we exceed the `numOfAttempts` in the backoff config it still calls this retry fn and just ignores the result
425
+ // if that's the case, we just want to bail out and ignore all the extra logging here.
426
+ if (connectionAttempt >= connectOpts.maxConnectionAttempts) {
427
+ return false;
428
+ }
429
+ const additionalErrorDetails = { connectionAttempt, error: err };
430
+ if (!err) {
431
+ additionalErrorDetails.error = new Error('streaming client backoff handler received undefined error');
432
+ }
433
+ else if (err.name === 'AxiosError') {
434
+ const axiosError = err;
435
+ const config = axiosError.config || { url: undefined, method: undefined };
436
+ const sanitizedError = {
437
+ config: {
438
+ url: config.url,
439
+ method: config.method
440
+ },
441
+ status: (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.status,
442
+ code: axiosError.code,
443
+ name: axiosError.name,
444
+ message: axiosError.message
445
+ };
446
+ additionalErrorDetails.error = sanitizedError;
447
+ if (this.networkErrorNeedsAuth(err)) {
448
+ this.logger.error('Streaming client received an error that it can\'t recover from and will not attempt to reconnect', additionalErrorDetails);
449
+ return false;
450
+ }
451
+ }
452
+ // If we get a sasl error, that means we made it all the way to the point of trying to open a websocket and
453
+ // it was rejected for some reason. Some errors might resolve if we try connecting again. Others need
454
+ // re-authentication.
455
+ if (err instanceof SaslError) {
456
+ if (this.saslErrorIsRetryable(err)) {
457
+ this.logger.info('hardReconnectRequired set to true due to sasl error');
458
+ this.hardReconnectRequired = true;
459
+ Object.assign(additionalErrorDetails, { channelId: err.channelId, stanzaInstanceId: err.stanzaInstanceId });
460
+ }
461
+ else {
462
+ additionalErrorDetails.error = err.condition;
463
+ this.logger.error('Streaming-client received a SASL error that it can\'t recover from and will not attempt to reconnect', additionalErrorDetails);
464
+ return false;
465
+ }
466
+ }
467
+ // we don't need to log the stack for a timeout message
468
+ if (err instanceof TimeoutError) {
469
+ additionalErrorDetails.error = err.message;
470
+ const details = err.details;
471
+ if (details) {
472
+ additionalErrorDetails.details = details;
473
+ }
474
+ }
475
+ if (err === null || err === void 0 ? void 0 : err.response) {
476
+ // This *should* be an axios error according to typings, but it appears this could be an AxiosError *or* and XmlHttpRequest
477
+ // we'll check both to be safe
478
+ const retryAfter = ((_b = err.response.headers) === null || _b === void 0 ? void 0 : _b['retry-after']) || ((_d = (_c = err.response).getResponseHeader) === null || _d === void 0 ? void 0 : _d.call(_c, 'retry-after'));
479
+ if (retryAfter) {
480
+ // retry after comes in seconds, we need to return milliseconds
481
+ const retryDelay = parseInt(retryAfter, 10) * 1000;
482
+ additionalErrorDetails.retryDelay = retryDelay;
483
+ this.logger.error('Failed streaming client connection attempt, respecting retry-after header and will retry afterwards.', additionalErrorDetails);
484
+ yield delay(retryDelay);
485
+ this.logger.debug('finished waiting for retry-after');
486
+ return true;
487
+ }
488
+ }
489
+ const connectionData = this.increaseBackoff();
490
+ this.logger.error('Failed streaming client connection attempt, retrying', additionalErrorDetails);
491
+ this.logger.debug('debug: retry info', { expectedRetryInMs: connectionData.currentDelayMs, appName: this.config.appName, clientId: this.logger.clientId });
492
+ return true;
493
+ });
494
+ }
495
+ networkErrorNeedsAuth(error) {
496
+ var _a;
497
+ return [401, 403].includes(((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) || 0);
498
+ }
499
+ saslErrorIsRetryable(error) {
500
+ const retryConditions = ['encryption-required', 'incorrect-encoding', 'invalid-mechanism', 'malformed-request', 'mechanism-too-weak'];
501
+ return retryConditions.includes(error.condition);
502
+ }
503
+ /**
504
+ * Performs an active network connectivity check by querying the API.
505
+ * navigator.onLine is unreliable (VPNs, virtual adapters, etc.), so we
506
+ * actually reach out to verify we can talk to the server.
507
+ *
508
+ * Returns true if connectivity is confirmed, false otherwise.
509
+ * This is advisory only — it does not gate connection attempts.
510
+ */
511
+ checkNetworkConnectivity() {
512
+ return __awaiter(this, void 0, void 0, function* () {
513
+ // Quick hint check first — if the browser says offline, that's a strong signal
514
+ if (!navigator.onLine) {
515
+ this.logger.warn('navigator.onLine reports offline — connectivity may be unavailable');
516
+ this.emit('networkConnectivityWarning', { reason: 'navigator.onLine is false' });
517
+ return false;
518
+ }
519
+ // JWT-based connections (e.g. background assistants) don't have an auth token
520
+ // that works with the users/me endpoint, so we can only rely on navigator.onLine
521
+ if (this.config.jwt && !this.config.authToken) {
522
+ this.logger.debug('Skipping active connectivity check in JWT mode, relying on navigator.onLine');
523
+ return true;
524
+ }
525
+ try {
526
+ const opts = {
527
+ method: 'get',
528
+ host: this.config.apiHost,
529
+ authToken: this.config.authToken,
530
+ logger: this.logger,
531
+ requestTimeout: 10000
532
+ };
533
+ yield this.http.requestApi('users/me', opts);
534
+ return true;
535
+ }
536
+ catch (err) {
537
+ this.logger.warn('Active network connectivity check failed — connectivity may be unavailable', { error: err });
538
+ this.emit('networkConnectivityWarning', { reason: 'active connectivity check failed', error: err });
539
+ return false;
540
+ }
541
+ });
542
+ }
543
+ makeConnectionAttempt() {
544
+ var _a, _b;
545
+ return __awaiter(this, void 0, void 0, function* () {
546
+ if (this.cancelConnectionAttempt) {
547
+ throw new UserCancelledError('Connection attempt cancelled');
548
+ }
549
+ // navigator.onLine is unreliable — use it as a hint, not a gate.
550
+ // Fire off an active connectivity check in the background. It will
551
+ // log and emit warnings if there's an issue, but we don't wait for it.
552
+ this.checkNetworkConnectivity().then(isConnected => {
553
+ if (!isConnected) {
554
+ this.logger.warn('Network connectivity check failed, but proceeding with connection attempt anyway');
555
+ }
556
+ });
557
+ let stanzaInstance;
558
+ const previousConnectingState = this.connecting;
559
+ try {
560
+ yield this.prepareForConnect();
561
+ if (this.cancelConnectionAttempt) {
562
+ throw new UserCancelledError('Connection attempt cancelled');
563
+ }
564
+ stanzaInstance = yield this.connectionManager.getNewStanzaConnection();
565
+ this.addInateEventHandlers(stanzaInstance);
566
+ this.proxyStanzaEvents(stanzaInstance);
567
+ // handle any extension configuration
568
+ for (const extension of this.extensions) {
569
+ if (extension.configureNewStanzaInstance) {
570
+ yield extension.configureNewStanzaInstance(stanzaInstance);
571
+ }
572
+ }
573
+ for (const extension of this.extensions) {
574
+ extension.handleStanzaInstanceChange(stanzaInstance);
575
+ }
576
+ yield this.setupConnectionMonitoring(stanzaInstance);
577
+ this.activeStanzaInstance = stanzaInstance;
578
+ this.connected = true;
579
+ this.connecting = false;
580
+ this.emit('connected');
581
+ }
582
+ catch (err) {
583
+ if (stanzaInstance) {
584
+ this.logger.error('Error occurred in connection attempt, but after websocket connected. Cleaning up connection so backoff is respected', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
585
+ this.removeStanzaBoundEventHandlers();
586
+ (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
587
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
588
+ yield stanzaInstance.disconnect();
589
+ this.connected = false;
590
+ this.connecting = previousConnectingState;
591
+ }
592
+ throw err;
593
+ }
594
+ });
595
+ }
596
+ setupConnectionMonitoring(stanzaInstance) {
597
+ return __awaiter(this, void 0, void 0, function* () {
598
+ stanzaInstance.connectionTransfer = new ConnectionTransfer(this, stanzaInstance);
599
+ const setupClientPinger = (message) => {
600
+ const logMessage = `${message}, falling back to client-side pinging`;
601
+ this.logger.warn(logMessage, { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
602
+ stanzaInstance.pinger = new Ping(this, stanzaInstance);
603
+ };
604
+ if (this.useServerSidePings) {
605
+ try {
606
+ // if this fails, then hawk doesn't support serverside pinging and we need to do client side pings
607
+ yield stanzaInstance.subscribeToNode(this._notifications.pubsubHost, 'enable.server.side.pings');
608
+ stanzaInstance.serverMonitor = new ServerMonitor(this, stanzaInstance);
609
+ }
610
+ catch (err) {
611
+ setupClientPinger('failed to establish server-side pinging');
612
+ }
613
+ }
614
+ else {
615
+ setupClientPinger('client configured to not use server-side pinging');
616
+ }
617
+ });
618
+ }
619
+ prepareForConnect() {
620
+ return __awaiter(this, void 0, void 0, function* () {
621
+ if (this.config.jwt) {
622
+ this.hardReconnectRequired = false;
623
+ return this.connectionManager.setConfig(this.config);
624
+ }
625
+ if (!this.hardReconnectRequired) {
626
+ this.channelReuses++;
627
+ if (this.channelReuses >= MAX_CHANNEL_REUSES) {
628
+ this.logger.warn('Forcing a hard reconnect due to max channel reuses', { channelId: this.config.channelId, channelReuses: this.channelReuses });
629
+ this.channelReuses = 0;
630
+ this.hardReconnectRequired = true;
631
+ }
632
+ }
633
+ if (this.hardReconnectRequired) {
634
+ let userPromise;
635
+ if (this.config.userId && this.config.jid) {
636
+ userPromise = Promise.resolve({ userId: this.config.userId, jid: this.config.jid });
637
+ }
638
+ else {
639
+ const jidRequestOpts = {
640
+ method: 'get',
641
+ host: this.config.apiHost,
642
+ authToken: this.config.authToken,
643
+ logger: this.logger
644
+ };
645
+ userPromise = this.http.requestApi('users/me', jidRequestOpts)
646
+ .then(res => {
647
+ return {
648
+ userId: res.data.id,
649
+ jid: res.data.chat.jabberId
650
+ };
651
+ });
652
+ }
653
+ // If no jidResource is provided, generate a random one to maintain ourselves.
654
+ this.jidResource = this.config.jidResource || globalThis.crypto.randomUUID();
655
+ const channelRequestOpts = {
656
+ method: 'post',
657
+ host: this.config.apiHost,
658
+ authToken: this.config.authToken,
659
+ logger: this.logger
660
+ };
661
+ const channelPromise = yield this.http.requestApi('notifications/channels?connectionType=streaming', channelRequestOpts)
662
+ .then(res => res.data.id);
663
+ const [{ userId, jid }, channelId] = yield Promise.all([userPromise, channelPromise]);
664
+ this.config.userId = userId;
665
+ this.config.jid = jid;
666
+ this.config.jidResource = this.jidResource;
667
+ this.config.channelId = channelId;
668
+ this.autoReconnect = true;
669
+ this.logger.info('attempting to connect streaming client on channel', { channelId });
670
+ this.connectionManager.setConfig(this.config);
671
+ this.hardReconnectRequired = false;
672
+ }
673
+ });
674
+ }
675
+ stopServerLogging() {
676
+ /* flush all pending logs – then turn off the logger */
677
+ this.logger.sendAllLogsInstantly();
678
+ this.logger.stopServerLogging();
679
+ }
680
+ startServerLogging() {
681
+ this.logger.startServerLogging();
682
+ }
683
+ setAccessToken(token) {
684
+ this.config.authToken = token;
685
+ this.logger.setAccessToken(token);
686
+ }
687
+ static extend(namespace, extension) {
688
+ if (extensions[namespace]) {
689
+ throw new Error(`Cannot register already existing namespace ${namespace}`);
690
+ }
691
+ extensions[namespace] = extension;
692
+ }
693
+ get version() {
694
+ return Client.version;
695
+ }
696
+ static get version() {
697
+ return '20.1.0';
698
+ }
699
+ }