@unboundcx/sdk 4.8.17 → 4.9.0
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 +1 -1
- package/index.js +3 -0
- package/package.json +1 -1
- package/services/branding.js +352 -0
- package/services/layouts.js +25 -1
- package/services/voice.js +14 -8
package/README.md
CHANGED
|
@@ -352,7 +352,7 @@ const call = await api.voice.createCall({
|
|
|
352
352
|
// Call controls
|
|
353
353
|
await api.voice.mute(call.callControlId);
|
|
354
354
|
await api.voice.hold(call.callControlId);
|
|
355
|
-
await api.voice.sendDtmf({ callId: call.callId, dtmf: '1234' }); // mode: 'auto'|'rtp'|'inband'|'info'
|
|
355
|
+
await api.voice.sendDtmf({ callId: call.callId, dtmf: '1234' }); // mode: 'auto'|'rtp'|'inband'|'info'; target: 'leg' (default) | 'peer'
|
|
356
356
|
await api.voice.transfer(call.callControlId, '+1555555555');
|
|
357
357
|
await api.voice.hangup(call.callControlId);
|
|
358
358
|
|
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { SubscriptionsService } from './services/subscriptions.js';
|
|
|
14
14
|
import { WorkflowsService } from './services/workflows.js';
|
|
15
15
|
import { NotesService } from './services/notes.js';
|
|
16
16
|
import { StorageService } from './services/storage.js';
|
|
17
|
+
import { BrandingService } from './services/branding.js';
|
|
17
18
|
import { VerificationService } from './services/verification.js';
|
|
18
19
|
import { PortalsService } from './services/portals.js';
|
|
19
20
|
import { SipEndpointsService } from './services/sipEndpoints.js';
|
|
@@ -90,6 +91,7 @@ class UnboundSDK extends BaseSDK {
|
|
|
90
91
|
this.workflows = new WorkflowsService(this);
|
|
91
92
|
this.notes = new NotesService(this);
|
|
92
93
|
this.storage = new StorageService(this);
|
|
94
|
+
this.branding = new BrandingService(this);
|
|
93
95
|
this.verification = new VerificationService(this);
|
|
94
96
|
this.portals = new PortalsService(this);
|
|
95
97
|
this.sipEndpoints = new SipEndpointsService(this);
|
|
@@ -270,6 +272,7 @@ export { SubscriptionsService } from './services/subscriptions.js';
|
|
|
270
272
|
export { WorkflowsService } from './services/workflows.js';
|
|
271
273
|
export { NotesService } from './services/notes.js';
|
|
272
274
|
export { StorageService } from './services/storage.js';
|
|
275
|
+
export { BrandingService, BrandingEmailTemplatesService } from './services/branding.js';
|
|
273
276
|
export { VerificationService } from './services/verification.js';
|
|
274
277
|
export { PortalsService } from './services/portals.js';
|
|
275
278
|
export { SipEndpointsService } from './services/sipEndpoints.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unboundcx/sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
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,352 @@
|
|
|
1
|
+
import { internalRequest } from '../base.js';
|
|
2
|
+
|
|
3
|
+
const LOGO_KINDS = ['main', 'icon', 'favicon'];
|
|
4
|
+
|
|
5
|
+
const EXT_CONTENT_TYPES = {
|
|
6
|
+
png: 'image/png',
|
|
7
|
+
jpg: 'image/jpeg',
|
|
8
|
+
jpeg: 'image/jpeg',
|
|
9
|
+
gif: 'image/gif',
|
|
10
|
+
svg: 'image/svg+xml',
|
|
11
|
+
webp: 'image/webp',
|
|
12
|
+
ico: 'image/x-icon',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function guessContentType(fileName) {
|
|
16
|
+
const ext = (fileName || '').split('.').pop()?.toLowerCase();
|
|
17
|
+
return EXT_CONTENT_TYPES[ext] || 'application/octet-stream';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Public `branding` service — white-label brand settings, logo/domain
|
|
22
|
+
* management, and per-brand email template overrides.
|
|
23
|
+
*
|
|
24
|
+
* @see /workspace/code/app1/plans/white-label-plan.md §3
|
|
25
|
+
* @see app1-api src/services/branding/routes.js
|
|
26
|
+
*/
|
|
27
|
+
export class BrandingService {
|
|
28
|
+
constructor(sdk) {
|
|
29
|
+
this.sdk = sdk;
|
|
30
|
+
this.emailTemplates = new BrandingEmailTemplatesService(sdk);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Host-resolved brand for the current request (public-safe fields only).
|
|
35
|
+
* No auth required.
|
|
36
|
+
*
|
|
37
|
+
* @returns {Promise<Object>} Brand
|
|
38
|
+
*/
|
|
39
|
+
async current() {
|
|
40
|
+
const result = await internalRequest(this.sdk, '/branding/current', 'GET', {});
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get a brand by id. Requires brand-owner auth.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} id - Brand id
|
|
48
|
+
* @returns {Promise<Object>} Brand
|
|
49
|
+
*/
|
|
50
|
+
async get(id) {
|
|
51
|
+
this.sdk.validateParams(
|
|
52
|
+
{ id },
|
|
53
|
+
{ id: { type: 'string', required: true } },
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const result = await internalRequest(this.sdk, `/branding/${id}`, 'GET', {});
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Update a brand's settings. Requires brand-owner auth.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} id - Brand id
|
|
64
|
+
* @param {Object} patch - Whitelisted fields to update (displayName, colors,
|
|
65
|
+
* baseUrl/baseAppUrl/enrollmentUrl, primaryEmail/supportEmail/supportPhone, etc.)
|
|
66
|
+
* @returns {Promise<Object>} Updated brand
|
|
67
|
+
*/
|
|
68
|
+
async update(id, patch) {
|
|
69
|
+
this.sdk.validateParams(
|
|
70
|
+
{ id, patch },
|
|
71
|
+
{
|
|
72
|
+
id: { type: 'string', required: true },
|
|
73
|
+
patch: { type: 'object', required: true },
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const params = {
|
|
78
|
+
body: patch,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = await internalRequest(this.sdk, `/branding/${id}`, 'PATCH', params);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Upload a brand logo image. Requires brand-owner auth.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} id - Brand id
|
|
89
|
+
* @param {('main'|'icon'|'favicon')} kind - Which logo slot to upload into
|
|
90
|
+
* @param {(Buffer|Blob|File|{buffer: Buffer, fileName?: string, originalname?: string, contentType?: string, mimetype?: string})} file -
|
|
91
|
+
* The image. In Node, pass a Buffer (a generic filename is used) or a
|
|
92
|
+
* multer-style object (`{ buffer, originalname, mimetype }`). In the
|
|
93
|
+
* browser, pass a `File`/`Blob` (its `.name`/`.type` are used).
|
|
94
|
+
* @returns {Promise<Object>} Updated brand with the new logo URL
|
|
95
|
+
*/
|
|
96
|
+
async uploadLogo(id, kind, file) {
|
|
97
|
+
this.sdk.validateParams(
|
|
98
|
+
{ id, kind, file },
|
|
99
|
+
{
|
|
100
|
+
id: { type: 'string', required: true },
|
|
101
|
+
kind: { type: 'string', required: true },
|
|
102
|
+
file: { type: 'object', required: true },
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (!LOGO_KINDS.includes(kind)) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`branding.uploadLogo: kind must be one of ${LOGO_KINDS.join(', ')}, got: ${kind}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { body, headers } = this._buildLogoFormData(file);
|
|
113
|
+
|
|
114
|
+
const params = {
|
|
115
|
+
body,
|
|
116
|
+
headers,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const result = await internalRequest(
|
|
120
|
+
this.sdk,
|
|
121
|
+
`/branding/${id}/logos/${kind}`,
|
|
122
|
+
'POST',
|
|
123
|
+
params,
|
|
124
|
+
true,
|
|
125
|
+
);
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Private helper — builds a single-file multipart body under field name
|
|
130
|
+
// "file" (matches app1-api's `upload.single('file')`). Mirrors the
|
|
131
|
+
// Node/browser split in services/storage.js's upload helpers, trimmed to
|
|
132
|
+
// the single-small-image case (no streaming/progress paths needed here).
|
|
133
|
+
_buildLogoFormData(file) {
|
|
134
|
+
const isNode = typeof window === 'undefined';
|
|
135
|
+
|
|
136
|
+
if (!isNode) {
|
|
137
|
+
const formData = new FormData();
|
|
138
|
+
const fileName = file?.name || 'logo';
|
|
139
|
+
formData.append('file', file, fileName);
|
|
140
|
+
return { body: formData, headers: {} };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const buffer = Buffer.isBuffer(file) ? file : file?.buffer;
|
|
144
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
'branding.uploadLogo: file must be a Buffer, or an object with a Buffer .buffer property, in Node',
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const fileName = file?.fileName || file?.originalname || 'logo';
|
|
150
|
+
const contentType =
|
|
151
|
+
file?.contentType || file?.mimetype || guessContentType(fileName);
|
|
152
|
+
|
|
153
|
+
const boundary = `----formdata-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
154
|
+
const CRLF = '\r\n';
|
|
155
|
+
|
|
156
|
+
const header = Buffer.from(
|
|
157
|
+
`--${boundary}${CRLF}` +
|
|
158
|
+
`Content-Disposition: form-data; name="file"; filename="${fileName}"${CRLF}` +
|
|
159
|
+
`Content-Type: ${contentType}${CRLF}${CRLF}`,
|
|
160
|
+
'utf8',
|
|
161
|
+
);
|
|
162
|
+
const footer = Buffer.from(`${CRLF}--${boundary}--${CRLF}`, 'utf8');
|
|
163
|
+
const body = Buffer.concat([header, buffer, footer]);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
body,
|
|
167
|
+
headers: { 'content-type': `multipart/form-data; boundary=${boundary}` },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Trigger DNS verification of a brand's custom domain(s). Requires
|
|
173
|
+
* brand-owner auth.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} id - Brand id
|
|
176
|
+
* @returns {Promise<Object>} Verification result / updated `domainStatus`
|
|
177
|
+
*/
|
|
178
|
+
async verifyDomain(id) {
|
|
179
|
+
this.sdk.validateParams(
|
|
180
|
+
{ id },
|
|
181
|
+
{ id: { type: 'string', required: true } },
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const result = await internalRequest(this.sdk, `/branding/${id}/domains/verify`, 'POST', {});
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* List accounts on a brand. Requires brand-owner auth.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} id - Brand id
|
|
192
|
+
* @returns {Promise<Array>} Accounts
|
|
193
|
+
*/
|
|
194
|
+
async accounts(id) {
|
|
195
|
+
this.sdk.validateParams(
|
|
196
|
+
{ id },
|
|
197
|
+
{ id: { type: 'string', required: true } },
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const result = await internalRequest(this.sdk, `/branding/${id}/accounts`, 'GET', {});
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Per-brand system email template overrides. `sdk.branding.emailTemplates.*`.
|
|
207
|
+
* A brand template is a system default + optional brand override; these
|
|
208
|
+
* methods manage the override.
|
|
209
|
+
*/
|
|
210
|
+
export class BrandingEmailTemplatesService {
|
|
211
|
+
constructor(sdk) {
|
|
212
|
+
this.sdk = sdk;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* List all system email template types for a brand, each with a
|
|
217
|
+
* `source: 'default'|'override'` flag.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} id - Brand id
|
|
220
|
+
* @returns {Promise<Array>} Templates
|
|
221
|
+
*/
|
|
222
|
+
async list(id) {
|
|
223
|
+
this.sdk.validateParams(
|
|
224
|
+
{ id },
|
|
225
|
+
{ id: { type: 'string', required: true } },
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const result = await internalRequest(this.sdk, `/branding/${id}/email-templates`, 'GET', {});
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get the resolved template (override, falling back to system default)
|
|
234
|
+
* for one type.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} id - Brand id
|
|
237
|
+
* @param {string} type - Template type (newUser, newUserInvite, verification, forgotPassword, passwordChanged)
|
|
238
|
+
* @returns {Promise<Object>} Resolved template, with `isOverride` flag
|
|
239
|
+
*/
|
|
240
|
+
async get(id, type) {
|
|
241
|
+
this.sdk.validateParams(
|
|
242
|
+
{ id, type },
|
|
243
|
+
{
|
|
244
|
+
id: { type: 'string', required: true },
|
|
245
|
+
type: { type: 'string', required: true },
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const result = await internalRequest(this.sdk, `/branding/${id}/email-templates/${type}`, 'GET', {});
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Create or replace a brand's override for one template type.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} id - Brand id
|
|
257
|
+
* @param {string} type - Template type
|
|
258
|
+
* @param {Object} body - Template fields (fromEmail, subject, html, regions, redirects, ...)
|
|
259
|
+
* @returns {Promise<Object>} Updated override
|
|
260
|
+
*/
|
|
261
|
+
async update(id, type, body) {
|
|
262
|
+
this.sdk.validateParams(
|
|
263
|
+
{ id, type, body },
|
|
264
|
+
{
|
|
265
|
+
id: { type: 'string', required: true },
|
|
266
|
+
type: { type: 'string', required: true },
|
|
267
|
+
body: { type: 'object', required: true },
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const params = {
|
|
272
|
+
body,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const result = await internalRequest(this.sdk, `/branding/${id}/email-templates/${type}`, 'PUT', params);
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Remove a brand's override for one template type, reverting to the
|
|
281
|
+
* system default.
|
|
282
|
+
*
|
|
283
|
+
* @param {string} id - Brand id
|
|
284
|
+
* @param {string} type - Template type
|
|
285
|
+
* @returns {Promise<Object>} Confirmation
|
|
286
|
+
*/
|
|
287
|
+
async reset(id, type) {
|
|
288
|
+
this.sdk.validateParams(
|
|
289
|
+
{ id, type },
|
|
290
|
+
{
|
|
291
|
+
id: { type: 'string', required: true },
|
|
292
|
+
type: { type: 'string', required: true },
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const result = await internalRequest(this.sdk, `/branding/${id}/email-templates/${type}`, 'DELETE', {});
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Render a template (override or draft `body`) with sample variables for
|
|
302
|
+
* preview, without saving.
|
|
303
|
+
*
|
|
304
|
+
* @param {string} id - Brand id
|
|
305
|
+
* @param {string} type - Template type
|
|
306
|
+
* @param {Object} body - Draft template fields to render
|
|
307
|
+
* @returns {Promise<Object>} Rendered preview
|
|
308
|
+
*/
|
|
309
|
+
async preview(id, type, body) {
|
|
310
|
+
this.sdk.validateParams(
|
|
311
|
+
{ id, type, body },
|
|
312
|
+
{
|
|
313
|
+
id: { type: 'string', required: true },
|
|
314
|
+
type: { type: 'string', required: true },
|
|
315
|
+
body: { type: 'object', required: true },
|
|
316
|
+
},
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const params = {
|
|
320
|
+
body,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const result = await internalRequest(this.sdk, `/branding/${id}/email-templates/${type}/preview`, 'POST', params);
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Send a test email of a template to an address.
|
|
329
|
+
*
|
|
330
|
+
* @param {string} id - Brand id
|
|
331
|
+
* @param {string} type - Template type
|
|
332
|
+
* @param {string} to - Destination email address
|
|
333
|
+
* @returns {Promise<Object>} Send confirmation
|
|
334
|
+
*/
|
|
335
|
+
async sendTest(id, type, to) {
|
|
336
|
+
this.sdk.validateParams(
|
|
337
|
+
{ id, type, to },
|
|
338
|
+
{
|
|
339
|
+
id: { type: 'string', required: true },
|
|
340
|
+
type: { type: 'string', required: true },
|
|
341
|
+
to: { type: 'string', required: true },
|
|
342
|
+
},
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const params = {
|
|
346
|
+
body: { to },
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const result = await internalRequest(this.sdk, `/branding/${id}/email-templates/${type}/test`, 'POST', params);
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
}
|
package/services/layouts.js
CHANGED
|
@@ -169,13 +169,37 @@ export class LayoutsService {
|
|
|
169
169
|
},
|
|
170
170
|
);
|
|
171
171
|
|
|
172
|
-
const result = await internalRequest(this.sdk,
|
|
172
|
+
const result = await internalRequest(this.sdk,
|
|
173
173
|
`/layouts/${layoutId}/versions/${version}/rollback`,
|
|
174
174
|
'POST',
|
|
175
175
|
{},
|
|
176
176
|
);
|
|
177
177
|
return result;
|
|
178
178
|
}
|
|
179
|
+
|
|
180
|
+
async clone(id, { name } = {}) {
|
|
181
|
+
this.sdk.validateParams(
|
|
182
|
+
{ id },
|
|
183
|
+
{ id: { type: 'string', required: true } },
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const params = {
|
|
187
|
+
body: { name },
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const result = await internalRequest(this.sdk, `/layouts/${id}/clone`, 'POST', params);
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async getSystemSource(id) {
|
|
195
|
+
this.sdk.validateParams(
|
|
196
|
+
{ id },
|
|
197
|
+
{ id: { type: 'string', required: true } },
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const result = await internalRequest(this.sdk, `/layouts/${id}/system-source`, 'GET', {});
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
179
203
|
}
|
|
180
204
|
|
|
181
205
|
export class LayoutAssignmentsService {
|
package/services/voice.js
CHANGED
|
@@ -251,23 +251,29 @@ export class VoiceService {
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
/**
|
|
254
|
-
* Send DTMF digits on a live call (SIP call ID).
|
|
255
|
-
* RFC4733 RTP if negotiated, else in-band tones)
|
|
256
|
-
* (SIP INFO application/dtmf-relay).
|
|
254
|
+
* Send DTMF digits on a live call (SIP call ID).
|
|
255
|
+
* mode: 'auto' (default — RFC4733 RTP if negotiated, else in-band tones)
|
|
256
|
+
* | 'rtp' | 'inband' | 'info' (SIP INFO application/dtmf-relay).
|
|
257
|
+
* target: 'leg' (default) — transmit on callId's media toward its far end
|
|
258
|
+
* (e.g. a bot sending digits on the PSTN leg it dialed);
|
|
259
|
+
* 'peer' — as if the party on callId pressed the keys: the bridge
|
|
260
|
+
* relays to the other side (a user's keypad on their own leg).
|
|
257
261
|
*/
|
|
258
|
-
async sendDtmf({ callId, dtmf, mode }) {
|
|
262
|
+
async sendDtmf({ callId, dtmf, mode, target }) {
|
|
259
263
|
this.sdk.validateParams(
|
|
260
|
-
{ callId, dtmf, mode },
|
|
264
|
+
{ callId, dtmf, mode, target },
|
|
261
265
|
{
|
|
262
266
|
callId: { type: 'string', required: true },
|
|
263
267
|
dtmf: { type: 'string', required: true },
|
|
264
268
|
mode: { type: 'string', required: false },
|
|
269
|
+
target: { type: 'string', required: false },
|
|
265
270
|
},
|
|
266
271
|
);
|
|
267
272
|
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
273
|
+
const body = { callId, dtmf };
|
|
274
|
+
if (mode) body.mode = mode;
|
|
275
|
+
if (target) body.target = target;
|
|
276
|
+
const params = { body };
|
|
271
277
|
|
|
272
278
|
const result = await internalRequest(this.sdk, `/voice/dtmf`, 'POST', params);
|
|
273
279
|
return result;
|