@dereekb/nestjs 14.14.0 → 14.16.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.
@@ -1,13 +1,13 @@
1
1
  import { Injectable, Inject, Logger, Module, Post, Req, Controller, ForbiddenException } from '@nestjs/common';
2
2
  import * as twilioModule from 'twilio';
3
- import { performAsyncTasks, handlerFactory, handlerConfigurerFactory, handlerMappedSetFunctionFactory } from '@dereekb/util';
3
+ import { performAsyncTasks, websiteUrlFromPaths, handlerFactory, handlerConfigurerFactory, handlerMappedSetFunctionFactory } from '@dereekb/util';
4
4
  import { ConfigService, ConfigModule } from '@nestjs/config';
5
- import { RawBody } from '@dereekb/nestjs';
5
+ import { RawBody, DEFAULT_BASE_WEBHOOK_PATH } from '@dereekb/nestjs';
6
6
 
7
- function _class_call_check$e(instance, Constructor) {
7
+ function _class_call_check$f(instance, Constructor) {
8
8
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
9
9
  }
10
- function _defineProperties$8(target, props) {
10
+ function _defineProperties$9(target, props) {
11
11
  for(var i = 0; i < props.length; i++){
12
12
  var descriptor = props[i];
13
13
  descriptor.enumerable = descriptor.enumerable || false;
@@ -16,11 +16,11 @@ function _defineProperties$8(target, props) {
16
16
  Object.defineProperty(target, descriptor.key, descriptor);
17
17
  }
18
18
  }
19
- function _create_class$8(Constructor, protoProps, staticProps) {
20
- if (staticProps) _defineProperties$8(Constructor, staticProps);
19
+ function _create_class$9(Constructor, protoProps, staticProps) {
20
+ if (staticProps) _defineProperties$9(Constructor, staticProps);
21
21
  return Constructor;
22
22
  }
23
- function _define_property$b(obj, key, value) {
23
+ function _define_property$e(obj, key, value) {
24
24
  if (key in obj) {
25
25
  Object.defineProperty(obj, key, {
26
26
  value: value,
@@ -37,21 +37,20 @@ var TWILIO_API_KEY_SID_ENV_VAR = 'TWILIO_API_KEY_SID';
37
37
  var TWILIO_API_KEY_SECRET_ENV_VAR = 'TWILIO_API_KEY_SECRET';
38
38
  var TWILIO_PHONE_NUMBER_ENV_VAR = 'TWILIO_PHONE_NUMBER';
39
39
  var TWILIO_MESSAGING_SERVICE_SID_ENV_VAR = 'TWILIO_MESSAGING_SERVICE_SID';
40
- var TWILIO_STATUS_CALLBACK_URL_ENV_VAR = 'TWILIO_STATUS_CALLBACK_URL';
41
40
  var TWILIO_SANDBOX_ENV_VAR = 'TWILIO_SANDBOX';
42
41
  /**
43
42
  * Configuration for {@link TwilioApi} and {@link TwilioService}.
44
43
  */ var TwilioServiceConfig = /*#__PURE__*/ function() {
45
44
  function TwilioServiceConfig() {
46
- _class_call_check$e(this, TwilioServiceConfig);
45
+ _class_call_check$f(this, TwilioServiceConfig);
47
46
  /**
48
47
  * Client credentials.
49
- */ _define_property$b(this, "twilio", void 0);
48
+ */ _define_property$e(this, "twilio", void 0);
50
49
  /**
51
50
  * Outbound message defaults.
52
- */ _define_property$b(this, "messages", void 0);
51
+ */ _define_property$e(this, "messages", void 0);
53
52
  }
54
- _create_class$8(TwilioServiceConfig, null, [
53
+ _create_class$9(TwilioServiceConfig, null, [
55
54
  {
56
55
  key: "assertValidConfig",
57
56
  value: function assertValidConfig(config) {
@@ -70,8 +69,97 @@ var TWILIO_SANDBOX_ENV_VAR = 'TWILIO_SANDBOX';
70
69
  }
71
70
  ]);
72
71
  return TwilioServiceConfig;
73
- }
74
- ();
72
+ }();
73
+ // MARK: Usable Config
74
+ /**
75
+ * Values treated as unset by {@link usableTwilioServiceConfig}.
76
+ *
77
+ * These are the stand-in values a committed `.env` ships with, which are truthy and so pass
78
+ * {@link TwilioServiceConfig.assertValidConfig}, but crash the `twilio` SDK constructor.
79
+ */ var TWILIO_PLACEHOLDER_CONFIG_VALUES = [
80
+ '',
81
+ 'placeholder',
82
+ 'xxx'
83
+ ];
84
+ /**
85
+ * Returns true if the input is unset or one of the {@link TWILIO_PLACEHOLDER_CONFIG_VALUES}.
86
+ *
87
+ * @param value - Configured value to check.
88
+ * @returns True if the value should be treated as unset.
89
+ */ function isPlaceholderTwilioConfigValue(value) {
90
+ return value == null || TWILIO_PLACEHOLDER_CONFIG_VALUES.includes(value.trim().toLowerCase());
91
+ }
92
+ /**
93
+ * Returns a copy of the input config with its placeholder and malformed values removed, or
94
+ * undefined if what remains cannot send SMS.
95
+ *
96
+ * A usable config has:
97
+ * - an Account SID starting with `AC`
98
+ * - an Auth Token, or an API Key SID starting with `SK` together with its secret
99
+ * - a sender phone number starting with `+`, or a Messaging Service SID starting with `MG`
100
+ *
101
+ * Values that are unset, one of the {@link TWILIO_PLACEHOLDER_CONFIG_VALUES}, or missing their
102
+ * expected prefix are dropped from the returned config, so a placeholder Messaging Service SID or
103
+ * API key never shadows a real sender or auth token. A status callback URL that is not an
104
+ * `http(s)` URL is dropped as well, since it does not affect whether the config can send.
105
+ *
106
+ * `twilioServiceConfigFactory()` uses this to remove placeholder values from a usable environment config.
107
+ *
108
+ * @param config - Config to check, typically read from the environment.
109
+ * @returns The usable config, or undefined if the config cannot send SMS.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const config = usableTwilioServiceConfig(twilioServiceConfigFromConfigService(configService));
114
+ * ```
115
+ */ function usableTwilioServiceConfig(config) {
116
+ var realValueWithPrefix = function realValueWithPrefix(value, prefix) {
117
+ return !isPlaceholderTwilioConfigValue(value) && (prefix == null || value.startsWith(prefix)) ? value : undefined;
118
+ };
119
+ var result;
120
+ if (config) {
121
+ var twilio = config.twilio, messages = config.messages;
122
+ var accountSid = realValueWithPrefix(twilio.accountSid, 'AC');
123
+ var authToken = realValueWithPrefix(twilio.authToken);
124
+ var apiKeySid = realValueWithPrefix(twilio.apiKeySid, 'SK');
125
+ var apiKeySecret = realValueWithPrefix(twilio.apiKeySecret);
126
+ var hasApiKey = apiKeySid != null && apiKeySecret != null;
127
+ var defaultFrom = realValueWithPrefix(messages.defaultFrom, '+');
128
+ var messagingServiceSid = realValueWithPrefix(messages.messagingServiceSid, 'MG');
129
+ var defaultStatusCallback = realValueWithPrefix(messages.defaultStatusCallback, 'http');
130
+ if (accountSid && (authToken || hasApiKey) && (defaultFrom || messagingServiceSid)) {
131
+ result = {
132
+ twilio: {
133
+ accountSid: accountSid,
134
+ authToken: authToken,
135
+ apiKeySid: hasApiKey ? apiKeySid : undefined,
136
+ apiKeySecret: hasApiKey ? apiKeySecret : undefined
137
+ },
138
+ messages: {
139
+ defaultFrom: defaultFrom,
140
+ messagingServiceSid: messagingServiceSid,
141
+ defaultStatusCallback: defaultStatusCallback,
142
+ sandbox: messages.sandbox
143
+ }
144
+ };
145
+ }
146
+ }
147
+ return result;
148
+ }
149
+ /**
150
+ * Returns true if {@link usableTwilioServiceConfig} can build a usable config from the input.
151
+ *
152
+ * Use it to decide whether a provided `TwilioService` can send, e.g.
153
+ * `isUsableTwilioServiceConfig(twilioService.twilioApi.config)`.
154
+ *
155
+ * Note that a usable config may still carry placeholder values; pass the input through
156
+ * {@link usableTwilioServiceConfig} to get a copy with them removed before using it.
157
+ *
158
+ * @param config - Config to check, typically read from the environment.
159
+ * @returns True if the config can send SMS.
160
+ */ function isUsableTwilioServiceConfig(config) {
161
+ return usableTwilioServiceConfig(config) != null;
162
+ }
75
163
 
76
164
  function _type_of(obj) {
77
165
  "@swc/helpers - typeof";
@@ -97,10 +185,23 @@ var _twilioModule_default;
97
185
  var twilio = (_twilioModule_default = twilioModule.default) !== null && _twilioModule_default !== void 0 ? _twilioModule_default : twilioModule;
98
186
  var Twilio = twilio.Twilio, validateRequest = twilio.validateRequest;
99
187
 
100
- function _class_call_check$d(instance, Constructor) {
188
+ function _class_call_check$e(instance, Constructor) {
101
189
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
102
190
  }
103
- function _define_property$a(obj, key, value) {
191
+ function _defineProperties$8(target, props) {
192
+ for(var i = 0; i < props.length; i++){
193
+ var descriptor = props[i];
194
+ descriptor.enumerable = descriptor.enumerable || false;
195
+ descriptor.configurable = true;
196
+ if ("value" in descriptor) descriptor.writable = true;
197
+ Object.defineProperty(target, descriptor.key, descriptor);
198
+ }
199
+ }
200
+ function _create_class$8(Constructor, protoProps, staticProps) {
201
+ if (protoProps) _defineProperties$8(Constructor.prototype, protoProps);
202
+ return Constructor;
203
+ }
204
+ function _define_property$d(obj, key, value) {
104
205
  if (key in obj) {
105
206
  Object.defineProperty(obj, key, {
106
207
  value: value,
@@ -111,28 +212,543 @@ function _define_property$a(obj, key, value) {
111
212
  } else obj[key] = value;
112
213
  return obj;
113
214
  }
114
- var TwilioApi = function TwilioApi(config) {
115
- _class_call_check$d(this, TwilioApi);
116
- _define_property$a(this, "config", void 0);
117
- _define_property$a(this, "client", void 0);
118
- this.config = config;
119
- var _config_twilio = config.twilio, accountSid = _config_twilio.accountSid, authToken = _config_twilio.authToken, apiKeySid = _config_twilio.apiKeySid, apiKeySecret = _config_twilio.apiKeySecret;
120
- var client;
121
- if (apiKeySid && apiKeySecret) {
122
- client = new Twilio(apiKeySid, apiKeySecret, {
123
- accountSid: accountSid
124
- });
125
- } else {
126
- client = new Twilio(accountSid, authToken);
215
+ var TwilioApi = /*#__PURE__*/ function() {
216
+ function TwilioApi(config) {
217
+ _class_call_check$e(this, TwilioApi);
218
+ _define_property$d(this, "config", void 0);
219
+ _define_property$d(this, "_client", void 0);
220
+ this.config = config;
127
221
  }
128
- this.client = client;
129
- };
222
+ _create_class$8(TwilioApi, [
223
+ {
224
+ key: "client",
225
+ get: /**
226
+ * The Twilio SDK client, created on first access.
227
+ *
228
+ * Created lazily because the SDK constructor throws when the Account SID does not start with `AC`, so a
229
+ * config holding the placeholder values of a committed `.env` can still be provided. Accessing the client
230
+ * with such a config throws.
231
+ *
232
+ * @returns The Twilio SDK client.
233
+ */ function get() {
234
+ if (!this._client) {
235
+ var _this_config_twilio = this.config.twilio, accountSid = _this_config_twilio.accountSid, authToken = _this_config_twilio.authToken, apiKeySid = _this_config_twilio.apiKeySid, apiKeySecret = _this_config_twilio.apiKeySecret;
236
+ this._client = apiKeySid && apiKeySecret ? new Twilio(apiKeySid, apiKeySecret, {
237
+ accountSid: accountSid
238
+ }) : new Twilio(accountSid, authToken);
239
+ }
240
+ return this._client;
241
+ }
242
+ }
243
+ ]);
244
+ return TwilioApi;
245
+ }();
130
246
  TwilioApi = __decorate([
131
247
  Injectable(),
132
248
  __param(0, Inject(TwilioServiceConfig))
133
249
  ], TwilioApi);
134
250
 
135
- function _array_like_to_array(arr, len) {
251
+ function asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, key, arg) {
252
+ try {
253
+ var info = gen[key](arg);
254
+ var value = info.value;
255
+ } catch (error) {
256
+ reject(error);
257
+ return;
258
+ }
259
+ if (info.done) resolve(value);
260
+ else Promise.resolve(value).then(_next, _throw);
261
+ }
262
+ function _async_to_generator$5(fn) {
263
+ return function() {
264
+ var self = this, args = arguments;
265
+ return new Promise(function(resolve, reject) {
266
+ var gen = fn.apply(self, args);
267
+ function _next(value) {
268
+ asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, "next", value);
269
+ }
270
+ function _throw(err) {
271
+ asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, "throw", err);
272
+ }
273
+ _next(undefined);
274
+ });
275
+ };
276
+ }
277
+ function _define_property$c(obj, key, value) {
278
+ if (key in obj) {
279
+ Object.defineProperty(obj, key, {
280
+ value: value,
281
+ enumerable: true,
282
+ configurable: true,
283
+ writable: true
284
+ });
285
+ } else obj[key] = value;
286
+ return obj;
287
+ }
288
+ function _instanceof$2(left, right) {
289
+ "@swc/helpers - instanceof";
290
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
291
+ return !!right[Symbol.hasInstance](left);
292
+ } else return left instanceof right;
293
+ }
294
+ function _object_spread$4(target) {
295
+ for(var i = 1; i < arguments.length; i++){
296
+ var source = arguments[i] != null ? arguments[i] : {};
297
+ var ownKeys = Object.keys(source);
298
+ if (typeof Object.getOwnPropertySymbols === "function") {
299
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
300
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
301
+ }));
302
+ }
303
+ ownKeys.forEach(function(key) {
304
+ _define_property$c(target, key, source[key]);
305
+ });
306
+ }
307
+ return target;
308
+ }
309
+ function _ts_generator$5(thisArg, body) {
310
+ var f, y, t, _ = {
311
+ label: 0,
312
+ sent: function() {
313
+ if (t[0] & 1) throw t[1];
314
+ return t[1];
315
+ },
316
+ trys: [],
317
+ ops: []
318
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
319
+ return d(g, "next", {
320
+ value: verb(0)
321
+ }), d(g, "throw", {
322
+ value: verb(1)
323
+ }), d(g, "return", {
324
+ value: verb(2)
325
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
326
+ value: function() {
327
+ return this;
328
+ }
329
+ }), g;
330
+ function verb(n) {
331
+ return function(v) {
332
+ return step([
333
+ n,
334
+ v
335
+ ]);
336
+ };
337
+ }
338
+ function step(op) {
339
+ if (f) throw new TypeError("Generator is already executing.");
340
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
341
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
342
+ if (y = 0, t) op = [
343
+ op[0] & 2,
344
+ t.value
345
+ ];
346
+ switch(op[0]){
347
+ case 0:
348
+ case 1:
349
+ t = op;
350
+ break;
351
+ case 4:
352
+ _.label++;
353
+ return {
354
+ value: op[1],
355
+ done: false
356
+ };
357
+ case 5:
358
+ _.label++;
359
+ y = op[1];
360
+ op = [
361
+ 0
362
+ ];
363
+ continue;
364
+ case 7:
365
+ op = _.ops.pop();
366
+ _.trys.pop();
367
+ continue;
368
+ default:
369
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
370
+ _ = 0;
371
+ continue;
372
+ }
373
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
374
+ _.label = op[1];
375
+ break;
376
+ }
377
+ if (op[0] === 6 && _.label < t[1]) {
378
+ _.label = t[1];
379
+ t = op;
380
+ break;
381
+ }
382
+ if (t && _.label < t[2]) {
383
+ _.label = t[2];
384
+ _.ops.push(op);
385
+ break;
386
+ }
387
+ if (t[2]) _.ops.pop();
388
+ _.trys.pop();
389
+ continue;
390
+ }
391
+ op = body.call(thisArg, _);
392
+ } catch (e) {
393
+ op = [
394
+ 6,
395
+ e
396
+ ];
397
+ y = 0;
398
+ } finally{
399
+ f = t = 0;
400
+ }
401
+ if (op[0] & 5) throw op[1];
402
+ return {
403
+ value: op[0] ? op[1] : void 0,
404
+ done: true
405
+ };
406
+ }
407
+ }
408
+ /**
409
+ * The default number of messages to read back when inspecting a number's recent texts.
410
+ */ var DEFAULT_TWILIO_RECENT_MESSAGES_LIMIT = 20;
411
+ /**
412
+ * The default window of history to inspect when reading a number's recent texts.
413
+ */ var DEFAULT_TWILIO_RECENT_MESSAGES_WINDOW_DAYS = 30;
414
+ /**
415
+ * Twilio error codes relevant to delivery diagnosis.
416
+ *
417
+ * Twilio reports these on a failed or undelivered message, and on the error a rejected send throws. Twilio
418
+ * notes that the code for a given cause may change as it improves its errors, so treat them as hints and
419
+ * keep the error message alongside. Unknown codes are passed through untouched.
420
+ */ var TwilioMessageErrorCode;
421
+ (function(TwilioMessageErrorCode) {
422
+ /**
423
+ * The `To` number is not a valid phone number.
424
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["INVALID_TO_NUMBER"] = 21211] = "INVALID_TO_NUMBER";
425
+ /**
426
+ * The recipient replied STOP, so the sender may not text them until they reply START.
427
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["UNSUBSCRIBED_RECIPIENT"] = 21610] = "UNSUBSCRIBED_RECIPIENT";
428
+ /**
429
+ * The `To` number is not a mobile number, so it cannot receive texts.
430
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["NOT_A_MOBILE_NUMBER"] = 21614] = "NOT_A_MOBILE_NUMBER";
431
+ /**
432
+ * The account's message queue overflowed. Temporary.
433
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["QUEUE_OVERFLOW"] = 30001] = "QUEUE_OVERFLOW";
434
+ /**
435
+ * The Twilio account is suspended.
436
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["ACCOUNT_SUSPENDED"] = 30002] = "ACCOUNT_SUSPENDED";
437
+ /**
438
+ * The phone is switched off or out of coverage. Usually temporary.
439
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["UNREACHABLE_DESTINATION_HANDSET"] = 30003] = "UNREACHABLE_DESTINATION_HANDSET";
440
+ /**
441
+ * The carrier or the phone blocked the message.
442
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["MESSAGE_BLOCKED"] = 30004] = "MESSAGE_BLOCKED";
443
+ /**
444
+ * The number does not exist or is no longer in service.
445
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["UNKNOWN_DESTINATION_HANDSET"] = 30005] = "UNKNOWN_DESTINATION_HANDSET";
446
+ /**
447
+ * The number is a landline, or its carrier cannot receive texts.
448
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["LANDLINE_OR_UNREACHABLE_CARRIER"] = 30006] = "LANDLINE_OR_UNREACHABLE_CARRIER";
449
+ /**
450
+ * The carrier filtered the message, usually as suspected spam.
451
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["MESSAGE_FILTERED"] = 30007] = "MESSAGE_FILTERED";
452
+ /**
453
+ * Twilio could not determine why the message failed. Often temporary.
454
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["UNKNOWN_ERROR"] = 30008] = "UNKNOWN_ERROR";
455
+ /**
456
+ * The sending number is not registered for A2P 10DLC messaging, so US carriers block it.
457
+ */ TwilioMessageErrorCode[TwilioMessageErrorCode["UNREGISTERED_NUMBER"] = 30034] = "UNREGISTERED_NUMBER";
458
+ })(TwilioMessageErrorCode || (TwilioMessageErrorCode = {}));
459
+ /**
460
+ * Converts a Twilio message instance to a {@link TwilioDiagnosticMessage}.
461
+ *
462
+ * @param message - The message instance.
463
+ * @returns The diagnostic message.
464
+ */ function twilioDiagnosticMessage(message) {
465
+ var sid = message.sid, status = message.status, to = message.to, errorCode = message.errorCode, errorMessage = message.errorMessage, dateSent = message.dateSent, dateCreated = message.dateCreated, dateUpdated = message.dateUpdated;
466
+ return {
467
+ sid: sid,
468
+ status: status,
469
+ to: to,
470
+ errorCode: errorCode !== null && errorCode !== void 0 ? errorCode : undefined,
471
+ errorMessage: errorMessage || undefined,
472
+ dateSent: dateSent !== null && dateSent !== void 0 ? dateSent : undefined,
473
+ dateCreated: dateCreated !== null && dateCreated !== void 0 ? dateCreated : undefined,
474
+ dateUpdated: dateUpdated !== null && dateUpdated !== void 0 ? dateUpdated : undefined
475
+ };
476
+ }
477
+ /**
478
+ * Returns the time a message last changed state: its update time, falling back to its send and creation times.
479
+ *
480
+ * @param message - The message.
481
+ * @returns The time, if Twilio recorded any.
482
+ */ function twilioDiagnosticMessageDate(message) {
483
+ var _ref, _message_dateUpdated;
484
+ return (_ref = (_message_dateUpdated = message.dateUpdated) !== null && _message_dateUpdated !== void 0 ? _message_dateUpdated : message.dateSent) !== null && _ref !== void 0 ? _ref : message.dateCreated;
485
+ }
486
+ /**
487
+ * Returns the Twilio error code of a thrown Twilio error, such as the error a rejected send throws.
488
+ *
489
+ * @param error - The thrown value.
490
+ * @returns The error code, if the error carries one.
491
+ */ function twilioErrorCode(error) {
492
+ var code = error === null || error === void 0 ? void 0 : error.code;
493
+ return typeof code === 'number' ? code : undefined;
494
+ }
495
+ /**
496
+ * Returns the HTTP status of a thrown Twilio error.
497
+ *
498
+ * @param error - The thrown value.
499
+ * @returns The HTTP status, if the error carries one.
500
+ */ function twilioErrorStatus(error) {
501
+ var status = error === null || error === void 0 ? void 0 : error.status;
502
+ return typeof status === 'number' ? status : undefined;
503
+ }
504
+ /**
505
+ * Converts a thrown value to a {@link TwilioDiagnosticError}.
506
+ *
507
+ * @param error - The thrown value.
508
+ * @returns The diagnostic error.
509
+ */ function twilioDiagnosticError(error) {
510
+ return {
511
+ message: _instanceof$2(error, Error) ? error.message : String(error),
512
+ status: twilioErrorStatus(error),
513
+ code: twilioErrorCode(error)
514
+ };
515
+ }
516
+ /**
517
+ * Whether a diagnostic read was refused because of the credentials, rather than failing to reach Twilio.
518
+ *
519
+ * @param error - The read's error, if it failed.
520
+ * @returns True if Twilio answered with 401 or 403.
521
+ */ function isTwilioDiagnosticPermissionError(error) {
522
+ return (error === null || error === void 0 ? void 0 : error.status) === 401 || (error === null || error === void 0 ? void 0 : error.status) === 403;
523
+ }
524
+ /**
525
+ * Reads the messages recently sent to a number, newest first.
526
+ *
527
+ * @param api - The Twilio API.
528
+ * @param to - The number the messages were sent to.
529
+ * @param options - How many messages to read, and how far back.
530
+ * @returns The messages, with `unknown` set if they could not be read.
531
+ */ function twilioRecentMessagesForRecipient(api, to, options) {
532
+ return _async_to_generator$5(function() {
533
+ var _ref, _ref1, limit, dateSentAfter, result, messages, e;
534
+ return _ts_generator$5(this, function(_state) {
535
+ switch(_state.label){
536
+ case 0:
537
+ limit = (_ref = options === null || options === void 0 ? void 0 : options.limit) !== null && _ref !== void 0 ? _ref : DEFAULT_TWILIO_RECENT_MESSAGES_LIMIT;
538
+ dateSentAfter = (_ref1 = options === null || options === void 0 ? void 0 : options.dateSentAfter) !== null && _ref1 !== void 0 ? _ref1 : undefined;
539
+ _state.label = 1;
540
+ case 1:
541
+ _state.trys.push([
542
+ 1,
543
+ 3,
544
+ ,
545
+ 4
546
+ ]);
547
+ return [
548
+ 4,
549
+ api.client.messages.list(_object_spread$4({
550
+ to: to,
551
+ limit: limit
552
+ }, dateSentAfter ? {
553
+ dateSentAfter: dateSentAfter
554
+ } : {}))
555
+ ];
556
+ case 2:
557
+ messages = _state.sent();
558
+ result = {
559
+ messages: messages.map(function(x) {
560
+ return twilioDiagnosticMessage(x);
561
+ })
562
+ };
563
+ return [
564
+ 3,
565
+ 4
566
+ ];
567
+ case 3:
568
+ e = _state.sent();
569
+ result = {
570
+ messages: [],
571
+ unknown: true,
572
+ error: twilioDiagnosticError(e)
573
+ };
574
+ return [
575
+ 3,
576
+ 4
577
+ ];
578
+ case 4:
579
+ return [
580
+ 2,
581
+ result
582
+ ];
583
+ }
584
+ });
585
+ })();
586
+ }
587
+ /**
588
+ * Reads a single message, such as a test message whose outcome is being followed.
589
+ *
590
+ * @param api - The Twilio API.
591
+ * @param sid - The message SID.
592
+ * @returns The message, with `unknown` set if it could not be read.
593
+ */ function twilioMessageForSid(api, sid) {
594
+ return _async_to_generator$5(function() {
595
+ var result, message, e;
596
+ return _ts_generator$5(this, function(_state) {
597
+ switch(_state.label){
598
+ case 0:
599
+ _state.trys.push([
600
+ 0,
601
+ 2,
602
+ ,
603
+ 3
604
+ ]);
605
+ return [
606
+ 4,
607
+ api.client.messages(sid).fetch()
608
+ ];
609
+ case 1:
610
+ message = _state.sent();
611
+ result = {
612
+ message: twilioDiagnosticMessage(message)
613
+ };
614
+ return [
615
+ 3,
616
+ 3
617
+ ];
618
+ case 2:
619
+ e = _state.sent();
620
+ result = twilioErrorStatus(e) === 404 ? {} : {
621
+ unknown: true,
622
+ error: twilioDiagnosticError(e)
623
+ };
624
+ return [
625
+ 3,
626
+ 3
627
+ ];
628
+ case 3:
629
+ return [
630
+ 2,
631
+ result
632
+ ];
633
+ }
634
+ });
635
+ })();
636
+ }
637
+ /**
638
+ * Reads the account's state.
639
+ *
640
+ * Twilio does not let a Standard API key read the account, so with one this is always `unknown`, with a permission
641
+ * error. See {@link isTwilioDiagnosticPermissionError}.
642
+ *
643
+ * An account that is not `active` sends nothing regardless of the recipient, so this distinguishes a system-wide
644
+ * outage from a per-recipient problem.
645
+ *
646
+ * @param api - The Twilio API.
647
+ * @returns The account's state, with `unknown` set if it could not be read.
648
+ */ function twilioAccountState(api) {
649
+ return _async_to_generator$5(function() {
650
+ var state, account, e;
651
+ return _ts_generator$5(this, function(_state) {
652
+ switch(_state.label){
653
+ case 0:
654
+ _state.trys.push([
655
+ 0,
656
+ 2,
657
+ ,
658
+ 3
659
+ ]);
660
+ return [
661
+ 4,
662
+ api.client.api.v2010.account.fetch()
663
+ ];
664
+ case 1:
665
+ account = _state.sent();
666
+ state = {
667
+ status: account.status
668
+ };
669
+ return [
670
+ 3,
671
+ 3
672
+ ];
673
+ case 2:
674
+ e = _state.sent();
675
+ state = {
676
+ unknown: true,
677
+ error: twilioDiagnosticError(e)
678
+ };
679
+ return [
680
+ 3,
681
+ 3
682
+ ];
683
+ case 3:
684
+ return [
685
+ 2,
686
+ state
687
+ ];
688
+ }
689
+ });
690
+ })();
691
+ }
692
+ /**
693
+ * Looks up a number with Twilio Lookup v2.
694
+ *
695
+ * Unlike {@link TwilioLookupService.lookup}, a failed lookup is reported as `unknown` rather than as an invalid number.
696
+ *
697
+ * @param api - The Twilio API.
698
+ * @param phoneNumber - The number to look up.
699
+ * @param includeLineType - Whether to request the line type. Twilio charges per lookup for it.
700
+ * @returns The diagnosis, with `unknown` set if the lookup could not be made.
701
+ */ function twilioLookupPhoneNumberForDiagnosis(api, phoneNumber, includeLineType) {
702
+ return _async_to_generator$5(function() {
703
+ var result, _ref, _ref1, lookup, lineTypeIntelligence, e;
704
+ return _ts_generator$5(this, function(_state) {
705
+ switch(_state.label){
706
+ case 0:
707
+ _state.trys.push([
708
+ 0,
709
+ 2,
710
+ ,
711
+ 3
712
+ ]);
713
+ return [
714
+ 4,
715
+ api.client.lookups.v2.phoneNumbers(phoneNumber).fetch(includeLineType ? {
716
+ fields: 'line_type_intelligence'
717
+ } : {})
718
+ ];
719
+ case 1:
720
+ lookup = _state.sent();
721
+ lineTypeIntelligence = lookup.lineTypeIntelligence;
722
+ result = {
723
+ valid: Boolean(lookup.valid),
724
+ lineType: (_ref = lineTypeIntelligence === null || lineTypeIntelligence === void 0 ? void 0 : lineTypeIntelligence['type']) !== null && _ref !== void 0 ? _ref : undefined,
725
+ carrierName: (_ref1 = lineTypeIntelligence === null || lineTypeIntelligence === void 0 ? void 0 : lineTypeIntelligence['carrier_name']) !== null && _ref1 !== void 0 ? _ref1 : undefined
726
+ };
727
+ return [
728
+ 3,
729
+ 3
730
+ ];
731
+ case 2:
732
+ e = _state.sent();
733
+ result = {
734
+ unknown: true,
735
+ error: twilioDiagnosticError(e)
736
+ };
737
+ return [
738
+ 3,
739
+ 3
740
+ ];
741
+ case 3:
742
+ return [
743
+ 2,
744
+ result
745
+ ];
746
+ }
747
+ });
748
+ })();
749
+ }
750
+
751
+ function _array_like_to_array$5(arr, len) {
136
752
  if (len == null || len > arr.length) len = arr.length;
137
753
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
138
754
  return arr2;
@@ -166,7 +782,7 @@ function _async_to_generator$4(fn) {
166
782
  });
167
783
  };
168
784
  }
169
- function _class_call_check$c(instance, Constructor) {
785
+ function _class_call_check$d(instance, Constructor) {
170
786
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
171
787
  }
172
788
  function _defineProperties$7(target, props) {
@@ -182,7 +798,7 @@ function _create_class$7(Constructor, protoProps, staticProps) {
182
798
  if (protoProps) _defineProperties$7(Constructor.prototype, protoProps);
183
799
  return Constructor;
184
800
  }
185
- function _define_property$9(obj, key, value) {
801
+ function _define_property$b(obj, key, value) {
186
802
  if (key in obj) {
187
803
  Object.defineProperty(obj, key, {
188
804
  value: value,
@@ -226,7 +842,7 @@ function _iterable_to_array_limit(arr, i) {
226
842
  function _non_iterable_rest() {
227
843
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
228
844
  }
229
- function _object_spread$2(target) {
845
+ function _object_spread$3(target) {
230
846
  for(var i = 1; i < arguments.length; i++){
231
847
  var source = arguments[i] != null ? arguments[i] : {};
232
848
  var ownKeys = Object.keys(source);
@@ -236,13 +852,13 @@ function _object_spread$2(target) {
236
852
  }));
237
853
  }
238
854
  ownKeys.forEach(function(key) {
239
- _define_property$9(target, key, source[key]);
855
+ _define_property$b(target, key, source[key]);
240
856
  });
241
857
  }
242
858
  return target;
243
859
  }
244
860
  function _sliced_to_array(arr, i) {
245
- return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
861
+ return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array$5(arr, i) || _non_iterable_rest();
246
862
  }
247
863
  function _ts_generator$4(thisArg, body) {
248
864
  var f, y, t, _ = {
@@ -343,22 +959,22 @@ function _ts_generator$4(thisArg, body) {
343
959
  };
344
960
  }
345
961
  }
346
- function _unsupported_iterable_to_array(o, minLen) {
962
+ function _unsupported_iterable_to_array$5(o, minLen) {
347
963
  if (!o) return;
348
- if (typeof o === "string") return _array_like_to_array(o, minLen);
964
+ if (typeof o === "string") return _array_like_to_array$5(o, minLen);
349
965
  var n = Object.prototype.toString.call(o).slice(8, -1);
350
966
  if (n === "Object" && o.constructor) n = o.constructor.name;
351
967
  if (n === "Map" || n === "Set") return Array.from(n);
352
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
968
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$5(o, minLen);
353
969
  }
354
970
  /**
355
971
  * Maximum number of concurrent SMS send requests issued by {@link TwilioService.sendBulkSms}.
356
972
  */ var DEFAULT_TWILIO_SEND_SMS_MAX_PARALLEL_TASKS = 5;
357
973
  var TwilioService = /*#__PURE__*/ function() {
358
974
  function TwilioService(twilioApi) {
359
- _class_call_check$c(this, TwilioService);
360
- _define_property$9(this, "logger", new Logger('TwilioService'));
361
- _define_property$9(this, "_twilioApi", void 0);
975
+ _class_call_check$d(this, TwilioService);
976
+ _define_property$b(this, "logger", new Logger('TwilioService'));
977
+ _define_property$b(this, "_twilioApi", void 0);
362
978
  this._twilioApi = twilioApi;
363
979
  }
364
980
  _create_class$7(TwilioService, [
@@ -411,7 +1027,7 @@ var TwilioService = /*#__PURE__*/ function() {
411
1027
  ]);
412
1028
  return [
413
1029
  4,
414
- this._twilioApi.client.messages.create(_object_spread$2({
1030
+ this._twilioApi.client.messages.create(_object_spread$3({
415
1031
  to: input.to,
416
1032
  body: input.body
417
1033
  }, messagingServiceSid ? {
@@ -445,7 +1061,8 @@ var TwilioService = /*#__PURE__*/ function() {
445
1061
  to: input.to,
446
1062
  status: 'failed',
447
1063
  sandboxed: false,
448
- error: errorMessage
1064
+ error: errorMessage,
1065
+ errorCode: twilioErrorCode(e)
449
1066
  };
450
1067
  return [
451
1068
  3,
@@ -510,16 +1127,150 @@ TwilioService = __decorate([
510
1127
  __param(0, Inject(TwilioApi))
511
1128
  ], TwilioService);
512
1129
 
1130
+ function _class_call_check$c(instance, Constructor) {
1131
+ if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
1132
+ }
1133
+ function _define_property$a(obj, key, value) {
1134
+ if (key in obj) {
1135
+ Object.defineProperty(obj, key, {
1136
+ value: value,
1137
+ enumerable: true,
1138
+ configurable: true,
1139
+ writable: true
1140
+ });
1141
+ } else obj[key] = value;
1142
+ return obj;
1143
+ }
1144
+ /**
1145
+ * Path of the {@link TwilioWebhookController} under the app's webhook path (`/webhook`).
1146
+ */ var TWILIO_WEBHOOK_PATH = '/twilio';
1147
+ /**
1148
+ * Path of the message status callback route under {@link TWILIO_WEBHOOK_PATH}.
1149
+ */ var TWILIO_WEBHOOK_STATUS_PATH = '/status';
1150
+ /**
1151
+ * Path of the incoming message route under {@link TWILIO_WEBHOOK_PATH}.
1152
+ */ var TWILIO_WEBHOOK_INCOMING_PATH = '/incoming';
1153
+ /**
1154
+ * Builds the public URLs of the {@link TwilioWebhookController} routes from the app's public webhook URL.
1155
+ *
1156
+ * The `baseUrl` is the origin of the webhook URL, since the verifier appends the request's full path to it.
1157
+ * This assumes requests reach the app with their public path intact, as they do through a Firebase Hosting
1158
+ * rewrite.
1159
+ *
1160
+ * @param webhookUrl - Public URL the app's `/webhook` routes are served at, e.g. `https://app.example.com/api/webhook`.
1161
+ * @returns The Twilio webhook URLs.
1162
+ */ function twilioWebhookUrls(webhookUrl) {
1163
+ return {
1164
+ baseUrl: new URL(webhookUrl).origin,
1165
+ statusCallbackUrl: websiteUrlFromPaths(webhookUrl, [
1166
+ TWILIO_WEBHOOK_PATH,
1167
+ TWILIO_WEBHOOK_STATUS_PATH
1168
+ ]),
1169
+ incomingMessageUrl: websiteUrlFromPaths(webhookUrl, [
1170
+ TWILIO_WEBHOOK_PATH,
1171
+ TWILIO_WEBHOOK_INCOMING_PATH
1172
+ ])
1173
+ };
1174
+ }
1175
+ /**
1176
+ * The app's public Twilio webhook URLs, provided to the Twilio modules through their `dependencyModule`.
1177
+ *
1178
+ * When a module built by `appTwilioModuleMetadata()` can resolve it, outbound texts request status callbacks at
1179
+ * {@link TwilioWebhookUrls.statusCallbackUrl}. When one built by `appTwilioWebhookModuleMetadata()` can, webhook
1180
+ * requests are verified against {@link TwilioWebhookUrls.baseUrl}.
1181
+ *
1182
+ * `twilioWebhookUrlsConfigProvider()` from `@dereekb/firebase-server/twilio` provides it from the server environment.
1183
+ */ var TwilioWebhookUrlsConfig = function TwilioWebhookUrlsConfig() {
1184
+ _class_call_check$c(this, TwilioWebhookUrlsConfig);
1185
+ /**
1186
+ * The URLs, or undefined when Twilio cannot reach the app's webhooks.
1187
+ */ _define_property$a(this, "twilioWebhookUrls", void 0);
1188
+ }
1189
+ ;
1190
+
1191
+ function _array_like_to_array$4(arr, len) {
1192
+ if (len == null || len > arr.length) len = arr.length;
1193
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
1194
+ return arr2;
1195
+ }
1196
+ function _array_without_holes$4(arr) {
1197
+ if (Array.isArray(arr)) return _array_like_to_array$4(arr);
1198
+ }
513
1199
  function _class_call_check$b(instance, Constructor) {
514
1200
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
515
1201
  }
1202
+ function _define_property$9(obj, key, value) {
1203
+ if (key in obj) {
1204
+ Object.defineProperty(obj, key, {
1205
+ value: value,
1206
+ enumerable: true,
1207
+ configurable: true,
1208
+ writable: true
1209
+ });
1210
+ } else obj[key] = value;
1211
+ return obj;
1212
+ }
1213
+ function _iterable_to_array$4(iter) {
1214
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
1215
+ return Array.from(iter);
1216
+ }
1217
+ }
1218
+ function _non_iterable_spread$4() {
1219
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
1220
+ }
1221
+ function _object_spread$2(target) {
1222
+ for(var i = 1; i < arguments.length; i++){
1223
+ var source = arguments[i] != null ? arguments[i] : {};
1224
+ var ownKeys = Object.keys(source);
1225
+ if (typeof Object.getOwnPropertySymbols === "function") {
1226
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
1227
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
1228
+ }));
1229
+ }
1230
+ ownKeys.forEach(function(key) {
1231
+ _define_property$9(target, key, source[key]);
1232
+ });
1233
+ }
1234
+ return target;
1235
+ }
1236
+ function ownKeys$1(object, enumerableOnly) {
1237
+ var keys = Object.keys(object);
1238
+ if (Object.getOwnPropertySymbols) {
1239
+ var symbols = Object.getOwnPropertySymbols(object);
1240
+ keys.push.apply(keys, symbols);
1241
+ }
1242
+ return keys;
1243
+ }
1244
+ function _object_spread_props$1(target, source) {
1245
+ source = source != null ? source : {};
1246
+ if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
1247
+ else {
1248
+ ownKeys$1(Object(source)).forEach(function(key) {
1249
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
1250
+ });
1251
+ }
1252
+ return target;
1253
+ }
1254
+ function _to_consumable_array$4(arr) {
1255
+ return _array_without_holes$4(arr) || _iterable_to_array$4(arr) || _unsupported_iterable_to_array$4(arr) || _non_iterable_spread$4();
1256
+ }
1257
+ function _unsupported_iterable_to_array$4(o, minLen) {
1258
+ if (!o) return;
1259
+ if (typeof o === "string") return _array_like_to_array$4(o, minLen);
1260
+ var n = Object.prototype.toString.call(o).slice(8, -1);
1261
+ if (n === "Object" && o.constructor) n = o.constructor.name;
1262
+ if (n === "Map" || n === "Set") return Array.from(n);
1263
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$4(o, minLen);
1264
+ }
516
1265
  /**
517
- * Factory that creates a {@link TwilioServiceConfig} from environment variables.
1266
+ * Reads a {@link TwilioServiceConfig} from environment variables without validating it.
1267
+ *
1268
+ * {@link twilioServiceConfigFactory} builds the validated config from it.
518
1269
  *
519
1270
  * @param configService - NestJS config service for reading environment variables.
520
- * @returns A validated {@link TwilioServiceConfig}.
521
- */ function twilioServiceConfigFactory(configService) {
522
- var config = {
1271
+ * @returns The unvalidated {@link TwilioServiceConfig}.
1272
+ */ function twilioServiceConfigFromConfigService(configService) {
1273
+ return {
523
1274
  twilio: {
524
1275
  accountSid: configService.get(TWILIO_ACCOUNT_SID_ENV_VAR),
525
1276
  authToken: configService.get(TWILIO_AUTH_TOKEN_ENV_VAR),
@@ -529,42 +1280,84 @@ function _class_call_check$b(instance, Constructor) {
529
1280
  messages: {
530
1281
  defaultFrom: configService.get(TWILIO_PHONE_NUMBER_ENV_VAR),
531
1282
  messagingServiceSid: configService.get(TWILIO_MESSAGING_SERVICE_SID_ENV_VAR),
532
- defaultStatusCallback: configService.get(TWILIO_STATUS_CALLBACK_URL_ENV_VAR),
533
1283
  sandbox: configService.get(TWILIO_SANDBOX_ENV_VAR) === 'true'
534
1284
  }
535
1285
  };
536
- TwilioServiceConfig.assertValidConfig(config);
537
- return config;
538
1286
  }
539
1287
  /**
540
- * NestJS module that provides the {@link TwilioApi} and {@link TwilioService} for sending SMS
541
- * via Twilio.
1288
+ * Factory that creates a {@link TwilioServiceConfig} from environment variables.
542
1289
  *
543
- * Reads Twilio credentials and default sender configuration from environment variables.
544
- */ var TwilioModule = function TwilioModule() {
545
- _class_call_check$b(this, TwilioModule);
546
- };
547
- TwilioModule = __decorate([
548
- Module({
1290
+ * When the environment holds a usable config, its placeholder values are removed (see
1291
+ * {@link usableTwilioServiceConfig}), so a placeholder Messaging Service SID or API key never shadows a real
1292
+ * sender or auth token. Otherwise the placeholder values are kept, which lets the app start; {@link TwilioApi}
1293
+ * only creates the SDK client when it is first used.
1294
+ *
1295
+ * @param configService - NestJS config service for reading environment variables.
1296
+ * @param webhookUrlsConfig - The app's public webhook URLs. When set, outbound texts request status callbacks at its status callback URL.
1297
+ * @returns A validated {@link TwilioServiceConfig}.
1298
+ */ function twilioServiceConfigFactory(configService, webhookUrlsConfig) {
1299
+ var _usableTwilioServiceConfig, _ref;
1300
+ var _webhookUrlsConfig_twilioWebhookUrls;
1301
+ var envConfig = twilioServiceConfigFromConfigService(configService);
1302
+ var config = (_usableTwilioServiceConfig = usableTwilioServiceConfig(envConfig)) !== null && _usableTwilioServiceConfig !== void 0 ? _usableTwilioServiceConfig : envConfig;
1303
+ var defaultStatusCallback = (_ref = webhookUrlsConfig === null || webhookUrlsConfig === void 0 ? void 0 : (_webhookUrlsConfig_twilioWebhookUrls = webhookUrlsConfig.twilioWebhookUrls) === null || _webhookUrlsConfig_twilioWebhookUrls === void 0 ? void 0 : _webhookUrlsConfig_twilioWebhookUrls.statusCallbackUrl) !== null && _ref !== void 0 ? _ref : config.messages.defaultStatusCallback;
1304
+ var result = _object_spread_props$1(_object_spread$2({}, config), {
1305
+ messages: _object_spread_props$1(_object_spread$2({}, config.messages), {
1306
+ defaultStatusCallback: defaultStatusCallback
1307
+ })
1308
+ });
1309
+ TwilioServiceConfig.assertValidConfig(result);
1310
+ return result;
1311
+ }
1312
+ /**
1313
+ * Convenience function used to generate ModuleMetadata for an app's Twilio module, which provides
1314
+ * {@link TwilioApi} and {@link TwilioService} for sending SMS.
1315
+ *
1316
+ * Reads the Twilio credentials and default sender from environment variables.
1317
+ *
1318
+ * @param config - The module metadata configuration including the optional dependency module.
1319
+ * @returns NestJS ModuleMetadata for registering the Twilio module.
1320
+ */ function appTwilioModuleMetadata(config) {
1321
+ var _ref = config !== null && config !== void 0 ? config : {}, dependencyModule = _ref.dependencyModule, imports = _ref.imports, exports = _ref.exports, providers = _ref.providers;
1322
+ var dependencyModuleImport = dependencyModule ? [
1323
+ dependencyModule
1324
+ ] : [];
1325
+ return {
549
1326
  imports: [
550
1327
  ConfigModule
551
- ],
1328
+ ].concat(_to_consumable_array$4(dependencyModuleImport), _to_consumable_array$4(imports !== null && imports !== void 0 ? imports : [])),
1329
+ exports: [
1330
+ TwilioApi,
1331
+ TwilioService
1332
+ ].concat(_to_consumable_array$4(exports !== null && exports !== void 0 ? exports : [])),
552
1333
  providers: [
553
1334
  {
554
1335
  provide: TwilioServiceConfig,
555
1336
  inject: [
556
- ConfigService
1337
+ ConfigService,
1338
+ {
1339
+ token: TwilioWebhookUrlsConfig,
1340
+ optional: true
1341
+ }
557
1342
  ],
558
1343
  useFactory: twilioServiceConfigFactory
559
1344
  },
560
1345
  TwilioApi,
561
1346
  TwilioService
562
- ],
563
- exports: [
564
- TwilioApi,
565
- TwilioService
566
- ]
567
- })
1347
+ ].concat(_to_consumable_array$4(providers !== null && providers !== void 0 ? providers : []))
1348
+ };
1349
+ }
1350
+ /**
1351
+ * NestJS module that provides the {@link TwilioApi} and {@link TwilioService} for sending SMS
1352
+ * via Twilio.
1353
+ *
1354
+ * Reads Twilio credentials and default sender configuration from environment variables. Use
1355
+ * {@link appTwilioModuleMetadata} to supply the app's webhook URLs.
1356
+ */ var TwilioModule = function TwilioModule() {
1357
+ _class_call_check$b(this, TwilioModule);
1358
+ };
1359
+ TwilioModule = __decorate([
1360
+ Module(appTwilioModuleMetadata())
568
1361
  ], TwilioModule);
569
1362
 
570
1363
  function _class_call_check$a(instance, Constructor) {
@@ -878,30 +1671,67 @@ TwilioLookupService = __decorate([
878
1671
  __param(0, Inject(TwilioLookupApi))
879
1672
  ], TwilioLookupService);
880
1673
 
1674
+ function _array_like_to_array$3(arr, len) {
1675
+ if (len == null || len > arr.length) len = arr.length;
1676
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
1677
+ return arr2;
1678
+ }
1679
+ function _array_without_holes$3(arr) {
1680
+ if (Array.isArray(arr)) return _array_like_to_array$3(arr);
1681
+ }
881
1682
  function _class_call_check$8(instance, Constructor) {
882
1683
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
883
1684
  }
1685
+ function _iterable_to_array$3(iter) {
1686
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
1687
+ return Array.from(iter);
1688
+ }
1689
+ }
1690
+ function _non_iterable_spread$3() {
1691
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
1692
+ }
1693
+ function _to_consumable_array$3(arr) {
1694
+ return _array_without_holes$3(arr) || _iterable_to_array$3(arr) || _unsupported_iterable_to_array$3(arr) || _non_iterable_spread$3();
1695
+ }
1696
+ function _unsupported_iterable_to_array$3(o, minLen) {
1697
+ if (!o) return;
1698
+ if (typeof o === "string") return _array_like_to_array$3(o, minLen);
1699
+ var n = Object.prototype.toString.call(o).slice(8, -1);
1700
+ if (n === "Object" && o.constructor) n = o.constructor.name;
1701
+ if (n === "Map" || n === "Set") return Array.from(n);
1702
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$3(o, minLen);
1703
+ }
884
1704
  /**
885
- * NestJS module that exposes Twilio Lookup v2 (phone validation, carrier info).
1705
+ * Convenience function used to generate ModuleMetadata for an app's Twilio Lookup v2 (phone validation, carrier info) module.
886
1706
  *
887
- * Imports {@link TwilioModule} for the underlying Twilio client.
888
- */ var TwilioLookupModule = function TwilioLookupModule() {
889
- _class_call_check$8(this, TwilioLookupModule);
890
- };
891
- TwilioLookupModule = __decorate([
892
- Module({
1707
+ * @param config - The module metadata configuration including the dependency module.
1708
+ * @returns NestJS ModuleMetadata for registering the Twilio Lookup module.
1709
+ */ function appTwilioLookupModuleMetadata(config) {
1710
+ var _ref = config !== null && config !== void 0 ? config : {}, dependencyModule = _ref.dependencyModule, imports = _ref.imports, exports = _ref.exports, providers = _ref.providers;
1711
+ return {
893
1712
  imports: [
894
- TwilioModule
895
- ],
896
- providers: [
1713
+ dependencyModule !== null && dependencyModule !== void 0 ? dependencyModule : TwilioModule
1714
+ ].concat(_to_consumable_array$3(imports !== null && imports !== void 0 ? imports : [])),
1715
+ exports: [
897
1716
  TwilioLookupApi,
898
1717
  TwilioLookupService
899
- ],
900
- exports: [
1718
+ ].concat(_to_consumable_array$3(exports !== null && exports !== void 0 ? exports : [])),
1719
+ providers: [
901
1720
  TwilioLookupApi,
902
1721
  TwilioLookupService
903
- ]
904
- })
1722
+ ].concat(_to_consumable_array$3(providers !== null && providers !== void 0 ? providers : []))
1723
+ };
1724
+ }
1725
+ /**
1726
+ * NestJS module that exposes Twilio Lookup v2 (phone validation, carrier info).
1727
+ *
1728
+ * Imports {@link TwilioModule} for the underlying Twilio client. Use {@link appTwilioLookupModuleMetadata}
1729
+ * to use the app's own Twilio module instead.
1730
+ */ var TwilioLookupModule = function TwilioLookupModule() {
1731
+ _class_call_check$8(this, TwilioLookupModule);
1732
+ };
1733
+ TwilioLookupModule = __decorate([
1734
+ Module(appTwilioLookupModuleMetadata())
905
1735
  ], TwilioLookupModule);
906
1736
 
907
1737
  function _class_call_check$7(instance, Constructor) {
@@ -1287,9 +2117,36 @@ TwilioVerifyService = __decorate([
1287
2117
  __param(0, Inject(TwilioVerifyApi))
1288
2118
  ], TwilioVerifyService);
1289
2119
 
2120
+ function _array_like_to_array$2(arr, len) {
2121
+ if (len == null || len > arr.length) len = arr.length;
2122
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
2123
+ return arr2;
2124
+ }
2125
+ function _array_without_holes$2(arr) {
2126
+ if (Array.isArray(arr)) return _array_like_to_array$2(arr);
2127
+ }
1290
2128
  function _class_call_check$4(instance, Constructor) {
1291
2129
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
1292
2130
  }
2131
+ function _iterable_to_array$2(iter) {
2132
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
2133
+ return Array.from(iter);
2134
+ }
2135
+ }
2136
+ function _non_iterable_spread$2() {
2137
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
2138
+ }
2139
+ function _to_consumable_array$2(arr) {
2140
+ return _array_without_holes$2(arr) || _iterable_to_array$2(arr) || _unsupported_iterable_to_array$2(arr) || _non_iterable_spread$2();
2141
+ }
2142
+ function _unsupported_iterable_to_array$2(o, minLen) {
2143
+ if (!o) return;
2144
+ if (typeof o === "string") return _array_like_to_array$2(o, minLen);
2145
+ var n = Object.prototype.toString.call(o).slice(8, -1);
2146
+ if (n === "Object" && o.constructor) n = o.constructor.name;
2147
+ if (n === "Map" || n === "Set") return Array.from(n);
2148
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$2(o, minLen);
2149
+ }
1293
2150
  /**
1294
2151
  * Factory that creates a {@link TwilioVerifyServiceConfig} from environment variables.
1295
2152
  *
@@ -1305,19 +2162,21 @@ function _class_call_check$4(instance, Constructor) {
1305
2162
  return config;
1306
2163
  }
1307
2164
  /**
1308
- * NestJS module that exposes Twilio Verify (OTP / 2FA) for starting verifications and
1309
- * checking submitted codes.
2165
+ * Convenience function used to generate ModuleMetadata for an app's Twilio Verify (OTP / 2FA) module.
1310
2166
  *
1311
- * Imports {@link TwilioModule} for the underlying Twilio client.
1312
- */ var TwilioVerifyModule = function TwilioVerifyModule() {
1313
- _class_call_check$4(this, TwilioVerifyModule);
1314
- };
1315
- TwilioVerifyModule = __decorate([
1316
- Module({
2167
+ * @param config - The module metadata configuration including the dependency module.
2168
+ * @returns NestJS ModuleMetadata for registering the Twilio Verify module.
2169
+ */ function appTwilioVerifyModuleMetadata(config) {
2170
+ var _ref = config !== null && config !== void 0 ? config : {}, dependencyModule = _ref.dependencyModule, imports = _ref.imports, exports = _ref.exports, providers = _ref.providers;
2171
+ return {
1317
2172
  imports: [
1318
2173
  ConfigModule,
1319
- TwilioModule
1320
- ],
2174
+ dependencyModule !== null && dependencyModule !== void 0 ? dependencyModule : TwilioModule
2175
+ ].concat(_to_consumable_array$2(imports !== null && imports !== void 0 ? imports : [])),
2176
+ exports: [
2177
+ TwilioVerifyApi,
2178
+ TwilioVerifyService
2179
+ ].concat(_to_consumable_array$2(exports !== null && exports !== void 0 ? exports : [])),
1321
2180
  providers: [
1322
2181
  {
1323
2182
  provide: TwilioVerifyServiceConfig,
@@ -1328,12 +2187,20 @@ TwilioVerifyModule = __decorate([
1328
2187
  },
1329
2188
  TwilioVerifyApi,
1330
2189
  TwilioVerifyService
1331
- ],
1332
- exports: [
1333
- TwilioVerifyApi,
1334
- TwilioVerifyService
1335
- ]
1336
- })
2190
+ ].concat(_to_consumable_array$2(providers !== null && providers !== void 0 ? providers : []))
2191
+ };
2192
+ }
2193
+ /**
2194
+ * NestJS module that exposes Twilio Verify (OTP / 2FA) for starting verifications and
2195
+ * checking submitted codes.
2196
+ *
2197
+ * Imports {@link TwilioModule} for the underlying Twilio client. Use {@link appTwilioVerifyModuleMetadata}
2198
+ * to use the app's own Twilio module instead.
2199
+ */ var TwilioVerifyModule = function TwilioVerifyModule() {
2200
+ _class_call_check$4(this, TwilioVerifyModule);
2201
+ };
2202
+ TwilioVerifyModule = __decorate([
2203
+ Module(appTwilioVerifyModuleMetadata())
1337
2204
  ], TwilioVerifyModule);
1338
2205
 
1339
2206
  function _define_property$3(obj, key, value) {
@@ -1400,6 +2267,125 @@ var twilioWebhookEventHandlerConfigurerFactory = handlerConfigurerFactory({
1400
2267
  }
1401
2268
  });
1402
2269
 
2270
+ function _array_like_to_array$1(arr, len) {
2271
+ if (len == null || len > arr.length) len = arr.length;
2272
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
2273
+ return arr2;
2274
+ }
2275
+ function _array_without_holes$1(arr) {
2276
+ if (Array.isArray(arr)) return _array_like_to_array$1(arr);
2277
+ }
2278
+ function _iterable_to_array$1(iter) {
2279
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
2280
+ return Array.from(iter);
2281
+ }
2282
+ }
2283
+ function _non_iterable_spread$1() {
2284
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
2285
+ }
2286
+ function _to_consumable_array$1(arr) {
2287
+ return _array_without_holes$1(arr) || _iterable_to_array$1(arr) || _unsupported_iterable_to_array$1(arr) || _non_iterable_spread$1();
2288
+ }
2289
+ function _unsupported_iterable_to_array$1(o, minLen) {
2290
+ if (!o) return;
2291
+ if (typeof o === "string") return _array_like_to_array$1(o, minLen);
2292
+ var n = Object.prototype.toString.call(o).slice(8, -1);
2293
+ if (n === "Object" && o.constructor) n = o.constructor.name;
2294
+ if (n === "Map" || n === "Set") return Array.from(n);
2295
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$1(o, minLen);
2296
+ }
2297
+ /**
2298
+ * The opt-out keyword type Twilio reports on an incoming message (`OptOutType`).
2299
+ *
2300
+ * https://help.twilio.com/articles/31560110671259
2301
+ */ var TwilioOptOutType;
2302
+ (function(TwilioOptOutType) {
2303
+ /**
2304
+ * The sender opted out. Twilio blocks every message to them until they opt back in.
2305
+ */ TwilioOptOutType["STOP"] = "STOP";
2306
+ /**
2307
+ * The sender opted back in.
2308
+ */ TwilioOptOutType["START"] = "START";
2309
+ /**
2310
+ * The sender asked for help.
2311
+ */ TwilioOptOutType["HELP"] = "HELP";
2312
+ })(TwilioOptOutType || (TwilioOptOutType = {}));
2313
+ /**
2314
+ * Twilio's default opt-out keywords.
2315
+ */ var DEFAULT_TWILIO_OPT_OUT_KEYWORDS = [
2316
+ 'STOP',
2317
+ 'STOPALL',
2318
+ 'UNSUBSCRIBE',
2319
+ 'CANCEL',
2320
+ 'END',
2321
+ 'QUIT',
2322
+ 'REVOKE',
2323
+ 'OPTOUT'
2324
+ ];
2325
+ /**
2326
+ * Twilio's default opt-in keywords.
2327
+ */ var DEFAULT_TWILIO_OPT_IN_KEYWORDS = [
2328
+ 'START',
2329
+ 'YES',
2330
+ 'UNSTOP'
2331
+ ];
2332
+ /**
2333
+ * Twilio's default help keywords.
2334
+ */ var DEFAULT_TWILIO_HELP_KEYWORDS = [
2335
+ 'HELP',
2336
+ 'INFO'
2337
+ ];
2338
+ var DEFAULT_TWILIO_OPT_OUT_TYPE_BY_KEYWORD = new Map(_to_consumable_array$1(DEFAULT_TWILIO_OPT_OUT_KEYWORDS.map(function(x) {
2339
+ return [
2340
+ x,
2341
+ TwilioOptOutType.STOP
2342
+ ];
2343
+ })).concat(_to_consumable_array$1(DEFAULT_TWILIO_OPT_IN_KEYWORDS.map(function(x) {
2344
+ return [
2345
+ x,
2346
+ TwilioOptOutType.START
2347
+ ];
2348
+ })), _to_consumable_array$1(DEFAULT_TWILIO_HELP_KEYWORDS.map(function(x) {
2349
+ return [
2350
+ x,
2351
+ TwilioOptOutType.HELP
2352
+ ];
2353
+ }))));
2354
+ /**
2355
+ * Reads a {@link TwilioOptOutType} from a string, case-insensitively.
2356
+ *
2357
+ * @param value - The value to read, such as Twilio's `OptOutType` parameter.
2358
+ * @returns The opt-out type, or undefined when the value is not one.
2359
+ */ function twilioOptOutTypeFromString(value) {
2360
+ var normalized = value === null || value === void 0 ? void 0 : value.trim().toUpperCase();
2361
+ var result;
2362
+ switch(normalized){
2363
+ case TwilioOptOutType.STOP:
2364
+ case TwilioOptOutType.START:
2365
+ case TwilioOptOutType.HELP:
2366
+ result = normalized;
2367
+ break;
2368
+ }
2369
+ return result;
2370
+ }
2371
+ /**
2372
+ * Returns the opt-out keyword type of an incoming message, if it is one.
2373
+ *
2374
+ * Twilio's `OptOutType` parameter wins when present. It is only sent for messages to a Messaging Service, or to a number with
2375
+ * Advanced Opt-Out. Otherwise the body is matched, trimmed and case-insensitively, against Twilio's default keywords. Only a whole-body
2376
+ * match counts, so "Stop please" is not a keyword, matching how Twilio itself handles keywords.
2377
+ *
2378
+ * @param payload - The incoming message.
2379
+ * @param config - Whether to skip the default keyword fallback.
2380
+ * @returns The opt-out type, or undefined when the message is not a keyword.
2381
+ */ function twilioIncomingMessageOptOutType(payload, config) {
2382
+ var result = payload.OptOutType;
2383
+ if (result == null && !(config === null || config === void 0 ? void 0 : config.optOutTypeOnly)) {
2384
+ result = DEFAULT_TWILIO_OPT_OUT_TYPE_BY_KEYWORD.get(payload.Body.trim().toUpperCase());
2385
+ }
2386
+ return result;
2387
+ }
2388
+
1403
2389
  function _class_call_check$3(instance, Constructor) {
1404
2390
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
1405
2391
  }
@@ -1415,7 +2401,6 @@ function _define_property$2(obj, key, value) {
1415
2401
  return obj;
1416
2402
  }
1417
2403
  var TWILIO_WEBHOOK_AUTH_TOKEN_ENV_VAR = 'TWILIO_WEBHOOK_AUTH_TOKEN';
1418
- var TWILIO_WEBHOOK_BASE_URL_ENV_VAR = 'TWILIO_WEBHOOK_BASE_URL';
1419
2404
  var TWILIO_WEBHOOK_SKIP_VERIFY_ENV_VAR = 'TWILIO_WEBHOOK_SKIP_VERIFY';
1420
2405
  /**
1421
2406
  * Configuration for the Twilio webhook controller / service.
@@ -1642,21 +2627,20 @@ function _ts_generator$1(thisArg, body) {
1642
2627
  }
1643
2628
  }
1644
2629
  var TwilioWebhookService = /*#__PURE__*/ function() {
1645
- function TwilioWebhookService(twilioApi, webhookConfig) {
2630
+ function TwilioWebhookService(webhookConfig) {
1646
2631
  _class_call_check$2(this, TwilioWebhookService);
1647
- var _webhookConfig_twilioWebhook_authToken;
1648
2632
  _define_property$1(this, "logger", new Logger('TwilioWebhookService'));
1649
2633
  _define_property$1(this, "_verifier", void 0);
1650
2634
  _define_property$1(this, "handler", twilioWebhookEventHandlerFactory());
1651
2635
  _define_property$1(this, "configure", twilioWebhookEventHandlerConfigurerFactory(this.handler));
1652
- var authToken = (_webhookConfig_twilioWebhook_authToken = webhookConfig.twilioWebhook.authToken) !== null && _webhookConfig_twilioWebhook_authToken !== void 0 ? _webhookConfig_twilioWebhook_authToken : twilioApi.config.twilio.authToken;
2636
+ var _webhookConfig_twilioWebhook = webhookConfig.twilioWebhook, authToken = _webhookConfig_twilioWebhook.authToken, baseUrl = _webhookConfig_twilioWebhook.baseUrl, skipVerify = _webhookConfig_twilioWebhook.skipVerify;
1653
2637
  if (!authToken) {
1654
2638
  throw new Error('TwilioWebhookService: authToken is required (set TWILIO_AUTH_TOKEN or TWILIO_WEBHOOK_AUTH_TOKEN).');
1655
2639
  }
1656
2640
  this._verifier = twilioWebhookVerifier({
1657
2641
  authToken: authToken,
1658
- baseUrl: webhookConfig.twilioWebhook.baseUrl,
1659
- skip: webhookConfig.twilioWebhook.skipVerify
2642
+ baseUrl: baseUrl,
2643
+ skip: skipVerify
1660
2644
  });
1661
2645
  }
1662
2646
  _create_class$1(TwilioWebhookService, [
@@ -1745,6 +2729,8 @@ var TwilioWebhookService = /*#__PURE__*/ function() {
1745
2729
  FromState: params['FromState'],
1746
2730
  FromZip: params['FromZip'],
1747
2731
  FromCountry: params['FromCountry'],
2732
+ MessagingServiceSid: params['MessagingServiceSid'],
2733
+ OptOutType: twilioOptOutTypeFromString(params['OptOutType']),
1748
2734
  mediaUrls: mediaUrls,
1749
2735
  raw: params
1750
2736
  }
@@ -1801,8 +2787,7 @@ var TwilioWebhookService = /*#__PURE__*/ function() {
1801
2787
  }();
1802
2788
  TwilioWebhookService = __decorate([
1803
2789
  Injectable(),
1804
- __param(0, Inject(TwilioApi)),
1805
- __param(1, Inject(TwilioWebhookServiceConfig))
2790
+ __param(0, Inject(TwilioWebhookServiceConfig))
1806
2791
  ], TwilioWebhookService);
1807
2792
 
1808
2793
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
@@ -2016,74 +3001,141 @@ var TwilioWebhookController = /*#__PURE__*/ function() {
2016
3001
  return TwilioWebhookController;
2017
3002
  }();
2018
3003
  __decorate([
2019
- Post('status'),
3004
+ Post(TWILIO_WEBHOOK_STATUS_PATH),
2020
3005
  __param(0, Req()),
2021
3006
  __param(1, RawBody())
2022
3007
  ], TwilioWebhookController.prototype, "handleStatus", null);
2023
3008
  __decorate([
2024
- Post('incoming'),
3009
+ Post(TWILIO_WEBHOOK_INCOMING_PATH),
2025
3010
  __param(0, Req()),
2026
3011
  __param(1, RawBody())
2027
3012
  ], TwilioWebhookController.prototype, "handleIncoming", null);
2028
3013
  TwilioWebhookController = __decorate([
2029
- Controller('/webhook/twilio'),
3014
+ Controller("".concat(DEFAULT_BASE_WEBHOOK_PATH).concat(TWILIO_WEBHOOK_PATH)),
2030
3015
  __param(0, Inject(TwilioWebhookService))
2031
3016
  ], TwilioWebhookController);
2032
3017
 
3018
+ function _array_like_to_array(arr, len) {
3019
+ if (len == null || len > arr.length) len = arr.length;
3020
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
3021
+ return arr2;
3022
+ }
3023
+ function _array_without_holes(arr) {
3024
+ if (Array.isArray(arr)) return _array_like_to_array(arr);
3025
+ }
2033
3026
  function _class_call_check(instance, Constructor) {
2034
3027
  if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
2035
3028
  }
3029
+ function _iterable_to_array(iter) {
3030
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) {
3031
+ return Array.from(iter);
3032
+ }
3033
+ }
3034
+ function _non_iterable_spread() {
3035
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
3036
+ }
3037
+ function _to_consumable_array(arr) {
3038
+ return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
3039
+ }
3040
+ function _unsupported_iterable_to_array(o, minLen) {
3041
+ if (!o) return;
3042
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
3043
+ var n = Object.prototype.toString.call(o).slice(8, -1);
3044
+ if (n === "Object" && o.constructor) n = o.constructor.name;
3045
+ if (n === "Map" || n === "Set") return Array.from(n);
3046
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
3047
+ }
2036
3048
  /**
2037
3049
  * Factory that creates a {@link TwilioWebhookServiceConfig} from environment variables.
2038
3050
  *
3051
+ * The auth token is read from `TWILIO_WEBHOOK_AUTH_TOKEN`, falling back to `TWILIO_AUTH_TOKEN`. A real
3052
+ * value is preferred over a placeholder one (see {@link isPlaceholderTwilioConfigValue}), so a placeholder
3053
+ * webhook token does not shadow a real auth token. When both are placeholders the placeholder is kept,
3054
+ * which lets the app start and rejects every request as unverified.
3055
+ *
3056
+ * Requests are verified against the app's public webhook URL when `webhookUrlsConfig` is set, and otherwise
3057
+ * against the URL rebuilt from the request headers.
3058
+ *
2039
3059
  * @param configService - NestJS config service for reading environment variables.
3060
+ * @param webhookUrlsConfig - The app's public webhook URLs.
2040
3061
  * @returns A populated {@link TwilioWebhookServiceConfig}.
2041
- */ function twilioWebhookServiceConfigFactory(configService) {
3062
+ */ function twilioWebhookServiceConfigFactory(configService, webhookUrlsConfig) {
3063
+ var _ref, _find;
3064
+ var _webhookUrlsConfig_twilioWebhookUrls;
3065
+ var webhookAuthToken = configService.get(TWILIO_WEBHOOK_AUTH_TOKEN_ENV_VAR);
3066
+ var accountAuthToken = configService.get(TWILIO_AUTH_TOKEN_ENV_VAR);
3067
+ var authToken = (_ref = (_find = [
3068
+ webhookAuthToken,
3069
+ accountAuthToken
3070
+ ].find(function(x) {
3071
+ return !isPlaceholderTwilioConfigValue(x);
3072
+ })) !== null && _find !== void 0 ? _find : webhookAuthToken) !== null && _ref !== void 0 ? _ref : accountAuthToken;
2042
3073
  var config = {
2043
3074
  twilioWebhook: {
2044
- authToken: configService.get(TWILIO_WEBHOOK_AUTH_TOKEN_ENV_VAR),
2045
- baseUrl: configService.get(TWILIO_WEBHOOK_BASE_URL_ENV_VAR),
3075
+ authToken: authToken,
3076
+ baseUrl: webhookUrlsConfig === null || webhookUrlsConfig === void 0 ? void 0 : (_webhookUrlsConfig_twilioWebhookUrls = webhookUrlsConfig.twilioWebhookUrls) === null || _webhookUrlsConfig_twilioWebhookUrls === void 0 ? void 0 : _webhookUrlsConfig_twilioWebhookUrls.baseUrl,
2046
3077
  skipVerify: configService.get(TWILIO_WEBHOOK_SKIP_VERIFY_ENV_VAR) === 'true'
2047
3078
  }
2048
3079
  };
2049
3080
  return config;
2050
3081
  }
2051
3082
  /**
2052
- * NestJS module that handles incoming Twilio webhook requests at:
3083
+ * Convenience function used to generate ModuleMetadata for an app's Twilio webhook module, which handles
3084
+ * incoming Twilio webhook requests at:
2053
3085
  * - `POST /webhook/twilio/status` (Message status callbacks)
2054
3086
  * - `POST /webhook/twilio/incoming` (Incoming SMS / MMS messages)
2055
3087
  *
2056
- * Webhook bodies are verified against the X-Twilio-Signature header before dispatch.
3088
+ * Webhook bodies are verified against the X-Twilio-Signature header before dispatch. Only the auth
3089
+ * token is needed for that, so this module does not depend on the Twilio module and can be imported in
3090
+ * environments without a usable Twilio sending config.
2057
3091
  *
2058
3092
  * Requires the consuming application to register the raw-body middleware from
2059
3093
  * `@dereekb/nestjs` for the `/webhook/twilio` path so that signature verification can read
2060
3094
  * the unparsed form body.
2061
- */ var TwilioWebhookModule = function TwilioWebhookModule() {
2062
- _class_call_check(this, TwilioWebhookModule);
2063
- };
2064
- TwilioWebhookModule = __decorate([
2065
- Module({
3095
+ *
3096
+ * Register handlers by injecting the exported {@link TwilioWebhookService} into a provider passed in `providers`.
3097
+ *
3098
+ * @param config - The module metadata configuration including the optional dependency module.
3099
+ * @returns NestJS ModuleMetadata for registering the Twilio webhook module.
3100
+ */ function appTwilioWebhookModuleMetadata(config) {
3101
+ var _ref = config !== null && config !== void 0 ? config : {}, dependencyModule = _ref.dependencyModule, imports = _ref.imports, exports = _ref.exports, providers = _ref.providers;
3102
+ var dependencyModuleImport = dependencyModule ? [
3103
+ dependencyModule
3104
+ ] : [];
3105
+ return {
2066
3106
  imports: [
2067
- ConfigModule,
2068
- TwilioModule
2069
- ],
3107
+ ConfigModule
3108
+ ].concat(_to_consumable_array(dependencyModuleImport), _to_consumable_array(imports !== null && imports !== void 0 ? imports : [])),
2070
3109
  controllers: [
2071
3110
  TwilioWebhookController
2072
3111
  ],
3112
+ exports: [
3113
+ TwilioWebhookService
3114
+ ].concat(_to_consumable_array(exports !== null && exports !== void 0 ? exports : [])),
2073
3115
  providers: [
2074
3116
  {
2075
3117
  provide: TwilioWebhookServiceConfig,
2076
3118
  inject: [
2077
- ConfigService
3119
+ ConfigService,
3120
+ {
3121
+ token: TwilioWebhookUrlsConfig,
3122
+ optional: true
3123
+ }
2078
3124
  ],
2079
3125
  useFactory: twilioWebhookServiceConfigFactory
2080
3126
  },
2081
3127
  TwilioWebhookService
2082
- ],
2083
- exports: [
2084
- TwilioWebhookService
2085
- ]
2086
- })
3128
+ ].concat(_to_consumable_array(providers !== null && providers !== void 0 ? providers : []))
3129
+ };
3130
+ }
3131
+ /**
3132
+ * NestJS module that handles incoming Twilio webhook requests at `/webhook/twilio`. See
3133
+ * {@link appTwilioWebhookModuleMetadata}, which also lets the app supply its public webhook URLs.
3134
+ */ var TwilioWebhookModule = function TwilioWebhookModule() {
3135
+ _class_call_check(this, TwilioWebhookModule);
3136
+ };
3137
+ TwilioWebhookModule = __decorate([
3138
+ Module(appTwilioWebhookModuleMetadata())
2087
3139
  ], TwilioWebhookModule);
2088
3140
 
2089
- export { DEFAULT_TWILIO_SEND_SMS_MAX_PARALLEL_TASKS, TWILIO_ACCOUNT_SID_ENV_VAR, TWILIO_API_KEY_SECRET_ENV_VAR, TWILIO_API_KEY_SID_ENV_VAR, TWILIO_AUTH_TOKEN_ENV_VAR, TWILIO_MESSAGING_SERVICE_SID_ENV_VAR, TWILIO_PHONE_NUMBER_ENV_VAR, TWILIO_SANDBOX_ENV_VAR, TWILIO_STATUS_CALLBACK_URL_ENV_VAR, TWILIO_VERIFY_SERVICE_SID_ENV_VAR, TWILIO_WEBHOOK_AUTH_TOKEN_ENV_VAR, TWILIO_WEBHOOK_BASE_URL_ENV_VAR, TWILIO_WEBHOOK_SKIP_VERIFY_ENV_VAR, TwilioApi, TwilioLookupApi, TwilioLookupModule, TwilioLookupService, TwilioModule, TwilioService, TwilioServiceConfig, TwilioVerifyApi, TwilioVerifyModule, TwilioVerifyService, TwilioVerifyServiceConfig, TwilioWebhookController, TwilioWebhookModule, TwilioWebhookService, TwilioWebhookServiceConfig, parseTwilioFormBody, twilioServiceConfigFactory, twilioVerifyServiceConfigFactory, twilioWebhookEventHandlerConfigurerFactory, twilioWebhookEventHandlerFactory, twilioWebhookRequestUrl, twilioWebhookServiceConfigFactory, twilioWebhookVerifier };
3141
+ export { DEFAULT_TWILIO_HELP_KEYWORDS, DEFAULT_TWILIO_OPT_IN_KEYWORDS, DEFAULT_TWILIO_OPT_OUT_KEYWORDS, DEFAULT_TWILIO_RECENT_MESSAGES_LIMIT, DEFAULT_TWILIO_RECENT_MESSAGES_WINDOW_DAYS, DEFAULT_TWILIO_SEND_SMS_MAX_PARALLEL_TASKS, TWILIO_ACCOUNT_SID_ENV_VAR, TWILIO_API_KEY_SECRET_ENV_VAR, TWILIO_API_KEY_SID_ENV_VAR, TWILIO_AUTH_TOKEN_ENV_VAR, TWILIO_MESSAGING_SERVICE_SID_ENV_VAR, TWILIO_PHONE_NUMBER_ENV_VAR, TWILIO_PLACEHOLDER_CONFIG_VALUES, TWILIO_SANDBOX_ENV_VAR, TWILIO_VERIFY_SERVICE_SID_ENV_VAR, TWILIO_WEBHOOK_AUTH_TOKEN_ENV_VAR, TWILIO_WEBHOOK_INCOMING_PATH, TWILIO_WEBHOOK_PATH, TWILIO_WEBHOOK_SKIP_VERIFY_ENV_VAR, TWILIO_WEBHOOK_STATUS_PATH, TwilioApi, TwilioLookupApi, TwilioLookupModule, TwilioLookupService, TwilioMessageErrorCode, TwilioModule, TwilioOptOutType, TwilioService, TwilioServiceConfig, TwilioVerifyApi, TwilioVerifyModule, TwilioVerifyService, TwilioVerifyServiceConfig, TwilioWebhookController, TwilioWebhookModule, TwilioWebhookService, TwilioWebhookServiceConfig, TwilioWebhookUrlsConfig, appTwilioLookupModuleMetadata, appTwilioModuleMetadata, appTwilioVerifyModuleMetadata, appTwilioWebhookModuleMetadata, isPlaceholderTwilioConfigValue, isTwilioDiagnosticPermissionError, isUsableTwilioServiceConfig, parseTwilioFormBody, twilioAccountState, twilioDiagnosticError, twilioDiagnosticMessage, twilioDiagnosticMessageDate, twilioErrorCode, twilioErrorStatus, twilioIncomingMessageOptOutType, twilioLookupPhoneNumberForDiagnosis, twilioMessageForSid, twilioOptOutTypeFromString, twilioRecentMessagesForRecipient, twilioServiceConfigFactory, twilioServiceConfigFromConfigService, twilioVerifyServiceConfigFactory, twilioWebhookEventHandlerConfigurerFactory, twilioWebhookEventHandlerFactory, twilioWebhookRequestUrl, twilioWebhookServiceConfigFactory, twilioWebhookUrls, twilioWebhookVerifier, usableTwilioServiceConfig };