@unboundcx/sdk 2.7.7 → 2.8.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/base.js CHANGED
@@ -30,7 +30,7 @@ export class BaseSDK {
30
30
  this.token = token;
31
31
  this.fwRequestId = fwRequestId;
32
32
  }
33
-
33
+ this.baseURL;
34
34
  this.transports = new Map();
35
35
  this.debugMode = false;
36
36
  this._initializeEnvironment();
@@ -101,7 +101,7 @@ export class BaseSDK {
101
101
  removeTransport(name) {
102
102
  this.transports.delete(name);
103
103
  }
104
-
104
+
105
105
  _getJsonSafely(str, defaultValue) {
106
106
  try {
107
107
  return JSON.parse(str);
@@ -167,20 +167,21 @@ export class BaseSDK {
167
167
  }
168
168
 
169
169
  async _fetch(endpoint, method, params = {}, forceFetch = false) {
170
- const { body, query, headers = {} } = params;
170
+ const { body, query, headers = {}, returnRawResponse = false } = params;
171
171
 
172
172
  this.validateParams(
173
- { endpoint, method, body, query, headers },
173
+ { endpoint, method, body, query, headers, returnRawResponse },
174
174
  {
175
175
  endpoint: { type: 'string', required: true },
176
176
  method: { type: 'string', required: true },
177
177
  body: { type: 'object', required: false },
178
178
  query: { type: 'object', required: false },
179
179
  headers: { type: 'object', required: false },
180
+ returnRawResponse: { type: 'boolean', required: false },
180
181
  },
181
182
  );
182
183
 
183
- // Add auth headers
184
+ // Add auth headers
184
185
  if (this.token) {
185
186
  headers.Authorization = `Bearer ${this.token}`;
186
187
  }
@@ -205,68 +206,75 @@ export class BaseSDK {
205
206
  fwRequestId: this.fwRequestId,
206
207
  baseURL: this.baseURL || this.fullUrl,
207
208
  });
208
-
209
-
210
209
  } catch (err) {
211
210
  // IMPORTANT: This catch block should ONLY handle transport-level failures
212
211
  // (e.g., WebSocket disconnected, plugin unavailable, network errors)
213
- //
212
+ //
214
213
  // Transport plugins should:
215
214
  // - RETURN API error responses normally (400, 500, etc.) as response objects
216
215
  // - ONLY THROW for transport mechanism failures
217
-
216
+
218
217
  console.warn(
219
218
  `Transport ${transport.name} mechanism failed, falling back to HTTP:`,
220
219
  err.message,
221
220
  );
222
221
 
223
222
  // Built-in HTTP transport (fallback)
224
- return this._httpRequest(endpoint, method, params);
223
+ return this._httpRequest(endpoint, method, params, returnRawResponse);
225
224
  }
226
225
  } else {
227
226
  // No transport available, fallback to HTTP
228
- return this._httpRequest(endpoint, method, params);
227
+ return this._httpRequest(endpoint, method, params, returnRawResponse);
229
228
  }
230
229
 
231
- return this._processResponse(response, transport.name, method, endpoint);
230
+ // For streaming requests, return the raw response from transports
231
+ if (returnRawResponse) {
232
+ return response;
233
+ }
232
234
 
235
+ return this._processResponse(response, transport.name, method, endpoint);
233
236
  }
234
237
 
235
238
  _isMultipartBody(body) {
236
239
  // Check if body is FormData or multipart content
237
240
  if (!body) return false;
238
-
241
+
239
242
  // Browser FormData
240
243
  if (typeof FormData !== 'undefined' && body instanceof FormData) {
241
244
  return true;
242
245
  }
243
-
246
+
244
247
  // Node.js Buffer (our manual multipart construction)
245
248
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer(body)) {
246
249
  return true;
247
250
  }
248
-
251
+
249
252
  // Uint8Array (fallback multipart construction)
250
253
  if (body instanceof Uint8Array) {
251
254
  return true;
252
255
  }
253
-
256
+
254
257
  // String-based multipart (check for multipart boundaries)
255
- if (typeof body === 'string' && body.includes('Content-Disposition: form-data')) {
258
+ if (
259
+ typeof body === 'string' &&
260
+ body.includes('Content-Disposition: form-data')
261
+ ) {
256
262
  return true;
257
263
  }
258
-
264
+
259
265
  return false;
260
266
  }
261
267
 
262
- async _httpRequest(endpoint, method, params = {}) {
268
+ async _httpRequest(endpoint, method, params = {}, returnRawResponse = false) {
263
269
  const { body, query, headers = {} } = params;
264
270
 
265
271
  const options = {
266
272
  method,
267
273
  headers: {
268
274
  // Smart content-type detection based on actual body content
269
- ...(this._isMultipartBody(body) || headers?.['Content-Type'] || headers?.['content-type']
275
+ ...(this._isMultipartBody(body) ||
276
+ headers?.['Content-Type'] ||
277
+ headers?.['content-type']
270
278
  ? {}
271
279
  : { 'Content-Type': 'application/json' }),
272
280
  ...headers,
@@ -300,7 +308,10 @@ export class BaseSDK {
300
308
  body &&
301
309
  (body.constructor.name === 'FormData' ||
302
310
  typeof body.getBoundary === 'function');
303
- const isBuffer = (typeof Buffer !== 'undefined') && Buffer.isBuffer && Buffer.isBuffer(body);
311
+ const isBuffer =
312
+ typeof Buffer !== 'undefined' &&
313
+ Buffer.isBuffer &&
314
+ Buffer.isBuffer(body);
304
315
 
305
316
  if (isFormData || isBuffer) {
306
317
  options.body = body;
@@ -310,9 +321,13 @@ export class BaseSDK {
310
321
  }
311
322
 
312
323
  const response = await fetch(url, options);
313
-
324
+
325
+ // For streaming requests, return the raw fetch response
326
+ if (returnRawResponse) {
327
+ return response;
328
+ }
329
+
314
330
  return this._processResponse(response, 'https', method, endpoint);
315
-
316
331
  }
317
332
 
318
333
  async _processResponse(response, transport, method, endpoint) {
@@ -322,18 +337,24 @@ export class BaseSDK {
322
337
  const responseHeaders = response.headers;
323
338
  const responseRequestId =
324
339
  responseHeaders?.get?.('x-request-id') ||
325
- responseHeaders?.['x-request-id'] || '';
340
+ responseHeaders?.['x-request-id'] ||
341
+ '';
326
342
 
327
- const contentType = responseHeaders?.get?.('content-type') || response.headers['content-type'];
343
+ const contentType =
344
+ responseHeaders?.get?.('content-type') ||
345
+ response?.headers?.['content-type'] ||
346
+ 'application/json';
328
347
 
329
348
  if (!response.ok) {
330
349
  let errorBody;
331
350
  if (response?.body) {
332
- errorBody = response.body;
351
+ errorBody = response.body;
333
352
  } else if (response?.headers?.['content-type']) {
334
-
335
353
  try {
336
- if (typeof response?.json === 'function' || typeof response?.text === 'function') {
354
+ if (
355
+ typeof response?.json === 'function' ||
356
+ typeof response?.text === 'function'
357
+ ) {
337
358
  if (contentType.includes('application/json')) {
338
359
  errorBody = await response.json();
339
360
  } else if (contentType.includes('text/')) {
@@ -341,7 +362,10 @@ export class BaseSDK {
341
362
  }
342
363
  } else {
343
364
  if (contentType.includes('application/json')) {
344
- errorBody = this._getJsonSafely(response?.body, response?.body || {});
365
+ errorBody = this._getJsonSafely(
366
+ response?.body,
367
+ response?.body || {},
368
+ );
345
369
  } else if (contentType.includes('text/')) {
346
370
  errorBody = response?.body || '';
347
371
  }
@@ -355,29 +379,37 @@ export class BaseSDK {
355
379
  } else {
356
380
  errorBody = `HTTP ${response.status} ${response.statusText}`;
357
381
  }
358
-
382
+
359
383
  // Create a structured error for API/HTTP failures
360
- const httpError = new Error(`API :: Error :: ${transport} :: ${method.toUpperCase()} :: ${endpoint} :: ${response.status} :: ${response.statusText}`);
384
+ const httpError = new Error(
385
+ `API :: Error :: ${transport} :: ${method.toUpperCase()} :: ${endpoint} :: ${
386
+ response.status
387
+ } :: ${response.statusText}`,
388
+ );
361
389
  httpError.status = response.status;
362
390
  httpError.statusText = response.statusText;
363
391
  httpError.method = method;
364
392
  httpError.endpoint = endpoint;
365
393
  httpError.body = errorBody;
366
394
  httpError.message = errorBody?.error || errorBody?.message || 'API Error';
367
-
368
- // Debug logging for successful HTTP requests
369
- if (this.debugMode) {
370
- console.log(`API :: ERROR :: ${transport} :: ${method.toUpperCase()} :: ${endpoint} :: ${response?.status} :: ${responseRequestId}`, httpError);
371
- }
395
+
396
+ // Debug logging for successful HTTP requests
397
+ if (this.debugMode) {
398
+ console.log(
399
+ `API :: ERROR :: ${transport} :: ${method.toUpperCase()} :: ${endpoint} :: ${
400
+ response?.status
401
+ } :: ${responseRequestId}`,
402
+ httpError,
403
+ );
404
+ }
372
405
 
373
406
  throw httpError;
374
407
  }
375
408
 
376
409
  let responseBody;
377
410
  if (response?.body && !contentType) {
378
- responseBody = response.body;
411
+ responseBody = response.body;
379
412
  } else if (contentType) {
380
-
381
413
  try {
382
414
  if (transport === 'https') {
383
415
  if (contentType.includes('application/json')) {
@@ -403,15 +435,16 @@ export class BaseSDK {
403
435
  } else {
404
436
  responseBody = {};
405
437
  }
406
-
407
-
408
-
409
438
 
410
439
  // Debug logging for successful HTTP requests
411
440
  if (this.debugMode) {
412
- console.log(`API :: ${transport} :: ${method.toUpperCase()} :: ${endpoint} :: ${response?.status} :: ${responseRequestId}`);
441
+ console.log(
442
+ `API :: ${transport} :: ${method.toUpperCase()} :: ${endpoint} :: ${
443
+ response?.status
444
+ } :: ${responseRequestId}`,
445
+ );
413
446
  }
414
-
447
+
415
448
  return responseBody;
416
449
  }
417
450
  }
package/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { BaseSDK } from './base.js';
5
5
  import { LoginService } from './services/login.js';
6
6
  import { ObjectsService } from './services/objects.js';
7
- import { MessagingService } from './services/messaging.js';
7
+ import { MessagingService } from './services/messaging/MessagingService.js';
8
8
  import { VideoService } from './services/video.js';
9
9
  import { VoiceService } from './services/voice.js';
10
10
  import { AIService } from './services/ai.js';
@@ -23,6 +23,7 @@ import { EnrollService } from './services/enroll.js';
23
23
  import { PhoneNumbersService } from './services/phoneNumbers.js';
24
24
  import { RecordTypesService } from './services/recordTypes.js';
25
25
  import { GenerateIdService } from './services/generateId.js';
26
+ import { EngagementMetricsService } from './services/engagementMetrics.js';
26
27
 
27
28
  class UnboundSDK extends BaseSDK {
28
29
  constructor(options = {}) {
@@ -87,6 +88,7 @@ class UnboundSDK extends BaseSDK {
87
88
  this.phoneNumbers = new PhoneNumbersService(this);
88
89
  this.recordTypes = new RecordTypesService(this);
89
90
  this.generateId = new GenerateIdService(this);
91
+ this.engagementMetrics = new EngagementMetricsService(this);
90
92
 
91
93
  // Add additional services that might be missing
92
94
  this._initializeAdditionalServices();
@@ -190,4 +192,5 @@ export {
190
192
  UserRecordTypeDefaultsService,
191
193
  } from './services/recordTypes.js';
192
194
  export { GenerateIdService } from './services/generateId.js';
195
+ export { EngagementMetricsService } from './services/engagementMetrics.js';
193
196
  export { BaseSDK } from './base.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "2.7.7",
3
+ "version": "2.8.1",
4
4
  "description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/services/ai.js CHANGED
@@ -22,11 +22,10 @@ export class GenerativeService {
22
22
  method,
23
23
  }) {
24
24
  this.sdk.validateParams(
25
- { method },
25
+ { method, stream, temperature, subscriptionId, model, prompt, messages },
26
26
  {
27
27
  prompt: { type: 'string', required: false },
28
28
  messages: { type: 'array', required: false },
29
- relatedId: { type: 'string', required: false },
30
29
  model: { type: 'string', required: false },
31
30
  temperature: { type: 'number', required: false },
32
31
  subscriptionId: { type: 'string', required: false },
@@ -46,9 +45,18 @@ export class GenerativeService {
46
45
  stream,
47
46
  method,
48
47
  },
48
+ // Return raw response for streaming to allow client-side stream handling
49
+ returnRawResponse: stream === true,
49
50
  };
50
51
 
51
- const result = await this.sdk._fetch('/ai/generative/chat', 'POST', params);
52
+ // Force HTTP transport when streaming is enabled since NATS doesn't support streaming responses
53
+ const forceFetch = stream === true;
54
+ const result = await this.sdk._fetch(
55
+ '/ai/generative/chat',
56
+ 'POST',
57
+ params,
58
+ forceFetch,
59
+ );
52
60
  return result;
53
61
  }
54
62
 
@@ -135,12 +143,17 @@ export class GenerativeService {
135
143
  stream,
136
144
  method,
137
145
  },
146
+ // Return raw response for streaming to allow client-side stream handling
147
+ returnRawResponse: stream === true,
138
148
  };
139
149
 
150
+ // Force HTTP transport when streaming is enabled since NATS doesn't support streaming responses
151
+ const forceFetch = stream === true;
140
152
  const result = await this.sdk._fetch(
141
153
  '/ai/generative/ollama',
142
154
  'POST',
143
155
  params,
156
+ forceFetch,
144
157
  );
145
158
  return result;
146
159
  }
@@ -0,0 +1,153 @@
1
+ export class EngagementMetricsService {
2
+ constructor(sdk) {
3
+ this.sdk = sdk;
4
+ }
5
+
6
+ /**
7
+ * Get engagement metrics with flexible filtering and configurable response options
8
+ * @param {Object} options - Query options
9
+ * @param {string[]} [options.queueIds] - Array of queue IDs to filter by
10
+ * @param {string[]} [options.statuses] - Array of statuses to filter by (new, working, wrapUp, completed)
11
+ * @param {string[]} [options.userIds] - Array of user IDs to filter by
12
+ * @param {boolean} [options.includeSummary=true] - Include overall summary metrics
13
+ * @param {boolean} [options.includeByQueue=true] - Include metrics grouped by queue and status
14
+ * @param {boolean} [options.includeQueuePerformance=false] - Include queue performance metrics
15
+ * @param {boolean} [options.includeAgentPerformance=false] - Include agent performance metrics
16
+ * @returns {Promise<Object>} Metrics data
17
+ */
18
+ async getMetrics({
19
+ queueIds = [],
20
+ statuses = [],
21
+ userIds = [],
22
+ includeSummary = true,
23
+ includeByQueue = true,
24
+ includeQueuePerformance = false,
25
+ includeAgentPerformance = false,
26
+ } = {}) {
27
+ this.sdk.validateParams(
28
+ {
29
+ queueIds,
30
+ statuses,
31
+ userIds,
32
+ includeSummary,
33
+ includeByQueue,
34
+ includeQueuePerformance,
35
+ includeAgentPerformance,
36
+ },
37
+ {
38
+ queueIds: { type: 'array', required: false },
39
+ statuses: { type: 'array', required: false },
40
+ userIds: { type: 'array', required: false },
41
+ includeSummary: { type: 'boolean', required: false },
42
+ includeByQueue: { type: 'boolean', required: false },
43
+ includeQueuePerformance: { type: 'boolean', required: false },
44
+ includeAgentPerformance: { type: 'boolean', required: false },
45
+ },
46
+ );
47
+
48
+ const params = {
49
+ query: {
50
+ queueIds: Array.isArray(queueIds) ? queueIds.join(',') : queueIds,
51
+ statuses: Array.isArray(statuses) ? statuses.join(',') : statuses,
52
+ userIds: Array.isArray(userIds) ? userIds.join(',') : userIds,
53
+ includeSummary,
54
+ includeByQueue,
55
+ includeQueuePerformance,
56
+ includeAgentPerformance,
57
+ },
58
+ };
59
+
60
+ const result = await this.sdk._fetch('/engagementMetrics/', 'GET', params);
61
+ return result;
62
+ }
63
+
64
+ /**
65
+ * Get basic engagement metrics summary
66
+ * @param {Object} options - Filter options
67
+ * @param {string[]} [options.queueIds] - Array of queue IDs to filter by
68
+ * @param {string[]} [options.statuses] - Array of statuses to filter by
69
+ * @returns {Promise<Object>} Summary metrics
70
+ */
71
+ async getSummary({ queueIds = [], statuses = [] } = {}) {
72
+ return this.getMetrics({
73
+ queueIds,
74
+ statuses,
75
+ includeSummary: true,
76
+ includeByQueue: false,
77
+ includeQueuePerformance: false,
78
+ includeAgentPerformance: false,
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Get metrics grouped by queue and status
84
+ * @param {Object} options - Filter options
85
+ * @param {string[]} [options.queueIds] - Array of queue IDs to filter by
86
+ * @param {string[]} [options.statuses] - Array of statuses to filter by
87
+ * @returns {Promise<Object>} Queue-grouped metrics
88
+ */
89
+ async getByQueue({ queueIds = [], statuses = [] } = {}) {
90
+ return this.getMetrics({
91
+ queueIds,
92
+ statuses,
93
+ includeSummary: false,
94
+ includeByQueue: true,
95
+ includeQueuePerformance: false,
96
+ includeAgentPerformance: false,
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Get queue performance metrics
102
+ * @param {Object} options - Filter options
103
+ * @param {string[]} [options.queueIds] - Array of queue IDs to filter by
104
+ * @returns {Promise<Object>} Queue performance metrics
105
+ */
106
+ async getQueuePerformance({ queueIds = [] } = {}) {
107
+ return this.getMetrics({
108
+ queueIds,
109
+ includeSummary: false,
110
+ includeByQueue: false,
111
+ includeQueuePerformance: true,
112
+ includeAgentPerformance: false,
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Get agent performance metrics
118
+ * @param {Object} options - Filter options
119
+ * @param {string[]} [options.queueIds] - Array of queue IDs to filter by
120
+ * @param {string[]} [options.userIds] - Array of user IDs to filter by
121
+ * @returns {Promise<Object>} Agent performance metrics
122
+ */
123
+ async getAgentPerformance({ queueIds = [], userIds = [] } = {}) {
124
+ return this.getMetrics({
125
+ queueIds,
126
+ userIds,
127
+ includeSummary: false,
128
+ includeByQueue: false,
129
+ includeQueuePerformance: false,
130
+ includeAgentPerformance: true,
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Get comprehensive metrics for dashboard
136
+ * @param {Object} options - Filter options
137
+ * @param {string[]} [options.queueIds] - Array of queue IDs to filter by
138
+ * @param {string[]} [options.statuses] - Array of statuses to filter by
139
+ * @param {string[]} [options.userIds] - Array of user IDs to filter by
140
+ * @returns {Promise<Object>} All metrics
141
+ */
142
+ async getDashboardMetrics({ queueIds = [], statuses = [], userIds = [] } = {}) {
143
+ return this.getMetrics({
144
+ queueIds,
145
+ statuses,
146
+ userIds,
147
+ includeSummary: true,
148
+ includeByQueue: true,
149
+ includeQueuePerformance: true,
150
+ includeAgentPerformance: true,
151
+ });
152
+ }
153
+ }
@@ -0,0 +1,10 @@
1
+ import { TollFreeCampaignsService } from './TollFreeCampaignsService.js';
2
+ import { TenDlcCampaignsService } from './TenDlcCampaignsService.js';
3
+
4
+ export class CampaignsService {
5
+ constructor(sdk) {
6
+ this.sdk = sdk;
7
+ this.tollFree = new TollFreeCampaignsService(sdk);
8
+ this.tenDlc = new TenDlcCampaignsService(sdk);
9
+ }
10
+ }
@@ -0,0 +1,89 @@
1
+ export class EmailAddressesService {
2
+ constructor(sdk) {
3
+ this.sdk = sdk;
4
+ }
5
+
6
+ /**
7
+ * Create email address verification
8
+ * @param {string} emailAddress - Email address to verify (required)
9
+ * @returns {Promise<Object>} Email verification details
10
+ */
11
+ async create(emailAddress) {
12
+ this.sdk.validateParams(
13
+ { emailAddress },
14
+ {
15
+ emailAddress: { type: 'string', required: true },
16
+ },
17
+ );
18
+
19
+ const options = {
20
+ body: { emailAddress },
21
+ };
22
+
23
+ const result = await this.sdk._fetch(
24
+ '/messaging/email/validate/emailAddress',
25
+ 'POST',
26
+ options,
27
+ );
28
+ return result;
29
+ }
30
+
31
+ /**
32
+ * Delete email address verification
33
+ * @param {string} emailAddress - Email address to remove (required)
34
+ * @returns {Promise<Object>} Deletion confirmation
35
+ */
36
+ async delete(emailAddress) {
37
+ this.sdk.validateParams(
38
+ { emailAddress },
39
+ {
40
+ emailAddress: { type: 'string', required: true },
41
+ },
42
+ );
43
+
44
+ const result = await this.sdk._fetch(
45
+ `/messaging/email/validate/emailAddress/${encodeURIComponent(
46
+ emailAddress,
47
+ )}`,
48
+ 'DELETE',
49
+ );
50
+ return result;
51
+ }
52
+
53
+ /**
54
+ * List all verified email addresses
55
+ * @returns {Promise<Array>} List of verified email addresses
56
+ */
57
+ async list() {
58
+ const result = await this.sdk._fetch(
59
+ '/messaging/email/validate/emailAddress',
60
+ 'GET',
61
+ );
62
+ return result;
63
+ }
64
+
65
+ /**
66
+ * Check email address verification status
67
+ * @param {string} emailAddress - Email address to check (required)
68
+ * @returns {Promise<Object>} Email verification status
69
+ */
70
+ async checkStatus(emailAddress) {
71
+ this.sdk.validateParams(
72
+ { emailAddress },
73
+ {
74
+ emailAddress: { type: 'string', required: true },
75
+ },
76
+ );
77
+
78
+ const options = {
79
+ query: { emailAddress },
80
+ };
81
+
82
+ const result = await this.sdk._fetch(
83
+ '/messaging/email/validate/emailAddress/status',
84
+ 'GET',
85
+ options,
86
+ );
87
+ return result;
88
+ }
89
+ }