genesys-cloud-streaming-client 20.0.0 → 20.0.1-release.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.
- package/README.md +32 -8
- package/dist/cjs/alerting-leader.d.ts +2 -1
- package/dist/cjs/alerting-leader.js +24 -8
- package/dist/cjs/client.js +1 -1
- package/dist/cjs/notifications.d.ts +2 -0
- package/dist/cjs/notifications.js +60 -6
- package/dist/deploy-info.json +5 -5
- package/dist/es/alerting-leader.d.ts +2 -1
- package/dist/es/alerting-leader.js +26 -8
- package/dist/es/client.js +1 -1
- package/dist/es/index.bundle.js +1176 -600
- package/dist/es/notifications.d.ts +2 -0
- package/dist/es/notifications.js +72 -16
- package/dist/manifest.json +5 -5
- package/dist/npm/CHANGELOG.md +7 -1
- package/dist/npm/README.md +32 -8
- package/dist/npm/alerting-leader.d.ts +2 -1
- package/dist/npm/alerting-leader.js +24 -8
- package/dist/npm/client.js +1 -1
- package/dist/npm/notifications.d.ts +2 -0
- package/dist/npm/notifications.js +60 -6
- package/dist/streaming-client.browser.js +1 -1
- package/dist/streaming-client.browser.js.LICENSE.txt +1 -1
- package/dist/v20/streaming-client.browser.js +1 -1
- package/dist/v20/streaming-client.browser.js.LICENSE.txt +1 -1
- package/dist/v20.0.1/streaming-client.browser.js +2 -0
- package/dist/{v20.0.0 → v20.0.1}/streaming-client.browser.js.LICENSE.txt +1 -1
- package/package.json +3 -3
- package/dist/v20.0.0/streaming-client.browser.js +0 -2
package/README.md
CHANGED
|
@@ -2,22 +2,46 @@
|
|
|
2
2
|
[](https://github.com/purecloudlabs/genesys-cloud-streaming-client/actions/workflows/matrix.yml)
|
|
3
3
|
|
|
4
4
|
### Overview
|
|
5
|
-
Client library for streaming
|
|
5
|
+
Client library for Genesys Cloud streaming services (websocket/XMPP interface).
|
|
6
6
|
|
|
7
7
|
### Installation
|
|
8
|
-
|
|
8
|
+
```bash
|
|
9
|
+
npm install genesys-cloud-streaming-client
|
|
10
|
+
```
|
|
9
11
|
|
|
10
|
-
###
|
|
11
|
-
|
|
12
|
+
### Quick Start
|
|
13
|
+
```ts
|
|
14
|
+
import StreamingClient from 'genesys-cloud-streaming-client';
|
|
15
|
+
|
|
16
|
+
const client = new StreamingClient({
|
|
17
|
+
host: 'wss://streaming.mypurecloud.com',
|
|
18
|
+
authToken: 'your-access-token',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await client.connect();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> If you're using this in a browser with a bundler like Vite or Webpack 5, you'll
|
|
25
|
+
> need polyfills for `global`, `events`, and possibly `process`. See the
|
|
26
|
+
> [full documentation](doc/documentation.md#browser-usage--polyfills) for details.
|
|
12
27
|
|
|
13
|
-
|
|
28
|
+
### Documentation
|
|
29
|
+
See [doc/documentation.md](doc/documentation.md) for the full API reference,
|
|
30
|
+
browser polyfill setup, and known issues.
|
|
31
|
+
|
|
32
|
+
### Development
|
|
33
|
+
|
|
34
|
+
Run `npm install` to install dependencies.
|
|
35
|
+
|
|
36
|
+
### Testing
|
|
37
|
+
Run the tests using `npm test` in the command line.
|
|
14
38
|
|
|
15
39
|
### Build for Local Use
|
|
16
|
-
Run
|
|
40
|
+
Run `npm run build`
|
|
17
41
|
|
|
18
42
|
### Linting and Style
|
|
19
|
-
semistandard has been added and you can run linting through the command line via `npm run lint`
|
|
43
|
+
ESLint with the semistandard config has been added and you can run linting through the command line via `npm run lint`
|
|
20
44
|
|
|
21
45
|
To fix minor styling errors, run `npm run lint:fix`
|
|
22
46
|
|
|
23
|
-
**If you can configure
|
|
47
|
+
**If you can configure your editor to run linting while typing or on save, this is preferable.**
|
|
@@ -7,13 +7,14 @@ export declare class AlertingLeaderExtension extends EventEmitter implements Str
|
|
|
7
7
|
private client;
|
|
8
8
|
private connectionId?;
|
|
9
9
|
private alertableInteractionTypes;
|
|
10
|
-
private
|
|
10
|
+
private getLeaderAbortController?;
|
|
11
11
|
private leaderStatus;
|
|
12
12
|
constructor(client: Client, options: IClientOptions);
|
|
13
13
|
handleStanzaInstanceChange(stanzaInstance: NamedAgent): void;
|
|
14
14
|
private setupAlertingLeader;
|
|
15
15
|
private subscribeToAlertingLeader;
|
|
16
16
|
private markAsAlertable;
|
|
17
|
+
private getAlertingLeaderEarly;
|
|
17
18
|
private getAlertingLeader;
|
|
18
19
|
private claimAlertingLeader;
|
|
19
20
|
get expose(): AlertingLeaderApi;
|
|
@@ -22,12 +22,13 @@ class AlertingLeaderExtension extends events_1.EventEmitter {
|
|
|
22
22
|
async setupAlertingLeader() {
|
|
23
23
|
if (this.alertableInteractionTypes.length !== 0) {
|
|
24
24
|
try {
|
|
25
|
+
this.getAlertingLeaderEarly();
|
|
25
26
|
await this.subscribeToAlertingLeader();
|
|
26
27
|
await this.markAsAlertable();
|
|
27
28
|
await this.getAlertingLeader();
|
|
28
29
|
}
|
|
29
30
|
catch (err) {
|
|
30
|
-
this.client.logger.warn('Failed to setup alerting leader; falling back to
|
|
31
|
+
this.client.logger.warn('Failed to setup alerting leader; falling back to acting as the leader');
|
|
31
32
|
// Fail 'open' so users don't miss calls
|
|
32
33
|
this.leaderStatus = { voice: { alerting: true, configured: false } };
|
|
33
34
|
this.emit('alertingLeaderChanged', this.leaderStatus);
|
|
@@ -38,9 +39,9 @@ class AlertingLeaderExtension extends events_1.EventEmitter {
|
|
|
38
39
|
const topic = `v2.users.${this.client.config.userId}.alertingleader`;
|
|
39
40
|
this.client.on(`notify:${topic}`, (event) => {
|
|
40
41
|
var _a, _b;
|
|
41
|
-
(_a = this.
|
|
42
|
+
(_a = this.getLeaderAbortController) === null || _a === void 0 ? void 0 : _a.abort();
|
|
42
43
|
if ((_b = event.eventBody) === null || _b === void 0 ? void 0 : _b.connectionId) {
|
|
43
|
-
//
|
|
44
|
+
// The consuming client should be the one alerting if our connection is the current leader
|
|
44
45
|
const alerting = event.eventBody.connectionId === this.connectionId;
|
|
45
46
|
const clientType = event.eventBody.clientType;
|
|
46
47
|
let voice = { alerting, configured: true };
|
|
@@ -79,21 +80,36 @@ class AlertingLeaderExtension extends events_1.EventEmitter {
|
|
|
79
80
|
}, 500, this.client.logger);
|
|
80
81
|
return retry.promise
|
|
81
82
|
.catch(() => {
|
|
82
|
-
this.client.logger.warn('Could not mark this connection as alertable
|
|
83
|
+
this.client.logger.warn('Could not mark this connection as alertable');
|
|
83
84
|
});
|
|
84
85
|
}
|
|
86
|
+
async getAlertingLeaderEarly() {
|
|
87
|
+
try {
|
|
88
|
+
await this.getAlertingLeader();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (axios_1.default.isAxiosError(error) && error.status === 400) {
|
|
92
|
+
this.client.logger.info('The org has not configured alerting leader functionality or there are not yet any active alertable connections; falling back to acting as the leader');
|
|
93
|
+
this.leaderStatus = { voice: { alerting: true, configured: false } };
|
|
94
|
+
this.emit('alertingLeaderChanged', this.leaderStatus);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
85
98
|
async getAlertingLeader() {
|
|
86
|
-
|
|
99
|
+
var _a;
|
|
100
|
+
// If an early request is still in-flight, cancel it and get more recent data
|
|
101
|
+
(_a = this.getLeaderAbortController) === null || _a === void 0 ? void 0 : _a.abort();
|
|
102
|
+
this.getLeaderAbortController = new AbortController();
|
|
87
103
|
const leaderRequestOptions = {
|
|
88
104
|
method: 'get',
|
|
89
105
|
host: this.client.config.apiHost,
|
|
90
106
|
authToken: this.client.config.authToken,
|
|
91
107
|
logger: this.client.logger,
|
|
92
|
-
signal: this.
|
|
108
|
+
signal: this.getLeaderAbortController.signal
|
|
93
109
|
};
|
|
94
110
|
try {
|
|
95
|
-
const currentLeader = await this.client.http.
|
|
96
|
-
//
|
|
111
|
+
const currentLeader = await this.client.http.requestApiWithRetry('users/alertingleader', leaderRequestOptions, 1000).promise;
|
|
112
|
+
// The consuming client should be the one alerting if our connection is the current leader
|
|
97
113
|
const alerting = currentLeader.data.connectionId === this.connectionId;
|
|
98
114
|
const clientType = currentLeader.data.clientType;
|
|
99
115
|
let voice = { alerting, configured: true };
|
package/dist/cjs/client.js
CHANGED
|
@@ -12,6 +12,8 @@ export declare class Notifications implements StreamingClientExtension {
|
|
|
12
12
|
debouncedResubscribe: () => Promise<BulkSubscribeResult>;
|
|
13
13
|
enablePartialBulkResubscribe: boolean;
|
|
14
14
|
private internalSubscriptions;
|
|
15
|
+
private pendingBulkSubscribeRetry?;
|
|
16
|
+
private pendingConnectedWait?;
|
|
15
17
|
constructor(client: any, options?: IClientOptions);
|
|
16
18
|
get pubsubHost(): string;
|
|
17
19
|
handleStanzaInstanceChange(stanza: NamedAgent): void;
|
|
@@ -178,20 +178,57 @@ class Notifications {
|
|
|
178
178
|
}
|
|
179
179
|
return keptTopics;
|
|
180
180
|
}
|
|
181
|
-
makeBulkSubscribeRequest(topics, options) {
|
|
181
|
+
async makeBulkSubscribeRequest(topics, options) {
|
|
182
|
+
// Cancel any in-flight retry or connected-wait from a previous bulk subscribe.
|
|
183
|
+
// If the topic list has changed (subscribe/unsubscribe called between retries),
|
|
184
|
+
// the stale request would send an outdated topic list. Since bulk subscribes with
|
|
185
|
+
// replace=true use PUT (full replacement), only the most recent request matters.
|
|
186
|
+
if (this.pendingConnectedWait) {
|
|
187
|
+
this.pendingConnectedWait.cancel(new Error('Superseded by newer bulk subscribe request'));
|
|
188
|
+
this.pendingConnectedWait = undefined;
|
|
189
|
+
}
|
|
190
|
+
if (this.pendingBulkSubscribeRetry && !this.pendingBulkSubscribeRetry.hasCompleted()) {
|
|
191
|
+
this.client.logger.info('Cancelling previous bulk subscribe retry — new request supersedes it');
|
|
192
|
+
this.pendingBulkSubscribeRetry.cancel(new Error('Superseded by newer bulk subscribe request'));
|
|
193
|
+
}
|
|
194
|
+
// If the client is not connected, wait for reconnection before attempting the request.
|
|
195
|
+
// This prevents firing requests against a dead/stale channel ID which would result in
|
|
196
|
+
// 400 errors (notification.unable.to.get.channel.id). After reconnection, the stanza
|
|
197
|
+
// instance will have the fresh channel ID from the new connection.
|
|
198
|
+
if (!this.client.connected) {
|
|
199
|
+
this.client.logger.info('Client not connected, waiting for reconnection before bulk subscribe', {
|
|
200
|
+
topicCount: topics.length
|
|
201
|
+
});
|
|
202
|
+
await new Promise((resolve, reject) => {
|
|
203
|
+
const onConnected = () => {
|
|
204
|
+
this.pendingConnectedWait = undefined;
|
|
205
|
+
resolve();
|
|
206
|
+
};
|
|
207
|
+
this.client.once('connected', onConnected);
|
|
208
|
+
this.pendingConnectedWait = {
|
|
209
|
+
cancel: (reason) => {
|
|
210
|
+
this.client.off('connected', onConnected);
|
|
211
|
+
reject(reason);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
}
|
|
182
216
|
const requestOptions = {
|
|
183
217
|
method: options.replace ? 'put' : 'post',
|
|
184
218
|
host: this.client.config.apiHost,
|
|
185
219
|
authToken: this.client.config.authToken,
|
|
186
220
|
data: JSON.stringify(this.mapCombineTopics(topics)),
|
|
187
|
-
logger: this.client.logger
|
|
221
|
+
logger: this.client.logger,
|
|
222
|
+
maxAttempts: 3
|
|
188
223
|
};
|
|
189
224
|
const channelId = this.stanzaInstance.channelId;
|
|
190
225
|
let path = `notifications/channels/${channelId}/subscriptions`;
|
|
191
226
|
if (this.enablePartialBulkResubscribe) {
|
|
192
227
|
path += '?ignoreErrors=true';
|
|
193
228
|
}
|
|
194
|
-
|
|
229
|
+
const retry = this.client.http.requestApiWithRetry(path, requestOptions, 2000);
|
|
230
|
+
this.pendingBulkSubscribeRetry = retry;
|
|
231
|
+
return retry.promise;
|
|
195
232
|
}
|
|
196
233
|
createSubscription(topic, handler) {
|
|
197
234
|
const topics = (0, utils_1.splitIntoIndividualTopics)(topic);
|
|
@@ -366,7 +403,7 @@ class Notifications {
|
|
|
366
403
|
return this.xmppUnsubscribe(topic);
|
|
367
404
|
}
|
|
368
405
|
async bulkSubscribe(topics, options = { replace: false, force: false }, priorities = {}) {
|
|
369
|
-
var _a;
|
|
406
|
+
var _a, _b;
|
|
370
407
|
this.setTopicPriorities(priorities);
|
|
371
408
|
let toSubscribe = mergeAndDedup(topics, this.internalSubscriptions);
|
|
372
409
|
if (options.replace && !options.force) {
|
|
@@ -377,7 +414,24 @@ class Notifications {
|
|
|
377
414
|
// if it's a forcible bulk subscribe, wipe out individual subscriptions
|
|
378
415
|
this.subscriptions = {};
|
|
379
416
|
}
|
|
380
|
-
|
|
417
|
+
let response;
|
|
418
|
+
try {
|
|
419
|
+
response = await this.makeBulkSubscribeRequest(toSubscribe, options);
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
// If this request was cancelled because a newer bulk subscribe superseded it,
|
|
423
|
+
// return an 'Unknown' state for all topics. The superseding request will carry
|
|
424
|
+
// the current desired topic list and handle the actual subscription.
|
|
425
|
+
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')) {
|
|
426
|
+
this.client.logger.debug('Bulk subscribe was superseded, deferring to newer request');
|
|
427
|
+
const result = {};
|
|
428
|
+
for (const topic of toSubscribe) {
|
|
429
|
+
result[topic] = { topic, state: 'Unknown' };
|
|
430
|
+
}
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
throw err;
|
|
434
|
+
}
|
|
381
435
|
let topicResponseEntities = [];
|
|
382
436
|
if (response && response.data && 'entities' in response.data && Array.isArray(response.data.entities)) {
|
|
383
437
|
topicResponseEntities = response.data.entities;
|
|
@@ -415,7 +469,7 @@ class Notifications {
|
|
|
415
469
|
// With partial bulk resubscribe enabled missing result means "Unknown" state but when not
|
|
416
470
|
// enabled the fallback is "Permitted" for backward compatibility (success response means OK).
|
|
417
471
|
for (const topic of toSubscribe) {
|
|
418
|
-
(
|
|
472
|
+
(_b = result[topic]) !== null && _b !== void 0 ? _b : (result[topic] = { topic, state: this.enablePartialBulkResubscribe ? 'Unknown' : 'Permitted' });
|
|
419
473
|
}
|
|
420
474
|
return result;
|
|
421
475
|
}
|
package/dist/deploy-info.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "
|
|
3
|
-
"build": "
|
|
4
|
-
"buildDate": "2026-06-
|
|
2
|
+
"version": "release/v20.0.1",
|
|
3
|
+
"build": "1",
|
|
4
|
+
"buildDate": "2026-06-23T16:09:25.734Z",
|
|
5
5
|
"indexFiles": [
|
|
6
6
|
{
|
|
7
|
-
"file": "v20.0.
|
|
7
|
+
"file": "v20.0.1/streaming-client.browser.js"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
|
-
"file": "v20.0.
|
|
10
|
+
"file": "v20.0.1/streaming-client.browser.js.LICENSE.txt"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"file": "v20/streaming-client.browser.js"
|
|
@@ -7,13 +7,14 @@ export declare class AlertingLeaderExtension extends EventEmitter implements Str
|
|
|
7
7
|
private client;
|
|
8
8
|
private connectionId?;
|
|
9
9
|
private alertableInteractionTypes;
|
|
10
|
-
private
|
|
10
|
+
private getLeaderAbortController?;
|
|
11
11
|
private leaderStatus;
|
|
12
12
|
constructor(client: Client, options: IClientOptions);
|
|
13
13
|
handleStanzaInstanceChange(stanzaInstance: NamedAgent): void;
|
|
14
14
|
private setupAlertingLeader;
|
|
15
15
|
private subscribeToAlertingLeader;
|
|
16
16
|
private markAsAlertable;
|
|
17
|
+
private getAlertingLeaderEarly;
|
|
17
18
|
private getAlertingLeader;
|
|
18
19
|
private claimAlertingLeader;
|
|
19
20
|
get expose(): AlertingLeaderApi;
|
|
@@ -20,12 +20,13 @@ export class AlertingLeaderExtension extends EventEmitter {
|
|
|
20
20
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
21
|
if (this.alertableInteractionTypes.length !== 0) {
|
|
22
22
|
try {
|
|
23
|
+
this.getAlertingLeaderEarly();
|
|
23
24
|
yield this.subscribeToAlertingLeader();
|
|
24
25
|
yield this.markAsAlertable();
|
|
25
26
|
yield this.getAlertingLeader();
|
|
26
27
|
}
|
|
27
28
|
catch (err) {
|
|
28
|
-
this.client.logger.warn('Failed to setup alerting leader; falling back to
|
|
29
|
+
this.client.logger.warn('Failed to setup alerting leader; falling back to acting as the leader');
|
|
29
30
|
// Fail 'open' so users don't miss calls
|
|
30
31
|
this.leaderStatus = { voice: { alerting: true, configured: false } };
|
|
31
32
|
this.emit('alertingLeaderChanged', this.leaderStatus);
|
|
@@ -38,9 +39,9 @@ export class AlertingLeaderExtension extends EventEmitter {
|
|
|
38
39
|
const topic = `v2.users.${this.client.config.userId}.alertingleader`;
|
|
39
40
|
this.client.on(`notify:${topic}`, (event) => {
|
|
40
41
|
var _a, _b;
|
|
41
|
-
(_a = this.
|
|
42
|
+
(_a = this.getLeaderAbortController) === null || _a === void 0 ? void 0 : _a.abort();
|
|
42
43
|
if ((_b = event.eventBody) === null || _b === void 0 ? void 0 : _b.connectionId) {
|
|
43
|
-
//
|
|
44
|
+
// The consuming client should be the one alerting if our connection is the current leader
|
|
44
45
|
const alerting = event.eventBody.connectionId === this.connectionId;
|
|
45
46
|
const clientType = event.eventBody.clientType;
|
|
46
47
|
let voice = { alerting, configured: true };
|
|
@@ -81,23 +82,40 @@ export class AlertingLeaderExtension extends EventEmitter {
|
|
|
81
82
|
}, 500, this.client.logger);
|
|
82
83
|
return retry.promise
|
|
83
84
|
.catch(() => {
|
|
84
|
-
this.client.logger.warn('Could not mark this connection as alertable
|
|
85
|
+
this.client.logger.warn('Could not mark this connection as alertable');
|
|
85
86
|
});
|
|
86
87
|
});
|
|
87
88
|
}
|
|
89
|
+
getAlertingLeaderEarly() {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
try {
|
|
92
|
+
yield this.getAlertingLeader();
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (axios.isAxiosError(error) && error.status === 400) {
|
|
96
|
+
this.client.logger.info('The org has not configured alerting leader functionality or there are not yet any active alertable connections; falling back to acting as the leader');
|
|
97
|
+
this.leaderStatus = { voice: { alerting: true, configured: false } };
|
|
98
|
+
this.emit('alertingLeaderChanged', this.leaderStatus);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
88
103
|
getAlertingLeader() {
|
|
104
|
+
var _a;
|
|
89
105
|
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
-
|
|
106
|
+
// If an early request is still in-flight, cancel it and get more recent data
|
|
107
|
+
(_a = this.getLeaderAbortController) === null || _a === void 0 ? void 0 : _a.abort();
|
|
108
|
+
this.getLeaderAbortController = new AbortController();
|
|
91
109
|
const leaderRequestOptions = {
|
|
92
110
|
method: 'get',
|
|
93
111
|
host: this.client.config.apiHost,
|
|
94
112
|
authToken: this.client.config.authToken,
|
|
95
113
|
logger: this.client.logger,
|
|
96
|
-
signal: this.
|
|
114
|
+
signal: this.getLeaderAbortController.signal
|
|
97
115
|
};
|
|
98
116
|
try {
|
|
99
|
-
const currentLeader = yield this.client.http.
|
|
100
|
-
//
|
|
117
|
+
const currentLeader = yield this.client.http.requestApiWithRetry('users/alertingleader', leaderRequestOptions, 1000).promise;
|
|
118
|
+
// The consuming client should be the one alerting if our connection is the current leader
|
|
101
119
|
const alerting = currentLeader.data.connectionId === this.connectionId;
|
|
102
120
|
const clientType = currentLeader.data.clientType;
|
|
103
121
|
let voice = { alerting, configured: true };
|