@drawbridge/drawbridge-utils 0.0.126 → 0.0.128

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.
@@ -1104,14 +1104,14 @@ var contacts = {
1104
1104
 
1105
1105
  // lib/connections/icons/drawbridge.js
1106
1106
  var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
1107
- <rect width="500" height="500" fill="#BAEC5F"/>
1107
+ <rect width="500" height="500" fill="#0D1314"/>
1108
1108
  <g clip-path="url(#clip0_2115_2832)">
1109
- <path d="M140.224 127.586L174.803 188.73V311.176L140 372.32L176.084 392.031L216.111 321.753V178.278L176.341 108L140.224 127.586Z" fill="#0D1314"/>
1110
- <path d="M360.002 127.523L323.694 108.282L284.949 178.498V321.596L322.924 391.749L359.394 372.79L326.225 311.52V188.73L360.002 127.523Z" fill="#0D1314"/>
1109
+ <path d="M153.198 142.241L183.69 196.065V303.852L153 357.676L184.82 375.028L220.116 313.163V186.865L185.046 125L153.198 142.241Z" fill="#BAEC5F"/>
1110
+ <path d="M347.002 142.186L314.984 125.249L280.818 187.058V313.025L314.306 374.779L346.465 358.091L317.216 304.156V196.066L347.002 142.186Z" fill="#BAEC5F"/>
1111
1111
  </g>
1112
1112
  <defs>
1113
1113
  <clipPath id="clip0_2115_2832">
1114
- <rect width="220" height="284" fill="white" transform="translate(140 108)"/>
1114
+ <rect width="194" height="250" fill="white" transform="translate(153 125)"/>
1115
1115
  </clipPath>
1116
1116
  </defs>
1117
1117
  </svg>`;
@@ -1567,14 +1567,14 @@ const contacts = {
1567
1567
  // lib/ directly. A bare .svg import would need a bundler loader and force the
1568
1568
  // tests onto dist/, which is a worse trade than one line of wrapper.
1569
1569
  var icon$3 = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
1570
- <rect width="500" height="500" fill="#BAEC5F"/>
1570
+ <rect width="500" height="500" fill="#0D1314"/>
1571
1571
  <g clip-path="url(#clip0_2115_2832)">
1572
- <path d="M140.224 127.586L174.803 188.73V311.176L140 372.32L176.084 392.031L216.111 321.753V178.278L176.341 108L140.224 127.586Z" fill="#0D1314"/>
1573
- <path d="M360.002 127.523L323.694 108.282L284.949 178.498V321.596L322.924 391.749L359.394 372.79L326.225 311.52V188.73L360.002 127.523Z" fill="#0D1314"/>
1572
+ <path d="M153.198 142.241L183.69 196.065V303.852L153 357.676L184.82 375.028L220.116 313.163V186.865L185.046 125L153.198 142.241Z" fill="#BAEC5F"/>
1573
+ <path d="M347.002 142.186L314.984 125.249L280.818 187.058V313.025L314.306 374.779L346.465 358.091L317.216 304.156V196.066L347.002 142.186Z" fill="#BAEC5F"/>
1574
1574
  </g>
1575
1575
  <defs>
1576
1576
  <clipPath id="clip0_2115_2832">
1577
- <rect width="220" height="284" fill="white" transform="translate(140 108)"/>
1577
+ <rect width="194" height="250" fill="white" transform="translate(153 125)"/>
1578
1578
  </clipPath>
1579
1579
  </defs>
1580
1580
  </svg>`;
@@ -1567,14 +1567,14 @@ const contacts = {
1567
1567
  // lib/ directly. A bare .svg import would need a bundler loader and force the
1568
1568
  // tests onto dist/, which is a worse trade than one line of wrapper.
1569
1569
  var icon$3 = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
1570
- <rect width="500" height="500" fill="#BAEC5F"/>
1570
+ <rect width="500" height="500" fill="#0D1314"/>
1571
1571
  <g clip-path="url(#clip0_2115_2832)">
1572
- <path d="M140.224 127.586L174.803 188.73V311.176L140 372.32L176.084 392.031L216.111 321.753V178.278L176.341 108L140.224 127.586Z" fill="#0D1314"/>
1573
- <path d="M360.002 127.523L323.694 108.282L284.949 178.498V321.596L322.924 391.749L359.394 372.79L326.225 311.52V188.73L360.002 127.523Z" fill="#0D1314"/>
1572
+ <path d="M153.198 142.241L183.69 196.065V303.852L153 357.676L184.82 375.028L220.116 313.163V186.865L185.046 125L153.198 142.241Z" fill="#BAEC5F"/>
1573
+ <path d="M347.002 142.186L314.984 125.249L280.818 187.058V313.025L314.306 374.779L346.465 358.091L317.216 304.156V196.066L347.002 142.186Z" fill="#BAEC5F"/>
1574
1574
  </g>
1575
1575
  <defs>
1576
1576
  <clipPath id="clip0_2115_2832">
1577
- <rect width="220" height="284" fill="white" transform="translate(140 108)"/>
1577
+ <rect width="194" height="250" fill="white" transform="translate(153 125)"/>
1578
1578
  </clipPath>
1579
1579
  </defs>
1580
1580
  </svg>`;
@@ -1030,14 +1030,14 @@ var contacts = {
1030
1030
 
1031
1031
  // lib/connections/icons/drawbridge.js
1032
1032
  var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
1033
- <rect width="500" height="500" fill="#BAEC5F"/>
1033
+ <rect width="500" height="500" fill="#0D1314"/>
1034
1034
  <g clip-path="url(#clip0_2115_2832)">
1035
- <path d="M140.224 127.586L174.803 188.73V311.176L140 372.32L176.084 392.031L216.111 321.753V178.278L176.341 108L140.224 127.586Z" fill="#0D1314"/>
1036
- <path d="M360.002 127.523L323.694 108.282L284.949 178.498V321.596L322.924 391.749L359.394 372.79L326.225 311.52V188.73L360.002 127.523Z" fill="#0D1314"/>
1035
+ <path d="M153.198 142.241L183.69 196.065V303.852L153 357.676L184.82 375.028L220.116 313.163V186.865L185.046 125L153.198 142.241Z" fill="#BAEC5F"/>
1036
+ <path d="M347.002 142.186L314.984 125.249L280.818 187.058V313.025L314.306 374.779L346.465 358.091L317.216 304.156V196.066L347.002 142.186Z" fill="#BAEC5F"/>
1037
1037
  </g>
1038
1038
  <defs>
1039
1039
  <clipPath id="clip0_2115_2832">
1040
- <rect width="220" height="284" fill="white" transform="translate(140 108)"/>
1040
+ <rect width="194" height="250" fill="white" transform="translate(153 125)"/>
1041
1041
  </clipPath>
1042
1042
  </defs>
1043
1043
  </svg>`;
@@ -1010,14 +1010,14 @@ var contacts = {
1010
1010
 
1011
1011
  // lib/connections/icons/drawbridge.js
1012
1012
  var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
1013
- <rect width="500" height="500" fill="#BAEC5F"/>
1013
+ <rect width="500" height="500" fill="#0D1314"/>
1014
1014
  <g clip-path="url(#clip0_2115_2832)">
1015
- <path d="M140.224 127.586L174.803 188.73V311.176L140 372.32L176.084 392.031L216.111 321.753V178.278L176.341 108L140.224 127.586Z" fill="#0D1314"/>
1016
- <path d="M360.002 127.523L323.694 108.282L284.949 178.498V321.596L322.924 391.749L359.394 372.79L326.225 311.52V188.73L360.002 127.523Z" fill="#0D1314"/>
1015
+ <path d="M153.198 142.241L183.69 196.065V303.852L153 357.676L184.82 375.028L220.116 313.163V186.865L185.046 125L153.198 142.241Z" fill="#BAEC5F"/>
1016
+ <path d="M347.002 142.186L314.984 125.249L280.818 187.058V313.025L314.306 374.779L346.465 358.091L317.216 304.156V196.066L347.002 142.186Z" fill="#BAEC5F"/>
1017
1017
  </g>
1018
1018
  <defs>
1019
1019
  <clipPath id="clip0_2115_2832">
1020
- <rect width="220" height="284" fill="white" transform="translate(140 108)"/>
1020
+ <rect width="194" height="250" fill="white" transform="translate(153 125)"/>
1021
1021
  </clipPath>
1022
1022
  </defs>
1023
1023
  </svg>`;
@@ -4740,9 +4740,10 @@ var saveProviderSettings = async ({ authenticated, clear, controller, settings,
4740
4740
  query: { slug }
4741
4741
  });
4742
4742
  const stored = (existing == null ? void 0 : existing.settings) ? decrypt(existing.settings) : {};
4743
+ const pasted = (value) => typeof value === "string" ? value.trim().replace(/^['"]+|['"]+$/g, "") : value;
4743
4744
  const merged = mergeSettings({
4744
4745
  existing: stored,
4745
- incoming: Object.fromEntries(fields2.map((field2) => [field2.key, settings == null ? void 0 : settings[field2.key]]))
4746
+ incoming: Object.fromEntries(fields2.map((field2) => [field2.key, pasted(settings == null ? void 0 : settings[field2.key])]))
4746
4747
  });
4747
4748
  for (const key of Array.isArray(clear) ? clear : []) {
4748
4749
  if (fields2.some((field2) => field2.key === key)) delete merged[key];
@@ -191,6 +191,15 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
191
191
 
192
192
  const stored = existing?.settings ? decrypt( existing.settings ) : {};
193
193
 
194
+ // A CREDENTIAL ARRIVES BY PASTE, and a paste from a shell line or a password
195
+ // manager brings wrapping quotes and whitespace with it. One stray trailing
196
+ // `'` on a SendGrid key turned every sign-in email into a silent 401 — no
197
+ // legitimate credential begins or ends with a quote or a space, so stripping
198
+ // them here loses nothing and ends the failure mode for every field at once.
199
+ const pasted = ( value ) => typeof value === 'string'
200
+ ? value.trim().replace( /^['"]+|['"]+$/g, '' )
201
+ : value;
202
+
194
203
  // mergeSettings is the blank-keeps rule, already used for merchant
195
204
  // connection settings in route/organization-connection.js — same behaviour,
196
205
  // one implementation. The declared-field allowlist is the part that is ours:
@@ -198,7 +207,7 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
198
207
  // not declare reaching the document.
199
208
  const merged = mergeSettings({
200
209
  existing : stored,
201
- incoming : Object.fromEntries( fields.map( ( field ) => [ field.key, settings?.[ field.key ] ] ) )
210
+ incoming : Object.fromEntries( fields.map( ( field ) => [ field.key, pasted( settings?.[ field.key ] ) ] ) )
202
211
  });
203
212
 
204
213
  // CLEARING IS EXPLICIT, because blank already means keep and the two cannot
@@ -191,6 +191,15 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
191
191
 
192
192
  const stored = existing?.settings ? decrypt( existing.settings ) : {};
193
193
 
194
+ // A CREDENTIAL ARRIVES BY PASTE, and a paste from a shell line or a password
195
+ // manager brings wrapping quotes and whitespace with it. One stray trailing
196
+ // `'` on a SendGrid key turned every sign-in email into a silent 401 — no
197
+ // legitimate credential begins or ends with a quote or a space, so stripping
198
+ // them here loses nothing and ends the failure mode for every field at once.
199
+ const pasted = ( value ) => typeof value === 'string'
200
+ ? value.trim().replace( /^['"]+|['"]+$/g, '' )
201
+ : value;
202
+
194
203
  // mergeSettings is the blank-keeps rule, already used for merchant
195
204
  // connection settings in route/organization-connection.js — same behaviour,
196
205
  // one implementation. The declared-field allowlist is the part that is ours:
@@ -198,7 +207,7 @@ const saveProviderSettings = async ({ authenticated, clear, controller, settings
198
207
  // not declare reaching the document.
199
208
  const merged = mergeSettings({
200
209
  existing : stored,
201
- incoming : Object.fromEntries( fields.map( ( field ) => [ field.key, settings?.[ field.key ] ] ) )
210
+ incoming : Object.fromEntries( fields.map( ( field ) => [ field.key, pasted( settings?.[ field.key ] ) ] ) )
202
211
  });
203
212
 
204
213
  // CLEARING IS EXPLICIT, because blank already means keep and the two cannot
package/dist/providers.js CHANGED
@@ -966,14 +966,14 @@ var contacts = {
966
966
 
967
967
  // lib/connections/icons/drawbridge.js
968
968
  var drawbridge_default = `<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
969
- <rect width="500" height="500" fill="#BAEC5F"/>
969
+ <rect width="500" height="500" fill="#0D1314"/>
970
970
  <g clip-path="url(#clip0_2115_2832)">
971
- <path d="M140.224 127.586L174.803 188.73V311.176L140 372.32L176.084 392.031L216.111 321.753V178.278L176.341 108L140.224 127.586Z" fill="#0D1314"/>
972
- <path d="M360.002 127.523L323.694 108.282L284.949 178.498V321.596L322.924 391.749L359.394 372.79L326.225 311.52V188.73L360.002 127.523Z" fill="#0D1314"/>
971
+ <path d="M153.198 142.241L183.69 196.065V303.852L153 357.676L184.82 375.028L220.116 313.163V186.865L185.046 125L153.198 142.241Z" fill="#BAEC5F"/>
972
+ <path d="M347.002 142.186L314.984 125.249L280.818 187.058V313.025L314.306 374.779L346.465 358.091L317.216 304.156V196.066L347.002 142.186Z" fill="#BAEC5F"/>
973
973
  </g>
974
974
  <defs>
975
975
  <clipPath id="clip0_2115_2832">
976
- <rect width="220" height="284" fill="white" transform="translate(140 108)"/>
976
+ <rect width="194" height="250" fill="white" transform="translate(153 125)"/>
977
977
  </clipPath>
978
978
  </defs>
979
979
  </svg>`;
@@ -4696,9 +4696,10 @@ var saveProviderSettings = async ({ authenticated, clear, controller, settings,
4696
4696
  query: { slug }
4697
4697
  });
4698
4698
  const stored = (existing == null ? void 0 : existing.settings) ? decrypt(existing.settings) : {};
4699
+ const pasted = (value) => typeof value === "string" ? value.trim().replace(/^['"]+|['"]+$/g, "") : value;
4699
4700
  const merged = mergeSettings({
4700
4701
  existing: stored,
4701
- incoming: Object.fromEntries(fields2.map((field2) => [field2.key, settings == null ? void 0 : settings[field2.key]]))
4702
+ incoming: Object.fromEntries(fields2.map((field2) => [field2.key, pasted(settings == null ? void 0 : settings[field2.key])]))
4702
4703
  });
4703
4704
  for (const key of Array.isArray(clear) ? clear : []) {
4704
4705
  if (fields2.some((field2) => field2.key === key)) delete merged[key];
package/dist/twilio.cjs CHANGED
@@ -103,6 +103,68 @@ var twilio = {
103
103
  To: to
104
104
  }
105
105
  });
106
+ },
107
+ // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
108
+ // parameter names are Twilio's own, from the AvailablePhoneNumbers and
109
+ // IncomingPhoneNumbers resource docs:
110
+ // GET /2010-04-01/Accounts/{AccountSid}/AvailablePhoneNumbers/{CountryCode}/Local.json
111
+ // query: SmsEnabled, AreaCode (US/CA only), PageSize (1-1000)
112
+ // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
113
+ // body: PhoneNumber (E.164), SmsUrl, SmsMethod
114
+ // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
115
+ // Basic auth throughout, like Messages.
116
+ searchNumbers: async ({ accountSid, areaCode, authToken, country = "US", limit = 10, request: request2 = request }) => {
117
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
118
+ const result = await request2({
119
+ method: "GET",
120
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/Local.json",
121
+ headers: {
122
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
123
+ },
124
+ query: {
125
+ SmsEnabled: true,
126
+ PageSize: limit,
127
+ ...areaCode && { AreaCode: areaCode }
128
+ }
129
+ });
130
+ return ((result == null ? void 0 : result.available_phone_numbers) || []).map((entry) => ({
131
+ friendly: entry.friendly_name,
132
+ locality: entry.locality || entry.region || null,
133
+ number: entry.phone_number
134
+ }));
135
+ },
136
+ purchaseNumber: async ({ accountSid, authToken, number, request: request2 = request, smsUrl }) => {
137
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
138
+ if (!number) throw new Error("Twilio purchase needs the E.164 number to buy");
139
+ const result = await request2({
140
+ method: "POST",
141
+ type: "form",
142
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/IncomingPhoneNumbers.json",
143
+ headers: {
144
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
145
+ },
146
+ body: {
147
+ PhoneNumber: number,
148
+ // Inbound STOP/HELP must land on the same generic route every
149
+ // platform number uses — the drawbridge manifest owns inbound.
150
+ ...smsUrl && { SmsUrl: smsUrl, SmsMethod: "POST" }
151
+ }
152
+ });
153
+ return {
154
+ number: result == null ? void 0 : result.phone_number,
155
+ sid: result == null ? void 0 : result.sid
156
+ };
157
+ },
158
+ releaseNumber: async ({ accountSid, authToken, request: request2 = request, sid }) => {
159
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
160
+ if (!sid) throw new Error("Twilio release needs the IncomingPhoneNumber sid");
161
+ return await request2({
162
+ method: "DELETE",
163
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/IncomingPhoneNumbers/" + sid + ".json",
164
+ headers: {
165
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
166
+ }
167
+ });
106
168
  }
107
169
  };
108
170
  // Annotate the CommonJS export names for ESM import in node:
package/dist/twilio.d.cts CHANGED
@@ -65,6 +65,84 @@ const twilio = {
65
65
  }
66
66
  });
67
67
 
68
+ },
69
+
70
+ // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
71
+ // parameter names are Twilio's own, from the AvailablePhoneNumbers and
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)
75
+ // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
76
+ // body: PhoneNumber (E.164), SmsUrl, SmsMethod
77
+ // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
78
+ // Basic auth throughout, like Messages.
79
+
80
+ searchNumbers : async ({ accountSid, areaCode, authToken, country = 'US', limit = 10, request: request$1 = request }) => {
81
+
82
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
83
+
84
+ const result = await request$1({
85
+ method : 'GET',
86
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/Local.json',
87
+ headers : {
88
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
89
+ },
90
+ query : {
91
+ SmsEnabled : true,
92
+ PageSize : limit,
93
+ ...( areaCode && { AreaCode : areaCode } )
94
+ }
95
+ });
96
+
97
+ // The caller-facing shape is ours; the vendor's field names stay here.
98
+ return ( result?.available_phone_numbers || [] ).map( ( entry ) => ({
99
+ friendly : entry.friendly_name,
100
+ locality : entry.locality || entry.region || null,
101
+ number : entry.phone_number
102
+ }) );
103
+
104
+ },
105
+
106
+ purchaseNumber : async ({ accountSid, authToken, number, request: request$1 = request, smsUrl }) => {
107
+
108
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
109
+ if( ! number ) throw new Error( 'Twilio purchase needs the E.164 number to buy' );
110
+
111
+ const result = await request$1({
112
+ method : 'POST',
113
+ type : 'form',
114
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers.json',
115
+ headers : {
116
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
117
+ },
118
+ body : {
119
+ PhoneNumber : number,
120
+ // Inbound STOP/HELP must land on the same generic route every
121
+ // platform number uses — the drawbridge manifest owns inbound.
122
+ ...( smsUrl && { SmsUrl : smsUrl, SmsMethod : 'POST' } )
123
+ }
124
+ });
125
+
126
+ return {
127
+ number : result?.phone_number,
128
+ sid : result?.sid
129
+ };
130
+
131
+ },
132
+
133
+ releaseNumber : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
134
+
135
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
136
+ if( ! sid ) throw new Error( 'Twilio release needs the IncomingPhoneNumber sid' );
137
+
138
+ return await request$1({
139
+ method : 'DELETE',
140
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers/' + sid + '.json',
141
+ headers : {
142
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
143
+ }
144
+ });
145
+
68
146
  }
69
147
 
70
148
  };
package/dist/twilio.d.ts CHANGED
@@ -65,6 +65,84 @@ const twilio = {
65
65
  }
66
66
  });
67
67
 
68
+ },
69
+
70
+ // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
71
+ // parameter names are Twilio's own, from the AvailablePhoneNumbers and
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)
75
+ // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
76
+ // body: PhoneNumber (E.164), SmsUrl, SmsMethod
77
+ // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
78
+ // Basic auth throughout, like Messages.
79
+
80
+ searchNumbers : async ({ accountSid, areaCode, authToken, country = 'US', limit = 10, request: request$1 = request }) => {
81
+
82
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
83
+
84
+ const result = await request$1({
85
+ method : 'GET',
86
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/AvailablePhoneNumbers/' + country + '/Local.json',
87
+ headers : {
88
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
89
+ },
90
+ query : {
91
+ SmsEnabled : true,
92
+ PageSize : limit,
93
+ ...( areaCode && { AreaCode : areaCode } )
94
+ }
95
+ });
96
+
97
+ // The caller-facing shape is ours; the vendor's field names stay here.
98
+ return ( result?.available_phone_numbers || [] ).map( ( entry ) => ({
99
+ friendly : entry.friendly_name,
100
+ locality : entry.locality || entry.region || null,
101
+ number : entry.phone_number
102
+ }) );
103
+
104
+ },
105
+
106
+ purchaseNumber : async ({ accountSid, authToken, number, request: request$1 = request, smsUrl }) => {
107
+
108
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
109
+ if( ! number ) throw new Error( 'Twilio purchase needs the E.164 number to buy' );
110
+
111
+ const result = await request$1({
112
+ method : 'POST',
113
+ type : 'form',
114
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers.json',
115
+ headers : {
116
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
117
+ },
118
+ body : {
119
+ PhoneNumber : number,
120
+ // Inbound STOP/HELP must land on the same generic route every
121
+ // platform number uses — the drawbridge manifest owns inbound.
122
+ ...( smsUrl && { SmsUrl : smsUrl, SmsMethod : 'POST' } )
123
+ }
124
+ });
125
+
126
+ return {
127
+ number : result?.phone_number,
128
+ sid : result?.sid
129
+ };
130
+
131
+ },
132
+
133
+ releaseNumber : async ({ accountSid, authToken, request: request$1 = request, sid }) => {
134
+
135
+ if( ! accountSid || ! authToken ) throw new Error( 'Twilio credentials missing — accountSid, authToken' );
136
+ if( ! sid ) throw new Error( 'Twilio release needs the IncomingPhoneNumber sid' );
137
+
138
+ return await request$1({
139
+ method : 'DELETE',
140
+ url : 'https://api.twilio.com/2010-04-01/Accounts/' + accountSid + '/IncomingPhoneNumbers/' + sid + '.json',
141
+ headers : {
142
+ 'Authorization' : 'Basic ' + Buffer.from( accountSid + ':' + authToken ).toString( 'base64' )
143
+ }
144
+ });
145
+
68
146
  }
69
147
 
70
148
  };
package/dist/twilio.js CHANGED
@@ -78,6 +78,68 @@ var twilio = {
78
78
  To: to
79
79
  }
80
80
  });
81
+ },
82
+ // NUMBER LIFECYCLE, same transport discipline as sms above. Endpoints and
83
+ // parameter names are Twilio's own, from the AvailablePhoneNumbers and
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)
87
+ // POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json
88
+ // body: PhoneNumber (E.164), SmsUrl, SmsMethod
89
+ // DELETE /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers/{Sid}.json
90
+ // Basic auth throughout, like Messages.
91
+ searchNumbers: async ({ accountSid, areaCode, authToken, country = "US", limit = 10, request: request2 = request }) => {
92
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
93
+ const result = await request2({
94
+ method: "GET",
95
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/AvailablePhoneNumbers/" + country + "/Local.json",
96
+ headers: {
97
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
98
+ },
99
+ query: {
100
+ SmsEnabled: true,
101
+ PageSize: limit,
102
+ ...areaCode && { AreaCode: areaCode }
103
+ }
104
+ });
105
+ return ((result == null ? void 0 : result.available_phone_numbers) || []).map((entry) => ({
106
+ friendly: entry.friendly_name,
107
+ locality: entry.locality || entry.region || null,
108
+ number: entry.phone_number
109
+ }));
110
+ },
111
+ purchaseNumber: async ({ accountSid, authToken, number, request: request2 = request, smsUrl }) => {
112
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
113
+ if (!number) throw new Error("Twilio purchase needs the E.164 number to buy");
114
+ const result = await request2({
115
+ method: "POST",
116
+ type: "form",
117
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/IncomingPhoneNumbers.json",
118
+ headers: {
119
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
120
+ },
121
+ body: {
122
+ PhoneNumber: number,
123
+ // Inbound STOP/HELP must land on the same generic route every
124
+ // platform number uses — the drawbridge manifest owns inbound.
125
+ ...smsUrl && { SmsUrl: smsUrl, SmsMethod: "POST" }
126
+ }
127
+ });
128
+ return {
129
+ number: result == null ? void 0 : result.phone_number,
130
+ sid: result == null ? void 0 : result.sid
131
+ };
132
+ },
133
+ releaseNumber: async ({ accountSid, authToken, request: request2 = request, sid }) => {
134
+ if (!accountSid || !authToken) throw new Error("Twilio credentials missing \u2014 accountSid, authToken");
135
+ if (!sid) throw new Error("Twilio release needs the IncomingPhoneNumber sid");
136
+ return await request2({
137
+ method: "DELETE",
138
+ url: "https://api.twilio.com/2010-04-01/Accounts/" + accountSid + "/IncomingPhoneNumbers/" + sid + ".json",
139
+ headers: {
140
+ "Authorization": "Basic " + Buffer.from(accountSid + ":" + authToken).toString("base64")
141
+ }
142
+ });
81
143
  }
82
144
  };
83
145
  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.126"
218
+ "version": "0.0.128"
219
219
  }