genesys-cloud-streaming-client 19.3.1 → 19.4.0-release.2

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 (38) hide show
  1. package/README.md +3 -0
  2. package/dist/cjs/client.js +1 -1
  3. package/dist/cjs/index.d.ts +1 -1
  4. package/dist/cjs/index.js +2 -1
  5. package/dist/cjs/notifications.d.ts +26 -7
  6. package/dist/cjs/notifications.js +60 -9
  7. package/dist/cjs/types/genesys-cloud-media-session.js +2 -1
  8. package/dist/cjs/types/interfaces.d.ts +2 -0
  9. package/dist/cjs/utils.d.ts +6 -0
  10. package/dist/cjs/utils.js +10 -1
  11. package/dist/deploy-info.json +3 -3
  12. package/dist/es/client.js +1 -1
  13. package/dist/es/index.bundle.js +87 -28
  14. package/dist/es/index.d.ts +1 -1
  15. package/dist/es/index.js +1 -1
  16. package/dist/es/notifications.d.ts +26 -7
  17. package/dist/es/notifications.js +76 -24
  18. package/dist/es/types/genesys-cloud-media-session.js +2 -1
  19. package/dist/es/types/interfaces.d.ts +2 -0
  20. package/dist/es/utils.d.ts +6 -0
  21. package/dist/es/utils.js +8 -0
  22. package/dist/manifest.json +3 -3
  23. package/dist/npm/CHANGELOG.md +9 -1
  24. package/dist/npm/client.js +1 -1
  25. package/dist/npm/index.d.ts +1 -1
  26. package/dist/npm/index.js +2 -1
  27. package/dist/npm/module.js +1 -1
  28. package/dist/npm/notifications.d.ts +26 -7
  29. package/dist/npm/notifications.js +60 -9
  30. package/dist/npm/types/genesys-cloud-media-session.js +2 -1
  31. package/dist/npm/types/interfaces.d.ts +2 -0
  32. package/dist/npm/utils.d.ts +6 -0
  33. package/dist/npm/utils.js +10 -1
  34. package/dist/streaming-client.browser.js +6 -6
  35. package/dist/v19/streaming-client.browser.js +6 -6
  36. package/dist/v19.4.0/streaming-client.browser.js +19 -0
  37. package/package.json +3 -1
  38. package/dist/v19.3.1/streaming-client.browser.js +0 -19
@@ -1,6 +1,7 @@
1
1
  import { __awaiter } from "tslib";
2
- const debounce = require('debounce-promise');
2
+ import debounce from 'debounce-promise';
3
3
  import { splitIntoIndividualTopics } from './utils';
4
+ import { StreamingSubscriptionError } from './';
4
5
  const PUBSUB_HOST_DEFAULT = 'notifications.mypurecloud.com';
5
6
  const MAX_SUBSCRIBABLE_TOPICS = 1000;
6
7
  const DROPPED_TOPICS_DISPLAY_COUNT = 20;
@@ -9,11 +10,13 @@ function mergeAndDedup(arr1, arr2) {
9
10
  return [...arr1, ...arr2].filter((t, i, arr) => arr.indexOf(t) === i);
10
11
  }
11
12
  export class Notifications {
12
- constructor(client) {
13
+ constructor(client, options) {
14
+ var _a;
13
15
  this.subscriptions = {};
14
16
  this.bulkSubscriptions = {};
15
17
  this.topicPriorities = {};
16
18
  this.client = client;
19
+ this.enablePartialBulkResubscribe = (_a = options === null || options === void 0 ? void 0 : options.enablePartialBulkResubscribe) !== null && _a !== void 0 ? _a : false;
17
20
  client.on('pubsub:event', this.pubsubEvent.bind(this));
18
21
  client.on('connected', this.subscriptionsKeepAlive.bind(this));
19
22
  this.debouncedResubscribe = debounce(this.resubscribe.bind(this), 100);
@@ -35,7 +38,7 @@ export class Notifications {
35
38
  this.stanzaInstance = stanza;
36
39
  if (needsToResub) {
37
40
  this.client.logger.info('resubscribing due to hard reconnect');
38
- this.debouncedResubscribe();
41
+ void this.debouncedResubscribe();
39
42
  }
40
43
  }
41
44
  topicHandlers(topic) {
@@ -178,7 +181,11 @@ export class Notifications {
178
181
  logger: this.client.logger
179
182
  };
180
183
  const channelId = this.stanzaInstance.channelId;
181
- return this.client.http.requestApi(`notifications/channels/${channelId}/subscriptions`, requestOptions);
184
+ let path = `notifications/channels/${channelId}/subscriptions`;
185
+ if (this.enablePartialBulkResubscribe) {
186
+ path += '?ignoreErrors=true';
187
+ }
188
+ return this.client.http.requestApi(path, requestOptions);
182
189
  }
183
190
  createSubscription(topic, handler) {
184
191
  const topics = splitIntoIndividualTopics(topic);
@@ -234,7 +241,7 @@ export class Notifications {
234
241
  /* if we don't have bulk or individual subs, we don't need to resubscribe */
235
242
  const noTopics = bulkSubs.length + this.getActiveIndividualTopics().length === 0;
236
243
  if (noTopics) {
237
- return Promise.resolve();
244
+ return Promise.resolve({});
238
245
  }
239
246
  /* only pass in bulk subs with the replace flag – bulkSubscribe() will handle merging our individual topics (see PCM-1846) */
240
247
  return this.bulkSubscribe(bulkSubs, { replace: true });
@@ -290,24 +297,38 @@ export class Notifications {
290
297
  });
291
298
  }
292
299
  subscribe(topic, handler, immediate, priority) {
293
- if (priority) {
294
- this.setTopicPriorities({ [topic]: priority });
295
- }
296
- let promise;
297
- if (!immediate) {
298
- // let this and any other subscribe/unsubscribe calls roll in, then trigger a whole resubscribe
299
- promise = this.debouncedResubscribe();
300
- }
301
- else {
302
- promise = this.xmppSubscribe(topic);
303
- }
304
- if (handler) {
305
- this.createSubscription(topic, handler);
306
- }
307
- else {
308
- this.bulkSubscriptions[topic] = true;
309
- }
310
- return promise;
300
+ return __awaiter(this, void 0, void 0, function* () {
301
+ if (priority) {
302
+ this.setTopicPriorities({ [topic]: priority });
303
+ }
304
+ let promise;
305
+ if (!immediate) {
306
+ // let this and any other subscribe/unsubscribe calls roll in, then trigger a whole resubscribe
307
+ promise = this.debouncedResubscribe();
308
+ }
309
+ else {
310
+ promise = this.xmppSubscribe(topic);
311
+ }
312
+ if (handler) {
313
+ this.createSubscription(topic, handler);
314
+ }
315
+ else {
316
+ this.bulkSubscriptions[topic] = true;
317
+ }
318
+ const result = yield promise;
319
+ // Assume topic subscription succeeded if promise is resolved...
320
+ let topicResult = { topic, state: 'Permitted' };
321
+ // ... but if partial bulk resubscribe is enabled, use topic's individual result from the API response.
322
+ if (this.enablePartialBulkResubscribe && result && typeof result === 'object' && isTopicSubscribeResult(result[topic])) {
323
+ topicResult = result[topic];
324
+ }
325
+ // Topic result other than state=Permitted becomes a StreamingSubscriptionError promise rejection.
326
+ if (topicResult.state !== 'Permitted') {
327
+ const message = topicResult.rejectionReason || `Failed to subscribe topic ${topic}`;
328
+ throw new StreamingSubscriptionError(message, topic, 'subscribe');
329
+ }
330
+ return topicResult;
331
+ });
311
332
  }
312
333
  unsubscribe(topic, handler, immediate) {
313
334
  if (handler) {
@@ -336,13 +357,35 @@ export class Notifications {
336
357
  // if it's a forcible bulk subscribe, wipe out individual subscriptions
337
358
  this.subscriptions = {};
338
359
  }
339
- yield this.makeBulkSubscribeRequest(toSubscribe, options);
360
+ const response = yield this.makeBulkSubscribeRequest(toSubscribe, options);
361
+ let topicResponseEntities = [];
362
+ if (response && response.data && 'entities' in response.data && Array.isArray(response.data.entities)) {
363
+ topicResponseEntities = response.data.entities;
364
+ }
365
+ const topicResponsesById = {};
366
+ for (const topicEntity of topicResponseEntities) {
367
+ topicResponsesById[topicEntity.id] = topicEntity;
368
+ }
369
+ const result = {};
340
370
  if (options.replace) {
341
371
  this.bulkSubscriptions = {};
342
372
  }
343
373
  topics.forEach(topic => {
344
374
  this.bulkSubscriptions[topic] = true;
375
+ if (this.enablePartialBulkResubscribe) {
376
+ if (topic in topicResponsesById) {
377
+ const { state, rejectionReason } = topicResponsesById[topic];
378
+ result[topic] = { topic, state, rejectionReason };
379
+ }
380
+ else {
381
+ result[topic] = { topic, state: 'Unknown' };
382
+ }
383
+ }
384
+ else {
385
+ result[topic] = { topic, state: 'Permitted' };
386
+ }
345
387
  });
388
+ return result;
346
389
  });
347
390
  }
348
391
  get expose() {
@@ -353,3 +396,12 @@ export class Notifications {
353
396
  };
354
397
  }
355
398
  }
399
+ function isTopicSubscribeResult(value) {
400
+ let hasTopic = false;
401
+ let hasValidState = false;
402
+ if (value && typeof value === 'object') {
403
+ hasTopic = 'topic' in value && typeof value.topic === 'string';
404
+ hasValidState = 'state' in value && ['Permitted', 'Rejected', 'Unknown'].includes(value.state);
405
+ }
406
+ return hasTopic && hasValidState;
407
+ }
@@ -305,7 +305,8 @@ export class GenesysCloudMediaSession {
305
305
  sdp: answer.sdp,
306
306
  sessionId: this.id
307
307
  };
308
- this.logger.info('sending sdp answer', params);
308
+ // Do not log the SDP payload to avoid logging sensitive information.
309
+ this.logger.info('sending sdp answer', { sessionId: this.id, conversationId: this.conversationId });
309
310
  return this.sendGenesysWebrtc({
310
311
  jsonrpc: '2.0',
311
312
  method: 'answer',
@@ -22,6 +22,8 @@ export interface IClientOptions {
22
22
  appVersion?: string;
23
23
  appId?: string;
24
24
  customHeaders?: ICustomHeader;
25
+ /** Allow bulk topic resubscribe to succeed or fail per-topic rather than all or nothing */
26
+ enablePartialBulkResubscribe?: boolean;
25
27
  }
26
28
  export interface ICustomHeader {
27
29
  [header: string]: string;
@@ -4,6 +4,12 @@ export declare class StreamingClientError extends Error {
4
4
  details?: unknown;
5
5
  constructor(type: StreamingClientErrorTypes | null, messageOrError: string | Error, details?: unknown);
6
6
  }
7
+ export declare class StreamingSubscriptionError extends Error {
8
+ readonly topic?: string | undefined;
9
+ readonly operation?: "subscribe" | "unsubscribe" | undefined;
10
+ name: string;
11
+ constructor(message: string, topic?: string | undefined, operation?: "subscribe" | "unsubscribe" | undefined);
12
+ }
7
13
  export declare function timeoutPromise(fn: Function, timeoutMs: number, msg: string, details?: any): Promise<any>;
8
14
  export declare function delay(ms: number): Promise<void>;
9
15
  export declare function splitIntoIndividualTopics(topicString: string): string[];
package/dist/es/utils.js CHANGED
@@ -19,6 +19,14 @@ export class StreamingClientError extends Error {
19
19
  this.details = details;
20
20
  }
21
21
  }
22
+ export class StreamingSubscriptionError extends Error {
23
+ constructor(message, topic, operation) {
24
+ super(message);
25
+ this.topic = topic;
26
+ this.operation = operation;
27
+ this.name = 'StreamingSubscriptionError';
28
+ }
29
+ }
22
30
  /* istanbul ignore next */
23
31
  export function timeoutPromise(fn, timeoutMs, msg, details) {
24
32
  return new Promise(function (resolve, reject) {
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "19.3.1",
2
+ "version": "release/v19.4.0",
3
3
  "build": "2",
4
- "buildDate": "2025-07-24T14:11:50.599Z",
4
+ "buildDate": "2025-09-19T03:18:00.531Z",
5
5
  "indexFiles": [
6
6
  {
7
- "file": "v19.3.1/streaming-client.browser.js"
7
+ "file": "v19.4.0/streaming-client.browser.js"
8
8
  },
9
9
  {
10
10
  "file": "v19/streaming-client.browser.js"
@@ -4,7 +4,15 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- # [Unreleased](https://github.com/purecloudlabs/genesys-cloud-streaming-client/compare/v19.3.0...HEAD)
7
+ # [Unreleased](https://github.com/purecloudlabs/genesys-cloud-streaming-client/compare/v19.4.0...HEAD)
8
+
9
+ # [v19.4.0](https://github.com/purecloudlabs/genesys-cloud-streaming-client/compare/v19.3.1...v19.4.0)
10
+ ### Added
11
+ * [STREAM-865](https://inindca.atlassian.net/browse/STREAM-865) - Generate a test report in JUnit.xml format.
12
+ * [STREAM-892](https://inindca.atlassian.net/browse/STREAM-892) - Add `enablePartialBulkResubscribe` client option to make notifications bulk subscription changes succeed or fail each topic independently rather than a single failed topic causing the whole bulk operation to fail.
13
+
14
+ ### Changed
15
+ * [STREAM-865](https://inindca.atlassian.net/browse/STREAM-865) - Remove the SDP payload from SDP answer logs.
8
16
 
9
17
  # [v19.3.1](https://github.com/purecloudlabs/genesys-cloud-streaming-client/compare/v19.3.0...v19.3.1)
10
18
  * [STREAM-801](https://inindca.atlassian.net/browse/STREAM-801) - Update genesys-cloud-client-logger and axios to address Snyk vulnerability.
@@ -610,7 +610,7 @@ class Client extends events_1.default {
610
610
  return Client.version;
611
611
  }
612
612
  static get version() {
613
- return '19.3.1';
613
+ return '19.4.0';
614
614
  }
615
615
  }
616
616
  exports.Client = Client;
@@ -6,5 +6,5 @@ export * from './types/media-session';
6
6
  export * from './types/interfaces';
7
7
  export * from './messenger';
8
8
  export { HttpClient } from './http-client';
9
- export { StreamingClientError, parseJwt } from './utils';
9
+ export { StreamingClientError, StreamingSubscriptionError, parseJwt } from './utils';
10
10
  export default Client;
package/dist/npm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseJwt = exports.StreamingClientError = exports.HttpClient = void 0;
3
+ exports.parseJwt = exports.StreamingSubscriptionError = exports.StreamingClientError = exports.HttpClient = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  /// <reference path="types/libs.ts" />
6
6
  const client_1 = require("./client");
@@ -13,5 +13,6 @@ var http_client_1 = require("./http-client");
13
13
  Object.defineProperty(exports, "HttpClient", { enumerable: true, get: function () { return http_client_1.HttpClient; } });
14
14
  var utils_1 = require("./utils");
15
15
  Object.defineProperty(exports, "StreamingClientError", { enumerable: true, get: function () { return utils_1.StreamingClientError; } });
16
+ Object.defineProperty(exports, "StreamingSubscriptionError", { enumerable: true, get: function () { return utils_1.StreamingSubscriptionError; } });
16
17
  Object.defineProperty(exports, "parseJwt", { enumerable: true, get: function () { return utils_1.parseJwt; } });
17
18
  exports.default = client_1.Client;
@@ -6,5 +6,5 @@ export * from './types/media-session';
6
6
  export * from './types/interfaces';
7
7
  export * from './messenger';
8
8
  export { HttpClient } from './http-client';
9
- export { StreamingClientError, parseJwt } from './utils';
9
+ export { StreamingClientError, StreamingSubscriptionError, parseJwt } from './utils';
10
10
  export default Client;
@@ -1,15 +1,17 @@
1
1
  import { PubsubEvent, PubsubSubscription, PubsubSubscriptionWithOptions } from 'stanza/protocol';
2
2
  import { Client } from './client';
3
- import { StreamingClientExtension } from './types/interfaces';
3
+ import { IClientOptions, StreamingClientExtension } from './types/interfaces';
4
4
  import { NamedAgent } from './types/named-agent';
5
+ import { AxiosResponse } from 'axios';
5
6
  export declare class Notifications implements StreamingClientExtension {
6
7
  client: Client;
7
8
  stanzaInstance?: NamedAgent;
8
9
  subscriptions: any;
9
10
  bulkSubscriptions: any;
10
11
  topicPriorities: any;
11
- debouncedResubscribe: any;
12
- constructor(client: any);
12
+ debouncedResubscribe: () => Promise<BulkSubscribeResult>;
13
+ enablePartialBulkResubscribe: boolean;
14
+ constructor(client: any, options?: IClientOptions);
13
15
  get pubsubHost(): string;
14
16
  handleStanzaInstanceChange(stanza: NamedAgent): void;
15
17
  topicHandlers(topic: string): Array<(obj?: any) => void>;
@@ -32,23 +34,23 @@ export declare class Notifications implements StreamingClientExtension {
32
34
  }>): Array<{
33
35
  id: string;
34
36
  }>;
35
- makeBulkSubscribeRequest(topics: string[], options: any): Promise<any>;
37
+ makeBulkSubscribeRequest(topics: string[], options: any): Promise<AxiosResponse<ChannelTopicsEntityListing>>;
36
38
  createSubscription(topic: string, handler: (obj?: any) => void): void;
37
39
  removeSubscription(topic: string, handler: (obj?: any) => void): void;
38
40
  removeTopicPriority(topic: string): void;
39
41
  getActiveIndividualTopics(): string[];
40
- resubscribe(): Promise<any>;
42
+ resubscribe(): Promise<BulkSubscribeResult>;
41
43
  subscriptionsKeepAlive(): void;
42
44
  getTopicParts(topic: string): {
43
45
  prefix: string;
44
46
  postfixes: string[];
45
47
  };
46
48
  setTopicPriorities(priorities?: {}): void;
47
- subscribe(topic: string, handler?: (..._: any[]) => void, immediate?: boolean, priority?: number): Promise<any>;
49
+ subscribe(topic: string, handler?: (..._: any[]) => void, immediate?: boolean, priority?: number): Promise<TopicSubscribeResult>;
48
50
  unsubscribe(topic: string, handler?: (..._: any[]) => void, immediate?: boolean): Promise<any>;
49
51
  bulkSubscribe(topics: string[], options?: BulkSubscribeOpts, priorities?: {
50
52
  [topicName: string]: number;
51
- }): Promise<any>;
53
+ }): Promise<BulkSubscribeResult>;
52
54
  get expose(): NotificationsAPI;
53
55
  }
54
56
  export interface NotificationsAPI {
@@ -62,3 +64,20 @@ export interface BulkSubscribeOpts {
62
64
  replace?: boolean;
63
65
  force?: boolean;
64
66
  }
67
+ export interface BulkSubscribeResult {
68
+ [topic: string]: TopicSubscribeResult;
69
+ }
70
+ export interface TopicSubscribeResult {
71
+ topic: string;
72
+ state: 'Permitted' | 'Rejected' | 'Unknown';
73
+ rejectionReason?: string;
74
+ }
75
+ export interface ChannelTopicResponseEntity {
76
+ id: string;
77
+ state: 'Permitted' | 'Rejected';
78
+ rejectionReason?: string;
79
+ selfUri?: string;
80
+ }
81
+ export interface ChannelTopicsEntityListing {
82
+ entities: ChannelTopicResponseEntity[];
83
+ }
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Notifications = void 0;
4
- const debounce = require('debounce-promise');
4
+ const tslib_1 = require("tslib");
5
+ const debounce_promise_1 = tslib_1.__importDefault(require("debounce-promise"));
5
6
  const utils_1 = require("./utils");
7
+ const _1 = require("./");
6
8
  const PUBSUB_HOST_DEFAULT = 'notifications.mypurecloud.com';
7
9
  const MAX_SUBSCRIBABLE_TOPICS = 1000;
8
10
  const DROPPED_TOPICS_DISPLAY_COUNT = 20;
@@ -11,14 +13,16 @@ function mergeAndDedup(arr1, arr2) {
11
13
  return [...arr1, ...arr2].filter((t, i, arr) => arr.indexOf(t) === i);
12
14
  }
13
15
  class Notifications {
14
- constructor(client) {
16
+ constructor(client, options) {
17
+ var _a;
15
18
  this.subscriptions = {};
16
19
  this.bulkSubscriptions = {};
17
20
  this.topicPriorities = {};
18
21
  this.client = client;
22
+ this.enablePartialBulkResubscribe = (_a = options === null || options === void 0 ? void 0 : options.enablePartialBulkResubscribe) !== null && _a !== void 0 ? _a : false;
19
23
  client.on('pubsub:event', this.pubsubEvent.bind(this));
20
24
  client.on('connected', this.subscriptionsKeepAlive.bind(this));
21
- this.debouncedResubscribe = debounce(this.resubscribe.bind(this), 100);
25
+ this.debouncedResubscribe = debounce_promise_1.default(this.resubscribe.bind(this), 100);
22
26
  }
23
27
  get pubsubHost() {
24
28
  try {
@@ -37,7 +41,7 @@ class Notifications {
37
41
  this.stanzaInstance = stanza;
38
42
  if (needsToResub) {
39
43
  this.client.logger.info('resubscribing due to hard reconnect');
40
- this.debouncedResubscribe();
44
+ void this.debouncedResubscribe();
41
45
  }
42
46
  }
43
47
  topicHandlers(topic) {
@@ -178,7 +182,11 @@ class Notifications {
178
182
  logger: this.client.logger
179
183
  };
180
184
  const channelId = this.stanzaInstance.channelId;
181
- return this.client.http.requestApi(`notifications/channels/${channelId}/subscriptions`, requestOptions);
185
+ let path = `notifications/channels/${channelId}/subscriptions`;
186
+ if (this.enablePartialBulkResubscribe) {
187
+ path += '?ignoreErrors=true';
188
+ }
189
+ return this.client.http.requestApi(path, requestOptions);
182
190
  }
183
191
  createSubscription(topic, handler) {
184
192
  const topics = utils_1.splitIntoIndividualTopics(topic);
@@ -234,7 +242,7 @@ class Notifications {
234
242
  /* if we don't have bulk or individual subs, we don't need to resubscribe */
235
243
  const noTopics = bulkSubs.length + this.getActiveIndividualTopics().length === 0;
236
244
  if (noTopics) {
237
- return Promise.resolve();
245
+ return Promise.resolve({});
238
246
  }
239
247
  /* only pass in bulk subs with the replace flag – bulkSubscribe() will handle merging our individual topics (see PCM-1846) */
240
248
  return this.bulkSubscribe(bulkSubs, { replace: true });
@@ -289,7 +297,7 @@ class Notifications {
289
297
  }
290
298
  });
291
299
  }
292
- subscribe(topic, handler, immediate, priority) {
300
+ async subscribe(topic, handler, immediate, priority) {
293
301
  if (priority) {
294
302
  this.setTopicPriorities({ [topic]: priority });
295
303
  }
@@ -307,7 +315,19 @@ class Notifications {
307
315
  else {
308
316
  this.bulkSubscriptions[topic] = true;
309
317
  }
310
- return promise;
318
+ const result = await promise;
319
+ // Assume topic subscription succeeded if promise is resolved...
320
+ let topicResult = { topic, state: 'Permitted' };
321
+ // ... but if partial bulk resubscribe is enabled, use topic's individual result from the API response.
322
+ if (this.enablePartialBulkResubscribe && result && typeof result === 'object' && isTopicSubscribeResult(result[topic])) {
323
+ topicResult = result[topic];
324
+ }
325
+ // Topic result other than state=Permitted becomes a StreamingSubscriptionError promise rejection.
326
+ if (topicResult.state !== 'Permitted') {
327
+ const message = topicResult.rejectionReason || `Failed to subscribe topic ${topic}`;
328
+ throw new _1.StreamingSubscriptionError(message, topic, 'subscribe');
329
+ }
330
+ return topicResult;
311
331
  }
312
332
  unsubscribe(topic, handler, immediate) {
313
333
  if (handler) {
@@ -335,13 +355,35 @@ class Notifications {
335
355
  // if it's a forcible bulk subscribe, wipe out individual subscriptions
336
356
  this.subscriptions = {};
337
357
  }
338
- await this.makeBulkSubscribeRequest(toSubscribe, options);
358
+ const response = await this.makeBulkSubscribeRequest(toSubscribe, options);
359
+ let topicResponseEntities = [];
360
+ if (response && response.data && 'entities' in response.data && Array.isArray(response.data.entities)) {
361
+ topicResponseEntities = response.data.entities;
362
+ }
363
+ const topicResponsesById = {};
364
+ for (const topicEntity of topicResponseEntities) {
365
+ topicResponsesById[topicEntity.id] = topicEntity;
366
+ }
367
+ const result = {};
339
368
  if (options.replace) {
340
369
  this.bulkSubscriptions = {};
341
370
  }
342
371
  topics.forEach(topic => {
343
372
  this.bulkSubscriptions[topic] = true;
373
+ if (this.enablePartialBulkResubscribe) {
374
+ if (topic in topicResponsesById) {
375
+ const { state, rejectionReason } = topicResponsesById[topic];
376
+ result[topic] = { topic, state, rejectionReason };
377
+ }
378
+ else {
379
+ result[topic] = { topic, state: 'Unknown' };
380
+ }
381
+ }
382
+ else {
383
+ result[topic] = { topic, state: 'Permitted' };
384
+ }
344
385
  });
386
+ return result;
345
387
  }
346
388
  get expose() {
347
389
  return {
@@ -352,3 +394,12 @@ class Notifications {
352
394
  }
353
395
  }
354
396
  exports.Notifications = Notifications;
397
+ function isTopicSubscribeResult(value) {
398
+ let hasTopic = false;
399
+ let hasValidState = false;
400
+ if (value && typeof value === 'object') {
401
+ hasTopic = 'topic' in value && typeof value.topic === 'string';
402
+ hasValidState = 'state' in value && ['Permitted', 'Rejected', 'Unknown'].includes(value.state);
403
+ }
404
+ return hasTopic && hasValidState;
405
+ }
@@ -295,7 +295,8 @@ class GenesysCloudMediaSession {
295
295
  sdp: answer.sdp,
296
296
  sessionId: this.id
297
297
  };
298
- this.logger.info('sending sdp answer', params);
298
+ // Do not log the SDP payload to avoid logging sensitive information.
299
+ this.logger.info('sending sdp answer', { sessionId: this.id, conversationId: this.conversationId });
299
300
  return this.sendGenesysWebrtc({
300
301
  jsonrpc: '2.0',
301
302
  method: 'answer',
@@ -22,6 +22,8 @@ export interface IClientOptions {
22
22
  appVersion?: string;
23
23
  appId?: string;
24
24
  customHeaders?: ICustomHeader;
25
+ /** Allow bulk topic resubscribe to succeed or fail per-topic rather than all or nothing */
26
+ enablePartialBulkResubscribe?: boolean;
25
27
  }
26
28
  export interface ICustomHeader {
27
29
  [header: string]: string;
@@ -4,6 +4,12 @@ export declare class StreamingClientError extends Error {
4
4
  details?: unknown;
5
5
  constructor(type: StreamingClientErrorTypes | null, messageOrError: string | Error, details?: unknown);
6
6
  }
7
+ export declare class StreamingSubscriptionError extends Error {
8
+ readonly topic?: string | undefined;
9
+ readonly operation?: "subscribe" | "unsubscribe" | undefined;
10
+ name: string;
11
+ constructor(message: string, topic?: string | undefined, operation?: "subscribe" | "unsubscribe" | undefined);
12
+ }
7
13
  export declare function timeoutPromise(fn: Function, timeoutMs: number, msg: string, details?: any): Promise<any>;
8
14
  export declare function delay(ms: number): Promise<void>;
9
15
  export declare function splitIntoIndividualTopics(topicString: string): string[];
package/dist/npm/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.iceIsDifferent = exports.getIcePwdFromSdp = exports.getUfragFromSdp = exports.calculatePayloadSize = exports.parseJwt = exports.retryPromise = exports.isVideoJid = exports.isSoftphoneJid = exports.isScreenRecordingJid = exports.isAcdJid = exports.splitIntoIndividualTopics = exports.delay = exports.timeoutPromise = exports.StreamingClientError = void 0;
3
+ exports.iceIsDifferent = exports.getIcePwdFromSdp = exports.getUfragFromSdp = exports.calculatePayloadSize = exports.parseJwt = exports.retryPromise = exports.isVideoJid = exports.isSoftphoneJid = exports.isScreenRecordingJid = exports.isAcdJid = exports.splitIntoIndividualTopics = exports.delay = exports.timeoutPromise = exports.StreamingSubscriptionError = exports.StreamingClientError = void 0;
4
4
  const uuid_1 = require("uuid");
5
5
  const timeout_error_1 = require("./types/timeout-error");
6
6
  const interfaces_1 = require("./types/interfaces");
@@ -22,6 +22,15 @@ class StreamingClientError extends Error {
22
22
  }
23
23
  }
24
24
  exports.StreamingClientError = StreamingClientError;
25
+ class StreamingSubscriptionError extends Error {
26
+ constructor(message, topic, operation) {
27
+ super(message);
28
+ this.topic = topic;
29
+ this.operation = operation;
30
+ this.name = 'StreamingSubscriptionError';
31
+ }
32
+ }
33
+ exports.StreamingSubscriptionError = StreamingSubscriptionError;
25
34
  /* istanbul ignore next */
26
35
  function timeoutPromise(fn, timeoutMs, msg, details) {
27
36
  return new Promise(function (resolve, reject) {