@signalhousellc/sdk 1.4.1 → 1.4.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/package.json
CHANGED
|
File without changes
|
package/src/SignalHouseSDK.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
|
|
3
|
+
// Bounds every request made by this SDK. Chosen well above typical p99 latency so it only trips on
|
|
4
|
+
// a genuinely hung connection, not a slow-but-completing request.
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* @typedef {Object} RequestOptions
|
|
5
9
|
* @property {string} [token] - An optional bearer token to include in the request for authentication
|
|
@@ -28,6 +32,7 @@ import { Campaigns } from "./domains/Campaigns.js";
|
|
|
28
32
|
import { Groups } from "./domains/Groups.js";
|
|
29
33
|
import { Landings } from "./domains/Landings.js";
|
|
30
34
|
import { Messages } from "./domains/Messages.js";
|
|
35
|
+
import { Templates } from "./domains/Templates.js";
|
|
31
36
|
import { Numbers } from "./domains/Numbers.js";
|
|
32
37
|
import { Registrations } from "./domains/Registrations.js";
|
|
33
38
|
import { Onboarding } from "./domains/Onboarding.js";
|
|
@@ -74,6 +79,7 @@ export class SignalHouseSDK {
|
|
|
74
79
|
this.groups = new Groups(client, this.enableAdmin);
|
|
75
80
|
this.landings = new Landings(client, multipartClient, this.enableAdmin);
|
|
76
81
|
this.messages = new Messages(client, multipartClient, this.enableAdmin);
|
|
82
|
+
this.templates = new Templates(client, multipartClient, this.enableAdmin);
|
|
77
83
|
this.numbers = new Numbers(client, multipartClient, this.enableAdmin);
|
|
78
84
|
this.registrations = new Registrations(client, this.enableAdmin);
|
|
79
85
|
this.onboarding = new Onboarding(client, this.enableAdmin);
|
|
@@ -100,8 +106,10 @@ export class SignalHouseSDK {
|
|
|
100
106
|
* @returns {Function} A function to make API requests
|
|
101
107
|
*/
|
|
102
108
|
_createClient(isMultipart = false) {
|
|
103
|
-
// Create an Axios instance with the base URL and default headers
|
|
104
|
-
|
|
109
|
+
// Create an Axios instance with the base URL and default headers. `timeout` bounds every
|
|
110
|
+
// request so a hung connection (server-side lock contention, a stalled upstream call) rejects
|
|
111
|
+
// instead of leaving the caller's promise unsettled forever with no error and no feedback.
|
|
112
|
+
const instance = axios.create({ baseURL: this.baseUrl, timeout: REQUEST_TIMEOUT_MS, headers: isMultipart ? {} : { "Content-Type": "application/json" } });
|
|
105
113
|
|
|
106
114
|
// Add a request interceptor to include the API key in the Authorization header
|
|
107
115
|
instance.interceptors.request.use((/** @type {import('axios').InternalAxiosRequestConfig & { token?: string, idempotencyKey?: string }} */ config) => {
|
|
@@ -122,8 +130,17 @@ export class SignalHouseSDK {
|
|
|
122
130
|
return { success: true, data: response.data, status: response.status };
|
|
123
131
|
}),
|
|
124
132
|
|
|
125
|
-
// Non-2xx responses
|
|
133
|
+
// Non-2xx responses, plus timeouts and network failures (no `error.response` at all) - those
|
|
134
|
+
// get a clear message here rather than surfacing axios's internal error.message verbatim.
|
|
126
135
|
(error) => {
|
|
136
|
+
if (!error.response) {
|
|
137
|
+
const isTimeout = error.code === "ECONNABORTED" || /timeout/i.test(error.message || "");
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
error: isTimeout ? "Request timed out. Please try again." : "Network error. Please check your connection and try again.",
|
|
141
|
+
status: null,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
127
144
|
return {
|
|
128
145
|
success: false,
|
|
129
146
|
error: error.response?.data?.message || error.response?.data || error.message || 500,
|
package/src/domains/Campaigns.js
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
* @property {string} privacyPolicyLink - A URL linking to the privacy policy for the campaign (required, must be at most 2048 characters)
|
|
37
37
|
* @property {string} termsAndConditionsLink - A URL linking to the terms and conditions for the campaign (required, must be at most 2048 characters)
|
|
38
38
|
* @property {string} [embeddedLinkSample] - A sample of the embedded link used in the campaign (optional, must be at most 255 characters)
|
|
39
|
+
* @property {string[]} [sampleMediaIds] - Up to 5 mediaIds from uploadSampleMedia: sample MMS media shown to carrier and TCR reviewers (10DLC only)
|
|
39
40
|
* @property {Array<string>} phoneNumbers - An array of phone numbers associated with the campaign (required, each number must be at least 10 digits and contain only digits, a maximum of 5000 numbers is allowed)
|
|
40
41
|
*/
|
|
41
42
|
|
|
@@ -70,6 +71,7 @@
|
|
|
70
71
|
* @property {string} [privacyPolicyLink] - A URL linking to the privacy policy for the campaign (optional, must be at most 2048 characters if provided)
|
|
71
72
|
* @property {string} [termsAndConditionsLink] - A URL linking to the terms and conditions for the campaign (optional, must be at most 2048 characters if provided)
|
|
72
73
|
* @property {string} [embeddedLinkSample] - A sample of the embedded link used in the campaign (optional, must be at most 255 characters if provided)
|
|
74
|
+
* @property {string[]} [sampleMediaIds] - Up to 5 mediaIds from uploadSampleMedia: sample MMS media shown to carrier and TCR reviewers (10DLC only)
|
|
73
75
|
* @property {Object} [tollFree] - Toll-Free editable fields, used when updating a Toll-Free campaign. Mirrors the CreateTollFreeCampaignData `tollFree` object with every field optional (useCase, messageVolume, programSummary (40-500), exampleMessage (≤1000), customerCareEmail, optInImageURLs, optIns, multiNumberReason (≤500), useCases, channels, landingId). Phone numbers cannot be changed via update — Toll-Free numbers are locked to their campaign.
|
|
74
76
|
*/
|
|
75
77
|
|
|
@@ -529,6 +531,43 @@ export class Campaigns {
|
|
|
529
531
|
return this.client(`/campaign/short-code/artifact/${safeArtifactId}`, { method: 'GET', ...options });
|
|
530
532
|
}
|
|
531
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Upload a sample media file (JPEG, PNG, GIF, MP4 or MP3, up to 5MB) for a 10DLC campaign. Reference the
|
|
536
|
+
* returned mediaId in sampleMediaIds on campaign create or update; it is attached to the TCR campaign for
|
|
537
|
+
* carrier review
|
|
538
|
+
* @async
|
|
539
|
+
* @roles api, admin, developer, user
|
|
540
|
+
* @param {Object} params - The parameters for uploading the sample media
|
|
541
|
+
* @param {Blob|File} params.file - The media file to upload, provided as a Blob/File
|
|
542
|
+
* @param {string} [params.groupId] - The group the file belongs to. Required with a staff (signalhouse_*) key, which has no active group; otherwise optional and must be your own group
|
|
543
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
544
|
+
* @throws {Error} Throws an error if the file parameter is missing
|
|
545
|
+
* @returns {Promise<SdkResponse<any>>} The response from the server containing the mediaId, fileName, mimeType and size
|
|
546
|
+
*/
|
|
547
|
+
async uploadSampleMedia({ file, groupId, options = {} }) {
|
|
548
|
+
this.client._require({ file });
|
|
549
|
+
const formData = new FormData();
|
|
550
|
+
if (groupId) formData.append('groupId', groupId);
|
|
551
|
+
formData.append('file', file);
|
|
552
|
+
return this.multipartClient(`/campaign/sample-media`, { method: 'POST', body: formData, ...options });
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Download a campaign sample media file your group owns
|
|
557
|
+
* @async
|
|
558
|
+
* @roles api, admin, developer, user
|
|
559
|
+
* @param {Object} params - The parameters for downloading the sample media
|
|
560
|
+
* @param {string} params.mediaId - The mediaId returned by uploadSampleMedia
|
|
561
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
562
|
+
* @throws {Error} Throws an error if the mediaId parameter is missing
|
|
563
|
+
* @returns {Promise<SdkResponse<any>>} The response from the server containing the file bytes
|
|
564
|
+
*/
|
|
565
|
+
async readSampleMedia({ mediaId, options = {} }) {
|
|
566
|
+
this.client._require({ mediaId });
|
|
567
|
+
const safeMediaId = encodeURIComponent(mediaId);
|
|
568
|
+
return this.client(`/campaign/sample-media/${safeMediaId}`, { method: 'GET', ...options });
|
|
569
|
+
}
|
|
570
|
+
|
|
532
571
|
/**
|
|
533
572
|
* Upload an opt-in proof image to be hosted on-platform, returning a publicly-fetchable URL suitable for a
|
|
534
573
|
* Toll-Free campaign's optInImageURLs or a Short Code campaign's shortCode.screenshotUrl
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/** @typedef {import('../SignalHouseSDK.js').RequestOptions} RequestOptions */
|
|
2
|
+
/**
|
|
3
|
+
* @template T
|
|
4
|
+
* @typedef {import('../SignalHouseSDK.js').SdkResponse<T>} SdkResponse
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A version selector: a positive version number, "draft" or "published". Omitted means the published
|
|
9
|
+
* version when one exists, otherwise the draft; an explicit selector never falls back (404 when missing).
|
|
10
|
+
* @typedef {number|"draft"|"published"} VersionSelector
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} RcsVariable
|
|
15
|
+
* @property {string} key - Placeholder key used as {{key}} (/^[a-z][a-z0-9_]{0,63}$/)
|
|
16
|
+
* @property {string} [label] - Display label
|
|
17
|
+
* @property {"text"|"number"|"dateTime"|"phoneNumber"|"url"} type - Value type
|
|
18
|
+
* @property {boolean} required - Whether a send must supply a value
|
|
19
|
+
* @property {string} [default] - Value used when none is supplied
|
|
20
|
+
* @property {string} [sample] - Example value for previews
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The raw RCS template body. Unknown keys are rejected at every level; see the API docs for the block,
|
|
25
|
+
* suggestion and fallback shapes.
|
|
26
|
+
* @typedef {Object} RcsTemplateBody
|
|
27
|
+
* @property {"rcs"} channel - Always "rcs"
|
|
28
|
+
* @property {"rcs.blocks.v1"} contentFormat - Always "rcs.blocks.v1"
|
|
29
|
+
* @property {{ blocks: Object[] }} content - Ordered blocks of kind text, richCard, video, carousel, suggestions or attachment
|
|
30
|
+
* @property {RcsVariable[]} [variables] - Declared placeholders; omitted means []
|
|
31
|
+
* @property {{ trafficType?: string, ttlSeconds?: number }} [defaults] - Send defaults; omitted means {}
|
|
32
|
+
* @property {{ delivery?: { channel: "sms"|"mms", campaignId?: string, phoneNumber?: string, content: Object }, validitySeconds?: number }} [fallback] - SMS/MMS sent when RCS cannot be delivered; no delivery means no fallback
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} TemplateHead
|
|
37
|
+
* @property {string} templateId - "TPL" + 10 characters
|
|
38
|
+
* @property {string} groupId - Owning group
|
|
39
|
+
* @property {"rcs"} channel - Template channel
|
|
40
|
+
* @property {string} name - 1-120 characters, unique per group and channel
|
|
41
|
+
* @property {string|null} tag - Tag (1-60 characters) or null
|
|
42
|
+
* @property {string|null} archivedAt - When the template was archived
|
|
43
|
+
* @property {number|null} draftVersion - The current draft version
|
|
44
|
+
* @property {number|null} publishedVersion - The current published version
|
|
45
|
+
* @property {number} revision - Head revision, incremented on every write; starts at 1
|
|
46
|
+
* @property {string} createdBy - Creating user
|
|
47
|
+
* @property {string} createdAt - ISO timestamp
|
|
48
|
+
* @property {string} updatedAt - ISO timestamp
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {TemplateHead & { state: "DRAFT"|"PUBLISHED"|"ARCHIVED", hasUnpublishedChanges: boolean }} TemplateSummary
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @typedef {Object} VersionSummary
|
|
57
|
+
* @property {string} templateId - The template ID
|
|
58
|
+
* @property {string} groupId - Owning group
|
|
59
|
+
* @property {number} version - Version number
|
|
60
|
+
* @property {"DRAFT"|"PUBLISHED"} state - Version state
|
|
61
|
+
* @property {string} contentHash - Hex SHA-256 of the canonical body JSON
|
|
62
|
+
* @property {string} createdBy - Creating user
|
|
63
|
+
* @property {string} createdAt - ISO timestamp
|
|
64
|
+
* @property {string} updatedAt - ISO timestamp
|
|
65
|
+
* @property {string|null} publishedAt - ISO timestamp
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @typedef {VersionSummary & { body: RcsTemplateBody }} TemplateVersion
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @typedef {Object} TemplateDetail
|
|
74
|
+
* @property {TemplateSummary} head - The template head
|
|
75
|
+
* @property {TemplateVersion} version - The selected version
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @template T
|
|
80
|
+
* @typedef {{ data: T[], total: number, page: number, limit: number }} Page
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @typedef {Object} ValidationIssue
|
|
85
|
+
* @property {"error"|"warning"} severity - Issue severity
|
|
86
|
+
* @property {string} path - JSON path of the offending field
|
|
87
|
+
* @property {string} code - Machine-readable code
|
|
88
|
+
* @property {string} message - Human-readable message
|
|
89
|
+
* @property {string} [blockId] - The block the issue belongs to
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @typedef {Object} ValidationResult
|
|
94
|
+
* @property {boolean} valid - Whether the body is publishable
|
|
95
|
+
* @property {string} contentHash - Hex SHA-256 of the canonical body JSON
|
|
96
|
+
* @property {string} validatorVersion - The validator that produced the result
|
|
97
|
+
* @property {ValidationIssue[]} errors - Publication errors
|
|
98
|
+
* @property {ValidationIssue[]} warnings - Non-blocking warnings
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @typedef {Object} MediaAsset
|
|
103
|
+
* @property {string} mediaId - "MED" + 10 characters
|
|
104
|
+
* @property {string} groupId - Owning group
|
|
105
|
+
* @property {string} url - Public HTTPS URL to reference from a template body
|
|
106
|
+
* @property {number} bytes - File size
|
|
107
|
+
* @property {string} mimeType - MIME type
|
|
108
|
+
* @property {"image"|"video"|"audio"|"file"} kind - Media kind
|
|
109
|
+
* @property {number|null} width - Pixel width, when known
|
|
110
|
+
* @property {number|null} height - Pixel height, when known
|
|
111
|
+
* @property {string} createdAt - ISO timestamp
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Server-defined channel capabilities; values come from the server and drive client-side editors.
|
|
116
|
+
* @typedef {Object} Capabilities
|
|
117
|
+
* @property {"rcs"} channel - The channel described
|
|
118
|
+
* @property {"rcs.blocks.v1"} contentFormat - The body format
|
|
119
|
+
* @property {string} validatorVersion - The validator version
|
|
120
|
+
* @property {string[]} blockKinds - Allowed block kinds
|
|
121
|
+
* @property {string[]} actionTypes - Allowed suggestion types
|
|
122
|
+
* @property {string[]} variableTypes - Allowed variable types
|
|
123
|
+
* @property {string[]} trafficTypes - Allowed traffic types
|
|
124
|
+
* @property {string[]} cardWidths - Allowed carousel card widths
|
|
125
|
+
* @property {string[]} mediaHeights - Allowed card media heights
|
|
126
|
+
* @property {Object<string, number>} limits - Length and count limits (maxBlocks, maxTextLength, maxCarouselCards, ...)
|
|
127
|
+
* @property {{ maxUploadBytes: number, mimeTypes: string[] }} media - Upload constraints
|
|
128
|
+
* @property {{ provider: "infobip"|"sinch", adapterVersion: string, limits: Object<string, number>, unsupported: Array<{ feature: string, code: string, message: string }> }|null} provider - Provider-specific limits when a provider was requested
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build the client-side validation error the SDK throws for a malformed request.
|
|
133
|
+
* @param {string} message - The error message
|
|
134
|
+
* @returns {Error & { status?: number }} - An error named SignalHouseValidationError with status 400
|
|
135
|
+
*/
|
|
136
|
+
function validationError(message) {
|
|
137
|
+
const error = /** @type {Error & { status?: number }} */ (new Error(message));
|
|
138
|
+
error.name = "SignalHouseValidationError";
|
|
139
|
+
error.status = 400;
|
|
140
|
+
return error;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Build the update request body; a null tag stays on the wire, an undefined one is dropped.
|
|
145
|
+
* @param {Object} params - The update fields
|
|
146
|
+
* @param {string} params.groupId - The owning group
|
|
147
|
+
* @param {string} [params.name] - New name
|
|
148
|
+
* @param {string|null} [params.tag] - New tag; null clears it, undefined leaves it unchanged
|
|
149
|
+
* @param {RcsTemplateBody} [params.body] - Full replacement body
|
|
150
|
+
* @returns {Object} - The request body
|
|
151
|
+
*/
|
|
152
|
+
export function templateUpdateBody({ groupId, name, tag, body }) {
|
|
153
|
+
return { groupId, name, tag, body };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Build the validate request body from exactly one of body or version.
|
|
158
|
+
* @param {Object} params - The validate fields
|
|
159
|
+
* @param {string} params.groupId - The owning group
|
|
160
|
+
* @param {RcsTemplateBody} [params.body] - An unsaved body to validate
|
|
161
|
+
* @param {VersionSelector} [params.version] - A stored version to validate
|
|
162
|
+
* @throws {Error} Throws a SignalHouseValidationError unless exactly one of body or version is given
|
|
163
|
+
* @returns {Object} - The request body
|
|
164
|
+
*/
|
|
165
|
+
export function templateValidateBody({ groupId, body, version }) {
|
|
166
|
+
const hasBody = body !== undefined && body !== null;
|
|
167
|
+
const hasVersion = version !== undefined && version !== null;
|
|
168
|
+
if (hasBody === hasVersion) throw validationError("Pass exactly one of body or version");
|
|
169
|
+
return hasBody ? { groupId, body } : { groupId, version };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build the multipart form for a media upload.
|
|
174
|
+
* @param {Object} params - The upload fields
|
|
175
|
+
* @param {string} params.groupId - The owning group
|
|
176
|
+
* @param {Blob|File} params.file - The file to upload
|
|
177
|
+
* @param {string} [params.fileName] - File name to send with the file
|
|
178
|
+
* @returns {FormData} - The form with file and groupId fields
|
|
179
|
+
*/
|
|
180
|
+
export function templateMediaForm({ groupId, file, fileName }) {
|
|
181
|
+
const formData = new FormData();
|
|
182
|
+
if (fileName) formData.append("file", file, fileName);
|
|
183
|
+
else formData.append("file", file);
|
|
184
|
+
formData.append("groupId", groupId);
|
|
185
|
+
return formData;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Channel-generic message templates under /template. Templates keep an immutable published version and
|
|
190
|
+
* at most one editable draft; writes apply directly (last write wins). 404 while the environment's RCS
|
|
191
|
+
* switch is off.
|
|
192
|
+
*/
|
|
193
|
+
export class Templates {
|
|
194
|
+
constructor(client, multipartClient, enableAdmin) {
|
|
195
|
+
this.client = client;
|
|
196
|
+
this.multipartClient = multipartClient;
|
|
197
|
+
this.enableAdmin = enableAdmin;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create a template with draft version 1
|
|
202
|
+
* @async
|
|
203
|
+
* @roles api, admin, developer
|
|
204
|
+
* @param {Object} params - The parameters for creating a template
|
|
205
|
+
* @param {string} params.groupId - The owning group
|
|
206
|
+
* @param {string} params.name - Template name (1-120 characters, unique per group and channel)
|
|
207
|
+
* @param {string} [params.tag] - Tag (1-60 characters)
|
|
208
|
+
* @param {RcsTemplateBody} params.body - The raw template body
|
|
209
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
210
|
+
* @returns {Promise<SdkResponse<TemplateDetail>>} A promise that resolves to the new template and its draft (201)
|
|
211
|
+
*/
|
|
212
|
+
async create({ groupId, name, tag, body, options = {} }) {
|
|
213
|
+
this.client._require({ groupId, name, body });
|
|
214
|
+
return this.client(`/template`, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
body: { groupId, name, tag, body },
|
|
217
|
+
...options,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* List a group's templates
|
|
223
|
+
* @async
|
|
224
|
+
* @roles api, admin, developer, billing, user
|
|
225
|
+
* @param {Object} params - The parameters for listing templates
|
|
226
|
+
* @param {string} params.groupId - The owning group
|
|
227
|
+
* @param {("DRAFT"|"PUBLISHED"|"ARCHIVED")} [params.state] - Filter by state
|
|
228
|
+
* @param {"rcs"} [params.channel] - Filter by channel
|
|
229
|
+
* @param {string} [params.tag] - Filter by tag
|
|
230
|
+
* @param {string} [params.search] - Search template names
|
|
231
|
+
* @param {("name"|"createdAt"|"updatedAt")} [params.sortBy] - Sort field
|
|
232
|
+
* @param {("asc"|"desc")} [params.sortOrder] - Sort direction
|
|
233
|
+
* @param {number} [params.page] - Page number (default 1)
|
|
234
|
+
* @param {number} [params.limit] - Templates per page (default 25, max 100)
|
|
235
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
236
|
+
* @returns {Promise<SdkResponse<Page<TemplateSummary>>>} A promise that resolves to { data, total, page, limit }
|
|
237
|
+
*/
|
|
238
|
+
async list({ groupId, state, channel, tag, search, sortBy, sortOrder, page, limit, options = {} }) {
|
|
239
|
+
this.client._require({ groupId });
|
|
240
|
+
const queryString = this.client._getQueryString({ groupId, state, channel, tag, search, sortBy, sortOrder, page, limit });
|
|
241
|
+
return this.client(`/template${queryString}`, { method: "GET", ...options });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get a template with one of its versions
|
|
246
|
+
* @async
|
|
247
|
+
* @roles api, admin, developer, billing, user
|
|
248
|
+
* @param {Object} params - The parameters for getting a template
|
|
249
|
+
* @param {string} params.templateId - The template ID
|
|
250
|
+
* @param {string} params.groupId - The owning group
|
|
251
|
+
* @param {VersionSelector} [params.version] - Version to return; defaults to published, else draft
|
|
252
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
253
|
+
* @returns {Promise<SdkResponse<TemplateDetail>>} A promise that resolves to { head, version }
|
|
254
|
+
*/
|
|
255
|
+
async get({ templateId, groupId, version, options = {} }) {
|
|
256
|
+
this.client._require({ templateId, groupId });
|
|
257
|
+
const safeId = encodeURIComponent(templateId);
|
|
258
|
+
const queryString = this.client._getQueryString({ groupId, version });
|
|
259
|
+
return this.client(`/template/${safeId}${queryString}`, { method: "GET", ...options });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Update a template's name, tag or draft body; editing the body of a published template opens a new draft
|
|
264
|
+
* @async
|
|
265
|
+
* @roles api, admin, developer
|
|
266
|
+
* @param {Object} params - The parameters for updating a template
|
|
267
|
+
* @param {string} params.templateId - The template ID
|
|
268
|
+
* @param {string} params.groupId - The owning group
|
|
269
|
+
* @param {string} [params.name] - New name
|
|
270
|
+
* @param {string|null} [params.tag] - New tag; null clears it, omitted leaves it unchanged
|
|
271
|
+
* @param {RcsTemplateBody} [params.body] - Full replacement body
|
|
272
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
273
|
+
* @returns {Promise<SdkResponse<TemplateDetail>>} A promise that resolves to { head, version }
|
|
274
|
+
*/
|
|
275
|
+
async update({ templateId, groupId, name, tag, body, options = {} }) {
|
|
276
|
+
this.client._require({ templateId, groupId });
|
|
277
|
+
const safeId = encodeURIComponent(templateId);
|
|
278
|
+
return this.client(`/template/${safeId}`, {
|
|
279
|
+
method: "PUT",
|
|
280
|
+
body: templateUpdateBody({ groupId, name, tag, body }),
|
|
281
|
+
...options,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Publish a template's current draft; no draft returns 409 NO_DRAFT, a draft changed mid-publish returns 409 DRAFT_CHANGED (publish again) and an incomplete draft returns 400 TEMPLATE_INVALID
|
|
287
|
+
* @async
|
|
288
|
+
* @roles api, admin, developer
|
|
289
|
+
* @param {Object} params - The parameters for publishing a template
|
|
290
|
+
* @param {string} params.templateId - The template ID
|
|
291
|
+
* @param {string} params.groupId - The owning group
|
|
292
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
293
|
+
* @returns {Promise<SdkResponse<TemplateDetail>>} A promise that resolves to { head, version } for the published version
|
|
294
|
+
*/
|
|
295
|
+
async publish({ templateId, groupId, options = {} }) {
|
|
296
|
+
this.client._require({ templateId, groupId });
|
|
297
|
+
const safeId = encodeURIComponent(templateId);
|
|
298
|
+
return this.client(`/template/${safeId}/publish`, {
|
|
299
|
+
method: "POST",
|
|
300
|
+
body: { groupId },
|
|
301
|
+
...options,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Archive a template
|
|
307
|
+
* @async
|
|
308
|
+
* @roles api, admin, developer
|
|
309
|
+
* @param {Object} params - The parameters for archiving a template
|
|
310
|
+
* @param {string} params.templateId - The template ID
|
|
311
|
+
* @param {string} params.groupId - The owning group
|
|
312
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
313
|
+
* @returns {Promise<SdkResponse<TemplateHead>>} A promise that resolves to the archived template head
|
|
314
|
+
*/
|
|
315
|
+
async archive({ templateId, groupId, options = {} }) {
|
|
316
|
+
this.client._require({ templateId, groupId });
|
|
317
|
+
const safeId = encodeURIComponent(templateId);
|
|
318
|
+
return this.client(`/template/${safeId}/archive`, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
body: { groupId },
|
|
321
|
+
...options,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* List a template's versions without their bodies
|
|
327
|
+
* @async
|
|
328
|
+
* @roles api, admin, developer, billing, user
|
|
329
|
+
* @param {Object} params - The parameters for listing versions
|
|
330
|
+
* @param {string} params.templateId - The template ID
|
|
331
|
+
* @param {string} params.groupId - The owning group
|
|
332
|
+
* @param {number} [params.page] - Page number (default 1)
|
|
333
|
+
* @param {number} [params.limit] - Versions per page (default 25, max 100)
|
|
334
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
335
|
+
* @returns {Promise<SdkResponse<Page<VersionSummary>>>} A promise that resolves to { data, total, page, limit }
|
|
336
|
+
*/
|
|
337
|
+
async versions({ templateId, groupId, page, limit, options = {} }) {
|
|
338
|
+
this.client._require({ templateId, groupId });
|
|
339
|
+
const safeId = encodeURIComponent(templateId);
|
|
340
|
+
const queryString = this.client._getQueryString({ groupId, page, limit });
|
|
341
|
+
return this.client(`/template/${safeId}/versions${queryString}`, { method: "GET", ...options });
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Validate an unsaved body or a stored version without writing anything
|
|
346
|
+
* @async
|
|
347
|
+
* @roles api, admin, developer, billing, user
|
|
348
|
+
* @param {Object} params - The parameters for validation; pass exactly one of body or version
|
|
349
|
+
* @param {string} params.templateId - The template ID
|
|
350
|
+
* @param {string} params.groupId - The owning group
|
|
351
|
+
* @param {RcsTemplateBody} [params.body] - An unsaved body to validate
|
|
352
|
+
* @param {VersionSelector} [params.version] - A stored version to validate
|
|
353
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
354
|
+
* @throws {Error} Throws a SignalHouseValidationError unless exactly one of body or version is given
|
|
355
|
+
* @returns {Promise<SdkResponse<ValidationResult>>} A promise that resolves to { valid, contentHash, validatorVersion, errors, warnings }
|
|
356
|
+
*/
|
|
357
|
+
async validate({ templateId, groupId, body, version, options = {} }) {
|
|
358
|
+
this.client._require({ templateId, groupId });
|
|
359
|
+
const safeId = encodeURIComponent(templateId);
|
|
360
|
+
return this.client(`/template/${safeId}/validate`, {
|
|
361
|
+
method: "POST",
|
|
362
|
+
body: templateValidateBody({ groupId, body, version }),
|
|
363
|
+
...options,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Get a channel's template capabilities: allowed blocks, actions, limits and upload constraints
|
|
369
|
+
* @async
|
|
370
|
+
* @roles api, admin, developer, billing, user
|
|
371
|
+
* @param {Object} params - The parameters for getting capabilities
|
|
372
|
+
* @param {string} params.groupId - The owning group
|
|
373
|
+
* @param {"rcs"} [params.channel] - The channel (default "rcs")
|
|
374
|
+
* @param {("infobip"|"sinch")} [params.provider] - Include this provider's limits and unsupported features
|
|
375
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
376
|
+
* @returns {Promise<SdkResponse<Capabilities>>} A promise that resolves to the capabilities
|
|
377
|
+
*/
|
|
378
|
+
async capabilities({ groupId, channel, provider, options = {} }) {
|
|
379
|
+
this.client._require({ groupId });
|
|
380
|
+
const queryString = this.client._getQueryString({ groupId, channel, provider });
|
|
381
|
+
return this.client(`/template/capabilities${queryString}`, { method: "GET", ...options });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Upload a media file for use in template bodies; returns its public URL. 503 when media storage is not configured
|
|
386
|
+
* @async
|
|
387
|
+
* @roles api, admin, developer
|
|
388
|
+
* @param {Object} params - The parameters for the upload
|
|
389
|
+
* @param {string} params.groupId - The owning group
|
|
390
|
+
* @param {Blob|File} params.file - The file to upload
|
|
391
|
+
* @param {string} [params.fileName] - File name to send with the file
|
|
392
|
+
* @param {RequestOptions} [params.options] - Additional options for the request
|
|
393
|
+
* @returns {Promise<SdkResponse<MediaAsset>>} A promise that resolves to the stored media asset (201)
|
|
394
|
+
*/
|
|
395
|
+
async uploadMedia({ groupId, file, fileName, options = {} }) {
|
|
396
|
+
this.client._require({ groupId, file });
|
|
397
|
+
return this.multipartClient(`/template/media`, {
|
|
398
|
+
method: "POST",
|
|
399
|
+
body: templateMediaForm({ groupId, file, fileName }),
|
|
400
|
+
...options,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
package/src/domains/Voice.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SipTrunks } from "./voice/SipTrunks.js";
|
|
2
2
|
import { SipProfiles } from "./voice/SipProfiles.js";
|
|
3
3
|
import { ProgrammableVoiceProfiles } from "./voice/ProgrammableVoiceProfiles.js";
|
|
4
|
+
import { NumberRoutes } from "./voice/NumberRoutes.js";
|
|
4
5
|
import { Calls } from "./voice/Calls.js";
|
|
5
6
|
import { CallLogs } from "./voice/CallLogs.js";
|
|
6
7
|
import { GlobalVoiceSettings } from "./voice/GlobalVoiceSettings.js";
|
|
@@ -16,6 +17,7 @@ import { Analytics } from "./voice/Analytics.js";
|
|
|
16
17
|
* - `sdk.voice.sipTrunks` — SIP trunk (peer-to-peer connections to PBX/carrier).
|
|
17
18
|
* - `sdk.voice.sipProfiles` — SIP profile / endpoint (single registerable UA).
|
|
18
19
|
* - `sdk.voice.programmableVoiceProfiles` — Programmable Voice Profile (route a set of numbers through Signal House).
|
|
20
|
+
* - `sdk.voice.numberRoutes` — Per-number inbound route (overrides the profile and subgroup settings).
|
|
19
21
|
* - `sdk.voice.calls` — Twilio-compat programmatic call control (create/hangup/etc.).
|
|
20
22
|
* - `sdk.voice.callLogs` — Account-scoped call history: list/get + presigned recording.
|
|
21
23
|
* - `sdk.voice.globalVoiceSettings` — Account-wide voice defaults (accepted regions, max spend, E911).
|
|
@@ -29,6 +31,7 @@ export class Voice {
|
|
|
29
31
|
this.sipTrunks = new SipTrunks(client, enableAdmin);
|
|
30
32
|
this.sipProfiles = new SipProfiles(client, enableAdmin);
|
|
31
33
|
this.programmableVoiceProfiles = new ProgrammableVoiceProfiles(client, enableAdmin);
|
|
34
|
+
this.numberRoutes = new NumberRoutes(client, enableAdmin);
|
|
32
35
|
this.calls = new Calls(client, enableAdmin);
|
|
33
36
|
this.callLogs = new CallLogs(client, enableAdmin);
|
|
34
37
|
this.globalVoiceSettings = new GlobalVoiceSettings(client, enableAdmin);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** @typedef {import('../../SignalHouseSDK.js').RequestOptions} RequestOptions */
|
|
2
|
+
/**
|
|
3
|
+
* @template T
|
|
4
|
+
* @typedef {import('../../SignalHouseSDK.js').SdkResponse<T>} SdkResponse
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} NumberRouteData
|
|
9
|
+
* @property {"FORWARD"|"WEBRTC"|"SIP_TRUNK"|"SIP_PROFILE"|"CALL_CONTROL"|"AI"|"VOICEMAIL"} routeAction - How inbound calls to the number are handled.
|
|
10
|
+
* @property {string|null} [forwardToE164] - FORWARD destination (required for FORWARD), or the WEBRTC no-answer destination when noAnswerAction is FORWARD.
|
|
11
|
+
* @property {"VOICEMAIL"|"FORWARD"|"KEEP_RINGING"|null} [noAnswerAction] - WEBRTC only: what happens when nobody answers (default VOICEMAIL).
|
|
12
|
+
* @property {number|null} [noAnswerSeconds] - WEBRTC only: seconds to ring before the no-answer action, 5-120 (default 20).
|
|
13
|
+
* @property {string|null} [routeSipTrunkId] - SIP trunk to deliver to (required for SIP_TRUNK).
|
|
14
|
+
* @property {string|null} [routeSipProfileId] - SIP endpoint to ring (required for SIP_PROFILE).
|
|
15
|
+
* @property {string|null} [webhookUrl] - SHML program URL (required for CALL_CONTROL).
|
|
16
|
+
* @property {string|null} [routeAgentProfileId] - AI agent profile id, AGP… (required for AI).
|
|
17
|
+
* @property {boolean} [recordingEnabled] - Record calls handled by this route.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Number Routes — one inbound route set directly on a single phone number. A
|
|
22
|
+
* number's own route takes priority over its Programmable Voice Profile and
|
|
23
|
+
* subgroup voice settings. Calls the voice-backend service mounted under
|
|
24
|
+
* /voice. Accessed via `sdk.voice.numberRoutes`.
|
|
25
|
+
*/
|
|
26
|
+
export class NumberRoutes {
|
|
27
|
+
constructor(client, enableAdmin) {
|
|
28
|
+
this.client = client;
|
|
29
|
+
this.enableAdmin = enableAdmin;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get a number's own route and the routing currently in effect for it.
|
|
34
|
+
* @async
|
|
35
|
+
* @roles api, admin, developer, billing, user
|
|
36
|
+
* @param {Object} params - Request parameters.
|
|
37
|
+
* @param {string} params.e164 - The phone number in E.164 (leading + optional).
|
|
38
|
+
* @param {RequestOptions} [params.options] - Additional options for the request.
|
|
39
|
+
* @throws {Error} If e164 is missing.
|
|
40
|
+
* @returns {Promise<SdkResponse<any>>} `{ e164, route: {...} | null, effective: {...} }`.
|
|
41
|
+
*/
|
|
42
|
+
async get({ e164, options = {} }) {
|
|
43
|
+
this.client._require({ e164 });
|
|
44
|
+
const safeE164 = encodeURIComponent(e164);
|
|
45
|
+
return this.client(`/voice/api/v1/numbers/${safeE164}/route`, { method: "GET", ...options });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Set (create or replace) a number's route. The whole route is replaced:
|
|
50
|
+
* fields the new routeAction doesn't use are cleared.
|
|
51
|
+
* @async
|
|
52
|
+
* @roles api, admin, developer
|
|
53
|
+
* @param {Object} params - Request parameters.
|
|
54
|
+
* @param {string} params.e164 - The phone number in E.164 (leading + optional).
|
|
55
|
+
* @param {NumberRouteData} params.routeData - The route to set.
|
|
56
|
+
* @param {RequestOptions} [params.options] - Additional options for the request.
|
|
57
|
+
* @throws {Error} If e164 or routeData is missing.
|
|
58
|
+
* @returns {Promise<SdkResponse<any>>} `{ e164, route: {...}, effective: {...} }`.
|
|
59
|
+
*/
|
|
60
|
+
async set({ e164, routeData, options = {} }) {
|
|
61
|
+
this.client._require({ e164, routeData });
|
|
62
|
+
const safeE164 = encodeURIComponent(e164);
|
|
63
|
+
return this.client(`/voice/api/v1/numbers/${safeE164}/route`, { method: "PUT", body: routeData, ...options });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Remove a number's own route. The number falls back to its Programmable
|
|
68
|
+
* Voice Profile, subgroup voice settings, or the default.
|
|
69
|
+
* @async
|
|
70
|
+
* @roles api, admin, developer
|
|
71
|
+
* @param {Object} params - Request parameters.
|
|
72
|
+
* @param {string} params.e164 - The phone number in E.164 (leading + optional).
|
|
73
|
+
* @param {RequestOptions} [params.options] - Additional options for the request.
|
|
74
|
+
* @throws {Error} If e164 is missing.
|
|
75
|
+
* @returns {Promise<SdkResponse<any>>} `{ e164, route: null, effective: {...} }`.
|
|
76
|
+
*/
|
|
77
|
+
async delete({ e164, options = {} }) {
|
|
78
|
+
this.client._require({ e164 });
|
|
79
|
+
const safeE164 = encodeURIComponent(e164);
|
|
80
|
+
return this.client(`/voice/api/v1/numbers/${safeE164}/route`, { method: "DELETE", ...options });
|
|
81
|
+
}
|
|
82
|
+
}
|