@unboundcx/sdk 4.1.1 → 4.1.3

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 CHANGED
@@ -176,6 +176,36 @@ await api.objects.describe('contacts'); // Get schema
176
176
  await api.objects.list(); // List all object types
177
177
  ```
178
178
 
179
+ #### Live Queries (`api.objects.liveQuery`)
180
+
181
+ Real-time, server-evaluated subscriptions over App1 Database objects. The
182
+ server matches every mutation against your filter and pushes only relevant
183
+ events — no client-side polling or firehose filtering.
184
+
185
+ ```javascript
186
+ const handle = await api.objects.liveQuery({
187
+ socket, // an authed socket.io-client instance (required until the SDK ships its own transport)
188
+ object: 'contacts',
189
+ filter: { companyId: 'company-123' }, // bare value = equals; 'op::term' strings for other operators
190
+ onEvent: (frame) => {
191
+ // frame.type: 'enter' | 'change' | 'leave' | 'refresh' | 'resync' | 'revoked'
192
+ // 'enter' -> frame.record (full row now matches your filter)
193
+ // 'change' -> frame.changedFields ONLY (patch, never the full row)
194
+ // 'leave' -> frame.recordId no longer matches (or was deleted)
195
+ // 'refresh'/'resync' -> re-run your query (coarse mode or gap recovery)
196
+ },
197
+ onStateChange: (state) => {}, // 'active' | 'resubscribing' | 'revoked'
198
+ });
199
+
200
+ handle.unsubscribe(); // always tear down when done
201
+ ```
202
+
203
+ Notes: the resolved `handle.mode` is `'fine'` (row-level events) or
204
+ `'coarse'` (debounced refresh hints — used when the filter isn't
205
+ row-evaluable). Heartbeats, reconnect-resubscribe, and sequence-gap resync
206
+ are handled internally. Server caps: 25 subscriptions per socket, 500 per
207
+ account.
208
+
179
209
  #### Messaging (`api.messaging`)
180
210
 
181
211
  ```javascript
package/index.js CHANGED
@@ -27,6 +27,7 @@ import { EngagementMetricsService } from './services/engagementMetrics.js';
27
27
  import { TaskRouterService } from './services/taskRouter.js';
28
28
  import { KnowledgeBaseService } from './services/knowledgeBase.js';
29
29
  import { FaxService } from './services/fax.js';
30
+ import { PermissionsService } from './services/permissions.js';
30
31
 
31
32
  class UnboundSDK extends BaseSDK {
32
33
  constructor(options = {}) {
@@ -95,6 +96,7 @@ class UnboundSDK extends BaseSDK {
95
96
  this.taskRouter = new TaskRouterService(this);
96
97
  this.knowledgeBase = new KnowledgeBaseService(this);
97
98
  this.fax = new FaxService(this);
99
+ this.permissions = new PermissionsService(this);
98
100
 
99
101
  // Add additional services that might be missing
100
102
  this._initializeAdditionalServices();
@@ -274,4 +276,5 @@ export { TaskRouterService } from './services/taskRouter.js';
274
276
  export { WorkerService } from './services/taskRouter/WorkerService.js';
275
277
  export { KnowledgeBaseService } from './services/knowledgeBase.js';
276
278
  export { FaxService } from './services/fax.js';
279
+ export { PermissionsService } from './services/permissions.js';
277
280
  export { BaseSDK } from './base.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "4.1.1",
3
+ "version": "4.1.3",
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",
@@ -0,0 +1,377 @@
1
+ export class PermissionsService {
2
+ constructor(sdk) {
3
+ this.sdk = sdk;
4
+ }
5
+
6
+ /**
7
+ * List permission groups
8
+ * @returns {Promise<Object>} Object with results: Array of permission groups
9
+ * @example
10
+ * const { results } = await sdk.permissions.listGroups();
11
+ */
12
+ async listGroups() {
13
+ const result = await this.sdk._fetch('/permissions/groups', 'GET');
14
+ return result;
15
+ }
16
+
17
+ /**
18
+ * Create a new permission group
19
+ * @param {Object} group - Group configuration
20
+ * @param {string} group.name - Group name (required)
21
+ * @param {string} group.description - Group description
22
+ * @returns {Promise<Object>} Created group
23
+ * @example
24
+ * await sdk.permissions.createGroup({
25
+ * name: 'Support Leads',
26
+ * description: 'Escalation-tier support agents',
27
+ * });
28
+ */
29
+ async createGroup({ name, description }) {
30
+ this.sdk.validateParams(
31
+ { name, description },
32
+ {
33
+ name: { type: 'string', required: true },
34
+ description: { type: 'string', required: false },
35
+ },
36
+ );
37
+
38
+ const groupData = { name };
39
+ if (description !== undefined) groupData.description = description;
40
+
41
+ const params = {
42
+ body: groupData,
43
+ };
44
+
45
+ const result = await this.sdk._fetch('/permissions/groups', 'POST', params);
46
+ return result;
47
+ }
48
+
49
+ /**
50
+ * Update an existing permission group
51
+ * @param {string} groupId - Group ID to update
52
+ * @param {Object} data - Fields to update (e.g. name, description)
53
+ * @returns {Promise<Object>} Updated group
54
+ * @example
55
+ * await sdk.permissions.updateGroup('group-123', { description: 'Updated' });
56
+ */
57
+ async updateGroup(groupId, data) {
58
+ groupId = String(groupId);
59
+ this.sdk.validateParams(
60
+ { groupId },
61
+ {
62
+ groupId: { type: 'string', required: true },
63
+ },
64
+ );
65
+
66
+ const params = {
67
+ body: data,
68
+ };
69
+
70
+ const result = await this.sdk._fetch(
71
+ `/permissions/groups/${groupId}`,
72
+ 'PUT',
73
+ params,
74
+ );
75
+ return result;
76
+ }
77
+
78
+ /**
79
+ * Delete a permission group
80
+ * @param {string} groupId - Group ID to delete
81
+ * @returns {Promise<Object>} Deletion confirmation
82
+ * @example
83
+ * await sdk.permissions.deleteGroup('group-123');
84
+ */
85
+ async deleteGroup(groupId) {
86
+ groupId = String(groupId);
87
+ this.sdk.validateParams(
88
+ { groupId },
89
+ {
90
+ groupId: { type: 'string', required: true },
91
+ },
92
+ );
93
+
94
+ const result = await this.sdk._fetch(
95
+ `/permissions/groups/${groupId}`,
96
+ 'DELETE',
97
+ );
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * Add a user to a permission group
103
+ * @param {string} groupId - Group ID
104
+ * @param {string} userId - User ID to add
105
+ * @returns {Promise<Object>} Membership confirmation
106
+ * @example
107
+ * await sdk.permissions.addGroupMember('group-123', 'user-456');
108
+ */
109
+ async addGroupMember(groupId, userId) {
110
+ groupId = String(groupId);
111
+ userId = String(userId);
112
+ this.sdk.validateParams(
113
+ { groupId, userId },
114
+ {
115
+ groupId: { type: 'string', required: true },
116
+ userId: { type: 'string', required: true },
117
+ },
118
+ );
119
+
120
+ const params = {
121
+ body: { userId },
122
+ };
123
+
124
+ const result = await this.sdk._fetch(
125
+ `/permissions/groups/${groupId}/members`,
126
+ 'POST',
127
+ params,
128
+ );
129
+ return result;
130
+ }
131
+
132
+ /**
133
+ * Remove a user from a permission group
134
+ * @param {string} groupId - Group ID
135
+ * @param {string} userId - User ID to remove
136
+ * @returns {Promise<Object>} Removal confirmation
137
+ * @example
138
+ * await sdk.permissions.removeGroupMember('group-123', 'user-456');
139
+ */
140
+ async removeGroupMember(groupId, userId) {
141
+ groupId = String(groupId);
142
+ userId = String(userId);
143
+ this.sdk.validateParams(
144
+ { groupId, userId },
145
+ {
146
+ groupId: { type: 'string', required: true },
147
+ userId: { type: 'string', required: true },
148
+ },
149
+ );
150
+
151
+ const result = await this.sdk._fetch(
152
+ `/permissions/groups/${groupId}/members/${userId}`,
153
+ 'DELETE',
154
+ );
155
+ return result;
156
+ }
157
+
158
+ /**
159
+ * List permission sets (system defaults are returned first)
160
+ * @returns {Promise<Object>} Object with results: Array of permission sets
161
+ * @example
162
+ * const { results } = await sdk.permissions.listPermissionSets();
163
+ */
164
+ async listPermissionSets() {
165
+ const result = await this.sdk._fetch('/permissions/sets', 'GET');
166
+ return result;
167
+ }
168
+
169
+ /**
170
+ * Create a new permission set
171
+ * @param {Object} set - Permission set configuration
172
+ * @param {string} set.name - Permission set name (required)
173
+ * @param {Array<string>} set.scopes - Scopes granted by this set (required)
174
+ * @returns {Promise<Object>} Created permission set
175
+ * @example
176
+ * await sdk.permissions.createPermissionSet({
177
+ * name: 'Voice Admin',
178
+ * scopes: ['voice:calls:read', 'voice:calls:write'],
179
+ * });
180
+ */
181
+ async createPermissionSet({ name, scopes }) {
182
+ this.sdk.validateParams(
183
+ { name, scopes },
184
+ {
185
+ name: { type: 'string', required: true },
186
+ scopes: { type: 'array', required: true },
187
+ },
188
+ );
189
+
190
+ const params = {
191
+ body: { name, scopes },
192
+ };
193
+
194
+ const result = await this.sdk._fetch('/permissions/sets', 'POST', params);
195
+ return result;
196
+ }
197
+
198
+ /**
199
+ * Update an existing permission set
200
+ * @param {string} setId - Permission set ID to update
201
+ * @param {Object} data - Fields to update (e.g. name, scopes)
202
+ * @returns {Promise<Object>} Updated permission set
203
+ * @example
204
+ * await sdk.permissions.updatePermissionSet('set-123', { scopes: ['voice:calls:read'] });
205
+ */
206
+ async updatePermissionSet(setId, data) {
207
+ setId = String(setId);
208
+ this.sdk.validateParams(
209
+ { setId },
210
+ {
211
+ setId: { type: 'string', required: true },
212
+ },
213
+ );
214
+
215
+ const params = {
216
+ body: data,
217
+ };
218
+
219
+ const result = await this.sdk._fetch(
220
+ `/permissions/sets/${setId}`,
221
+ 'PUT',
222
+ params,
223
+ );
224
+ return result;
225
+ }
226
+
227
+ /**
228
+ * Delete a permission set
229
+ * @param {string} setId - Permission set ID to delete
230
+ * @returns {Promise<Object>} Deletion confirmation
231
+ * @example
232
+ * await sdk.permissions.deletePermissionSet('set-123');
233
+ */
234
+ async deletePermissionSet(setId) {
235
+ setId = String(setId);
236
+ this.sdk.validateParams(
237
+ { setId },
238
+ {
239
+ setId: { type: 'string', required: true },
240
+ },
241
+ );
242
+
243
+ const result = await this.sdk._fetch(
244
+ `/permissions/sets/${setId}`,
245
+ 'DELETE',
246
+ );
247
+ return result;
248
+ }
249
+
250
+ /**
251
+ * Assign a permission set to a principal (user or group)
252
+ * @param {Object} assignment - Assignment configuration
253
+ * @param {string} assignment.permissionSetId - Permission set ID (required)
254
+ * @param {string} assignment.principalType - Principal type, e.g. 'user' or 'group' (required)
255
+ * @param {string} assignment.principalId - Principal ID (required)
256
+ * @param {string} assignment.grantType - Grant type, e.g. 'allow' or 'deny' (required)
257
+ * @returns {Promise<Object>} Created assignment
258
+ * @example
259
+ * await sdk.permissions.assignPermissionSet({
260
+ * permissionSetId: 'set-123',
261
+ * principalType: 'user',
262
+ * principalId: 'user-456',
263
+ * grantType: 'allow',
264
+ * });
265
+ */
266
+ async assignPermissionSet({
267
+ permissionSetId,
268
+ principalType,
269
+ principalId,
270
+ grantType,
271
+ }) {
272
+ permissionSetId = String(permissionSetId);
273
+ principalId = String(principalId);
274
+ this.sdk.validateParams(
275
+ { permissionSetId, principalType, principalId, grantType },
276
+ {
277
+ permissionSetId: { type: 'string', required: true },
278
+ principalType: { type: 'string', required: true },
279
+ principalId: { type: 'string', required: true },
280
+ grantType: { type: 'string', required: true },
281
+ },
282
+ );
283
+
284
+ const params = {
285
+ body: { permissionSetId, principalType, principalId, grantType },
286
+ };
287
+
288
+ const result = await this.sdk._fetch(
289
+ '/permissions/assignments',
290
+ 'POST',
291
+ params,
292
+ );
293
+ return result;
294
+ }
295
+
296
+ /**
297
+ * Remove a permission set assignment from a principal
298
+ * @param {string} permissionSetId - Permission set ID
299
+ * @param {string} principalType - Principal type, e.g. 'user' or 'group'
300
+ * @param {string} principalId - Principal ID
301
+ * @returns {Promise<Object>} Removal confirmation
302
+ * @example
303
+ * await sdk.permissions.unassignPermissionSet('set-123', 'user', 'user-456');
304
+ */
305
+ async unassignPermissionSet(permissionSetId, principalType, principalId) {
306
+ permissionSetId = String(permissionSetId);
307
+ principalId = String(principalId);
308
+ this.sdk.validateParams(
309
+ { permissionSetId, principalType, principalId },
310
+ {
311
+ permissionSetId: { type: 'string', required: true },
312
+ principalType: { type: 'string', required: true },
313
+ principalId: { type: 'string', required: true },
314
+ },
315
+ );
316
+
317
+ const result = await this.sdk._fetch(
318
+ `/permissions/assignments/${permissionSetId}/${principalType}/${principalId}`,
319
+ 'DELETE',
320
+ );
321
+ return result;
322
+ }
323
+
324
+ /**
325
+ * Get a user's effective scopes, resolved from all assigned permission sets
326
+ * @param {string} userId - User ID
327
+ * @returns {Promise<Object>} Object with userId, scopes, breakdown ({scope, sources}[]), deniedScopes
328
+ * @example
329
+ * const { scopes, breakdown, deniedScopes } = await sdk.permissions.getEffectiveScopes('user-456');
330
+ */
331
+ /**
332
+ * Effective scopes contributed by membership in a group.
333
+ * @param {string|number} groupId - Group ID (required)
334
+ * @returns {Promise<Object>} { groupId, scopes, breakdown, deniedScopes }
335
+ */
336
+ async getGroupEffectiveScopes(groupId) {
337
+ groupId = String(groupId);
338
+ this.sdk.validateParams(
339
+ { groupId },
340
+ {
341
+ groupId: { type: 'string', required: true },
342
+ },
343
+ );
344
+ const result = await this.sdk._fetch(
345
+ `/permissions/groups/${groupId}/effective-scopes`,
346
+ 'GET',
347
+ );
348
+ return result;
349
+ }
350
+
351
+ async getEffectiveScopes(userId) {
352
+ userId = String(userId);
353
+ this.sdk.validateParams(
354
+ { userId },
355
+ {
356
+ userId: { type: 'string', required: true },
357
+ },
358
+ );
359
+
360
+ const result = await this.sdk._fetch(
361
+ `/permissions/users/${userId}/effective-scopes`,
362
+ 'GET',
363
+ );
364
+ return result;
365
+ }
366
+
367
+ /**
368
+ * Get the full catalog of available scopes, grouped by pillar
369
+ * @returns {Promise<Object>} Object with pillars: Array of {pillar, scopes: [{scope, label}]}
370
+ * @example
371
+ * const { pillars } = await sdk.permissions.getScopeCatalog();
372
+ */
373
+ async getScopeCatalog() {
374
+ const result = await this.sdk._fetch('/permissions/scope-catalog', 'GET');
375
+ return result;
376
+ }
377
+ }
package/services/video.js CHANGED
@@ -1043,6 +1043,24 @@ export class VideoService {
1043
1043
  return result;
1044
1044
  }
1045
1045
 
1046
+ /**
1047
+ * Get live presence for a video room (current participants, grouped by
1048
+ * waiting-room state)
1049
+ * @param {string} roomId - The video room ID
1050
+ * @returns {Promise} Live presence info
1051
+ */
1052
+ async getLivePresence(roomId) {
1053
+ this.sdk.validateParams(
1054
+ { roomId },
1055
+ {
1056
+ roomId: { type: 'string', required: true },
1057
+ },
1058
+ );
1059
+
1060
+ const result = await this.sdk._fetch(`/video/${roomId}/livePresence`, 'GET');
1061
+ return result;
1062
+ }
1063
+
1046
1064
  /**
1047
1065
  * Get the AI-generated summary for a video room's transcript
1048
1066
  * @param {string} roomId - The video room ID
package/services/voice.js CHANGED
@@ -22,9 +22,9 @@ export class VoiceService {
22
22
  return result;
23
23
  }
24
24
 
25
- async call({ to, from, destination, app, timeout, customHeaders }) {
25
+ async call({ to, from, destination, app, timeout, customHeaders, statusWebhook }) {
26
26
  this.sdk.validateParams(
27
- { to, from, destination, app, timeout, customHeaders },
27
+ { to, from, destination, app, timeout, customHeaders, statusWebhook },
28
28
  {
29
29
  to: { type: 'string', required: true },
30
30
  from: { type: 'string', required: true },
@@ -32,6 +32,10 @@ export class VoiceService {
32
32
  app: { type: 'object', required: false },
33
33
  timeout: { type: 'number', required: false },
34
34
  customHeaders: { type: 'object', required: false },
35
+ // { url, static } — internal endpoint that receives call progress
36
+ // events (trying/ringing/answered/failed) with `static` fields
37
+ // merged into each POST body
38
+ statusWebhook: { type: 'object', required: false },
35
39
  },
36
40
  );
37
41
 
@@ -43,6 +47,7 @@ export class VoiceService {
43
47
  app,
44
48
  timeout,
45
49
  customHeaders,
50
+ statusWebhook,
46
51
  },
47
52
  };
48
53