@textbee/sdk 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,7 +1,15 @@
1
1
  interface TextbeeOptions {
2
2
  /** API key from the textbee dashboard. Sent as the `x-api-key` header. */
3
3
  apiKey: string;
4
- /** Override the API base URL. Defaults to `https://api.textbee.dev/api/v1`. */
4
+ /**
5
+ * Your self-hosted textbee API. Defaults to `https://api.textbee.dev/api/v1`.
6
+ *
7
+ * A bare origin such as `https://sms.example.com` gets `/api/v1` added. A URL
8
+ * that contains `/api/v1` is cut there, so a pasted endpoint URL still works.
9
+ * Any other path is used as given, for proxies that map a prefix onto the API.
10
+ * A value with no scheme gets `https://`. An invalid URL throws a `TypeError`
11
+ * rather than falling back, so a typo never sends your key to the public API.
12
+ */
5
13
  baseUrl?: string;
6
14
  }
7
15
  interface SendSmsRequest {
@@ -33,20 +41,32 @@ interface SendSmsRequest {
33
41
  /**
34
42
  * Send later instead of now, as an ISO 8601 string or a `Date`.
35
43
  *
36
- * Must be in the future, and up to 72 hours ahead. Omit for an immediate send.
44
+ * Must be in the future. Omit for an immediate send.
37
45
  */
38
46
  scheduledAt?: string | Date;
39
47
  }
48
+ /** A reason the send will probably fail, such as `SMS_PERMISSION_MISSING`. */
49
+ interface SendSmsWarning {
50
+ code: string;
51
+ message: string;
52
+ }
40
53
  /** Returned when the message was accepted for delivery and queued. */
41
54
  interface SendSmsQueuedResponse {
42
55
  success: true;
43
56
  message: string;
44
57
  smsBatchId: string;
45
58
  recipientCount: number;
59
+ /** When a large send, spread over several waves, should finish. */
60
+ estimatedCompletionAt?: string;
61
+ /** Present when the phone last reported a problem, such as the SMS permission off. */
62
+ warning?: SendSmsWarning;
46
63
  }
47
64
  /** Returned when the message was dispatched to the device immediately. */
48
65
  interface SendSmsImmediateResponse {
49
66
  successCount: number;
67
+ failureCount?: number;
68
+ /** Present when the phone last reported a problem, such as the SMS permission off. */
69
+ warning?: SendSmsWarning;
50
70
  [key: string]: unknown;
51
71
  }
52
72
  /**
@@ -110,6 +130,7 @@ interface Device {
110
130
  }
111
131
  interface MessageDevice {
112
132
  _id: string;
133
+ name?: string;
113
134
  brand?: string;
114
135
  model?: string;
115
136
  buildId?: string;
@@ -129,7 +150,7 @@ interface Message {
129
150
  direction?: 'sent' | 'received';
130
151
  /** Message channel. Currently always `sms`; absent means `sms`. */
131
152
  channel?: string;
132
- /** The sending or receiving device, populated by the history endpoints. */
153
+ /** The sending or receiving device. */
133
154
  device?: MessageDevice;
134
155
  /** Lowercase, and absent on messages stored before status tracking. */
135
156
  status?: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
@@ -148,16 +169,35 @@ interface Message {
148
169
  createdAt?: string;
149
170
  updatedAt?: string;
150
171
  }
172
+ /**
173
+ * A send batch. The status and counts are calculated from the batch's
174
+ * messages, so they change as each message reports.
175
+ */
151
176
  interface SmsBatch {
152
177
  _id: string;
153
178
  message: string;
154
179
  recipientCount: number;
155
180
  recipientPreview?: string;
181
+ /** Messages sent or delivered: `sentCount + deliveredCount`. */
156
182
  successCount: number;
183
+ /** Messages that failed. */
157
184
  failureCount: number;
185
+ /** Messages not yet handed to the phone. */
186
+ pendingCount?: number;
187
+ /** Messages handed to the phone with no result yet. */
188
+ dispatchedCount?: number;
189
+ /** Messages sent with no delivery report yet. */
190
+ sentCount?: number;
191
+ /** Messages with a delivery report. */
192
+ deliveredCount?: number;
193
+ /** Messages with no report from the phone within 20 minutes. */
194
+ unknownCount?: number;
158
195
  status: 'pending' | 'processing' | 'completed' | 'partial_success' | 'failed' | 'unknown';
159
196
  error?: string;
197
+ /** When the batch reached its current final status. Absent while processing. */
160
198
  completedAt?: string;
199
+ /** When the status and counts were last calculated. */
200
+ statusCheckedAt?: string;
161
201
  createdAt?: string;
162
202
  updatedAt?: string;
163
203
  }
@@ -175,13 +215,21 @@ interface MessageListMeta {
175
215
  nextCursor?: string | null;
176
216
  hasMore?: boolean;
177
217
  }
218
+ /**
219
+ * A message as `getSms` and `getSmsBatch` return it: `device` is the id
220
+ * string, and `direction` is absent (read `type`).
221
+ */
222
+ interface Sms extends Omit<Message, 'device' | 'direction'> {
223
+ /** The id of the sending or receiving device. */
224
+ device?: string;
225
+ }
178
226
  interface MessageList {
179
227
  data: Message[];
180
228
  meta: MessageListMeta;
181
229
  }
182
230
  interface SmsBatchResult {
183
231
  batch: SmsBatch;
184
- messages: Message[];
232
+ messages: Sms[];
185
233
  }
186
234
  /** Event names textbee sends in a webhook payload. */
187
235
  type WebhookEvent = 'MESSAGE_RECEIVED' | 'MESSAGE_SENT' | 'MESSAGE_DELIVERED' | 'MESSAGE_FAILED' | 'UNKNOWN_STATE' | 'SMS_STATUS_UPDATED';
@@ -204,9 +252,10 @@ declare class Textbee {
204
252
  getMessages(options?: ListMessagesOptions): Promise<MessageList>;
205
253
  /**
206
254
  * Iterate every message matching the filters, following the pagination
207
- * cursor until the end. Use `order: 'asc'` with a `from` bound to drain
208
- * forward when polling; keep the last message's `createdAt` (or track
209
- * `meta.nextCursor` via `getMessages`) to resume the next poll.
255
+ * cursor until the end. `limit` is the page size, not a cap on the total.
256
+ * Use `order: 'asc'` with a `from` bound to drain forward when polling.
257
+ * `from` is inclusive, so a poll that resumes from the last `createdAt` sees
258
+ * that message again; skip ids you have already handled.
210
259
  *
211
260
  * ```ts
212
261
  * for await (const message of textbee.iterateMessages({ direction: 'received' })) {
@@ -216,8 +265,12 @@ declare class Textbee {
216
265
  */
217
266
  iterateMessages(options?: IterateMessagesOptions): AsyncGenerator<Message, void, undefined>;
218
267
  /** Fetch a single message, including its delivery status. */
219
- getSms(deviceId: string, smsId: string): Promise<Message>;
220
- /** Fetch a batch and every message in it. Pair this with `smsBatchId` from a send. */
268
+ getSms(deviceId: string, smsId: string): Promise<Sms>;
269
+ /**
270
+ * Fetch a batch and every message in it, by the `smsBatchId` a send returns.
271
+ * Needs the id of the device that sent the batch. When textbee picked the
272
+ * device, use `getMessages({ smsBatchId })` instead.
273
+ */
221
274
  getSmsBatch(deviceId: string, smsBatchId: string): Promise<SmsBatchResult>;
222
275
  }
223
276
 
@@ -234,6 +287,97 @@ declare class TextbeeError extends Error {
234
287
  constructor(status: number, message: string, body?: unknown);
235
288
  }
236
289
 
290
+ /**
291
+ * Pure SMS helpers. No API key, no network, no dependencies. They work with
292
+ * any SMS provider, not just textbee.
293
+ */
294
+ /** How the message body will be encoded on the wire. */
295
+ type SmsEncoding = 'gsm-7' | 'ucs-2';
296
+ interface SmsSegmentInfo {
297
+ /** `gsm-7` when every character fits the GSM 03.38 alphabet, else `ucs-2`. */
298
+ encoding: SmsEncoding;
299
+ /**
300
+ * Encoded length: septets for gsm-7 (extension characters cost 2), UTF-16
301
+ * code units for ucs-2 (emoji and other astral characters cost 2).
302
+ */
303
+ length: number;
304
+ /** Number of SMS segments the message occupies. 0 for an empty string. */
305
+ segments: number;
306
+ /**
307
+ * How many more single-unit characters fit before another segment is needed.
308
+ * A two-unit character (extension character or emoji) may not fit even when
309
+ * this is 1.
310
+ */
311
+ remainingInSegment: number;
312
+ }
313
+ /**
314
+ * Detect the encoding a message requires: `gsm-7` when every character is in
315
+ * the GSM 03.38 alphabet or its extension table, `ucs-2` otherwise. An empty
316
+ * string reports `gsm-7`.
317
+ */
318
+ declare function getSmsEncoding(message: string): SmsEncoding;
319
+ /**
320
+ * Compute encoding, encoded length, segment count, and remaining room the way
321
+ * a standards-following handset splits a message.
322
+ *
323
+ * Single segment limits are 160 septets (gsm-7) and 70 code units (ucs-2).
324
+ * Concatenated messages carry a header, shrinking each segment to 153 and 67.
325
+ * A two-unit character never straddles a boundary: it moves whole to the next
326
+ * segment and wastes one unit.
327
+ */
328
+ declare function countSmsSegments(message: string): SmsSegmentInfo;
329
+ /**
330
+ * The unique characters forcing a message into ucs-2, in first appearance
331
+ * order. Empty when the message is already gsm-7 safe. Pair it with
332
+ * {@link sanitizeForGsm7} to see what is left after sanitizing.
333
+ */
334
+ declare function findNonGsm7Characters(message: string): string[];
335
+ interface SanitizeForGsm7Options {
336
+ /**
337
+ * Strip accents from Latin letters that GSM-7 does not carry, so `ï` becomes
338
+ * `i`. Letters GSM-7 already has (é, ü, ñ, à and friends) are never touched.
339
+ * Defaults to false.
340
+ */
341
+ transliterateAccents?: boolean;
342
+ }
343
+ /**
344
+ * Replace common Unicode lookalikes (curly quotes, ellipsis, exotic spaces and
345
+ * dashes) with GSM-7 equivalents, so pasted text does not silently fall into
346
+ * ucs-2 and more than double its segment count.
347
+ *
348
+ * Best effort by design: characters with no safe replacement are left alone.
349
+ * Check the result with {@link getSmsEncoding} and inspect leftovers with
350
+ * {@link findNonGsm7Characters}.
351
+ */
352
+ declare function sanitizeForGsm7(message: string, options?: SanitizeForGsm7Options): string;
353
+ /**
354
+ * Check that a string is strictly E.164: a plus sign, a leading digit 1-9, then
355
+ * 1 to 14 more digits, nothing else.
356
+ *
357
+ * This is a format check only. It does not know country dialing plans, so a
358
+ * well-formed but unassigned number still passes.
359
+ */
360
+ declare function isValidE164(input: string): boolean;
361
+ interface NormalizePhoneNumberOptions {
362
+ /**
363
+ * Country calling code applied to national-format input, with or without a
364
+ * leading plus: `1` and `+1` both work. With it, `(202) 555-0123` becomes
365
+ * `+12025550123` and one leading trunk zero is dropped. Without it,
366
+ * national-format input returns null.
367
+ */
368
+ defaultCountryCode?: string;
369
+ }
370
+ /**
371
+ * Normalize free-form phone input to E.164, or return null when it cannot be
372
+ * done safely. Strips spaces, dots, hyphens and parentheses, converts a leading
373
+ * `00` to a plus, and optionally applies a default country code.
374
+ *
375
+ * This is a formatter, not libphonenumber: it never validates a number against
376
+ * a country dialing plan. Bad input returns null, a bad
377
+ * {@link NormalizePhoneNumberOptions.defaultCountryCode} throws.
378
+ */
379
+ declare function normalizePhoneNumber(input: string, options?: NormalizePhoneNumberOptions): string | null;
380
+
237
381
  interface VerifyWebhookSignatureOptions {
238
382
  /**
239
383
  * The webhook request body. Prefer the raw body string.
@@ -256,4 +400,4 @@ interface VerifyWebhookSignatureOptions {
256
400
  */
257
401
  declare function verifyWebhookSignature(options: VerifyWebhookSignatureOptions): Promise<boolean>;
258
402
 
259
- export { type Device, type IterateMessagesOptions, type ListMessagesOptions, type Message, type MessageDevice, type MessageList, type MessageListMeta, type SendSmsImmediateResponse, type SendSmsQueuedResponse, type SendSmsRequest, type SendSmsResponse, type SmsBatch, type SmsBatchResult, Textbee, TextbeeError, type TextbeeOptions, type VerifyWebhookSignatureOptions, type WebhookEvent, verifyWebhookSignature };
403
+ export { type Device, type IterateMessagesOptions, type ListMessagesOptions, type Message, type MessageDevice, type MessageList, type MessageListMeta, type NormalizePhoneNumberOptions, type SanitizeForGsm7Options, type SendSmsImmediateResponse, type SendSmsQueuedResponse, type SendSmsRequest, type SendSmsResponse, type SendSmsWarning, type Sms, type SmsBatch, type SmsBatchResult, type SmsEncoding, type SmsSegmentInfo, Textbee, TextbeeError, type TextbeeOptions, type VerifyWebhookSignatureOptions, type WebhookEvent, countSmsSegments, findNonGsm7Characters, getSmsEncoding, isValidE164, normalizePhoneNumber, sanitizeForGsm7, verifyWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  interface TextbeeOptions {
2
2
  /** API key from the textbee dashboard. Sent as the `x-api-key` header. */
3
3
  apiKey: string;
4
- /** Override the API base URL. Defaults to `https://api.textbee.dev/api/v1`. */
4
+ /**
5
+ * Your self-hosted textbee API. Defaults to `https://api.textbee.dev/api/v1`.
6
+ *
7
+ * A bare origin such as `https://sms.example.com` gets `/api/v1` added. A URL
8
+ * that contains `/api/v1` is cut there, so a pasted endpoint URL still works.
9
+ * Any other path is used as given, for proxies that map a prefix onto the API.
10
+ * A value with no scheme gets `https://`. An invalid URL throws a `TypeError`
11
+ * rather than falling back, so a typo never sends your key to the public API.
12
+ */
5
13
  baseUrl?: string;
6
14
  }
7
15
  interface SendSmsRequest {
@@ -33,20 +41,32 @@ interface SendSmsRequest {
33
41
  /**
34
42
  * Send later instead of now, as an ISO 8601 string or a `Date`.
35
43
  *
36
- * Must be in the future, and up to 72 hours ahead. Omit for an immediate send.
44
+ * Must be in the future. Omit for an immediate send.
37
45
  */
38
46
  scheduledAt?: string | Date;
39
47
  }
48
+ /** A reason the send will probably fail, such as `SMS_PERMISSION_MISSING`. */
49
+ interface SendSmsWarning {
50
+ code: string;
51
+ message: string;
52
+ }
40
53
  /** Returned when the message was accepted for delivery and queued. */
41
54
  interface SendSmsQueuedResponse {
42
55
  success: true;
43
56
  message: string;
44
57
  smsBatchId: string;
45
58
  recipientCount: number;
59
+ /** When a large send, spread over several waves, should finish. */
60
+ estimatedCompletionAt?: string;
61
+ /** Present when the phone last reported a problem, such as the SMS permission off. */
62
+ warning?: SendSmsWarning;
46
63
  }
47
64
  /** Returned when the message was dispatched to the device immediately. */
48
65
  interface SendSmsImmediateResponse {
49
66
  successCount: number;
67
+ failureCount?: number;
68
+ /** Present when the phone last reported a problem, such as the SMS permission off. */
69
+ warning?: SendSmsWarning;
50
70
  [key: string]: unknown;
51
71
  }
52
72
  /**
@@ -110,6 +130,7 @@ interface Device {
110
130
  }
111
131
  interface MessageDevice {
112
132
  _id: string;
133
+ name?: string;
113
134
  brand?: string;
114
135
  model?: string;
115
136
  buildId?: string;
@@ -129,7 +150,7 @@ interface Message {
129
150
  direction?: 'sent' | 'received';
130
151
  /** Message channel. Currently always `sms`; absent means `sms`. */
131
152
  channel?: string;
132
- /** The sending or receiving device, populated by the history endpoints. */
153
+ /** The sending or receiving device. */
133
154
  device?: MessageDevice;
134
155
  /** Lowercase, and absent on messages stored before status tracking. */
135
156
  status?: 'pending' | 'dispatched' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received';
@@ -148,16 +169,35 @@ interface Message {
148
169
  createdAt?: string;
149
170
  updatedAt?: string;
150
171
  }
172
+ /**
173
+ * A send batch. The status and counts are calculated from the batch's
174
+ * messages, so they change as each message reports.
175
+ */
151
176
  interface SmsBatch {
152
177
  _id: string;
153
178
  message: string;
154
179
  recipientCount: number;
155
180
  recipientPreview?: string;
181
+ /** Messages sent or delivered: `sentCount + deliveredCount`. */
156
182
  successCount: number;
183
+ /** Messages that failed. */
157
184
  failureCount: number;
185
+ /** Messages not yet handed to the phone. */
186
+ pendingCount?: number;
187
+ /** Messages handed to the phone with no result yet. */
188
+ dispatchedCount?: number;
189
+ /** Messages sent with no delivery report yet. */
190
+ sentCount?: number;
191
+ /** Messages with a delivery report. */
192
+ deliveredCount?: number;
193
+ /** Messages with no report from the phone within 20 minutes. */
194
+ unknownCount?: number;
158
195
  status: 'pending' | 'processing' | 'completed' | 'partial_success' | 'failed' | 'unknown';
159
196
  error?: string;
197
+ /** When the batch reached its current final status. Absent while processing. */
160
198
  completedAt?: string;
199
+ /** When the status and counts were last calculated. */
200
+ statusCheckedAt?: string;
161
201
  createdAt?: string;
162
202
  updatedAt?: string;
163
203
  }
@@ -175,13 +215,21 @@ interface MessageListMeta {
175
215
  nextCursor?: string | null;
176
216
  hasMore?: boolean;
177
217
  }
218
+ /**
219
+ * A message as `getSms` and `getSmsBatch` return it: `device` is the id
220
+ * string, and `direction` is absent (read `type`).
221
+ */
222
+ interface Sms extends Omit<Message, 'device' | 'direction'> {
223
+ /** The id of the sending or receiving device. */
224
+ device?: string;
225
+ }
178
226
  interface MessageList {
179
227
  data: Message[];
180
228
  meta: MessageListMeta;
181
229
  }
182
230
  interface SmsBatchResult {
183
231
  batch: SmsBatch;
184
- messages: Message[];
232
+ messages: Sms[];
185
233
  }
186
234
  /** Event names textbee sends in a webhook payload. */
187
235
  type WebhookEvent = 'MESSAGE_RECEIVED' | 'MESSAGE_SENT' | 'MESSAGE_DELIVERED' | 'MESSAGE_FAILED' | 'UNKNOWN_STATE' | 'SMS_STATUS_UPDATED';
@@ -204,9 +252,10 @@ declare class Textbee {
204
252
  getMessages(options?: ListMessagesOptions): Promise<MessageList>;
205
253
  /**
206
254
  * Iterate every message matching the filters, following the pagination
207
- * cursor until the end. Use `order: 'asc'` with a `from` bound to drain
208
- * forward when polling; keep the last message's `createdAt` (or track
209
- * `meta.nextCursor` via `getMessages`) to resume the next poll.
255
+ * cursor until the end. `limit` is the page size, not a cap on the total.
256
+ * Use `order: 'asc'` with a `from` bound to drain forward when polling.
257
+ * `from` is inclusive, so a poll that resumes from the last `createdAt` sees
258
+ * that message again; skip ids you have already handled.
210
259
  *
211
260
  * ```ts
212
261
  * for await (const message of textbee.iterateMessages({ direction: 'received' })) {
@@ -216,8 +265,12 @@ declare class Textbee {
216
265
  */
217
266
  iterateMessages(options?: IterateMessagesOptions): AsyncGenerator<Message, void, undefined>;
218
267
  /** Fetch a single message, including its delivery status. */
219
- getSms(deviceId: string, smsId: string): Promise<Message>;
220
- /** Fetch a batch and every message in it. Pair this with `smsBatchId` from a send. */
268
+ getSms(deviceId: string, smsId: string): Promise<Sms>;
269
+ /**
270
+ * Fetch a batch and every message in it, by the `smsBatchId` a send returns.
271
+ * Needs the id of the device that sent the batch. When textbee picked the
272
+ * device, use `getMessages({ smsBatchId })` instead.
273
+ */
221
274
  getSmsBatch(deviceId: string, smsBatchId: string): Promise<SmsBatchResult>;
222
275
  }
223
276
 
@@ -234,6 +287,97 @@ declare class TextbeeError extends Error {
234
287
  constructor(status: number, message: string, body?: unknown);
235
288
  }
236
289
 
290
+ /**
291
+ * Pure SMS helpers. No API key, no network, no dependencies. They work with
292
+ * any SMS provider, not just textbee.
293
+ */
294
+ /** How the message body will be encoded on the wire. */
295
+ type SmsEncoding = 'gsm-7' | 'ucs-2';
296
+ interface SmsSegmentInfo {
297
+ /** `gsm-7` when every character fits the GSM 03.38 alphabet, else `ucs-2`. */
298
+ encoding: SmsEncoding;
299
+ /**
300
+ * Encoded length: septets for gsm-7 (extension characters cost 2), UTF-16
301
+ * code units for ucs-2 (emoji and other astral characters cost 2).
302
+ */
303
+ length: number;
304
+ /** Number of SMS segments the message occupies. 0 for an empty string. */
305
+ segments: number;
306
+ /**
307
+ * How many more single-unit characters fit before another segment is needed.
308
+ * A two-unit character (extension character or emoji) may not fit even when
309
+ * this is 1.
310
+ */
311
+ remainingInSegment: number;
312
+ }
313
+ /**
314
+ * Detect the encoding a message requires: `gsm-7` when every character is in
315
+ * the GSM 03.38 alphabet or its extension table, `ucs-2` otherwise. An empty
316
+ * string reports `gsm-7`.
317
+ */
318
+ declare function getSmsEncoding(message: string): SmsEncoding;
319
+ /**
320
+ * Compute encoding, encoded length, segment count, and remaining room the way
321
+ * a standards-following handset splits a message.
322
+ *
323
+ * Single segment limits are 160 septets (gsm-7) and 70 code units (ucs-2).
324
+ * Concatenated messages carry a header, shrinking each segment to 153 and 67.
325
+ * A two-unit character never straddles a boundary: it moves whole to the next
326
+ * segment and wastes one unit.
327
+ */
328
+ declare function countSmsSegments(message: string): SmsSegmentInfo;
329
+ /**
330
+ * The unique characters forcing a message into ucs-2, in first appearance
331
+ * order. Empty when the message is already gsm-7 safe. Pair it with
332
+ * {@link sanitizeForGsm7} to see what is left after sanitizing.
333
+ */
334
+ declare function findNonGsm7Characters(message: string): string[];
335
+ interface SanitizeForGsm7Options {
336
+ /**
337
+ * Strip accents from Latin letters that GSM-7 does not carry, so `ï` becomes
338
+ * `i`. Letters GSM-7 already has (é, ü, ñ, à and friends) are never touched.
339
+ * Defaults to false.
340
+ */
341
+ transliterateAccents?: boolean;
342
+ }
343
+ /**
344
+ * Replace common Unicode lookalikes (curly quotes, ellipsis, exotic spaces and
345
+ * dashes) with GSM-7 equivalents, so pasted text does not silently fall into
346
+ * ucs-2 and more than double its segment count.
347
+ *
348
+ * Best effort by design: characters with no safe replacement are left alone.
349
+ * Check the result with {@link getSmsEncoding} and inspect leftovers with
350
+ * {@link findNonGsm7Characters}.
351
+ */
352
+ declare function sanitizeForGsm7(message: string, options?: SanitizeForGsm7Options): string;
353
+ /**
354
+ * Check that a string is strictly E.164: a plus sign, a leading digit 1-9, then
355
+ * 1 to 14 more digits, nothing else.
356
+ *
357
+ * This is a format check only. It does not know country dialing plans, so a
358
+ * well-formed but unassigned number still passes.
359
+ */
360
+ declare function isValidE164(input: string): boolean;
361
+ interface NormalizePhoneNumberOptions {
362
+ /**
363
+ * Country calling code applied to national-format input, with or without a
364
+ * leading plus: `1` and `+1` both work. With it, `(202) 555-0123` becomes
365
+ * `+12025550123` and one leading trunk zero is dropped. Without it,
366
+ * national-format input returns null.
367
+ */
368
+ defaultCountryCode?: string;
369
+ }
370
+ /**
371
+ * Normalize free-form phone input to E.164, or return null when it cannot be
372
+ * done safely. Strips spaces, dots, hyphens and parentheses, converts a leading
373
+ * `00` to a plus, and optionally applies a default country code.
374
+ *
375
+ * This is a formatter, not libphonenumber: it never validates a number against
376
+ * a country dialing plan. Bad input returns null, a bad
377
+ * {@link NormalizePhoneNumberOptions.defaultCountryCode} throws.
378
+ */
379
+ declare function normalizePhoneNumber(input: string, options?: NormalizePhoneNumberOptions): string | null;
380
+
237
381
  interface VerifyWebhookSignatureOptions {
238
382
  /**
239
383
  * The webhook request body. Prefer the raw body string.
@@ -256,4 +400,4 @@ interface VerifyWebhookSignatureOptions {
256
400
  */
257
401
  declare function verifyWebhookSignature(options: VerifyWebhookSignatureOptions): Promise<boolean>;
258
402
 
259
- export { type Device, type IterateMessagesOptions, type ListMessagesOptions, type Message, type MessageDevice, type MessageList, type MessageListMeta, type SendSmsImmediateResponse, type SendSmsQueuedResponse, type SendSmsRequest, type SendSmsResponse, type SmsBatch, type SmsBatchResult, Textbee, TextbeeError, type TextbeeOptions, type VerifyWebhookSignatureOptions, type WebhookEvent, verifyWebhookSignature };
403
+ export { type Device, type IterateMessagesOptions, type ListMessagesOptions, type Message, type MessageDevice, type MessageList, type MessageListMeta, type NormalizePhoneNumberOptions, type SanitizeForGsm7Options, type SendSmsImmediateResponse, type SendSmsQueuedResponse, type SendSmsRequest, type SendSmsResponse, type SendSmsWarning, type Sms, type SmsBatch, type SmsBatchResult, type SmsEncoding, type SmsSegmentInfo, Textbee, TextbeeError, type TextbeeOptions, type VerifyWebhookSignatureOptions, type WebhookEvent, countSmsSegments, findNonGsm7Characters, getSmsEncoding, isValidE164, normalizePhoneNumber, sanitizeForGsm7, verifyWebhookSignature };