@messagebird/sdk 0.3.0 → 0.4.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/dist/index.d.mts +3904 -0
- package/dist/index.mjs +3030 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +6 -4
- package/dist/index.d.ts +0 -3054
- package/dist/index.js +0 -2211
- package/dist/index.js.map +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3030 @@
|
|
|
1
|
+
import { Webhook } from "standardwebhooks";
|
|
2
|
+
//#region src/generated/core/bodySerializer.gen.ts
|
|
3
|
+
const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/generated/core/serverSentEvents.gen.ts
|
|
6
|
+
function createSseClient({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url, ...options }) {
|
|
7
|
+
let lastEventId;
|
|
8
|
+
const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
9
|
+
const createStream = async function* () {
|
|
10
|
+
let retryDelay = sseDefaultRetryDelay ?? 3e3;
|
|
11
|
+
let attempt = 0;
|
|
12
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
13
|
+
while (true) {
|
|
14
|
+
if (signal.aborted) break;
|
|
15
|
+
attempt++;
|
|
16
|
+
const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
|
|
17
|
+
if (lastEventId !== void 0) headers.set("Last-Event-ID", lastEventId);
|
|
18
|
+
try {
|
|
19
|
+
const requestInit = {
|
|
20
|
+
redirect: "follow",
|
|
21
|
+
...options,
|
|
22
|
+
body: options.serializedBody,
|
|
23
|
+
headers,
|
|
24
|
+
signal
|
|
25
|
+
};
|
|
26
|
+
let request = new Request(url, requestInit);
|
|
27
|
+
if (onRequest) request = await onRequest(url, requestInit);
|
|
28
|
+
const response = await (options.fetch ?? globalThis.fetch)(request);
|
|
29
|
+
if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
|
|
30
|
+
if (!response.body) throw new Error("No body in SSE response");
|
|
31
|
+
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
32
|
+
let buffer = "";
|
|
33
|
+
const abortHandler = () => {
|
|
34
|
+
try {
|
|
35
|
+
reader.cancel();
|
|
36
|
+
} catch {}
|
|
37
|
+
};
|
|
38
|
+
signal.addEventListener("abort", abortHandler);
|
|
39
|
+
try {
|
|
40
|
+
while (true) {
|
|
41
|
+
const { done, value } = await reader.read();
|
|
42
|
+
if (done) break;
|
|
43
|
+
buffer += value;
|
|
44
|
+
buffer = buffer.replace(/\r\n?/g, "\n");
|
|
45
|
+
const chunks = buffer.split("\n\n");
|
|
46
|
+
buffer = chunks.pop() ?? "";
|
|
47
|
+
for (const chunk of chunks) {
|
|
48
|
+
const lines = chunk.split("\n");
|
|
49
|
+
const dataLines = [];
|
|
50
|
+
let eventName;
|
|
51
|
+
for (const line of lines) if (line.startsWith("data:")) dataLines.push(line.replace(/^data:\s*/, ""));
|
|
52
|
+
else if (line.startsWith("event:")) eventName = line.replace(/^event:\s*/, "");
|
|
53
|
+
else if (line.startsWith("id:")) lastEventId = line.replace(/^id:\s*/, "");
|
|
54
|
+
else if (line.startsWith("retry:")) {
|
|
55
|
+
const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
|
|
56
|
+
if (!Number.isNaN(parsed)) retryDelay = parsed;
|
|
57
|
+
}
|
|
58
|
+
let data;
|
|
59
|
+
let parsedJson = false;
|
|
60
|
+
if (dataLines.length) {
|
|
61
|
+
const rawData = dataLines.join("\n");
|
|
62
|
+
try {
|
|
63
|
+
data = JSON.parse(rawData);
|
|
64
|
+
parsedJson = true;
|
|
65
|
+
} catch {
|
|
66
|
+
data = rawData;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (parsedJson) {
|
|
70
|
+
if (responseValidator) await responseValidator(data);
|
|
71
|
+
if (responseTransformer) data = await responseTransformer(data);
|
|
72
|
+
}
|
|
73
|
+
onSseEvent?.({
|
|
74
|
+
data,
|
|
75
|
+
event: eventName,
|
|
76
|
+
id: lastEventId,
|
|
77
|
+
retry: retryDelay
|
|
78
|
+
});
|
|
79
|
+
if (dataLines.length) yield data;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
signal.removeEventListener("abort", abortHandler);
|
|
84
|
+
reader.releaseLock();
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
onSseError?.(error);
|
|
89
|
+
if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) break;
|
|
90
|
+
const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4);
|
|
91
|
+
await sleep(backoff);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
return { stream: createStream() };
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/generated/core/pathSerializer.gen.ts
|
|
99
|
+
const separatorArrayExplode = (style) => {
|
|
100
|
+
switch (style) {
|
|
101
|
+
case "label": return ".";
|
|
102
|
+
case "matrix": return ";";
|
|
103
|
+
case "simple": return ",";
|
|
104
|
+
default: return "&";
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const separatorArrayNoExplode = (style) => {
|
|
108
|
+
switch (style) {
|
|
109
|
+
case "form": return ",";
|
|
110
|
+
case "pipeDelimited": return "|";
|
|
111
|
+
case "spaceDelimited": return "%20";
|
|
112
|
+
default: return ",";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const separatorObjectExplode = (style) => {
|
|
116
|
+
switch (style) {
|
|
117
|
+
case "label": return ".";
|
|
118
|
+
case "matrix": return ";";
|
|
119
|
+
case "simple": return ",";
|
|
120
|
+
default: return "&";
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const serializeArrayParam = ({ allowReserved, explode, name, style, value }) => {
|
|
124
|
+
if (!explode) {
|
|
125
|
+
const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
|
|
126
|
+
switch (style) {
|
|
127
|
+
case "label": return `.${joinedValues}`;
|
|
128
|
+
case "matrix": return `;${name}=${joinedValues}`;
|
|
129
|
+
case "simple": return joinedValues;
|
|
130
|
+
default: return `${name}=${joinedValues}`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const separator = separatorArrayExplode(style);
|
|
134
|
+
const joinedValues = value.map((v) => {
|
|
135
|
+
if (style === "label" || style === "simple") return allowReserved ? v : encodeURIComponent(v);
|
|
136
|
+
return serializePrimitiveParam({
|
|
137
|
+
allowReserved,
|
|
138
|
+
name,
|
|
139
|
+
value: v
|
|
140
|
+
});
|
|
141
|
+
}).join(separator);
|
|
142
|
+
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
|
143
|
+
};
|
|
144
|
+
const serializePrimitiveParam = ({ allowReserved, name, value }) => {
|
|
145
|
+
if (value === void 0 || value === null) return "";
|
|
146
|
+
if (typeof value === "object") throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
|
|
147
|
+
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
|
|
148
|
+
};
|
|
149
|
+
const serializeObjectParam = ({ allowReserved, explode, name, style, value, valueOnly }) => {
|
|
150
|
+
if (value instanceof Date) return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
|
|
151
|
+
if (style !== "deepObject" && !explode) {
|
|
152
|
+
let values = [];
|
|
153
|
+
Object.entries(value).forEach(([key, v]) => {
|
|
154
|
+
values = [
|
|
155
|
+
...values,
|
|
156
|
+
key,
|
|
157
|
+
allowReserved ? v : encodeURIComponent(v)
|
|
158
|
+
];
|
|
159
|
+
});
|
|
160
|
+
const joinedValues = values.join(",");
|
|
161
|
+
switch (style) {
|
|
162
|
+
case "form": return `${name}=${joinedValues}`;
|
|
163
|
+
case "label": return `.${joinedValues}`;
|
|
164
|
+
case "matrix": return `;${name}=${joinedValues}`;
|
|
165
|
+
default: return joinedValues;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const separator = separatorObjectExplode(style);
|
|
169
|
+
const joinedValues = Object.entries(value).map(([key, v]) => serializePrimitiveParam({
|
|
170
|
+
allowReserved,
|
|
171
|
+
name: style === "deepObject" ? `${name}[${key}]` : key,
|
|
172
|
+
value: v
|
|
173
|
+
})).join(separator);
|
|
174
|
+
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/generated/core/utils.gen.ts
|
|
178
|
+
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
179
|
+
const defaultPathSerializer = ({ path, url: _url }) => {
|
|
180
|
+
let url = _url;
|
|
181
|
+
const matches = _url.match(PATH_PARAM_RE);
|
|
182
|
+
if (matches) for (const match of matches) {
|
|
183
|
+
let explode = false;
|
|
184
|
+
let name = match.substring(1, match.length - 1);
|
|
185
|
+
let style = "simple";
|
|
186
|
+
if (name.endsWith("*")) {
|
|
187
|
+
explode = true;
|
|
188
|
+
name = name.substring(0, name.length - 1);
|
|
189
|
+
}
|
|
190
|
+
if (name.startsWith(".")) {
|
|
191
|
+
name = name.substring(1);
|
|
192
|
+
style = "label";
|
|
193
|
+
} else if (name.startsWith(";")) {
|
|
194
|
+
name = name.substring(1);
|
|
195
|
+
style = "matrix";
|
|
196
|
+
}
|
|
197
|
+
const value = path[name];
|
|
198
|
+
if (value === void 0 || value === null) continue;
|
|
199
|
+
if (Array.isArray(value)) {
|
|
200
|
+
url = url.replace(match, serializeArrayParam({
|
|
201
|
+
explode,
|
|
202
|
+
name,
|
|
203
|
+
style,
|
|
204
|
+
value
|
|
205
|
+
}));
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (typeof value === "object") {
|
|
209
|
+
url = url.replace(match, serializeObjectParam({
|
|
210
|
+
explode,
|
|
211
|
+
name,
|
|
212
|
+
style,
|
|
213
|
+
value,
|
|
214
|
+
valueOnly: true
|
|
215
|
+
}));
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (style === "matrix") {
|
|
219
|
+
url = url.replace(match, `;${serializePrimitiveParam({
|
|
220
|
+
name,
|
|
221
|
+
value
|
|
222
|
+
})}`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const replaceValue = encodeURIComponent(style === "label" ? `.${value}` : value);
|
|
226
|
+
url = url.replace(match, replaceValue);
|
|
227
|
+
}
|
|
228
|
+
return url;
|
|
229
|
+
};
|
|
230
|
+
const getUrl = ({ baseUrl, path, query, querySerializer, url: _url }) => {
|
|
231
|
+
const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
|
|
232
|
+
let url = (baseUrl ?? "") + pathUrl;
|
|
233
|
+
if (path) url = defaultPathSerializer({
|
|
234
|
+
path,
|
|
235
|
+
url
|
|
236
|
+
});
|
|
237
|
+
let search = query ? querySerializer(query) : "";
|
|
238
|
+
if (search.startsWith("?")) search = search.substring(1);
|
|
239
|
+
if (search) url += `?${search}`;
|
|
240
|
+
return url;
|
|
241
|
+
};
|
|
242
|
+
function getValidRequestBody(options) {
|
|
243
|
+
const hasBody = options.body !== void 0;
|
|
244
|
+
if (hasBody && options.bodySerializer) {
|
|
245
|
+
if ("serializedBody" in options) return options.serializedBody !== void 0 && options.serializedBody !== "" ? options.serializedBody : null;
|
|
246
|
+
return options.body !== "" ? options.body : null;
|
|
247
|
+
}
|
|
248
|
+
if (hasBody) return options.body;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/generated/core/auth.gen.ts
|
|
252
|
+
const getAuthToken = async (auth, callback) => {
|
|
253
|
+
const token = typeof callback === "function" ? await callback(auth) : callback;
|
|
254
|
+
if (!token) return;
|
|
255
|
+
if (auth.scheme === "bearer") return `Bearer ${token}`;
|
|
256
|
+
if (auth.scheme === "basic") return `Basic ${btoa(token)}`;
|
|
257
|
+
return token;
|
|
258
|
+
};
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/generated/client/utils.gen.ts
|
|
261
|
+
const createQuerySerializer = ({ parameters = {}, ...args } = {}) => {
|
|
262
|
+
const querySerializer = (queryParams) => {
|
|
263
|
+
const search = [];
|
|
264
|
+
if (queryParams && typeof queryParams === "object") for (const name in queryParams) {
|
|
265
|
+
const value = queryParams[name];
|
|
266
|
+
if (value === void 0 || value === null) continue;
|
|
267
|
+
const options = parameters[name] || args;
|
|
268
|
+
if (Array.isArray(value)) {
|
|
269
|
+
const serializedArray = serializeArrayParam({
|
|
270
|
+
allowReserved: options.allowReserved,
|
|
271
|
+
explode: true,
|
|
272
|
+
name,
|
|
273
|
+
style: "form",
|
|
274
|
+
value,
|
|
275
|
+
...options.array
|
|
276
|
+
});
|
|
277
|
+
if (serializedArray) search.push(serializedArray);
|
|
278
|
+
} else if (typeof value === "object") {
|
|
279
|
+
const serializedObject = serializeObjectParam({
|
|
280
|
+
allowReserved: options.allowReserved,
|
|
281
|
+
explode: true,
|
|
282
|
+
name,
|
|
283
|
+
style: "deepObject",
|
|
284
|
+
value,
|
|
285
|
+
...options.object
|
|
286
|
+
});
|
|
287
|
+
if (serializedObject) search.push(serializedObject);
|
|
288
|
+
} else {
|
|
289
|
+
const serializedPrimitive = serializePrimitiveParam({
|
|
290
|
+
allowReserved: options.allowReserved,
|
|
291
|
+
name,
|
|
292
|
+
value
|
|
293
|
+
});
|
|
294
|
+
if (serializedPrimitive) search.push(serializedPrimitive);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return search.join("&");
|
|
298
|
+
};
|
|
299
|
+
return querySerializer;
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Infers parseAs value from provided Content-Type header.
|
|
303
|
+
*/
|
|
304
|
+
const getParseAs = (contentType) => {
|
|
305
|
+
if (!contentType) return "stream";
|
|
306
|
+
const cleanContent = contentType.split(";")[0]?.trim();
|
|
307
|
+
if (!cleanContent) return;
|
|
308
|
+
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) return "json";
|
|
309
|
+
if (cleanContent === "multipart/form-data") return "formData";
|
|
310
|
+
if ([
|
|
311
|
+
"application/",
|
|
312
|
+
"audio/",
|
|
313
|
+
"image/",
|
|
314
|
+
"video/"
|
|
315
|
+
].some((type) => cleanContent.startsWith(type))) return "blob";
|
|
316
|
+
if (cleanContent.startsWith("text/")) return "text";
|
|
317
|
+
};
|
|
318
|
+
const checkForExistence = (options, name) => {
|
|
319
|
+
if (!name) return false;
|
|
320
|
+
if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) return true;
|
|
321
|
+
return false;
|
|
322
|
+
};
|
|
323
|
+
async function setAuthParams(options) {
|
|
324
|
+
for (const auth of options.security ?? []) {
|
|
325
|
+
if (checkForExistence(options, auth.name)) continue;
|
|
326
|
+
const token = await getAuthToken(auth, options.auth);
|
|
327
|
+
if (!token) continue;
|
|
328
|
+
const name = auth.name ?? "Authorization";
|
|
329
|
+
switch (auth.in) {
|
|
330
|
+
case "query":
|
|
331
|
+
if (!options.query) options.query = {};
|
|
332
|
+
options.query[name] = token;
|
|
333
|
+
break;
|
|
334
|
+
case "cookie":
|
|
335
|
+
options.headers.append("Cookie", `${name}=${token}`);
|
|
336
|
+
break;
|
|
337
|
+
default:
|
|
338
|
+
options.headers.set(name, token);
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const buildUrl = (options) => getUrl({
|
|
344
|
+
baseUrl: options.baseUrl,
|
|
345
|
+
path: options.path,
|
|
346
|
+
query: options.query,
|
|
347
|
+
querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
|
|
348
|
+
url: options.url
|
|
349
|
+
});
|
|
350
|
+
const mergeConfigs = (a, b) => {
|
|
351
|
+
const config = {
|
|
352
|
+
...a,
|
|
353
|
+
...b
|
|
354
|
+
};
|
|
355
|
+
if (config.baseUrl?.endsWith("/")) config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
|
|
356
|
+
config.headers = mergeHeaders$1(a.headers, b.headers);
|
|
357
|
+
return config;
|
|
358
|
+
};
|
|
359
|
+
const headersEntries = (headers) => {
|
|
360
|
+
const entries = [];
|
|
361
|
+
headers.forEach((value, key) => {
|
|
362
|
+
entries.push([key, value]);
|
|
363
|
+
});
|
|
364
|
+
return entries;
|
|
365
|
+
};
|
|
366
|
+
const mergeHeaders$1 = (...headers) => {
|
|
367
|
+
const mergedHeaders = new Headers();
|
|
368
|
+
for (const header of headers) {
|
|
369
|
+
if (!header) continue;
|
|
370
|
+
const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
|
|
371
|
+
for (const [key, value] of iterator) if (value === null) mergedHeaders.delete(key);
|
|
372
|
+
else if (Array.isArray(value)) for (const v of value) mergedHeaders.append(key, v);
|
|
373
|
+
else if (value !== void 0) mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : value);
|
|
374
|
+
}
|
|
375
|
+
return mergedHeaders;
|
|
376
|
+
};
|
|
377
|
+
var Interceptors = class {
|
|
378
|
+
fns = [];
|
|
379
|
+
clear() {
|
|
380
|
+
this.fns = [];
|
|
381
|
+
}
|
|
382
|
+
eject(id) {
|
|
383
|
+
const index = this.getInterceptorIndex(id);
|
|
384
|
+
if (this.fns[index]) this.fns[index] = null;
|
|
385
|
+
}
|
|
386
|
+
exists(id) {
|
|
387
|
+
const index = this.getInterceptorIndex(id);
|
|
388
|
+
return Boolean(this.fns[index]);
|
|
389
|
+
}
|
|
390
|
+
getInterceptorIndex(id) {
|
|
391
|
+
if (typeof id === "number") return this.fns[id] ? id : -1;
|
|
392
|
+
return this.fns.indexOf(id);
|
|
393
|
+
}
|
|
394
|
+
update(id, fn) {
|
|
395
|
+
const index = this.getInterceptorIndex(id);
|
|
396
|
+
if (this.fns[index]) {
|
|
397
|
+
this.fns[index] = fn;
|
|
398
|
+
return id;
|
|
399
|
+
}
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
use(fn) {
|
|
403
|
+
this.fns.push(fn);
|
|
404
|
+
return this.fns.length - 1;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const createInterceptors = () => ({
|
|
408
|
+
error: new Interceptors(),
|
|
409
|
+
request: new Interceptors(),
|
|
410
|
+
response: new Interceptors()
|
|
411
|
+
});
|
|
412
|
+
const defaultQuerySerializer = createQuerySerializer({
|
|
413
|
+
allowReserved: false,
|
|
414
|
+
array: {
|
|
415
|
+
explode: true,
|
|
416
|
+
style: "form"
|
|
417
|
+
},
|
|
418
|
+
object: {
|
|
419
|
+
explode: true,
|
|
420
|
+
style: "deepObject"
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
const defaultHeaders = { "Content-Type": "application/json" };
|
|
424
|
+
const createConfig = (override = {}) => ({
|
|
425
|
+
...jsonBodySerializer,
|
|
426
|
+
headers: defaultHeaders,
|
|
427
|
+
parseAs: "auto",
|
|
428
|
+
querySerializer: defaultQuerySerializer,
|
|
429
|
+
...override
|
|
430
|
+
});
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region src/generated/client/client.gen.ts
|
|
433
|
+
const createClient = (config = {}) => {
|
|
434
|
+
let _config = mergeConfigs(createConfig(), config);
|
|
435
|
+
const getConfig = () => ({ ..._config });
|
|
436
|
+
const setConfig = (config) => {
|
|
437
|
+
_config = mergeConfigs(_config, config);
|
|
438
|
+
return getConfig();
|
|
439
|
+
};
|
|
440
|
+
const interceptors = createInterceptors();
|
|
441
|
+
const beforeRequest = async (options) => {
|
|
442
|
+
const opts = {
|
|
443
|
+
..._config,
|
|
444
|
+
...options,
|
|
445
|
+
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
|
446
|
+
headers: mergeHeaders$1(_config.headers, options.headers),
|
|
447
|
+
serializedBody: void 0
|
|
448
|
+
};
|
|
449
|
+
if (opts.security) await setAuthParams(opts);
|
|
450
|
+
if (opts.requestValidator) await opts.requestValidator(opts);
|
|
451
|
+
if (opts.body !== void 0 && opts.bodySerializer) opts.serializedBody = opts.bodySerializer(opts.body);
|
|
452
|
+
if (opts.body === void 0 || opts.serializedBody === "") opts.headers.delete("Content-Type");
|
|
453
|
+
const resolvedOpts = opts;
|
|
454
|
+
return {
|
|
455
|
+
opts: resolvedOpts,
|
|
456
|
+
url: buildUrl(resolvedOpts)
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
const request = async (options) => {
|
|
460
|
+
const throwOnError = options.throwOnError ?? _config.throwOnError;
|
|
461
|
+
const responseStyle = options.responseStyle ?? _config.responseStyle;
|
|
462
|
+
let request;
|
|
463
|
+
let response;
|
|
464
|
+
try {
|
|
465
|
+
const { opts, url } = await beforeRequest(options);
|
|
466
|
+
const requestInit = {
|
|
467
|
+
redirect: "follow",
|
|
468
|
+
...opts,
|
|
469
|
+
body: getValidRequestBody(opts)
|
|
470
|
+
};
|
|
471
|
+
request = new Request(url, requestInit);
|
|
472
|
+
for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
|
|
473
|
+
const _fetch = opts.fetch;
|
|
474
|
+
response = await _fetch(request);
|
|
475
|
+
for (const fn of interceptors.response.fns) if (fn) response = await fn(response, request, opts);
|
|
476
|
+
const result = {
|
|
477
|
+
request,
|
|
478
|
+
response
|
|
479
|
+
};
|
|
480
|
+
if (response.ok) {
|
|
481
|
+
const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
|
482
|
+
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
|
483
|
+
let emptyData;
|
|
484
|
+
switch (parseAs) {
|
|
485
|
+
case "arrayBuffer":
|
|
486
|
+
case "blob":
|
|
487
|
+
case "text":
|
|
488
|
+
emptyData = await response[parseAs]();
|
|
489
|
+
break;
|
|
490
|
+
case "formData":
|
|
491
|
+
emptyData = new FormData();
|
|
492
|
+
break;
|
|
493
|
+
case "stream":
|
|
494
|
+
emptyData = response.body;
|
|
495
|
+
break;
|
|
496
|
+
default:
|
|
497
|
+
emptyData = {};
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
return opts.responseStyle === "data" ? emptyData : {
|
|
501
|
+
data: emptyData,
|
|
502
|
+
...result
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
let data;
|
|
506
|
+
switch (parseAs) {
|
|
507
|
+
case "arrayBuffer":
|
|
508
|
+
case "blob":
|
|
509
|
+
case "formData":
|
|
510
|
+
case "text":
|
|
511
|
+
data = await response[parseAs]();
|
|
512
|
+
break;
|
|
513
|
+
case "json": {
|
|
514
|
+
const text = await response.text();
|
|
515
|
+
data = text ? JSON.parse(text) : {};
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case "stream": return opts.responseStyle === "data" ? response.body : {
|
|
519
|
+
data: response.body,
|
|
520
|
+
...result
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
if (parseAs === "json") {
|
|
524
|
+
if (opts.responseValidator) await opts.responseValidator(data);
|
|
525
|
+
if (opts.responseTransformer) data = await opts.responseTransformer(data);
|
|
526
|
+
}
|
|
527
|
+
return opts.responseStyle === "data" ? data : {
|
|
528
|
+
data,
|
|
529
|
+
...result
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
const textError = await response.text();
|
|
533
|
+
let jsonError;
|
|
534
|
+
try {
|
|
535
|
+
jsonError = JSON.parse(textError);
|
|
536
|
+
} catch {}
|
|
537
|
+
throw jsonError ?? textError;
|
|
538
|
+
} catch (error) {
|
|
539
|
+
let finalError = error;
|
|
540
|
+
for (const fn of interceptors.error.fns) if (fn) finalError = await fn(finalError, response, request, options);
|
|
541
|
+
finalError = finalError || {};
|
|
542
|
+
if (throwOnError) throw finalError;
|
|
543
|
+
return responseStyle === "data" ? void 0 : {
|
|
544
|
+
error: finalError,
|
|
545
|
+
request,
|
|
546
|
+
response
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
const makeMethodFn = (method) => (options) => request({
|
|
551
|
+
...options,
|
|
552
|
+
method
|
|
553
|
+
});
|
|
554
|
+
const makeSseFn = (method) => async (options) => {
|
|
555
|
+
const { opts, url } = await beforeRequest(options);
|
|
556
|
+
return createSseClient({
|
|
557
|
+
...opts,
|
|
558
|
+
body: opts.body,
|
|
559
|
+
method,
|
|
560
|
+
onRequest: async (url, init) => {
|
|
561
|
+
let request = new Request(url, init);
|
|
562
|
+
for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
|
|
563
|
+
return request;
|
|
564
|
+
},
|
|
565
|
+
serializedBody: getValidRequestBody(opts),
|
|
566
|
+
url
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
const _buildUrl = (options) => buildUrl({
|
|
570
|
+
..._config,
|
|
571
|
+
...options
|
|
572
|
+
});
|
|
573
|
+
return {
|
|
574
|
+
buildUrl: _buildUrl,
|
|
575
|
+
connect: makeMethodFn("CONNECT"),
|
|
576
|
+
delete: makeMethodFn("DELETE"),
|
|
577
|
+
get: makeMethodFn("GET"),
|
|
578
|
+
getConfig,
|
|
579
|
+
head: makeMethodFn("HEAD"),
|
|
580
|
+
interceptors,
|
|
581
|
+
options: makeMethodFn("OPTIONS"),
|
|
582
|
+
patch: makeMethodFn("PATCH"),
|
|
583
|
+
post: makeMethodFn("POST"),
|
|
584
|
+
put: makeMethodFn("PUT"),
|
|
585
|
+
request,
|
|
586
|
+
setConfig,
|
|
587
|
+
sse: {
|
|
588
|
+
connect: makeSseFn("CONNECT"),
|
|
589
|
+
delete: makeSseFn("DELETE"),
|
|
590
|
+
get: makeSseFn("GET"),
|
|
591
|
+
head: makeSseFn("HEAD"),
|
|
592
|
+
options: makeSseFn("OPTIONS"),
|
|
593
|
+
patch: makeSseFn("PATCH"),
|
|
594
|
+
post: makeSseFn("POST"),
|
|
595
|
+
put: makeSseFn("PUT"),
|
|
596
|
+
trace: makeSseFn("TRACE")
|
|
597
|
+
},
|
|
598
|
+
trace: makeMethodFn("TRACE")
|
|
599
|
+
};
|
|
600
|
+
};
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region src/region.ts
|
|
603
|
+
const REGION_PATTERN = /^[a-z]{2}[0-9]+$/;
|
|
604
|
+
/** Extracts the region code from a `bk_{region}_{token}` key, or undefined. */
|
|
605
|
+
function regionFromApiKey(apiKey) {
|
|
606
|
+
const [prefix, region, token] = apiKey.split("_");
|
|
607
|
+
if (prefix !== "bk" || !region || !token) return void 0;
|
|
608
|
+
return REGION_PATTERN.test(region) ? region : void 0;
|
|
609
|
+
}
|
|
610
|
+
function baseUrlForRegion(region) {
|
|
611
|
+
return `https://${region}.platform.bird.com`;
|
|
612
|
+
}
|
|
613
|
+
//#endregion
|
|
614
|
+
//#region src/caller-rules.gen.ts
|
|
615
|
+
const callerRules = [
|
|
616
|
+
{
|
|
617
|
+
env: "CLAUDECODE",
|
|
618
|
+
name: "claude-code"
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
env: "CODEX_CI",
|
|
622
|
+
name: "codex"
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
env: "GEMINI_CLI",
|
|
626
|
+
name: "gemini"
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
env: "QWEN_CODE",
|
|
630
|
+
name: "qwen"
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
env: "PI_CODING_AGENT",
|
|
634
|
+
name: "pi"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
env: "OPENCODE",
|
|
638
|
+
name: "opencode"
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
env: "CLINE_ACTIVE",
|
|
642
|
+
name: "cline"
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
env: "ROO_ACTIVE",
|
|
646
|
+
name: "roo"
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
env: "CURSOR_TRACE_ID",
|
|
650
|
+
name: "cursor"
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
env: "CURSOR_AGENT",
|
|
654
|
+
name: "cursor"
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
env: "ANTIGRAVITY_AGENT",
|
|
658
|
+
name: "antigravity"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
env: "AUGMENT_AGENT",
|
|
662
|
+
name: "augment"
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
env: "AGENT",
|
|
666
|
+
passthrough: true
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
env: "AI_AGENT",
|
|
670
|
+
passthrough: true
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
env: "REPL_ID",
|
|
674
|
+
name: "replit"
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
env: "CI",
|
|
678
|
+
name: "ci"
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
env: "GITHUB_ACTIONS",
|
|
682
|
+
name: "ci"
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
env: "TERM_PROGRAM",
|
|
686
|
+
equals: "zed",
|
|
687
|
+
name: "zed"
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
env: "ZED_TERM",
|
|
691
|
+
name: "zed"
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
env: "TERM_PROGRAM",
|
|
695
|
+
equals: "kiro",
|
|
696
|
+
name: "kiro"
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
env: "TERM_PROGRAM",
|
|
700
|
+
equals: "WarpTerminal",
|
|
701
|
+
name: "warp"
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
env: "TERMINAL_EMULATOR",
|
|
705
|
+
equals: "JetBrains-JediTerm",
|
|
706
|
+
name: "jetbrains"
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
env: "__CFBundleIdentifier",
|
|
710
|
+
equals: "com.exafunction.windsurf",
|
|
711
|
+
name: "windsurf"
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
env: "TERM_PROGRAM",
|
|
715
|
+
equals: "vscode",
|
|
716
|
+
name: "vscode"
|
|
717
|
+
}
|
|
718
|
+
];
|
|
719
|
+
const callerBooleanishSkip = /* @__PURE__ */ new Set([
|
|
720
|
+
"1",
|
|
721
|
+
"0",
|
|
722
|
+
"true",
|
|
723
|
+
"false",
|
|
724
|
+
"yes",
|
|
725
|
+
"no",
|
|
726
|
+
"on",
|
|
727
|
+
"off"
|
|
728
|
+
]);
|
|
729
|
+
const callerDefault = "shell";
|
|
730
|
+
//#endregion
|
|
731
|
+
//#region src/detect-caller.ts
|
|
732
|
+
/**
|
|
733
|
+
* Infers the environment driving the SDK for the `Bird-Caller` usage-telemetry
|
|
734
|
+
* label by walking the generated rules in order (single source of truth:
|
|
735
|
+
* `clients/caller-detection.yaml`, shared with the CLI and the other SDKs).
|
|
736
|
+
* Best-effort and non-authoritative — it only labels traffic, never gates
|
|
737
|
+
* behavior.
|
|
738
|
+
*
|
|
739
|
+
* Edge-safe: `process` is read only through a `typeof`-style `globalThis` guard,
|
|
740
|
+
* so on a browser (no `process.env`) it returns `""` and the client sends no
|
|
741
|
+
* `Bird-Caller` header. `env` is injected in tests.
|
|
742
|
+
*/
|
|
743
|
+
function detectCaller(env) {
|
|
744
|
+
let source = env;
|
|
745
|
+
if (source === void 0) {
|
|
746
|
+
const proc = globalThis.process;
|
|
747
|
+
if (proc?.versions?.node === void 0) return "";
|
|
748
|
+
source = proc.env ?? {};
|
|
749
|
+
}
|
|
750
|
+
for (const rule of callerRules) {
|
|
751
|
+
const value = source[rule.env];
|
|
752
|
+
if (value === void 0 || value === "" || rule.equals !== void 0 && value !== rule.equals) continue;
|
|
753
|
+
if (!rule.passthrough) return rule.name;
|
|
754
|
+
const sanitized = sanitizeCaller(value);
|
|
755
|
+
if (sanitized) return sanitized;
|
|
756
|
+
}
|
|
757
|
+
return callerDefault;
|
|
758
|
+
}
|
|
759
|
+
function sanitizeCaller(value) {
|
|
760
|
+
const s = value.trim().toLowerCase();
|
|
761
|
+
if (s === "" || s.length > 32 || callerBooleanishSkip.has(s)) return "";
|
|
762
|
+
return /^[a-z0-9._-]+$/.test(s) ? s : "";
|
|
763
|
+
}
|
|
764
|
+
//#endregion
|
|
765
|
+
//#region src/errors.ts
|
|
766
|
+
/** Root of the hierarchy. Catch this to catch anything the SDK throws. */
|
|
767
|
+
var BirdError = class extends Error {
|
|
768
|
+
constructor(message) {
|
|
769
|
+
super(message);
|
|
770
|
+
this.name = "BirdError";
|
|
771
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
/** Network-level failure with no HTTP response (DNS, refused, socket hangup). */
|
|
775
|
+
var BirdConnectionError = class extends BirdError {
|
|
776
|
+
constructor(message) {
|
|
777
|
+
super(message);
|
|
778
|
+
this.name = "BirdConnectionError";
|
|
779
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
/** A single attempt exceeded its timeout. Retryable. */
|
|
783
|
+
var BirdTimeoutError = class extends BirdError {
|
|
784
|
+
timeoutMs;
|
|
785
|
+
constructor(message, timeoutMs) {
|
|
786
|
+
super(message);
|
|
787
|
+
this.name = "BirdTimeoutError";
|
|
788
|
+
this.timeoutMs = timeoutMs;
|
|
789
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
/** A webhook payload failed signature verification (bad signature, stale timestamp, malformed headers). */
|
|
793
|
+
var BirdWebhookVerificationError = class extends BirdError {
|
|
794
|
+
constructor(message) {
|
|
795
|
+
super(message);
|
|
796
|
+
this.name = "BirdWebhookVerificationError";
|
|
797
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
/** The server returned an error body. Base for every `type`-specific class. */
|
|
801
|
+
var BirdAPIError = class extends BirdError {
|
|
802
|
+
statusCode;
|
|
803
|
+
code;
|
|
804
|
+
type;
|
|
805
|
+
errorName;
|
|
806
|
+
docUrl;
|
|
807
|
+
requestId;
|
|
808
|
+
param;
|
|
809
|
+
vendorCode;
|
|
810
|
+
remediation;
|
|
811
|
+
next;
|
|
812
|
+
constructor(fields) {
|
|
813
|
+
super(fields.message);
|
|
814
|
+
this.name = "BirdAPIError";
|
|
815
|
+
this.statusCode = fields.statusCode;
|
|
816
|
+
this.code = fields.code;
|
|
817
|
+
this.type = fields.type;
|
|
818
|
+
this.errorName = fields.errorName;
|
|
819
|
+
this.docUrl = fields.docUrl;
|
|
820
|
+
this.requestId = fields.requestId;
|
|
821
|
+
this.param = fields.param;
|
|
822
|
+
this.vendorCode = fields.vendorCode;
|
|
823
|
+
this.remediation = fields.remediation;
|
|
824
|
+
this.next = fields.next;
|
|
825
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
/** 401 — authentication failed or missing. */
|
|
829
|
+
var BirdAuthError = class extends BirdAPIError {
|
|
830
|
+
constructor(fields) {
|
|
831
|
+
super(fields);
|
|
832
|
+
this.name = "BirdAuthError";
|
|
833
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
/** 403 — authenticated but not allowed. */
|
|
837
|
+
var BirdPermissionError = class extends BirdAPIError {
|
|
838
|
+
constructor(fields) {
|
|
839
|
+
super(fields);
|
|
840
|
+
this.name = "BirdPermissionError";
|
|
841
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
/** 404 — resource does not exist. */
|
|
845
|
+
var BirdNotFoundError = class extends BirdAPIError {
|
|
846
|
+
constructor(fields) {
|
|
847
|
+
super(fields);
|
|
848
|
+
this.name = "BirdNotFoundError";
|
|
849
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
/** 409 — semantic conflict (e.g. a unique value already taken). */
|
|
853
|
+
var BirdConflictError = class extends BirdAPIError {
|
|
854
|
+
constructor(fields) {
|
|
855
|
+
super(fields);
|
|
856
|
+
this.name = "BirdConflictError";
|
|
857
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
/** 400 — malformed request. */
|
|
861
|
+
var BirdBadRequestError = class extends BirdAPIError {
|
|
862
|
+
constructor(fields) {
|
|
863
|
+
super(fields);
|
|
864
|
+
this.name = "BirdBadRequestError";
|
|
865
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
/** 402 — billing/balance problem. */
|
|
869
|
+
var BirdBillingError = class extends BirdAPIError {
|
|
870
|
+
constructor(fields) {
|
|
871
|
+
super(fields);
|
|
872
|
+
this.name = "BirdBillingError";
|
|
873
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
/** 412/428 — a precondition was not met. */
|
|
877
|
+
var BirdPreconditionError = class extends BirdAPIError {
|
|
878
|
+
constructor(fields) {
|
|
879
|
+
super(fields);
|
|
880
|
+
this.name = "BirdPreconditionError";
|
|
881
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
/** 413 — request body too large. */
|
|
885
|
+
var BirdPayloadTooLargeError = class extends BirdAPIError {
|
|
886
|
+
constructor(fields) {
|
|
887
|
+
super(fields);
|
|
888
|
+
this.name = "BirdPayloadTooLargeError";
|
|
889
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
/** 500 — unexpected server error. */
|
|
893
|
+
var BirdInternalError = class extends BirdAPIError {
|
|
894
|
+
constructor(fields) {
|
|
895
|
+
super(fields);
|
|
896
|
+
this.name = "BirdInternalError";
|
|
897
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
/** 501 — endpoint not implemented. */
|
|
901
|
+
var BirdNotImplementedError = class extends BirdAPIError {
|
|
902
|
+
constructor(fields) {
|
|
903
|
+
super(fields);
|
|
904
|
+
this.name = "BirdNotImplementedError";
|
|
905
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
/** 421 — request reached the wrong region (ADR-0036). */
|
|
909
|
+
var BirdMisdirectedError = class extends BirdAPIError {
|
|
910
|
+
constructor(fields) {
|
|
911
|
+
super(fields);
|
|
912
|
+
this.name = "BirdMisdirectedError";
|
|
913
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
/** 503 — service temporarily unavailable. */
|
|
917
|
+
var BirdServiceUnavailableError = class extends BirdAPIError {
|
|
918
|
+
constructor(fields) {
|
|
919
|
+
super(fields);
|
|
920
|
+
this.name = "BirdServiceUnavailableError";
|
|
921
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
/** 422 — field validation failed; `details` carries the per-field errors. */
|
|
925
|
+
var BirdValidationError = class extends BirdAPIError {
|
|
926
|
+
details;
|
|
927
|
+
constructor(fields) {
|
|
928
|
+
super(fields);
|
|
929
|
+
this.name = "BirdValidationError";
|
|
930
|
+
this.details = fields.details;
|
|
931
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
/** 429 — rate limited; `retryAfter` is the server-advised wait in seconds. */
|
|
935
|
+
var BirdRateLimitError = class extends BirdAPIError {
|
|
936
|
+
retryAfter;
|
|
937
|
+
constructor(fields) {
|
|
938
|
+
super(fields);
|
|
939
|
+
this.name = "BirdRateLimitError";
|
|
940
|
+
this.retryAfter = fields.retryAfter;
|
|
941
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
/**
|
|
945
|
+
* Parse `Retry-After` (delta-seconds or HTTP-date) into whole seconds. A
|
|
946
|
+
* negative or unparseable value yields `undefined` — a negative wait is
|
|
947
|
+
* meaningless, so both the user-facing `retryAfter` and the retry loop treat it
|
|
948
|
+
* as "no server advice". The single Retry-After parser; `retryDelay` builds on it.
|
|
949
|
+
*/
|
|
950
|
+
function parseRetryAfter(headers) {
|
|
951
|
+
const header = headers?.get("Retry-After");
|
|
952
|
+
if (!header) return void 0;
|
|
953
|
+
const seconds = Number(header);
|
|
954
|
+
const value = Number.isFinite(seconds) ? seconds : (Date.parse(header) - Date.now()) / 1e3;
|
|
955
|
+
return Number.isFinite(value) && value >= 0 ? Math.round(value) : void 0;
|
|
956
|
+
}
|
|
957
|
+
function inferType(status) {
|
|
958
|
+
switch (status) {
|
|
959
|
+
case 400: return "bad_request_error";
|
|
960
|
+
case 401: return "auth_error";
|
|
961
|
+
case 402: return "billing_error";
|
|
962
|
+
case 403: return "permission_error";
|
|
963
|
+
case 404: return "not_found_error";
|
|
964
|
+
case 409: return "conflict_error";
|
|
965
|
+
case 412:
|
|
966
|
+
case 428: return "precondition_error";
|
|
967
|
+
case 413: return "payload_too_large_error";
|
|
968
|
+
case 421: return "misdirected_error";
|
|
969
|
+
case 422: return "validation_error";
|
|
970
|
+
case 429: return "rate_limit_error";
|
|
971
|
+
case 501: return "not_implemented_error";
|
|
972
|
+
case 503: return "service_unavailable_error";
|
|
973
|
+
default: return status >= 500 ? "internal_error" : "bad_request_error";
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Map a non-2xx response to the right `BirdAPIError` subclass. The single place
|
|
978
|
+
* the SDK turns a wire error into a thrown error.
|
|
979
|
+
*/
|
|
980
|
+
function mapResponseToError(status, body, headers) {
|
|
981
|
+
const raw = body ?? {};
|
|
982
|
+
const b = raw.error ?? raw ?? {};
|
|
983
|
+
const fields = {
|
|
984
|
+
statusCode: status,
|
|
985
|
+
code: b.code ?? "unknown",
|
|
986
|
+
type: b.type ?? inferType(status),
|
|
987
|
+
errorName: b.name ?? "",
|
|
988
|
+
message: b.message ?? `Request failed with status ${status}`,
|
|
989
|
+
docUrl: b.doc_url ?? "",
|
|
990
|
+
requestId: b.request_id ?? headers?.get("X-Request-Id") ?? "",
|
|
991
|
+
param: b.param,
|
|
992
|
+
vendorCode: b.vendor_code,
|
|
993
|
+
remediation: b.remediation,
|
|
994
|
+
next: b.next ?? []
|
|
995
|
+
};
|
|
996
|
+
switch (fields.type) {
|
|
997
|
+
case "auth_error": return new BirdAuthError(fields);
|
|
998
|
+
case "permission_error": return new BirdPermissionError(fields);
|
|
999
|
+
case "not_found_error": return new BirdNotFoundError(fields);
|
|
1000
|
+
case "conflict_error": return new BirdConflictError(fields);
|
|
1001
|
+
case "bad_request_error": return new BirdBadRequestError(fields);
|
|
1002
|
+
case "billing_error": return new BirdBillingError(fields);
|
|
1003
|
+
case "precondition_error": return new BirdPreconditionError(fields);
|
|
1004
|
+
case "payload_too_large_error": return new BirdPayloadTooLargeError(fields);
|
|
1005
|
+
case "internal_error": return new BirdInternalError(fields);
|
|
1006
|
+
case "not_implemented_error": return new BirdNotImplementedError(fields);
|
|
1007
|
+
case "misdirected_error": return new BirdMisdirectedError(fields);
|
|
1008
|
+
case "service_unavailable_error": return new BirdServiceUnavailableError(fields);
|
|
1009
|
+
case "rate_limit_error": return new BirdRateLimitError({
|
|
1010
|
+
...fields,
|
|
1011
|
+
retryAfter: parseRetryAfter(headers)
|
|
1012
|
+
});
|
|
1013
|
+
case "validation_error": return new BirdValidationError({
|
|
1014
|
+
...fields,
|
|
1015
|
+
details: b.details ?? []
|
|
1016
|
+
});
|
|
1017
|
+
default: return new BirdAPIError(fields);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
//#endregion
|
|
1021
|
+
//#region src/core/http.ts
|
|
1022
|
+
const BACKOFF_BASE_MS = 500;
|
|
1023
|
+
const BACKOFF_CAP_MS = 8e3;
|
|
1024
|
+
const RETRY_AFTER_CAP_MS = 6e4;
|
|
1025
|
+
var BirdHTTPClient = class {
|
|
1026
|
+
defaults;
|
|
1027
|
+
constructor(defaults) {
|
|
1028
|
+
this.defaults = defaults;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Run a generated hey-api SDK call through the request lifecycle.
|
|
1032
|
+
*
|
|
1033
|
+
* @param call Invokes the SDK function; receives the per-attempt signal and
|
|
1034
|
+
* the idempotency key to set as a header.
|
|
1035
|
+
* @returns the parsed body plus transport metadata.
|
|
1036
|
+
* @throws a `BirdError` subclass on terminal failure; the native
|
|
1037
|
+
* `AbortError` if the caller's signal aborts.
|
|
1038
|
+
*/
|
|
1039
|
+
async request(call, options) {
|
|
1040
|
+
const maxRetries = options.maxRetries ?? this.defaults.maxRetries;
|
|
1041
|
+
const timeout = options.timeout ?? this.defaults.timeout;
|
|
1042
|
+
const idempotencyKey = options.idempotencyKey ?? (isMutation(options.method) ? crypto.randomUUID() : void 0);
|
|
1043
|
+
for (let attempt = 0;; attempt++) {
|
|
1044
|
+
throwIfAborted(options.signal);
|
|
1045
|
+
const retryOrThrow = async (terminal) => {
|
|
1046
|
+
if (attempt >= maxRetries) throw terminal();
|
|
1047
|
+
await sleep(backoffDelay(attempt), options.signal);
|
|
1048
|
+
};
|
|
1049
|
+
const timeoutSignal = AbortSignal.timeout(timeout);
|
|
1050
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
|
|
1051
|
+
let outcome;
|
|
1052
|
+
try {
|
|
1053
|
+
outcome = await call({
|
|
1054
|
+
signal,
|
|
1055
|
+
idempotencyKey
|
|
1056
|
+
});
|
|
1057
|
+
} catch (err) {
|
|
1058
|
+
throwIfAborted(options.signal);
|
|
1059
|
+
await retryOrThrow(() => timeoutSignal.aborted ? new BirdTimeoutError(`Request timed out after ${timeout}ms`, timeout) : new BirdConnectionError(errorMessage(err)));
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
const res = outcome.response;
|
|
1063
|
+
if (!res) {
|
|
1064
|
+
await retryOrThrow(() => new BirdConnectionError("No response received from the server"));
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
if (res.ok) return {
|
|
1068
|
+
data: outcome.data,
|
|
1069
|
+
response: toBirdResponse(res)
|
|
1070
|
+
};
|
|
1071
|
+
if (!isRetryableStatus(res.status) || attempt >= maxRetries) throw mapResponseToError(res.status, outcome.error, res.headers);
|
|
1072
|
+
await sleep(retryDelay(attempt, res.headers), options.signal);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
function isMutation(method) {
|
|
1077
|
+
return [
|
|
1078
|
+
"POST",
|
|
1079
|
+
"PATCH",
|
|
1080
|
+
"DELETE"
|
|
1081
|
+
].includes(method.toUpperCase());
|
|
1082
|
+
}
|
|
1083
|
+
function isRetryableStatus(status) {
|
|
1084
|
+
return [
|
|
1085
|
+
408,
|
|
1086
|
+
429,
|
|
1087
|
+
500,
|
|
1088
|
+
502,
|
|
1089
|
+
503,
|
|
1090
|
+
504
|
|
1091
|
+
].includes(status);
|
|
1092
|
+
}
|
|
1093
|
+
/** Full-jitter exponential backoff: random in [0, min(cap, base·2^attempt)). */
|
|
1094
|
+
function backoffDelay(attempt) {
|
|
1095
|
+
const ceiling = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
|
|
1096
|
+
return Math.random() * ceiling;
|
|
1097
|
+
}
|
|
1098
|
+
/** Honor Retry-After on a retryable response, else fall back to backoff. */
|
|
1099
|
+
function retryDelay(attempt, headers) {
|
|
1100
|
+
const seconds = parseRetryAfter(headers);
|
|
1101
|
+
return seconds === void 0 ? backoffDelay(attempt) : Math.min(seconds * 1e3, RETRY_AFTER_CAP_MS);
|
|
1102
|
+
}
|
|
1103
|
+
function toBirdResponse(res) {
|
|
1104
|
+
return {
|
|
1105
|
+
status: res.status,
|
|
1106
|
+
headers: res.headers,
|
|
1107
|
+
requestId: res.headers.get("X-Request-Id") ?? ""
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function abortReason(signal) {
|
|
1111
|
+
return signal?.reason ?? new DOMException("Aborted", "AbortError");
|
|
1112
|
+
}
|
|
1113
|
+
function throwIfAborted(signal) {
|
|
1114
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
1115
|
+
}
|
|
1116
|
+
/** Sleep, rejecting immediately if the caller's signal aborts. */
|
|
1117
|
+
function sleep(ms, signal) {
|
|
1118
|
+
return new Promise((resolve, reject) => {
|
|
1119
|
+
if (signal?.aborted) {
|
|
1120
|
+
reject(abortReason(signal));
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const timer = setTimeout(() => {
|
|
1124
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1125
|
+
resolve();
|
|
1126
|
+
}, ms);
|
|
1127
|
+
const onAbort = () => {
|
|
1128
|
+
clearTimeout(timer);
|
|
1129
|
+
reject(abortReason(signal));
|
|
1130
|
+
};
|
|
1131
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
function errorMessage(err) {
|
|
1135
|
+
if (err instanceof Error) return err.message;
|
|
1136
|
+
return String(err);
|
|
1137
|
+
}
|
|
1138
|
+
//#endregion
|
|
1139
|
+
//#region src/core/result.ts
|
|
1140
|
+
function basePromise(inner) {
|
|
1141
|
+
const promise = inner.then((r) => r.data);
|
|
1142
|
+
promise.catch(() => {});
|
|
1143
|
+
promise.withResponse = () => inner;
|
|
1144
|
+
promise.safe = () => toSafe(inner);
|
|
1145
|
+
return promise;
|
|
1146
|
+
}
|
|
1147
|
+
function apiPromise(inner) {
|
|
1148
|
+
return basePromise(inner);
|
|
1149
|
+
}
|
|
1150
|
+
function paginate(fetchPage) {
|
|
1151
|
+
const first = fetchPage();
|
|
1152
|
+
const promise = basePromise(first);
|
|
1153
|
+
promise[Symbol.asyncIterator] = async function* () {
|
|
1154
|
+
let result = await first;
|
|
1155
|
+
for (;;) {
|
|
1156
|
+
for (const item of result.data.data) yield item;
|
|
1157
|
+
if (result.data.next_cursor == null) return;
|
|
1158
|
+
result = await fetchPage(result.data.next_cursor);
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
return promise;
|
|
1162
|
+
}
|
|
1163
|
+
function toSafe(inner) {
|
|
1164
|
+
return inner.then(({ data, response }) => ({
|
|
1165
|
+
data,
|
|
1166
|
+
error: null,
|
|
1167
|
+
response
|
|
1168
|
+
}), (error) => {
|
|
1169
|
+
if (error instanceof BirdError) return {
|
|
1170
|
+
data: null,
|
|
1171
|
+
error,
|
|
1172
|
+
response: null
|
|
1173
|
+
};
|
|
1174
|
+
throw error;
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/generated/client.gen.ts
|
|
1179
|
+
const client = createClient(createConfig());
|
|
1180
|
+
//#endregion
|
|
1181
|
+
//#region src/generated/sdk.gen.ts
|
|
1182
|
+
/**
|
|
1183
|
+
* List messages
|
|
1184
|
+
*
|
|
1185
|
+
* Returns a paginated list of email messages in the workspace, newest first.
|
|
1186
|
+
*/
|
|
1187
|
+
const listEmailMessages = (options) => (options?.client ?? client).get({
|
|
1188
|
+
security: [{
|
|
1189
|
+
scheme: "bearer",
|
|
1190
|
+
type: "http"
|
|
1191
|
+
}, {
|
|
1192
|
+
in: "cookie",
|
|
1193
|
+
name: "bird_session",
|
|
1194
|
+
type: "apiKey"
|
|
1195
|
+
}],
|
|
1196
|
+
url: "/v1/email/messages",
|
|
1197
|
+
...options
|
|
1198
|
+
});
|
|
1199
|
+
/**
|
|
1200
|
+
* Send a message
|
|
1201
|
+
*
|
|
1202
|
+
* Sends an email to the recipients you list explicitly in `to`/`cc`/`bcc`. Use this for transactional sends (receipts, password resets, alerts) and for marketing sends where you have the recipient addresses on hand. For sends targeting a stored audience by reference, use POST /v1/email/broadcasts. The `category` field controls suppression policy independently — set it to `marketing` when sending marketing content from this endpoint. The 202 response is returned only after the message is safely accepted for delivery. If the sender domain is not verified, or all recipients are suppressed, the request fails immediately with a 422 — it is never accepted and then silently dropped. Other field-level validation failures also return 422.
|
|
1203
|
+
* Recipient addresses on reserved testing domains are rejected with a 422 RecipientDomainNotAllowed error. This covers @example.com, @example.net, @example.org, @example.edu, @test.com, and any address under the reserved .test, .example, .invalid, or .localhost top-level domains. These placeholder domains cannot receive mail, and the resulting bounces would hurt your sender reputation — use a real recipient address instead.
|
|
1204
|
+
* During onboarding, you can send without verifying a domain: use any sender address on the shared onboarding domain (for example onboarding@messagebird.dev). Such sends skip the sender-domain verification check, can only go to verified members of your workspace (other recipients are rejected with a 422 OnboardingRecipientNotAllowed error), and are subject to a daily recipient limit per organization (429 OnboardingSendLimitExceeded once exhausted).
|
|
1205
|
+
*
|
|
1206
|
+
*/
|
|
1207
|
+
const createEmailMessage = (options) => (options.client ?? client).post({
|
|
1208
|
+
security: [{
|
|
1209
|
+
scheme: "bearer",
|
|
1210
|
+
type: "http"
|
|
1211
|
+
}, {
|
|
1212
|
+
in: "cookie",
|
|
1213
|
+
name: "bird_session",
|
|
1214
|
+
type: "apiKey"
|
|
1215
|
+
}],
|
|
1216
|
+
url: "/v1/email/messages",
|
|
1217
|
+
...options,
|
|
1218
|
+
headers: {
|
|
1219
|
+
"Content-Type": "application/json",
|
|
1220
|
+
...options.headers
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
/**
|
|
1224
|
+
* Send a batch of messages
|
|
1225
|
+
*
|
|
1226
|
+
* Accepts up to 100 independent email messages and queues them for delivery. All items are validated before any are queued — if one fails validation, the entire batch is rejected. Field-level validation failures and business-rule failures (such as domain_not_verified or all_recipients_suppressed) both return 422. Attachments are allowed per message. Each message must stay within the 20 MB estimated generated message-size cap. The serialized JSON request body for the batch has a hard 20 MB cap.
|
|
1227
|
+
*
|
|
1228
|
+
*/
|
|
1229
|
+
const createEmailMessageBatch = (options) => (options.client ?? client).post({
|
|
1230
|
+
security: [{
|
|
1231
|
+
scheme: "bearer",
|
|
1232
|
+
type: "http"
|
|
1233
|
+
}, {
|
|
1234
|
+
in: "cookie",
|
|
1235
|
+
name: "bird_session",
|
|
1236
|
+
type: "apiKey"
|
|
1237
|
+
}],
|
|
1238
|
+
url: "/v1/email/batches",
|
|
1239
|
+
...options,
|
|
1240
|
+
headers: {
|
|
1241
|
+
"Content-Type": "application/json",
|
|
1242
|
+
...options.headers
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
/**
|
|
1246
|
+
* Get a message
|
|
1247
|
+
*
|
|
1248
|
+
* Returns a single email message object with aggregate delivery status and counts. Message body (html, text) is not returned — it is not stored after delivery.
|
|
1249
|
+
*
|
|
1250
|
+
*/
|
|
1251
|
+
const getEmailMessage = (options) => (options.client ?? client).get({
|
|
1252
|
+
security: [{
|
|
1253
|
+
scheme: "bearer",
|
|
1254
|
+
type: "http"
|
|
1255
|
+
}, {
|
|
1256
|
+
in: "cookie",
|
|
1257
|
+
name: "bird_session",
|
|
1258
|
+
type: "apiKey"
|
|
1259
|
+
}],
|
|
1260
|
+
url: "/v1/email/messages/{message_id}",
|
|
1261
|
+
...options
|
|
1262
|
+
});
|
|
1263
|
+
/**
|
|
1264
|
+
* List contacts
|
|
1265
|
+
*
|
|
1266
|
+
* Returns a paginated list of contacts in the workspace, newest first. Look up a single contact by its exact `email` or `external_id`, or search by email substring with `search`.
|
|
1267
|
+
*
|
|
1268
|
+
*/
|
|
1269
|
+
const listContacts = (options) => (options?.client ?? client).get({
|
|
1270
|
+
security: [{
|
|
1271
|
+
scheme: "bearer",
|
|
1272
|
+
type: "http"
|
|
1273
|
+
}, {
|
|
1274
|
+
in: "cookie",
|
|
1275
|
+
name: "bird_session",
|
|
1276
|
+
type: "apiKey"
|
|
1277
|
+
}],
|
|
1278
|
+
url: "/v1/contacts",
|
|
1279
|
+
...options
|
|
1280
|
+
});
|
|
1281
|
+
/**
|
|
1282
|
+
* Create a contact
|
|
1283
|
+
*
|
|
1284
|
+
* Creates a contact in the workspace. Contacts are unique by email address; creating a second contact with the same email returns a conflict error. The same applies to `external_id` — a value already used by another contact returns a conflict error. Custom values in `data` must use property keys you created via the contact properties API, with values matching each property's declared type.
|
|
1285
|
+
*
|
|
1286
|
+
*/
|
|
1287
|
+
const createContact = (options) => (options.client ?? client).post({
|
|
1288
|
+
security: [{
|
|
1289
|
+
scheme: "bearer",
|
|
1290
|
+
type: "http"
|
|
1291
|
+
}, {
|
|
1292
|
+
in: "cookie",
|
|
1293
|
+
name: "bird_session",
|
|
1294
|
+
type: "apiKey"
|
|
1295
|
+
}],
|
|
1296
|
+
url: "/v1/contacts",
|
|
1297
|
+
...options,
|
|
1298
|
+
headers: {
|
|
1299
|
+
"Content-Type": "application/json",
|
|
1300
|
+
...options.headers
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
/**
|
|
1304
|
+
* Create or update contacts in bulk
|
|
1305
|
+
*
|
|
1306
|
+
* Creates or updates up to 1,000 contacts in one request, matched by email address (trimmed and lowercased before matching). Existing contacts are updated with the supplied fields; new ones are created. Optionally adds every contact in the request to one or more audiences. Results are returned per contact, in submission order — a failed entry does not abort the rest of the request.
|
|
1307
|
+
*
|
|
1308
|
+
*/
|
|
1309
|
+
const createContactBatch = (options) => (options.client ?? client).post({
|
|
1310
|
+
security: [{
|
|
1311
|
+
scheme: "bearer",
|
|
1312
|
+
type: "http"
|
|
1313
|
+
}, {
|
|
1314
|
+
in: "cookie",
|
|
1315
|
+
name: "bird_session",
|
|
1316
|
+
type: "apiKey"
|
|
1317
|
+
}],
|
|
1318
|
+
url: "/v1/contacts/batch",
|
|
1319
|
+
...options,
|
|
1320
|
+
headers: {
|
|
1321
|
+
"Content-Type": "application/json",
|
|
1322
|
+
...options.headers
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
/**
|
|
1326
|
+
* Delete a contact
|
|
1327
|
+
*
|
|
1328
|
+
* Deletes a contact and removes it from every audience it belongs to. Suppression records for the address are not affected — an unsubscribed or bounced address stays suppressed even after the contact is deleted.
|
|
1329
|
+
*
|
|
1330
|
+
*/
|
|
1331
|
+
const deleteContact = (options) => (options.client ?? client).delete({
|
|
1332
|
+
security: [{
|
|
1333
|
+
scheme: "bearer",
|
|
1334
|
+
type: "http"
|
|
1335
|
+
}, {
|
|
1336
|
+
in: "cookie",
|
|
1337
|
+
name: "bird_session",
|
|
1338
|
+
type: "apiKey"
|
|
1339
|
+
}],
|
|
1340
|
+
url: "/v1/contacts/{contact_id}",
|
|
1341
|
+
...options
|
|
1342
|
+
});
|
|
1343
|
+
/**
|
|
1344
|
+
* Get a contact
|
|
1345
|
+
*
|
|
1346
|
+
* Returns a single contact by ID.
|
|
1347
|
+
*/
|
|
1348
|
+
const getContact = (options) => (options.client ?? client).get({
|
|
1349
|
+
security: [{
|
|
1350
|
+
scheme: "bearer",
|
|
1351
|
+
type: "http"
|
|
1352
|
+
}, {
|
|
1353
|
+
in: "cookie",
|
|
1354
|
+
name: "bird_session",
|
|
1355
|
+
type: "apiKey"
|
|
1356
|
+
}],
|
|
1357
|
+
url: "/v1/contacts/{contact_id}",
|
|
1358
|
+
...options
|
|
1359
|
+
});
|
|
1360
|
+
/**
|
|
1361
|
+
* Update a contact
|
|
1362
|
+
*
|
|
1363
|
+
* Updates a contact. Supplied fields are changed; omitted fields are left unchanged. Custom values in `data` are merged — keys you supply are set, keys set to null are removed, and keys you omit are unchanged. Changing the email address or `external_id` to a value already used by another contact returns a conflict error.
|
|
1364
|
+
*
|
|
1365
|
+
*/
|
|
1366
|
+
const updateContact = (options) => (options.client ?? client).patch({
|
|
1367
|
+
security: [{
|
|
1368
|
+
scheme: "bearer",
|
|
1369
|
+
type: "http"
|
|
1370
|
+
}, {
|
|
1371
|
+
in: "cookie",
|
|
1372
|
+
name: "bird_session",
|
|
1373
|
+
type: "apiKey"
|
|
1374
|
+
}],
|
|
1375
|
+
url: "/v1/contacts/{contact_id}",
|
|
1376
|
+
...options,
|
|
1377
|
+
headers: {
|
|
1378
|
+
"Content-Type": "application/json",
|
|
1379
|
+
...options.headers
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
/**
|
|
1383
|
+
* List contact properties
|
|
1384
|
+
*
|
|
1385
|
+
* Returns a paginated list of the workspace's contact properties.
|
|
1386
|
+
*/
|
|
1387
|
+
const listContactProperties = (options) => (options?.client ?? client).get({
|
|
1388
|
+
security: [{
|
|
1389
|
+
scheme: "bearer",
|
|
1390
|
+
type: "http"
|
|
1391
|
+
}, {
|
|
1392
|
+
in: "cookie",
|
|
1393
|
+
name: "bird_session",
|
|
1394
|
+
type: "apiKey"
|
|
1395
|
+
}],
|
|
1396
|
+
url: "/v1/contact-properties",
|
|
1397
|
+
...options
|
|
1398
|
+
});
|
|
1399
|
+
/**
|
|
1400
|
+
* Create a contact property
|
|
1401
|
+
*
|
|
1402
|
+
* Defines a custom property that contacts in the workspace can carry. The key becomes available in contact `data` and as a template variable in broadcasts. Keys are unique within the workspace; the key and type cannot be changed after creation.
|
|
1403
|
+
*
|
|
1404
|
+
*/
|
|
1405
|
+
const createContactProperty = (options) => (options.client ?? client).post({
|
|
1406
|
+
security: [{
|
|
1407
|
+
scheme: "bearer",
|
|
1408
|
+
type: "http"
|
|
1409
|
+
}, {
|
|
1410
|
+
in: "cookie",
|
|
1411
|
+
name: "bird_session",
|
|
1412
|
+
type: "apiKey"
|
|
1413
|
+
}],
|
|
1414
|
+
url: "/v1/contact-properties",
|
|
1415
|
+
...options,
|
|
1416
|
+
headers: {
|
|
1417
|
+
"Content-Type": "application/json",
|
|
1418
|
+
...options.headers
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
/**
|
|
1422
|
+
* Get a contact property
|
|
1423
|
+
*
|
|
1424
|
+
* Returns a single contact property by ID.
|
|
1425
|
+
*/
|
|
1426
|
+
const getContactProperty = (options) => (options.client ?? client).get({
|
|
1427
|
+
security: [{
|
|
1428
|
+
scheme: "bearer",
|
|
1429
|
+
type: "http"
|
|
1430
|
+
}, {
|
|
1431
|
+
in: "cookie",
|
|
1432
|
+
name: "bird_session",
|
|
1433
|
+
type: "apiKey"
|
|
1434
|
+
}],
|
|
1435
|
+
url: "/v1/contact-properties/{property_id}",
|
|
1436
|
+
...options
|
|
1437
|
+
});
|
|
1438
|
+
/**
|
|
1439
|
+
* Update a contact property
|
|
1440
|
+
*
|
|
1441
|
+
* Updates a contact property's fallback value. The key and type cannot be changed after creation — create a new property instead.
|
|
1442
|
+
*
|
|
1443
|
+
*/
|
|
1444
|
+
const updateContactProperty = (options) => (options.client ?? client).patch({
|
|
1445
|
+
security: [{
|
|
1446
|
+
scheme: "bearer",
|
|
1447
|
+
type: "http"
|
|
1448
|
+
}, {
|
|
1449
|
+
in: "cookie",
|
|
1450
|
+
name: "bird_session",
|
|
1451
|
+
type: "apiKey"
|
|
1452
|
+
}],
|
|
1453
|
+
url: "/v1/contact-properties/{property_id}",
|
|
1454
|
+
...options,
|
|
1455
|
+
headers: {
|
|
1456
|
+
"Content-Type": "application/json",
|
|
1457
|
+
...options.headers
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
/**
|
|
1461
|
+
* Archive a contact property
|
|
1462
|
+
*
|
|
1463
|
+
* Archives a contact property. The key stops being accepted in new contact writes and stops rendering in templates, but every value already stored on your contacts is preserved and still returned when you read a contact. The key stays reserved, so it cannot be re-created with a different type. Returns 409 if the property is already archived. Reverse it with unarchive.
|
|
1464
|
+
*
|
|
1465
|
+
*/
|
|
1466
|
+
const archiveContactProperty = (options) => (options.client ?? client).post({
|
|
1467
|
+
security: [{
|
|
1468
|
+
scheme: "bearer",
|
|
1469
|
+
type: "http"
|
|
1470
|
+
}, {
|
|
1471
|
+
in: "cookie",
|
|
1472
|
+
name: "bird_session",
|
|
1473
|
+
type: "apiKey"
|
|
1474
|
+
}],
|
|
1475
|
+
url: "/v1/contact-properties/{property_id}/archive",
|
|
1476
|
+
...options
|
|
1477
|
+
});
|
|
1478
|
+
/**
|
|
1479
|
+
* Unarchive a contact property
|
|
1480
|
+
*
|
|
1481
|
+
* Reactivates an archived contact property. The key is accepted in contact writes and renders in templates again; stored values were never removed, so they are unchanged. Returns 409 if the property is not archived.
|
|
1482
|
+
*
|
|
1483
|
+
*/
|
|
1484
|
+
const unarchiveContactProperty = (options) => (options.client ?? client).post({
|
|
1485
|
+
security: [{
|
|
1486
|
+
scheme: "bearer",
|
|
1487
|
+
type: "http"
|
|
1488
|
+
}, {
|
|
1489
|
+
in: "cookie",
|
|
1490
|
+
name: "bird_session",
|
|
1491
|
+
type: "apiKey"
|
|
1492
|
+
}],
|
|
1493
|
+
url: "/v1/contact-properties/{property_id}/unarchive",
|
|
1494
|
+
...options
|
|
1495
|
+
});
|
|
1496
|
+
/**
|
|
1497
|
+
* List audiences
|
|
1498
|
+
*
|
|
1499
|
+
* Returns a paginated list of audiences in the workspace, newest first.
|
|
1500
|
+
*/
|
|
1501
|
+
const listAudiences = (options) => (options?.client ?? client).get({
|
|
1502
|
+
security: [{
|
|
1503
|
+
scheme: "bearer",
|
|
1504
|
+
type: "http"
|
|
1505
|
+
}, {
|
|
1506
|
+
in: "cookie",
|
|
1507
|
+
name: "bird_session",
|
|
1508
|
+
type: "apiKey"
|
|
1509
|
+
}],
|
|
1510
|
+
url: "/v1/audiences",
|
|
1511
|
+
...options
|
|
1512
|
+
});
|
|
1513
|
+
/**
|
|
1514
|
+
* Create an audience
|
|
1515
|
+
*
|
|
1516
|
+
* Creates an audience in the workspace. Static audiences start empty — add contacts via the audience contacts endpoint or the bulk contact upsert.
|
|
1517
|
+
*
|
|
1518
|
+
*/
|
|
1519
|
+
const createAudience = (options) => (options.client ?? client).post({
|
|
1520
|
+
security: [{
|
|
1521
|
+
scheme: "bearer",
|
|
1522
|
+
type: "http"
|
|
1523
|
+
}, {
|
|
1524
|
+
in: "cookie",
|
|
1525
|
+
name: "bird_session",
|
|
1526
|
+
type: "apiKey"
|
|
1527
|
+
}],
|
|
1528
|
+
url: "/v1/audiences",
|
|
1529
|
+
...options,
|
|
1530
|
+
headers: {
|
|
1531
|
+
"Content-Type": "application/json",
|
|
1532
|
+
...options.headers
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
/**
|
|
1536
|
+
* Delete an audience
|
|
1537
|
+
*
|
|
1538
|
+
* Deletes an audience and its memberships. Contacts themselves are not deleted. An audience cannot be deleted while a broadcast targeting it is scheduled, accepted, sending, or canceling — cancel that broadcast first.
|
|
1539
|
+
*
|
|
1540
|
+
*/
|
|
1541
|
+
const deleteAudience = (options) => (options.client ?? client).delete({
|
|
1542
|
+
security: [{
|
|
1543
|
+
scheme: "bearer",
|
|
1544
|
+
type: "http"
|
|
1545
|
+
}, {
|
|
1546
|
+
in: "cookie",
|
|
1547
|
+
name: "bird_session",
|
|
1548
|
+
type: "apiKey"
|
|
1549
|
+
}],
|
|
1550
|
+
url: "/v1/audiences/{audience_id}",
|
|
1551
|
+
...options
|
|
1552
|
+
});
|
|
1553
|
+
/**
|
|
1554
|
+
* Get an audience
|
|
1555
|
+
*
|
|
1556
|
+
* Returns a single audience by ID.
|
|
1557
|
+
*/
|
|
1558
|
+
const getAudience = (options) => (options.client ?? client).get({
|
|
1559
|
+
security: [{
|
|
1560
|
+
scheme: "bearer",
|
|
1561
|
+
type: "http"
|
|
1562
|
+
}, {
|
|
1563
|
+
in: "cookie",
|
|
1564
|
+
name: "bird_session",
|
|
1565
|
+
type: "apiKey"
|
|
1566
|
+
}],
|
|
1567
|
+
url: "/v1/audiences/{audience_id}",
|
|
1568
|
+
...options
|
|
1569
|
+
});
|
|
1570
|
+
/**
|
|
1571
|
+
* Update an audience
|
|
1572
|
+
*
|
|
1573
|
+
* Updates an audience's name or description.
|
|
1574
|
+
*/
|
|
1575
|
+
const updateAudience = (options) => (options.client ?? client).patch({
|
|
1576
|
+
security: [{
|
|
1577
|
+
scheme: "bearer",
|
|
1578
|
+
type: "http"
|
|
1579
|
+
}, {
|
|
1580
|
+
in: "cookie",
|
|
1581
|
+
name: "bird_session",
|
|
1582
|
+
type: "apiKey"
|
|
1583
|
+
}],
|
|
1584
|
+
url: "/v1/audiences/{audience_id}",
|
|
1585
|
+
...options,
|
|
1586
|
+
headers: {
|
|
1587
|
+
"Content-Type": "application/json",
|
|
1588
|
+
...options.headers
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
/**
|
|
1592
|
+
* List an audience's contacts
|
|
1593
|
+
*
|
|
1594
|
+
* Lists the contacts in a static audience as a cursor page, ordered by the time each contact joined the audience, most recent first. Each entry is the contact together with the time it joined.
|
|
1595
|
+
*
|
|
1596
|
+
*/
|
|
1597
|
+
const listAudienceContacts = (options) => (options.client ?? client).get({
|
|
1598
|
+
security: [{
|
|
1599
|
+
scheme: "bearer",
|
|
1600
|
+
type: "http"
|
|
1601
|
+
}, {
|
|
1602
|
+
in: "cookie",
|
|
1603
|
+
name: "bird_session",
|
|
1604
|
+
type: "apiKey"
|
|
1605
|
+
}],
|
|
1606
|
+
url: "/v1/audiences/{audience_id}/contacts",
|
|
1607
|
+
...options
|
|
1608
|
+
});
|
|
1609
|
+
/**
|
|
1610
|
+
* Add contacts to an audience
|
|
1611
|
+
*
|
|
1612
|
+
* Adds up to 1,000 contacts to a static audience. Contacts that are already members are left in place. If any contact ID does not exist, the whole request fails and no contacts are added.
|
|
1613
|
+
*
|
|
1614
|
+
*/
|
|
1615
|
+
const assignAudienceContacts = (options) => (options.client ?? client).post({
|
|
1616
|
+
security: [{
|
|
1617
|
+
scheme: "bearer",
|
|
1618
|
+
type: "http"
|
|
1619
|
+
}, {
|
|
1620
|
+
in: "cookie",
|
|
1621
|
+
name: "bird_session",
|
|
1622
|
+
type: "apiKey"
|
|
1623
|
+
}],
|
|
1624
|
+
url: "/v1/audiences/{audience_id}/contacts",
|
|
1625
|
+
...options,
|
|
1626
|
+
headers: {
|
|
1627
|
+
"Content-Type": "application/json",
|
|
1628
|
+
...options.headers
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
/**
|
|
1632
|
+
* Remove contacts from an audience
|
|
1633
|
+
*
|
|
1634
|
+
* Removes up to 1,000 contacts from a static audience. Contacts that are not members are skipped. If any contact ID does not exist, the whole request fails and no contacts are removed. The contacts themselves are not deleted and remain members of any other audiences.
|
|
1635
|
+
*
|
|
1636
|
+
*/
|
|
1637
|
+
const unassignAudienceContacts = (options) => (options.client ?? client).post({
|
|
1638
|
+
security: [{
|
|
1639
|
+
scheme: "bearer",
|
|
1640
|
+
type: "http"
|
|
1641
|
+
}, {
|
|
1642
|
+
in: "cookie",
|
|
1643
|
+
name: "bird_session",
|
|
1644
|
+
type: "apiKey"
|
|
1645
|
+
}],
|
|
1646
|
+
url: "/v1/audiences/{audience_id}/contacts/remove",
|
|
1647
|
+
...options,
|
|
1648
|
+
headers: {
|
|
1649
|
+
"Content-Type": "application/json",
|
|
1650
|
+
...options.headers
|
|
1651
|
+
}
|
|
1652
|
+
});
|
|
1653
|
+
/**
|
|
1654
|
+
* Remove a contact from an audience
|
|
1655
|
+
*
|
|
1656
|
+
* Removes a contact's membership in an audience. The contact itself is not deleted and remains a member of any other audiences. Removing a contact that is not a member of the audience succeeds with no effect (204); an unknown audience or contact returns a not-found error.
|
|
1657
|
+
*
|
|
1658
|
+
*/
|
|
1659
|
+
const unassignAudienceContact = (options) => (options.client ?? client).delete({
|
|
1660
|
+
security: [{
|
|
1661
|
+
scheme: "bearer",
|
|
1662
|
+
type: "http"
|
|
1663
|
+
}, {
|
|
1664
|
+
in: "cookie",
|
|
1665
|
+
name: "bird_session",
|
|
1666
|
+
type: "apiKey"
|
|
1667
|
+
}],
|
|
1668
|
+
url: "/v1/audiences/{audience_id}/contacts/{contact_id}",
|
|
1669
|
+
...options
|
|
1670
|
+
});
|
|
1671
|
+
/**
|
|
1672
|
+
* List SMS messages
|
|
1673
|
+
*
|
|
1674
|
+
* Returns a paginated list of SMS messages in the workspace, newest first.
|
|
1675
|
+
*/
|
|
1676
|
+
const listSmsMessages = (options) => (options?.client ?? client).get({
|
|
1677
|
+
security: [{
|
|
1678
|
+
scheme: "bearer",
|
|
1679
|
+
type: "http"
|
|
1680
|
+
}, {
|
|
1681
|
+
in: "cookie",
|
|
1682
|
+
name: "bird_session",
|
|
1683
|
+
type: "apiKey"
|
|
1684
|
+
}],
|
|
1685
|
+
url: "/v1/sms/messages",
|
|
1686
|
+
...options
|
|
1687
|
+
});
|
|
1688
|
+
/**
|
|
1689
|
+
* Send an SMS message
|
|
1690
|
+
*
|
|
1691
|
+
* Sends a single SMS message to one recipient. The 202 response is returned only after the message is durably accepted for delivery; actual delivery happens asynchronously, and you track it with the get-message and list-events endpoints or with webhooks. `category` is required and controls opt-out (STOP) policy, quiet hours, and per-country compliance. A body exceeding the 12-segment cap is rejected with a 422, as are field-level validation failures and sends that cannot be afforded by the workspace balance.
|
|
1692
|
+
*
|
|
1693
|
+
*/
|
|
1694
|
+
const createSmsMessage = (options) => (options.client ?? client).post({
|
|
1695
|
+
security: [{
|
|
1696
|
+
scheme: "bearer",
|
|
1697
|
+
type: "http"
|
|
1698
|
+
}, {
|
|
1699
|
+
in: "cookie",
|
|
1700
|
+
name: "bird_session",
|
|
1701
|
+
type: "apiKey"
|
|
1702
|
+
}],
|
|
1703
|
+
url: "/v1/sms/messages",
|
|
1704
|
+
...options,
|
|
1705
|
+
headers: {
|
|
1706
|
+
"Content-Type": "application/json",
|
|
1707
|
+
...options.headers
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
/**
|
|
1711
|
+
* Send a batch of SMS messages
|
|
1712
|
+
*
|
|
1713
|
+
* Accepts up to 100 independent SMS messages and queues them for delivery. Each message is an independent send with its own ID, status, and cost. All items are validated before any are queued — if one fails validation, the entire batch is rejected. Field-level validation failures, the 12-segment body cap, and insufficient workspace balance all return 422 (or 402 for balance).
|
|
1714
|
+
*
|
|
1715
|
+
*/
|
|
1716
|
+
const createSmsMessageBatch = (options) => (options.client ?? client).post({
|
|
1717
|
+
security: [{
|
|
1718
|
+
scheme: "bearer",
|
|
1719
|
+
type: "http"
|
|
1720
|
+
}, {
|
|
1721
|
+
in: "cookie",
|
|
1722
|
+
name: "bird_session",
|
|
1723
|
+
type: "apiKey"
|
|
1724
|
+
}],
|
|
1725
|
+
url: "/v1/sms/batches",
|
|
1726
|
+
...options,
|
|
1727
|
+
headers: {
|
|
1728
|
+
"Content-Type": "application/json",
|
|
1729
|
+
...options.headers
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
/**
|
|
1733
|
+
* Get an SMS message
|
|
1734
|
+
*
|
|
1735
|
+
* Returns a single SMS message with its current status, segment breakdown, cost, and failure detail if it failed.
|
|
1736
|
+
*/
|
|
1737
|
+
const getSmsMessage = (options) => (options.client ?? client).get({
|
|
1738
|
+
security: [{
|
|
1739
|
+
scheme: "bearer",
|
|
1740
|
+
type: "http"
|
|
1741
|
+
}, {
|
|
1742
|
+
in: "cookie",
|
|
1743
|
+
name: "bird_session",
|
|
1744
|
+
type: "apiKey"
|
|
1745
|
+
}],
|
|
1746
|
+
url: "/v1/sms/messages/{message_id}",
|
|
1747
|
+
...options
|
|
1748
|
+
});
|
|
1749
|
+
/**
|
|
1750
|
+
* List SMS templates
|
|
1751
|
+
*
|
|
1752
|
+
* Returns the SMS templates available to your workspace, including Bird's built-in templates. Filter by scope, category, or language.
|
|
1753
|
+
*
|
|
1754
|
+
*/
|
|
1755
|
+
const listSmsTemplates = (options) => (options?.client ?? client).get({
|
|
1756
|
+
security: [{
|
|
1757
|
+
scheme: "bearer",
|
|
1758
|
+
type: "http"
|
|
1759
|
+
}, {
|
|
1760
|
+
in: "cookie",
|
|
1761
|
+
name: "bird_session",
|
|
1762
|
+
type: "apiKey"
|
|
1763
|
+
}],
|
|
1764
|
+
url: "/v1/sms/templates",
|
|
1765
|
+
...options
|
|
1766
|
+
});
|
|
1767
|
+
/**
|
|
1768
|
+
* Get an SMS template
|
|
1769
|
+
*
|
|
1770
|
+
* Returns a single SMS template by its name or id.
|
|
1771
|
+
*/
|
|
1772
|
+
const getSmsTemplate = (options) => (options.client ?? client).get({
|
|
1773
|
+
security: [{
|
|
1774
|
+
scheme: "bearer",
|
|
1775
|
+
type: "http"
|
|
1776
|
+
}, {
|
|
1777
|
+
in: "cookie",
|
|
1778
|
+
name: "bird_session",
|
|
1779
|
+
type: "apiKey"
|
|
1780
|
+
}],
|
|
1781
|
+
url: "/v1/sms/templates/{template_ref}",
|
|
1782
|
+
...options
|
|
1783
|
+
});
|
|
1784
|
+
/**
|
|
1785
|
+
* List email templates
|
|
1786
|
+
*
|
|
1787
|
+
* Returns a paginated list of the workspace's email templates, newest first. Filter by category, source, or a case-insensitive search that matches the template's name or description.
|
|
1788
|
+
*
|
|
1789
|
+
*/
|
|
1790
|
+
const listEmailTemplates = (options) => (options?.client ?? client).get({
|
|
1791
|
+
security: [{
|
|
1792
|
+
scheme: "bearer",
|
|
1793
|
+
type: "http"
|
|
1794
|
+
}, {
|
|
1795
|
+
in: "cookie",
|
|
1796
|
+
name: "bird_session",
|
|
1797
|
+
type: "apiKey"
|
|
1798
|
+
}],
|
|
1799
|
+
url: "/v1/email/templates",
|
|
1800
|
+
...options
|
|
1801
|
+
});
|
|
1802
|
+
/**
|
|
1803
|
+
* Create an email template
|
|
1804
|
+
*
|
|
1805
|
+
* Creates a template and its initial editable draft. The body carries the template's name, category, authoring format (`source`), and the draft's content (`subject`, `html`, `text`). A name already used in the workspace returns a conflict.
|
|
1806
|
+
*
|
|
1807
|
+
*/
|
|
1808
|
+
const createEmailTemplate = (options) => (options.client ?? client).post({
|
|
1809
|
+
security: [{
|
|
1810
|
+
scheme: "bearer",
|
|
1811
|
+
type: "http"
|
|
1812
|
+
}, {
|
|
1813
|
+
in: "cookie",
|
|
1814
|
+
name: "bird_session",
|
|
1815
|
+
type: "apiKey"
|
|
1816
|
+
}],
|
|
1817
|
+
url: "/v1/email/templates",
|
|
1818
|
+
...options,
|
|
1819
|
+
headers: {
|
|
1820
|
+
"Content-Type": "application/json",
|
|
1821
|
+
...options.headers
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
/**
|
|
1825
|
+
* Delete an email template
|
|
1826
|
+
*
|
|
1827
|
+
* Deletes the template and all its versions. The name becomes available for reuse within the workspace.
|
|
1828
|
+
*
|
|
1829
|
+
*/
|
|
1830
|
+
const deleteEmailTemplate = (options) => (options.client ?? client).delete({
|
|
1831
|
+
security: [{
|
|
1832
|
+
scheme: "bearer",
|
|
1833
|
+
type: "http"
|
|
1834
|
+
}, {
|
|
1835
|
+
in: "cookie",
|
|
1836
|
+
name: "bird_session",
|
|
1837
|
+
type: "apiKey"
|
|
1838
|
+
}],
|
|
1839
|
+
url: "/v1/email/templates/{template_id}",
|
|
1840
|
+
...options
|
|
1841
|
+
});
|
|
1842
|
+
/**
|
|
1843
|
+
* Get an email template
|
|
1844
|
+
*
|
|
1845
|
+
* Returns a single email template with its current draft content (subject, HTML, and plain text), the draft revision, and its draft and published version ids.
|
|
1846
|
+
*
|
|
1847
|
+
*/
|
|
1848
|
+
const getEmailTemplate = (options) => (options.client ?? client).get({
|
|
1849
|
+
security: [{
|
|
1850
|
+
scheme: "bearer",
|
|
1851
|
+
type: "http"
|
|
1852
|
+
}, {
|
|
1853
|
+
in: "cookie",
|
|
1854
|
+
name: "bird_session",
|
|
1855
|
+
type: "apiKey"
|
|
1856
|
+
}],
|
|
1857
|
+
url: "/v1/email/templates/{template_id}",
|
|
1858
|
+
...options
|
|
1859
|
+
});
|
|
1860
|
+
/**
|
|
1861
|
+
* Update an email template
|
|
1862
|
+
*
|
|
1863
|
+
* Updates a template's metadata and its draft content. Only the fields you send are changed. Send the draft `revision` you last read; if it is stale (someone else edited the draft first) the request returns a conflict so you can reload and retry.
|
|
1864
|
+
*
|
|
1865
|
+
*/
|
|
1866
|
+
const updateEmailTemplate = (options) => (options.client ?? client).patch({
|
|
1867
|
+
security: [{
|
|
1868
|
+
scheme: "bearer",
|
|
1869
|
+
type: "http"
|
|
1870
|
+
}, {
|
|
1871
|
+
in: "cookie",
|
|
1872
|
+
name: "bird_session",
|
|
1873
|
+
type: "apiKey"
|
|
1874
|
+
}],
|
|
1875
|
+
url: "/v1/email/templates/{template_id}",
|
|
1876
|
+
...options,
|
|
1877
|
+
headers: {
|
|
1878
|
+
"Content-Type": "application/json",
|
|
1879
|
+
...options.headers
|
|
1880
|
+
}
|
|
1881
|
+
});
|
|
1882
|
+
/**
|
|
1883
|
+
* List email template versions
|
|
1884
|
+
*
|
|
1885
|
+
* Returns every version of the template — the current draft plus all published versions — newest first.
|
|
1886
|
+
*
|
|
1887
|
+
*/
|
|
1888
|
+
const listEmailTemplateVersions = (options) => (options.client ?? client).get({
|
|
1889
|
+
security: [{
|
|
1890
|
+
scheme: "bearer",
|
|
1891
|
+
type: "http"
|
|
1892
|
+
}, {
|
|
1893
|
+
in: "cookie",
|
|
1894
|
+
name: "bird_session",
|
|
1895
|
+
type: "apiKey"
|
|
1896
|
+
}],
|
|
1897
|
+
url: "/v1/email/templates/{template_id}/versions",
|
|
1898
|
+
...options
|
|
1899
|
+
});
|
|
1900
|
+
/**
|
|
1901
|
+
* Get an email template version
|
|
1902
|
+
*
|
|
1903
|
+
* Returns a single version of an email template.
|
|
1904
|
+
*/
|
|
1905
|
+
const getEmailTemplateVersion = (options) => (options.client ?? client).get({
|
|
1906
|
+
security: [{
|
|
1907
|
+
scheme: "bearer",
|
|
1908
|
+
type: "http"
|
|
1909
|
+
}, {
|
|
1910
|
+
in: "cookie",
|
|
1911
|
+
name: "bird_session",
|
|
1912
|
+
type: "apiKey"
|
|
1913
|
+
}],
|
|
1914
|
+
url: "/v1/email/templates/{template_id}/versions/{version_id}",
|
|
1915
|
+
...options
|
|
1916
|
+
});
|
|
1917
|
+
/**
|
|
1918
|
+
* Publish an email template
|
|
1919
|
+
*
|
|
1920
|
+
* Publishes the template's current draft as a new immutable, numbered version and makes it the live version used by sends. The draft remains editable for future changes. The draft must have a subject and a body; an empty draft is rejected.
|
|
1921
|
+
*
|
|
1922
|
+
*/
|
|
1923
|
+
const publishEmailTemplate = (options) => (options.client ?? client).post({
|
|
1924
|
+
security: [{
|
|
1925
|
+
scheme: "bearer",
|
|
1926
|
+
type: "http"
|
|
1927
|
+
}, {
|
|
1928
|
+
in: "cookie",
|
|
1929
|
+
name: "bird_session",
|
|
1930
|
+
type: "apiKey"
|
|
1931
|
+
}],
|
|
1932
|
+
url: "/v1/email/templates/{template_id}/publish",
|
|
1933
|
+
...options
|
|
1934
|
+
});
|
|
1935
|
+
//#endregion
|
|
1936
|
+
//#region src/resources/base.ts
|
|
1937
|
+
var Resource = class {
|
|
1938
|
+
core;
|
|
1939
|
+
client;
|
|
1940
|
+
constructor(core, client) {
|
|
1941
|
+
this.core = core;
|
|
1942
|
+
this.client = client;
|
|
1943
|
+
}
|
|
1944
|
+
/** Run a single typed call through the lifecycle. */
|
|
1945
|
+
call(method, options, invoke) {
|
|
1946
|
+
return apiPromise(this.core.request((ctx) => invoke(callContext(ctx, options)), lifecycle(method, options)));
|
|
1947
|
+
}
|
|
1948
|
+
/** Run a cursor-paginated list through the lifecycle (each page retried independently). */
|
|
1949
|
+
paginated(method, options, invoke) {
|
|
1950
|
+
return paginate((cursor) => this.core.request((ctx) => invoke(callContext(ctx, options), cursor), lifecycle(method, options)));
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
function callContext(ctx, options) {
|
|
1954
|
+
return {
|
|
1955
|
+
signal: ctx.signal,
|
|
1956
|
+
headers: mergeHeaders(ctx.idempotencyKey, options?.headers)
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
function lifecycle(method, options) {
|
|
1960
|
+
return {
|
|
1961
|
+
method,
|
|
1962
|
+
idempotencyKey: options?.idempotencyKey,
|
|
1963
|
+
signal: options?.signal,
|
|
1964
|
+
timeout: options?.timeout,
|
|
1965
|
+
maxRetries: options?.maxRetries
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
function mergeHeaders(idempotencyKey, extra) {
|
|
1969
|
+
return {
|
|
1970
|
+
...extra,
|
|
1971
|
+
...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
//#endregion
|
|
1975
|
+
//#region src/resources/email.ts
|
|
1976
|
+
var EmailResource = class extends Resource {
|
|
1977
|
+
#defaults;
|
|
1978
|
+
constructor(core, client, defaults) {
|
|
1979
|
+
super(core, client);
|
|
1980
|
+
this.#defaults = defaults;
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Send an email message. Resolves once the message is accepted for delivery
|
|
1984
|
+
* (the API's 202). Throws on failure — a 422 (unverified sender, all
|
|
1985
|
+
* recipients suppressed, validation) is a `BirdValidationError`. Fields set as
|
|
1986
|
+
* channel defaults may be omitted (per-send value wins).
|
|
1987
|
+
*
|
|
1988
|
+
* @example Send a message
|
|
1989
|
+
* const msg = await bird.email.send({
|
|
1990
|
+
* from: { email: "onboarding@messagebird.dev", name: "Bird" },
|
|
1991
|
+
* to: ["delivered@messagebird.dev"],
|
|
1992
|
+
* subject: "Hello from Bird",
|
|
1993
|
+
* html: "<p>My first Bird email.</p>",
|
|
1994
|
+
* });
|
|
1995
|
+
* console.log(msg.id, msg.status); // "em_…", "accepted"
|
|
1996
|
+
*
|
|
1997
|
+
* @example A richer send — cc/bcc, reply-to, tags, metadata, click-tracking off, and an idempotency key (safe to retry; the server dedupes)
|
|
1998
|
+
* await bird.email.send(
|
|
1999
|
+
* {
|
|
2000
|
+
* from: "hello@acme.com",
|
|
2001
|
+
* to: ["a@example.com", "b@example.com"],
|
|
2002
|
+
* cc: ["manager@example.com"],
|
|
2003
|
+
* reply_to: ["support@acme.com"],
|
|
2004
|
+
* subject: "Your March invoice",
|
|
2005
|
+
* html: "<p>Attached.</p>",
|
|
2006
|
+
* tags: [{ name: "category", value: "billing" }],
|
|
2007
|
+
* metadata: { invoice_id: "inv_123" },
|
|
2008
|
+
* track_clicks: false,
|
|
2009
|
+
* },
|
|
2010
|
+
* { idempotencyKey: "invoice-march/cust_1" },
|
|
2011
|
+
* );
|
|
2012
|
+
*
|
|
2013
|
+
* @example Branch on the typed error hierarchy
|
|
2014
|
+
* import { BirdRateLimitError, BirdValidationError, BirdAPIError } from "@messagebird/sdk";
|
|
2015
|
+
*
|
|
2016
|
+
* try {
|
|
2017
|
+
* await bird.email.send({
|
|
2018
|
+
* from: { email: "onboarding@messagebird.dev", name: "Bird" },
|
|
2019
|
+
* to: ["delivered@messagebird.dev"],
|
|
2020
|
+
* subject: "Hello from Bird",
|
|
2021
|
+
* html: "<p>My first Bird email.</p>",
|
|
2022
|
+
* });
|
|
2023
|
+
* } catch (err) {
|
|
2024
|
+
* if (err instanceof BirdRateLimitError) console.log(`rate limited — retry in ${err.retryAfter}s`);
|
|
2025
|
+
* else if (err instanceof BirdValidationError) console.error(err.details);
|
|
2026
|
+
* else if (err instanceof BirdAPIError) console.error(err.code, err.requestId);
|
|
2027
|
+
* else throw err;
|
|
2028
|
+
* }
|
|
2029
|
+
*
|
|
2030
|
+
* @example Errors as values with `.safe()`
|
|
2031
|
+
* const { data, error } = await bird.email
|
|
2032
|
+
* .send({
|
|
2033
|
+
* from: { email: "onboarding@messagebird.dev", name: "Bird" },
|
|
2034
|
+
* to: ["delivered@messagebird.dev"],
|
|
2035
|
+
* subject: "Hello from Bird",
|
|
2036
|
+
* html: "<p>My first Bird email.</p>",
|
|
2037
|
+
* })
|
|
2038
|
+
* .safe();
|
|
2039
|
+
* if (error) console.error(error.message);
|
|
2040
|
+
* else console.log(data.id);
|
|
2041
|
+
*/
|
|
2042
|
+
send(params, options) {
|
|
2043
|
+
const body = {
|
|
2044
|
+
...this.#defaults,
|
|
2045
|
+
...params
|
|
2046
|
+
};
|
|
2047
|
+
return this.call("POST", options, ({ signal, headers }) => createEmailMessage({
|
|
2048
|
+
client: this.client,
|
|
2049
|
+
body,
|
|
2050
|
+
headers,
|
|
2051
|
+
signal
|
|
2052
|
+
}));
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Send a batch of up to 100 independent email messages in one request. The
|
|
2056
|
+
* batch is validated as a unit — if any item fails validation (unverified
|
|
2057
|
+
* sender, all recipients suppressed, field-level errors) the whole batch is
|
|
2058
|
+
* rejected with a `BirdValidationError` and nothing is queued. Resolves with
|
|
2059
|
+
* one accepted item per submitted message, in submission order, once the batch
|
|
2060
|
+
* is accepted (the API's 202). Channel defaults are applied per item.
|
|
2061
|
+
*
|
|
2062
|
+
* @example Send a batch of messages
|
|
2063
|
+
* const batch = await bird.email.sendBatch([
|
|
2064
|
+
* {
|
|
2065
|
+
* from: { email: "onboarding@messagebird.dev", name: "Bird" },
|
|
2066
|
+
* to: ["alice@example.com"],
|
|
2067
|
+
* subject: "Your receipt",
|
|
2068
|
+
* html: "<p>Thanks, Alice.</p>",
|
|
2069
|
+
* },
|
|
2070
|
+
* {
|
|
2071
|
+
* from: { email: "onboarding@messagebird.dev", name: "Bird" },
|
|
2072
|
+
* to: ["bob@example.com"],
|
|
2073
|
+
* subject: "Your receipt",
|
|
2074
|
+
* html: "<p>Thanks, Bob.</p>",
|
|
2075
|
+
* },
|
|
2076
|
+
* ]);
|
|
2077
|
+
* for (const item of batch.data) console.log(item.id, item.status);
|
|
2078
|
+
*/
|
|
2079
|
+
sendBatch(params, options) {
|
|
2080
|
+
const body = params.map((item) => ({
|
|
2081
|
+
...this.#defaults,
|
|
2082
|
+
...item
|
|
2083
|
+
}));
|
|
2084
|
+
return this.call("POST", options, ({ signal, headers }) => createEmailMessageBatch({
|
|
2085
|
+
client: this.client,
|
|
2086
|
+
body,
|
|
2087
|
+
headers,
|
|
2088
|
+
signal
|
|
2089
|
+
}));
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Fetch a message with aggregate delivery status.
|
|
2093
|
+
*
|
|
2094
|
+
* @example
|
|
2095
|
+
* const msg = await bird.email.get("em_abc123");
|
|
2096
|
+
* msg.status; // "accepted" | "processed" | "delivered" | "bounced" | …
|
|
2097
|
+
* msg.delivered_count;
|
|
2098
|
+
* msg.bounced_count;
|
|
2099
|
+
*/
|
|
2100
|
+
get(messageId, options) {
|
|
2101
|
+
return this.call("GET", options, ({ signal, headers }) => getEmailMessage({
|
|
2102
|
+
client: this.client,
|
|
2103
|
+
path: { message_id: messageId },
|
|
2104
|
+
headers,
|
|
2105
|
+
signal
|
|
2106
|
+
}));
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* List messages, newest first. `await` resolves the first page; `for await`
|
|
2110
|
+
* walks every message across all pages.
|
|
2111
|
+
*
|
|
2112
|
+
* @example Iterate every message, or take one page
|
|
2113
|
+
* for await (const message of bird.email.list({ status: "bounced" })) {
|
|
2114
|
+
* console.log(message.id);
|
|
2115
|
+
* }
|
|
2116
|
+
* const page = await bird.email.list({ limit: 50 }); // page.data, page.next_cursor
|
|
2117
|
+
*/
|
|
2118
|
+
list(query, options) {
|
|
2119
|
+
return this.paginated("GET", options, ({ signal, headers }, cursor) => listEmailMessages({
|
|
2120
|
+
client: this.client,
|
|
2121
|
+
query: {
|
|
2122
|
+
...query,
|
|
2123
|
+
starting_after: cursor ?? query?.starting_after
|
|
2124
|
+
},
|
|
2125
|
+
headers,
|
|
2126
|
+
signal
|
|
2127
|
+
}));
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
//#endregion
|
|
2131
|
+
//#region src/resources/audiences.ts
|
|
2132
|
+
var AudiencesResource = class extends Resource {
|
|
2133
|
+
/**
|
|
2134
|
+
* Create an audience.
|
|
2135
|
+
*
|
|
2136
|
+
* @example Create an audience
|
|
2137
|
+
* const audience = await bird.audiences.create({ name: "Newsletter subscribers" });
|
|
2138
|
+
* console.log(audience.id); // "aud_…"
|
|
2139
|
+
*/
|
|
2140
|
+
create(params, options) {
|
|
2141
|
+
return this.call("POST", options, ({ signal, headers }) => createAudience({
|
|
2142
|
+
client: this.client,
|
|
2143
|
+
body: params,
|
|
2144
|
+
headers,
|
|
2145
|
+
signal
|
|
2146
|
+
}));
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* List the workspace's audiences, newest first. `await` resolves the first
|
|
2150
|
+
* page; `for await` walks every audience across pages.
|
|
2151
|
+
*
|
|
2152
|
+
* @example
|
|
2153
|
+
* for await (const audience of bird.audiences.list()) {
|
|
2154
|
+
* console.log(audience.id, audience.name);
|
|
2155
|
+
* }
|
|
2156
|
+
*/
|
|
2157
|
+
list(query, options) {
|
|
2158
|
+
return this.paginated("GET", options, ({ signal, headers }, cursor) => listAudiences({
|
|
2159
|
+
client: this.client,
|
|
2160
|
+
query: {
|
|
2161
|
+
...query,
|
|
2162
|
+
starting_after: cursor ?? query?.starting_after
|
|
2163
|
+
},
|
|
2164
|
+
headers,
|
|
2165
|
+
signal
|
|
2166
|
+
}));
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Fetch a single audience by id.
|
|
2170
|
+
*
|
|
2171
|
+
* @example
|
|
2172
|
+
* const audience = await bird.audiences.get("aud_01krdgeqcxet5s7t44vh8rt9mg");
|
|
2173
|
+
*/
|
|
2174
|
+
get(audienceId, options) {
|
|
2175
|
+
return this.call("GET", options, ({ signal, headers }) => getAudience({
|
|
2176
|
+
client: this.client,
|
|
2177
|
+
path: { audience_id: audienceId },
|
|
2178
|
+
headers,
|
|
2179
|
+
signal
|
|
2180
|
+
}));
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* Update an audience. Only the fields you send change.
|
|
2184
|
+
*
|
|
2185
|
+
* @example
|
|
2186
|
+
* await bird.audiences.update("aud_01krdgeqcxet5s7t44vh8rt9mg", { name: "Renamed" });
|
|
2187
|
+
*/
|
|
2188
|
+
update(audienceId, params, options) {
|
|
2189
|
+
return this.call("PATCH", options, ({ signal, headers }) => updateAudience({
|
|
2190
|
+
client: this.client,
|
|
2191
|
+
path: { audience_id: audienceId },
|
|
2192
|
+
body: params,
|
|
2193
|
+
headers,
|
|
2194
|
+
signal
|
|
2195
|
+
}));
|
|
2196
|
+
}
|
|
2197
|
+
/**
|
|
2198
|
+
* Delete an audience. Its contacts are unaffected.
|
|
2199
|
+
*
|
|
2200
|
+
* @example
|
|
2201
|
+
* await bird.audiences.delete("aud_01krdgeqcxet5s7t44vh8rt9mg");
|
|
2202
|
+
*/
|
|
2203
|
+
delete(audienceId, options) {
|
|
2204
|
+
return this.call("DELETE", options, ({ signal, headers }) => deleteAudience({
|
|
2205
|
+
client: this.client,
|
|
2206
|
+
path: { audience_id: audienceId },
|
|
2207
|
+
headers,
|
|
2208
|
+
signal
|
|
2209
|
+
}));
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* List the contacts in an audience, newest first. `await` resolves the first
|
|
2213
|
+
* page; `for await` walks every member across pages.
|
|
2214
|
+
*
|
|
2215
|
+
* @example
|
|
2216
|
+
* for await (const member of bird.audiences.listContacts("aud_01krdgeqcxet5s7t44vh8rt9mg")) {
|
|
2217
|
+
* console.log(member.contact.id, member.joined_at);
|
|
2218
|
+
* }
|
|
2219
|
+
*/
|
|
2220
|
+
listContacts(audienceId, query, options) {
|
|
2221
|
+
return this.paginated("GET", options, ({ signal, headers }, cursor) => listAudienceContacts({
|
|
2222
|
+
client: this.client,
|
|
2223
|
+
path: { audience_id: audienceId },
|
|
2224
|
+
query: {
|
|
2225
|
+
...query,
|
|
2226
|
+
starting_after: cursor ?? query?.starting_after
|
|
2227
|
+
},
|
|
2228
|
+
headers,
|
|
2229
|
+
signal
|
|
2230
|
+
}));
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Add contacts to an audience by id.
|
|
2234
|
+
*
|
|
2235
|
+
* @example
|
|
2236
|
+
* await bird.audiences.addContacts("aud_01krdgeqcxet5s7t44vh8rt9mg", {
|
|
2237
|
+
* contact_ids: ["con_1", "con_2"],
|
|
2238
|
+
* });
|
|
2239
|
+
*/
|
|
2240
|
+
addContacts(audienceId, params, options) {
|
|
2241
|
+
return this.call("POST", options, ({ signal, headers }) => assignAudienceContacts({
|
|
2242
|
+
client: this.client,
|
|
2243
|
+
path: { audience_id: audienceId },
|
|
2244
|
+
body: params,
|
|
2245
|
+
headers,
|
|
2246
|
+
signal
|
|
2247
|
+
}));
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Remove a set of contacts from an audience.
|
|
2251
|
+
*
|
|
2252
|
+
* @example
|
|
2253
|
+
* await bird.audiences.removeContacts("aud_01krdgeqcxet5s7t44vh8rt9mg", {
|
|
2254
|
+
* contact_ids: ["con_1", "con_2"],
|
|
2255
|
+
* });
|
|
2256
|
+
*/
|
|
2257
|
+
removeContacts(audienceId, params, options) {
|
|
2258
|
+
return this.call("POST", options, ({ signal, headers }) => unassignAudienceContacts({
|
|
2259
|
+
client: this.client,
|
|
2260
|
+
path: { audience_id: audienceId },
|
|
2261
|
+
body: params,
|
|
2262
|
+
headers,
|
|
2263
|
+
signal
|
|
2264
|
+
}));
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Remove a single contact from an audience.
|
|
2268
|
+
*
|
|
2269
|
+
* @example
|
|
2270
|
+
* await bird.audiences.removeContact("aud_01krdgeqcxet5s7t44vh8rt9mg", "con_1");
|
|
2271
|
+
*/
|
|
2272
|
+
removeContact(audienceId, contactId, options) {
|
|
2273
|
+
return this.call("DELETE", options, ({ signal, headers }) => unassignAudienceContact({
|
|
2274
|
+
client: this.client,
|
|
2275
|
+
path: {
|
|
2276
|
+
audience_id: audienceId,
|
|
2277
|
+
contact_id: contactId
|
|
2278
|
+
},
|
|
2279
|
+
headers,
|
|
2280
|
+
signal
|
|
2281
|
+
}));
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
//#endregion
|
|
2285
|
+
//#region src/resources/contactProperties.ts
|
|
2286
|
+
var ContactPropertiesResource = class extends Resource {
|
|
2287
|
+
/**
|
|
2288
|
+
* Define a contact property. The `key` must be unique in the workspace and is
|
|
2289
|
+
* how contacts reference the field in their `data`.
|
|
2290
|
+
*
|
|
2291
|
+
* @example
|
|
2292
|
+
* const prop = await bird.contactProperties.create({ key: "plan", type: "string" });
|
|
2293
|
+
* console.log(prop.id); // "cp_…"
|
|
2294
|
+
*/
|
|
2295
|
+
create(params, options) {
|
|
2296
|
+
return this.call("POST", options, ({ signal, headers }) => createContactProperty({
|
|
2297
|
+
client: this.client,
|
|
2298
|
+
body: params,
|
|
2299
|
+
headers,
|
|
2300
|
+
signal
|
|
2301
|
+
}));
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* List the workspace's contact properties. `await` resolves the first page;
|
|
2305
|
+
* `for await` walks every property across pages.
|
|
2306
|
+
*
|
|
2307
|
+
* @example
|
|
2308
|
+
* for await (const prop of bird.contactProperties.list()) {
|
|
2309
|
+
* console.log(prop.key, prop.type);
|
|
2310
|
+
* }
|
|
2311
|
+
*/
|
|
2312
|
+
list(query, options) {
|
|
2313
|
+
return this.paginated("GET", options, ({ signal, headers }, cursor) => listContactProperties({
|
|
2314
|
+
client: this.client,
|
|
2315
|
+
query: {
|
|
2316
|
+
...query,
|
|
2317
|
+
starting_after: cursor ?? query?.starting_after
|
|
2318
|
+
},
|
|
2319
|
+
headers,
|
|
2320
|
+
signal
|
|
2321
|
+
}));
|
|
2322
|
+
}
|
|
2323
|
+
/**
|
|
2324
|
+
* Fetch a single contact property by id.
|
|
2325
|
+
*
|
|
2326
|
+
* @example
|
|
2327
|
+
* const prop = await bird.contactProperties.get("cp_01krdgeqcxet5s7t44vh8rt9mg");
|
|
2328
|
+
*/
|
|
2329
|
+
get(propertyId, options) {
|
|
2330
|
+
return this.call("GET", options, ({ signal, headers }) => getContactProperty({
|
|
2331
|
+
client: this.client,
|
|
2332
|
+
path: { property_id: propertyId },
|
|
2333
|
+
headers,
|
|
2334
|
+
signal
|
|
2335
|
+
}));
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Update a contact property. Only the fields you send change.
|
|
2339
|
+
*
|
|
2340
|
+
* @example
|
|
2341
|
+
* await bird.contactProperties.update("cp_01krdgeqcxet5s7t44vh8rt9mg", { fallback_value: "free" });
|
|
2342
|
+
*/
|
|
2343
|
+
update(propertyId, params, options) {
|
|
2344
|
+
return this.call("PATCH", options, ({ signal, headers }) => updateContactProperty({
|
|
2345
|
+
client: this.client,
|
|
2346
|
+
path: { property_id: propertyId },
|
|
2347
|
+
body: params,
|
|
2348
|
+
headers,
|
|
2349
|
+
signal
|
|
2350
|
+
}));
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Archive a contact property, retiring the field without deleting its data.
|
|
2354
|
+
*
|
|
2355
|
+
* @example
|
|
2356
|
+
* await bird.contactProperties.archive("cp_01krdgeqcxet5s7t44vh8rt9mg");
|
|
2357
|
+
*/
|
|
2358
|
+
archive(propertyId, options) {
|
|
2359
|
+
return this.call("POST", options, ({ signal, headers }) => archiveContactProperty({
|
|
2360
|
+
client: this.client,
|
|
2361
|
+
path: { property_id: propertyId },
|
|
2362
|
+
headers,
|
|
2363
|
+
signal
|
|
2364
|
+
}));
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Restore an archived contact property.
|
|
2368
|
+
*
|
|
2369
|
+
* @example
|
|
2370
|
+
* await bird.contactProperties.unarchive("cp_01krdgeqcxet5s7t44vh8rt9mg");
|
|
2371
|
+
*/
|
|
2372
|
+
unarchive(propertyId, options) {
|
|
2373
|
+
return this.call("POST", options, ({ signal, headers }) => unarchiveContactProperty({
|
|
2374
|
+
client: this.client,
|
|
2375
|
+
path: { property_id: propertyId },
|
|
2376
|
+
headers,
|
|
2377
|
+
signal
|
|
2378
|
+
}));
|
|
2379
|
+
}
|
|
2380
|
+
};
|
|
2381
|
+
//#endregion
|
|
2382
|
+
//#region src/resources/contacts.ts
|
|
2383
|
+
var ContactsResource = class extends Resource {
|
|
2384
|
+
/**
|
|
2385
|
+
* Create a contact. `email` is required and unique within the workspace; set
|
|
2386
|
+
* custom fields via `data` (each key a property defined in contact properties).
|
|
2387
|
+
*
|
|
2388
|
+
* @example Create a contact
|
|
2389
|
+
* const contact = await bird.contacts.create({
|
|
2390
|
+
* email: "jane@acme.com",
|
|
2391
|
+
* first_name: "Jane",
|
|
2392
|
+
* });
|
|
2393
|
+
* console.log(contact.id); // "con_…"
|
|
2394
|
+
*/
|
|
2395
|
+
create(params, options) {
|
|
2396
|
+
return this.call("POST", options, ({ signal, headers }) => createContact({
|
|
2397
|
+
client: this.client,
|
|
2398
|
+
body: params,
|
|
2399
|
+
headers,
|
|
2400
|
+
signal
|
|
2401
|
+
}));
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* List the workspace's contacts, newest first. `await` resolves the first page;
|
|
2405
|
+
* `for await` walks every contact across pages. Filter by `email`,
|
|
2406
|
+
* `external_id`, or a `search` term.
|
|
2407
|
+
*
|
|
2408
|
+
* @example Iterate every contact, or take one page
|
|
2409
|
+
* for await (const contact of bird.contacts.list({ search: "acme.com" })) {
|
|
2410
|
+
* console.log(contact.id, contact.email);
|
|
2411
|
+
* }
|
|
2412
|
+
* const page = await bird.contacts.list({ limit: 50 }); // page.data, page.next_cursor
|
|
2413
|
+
*/
|
|
2414
|
+
list(query, options) {
|
|
2415
|
+
return this.paginated("GET", options, ({ signal, headers }, cursor) => listContacts({
|
|
2416
|
+
client: this.client,
|
|
2417
|
+
query: {
|
|
2418
|
+
...query,
|
|
2419
|
+
starting_after: cursor ?? query?.starting_after
|
|
2420
|
+
},
|
|
2421
|
+
headers,
|
|
2422
|
+
signal
|
|
2423
|
+
}));
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Fetch a single contact by id.
|
|
2427
|
+
*
|
|
2428
|
+
* @example
|
|
2429
|
+
* const contact = await bird.contacts.get("con_01krdgeqcxet5s7t44vh8rt9mg");
|
|
2430
|
+
* contact.email;
|
|
2431
|
+
*/
|
|
2432
|
+
get(contactId, options) {
|
|
2433
|
+
return this.call("GET", options, ({ signal, headers }) => getContact({
|
|
2434
|
+
client: this.client,
|
|
2435
|
+
path: { contact_id: contactId },
|
|
2436
|
+
headers,
|
|
2437
|
+
signal
|
|
2438
|
+
}));
|
|
2439
|
+
}
|
|
2440
|
+
/**
|
|
2441
|
+
* Update a contact. Only the fields you send change.
|
|
2442
|
+
*
|
|
2443
|
+
* @example
|
|
2444
|
+
* const contact = await bird.contacts.update("con_01krdgeqcxet5s7t44vh8rt9mg", {
|
|
2445
|
+
* first_name: "Jane",
|
|
2446
|
+
* });
|
|
2447
|
+
*/
|
|
2448
|
+
update(contactId, params, options) {
|
|
2449
|
+
return this.call("PATCH", options, ({ signal, headers }) => updateContact({
|
|
2450
|
+
client: this.client,
|
|
2451
|
+
path: { contact_id: contactId },
|
|
2452
|
+
body: params,
|
|
2453
|
+
headers,
|
|
2454
|
+
signal
|
|
2455
|
+
}));
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Delete a contact by id.
|
|
2459
|
+
*
|
|
2460
|
+
* @example
|
|
2461
|
+
* await bird.contacts.delete("con_01krdgeqcxet5s7t44vh8rt9mg");
|
|
2462
|
+
*/
|
|
2463
|
+
delete(contactId, options) {
|
|
2464
|
+
return this.call("DELETE", options, ({ signal, headers }) => deleteContact({
|
|
2465
|
+
client: this.client,
|
|
2466
|
+
path: { contact_id: contactId },
|
|
2467
|
+
headers,
|
|
2468
|
+
signal
|
|
2469
|
+
}));
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Create or update many contacts in one call, matched by email. Returns a
|
|
2473
|
+
* per-contact result.
|
|
2474
|
+
*
|
|
2475
|
+
* @example
|
|
2476
|
+
* const result = await bird.contacts.batch({
|
|
2477
|
+
* contacts: [{ email: "jane@acme.com", first_name: "Jane" }],
|
|
2478
|
+
* });
|
|
2479
|
+
*/
|
|
2480
|
+
batch(params, options) {
|
|
2481
|
+
return this.call("POST", options, ({ signal, headers }) => createContactBatch({
|
|
2482
|
+
client: this.client,
|
|
2483
|
+
body: params,
|
|
2484
|
+
headers,
|
|
2485
|
+
signal
|
|
2486
|
+
}));
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
//#endregion
|
|
2490
|
+
//#region src/resources/emailTemplates.ts
|
|
2491
|
+
var EmailTemplatesResource = class extends Resource {
|
|
2492
|
+
/**
|
|
2493
|
+
* Create a template and its initial editable draft. Pick the authoring format
|
|
2494
|
+
* with `source` (`liquid`, `handlebars`, or `html`); the name must be unique
|
|
2495
|
+
* in the workspace or the call throws a `BirdConflictError`.
|
|
2496
|
+
*
|
|
2497
|
+
* @example Create a template
|
|
2498
|
+
* const tpl = await bird.emailTemplates.create({
|
|
2499
|
+
* name: "welcome-email",
|
|
2500
|
+
* description: "Welcome",
|
|
2501
|
+
* category: "transactional",
|
|
2502
|
+
* source: "handlebars",
|
|
2503
|
+
* subject: "Welcome, {{ first_name }}!",
|
|
2504
|
+
* html: "<h1>Hi {{ first_name }}</h1>",
|
|
2505
|
+
* });
|
|
2506
|
+
* console.log(tpl.id, tpl.revision); // "emt_…", 0
|
|
2507
|
+
*/
|
|
2508
|
+
create(params, options) {
|
|
2509
|
+
return this.call("POST", options, ({ signal, headers }) => createEmailTemplate({
|
|
2510
|
+
client: this.client,
|
|
2511
|
+
body: params,
|
|
2512
|
+
headers,
|
|
2513
|
+
signal
|
|
2514
|
+
}));
|
|
2515
|
+
}
|
|
2516
|
+
/**
|
|
2517
|
+
* List the workspace's templates, newest first. `await` resolves the first
|
|
2518
|
+
* page; `for await` walks every template across all pages. Filter by
|
|
2519
|
+
* `category`, `source`, or a case-insensitive `name` prefix.
|
|
2520
|
+
*
|
|
2521
|
+
* @example Iterate every template, or take one page
|
|
2522
|
+
* for await (const tpl of bird.emailTemplates.list({ category: "transactional" })) {
|
|
2523
|
+
* console.log(tpl.id, tpl.name);
|
|
2524
|
+
* }
|
|
2525
|
+
* const page = await bird.emailTemplates.list({ limit: 50 }); // page.data, page.next_cursor
|
|
2526
|
+
*/
|
|
2527
|
+
list(query, options) {
|
|
2528
|
+
return this.paginated("GET", options, ({ signal, headers }, cursor) => listEmailTemplates({
|
|
2529
|
+
client: this.client,
|
|
2530
|
+
query: {
|
|
2531
|
+
...query,
|
|
2532
|
+
starting_after: cursor ?? query?.starting_after
|
|
2533
|
+
},
|
|
2534
|
+
headers,
|
|
2535
|
+
signal
|
|
2536
|
+
}));
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Fetch a template with its current draft content (subject, HTML, text), the
|
|
2540
|
+
* draft `revision`, and its draft/published version ids.
|
|
2541
|
+
*
|
|
2542
|
+
* @example
|
|
2543
|
+
* const tpl = await bird.emailTemplates.get("emt_abc123");
|
|
2544
|
+
* tpl.subject;
|
|
2545
|
+
* tpl.published_version_id; // null until first publish
|
|
2546
|
+
*/
|
|
2547
|
+
get(templateId, options) {
|
|
2548
|
+
return this.call("GET", options, ({ signal, headers }) => getEmailTemplate({
|
|
2549
|
+
client: this.client,
|
|
2550
|
+
path: { template_id: templateId },
|
|
2551
|
+
headers,
|
|
2552
|
+
signal
|
|
2553
|
+
}));
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Update a template's metadata and draft content. Only the fields you send
|
|
2557
|
+
* change. Pass the draft `revision` you last read; if another edit landed
|
|
2558
|
+
* first the call throws a `BirdConflictError` — reload and retry.
|
|
2559
|
+
*
|
|
2560
|
+
* @example Edit the draft, guarded by the revision you read
|
|
2561
|
+
* const tpl = await bird.emailTemplates.get("emt_abc123");
|
|
2562
|
+
* const updated = await bird.emailTemplates.update("emt_abc123", {
|
|
2563
|
+
* revision: tpl.revision,
|
|
2564
|
+
* subject: "Welcome aboard, {{ first_name }}!",
|
|
2565
|
+
* });
|
|
2566
|
+
*/
|
|
2567
|
+
update(templateId, params, options) {
|
|
2568
|
+
return this.call("PATCH", options, ({ signal, headers }) => updateEmailTemplate({
|
|
2569
|
+
client: this.client,
|
|
2570
|
+
path: { template_id: templateId },
|
|
2571
|
+
body: params,
|
|
2572
|
+
headers,
|
|
2573
|
+
signal
|
|
2574
|
+
}));
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Delete a template and all its versions. The name becomes available for
|
|
2578
|
+
* reuse in the workspace.
|
|
2579
|
+
*
|
|
2580
|
+
* @example
|
|
2581
|
+
* await bird.emailTemplates.delete("emt_abc123");
|
|
2582
|
+
*/
|
|
2583
|
+
delete(templateId, options) {
|
|
2584
|
+
return this.call("DELETE", options, ({ signal, headers }) => deleteEmailTemplate({
|
|
2585
|
+
client: this.client,
|
|
2586
|
+
path: { template_id: templateId },
|
|
2587
|
+
headers,
|
|
2588
|
+
signal
|
|
2589
|
+
}));
|
|
2590
|
+
}
|
|
2591
|
+
/**
|
|
2592
|
+
* Publish the current draft as a new immutable, numbered version and make it
|
|
2593
|
+
* the live version used by sends. The draft stays editable. The draft must
|
|
2594
|
+
* have a subject and a body, or the call throws.
|
|
2595
|
+
*
|
|
2596
|
+
* @example Publish, then send by template
|
|
2597
|
+
* const version = await bird.emailTemplates.publish("emt_abc123");
|
|
2598
|
+
* console.log(version.version_number); // 1, 2, 3…
|
|
2599
|
+
* await bird.email.send({
|
|
2600
|
+
* from: "hello@acme.com",
|
|
2601
|
+
* to: ["alice@example.com"],
|
|
2602
|
+
* template: { id: "emt_abc123", parameters: { first_name: "Alice" } },
|
|
2603
|
+
* });
|
|
2604
|
+
*/
|
|
2605
|
+
publish(templateId, options) {
|
|
2606
|
+
return this.call("POST", options, ({ signal, headers }) => publishEmailTemplate({
|
|
2607
|
+
client: this.client,
|
|
2608
|
+
path: { template_id: templateId },
|
|
2609
|
+
headers,
|
|
2610
|
+
signal
|
|
2611
|
+
}));
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* List every version of a template — the current draft plus all published
|
|
2615
|
+
* versions — newest first. Returns the full set in one response (`.data`);
|
|
2616
|
+
* this list is not paginated.
|
|
2617
|
+
*
|
|
2618
|
+
* @example
|
|
2619
|
+
* const { data } = await bird.emailTemplates.listVersions("emt_abc123");
|
|
2620
|
+
* for (const v of data) console.log(v.version_number, v.status);
|
|
2621
|
+
*/
|
|
2622
|
+
listVersions(templateId, options) {
|
|
2623
|
+
return this.call("GET", options, ({ signal, headers }) => listEmailTemplateVersions({
|
|
2624
|
+
client: this.client,
|
|
2625
|
+
path: { template_id: templateId },
|
|
2626
|
+
headers,
|
|
2627
|
+
signal
|
|
2628
|
+
}));
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* Fetch a single version of a template.
|
|
2632
|
+
*
|
|
2633
|
+
* @example
|
|
2634
|
+
* const version = await bird.emailTemplates.getVersion("emt_abc123", "emv_def456");
|
|
2635
|
+
* version.status; // "draft" | "published"
|
|
2636
|
+
*/
|
|
2637
|
+
getVersion(templateId, versionId, options) {
|
|
2638
|
+
return this.call("GET", options, ({ signal, headers }) => getEmailTemplateVersion({
|
|
2639
|
+
client: this.client,
|
|
2640
|
+
path: {
|
|
2641
|
+
template_id: templateId,
|
|
2642
|
+
version_id: versionId
|
|
2643
|
+
},
|
|
2644
|
+
headers,
|
|
2645
|
+
signal
|
|
2646
|
+
}));
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
//#endregion
|
|
2650
|
+
//#region src/resources/sms.ts
|
|
2651
|
+
var SmsResource = class extends Resource {
|
|
2652
|
+
/**
|
|
2653
|
+
* Send one SMS to a single recipient. Supply either `text` (with a `category`)
|
|
2654
|
+
* or a stored `template` (by `id` or `name`, with its `parameters`). The
|
|
2655
|
+
* result is `accepted`, not yet delivered — read it back with `get` to confirm.
|
|
2656
|
+
*
|
|
2657
|
+
* @example Send free text
|
|
2658
|
+
* const msg = await bird.sms.send({
|
|
2659
|
+
* to: "+15551234567",
|
|
2660
|
+
* text: "Your verification code is 123456.",
|
|
2661
|
+
* category: "authentication",
|
|
2662
|
+
* });
|
|
2663
|
+
* console.log(msg.id, msg.status);
|
|
2664
|
+
*
|
|
2665
|
+
* @example Send by template
|
|
2666
|
+
* await bird.sms.send({
|
|
2667
|
+
* to: "+15551234567",
|
|
2668
|
+
* template: { name: "bird_otp_verification", parameters: { code: "123456" } },
|
|
2669
|
+
* });
|
|
2670
|
+
*/
|
|
2671
|
+
send(params, options) {
|
|
2672
|
+
return this.call("POST", options, ({ signal, headers }) => createSmsMessage({
|
|
2673
|
+
client: this.client,
|
|
2674
|
+
body: params,
|
|
2675
|
+
headers,
|
|
2676
|
+
signal
|
|
2677
|
+
}));
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Send up to 100 independent SMS messages in one call. Each item is a full send
|
|
2681
|
+
* (free text or template); all items are validated before any are queued.
|
|
2682
|
+
*
|
|
2683
|
+
* @example
|
|
2684
|
+
* const result = await bird.sms.sendBatch([
|
|
2685
|
+
* { to: "+15551111111", text: "Hi Alice!", category: "marketing" },
|
|
2686
|
+
* { to: "+15552222222", text: "Hi Bob!", category: "marketing" },
|
|
2687
|
+
* ]);
|
|
2688
|
+
*/
|
|
2689
|
+
sendBatch(params, options) {
|
|
2690
|
+
return this.call("POST", options, ({ signal, headers }) => createSmsMessageBatch({
|
|
2691
|
+
client: this.client,
|
|
2692
|
+
body: params,
|
|
2693
|
+
headers,
|
|
2694
|
+
signal
|
|
2695
|
+
}));
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Fetch a single SMS message: its current delivery status, segment breakdown,
|
|
2699
|
+
* cost, and failure detail if it failed.
|
|
2700
|
+
*
|
|
2701
|
+
* @example
|
|
2702
|
+
* const msg = await bird.sms.get("sms_abc123");
|
|
2703
|
+
* msg.status; // "accepted" | "delivered" | …
|
|
2704
|
+
*/
|
|
2705
|
+
get(messageId, options) {
|
|
2706
|
+
return this.call("GET", options, ({ signal, headers }) => getSmsMessage({
|
|
2707
|
+
client: this.client,
|
|
2708
|
+
path: { message_id: messageId },
|
|
2709
|
+
headers,
|
|
2710
|
+
signal
|
|
2711
|
+
}));
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* List SMS messages, newest first. `await` resolves the first page; `for await`
|
|
2715
|
+
* walks every message across all pages. Filter by direction, status, category,
|
|
2716
|
+
* recipient, sender, or tag.
|
|
2717
|
+
*
|
|
2718
|
+
* @example
|
|
2719
|
+
* for await (const msg of bird.sms.list({ direction: "outbound" })) {
|
|
2720
|
+
* console.log(msg.id, msg.status);
|
|
2721
|
+
* }
|
|
2722
|
+
*/
|
|
2723
|
+
list(query, options) {
|
|
2724
|
+
return this.paginated("GET", options, ({ signal, headers }, cursor) => listSmsMessages({
|
|
2725
|
+
client: this.client,
|
|
2726
|
+
query: {
|
|
2727
|
+
...query,
|
|
2728
|
+
starting_after: cursor ?? query?.starting_after
|
|
2729
|
+
},
|
|
2730
|
+
headers,
|
|
2731
|
+
signal
|
|
2732
|
+
}));
|
|
2733
|
+
}
|
|
2734
|
+
};
|
|
2735
|
+
//#endregion
|
|
2736
|
+
//#region src/resources/smsTemplates.ts
|
|
2737
|
+
var SmsTemplatesResource = class extends Resource {
|
|
2738
|
+
/**
|
|
2739
|
+
* List the SMS templates available to the workspace — Bird's built-in
|
|
2740
|
+
* templates plus any the workspace authored. The catalogue is small and
|
|
2741
|
+
* returned in full (`.data`); this list is not paginated. Filter by `scope`,
|
|
2742
|
+
* `category`, or `language` (a BCP-47 language tag).
|
|
2743
|
+
*
|
|
2744
|
+
* @example List the built-in templates
|
|
2745
|
+
* const { data } = await bird.smsTemplates.list({ scope: "system" });
|
|
2746
|
+
* for (const tpl of data) console.log(tpl.id, tpl.name);
|
|
2747
|
+
*/
|
|
2748
|
+
list(query, options) {
|
|
2749
|
+
return this.call("GET", options, ({ signal, headers }) => listSmsTemplates({
|
|
2750
|
+
client: this.client,
|
|
2751
|
+
query,
|
|
2752
|
+
headers,
|
|
2753
|
+
signal
|
|
2754
|
+
}));
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Fetch a single SMS template by its name or id, including its body and the
|
|
2758
|
+
* variables it expects.
|
|
2759
|
+
*
|
|
2760
|
+
* @example
|
|
2761
|
+
* const tpl = await bird.smsTemplates.get("bird_otp_verification");
|
|
2762
|
+
* console.log(tpl.body, tpl.variables);
|
|
2763
|
+
*/
|
|
2764
|
+
get(templateRef, options) {
|
|
2765
|
+
return this.call("GET", options, ({ signal, headers }) => getSmsTemplate({
|
|
2766
|
+
client: this.client,
|
|
2767
|
+
path: { template_ref: templateRef },
|
|
2768
|
+
headers,
|
|
2769
|
+
signal
|
|
2770
|
+
}));
|
|
2771
|
+
}
|
|
2772
|
+
};
|
|
2773
|
+
//#endregion
|
|
2774
|
+
//#region src/resources/webhooks.ts
|
|
2775
|
+
var WebhooksResource = class {
|
|
2776
|
+
#secret;
|
|
2777
|
+
constructor(config) {
|
|
2778
|
+
this.#secret = config?.secret;
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Verify a webhook delivery and return the typed event.
|
|
2782
|
+
*
|
|
2783
|
+
* **Pass the raw request body**, exactly as received — do NOT parse it first.
|
|
2784
|
+
* The Standard Webhooks signature is computed over the raw bytes, so parsing
|
|
2785
|
+
* and re-serializing before verifying is the classic webhook bug.
|
|
2786
|
+
*
|
|
2787
|
+
* The secret comes from `webhooks.secret` on the client; pass `{ secret }` to
|
|
2788
|
+
* override per call. Throws {@link BirdWebhookVerificationError} on a bad
|
|
2789
|
+
* signature, a stale timestamp, or missing/malformed headers. Unknown event
|
|
2790
|
+
* types are returned as-is (handle them in a `default` case) so a newer server
|
|
2791
|
+
* event can't break an older SDK.
|
|
2792
|
+
*
|
|
2793
|
+
* @example One call verifies the signature and returns the typed event
|
|
2794
|
+
* // Pass the RAW request body; set the secret via new BirdClient({ webhooks: { secret } }).
|
|
2795
|
+
* const event = bird.webhooks.unwrap(rawBody, headers);
|
|
2796
|
+
* console.log(event.type); // discriminated union — narrow on event.type
|
|
2797
|
+
*
|
|
2798
|
+
* @example Verify and dispatch — pass the raw request body, never the parsed JSON
|
|
2799
|
+
* // new BirdClient({ apiKey, webhooks: { secret } })
|
|
2800
|
+
* try {
|
|
2801
|
+
* const event = bird.webhooks.unwrap(rawBody, req.headers);
|
|
2802
|
+
* switch (event.type) {
|
|
2803
|
+
* case "email.delivered":
|
|
2804
|
+
* markDelivered(event.email_id, event.recipient); // narrowed; fields are flat
|
|
2805
|
+
* break;
|
|
2806
|
+
* case "email.bounced":
|
|
2807
|
+
* case "email.complained":
|
|
2808
|
+
* suppress(event.recipient);
|
|
2809
|
+
* break;
|
|
2810
|
+
* default: // unknown future event types — an older SDK won't break on a new one
|
|
2811
|
+
* }
|
|
2812
|
+
* } catch (err) {
|
|
2813
|
+
* if (err instanceof BirdWebhookVerificationError) {
|
|
2814
|
+
* // reject with 400 — bad signature, stale timestamp, or missing/malformed headers
|
|
2815
|
+
* } else throw err;
|
|
2816
|
+
* }
|
|
2817
|
+
*/
|
|
2818
|
+
unwrap(payload, headers, options) {
|
|
2819
|
+
const secret = options?.secret ?? this.#secret;
|
|
2820
|
+
if (!secret) throw new Error("No webhook secret. Set `webhooks: { secret }` on the client, or pass `{ secret }` to unwrap.");
|
|
2821
|
+
const wh = new Webhook(secret);
|
|
2822
|
+
let verified;
|
|
2823
|
+
try {
|
|
2824
|
+
verified = wh.verify(payload, toHeaderRecord(headers));
|
|
2825
|
+
} catch (err) {
|
|
2826
|
+
throw new BirdWebhookVerificationError(err instanceof Error ? err.message : "Webhook signature verification failed");
|
|
2827
|
+
}
|
|
2828
|
+
return verified;
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2831
|
+
function toHeaderRecord(headers) {
|
|
2832
|
+
return headers instanceof Headers ? Object.fromEntries(headers) : headers;
|
|
2833
|
+
}
|
|
2834
|
+
//#endregion
|
|
2835
|
+
//#region src/client.ts
|
|
2836
|
+
const DEFAULT_TIMEOUT_MS = 6e4;
|
|
2837
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
2838
|
+
function resolveBaseUrl(options) {
|
|
2839
|
+
if (options.baseUrl) return options.baseUrl;
|
|
2840
|
+
const region = options.region ?? regionFromApiKey(options.apiKey);
|
|
2841
|
+
if (!region) throw new Error("Unable to determine region: API key is not in the expected bk_{region}_{token} format. Pass an explicit `region` or `baseUrl`.");
|
|
2842
|
+
return baseUrlForRegion(region);
|
|
2843
|
+
}
|
|
2844
|
+
function resolveRawRequestUrl(baseUrl, path) {
|
|
2845
|
+
if (!path.startsWith("/") || path.startsWith("//")) throw new TypeError("bird.request path must be an absolute path starting with a single `/`");
|
|
2846
|
+
const base = new URL(baseUrl);
|
|
2847
|
+
const url = new URL(baseUrl + path);
|
|
2848
|
+
if (url.origin !== base.origin) throw new TypeError("bird.request path must stay on the configured Bird API origin");
|
|
2849
|
+
return url;
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* The Bird API client. Construct it with an API key; the region is taken from
|
|
2853
|
+
* the key's prefix (`bk_{region}_…`) — pass `baseUrl` or `region` to override.
|
|
2854
|
+
*
|
|
2855
|
+
* @example Construct and send
|
|
2856
|
+
* const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });
|
|
2857
|
+
* const msg = await bird.email.send({
|
|
2858
|
+
* from: "hello@acme.com",
|
|
2859
|
+
* to: ["customer@example.com"],
|
|
2860
|
+
* subject: "Welcome aboard",
|
|
2861
|
+
* html: "<h1>Hi there 👋</h1>",
|
|
2862
|
+
* });
|
|
2863
|
+
* console.log(msg.id);
|
|
2864
|
+
*
|
|
2865
|
+
* @example Channel defaults — set common send fields once; a per-send value always wins
|
|
2866
|
+
* const bird = new BirdClient({
|
|
2867
|
+
* apiKey: process.env.BIRD_API_KEY!,
|
|
2868
|
+
* email: { from: "hello@acme.com", category: "transactional" },
|
|
2869
|
+
* });
|
|
2870
|
+
* // `from` and `category` are filled from the defaults; both stay optional in `send`.
|
|
2871
|
+
* await bird.email.send({ to: ["customer@example.com"], subject: "Hi", html: "<p>hi</p>" });
|
|
2872
|
+
*
|
|
2873
|
+
* @example All client options
|
|
2874
|
+
* const bird = new BirdClient({
|
|
2875
|
+
* apiKey: process.env.BIRD_API_KEY!,
|
|
2876
|
+
* region: "eu1", // optional — override the region from the key prefix
|
|
2877
|
+
* baseUrl: "http://localhost:8080", // optional — overrides region entirely (local/self-hosted)
|
|
2878
|
+
* timeout: 60_000, // per-attempt timeout in ms (default 60_000)
|
|
2879
|
+
* maxRetries: 2, // retry budget for transient failures (default 2)
|
|
2880
|
+
* });
|
|
2881
|
+
*/
|
|
2882
|
+
var BirdClient = class {
|
|
2883
|
+
core;
|
|
2884
|
+
#client;
|
|
2885
|
+
#baseUrl;
|
|
2886
|
+
#fetch;
|
|
2887
|
+
#headers;
|
|
2888
|
+
/** The email channel — `bird.email.send(...)`, `.get(...)`, `.list(...)`. */
|
|
2889
|
+
email;
|
|
2890
|
+
/** Email templates — `bird.emailTemplates.create(...)`, `.list(...)`, `.publish(...)`, … */
|
|
2891
|
+
emailTemplates;
|
|
2892
|
+
/** The SMS channel — `bird.sms.send(...)`, `.get(...)`, `.list(...)`. */
|
|
2893
|
+
sms;
|
|
2894
|
+
/** SMS templates — `bird.smsTemplates.list(...)`, `.get(...)`. */
|
|
2895
|
+
smsTemplates;
|
|
2896
|
+
/** Contacts — `bird.contacts.create(...)`, `.list(...)`, `.get(...)`, `.batch(...)`, … */
|
|
2897
|
+
contacts;
|
|
2898
|
+
/** Audiences — `bird.audiences.create(...)`, `.list(...)`, `.addContacts(...)`, … */
|
|
2899
|
+
audiences;
|
|
2900
|
+
/** Contact properties — `bird.contactProperties.create(...)`, `.list(...)`, `.archive(...)`, … */
|
|
2901
|
+
contactProperties;
|
|
2902
|
+
/** Webhooks — `bird.webhooks.unwrap(payload, headers)` verifies an inbound delivery. */
|
|
2903
|
+
webhooks;
|
|
2904
|
+
constructor(options) {
|
|
2905
|
+
const opts = options;
|
|
2906
|
+
this.#baseUrl = resolveBaseUrl(opts);
|
|
2907
|
+
this.#fetch = opts.fetch ?? fetch;
|
|
2908
|
+
this.#headers = {
|
|
2909
|
+
...opts.defaultHeaders,
|
|
2910
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
2911
|
+
"User-Agent": `bird-sdk-js/0.4.1`,
|
|
2912
|
+
"Bird-Surface": "sdk-js",
|
|
2913
|
+
"Bird-Version": "0.4.1"
|
|
2914
|
+
};
|
|
2915
|
+
const caller = detectCaller();
|
|
2916
|
+
if (caller) this.#headers["Bird-Caller"] = caller;
|
|
2917
|
+
this.#client = createClient(createConfig({
|
|
2918
|
+
baseUrl: this.#baseUrl,
|
|
2919
|
+
fetch: this.#fetch,
|
|
2920
|
+
headers: this.#headers
|
|
2921
|
+
}));
|
|
2922
|
+
this.core = new BirdHTTPClient({
|
|
2923
|
+
timeout: opts.timeout ?? DEFAULT_TIMEOUT_MS,
|
|
2924
|
+
maxRetries: opts.maxRetries ?? DEFAULT_MAX_RETRIES
|
|
2925
|
+
});
|
|
2926
|
+
this.email = new EmailResource(this.core, this.#client, opts.email);
|
|
2927
|
+
this.emailTemplates = new EmailTemplatesResource(this.core, this.#client);
|
|
2928
|
+
this.sms = new SmsResource(this.core, this.#client);
|
|
2929
|
+
this.smsTemplates = new SmsTemplatesResource(this.core, this.#client);
|
|
2930
|
+
this.contacts = new ContactsResource(this.core, this.#client);
|
|
2931
|
+
this.audiences = new AudiencesResource(this.core, this.#client);
|
|
2932
|
+
this.contactProperties = new ContactPropertiesResource(this.core, this.#client);
|
|
2933
|
+
this.webhooks = new WebhooksResource(opts.webhooks);
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Escape hatch for endpoints the typed resources don't cover. Runs the full
|
|
2937
|
+
* lifecycle (auth, retries, idempotency, error mapping); you supply the
|
|
2938
|
+
* response type. Prefer a typed resource method where one exists.
|
|
2939
|
+
*
|
|
2940
|
+
* @throws {TypeError} if `req.path` does not start with exactly one `/` or
|
|
2941
|
+
* resolves to a different origin than the configured Bird API base URL.
|
|
2942
|
+
*
|
|
2943
|
+
* @example Reach an endpoint outside the curated surface — you supply the response type
|
|
2944
|
+
* type Suppressions = { data: Array<{ recipient: string }> };
|
|
2945
|
+
* const suppressions = await bird.request<Suppressions>({ method: "GET", path: "/v1/email/suppressions" });
|
|
2946
|
+
* console.log(suppressions.data.length);
|
|
2947
|
+
*/
|
|
2948
|
+
request(req, options) {
|
|
2949
|
+
const url = resolveRawRequestUrl(this.#baseUrl, req.path);
|
|
2950
|
+
return apiPromise(this.core.request((ctx) => this.#raw(url, req, ctx, options?.headers), {
|
|
2951
|
+
method: req.method,
|
|
2952
|
+
idempotencyKey: options?.idempotencyKey,
|
|
2953
|
+
signal: options?.signal,
|
|
2954
|
+
timeout: options?.timeout,
|
|
2955
|
+
maxRetries: options?.maxRetries
|
|
2956
|
+
}));
|
|
2957
|
+
}
|
|
2958
|
+
async #raw(url, req, ctx, extraHeaders) {
|
|
2959
|
+
url = new URL(url);
|
|
2960
|
+
if (req.query) {
|
|
2961
|
+
for (const [key, value] of Object.entries(req.query)) if (value !== void 0) url.searchParams.set(key, String(value));
|
|
2962
|
+
}
|
|
2963
|
+
const headers = {
|
|
2964
|
+
...extraHeaders,
|
|
2965
|
+
...this.#headers
|
|
2966
|
+
};
|
|
2967
|
+
if (ctx.idempotencyKey) headers["Idempotency-Key"] = ctx.idempotencyKey;
|
|
2968
|
+
if (req.body !== void 0) headers["Content-Type"] = "application/json";
|
|
2969
|
+
const response = await this.#fetch(url, {
|
|
2970
|
+
method: req.method,
|
|
2971
|
+
headers,
|
|
2972
|
+
body: req.body !== void 0 ? JSON.stringify(req.body) : void 0,
|
|
2973
|
+
signal: ctx.signal
|
|
2974
|
+
});
|
|
2975
|
+
if (response.ok) return {
|
|
2976
|
+
data: response.status === 204 ? void 0 : await response.json().catch(() => void 0),
|
|
2977
|
+
response
|
|
2978
|
+
};
|
|
2979
|
+
return {
|
|
2980
|
+
error: await response.clone().json().catch(() => void 0),
|
|
2981
|
+
response
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
//#endregion
|
|
2986
|
+
//#region src/event-types.gen.ts
|
|
2987
|
+
/**
|
|
2988
|
+
* Webhook event types known at this SDK version. The wire value is an open
|
|
2989
|
+
* string: a value added by a newer server is returned by `unwrap` unchanged,
|
|
2990
|
+
* so switch on these with a `default` branch.
|
|
2991
|
+
*/
|
|
2992
|
+
const WebhookEventType = {
|
|
2993
|
+
DomainFailed: "domain.failed",
|
|
2994
|
+
DomainVerified: "domain.verified",
|
|
2995
|
+
EmailAccepted: "email.accepted",
|
|
2996
|
+
EmailBounced: "email.bounced",
|
|
2997
|
+
EmailCanceled: "email.canceled",
|
|
2998
|
+
EmailClicked: "email.clicked",
|
|
2999
|
+
EmailComplained: "email.complained",
|
|
3000
|
+
EmailDeferred: "email.deferred",
|
|
3001
|
+
EmailDelivered: "email.delivered",
|
|
3002
|
+
EmailListUnsubscribed: "email.list_unsubscribed",
|
|
3003
|
+
EmailMailboxMessageDelivered: "email_mailbox.message_delivered",
|
|
3004
|
+
EmailMailboxMessageFailed: "email_mailbox.message_failed",
|
|
3005
|
+
EmailMailboxMessageReceived: "email_mailbox.message_received",
|
|
3006
|
+
EmailMailboxMessageReceivedBlocked: "email_mailbox.message_received_blocked",
|
|
3007
|
+
EmailMailboxMessageReceivedUnauthenticated: "email_mailbox.message_received_unauthenticated",
|
|
3008
|
+
EmailMailboxMessageSent: "email_mailbox.message_sent",
|
|
3009
|
+
EmailMailboxSuspended: "email_mailbox.suspended",
|
|
3010
|
+
EmailMailboxThreadCreated: "email_mailbox.thread_created",
|
|
3011
|
+
EmailOpened: "email.opened",
|
|
3012
|
+
EmailOutOfBandBounce: "email.out_of_band_bounce",
|
|
3013
|
+
EmailProcessed: "email.processed",
|
|
3014
|
+
EmailReceived: "email.received",
|
|
3015
|
+
EmailRejected: "email.rejected",
|
|
3016
|
+
EmailScheduled: "email.scheduled",
|
|
3017
|
+
EmailSuppressionCreated: "email_suppression.created",
|
|
3018
|
+
EmailUnsubscribed: "email.unsubscribed",
|
|
3019
|
+
SmsAccepted: "sms.accepted",
|
|
3020
|
+
SmsDelivered: "sms.delivered",
|
|
3021
|
+
SmsExpired: "sms.expired",
|
|
3022
|
+
SmsFailed: "sms.failed",
|
|
3023
|
+
SmsRejected: "sms.rejected",
|
|
3024
|
+
SmsSent: "sms.sent",
|
|
3025
|
+
SmsUndelivered: "sms.undelivered"
|
|
3026
|
+
};
|
|
3027
|
+
//#endregion
|
|
3028
|
+
export { BirdAPIError, BirdAuthError, BirdBadRequestError, BirdBillingError, BirdClient, BirdConflictError, BirdConnectionError, BirdError, BirdInternalError, BirdMisdirectedError, BirdNotFoundError, BirdNotImplementedError, BirdPayloadTooLargeError, BirdPermissionError, BirdPreconditionError, BirdRateLimitError, BirdServiceUnavailableError, BirdTimeoutError, BirdValidationError, BirdWebhookVerificationError, WebhookEventType, baseUrlForRegion, regionFromApiKey };
|
|
3029
|
+
|
|
3030
|
+
//# sourceMappingURL=index.mjs.map
|