@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,507 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import debounce from 'debounce-promise';
11
+ import { splitIntoIndividualTopics } from './utils';
12
+ import { StreamingSubscriptionError } from './';
13
+ const PUBSUB_HOST_DEFAULT = 'notifications.mypurecloud.com';
14
+ const MAX_SUBSCRIBABLE_TOPICS = 1000;
15
+ const DROPPED_TOPICS_DISPLAY_COUNT = 20;
16
+ const DEFAULT_PRIORITY = 0;
17
+ function mergeAndDedup(arr1, arr2) {
18
+ return [...arr1, ...arr2].filter((t, i, arr) => arr.indexOf(t) === i);
19
+ }
20
+ export class Notifications {
21
+ constructor(client, options) {
22
+ var _a;
23
+ this.subscriptions = {};
24
+ this.bulkSubscriptions = {};
25
+ this.topicPriorities = {};
26
+ this.internalSubscriptions = [];
27
+ this.client = client;
28
+ this.enablePartialBulkResubscribe = (_a = options === null || options === void 0 ? void 0 : options.enablePartialBulkResubscribe) !== null && _a !== void 0 ? _a : false;
29
+ client.on('pubsub:event', this.pubsubEvent.bind(this));
30
+ client.on('connected', this.subscriptionsKeepAlive.bind(this));
31
+ this.debouncedResubscribe = debounce(this.resubscribe.bind(this), 100);
32
+ }
33
+ get pubsubHost() {
34
+ try {
35
+ const host = this.client.config.apiHost.replace(/http(s?):\/\//, '');
36
+ return `notifications.${host}`;
37
+ }
38
+ catch (e) {
39
+ return PUBSUB_HOST_DEFAULT;
40
+ }
41
+ }
42
+ handleStanzaInstanceChange(stanza) {
43
+ // we need to resub if we go through a hard reconnect
44
+ const needsToResub = this.stanzaInstance &&
45
+ this.stanzaInstance.id !== stanza.id &&
46
+ this.stanzaInstance.channelId !== stanza.channelId;
47
+ this.stanzaInstance = stanza;
48
+ if (needsToResub) {
49
+ this.client.logger.info('resubscribing due to hard reconnect');
50
+ this.debouncedResubscribe().catch((err) => {
51
+ const msg = 'Error resubscribing to topics';
52
+ this.client.logger.error(msg, err);
53
+ this.client.emit('pubsub:error', { msg, err });
54
+ });
55
+ }
56
+ }
57
+ topicHandlers(topic) {
58
+ if (!this.subscriptions[topic]) {
59
+ this.subscriptions[topic] = [];
60
+ }
61
+ return this.subscriptions[topic];
62
+ }
63
+ pubsubEvent({ pubsub }) {
64
+ let topic = pubsub.items.node;
65
+ if (topic.includes('no_longer_subscribed')) {
66
+ topic = 'no_longer_subscribed';
67
+ }
68
+ else if (topic.includes('duplicate_id')) {
69
+ topic = 'duplicate_id';
70
+ }
71
+ const payload = pubsub.items.published[0].content.json;
72
+ const handlers = this.topicHandlers(topic);
73
+ this.client.emit('notify', { topic, data: payload });
74
+ this.client.emit(`notify:${topic}`, payload);
75
+ handlers.forEach((handler) => {
76
+ handler(payload);
77
+ });
78
+ }
79
+ xmppSubscribe(topic) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ if (this.topicHandlers(topic).length !== 0 || this.bulkSubscriptions[topic]) {
82
+ return Promise.resolve();
83
+ }
84
+ const subscribe = () => this.stanzaInstance.subscribeToNode(this.pubsubHost, topic);
85
+ if (this.client.connected) {
86
+ return subscribe();
87
+ }
88
+ else {
89
+ return new Promise((resolve, reject) => {
90
+ this.client.once('connected', () => {
91
+ return subscribe().then(resolve, reject);
92
+ });
93
+ });
94
+ }
95
+ });
96
+ }
97
+ xmppUnsubscribe(topic) {
98
+ if (this.topicHandlers(topic).length !== 0 || this.bulkSubscriptions[topic]) {
99
+ return Promise.resolve();
100
+ }
101
+ const unsubscribe = () => this.stanzaInstance.unsubscribeFromNode(this.pubsubHost, topic);
102
+ if (this.client.connected) {
103
+ return unsubscribe();
104
+ }
105
+ else {
106
+ return new Promise((resolve, reject) => {
107
+ this.client.once('connected', () => {
108
+ return unsubscribe().then(resolve, reject);
109
+ });
110
+ });
111
+ }
112
+ }
113
+ mapCombineTopics(topics) {
114
+ const prefixes = {};
115
+ const precombinedTopics = [];
116
+ const uncombinedTopics = [];
117
+ topics.forEach(t => {
118
+ if (t.includes('?')) {
119
+ precombinedTopics.push({ id: t });
120
+ }
121
+ else {
122
+ uncombinedTopics.push(t);
123
+ }
124
+ });
125
+ uncombinedTopics.map(t => {
126
+ const split = t.split('.');
127
+ const postfix = split.splice(split.length - 1);
128
+ const prefix = split.join('.');
129
+ return { prefix, postfix };
130
+ }).forEach(t => {
131
+ if (prefixes[t.prefix]) {
132
+ prefixes[t.prefix].push(t.postfix);
133
+ }
134
+ else {
135
+ prefixes[t.prefix] = [t.postfix];
136
+ }
137
+ });
138
+ const combinedTopics = [];
139
+ // Max length of 200 in topic names
140
+ // so recursively break them up if the combined length exceeds 200
141
+ const combineTopics = (prefix, postFixes) => {
142
+ const delimiter = postFixes.length === 1 ? '.' : '?';
143
+ const id = `${prefix}${delimiter}${postFixes.join('&')}`;
144
+ if (id.length < 200) {
145
+ combinedTopics.push({ id });
146
+ }
147
+ else if (postFixes.length === 1) {
148
+ this.client.logger.error('Refusing to attempt topic with length > 200', id);
149
+ }
150
+ else {
151
+ combineTopics(prefix, postFixes.slice(0, postFixes.length / 2));
152
+ combineTopics(prefix, postFixes.slice(postFixes.length / 2));
153
+ }
154
+ };
155
+ Object.keys(prefixes).forEach(prefix => {
156
+ const postFixes = prefixes[prefix];
157
+ combineTopics(prefix, postFixes);
158
+ });
159
+ const allTopics = combinedTopics.concat(precombinedTopics);
160
+ return this.truncateTopicList(this.prioritizeTopicList(allTopics));
161
+ }
162
+ prioritizeTopicList(topics) {
163
+ topics.sort((topicA, topicB) => {
164
+ return (this.getTopicPriority(topicB.id) - this.getTopicPriority(topicA.id));
165
+ });
166
+ return topics;
167
+ }
168
+ getTopicPriority(topic, returnDefault = true) {
169
+ const { prefix, postfixes } = this.getTopicParts(topic);
170
+ const oldPriorities = this.topicPriorities[prefix];
171
+ const matches = oldPriorities && Object.keys(oldPriorities).filter(p => postfixes.includes(p)).map(p => oldPriorities[p]);
172
+ const priority = matches && matches.length && matches.reduce((max, current) => current > max ? current : max);
173
+ return returnDefault ? priority || DEFAULT_PRIORITY : priority;
174
+ }
175
+ truncateTopicList(topics) {
176
+ const keptTopics = topics.slice(0, MAX_SUBSCRIBABLE_TOPICS);
177
+ if (topics.length > MAX_SUBSCRIBABLE_TOPICS) {
178
+ let droppedTopics = topics.slice(MAX_SUBSCRIBABLE_TOPICS);
179
+ if (droppedTopics.length > DROPPED_TOPICS_DISPLAY_COUNT) {
180
+ const length = droppedTopics.length - DROPPED_TOPICS_DISPLAY_COUNT;
181
+ droppedTopics = droppedTopics.slice(DROPPED_TOPICS_DISPLAY_COUNT);
182
+ droppedTopics.push(`...and ${length} more`);
183
+ }
184
+ this.client.logger.warn('Too many topics to subscribe to; truncating extra topics', { droppedTopics });
185
+ }
186
+ return keptTopics;
187
+ }
188
+ makeBulkSubscribeRequest(topics, options) {
189
+ return __awaiter(this, void 0, void 0, function* () {
190
+ // Cancel any in-flight retry or connected-wait from a previous bulk subscribe.
191
+ // If the topic list has changed (subscribe/unsubscribe called between retries),
192
+ // the stale request would send an outdated topic list. Since bulk subscribes with
193
+ // replace=true use PUT (full replacement), only the most recent request matters.
194
+ if (this.pendingConnectedWait) {
195
+ this.pendingConnectedWait.cancel(new Error('Superseded by newer bulk subscribe request'));
196
+ this.pendingConnectedWait = undefined;
197
+ }
198
+ if (this.pendingBulkSubscribeRetry && !this.pendingBulkSubscribeRetry.hasCompleted()) {
199
+ this.client.logger.info('Cancelling previous bulk subscribe retry — new request supersedes it');
200
+ this.pendingBulkSubscribeRetry.cancel(new Error('Superseded by newer bulk subscribe request'));
201
+ }
202
+ // If the client is not connected, wait for reconnection before attempting the request.
203
+ // This prevents firing requests against a dead/stale channel ID which would result in
204
+ // 400 errors (notification.unable.to.get.channel.id). After reconnection, the stanza
205
+ // instance will have the fresh channel ID from the new connection.
206
+ if (!this.client.connected) {
207
+ this.client.logger.info('Client not connected, waiting for reconnection before bulk subscribe', {
208
+ topicCount: topics.length
209
+ });
210
+ yield new Promise((resolve, reject) => {
211
+ const onConnected = () => {
212
+ this.pendingConnectedWait = undefined;
213
+ resolve();
214
+ };
215
+ this.client.once('connected', onConnected);
216
+ this.pendingConnectedWait = {
217
+ cancel: (reason) => {
218
+ this.client.off('connected', onConnected);
219
+ reject(reason);
220
+ }
221
+ };
222
+ });
223
+ }
224
+ const requestOptions = {
225
+ method: options.replace ? 'put' : 'post',
226
+ host: this.client.config.apiHost,
227
+ authToken: this.client.config.authToken,
228
+ data: JSON.stringify(this.mapCombineTopics(topics)),
229
+ logger: this.client.logger,
230
+ maxAttempts: 3
231
+ };
232
+ const channelId = this.stanzaInstance.channelId;
233
+ let path = `notifications/channels/${channelId}/subscriptions`;
234
+ if (this.enablePartialBulkResubscribe) {
235
+ path += '?ignoreErrors=true';
236
+ }
237
+ const retry = this.client.http.requestApiWithRetry(path, requestOptions, 2000);
238
+ this.pendingBulkSubscribeRetry = retry;
239
+ return retry.promise;
240
+ });
241
+ }
242
+ createSubscription(topic, handler) {
243
+ const topics = splitIntoIndividualTopics(topic);
244
+ topics.forEach(t => {
245
+ const handlers = this.topicHandlers(t);
246
+ if (!handlers.includes(handler)) {
247
+ handlers.push(handler);
248
+ }
249
+ });
250
+ }
251
+ removeSubscription(topic, handler) {
252
+ const topics = splitIntoIndividualTopics(topic);
253
+ topics.forEach(t => {
254
+ const handlers = this.topicHandlers(t);
255
+ const handlerIndex = handlers.indexOf(handler);
256
+ if (handlerIndex > -1) {
257
+ handlers.splice(handlerIndex, 1);
258
+ }
259
+ /* if we have removed all our individual handlers, we don't need the topic anymore
260
+ (note: we aren't removing any bulkSubs if they exist for this topic) */
261
+ if (!handlers.length) {
262
+ delete this.subscriptions[t];
263
+ }
264
+ });
265
+ }
266
+ removeTopicPriority(topic) {
267
+ if (this.getTopicPriority(topic, false)) {
268
+ const { prefix, postfixes } = this.getTopicParts(topic);
269
+ postfixes.forEach(postfix => {
270
+ delete this.topicPriorities[prefix][postfix];
271
+ });
272
+ if (!Object.keys(this.topicPriorities[prefix]).length) {
273
+ delete this.topicPriorities[prefix];
274
+ }
275
+ }
276
+ }
277
+ getActiveIndividualTopics() {
278
+ const activeTopics = [];
279
+ const topics = Object.keys(this.subscriptions);
280
+ topics.forEach(topic => {
281
+ if (topic === 'streaming-subscriptions-expiring') {
282
+ return; // this doesn't need subscribed
283
+ }
284
+ const handlers = this.topicHandlers(topic);
285
+ if (handlers.length > 0) {
286
+ activeTopics.push(topic);
287
+ }
288
+ });
289
+ return activeTopics;
290
+ }
291
+ resubscribe() {
292
+ const bulkSubs = Object.keys(this.bulkSubscriptions);
293
+ /* if we don't have bulk or individual subs, we don't need to resubscribe */
294
+ const noTopics = bulkSubs.length + this.getActiveIndividualTopics().length === 0;
295
+ if (noTopics) {
296
+ return Promise.resolve({});
297
+ }
298
+ /* only pass in bulk subs with the replace flag – bulkSubscribe() will handle merging our individual topics (see PCM-1846) */
299
+ return this.bulkSubscribe(bulkSubs, { replace: true });
300
+ }
301
+ subscriptionsKeepAlive() {
302
+ const topic = 'streaming-subscriptions-expiring';
303
+ if (this.topicHandlers(topic).length === 0) {
304
+ this.createSubscription(topic, () => {
305
+ this.client.logger.info(`${topic} - Triggering resubscribe.`, { channelId: this.client.config.channelId });
306
+ this.debouncedResubscribe().catch((err) => {
307
+ const msg = 'Error resubscribing to topics';
308
+ this.client.logger.error(msg, err);
309
+ this.client.emit('pubsub:error', { msg, err });
310
+ });
311
+ });
312
+ }
313
+ }
314
+ getTopicParts(topic) {
315
+ const isCombined = topic.includes('?');
316
+ const separator = isCombined ? '?' : '.';
317
+ const split = topic.split(separator);
318
+ const postfix = isCombined ? split[1] : split.splice(split.length - 1);
319
+ const prefix = isCombined ? split[0] : split.join('.');
320
+ let postfixes = [];
321
+ if (isCombined) {
322
+ postfixes = postfix.split('&');
323
+ }
324
+ else {
325
+ postfixes = postfix;
326
+ }
327
+ return { prefix, postfixes };
328
+ }
329
+ setTopicPriorities(priorities = {}) {
330
+ Object.keys(priorities).forEach(priority => {
331
+ const topicParts = this.getTopicParts(priority);
332
+ const oldPriorities = this.topicPriorities[topicParts.prefix];
333
+ const newPriority = priorities[priority];
334
+ if (oldPriorities) {
335
+ topicParts.postfixes.forEach(postfix => {
336
+ const oldPriority = oldPriorities[postfix];
337
+ if ((oldPriority && oldPriority < newPriority) || !oldPriority) {
338
+ oldPriorities[postfix] = newPriority;
339
+ }
340
+ });
341
+ }
342
+ else {
343
+ const newTopics = topicParts.postfixes.reduce((newTopics, p) => {
344
+ newTopics[p] = newPriority;
345
+ return newTopics;
346
+ }, {});
347
+ this.topicPriorities[topicParts.prefix] = newTopics;
348
+ }
349
+ });
350
+ }
351
+ subscribe(topic, handler, immediate, priority) {
352
+ return __awaiter(this, void 0, void 0, function* () {
353
+ if (priority) {
354
+ this.setTopicPriorities({ [topic]: priority });
355
+ }
356
+ let promise;
357
+ if (!immediate) {
358
+ // let this and any other subscribe/unsubscribe calls roll in, then trigger a whole resubscribe
359
+ promise = this.debouncedResubscribe();
360
+ }
361
+ else {
362
+ promise = this.xmppSubscribe(topic);
363
+ }
364
+ if (handler) {
365
+ this.createSubscription(topic, handler);
366
+ }
367
+ else {
368
+ this.bulkSubscriptions[topic] = true;
369
+ }
370
+ const result = yield promise;
371
+ // Assume topic subscription succeeded if promise is resolved...
372
+ let topicResult = { topic, state: 'Permitted' };
373
+ // ... but if partial bulk resubscribe is enabled, use topic's individual result from the API response.
374
+ if (this.enablePartialBulkResubscribe && result && typeof result === 'object' && isTopicSubscribeResult(result[topic])) {
375
+ topicResult = result[topic];
376
+ }
377
+ // Topic result other than state=Permitted becomes a StreamingSubscriptionError promise rejection.
378
+ if (topicResult.state !== 'Permitted') {
379
+ const message = topicResult.rejectionReason || `Failed to subscribe topic ${topic}`;
380
+ const missingPermissions = topicResult.missingPermissions;
381
+ throw new StreamingSubscriptionError(message, topic, 'subscribe', { missingPermissions });
382
+ }
383
+ return topicResult;
384
+ });
385
+ }
386
+ /**
387
+ * Use `_subscribeInternal` when subscribing to a topic from within streaming-client itself.
388
+ * Internal subscriptions won't be overwritten by subscriptions from consumers and will be prioritized
389
+ * over subscriptions from consumers.
390
+ */
391
+ _subscribeInternal(topic) {
392
+ return __awaiter(this, void 0, void 0, function* () {
393
+ if (this.internalSubscriptions.includes(topic)) {
394
+ return Promise.resolve();
395
+ }
396
+ this.setTopicPriorities({ [topic]: Number.MAX_VALUE });
397
+ const promise = this.xmppSubscribe(topic);
398
+ this.internalSubscriptions.push(topic);
399
+ this.bulkSubscriptions[topic] = true;
400
+ return promise;
401
+ });
402
+ }
403
+ unsubscribe(topic, handler, immediate) {
404
+ if (handler) {
405
+ this.removeSubscription(topic, handler);
406
+ }
407
+ else {
408
+ delete this.bulkSubscriptions[topic];
409
+ delete this.subscriptions[topic];
410
+ }
411
+ this.removeTopicPriority(topic);
412
+ if (!immediate) {
413
+ // let this and any other subscribe/unsubscribe calls roll in, then trigger a whole resubscribe
414
+ return this.debouncedResubscribe();
415
+ }
416
+ return this.xmppUnsubscribe(topic);
417
+ }
418
+ bulkSubscribe(topics, options = { replace: false, force: false }, priorities = {}) {
419
+ var _a, _b;
420
+ return __awaiter(this, void 0, void 0, function* () {
421
+ this.setTopicPriorities(priorities);
422
+ let toSubscribe = mergeAndDedup(topics, this.internalSubscriptions);
423
+ if (options.replace && !options.force) {
424
+ // if this is a bulk subscription, but not a forcible one, keep all individual subscriptions
425
+ toSubscribe = mergeAndDedup(toSubscribe, this.getActiveIndividualTopics());
426
+ }
427
+ else if (options.force) {
428
+ // if it's a forcible bulk subscribe, wipe out individual subscriptions
429
+ this.subscriptions = {};
430
+ }
431
+ let response;
432
+ try {
433
+ response = yield this.makeBulkSubscribeRequest(toSubscribe, options);
434
+ }
435
+ catch (err) {
436
+ // If this request was cancelled because a newer bulk subscribe superseded it,
437
+ // return an 'Unknown' state for all topics. The superseding request will carry
438
+ // the current desired topic list and handle the actual subscription.
439
+ if ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes('Superseded by newer bulk subscribe request')) {
440
+ this.client.logger.debug('Bulk subscribe was superseded, deferring to newer request');
441
+ const result = {};
442
+ for (const topic of toSubscribe) {
443
+ result[topic] = { topic, state: 'Unknown' };
444
+ }
445
+ return result;
446
+ }
447
+ throw err;
448
+ }
449
+ let topicResponseEntities = [];
450
+ if (response && response.data && 'entities' in response.data && Array.isArray(response.data.entities)) {
451
+ topicResponseEntities = response.data.entities;
452
+ }
453
+ const result = {};
454
+ for (const topicEntity of topicResponseEntities) {
455
+ const { id, state, rejectionReason, missingPermissions } = topicEntity;
456
+ result[id] = { topic: id, state, rejectionReason, missingPermissions };
457
+ // If response entity is a combined topic ID like "a.b?c&d" include individualized topic IDs
458
+ // as keys in the map. This could either point to the same result as the combined topic ID
459
+ // or to a specific result for that individual topic if backend provides a specific result.
460
+ // Example: caller asked to subscribe "a.b?c&d" but user lacks permission for topic "a.b.d"
461
+ // In this case, API response will include "a.b?c&d" as success along with "a.b.d" as failure.
462
+ if (id.includes('?')) {
463
+ for (const individualTopic of splitIntoIndividualTopics(id)) {
464
+ const hasIndividualTopicResult = result.hasOwnProperty(individualTopic);
465
+ // Only use the combined topic result for this individual topic ID if there isn't already
466
+ // a result for the individual topic itself. Exact topic result takes precedence.
467
+ if (!hasIndividualTopicResult) {
468
+ result[individualTopic] = result[id];
469
+ }
470
+ }
471
+ }
472
+ }
473
+ if (options.replace) {
474
+ this.bulkSubscriptions = {};
475
+ }
476
+ topics.forEach(topic => {
477
+ this.bulkSubscriptions[topic] = true;
478
+ });
479
+ this.internalSubscriptions.forEach(topic => {
480
+ this.bulkSubscriptions[topic] = true;
481
+ });
482
+ // Add a fallback result for any topic in the toSubscribe list that isn't already in result.
483
+ // With partial bulk resubscribe enabled missing result means "Unknown" state but when not
484
+ // enabled the fallback is "Permitted" for backward compatibility (success response means OK).
485
+ for (const topic of toSubscribe) {
486
+ (_b = result[topic]) !== null && _b !== void 0 ? _b : (result[topic] = { topic, state: this.enablePartialBulkResubscribe ? 'Unknown' : 'Permitted' });
487
+ }
488
+ return result;
489
+ });
490
+ }
491
+ get expose() {
492
+ return {
493
+ subscribe: this.subscribe.bind(this),
494
+ unsubscribe: this.unsubscribe.bind(this),
495
+ bulkSubscribe: this.bulkSubscribe.bind(this)
496
+ };
497
+ }
498
+ }
499
+ function isTopicSubscribeResult(value) {
500
+ let hasTopic = false;
501
+ let hasValidState = false;
502
+ if (value && typeof value === 'object') {
503
+ hasTopic = 'topic' in value && typeof value.topic === 'string';
504
+ hasValidState = 'state' in value && ['Permitted', 'Rejected', 'Unknown'].includes(value.state);
505
+ }
506
+ return hasTopic && hasValidState;
507
+ }
@@ -0,0 +1,21 @@
1
+ import { Client } from './client';
2
+ import { NamedAgent } from './types/named-agent';
3
+ export interface PingOptions {
4
+ pingInterval?: number;
5
+ failedPingsBeforeDisconnect?: number;
6
+ jid?: string;
7
+ }
8
+ export declare class Ping {
9
+ private client;
10
+ private stanzaInstance;
11
+ private options;
12
+ private pingInterval;
13
+ private failedPingsBeforeDisconnect;
14
+ private numberOfFailedPings;
15
+ private nextPingTimeoutId;
16
+ constructor(client: Client, stanzaInstance: NamedAgent, options?: PingOptions);
17
+ start(): void;
18
+ stop(): void;
19
+ private performPing;
20
+ private queueNextPing;
21
+ }
@@ -0,0 +1,68 @@
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
+ const DEFAULT_PING_INTERVAL = 14 * 1000;
12
+ const DEFAULT_MAXIMUM_FAILED_PINGS_BEFORE_DISCONNECT = 1;
13
+ export class Ping {
14
+ constructor(client, stanzaInstance, options = {}) {
15
+ this.client = client;
16
+ this.stanzaInstance = stanzaInstance;
17
+ this.options = options;
18
+ this.pingInterval = options.pingInterval || DEFAULT_PING_INTERVAL;
19
+ this.failedPingsBeforeDisconnect = options.failedPingsBeforeDisconnect || DEFAULT_MAXIMUM_FAILED_PINGS_BEFORE_DISCONNECT;
20
+ this.numberOfFailedPings = 0;
21
+ this.nextPingTimeoutId = undefined;
22
+ this.start();
23
+ }
24
+ start() {
25
+ if (!this.nextPingTimeoutId) {
26
+ this.nextPingTimeoutId = -1;
27
+ this.queueNextPing();
28
+ }
29
+ }
30
+ stop() {
31
+ clearTimeout(this.nextPingTimeoutId);
32
+ this.nextPingTimeoutId = undefined;
33
+ this.numberOfFailedPings = 0;
34
+ }
35
+ performPing() {
36
+ var _a;
37
+ return __awaiter(this, void 0, void 0, function* () {
38
+ try {
39
+ yield this.stanzaInstance.ping(this.options.jid);
40
+ this.numberOfFailedPings = 0;
41
+ this.queueNextPing();
42
+ }
43
+ catch (err) {
44
+ const info = {
45
+ channelId: this.client.config.channelId,
46
+ jid: this.stanzaInstance.jid,
47
+ stanzaInstanceId: this.stanzaInstance.id
48
+ };
49
+ this.client.logger.warn('Missed a ping.', Object.assign({ error: err }, info));
50
+ /* if we have reached max number of missed pings, disconnect */
51
+ if (++this.numberOfFailedPings > this.failedPingsBeforeDisconnect) {
52
+ this.client.logger.error('Missed too many pings, disconnecting', Object.assign({ numberOfFailedPings: this.numberOfFailedPings }, info));
53
+ this.stanzaInstance.sendStreamError({ text: 'too many missed pongs', condition: 'connection-timeout' });
54
+ this.stop();
55
+ (_a = this.stanzaInstance.transport) === null || _a === void 0 ? void 0 : _a.disconnect(false);
56
+ }
57
+ else {
58
+ this.queueNextPing();
59
+ }
60
+ }
61
+ });
62
+ }
63
+ queueNextPing() {
64
+ if (this.nextPingTimeoutId) {
65
+ this.nextPingTimeoutId = setTimeout(this.performPing.bind(this), this.pingInterval);
66
+ }
67
+ }
68
+ }
File without changes
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /* istanbul ignore file */
3
+ if (!global) {
4
+ // @ts-ignore
5
+ Object.defineProperty(window, 'crypto', { value: window.crypto || window.msCrypto, writable: false, configurable: false });
6
+ }
@@ -0,0 +1,16 @@
1
+ import { Client } from './client';
2
+ import { NamedAgent } from './types/named-agent';
3
+ export interface ServerMonitorOptions {
4
+ stanzaTimeout?: number;
5
+ }
6
+ export declare class ServerMonitor {
7
+ private client;
8
+ private stanzaInstance;
9
+ private stanzaTimeout;
10
+ private timeoutId?;
11
+ private boundSetupStanzaTimeout?;
12
+ constructor(client: Client, stanzaInstance: NamedAgent, options?: ServerMonitorOptions);
13
+ private start;
14
+ stop(): void;
15
+ private setupStanzaTimeout;
16
+ }
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+ const DEFAULT_STANZA_TIMEOUT = 35 * 1000;
3
+ export class ServerMonitor {
4
+ constructor(client, stanzaInstance, options = {}) {
5
+ this.client = client;
6
+ this.stanzaInstance = stanzaInstance;
7
+ this.stanzaTimeout = options.stanzaTimeout || DEFAULT_STANZA_TIMEOUT;
8
+ this.timeoutId = undefined;
9
+ this.start();
10
+ }
11
+ start() {
12
+ this.boundSetupStanzaTimeout = this.setupStanzaTimeout.bind(this);
13
+ this.client.on('connected', this.boundSetupStanzaTimeout);
14
+ this.stanzaInstance.on('raw:incoming', this.boundSetupStanzaTimeout);
15
+ }
16
+ stop() {
17
+ clearTimeout(this.timeoutId);
18
+ this.timeoutId = undefined;
19
+ if (this.boundSetupStanzaTimeout) {
20
+ this.client.off('connected', this.boundSetupStanzaTimeout);
21
+ this.stanzaInstance.off('raw:incoming', this.boundSetupStanzaTimeout);
22
+ this.boundSetupStanzaTimeout = undefined;
23
+ }
24
+ }
25
+ setupStanzaTimeout() {
26
+ clearTimeout(this.timeoutId);
27
+ this.timeoutId = setTimeout(() => {
28
+ var _a;
29
+ const info = {
30
+ channelId: this.client.config.channelId,
31
+ jid: this.stanzaInstance.jid,
32
+ stanzaInstanceId: this.stanzaInstance.id,
33
+ timeout: this.stanzaTimeout
34
+ };
35
+ this.client.logger.error('Time between XMPP stanzas exceeded timeout, disconnecting', info);
36
+ this.stanzaInstance.sendStreamError({ text: 'time between stanzas exceeded timeout', condition: 'connection-timeout' });
37
+ this.stop();
38
+ (_a = this.stanzaInstance.transport) === null || _a === void 0 ? void 0 : _a.disconnect(false);
39
+ }, this.stanzaTimeout);
40
+ }
41
+ }