@drawbridge/drawbridge-utils 0.0.133 → 0.0.135

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/twilio.d.ts CHANGED
@@ -70,14 +70,23 @@ const twilio = {
70
70
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
71
71
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
72
72
  // IncomingPhoneNumbers resource docs:
73
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
74
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
73
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
74
+ // query: SmsEnabled, Contains, PageSize (1-1000)
75
75
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
76
76
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
77
77
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
78
78
  // Basic auth throughout, like Messages.
79
+ //
80
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
81
+ // than an inventory one. A US local number may not send A2P SMS without full
82
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
83
+ // Toll-Free Verification — one API submission per number (see
84
+ // submitVerification below), no TCR brand, no campaign fees. Until a
85
+ // verification is approved, Twilio blocks the number's US-bound messages
86
+ // outright — which is why a purchased number starts `pending` and the send
87
+ // path refuses anything but `verified`.
79
88
 
80
- searchNumbers : async ({ accountSid, areaCode, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
89
+ searchNumbers : async ({ accountSid, authToken, contains, country = 'US', cursor, limit = 10, request: request$1 = request }) => {
81
90
 
82
91
  if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
83
92
 
@@ -93,17 +102,17 @@ const twilio = {
93
102
 
94
103
  const result = await request$1({
95
104
  method : 'GET',
96
- url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/Local.json',
105
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/TollFree.json',
97
106
  headers : {
98
107
  'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
99
108
  },
100
109
  query : {
101
110
  SmsEnabled : true,
102
111
  PageSize : limit,
103
- ...( areaCode && { AreaCode : areaCode } ),
104
112
  // `Contains` is the vendor's matching-pattern filter — digits
105
- // match anywhere in the number, so one search box serves "my
106
- // area code" and "ends in 7777" alike.
113
+ // match anywhere in the number, so one search box serves "an 888
114
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
115
+ // filter and went with Local.json.)
107
116
  ...( contains && { Contains : contains } ),
108
117
  ...( page && { Page : page } )
109
118
  }
@@ -150,6 +159,73 @@ const twilio = {
150
159
 
151
160
  },
152
161
 
162
+ // THE NUMBER AS TWILIO HOLDS IT NOW. Same IncomingPhoneNumber resource the
163
+ // purchase and release above use (api.twilio.com 2010-04-01, GET
164
+ // /IncomingPhoneNumbers/{sid}.json) — a 404 means the number is no longer
165
+ // on the account, which the health sweep must distinguish from a transient,
166
+ // so it is answered as { gone : true } rather than thrown.
167
+ numberConfig : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
168
+
169
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
170
+ if( ! sid ) throw new Error( 'Twilio config read needs the IncomingPhoneNumber sid' );
171
+
172
+ let result;
173
+
174
+ try {
175
+
176
+ result = await request$1({
177
+ method : 'GET',
178
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers/' + sid + '.json',
179
+ headers : {
180
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
181
+ }
182
+ });
183
+
184
+ } catch ( error ) {
185
+
186
+ if( error?.status === 404 ) return { gone : true };
187
+
188
+ throw error;
189
+
190
+ }
191
+
192
+ return {
193
+ gone : false,
194
+ number : result?.phone_number,
195
+ smsMethod : result?.sms_method,
196
+ smsUrl : result?.sms_url,
197
+ status : result?.status
198
+ };
199
+
200
+ },
201
+
202
+ // RE-POINT AN OWNED NUMBER'S INBOUND WEBHOOK — POST on the same resource,
203
+ // the exact fields purchase sets. The health sweep's repair arm.
204
+ updateNumber : async ({ accountSid, authToken, request: request$1 = request, sid, smsUrl }) => {
205
+
206
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
207
+ if( ! sid ) throw new Error( 'Twilio update needs the IncomingPhoneNumber sid' );
208
+ if( ! smsUrl ) throw new Error( 'Twilio update needs the smsUrl to point the number at' );
209
+
210
+ const result = await request$1({
211
+ method : 'POST',
212
+ type : 'form',
213
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers/' + sid + '.json',
214
+ headers : {
215
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
216
+ },
217
+ body : {
218
+ SmsUrl : smsUrl,
219
+ SmsMethod : 'POST'
220
+ }
221
+ });
222
+
223
+ return {
224
+ smsUrl : result?.sms_url
225
+ };
226
+
227
+ },
228
+
153
229
  releaseNumber : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
154
230
 
155
231
  if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
@@ -163,6 +239,111 @@ const twilio = {
163
239
  }
164
240
  });
165
241
 
242
+ },
243
+
244
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
245
+ // number actually deliver. From the Tollfree Verification resource docs
246
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
247
+ // POST /v1/Tollfree/Verifications
248
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
249
+ // UseCaseCategories (array), UseCaseSummary,
250
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
251
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
252
+ // GET /v1/Tollfree/Verifications/{Sid}
253
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
254
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
255
+ // Basic auth, form-encoded, like everything above. There is no status
256
+ // callback on this resource — the caller polls the fetch.
257
+ //
258
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
259
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
260
+ // ISVs, support.twilio.com) — so businessName, website and email here are
261
+ // the merchant's, collected at purchase.
262
+ //
263
+ // The use-case fields are PLATFORM facts and live here beside the message
264
+ // composition for the same reason it does: how Drawbridge collects opt-in
265
+ // (its own hosted entry forms) and what its messages look like are
266
+ // properties of sending through Drawbridge, not of any one merchant.
267
+ submitVerification : async ({
268
+ accountSid,
269
+ authToken,
270
+ businessName,
271
+ email,
272
+ numberSid,
273
+ optInImage,
274
+ request: request$1 = request,
275
+ volume,
276
+ website
277
+ }) => {
278
+
279
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website })
280
+ .filter( ( [ , value ] ) => ! value )
281
+ .map( ( [ name ] ) => name );
282
+
283
+ if( missing.length ) throw new Error( 'Toll-free verification submission missing — ' + missing.join( ', ' ) );
284
+
285
+ // URLSearchParams rather than an object: UseCaseCategories and
286
+ // OptInImageUrls are array parameters, which Twilio takes as REPEATED
287
+ // form keys — an object body would comma-join them into one value.
288
+ const body = new URLSearchParams();
289
+
290
+ body.append( 'BusinessName', businessName );
291
+ body.append( 'BusinessWebsite', website );
292
+ body.append( 'NotificationEmail', email );
293
+ body.append( 'UseCaseCategories', 'MARKETING' );
294
+ body.append( 'UseCaseSummary', 'Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business\'s Drawbridge-hosted entry form.' );
295
+ // Mirrors the sms() composition above — newline-separated, brand first,
296
+ // opt-out line appended — so the reviewed sample is the sent shape.
297
+ body.append( 'ProductionMessageSample', businessName + '\nThanks for entering! We\'ll text you here about your entry.\nReply STOP to opt out' );
298
+ body.append( 'OptInImageUrls', optInImage );
299
+ // The entry form is a web form; the phone field is beside the consent
300
+ // language on the merchant's campaign page.
301
+ body.append( 'OptInType', 'WEB_FORM' );
302
+ body.append( 'MessageVolume', volume || '1,000' );
303
+ body.append( 'TollfreePhoneNumberSid', numberSid );
304
+
305
+ const result = await request$1({
306
+ method : 'POST',
307
+ type : 'form',
308
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications',
309
+ headers : {
310
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
311
+ },
312
+ body
313
+ });
314
+
315
+ return {
316
+ sid : result?.sid,
317
+ status : result?.status
318
+ };
319
+
320
+ },
321
+
322
+ fetchVerification : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
323
+
324
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
325
+ if( ! sid ) throw new Error( 'Twilio verification fetch needs the verification sid' );
326
+
327
+ const result = await request$1({
328
+ method : 'GET',
329
+ url : 'https://messaging.twilio.com/v1/Tollfree/Verifications/' + sid,
330
+ headers : {
331
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
332
+ }
333
+ });
334
+
335
+ // The caller-facing shape is ours; the vendor's vocabulary stays here.
336
+ return {
337
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
338
+ // review states → pending.
339
+ outcome : result?.status === 'TWILIO_APPROVED'
340
+ ? 'approved'
341
+ : result?.status === 'TWILIO_REJECTED'
342
+ ? 'rejected'
343
+ : 'pending',
344
+ reason : result?.rejection_reason || null
345
+ };
346
+
166
347
  }
167
348
 
168
349
  };
package/dist/twilio.js CHANGED
@@ -82,28 +82,37 @@ var twilio = {
82
82
  // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
83
83
  // parameter names are Twilio's own, from the AvailablePhoneNumbers and
84
84
  // IncomingPhoneNumbers resource docs:
85
- // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
86
- // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
85
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/TollFree.json
86
+ // query: SmsEnabled, Contains, PageSize (1-1000)
87
87
  // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
88
88
  // body: PhoneNumber (E.164), SmsUrl, SmsMethod
89
89
  // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
90
90
  // Basic auth throughout, like Messages.
91
- searchNumbers: async ({ accountSid, areaCode, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
91
+ //
92
+ // TOLL-FREE, not Local, and that is a carrier-registration decision rather
93
+ // than an inventory one. A US local number may not send A2P SMS without full
94
+ // 10DLC brand-and-campaign registration; a toll-free number needs only
95
+ // Toll-Free Verification — one API submission per number (see
96
+ // submitVerification below), no TCR brand, no campaign fees. Until a
97
+ // verification is approved, Twilio blocks the number's US-bound messages
98
+ // outright — which is why a purchased number starts `pending` and the send
99
+ // path refuses anything but `verified`.
100
+ searchNumbers: async ({ accountSid, authToken, contains, country = "US", cursor, limit = 10, request: request2 = request }) => {
92
101
  if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
93
102
  const page = Number(cursor) || 0;
94
103
  const result = await request2({
95
104
  method: "GET",
96
- url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/Local.json",
105
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/TollFree.json",
97
106
  headers: {
98
107
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
99
108
  },
100
109
  query: {
101
110
  SmsEnabled: true,
102
111
  PageSize: limit,
103
- ...areaCode && { AreaCode: areaCode },
104
112
  // `Contains` is the vendor's matching-pattern filter — digits
105
- // match anywhere in the number, so one search box serves "my
106
- // area code" and "ends in 7777" alike.
113
+ // match anywhere in the number, so one search box serves "an 888
114
+ // prefix" and "ends in 7777" alike. (AreaCode is a Local-only
115
+ // filter and went with Local.json.)
107
116
  ...contains && { Contains: contains },
108
117
  ...page && { Page: page }
109
118
  }
@@ -140,6 +149,57 @@ var twilio = {
140
149
  sid: result == null ? void 0 : result.sid
141
150
  };
142
151
  },
152
+ // THE NUMBER AS TWILIO HOLDS IT NOW. Same IncomingPhoneNumber resource the
153
+ // purchase and release above use (api.twilio.com 2010-04-01, GET
154
+ // /IncomingPhoneNumbers/{sid}.json) — a 404 means the number is no longer
155
+ // on the account, which the health sweep must distinguish from a transient,
156
+ // so it is answered as { gone : true } rather than thrown.
157
+ numberConfig: async ({ accountSid, authToken, request: request2 = request, sid }) => {
158
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
159
+ if (!sid) throw new Error("Twilio config read needs the IncomingPhoneNumber sid");
160
+ let result;
161
+ try {
162
+ result = await request2({
163
+ method: "GET",
164
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/IncomingPhoneNumbers/" + sid + ".json",
165
+ headers: {
166
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
167
+ }
168
+ });
169
+ } catch (error) {
170
+ if ((error == null ? void 0 : error.status) === 404) return { gone: true };
171
+ throw error;
172
+ }
173
+ return {
174
+ gone: false,
175
+ number: result == null ? void 0 : result.phone_number,
176
+ smsMethod: result == null ? void 0 : result.sms_method,
177
+ smsUrl: result == null ? void 0 : result.sms_url,
178
+ status: result == null ? void 0 : result.status
179
+ };
180
+ },
181
+ // RE-POINT AN OWNED NUMBER'S INBOUND WEBHOOK — POST on the same resource,
182
+ // the exact fields purchase sets. The health sweep's repair arm.
183
+ updateNumber: async ({ accountSid, authToken, request: request2 = request, sid, smsUrl }) => {
184
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
185
+ if (!sid) throw new Error("Twilio update needs the IncomingPhoneNumber sid");
186
+ if (!smsUrl) throw new Error("Twilio update needs the smsUrl to point the number at");
187
+ const result = await request2({
188
+ method: "POST",
189
+ type: "form",
190
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/IncomingPhoneNumbers/" + sid + ".json",
191
+ headers: {
192
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
193
+ },
194
+ body: {
195
+ SmsUrl: smsUrl,
196
+ SmsMethod: "POST"
197
+ }
198
+ });
199
+ return {
200
+ smsUrl: result == null ? void 0 : result.sms_url
201
+ };
202
+ },
143
203
  releaseNumber: async ({ accountSid, authToken, request: request2 = request, sid }) => {
144
204
  if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
145
205
  if (!sid) throw new Error("Twilio release needs the IncomingPhoneNumber sid");
@@ -150,6 +210,84 @@ var twilio = {
150
210
  "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
151
211
  }
152
212
  });
213
+ },
214
+ // TOLL-FREE VERIFICATION — the carrier registration that lets a purchased
215
+ // number actually deliver. From the Tollfree Verification resource docs
216
+ // (messaging.twilio.com, docs/messaging/api/tollfree-verification-resource):
217
+ // POST /v1/Tollfree/Verifications
218
+ // required: BusinessName, BusinessWebsite, NotificationEmail,
219
+ // UseCaseCategories (array), UseCaseSummary,
220
+ // ProductionMessageSample, OptInImageUrls (array), OptInType,
221
+ // MessageVolume, TollfreePhoneNumberSid (PN…)
222
+ // GET /v1/Tollfree/Verifications/{Sid}
223
+ // status: PENDING_REVIEW | IN_REVIEW | TWILIO_APPROVED |
224
+ // TWILIO_REJECTED, with rejection_reason beside a rejection.
225
+ // Basic auth, form-encoded, like everything above. There is no status
226
+ // callback on this resource — the caller polls the fetch.
227
+ //
228
+ // THE END BUSINESS'S DETAILS, never the platform's. Twilio rejects ISV
229
+ // submissions carrying the ISV's own identity (Toll-Free Verification for
230
+ // ISVs, support.twilio.com) — so businessName, website and email here are
231
+ // the merchant's, collected at purchase.
232
+ //
233
+ // The use-case fields are PLATFORM facts and live here beside the message
234
+ // composition for the same reason it does: how Drawbridge collects opt-in
235
+ // (its own hosted entry forms) and what its messages look like are
236
+ // properties of sending through Drawbridge, not of any one merchant.
237
+ submitVerification: async ({
238
+ accountSid,
239
+ authToken,
240
+ businessName,
241
+ email,
242
+ numberSid,
243
+ optInImage,
244
+ request: request2 = request,
245
+ volume,
246
+ website
247
+ }) => {
248
+ const missing = Object.entries({ accountSid, authToken, businessName, email, numberSid, optInImage, website }).filter(([, value]) => !value).map(([name]) => name);
249
+ if (missing.length) throw new Error("Toll-free verification submission missing \u2014 " + missing.join(", "));
250
+ const body = new URLSearchParams();
251
+ body.append("BusinessName", businessName);
252
+ body.append("BusinessWebsite", website);
253
+ body.append("NotificationEmail", email);
254
+ body.append("UseCaseCategories", "MARKETING");
255
+ body.append("UseCaseSummary", "Prize-draw entry confirmations, winner notifications and campaign updates to consumers who opted in on this business's Drawbridge-hosted entry form.");
256
+ body.append("ProductionMessageSample", businessName + "\nThanks for entering! We'll text you here about your entry.\nReply STOP to opt out");
257
+ body.append("OptInImageUrls", optInImage);
258
+ body.append("OptInType", "WEB_FORM");
259
+ body.append("MessageVolume", volume || "1,000");
260
+ body.append("TollfreePhoneNumberSid", numberSid);
261
+ const result = await request2({
262
+ method: "POST",
263
+ type: "form",
264
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications",
265
+ headers: {
266
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
267
+ },
268
+ body
269
+ });
270
+ return {
271
+ sid: result == null ? void 0 : result.sid,
272
+ status: result == null ? void 0 : result.status
273
+ };
274
+ },
275
+ fetchVerification: async ({ accountSid, authToken, request: request2 = request, sid }) => {
276
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
277
+ if (!sid) throw new Error("Twilio verification fetch needs the verification sid");
278
+ const result = await request2({
279
+ method: "GET",
280
+ url: "https://messaging.twilio.com/v1/Tollfree/Verifications/" + sid,
281
+ headers: {
282
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
283
+ }
284
+ });
285
+ return {
286
+ // TWILIO_APPROVED → verified, TWILIO_REJECTED → rejected, both
287
+ // review states → pending.
288
+ outcome: (result == null ? void 0 : result.status) === "TWILIO_APPROVED" ? "approved" : (result == null ? void 0 : result.status) === "TWILIO_REJECTED" ? "rejected" : "pending",
289
+ reason: (result == null ? void 0 : result.rejection_reason) || null
290
+ };
153
291
  }
154
292
  };
155
293
  export {
package/package.json CHANGED
@@ -215,5 +215,5 @@
215
215
  "test": ". \"$HOME/.nvm/nvm.sh\" && nvm use && node --test"
216
216
  },
217
217
  "types": "dist/index.d.ts",
218
- "version": "0.0.133"
218
+ "version": "0.0.135"
219
219
  }