@stacksjs/sms 0.70.73 → 0.70.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var N=Object.defineProperty;var h=(x)=>x;function D(x,M){this[x]=h.bind(null,M)}var B=(x,M)=>{for(var G in M)N(x,G,{get:M[G],enumerable:!0,configurable:!0,set:D.bind(M,G)})};var q=(x,M)=>()=>(x&&(M=x(x=0)),M);var _={};B(_,{default:()=>c});import{env as Z}from"@stacksjs/env";var c;var H=q(()=>{c={enabled:!1,provider:"twilio",from:String(Z.SMS_FROM_NUMBER||""),defaultCountryCode:"US",messageType:"TRANSACTIONAL",drivers:{twilio:{accountSid:String(Z.TWILIO_ACCOUNT_SID||""),authToken:String(Z.TWILIO_AUTH_TOKEN||""),from:String(Z.TWILIO_FROM_NUMBER||""),messagingServiceSid:String(Z.TWILIO_MESSAGING_SERVICE_SID||"")},vonage:{apiKey:String(Z.VONAGE_API_KEY||""),apiSecret:String(Z.VONAGE_API_SECRET||""),from:String(Z.VONAGE_FROM_NUMBER||"")},pinpoint:{region:String(Z.AWS_REGION||"us-east-1"),accessKeyId:String(Z.AWS_ACCESS_KEY_ID||""),secretAccessKey:String(Z.AWS_SECRET_ACCESS_KEY||""),senderId:String(Z.SMS_SENDER_ID||""),originationNumber:String(Z.SMS_ORIGINATION_NUMBER||"")}},maxSpendPerMonth:100,optOut:{enabled:!0,keywords:["STOP","UNSUBSCRIBE","CANCEL","END","QUIT"]},templates:[],twoWay:{enabled:!1}}});async function C(x,M,G=3){let J=0;while(!0){let L=await fetch(x,M);if(L.status!==429&&L.status!==503)return L;if(J>=G)return L;let X=Number(L.headers.get("retry-after"))||2**J;await new Promise((Q)=>setTimeout(Q,Math.min(X,30)*1000)),J++}}class O{config;verifyServiceSid;constructor(x,M){this.config=x,this.verifyServiceSid=M}async send(x){let M=Array.isArray(x.to)?x.to:[x.to];if(M.length>1)return(await this.sendBulk(M.map((X)=>({...x,to:X}))))[0];let G=M[0],J=x.from||this.config.from;if(!J&&!this.config.messagingServiceSid)return{success:!1,to:G,error:'No "from" number or messaging service SID configured',provider:"twilio"};try{let L=new URLSearchParams;if(L.append("To",G),L.append("Body",x.body),this.config.messagingServiceSid)L.append("MessagingServiceSid",this.config.messagingServiceSid);else if(J)L.append("From",J);if(x.statusCallback||this.config.statusCallback)L.append("StatusCallback",x.statusCallback||this.config.statusCallback);if(x.mediaUrls&&x.mediaUrls.length>0)for(let z of x.mediaUrls)L.append("MediaUrl",z);let X=await C(`https://api.twilio.com/2010-04-01/Accounts/${this.config.accountSid}/Messages.json`,{method:"POST",headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`,"Content-Type":"application/x-www-form-urlencoded"},body:L.toString()}),Q=await X.json();if(!X.ok)return{success:!1,to:G,error:Q.message||`HTTP ${X.status}`,provider:"twilio"};return{success:!0,messageId:Q.sid,status:P(Q.status),to:G,provider:"twilio",segments:Q.num_segments?Number.parseInt(Q.num_segments,10):void 0,price:Q.price?Math.abs(Number.parseFloat(Q.price)):void 0,currency:Q.price_unit}}catch(L){return{success:!1,to:G,error:L instanceof Error?L.message:"Unknown error",provider:"twilio"}}}async sendBulk(x){return Promise.all(x.map((M)=>this.send({...M,to:Array.isArray(M.to)?M.to[0]:M.to})))}async getStatus(x){let M=await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.config.accountSid}/Messages/${x}.json`,{headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`}});if(!M.ok)throw Error(`Twilio message status API error: ${M.status}`);let G=await M.json();return{messageId:G.sid,to:G.to,status:P(G.status),timestamp:new Date(G.date_updated||G.date_created),errorCode:G.error_code?.toString(),errorMessage:G.error_message}}async verify(x){try{let M=await fetch(`https://lookups.twilio.com/v2/PhoneNumbers/${encodeURIComponent(x)}?Fields=line_type_intelligence`,{headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`}});if(!M.ok)return{valid:!1};let G=await M.json();return{valid:G.valid,carrier:G.line_type_intelligence?.carrier_name,type:G.line_type_intelligence?.type}}catch{return{valid:!1}}}async getBalance(){let x=await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.config.accountSid}/Balance.json`,{headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`}});if(!x.ok)throw Error(`Twilio balance API error: ${x.status}`);let M=await x.json();return{balance:Number.parseFloat(M.balance),currency:M.currency}}async startVerification(x){if(!this.verifyServiceSid)return{success:!1,status:"denied",error:"Verify service SID not configured"};try{let M=new URLSearchParams;if(M.append("To",x.to),M.append("Channel",x.channel||"sms"),x.locale)M.append("Locale",x.locale);if(x.customMessage)M.append("CustomMessage",x.customMessage);let G=await fetch(`https://verify.twilio.com/v2/Services/${this.verifyServiceSid}/Verifications`,{method:"POST",headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`,"Content-Type":"application/x-www-form-urlencoded"},body:M.toString()}),J=await G.json();if(!G.ok)return{success:!1,status:"denied",error:J.message||`HTTP ${G.status}`};return{success:!0,verificationId:J.sid,status:J.status==="pending"?"pending":"denied"}}catch(M){return{success:!1,status:"denied",error:M instanceof Error?M.message:"Unknown error"}}}async checkVerification(x){if(!this.verifyServiceSid)return{success:!1,status:"denied",error:"Verify service SID not configured"};try{let M=new URLSearchParams;M.append("To",x.to),M.append("Code",x.code);let G=await fetch(`https://verify.twilio.com/v2/Services/${this.verifyServiceSid}/VerificationCheck`,{method:"POST",headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`,"Content-Type":"application/x-www-form-urlencoded"},body:M.toString()}),J=await G.json();if(!G.ok)return{success:!1,status:"denied",error:J.message||`HTTP ${G.status}`};return{success:J.status==="approved",verificationId:J.sid,status:J.status==="approved"?"approved":"denied"}}catch(M){return{success:!1,status:"denied",error:M instanceof Error?M.message:"Unknown error"}}}async cancelVerification(x){if(!this.verifyServiceSid)return!1;try{let M=new URLSearchParams;return M.append("Status","canceled"),(await fetch(`https://verify.twilio.com/v2/Services/${this.verifyServiceSid}/Verifications/${x}`,{method:"POST",headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`,"Content-Type":"application/x-www-form-urlencoded"},body:M.toString()})).ok}catch{return!1}}}function P(x){return{queued:"queued",sending:"sending",sent:"sent",delivered:"delivered",undelivered:"undelivered",failed:"failed",canceled:"failed"}[x]||"unknown"}function y(x,M){return new O(x,M)}var T={};B(T,{default:()=>F,createVonageDriver:()=>A,VonageDriver:()=>F});class F{config;useMessagesApi;constructor(x,M=!1){this.config=x,this.useMessagesApi=M}async send(x){let M=Array.isArray(x.to)?x.to:[x.to];if(M.length>1)return(await this.sendBulk(M.map((X)=>({...x,to:X}))))[0];let G=M[0],J=x.from||this.config.from;if(!J)return{success:!1,to:G,error:'No "from" number configured',provider:"vonage"};if(!/^\+?[1-9]\d{1,14}$/.test(G))return{success:!1,to:G,error:`Invalid E.164 phone number: ${G}. Expected +<country><number> with 8\u201315 digits.`,provider:"vonage"};try{if(this.useMessagesApi)return await this.sendWithMessagesApi(G,J,x);return await this.sendWithSmsApi(G,J,x)}catch(L){return{success:!1,to:G,error:L instanceof Error?L.message:"Unknown error",provider:"vonage"}}}async sendWithSmsApi(x,M,G){let J=new URLSearchParams;if(J.append("api_key",this.config.apiKey),J.append("api_secret",this.config.apiSecret),J.append("to",x),J.append("from",M),J.append("text",G.body),G.statusCallback)J.append("callback",G.statusCallback);let L=await fetch("https://rest.nexmo.com/sms/json",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:J.toString()});if(!L.ok)throw Error(`Vonage SMS API error: ${L.status}`);let X=await L.json();if(X.messages&&X.messages.length>0){let Q=X.messages[0];if(Q.status!=="0")return{success:!1,to:x,error:Q["error-text"]||`Status: ${Q.status}`,provider:"vonage"};return{success:!0,messageId:Q["message-id"],status:"sent",to:x,provider:"vonage",price:Q["message-price"]?Number.parseFloat(Q["message-price"]):void 0,currency:"EUR"}}return{success:!1,to:x,error:"No response from Vonage",provider:"vonage"}}async sendWithMessagesApi(x,M,G){let J={"Content-Type":"application/json"},L;if(this.config.applicationId&&this.config.privateKey)L=`Bearer ${await this.generateJwt()}`;else L=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;J.Authorization=L;let X={message_type:"text",channel:"sms",to:x,from:M,text:G.body};if(G.statusCallback)X.webhook_url=G.statusCallback;let Q=await fetch("https://api.nexmo.com/v1/messages",{method:"POST",headers:J,body:JSON.stringify(X)}),z=await Q.json();if(!Q.ok)return{success:!1,to:x,error:z.title||z.detail||`HTTP ${Q.status}`,provider:"vonage"};return{success:!0,messageId:z.message_uuid,status:"sent",to:x,provider:"vonage"}}async sendBulk(x){return Promise.all(x.map((M)=>this.send({...M,to:Array.isArray(M.to)?M.to[0]:M.to})))}async getStatus(x){let M={};if(this.config.applicationId&&this.config.privateKey){let L=await this.generateJwt();M.Authorization=`Bearer ${L}`}else M.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;let G=await fetch(`https://api.nexmo.com/v1/messages/${x}`,{headers:M});if(!G.ok)throw Error(`Vonage message status API error: ${G.status}`);let J=await G.json();return{messageId:J.message_uuid,to:J.to,status:b(J.status),timestamp:new Date(J.timestamp),errorCode:J.error?.code?.toString(),errorMessage:J.error?.reason}}async verify(x){try{let M=new URLSearchParams;M.append("api_key",this.config.apiKey),M.append("api_secret",this.config.apiSecret),M.append("number",x);let G=await fetch("https://rest.nexmo.com/ni/basic/json",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:M.toString()});if(!G.ok)throw Error(`Vonage number insight API error: ${G.status}`);let J=await G.json();if(J.status!==0)return{valid:!1};return{valid:!0,carrier:J.current_carrier?.name,type:J.current_carrier?.network_type}}catch{return{valid:!1}}}async getBalance(){let x=typeof Buffer<"u"?Buffer.from(`${this.config.apiKey}:${this.config.apiSecret}`).toString("base64"):btoa(`${this.config.apiKey}:${this.config.apiSecret}`),M=await fetch("https://rest.nexmo.com/account/get-balance",{headers:{Authorization:`Basic ${x}`}});if(!M.ok)throw Error(`Vonage balance API error: ${M.status}`);return{balance:(await M.json()).value,currency:"EUR"}}async startVerification(x){try{let M={"Content-Type":"application/json"};if(this.config.applicationId&&this.config.privateKey){let X=await this.generateJwt();M.Authorization=`Bearer ${X}`}else M.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;let G={brand:this.config.from||"Verification",workflow:[{channel:x.channel==="whatsapp"?"whatsapp_interactive":"sms",to:x.to}]};if(x.codeLength)G.code_length=x.codeLength;if(x.locale)G.locale=x.locale;let J=await fetch("https://api.nexmo.com/v2/verify",{method:"POST",headers:M,body:JSON.stringify(G)}),L=await J.json();if(!J.ok)return{success:!1,status:"denied",error:L.title||L.detail||`HTTP ${J.status}`};return{success:!0,verificationId:L.request_id,status:"pending"}}catch(M){return{success:!1,status:"denied",error:M instanceof Error?M.message:"Unknown error"}}}async checkVerification(x){if(!x.verificationId)return{success:!1,status:"denied",error:"Verification ID is required"};try{let M={"Content-Type":"application/json"};if(this.config.applicationId&&this.config.privateKey){let L=await this.generateJwt();M.Authorization=`Bearer ${L}`}else M.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;let G=await fetch(`https://api.nexmo.com/v2/verify/${x.verificationId}`,{method:"POST",headers:M,body:JSON.stringify({code:x.code})}),J=await G.json();if(!G.ok)return{success:!1,status:"denied",error:J.title||J.detail||`HTTP ${G.status}`};return{success:J.status==="completed",verificationId:x.verificationId,status:J.status==="completed"?"approved":"denied"}}catch(M){return{success:!1,status:"denied",error:M instanceof Error?M.message:"Unknown error"}}}async cancelVerification(x){try{let M={};if(this.config.applicationId&&this.config.privateKey){let J=await this.generateJwt();M.Authorization=`Bearer ${J}`}else M.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;return(await fetch(`https://api.nexmo.com/v2/verify/${x}`,{method:"DELETE",headers:M})).ok}catch{return!1}}async generateJwt(){if(!this.config.applicationId||!this.config.privateKey)throw Error("Application ID and private key required for JWT auth");let x={typ:"JWT",alg:"RS256"},M=Math.floor(Date.now()/1000),G={application_id:this.config.applicationId,iat:M,exp:M+900,jti:crypto.randomUUID()},J=btoa(JSON.stringify(x)),L=btoa(JSON.stringify(G));return`${J}.${L}.signature`}}function b(x){return{submitted:"queued",delivered:"delivered",expired:"undelivered",failed:"failed",rejected:"failed",accepted:"sent"}[x]||"unknown"}function A(x,M=!1){return new F(x,M)}var W=null,E=null,Y={},R=null;async function S(){try{Y=(await Promise.resolve().then(() => (H(),_))).default}catch{}}async function $(){if(!R)R=S();await R}function l(x){Y={...Y,...x},W=null,E=null}function U(x){let M=x||Y.provider||"twilio";switch(M){case"twilio":{let G=Y.drivers?.twilio;if(!G?.accountSid||!G?.authToken)throw Error("Twilio configuration is incomplete. Please provide accountSid and authToken.");return new O({...G,from:G.from||Y.from})}case"vonage":{let G=Y.drivers?.vonage;if(!G?.apiKey||!G?.apiSecret)throw Error("Vonage configuration is incomplete. Please provide apiKey and apiSecret.");return new F({...G,from:G.from||Y.from})}default:throw Error(`Unsupported SMS provider: ${M}`)}}function I(x){let M=U(x);if(!("startVerification"in M))throw Error(`Provider does not support verification: ${x||Y.provider}`);return M}function K(){if(!W)W=U();return W}function j(){if(!E)E=I();return E}async function k(x){return await $(),K().send(x)}var p=k;async function v(x){return await $(),K().sendBulk(x)}async function f(x){await $();let M=K();if(M.getStatus)return M.getStatus(x);return null}async function u(x){await $();let M=K();if(M.verify)return M.verify(x);return{valid:!0}}async function m(){await $();let x=K();if(x.getBalance)return x.getBalance();return null}async function g(x){return await $(),j().startVerification(x)}async function d(x){return await $(),j().checkVerification(x)}async function n(x){await $();let M=j();if(M.cancelVerification)return M.cancelVerification(x);return!1}class V{message={};provider;to(x){return this.message.to=x,this}body(x){return this.message.body=x,this}text(x){return this.body(x)}from(x){return this.message.from=x,this}media(x){return this.message.mediaUrls=Array.isArray(x)?x:[x],this}callback(x){return this.message.statusCallback=x,this}via(x){return this.provider=x,this}async send(){if(await $(),!this.message.to)return{success:!1,to:"",error:"Recipient is required",provider:this.provider||Y.provider||"twilio"};if(!this.message.body)return{success:!1,to:Array.isArray(this.message.to)?this.message.to[0]:this.message.to,error:"Message body is required",provider:this.provider||Y.provider||"twilio"};return(this.provider?U(this.provider):K()).send(this.message)}}function i(){return new V}async function o(x,M,G={}){await $();let J=Y.templates?.find((X)=>X.name===M);if(!J)return{success:!1,to:Array.isArray(x)?x[0]:x,error:`Template not found: ${M}`,provider:Y.provider||"twilio"};let L=J.body;for(let[X,Q]of Object.entries(G)){let z=X.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");L=L.replace(new RegExp(`\\{${z}\\}`,"g"),Q)}return k({to:x,body:L})}function w(x,M){let G=x.replace(/[\s\-()]/g,"");if(G.startsWith("+"))return G;let J=M||Y.defaultCountryCode||"1";if(G.startsWith("00"))G=`+${G.slice(2)}`;else if(!G.startsWith("+"))G=`+${J}${G}`;return G}function t(x){let M=/^\+[1-9]\d{6,14}$/,G=w(x);return M.test(G)}async function a(){await S()}function r(){return Y.enabled===!0}function s(){return{...Y}}var e={init:a,configure:l,isEnabled:r,getConfig:s,send:k,sendSms:p,sendBulk:v,sendTemplate:o,getStatus:f,getBalance:m,verifyNumber:u,startVerification:g,checkVerification:d,cancelVerification:n,formatE164:w,isValidPhoneNumber:t,sms:i,getDriver:U,getVerificationDriver:I},xx=e;export{u as verifyNumber,g as startVerification,i as sms,o as sendTemplate,p as sendSms,v as sendBulk,k as send,T as nexmo,t as isValidPhoneNumber,r as isEnabled,a as init,I as getVerificationDriver,f as getStatus,U as getDriver,s as getConfig,m as getBalance,w as formatE164,A as createVonageDriver,y as createTwilioDriver,l as configure,d as checkVerification,n as cancelVerification,F as VonageDriver,O as TwilioDriver,V as SmsBuilder,xx as SMS};
2
+ var N=Object.defineProperty;var h=(x)=>x;function D(x,M){this[x]=h.bind(null,M)}var B=(x,M)=>{for(var G in M)N(x,G,{get:M[G],enumerable:!0,configurable:!0,set:D.bind(M,G)})};var q=(x,M)=>()=>(x&&(M=x(x=0)),M);var _={};B(_,{default:()=>c});import{env as Z}from"@stacksjs/env";var c;var H=q(()=>{c={enabled:!1,provider:"twilio",from:String(Z.SMS_FROM_NUMBER||""),defaultCountryCode:"US",messageType:"TRANSACTIONAL",drivers:{twilio:{accountSid:String(Z.TWILIO_ACCOUNT_SID||""),authToken:String(Z.TWILIO_AUTH_TOKEN||""),from:String(Z.TWILIO_FROM_NUMBER||""),messagingServiceSid:String(Z.TWILIO_MESSAGING_SERVICE_SID||"")},vonage:{apiKey:String(Z.VONAGE_API_KEY||""),apiSecret:String(Z.VONAGE_API_SECRET||""),from:String(Z.VONAGE_FROM_NUMBER||"")},pinpoint:{region:String(Z.AWS_REGION||"us-east-1"),accessKeyId:String(Z.AWS_ACCESS_KEY_ID||""),secretAccessKey:String(Z.AWS_SECRET_ACCESS_KEY||""),senderId:String(Z.SMS_SENDER_ID||""),originationNumber:String(Z.SMS_ORIGINATION_NUMBER||"")}},maxSpendPerMonth:100,optOut:{enabled:!0,keywords:["STOP","UNSUBSCRIBE","CANCEL","END","QUIT"]},templates:[],twoWay:{enabled:!1}}});async function C(x,M,G=3){let J=0;while(!0){let L=await fetch(x,M);if(L.status!==429&&L.status!==503)return L;if(J>=G)return L;let X=Number(L.headers.get("retry-after"))||2**J;await new Promise((Q)=>setTimeout(Q,Math.min(X,30)*1000)),J++}}class O{config;verifyServiceSid;constructor(x,M){this.config=x,this.verifyServiceSid=M}async send(x){let M=Array.isArray(x.to)?x.to:[x.to];if(M.length>1)return(await this.sendBulk(M.map((X)=>({...x,to:X}))))[0]??{success:!1,to:M[0]??"",error:"No recipients supplied",provider:"twilio"};let G=M[0];if(!G)return{success:!1,to:"",error:"No recipient supplied",provider:"twilio"};let J=x.from||this.config.from;if(!J&&!this.config.messagingServiceSid)return{success:!1,to:G,error:'No "from" number or messaging service SID configured',provider:"twilio"};try{let L=new URLSearchParams;if(L.append("To",G),L.append("Body",x.body),this.config.messagingServiceSid)L.append("MessagingServiceSid",this.config.messagingServiceSid);else if(J)L.append("From",J);if(x.statusCallback||this.config.statusCallback)L.append("StatusCallback",x.statusCallback||this.config.statusCallback);if(x.mediaUrls&&x.mediaUrls.length>0)for(let z of x.mediaUrls)L.append("MediaUrl",z);let X=await C(`https://api.twilio.com/2010-04-01/Accounts/${this.config.accountSid}/Messages.json`,{method:"POST",headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`,"Content-Type":"application/x-www-form-urlencoded"},body:L.toString()}),Q=await X.json();if(!X.ok)return{success:!1,to:G,error:Q.message||`HTTP ${X.status}`,provider:"twilio"};return{success:!0,messageId:Q.sid,status:P(Q.status),to:G,provider:"twilio",segments:Q.num_segments?Number.parseInt(Q.num_segments,10):void 0,price:Q.price?Math.abs(Number.parseFloat(Q.price)):void 0,currency:Q.price_unit}}catch(L){return{success:!1,to:G,error:L instanceof Error?L.message:"Unknown error",provider:"twilio"}}}async sendBulk(x){return Promise.all(x.map((M)=>this.send(M)))}async getStatus(x){let M=await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.config.accountSid}/Messages/${x}.json`,{headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`}});if(!M.ok)throw Error(`Twilio message status API error: ${M.status}`);let G=await M.json();return{messageId:G.sid,to:G.to,status:P(G.status),timestamp:new Date(G.date_updated||G.date_created),errorCode:G.error_code?.toString(),errorMessage:G.error_message}}async verify(x){try{let M=await fetch(`https://lookups.twilio.com/v2/PhoneNumbers/${encodeURIComponent(x)}?Fields=line_type_intelligence`,{headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`}});if(!M.ok)return{valid:!1};let G=await M.json();return{valid:G.valid,carrier:G.line_type_intelligence?.carrier_name,type:G.line_type_intelligence?.type}}catch{return{valid:!1}}}async getBalance(){let x=await fetch(`https://api.twilio.com/2010-04-01/Accounts/${this.config.accountSid}/Balance.json`,{headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`}});if(!x.ok)throw Error(`Twilio balance API error: ${x.status}`);let M=await x.json();return{balance:Number.parseFloat(M.balance),currency:M.currency}}async startVerification(x){if(!this.verifyServiceSid)return{success:!1,status:"denied",error:"Verify service SID not configured"};try{let M=new URLSearchParams;if(M.append("To",x.to),M.append("Channel",x.channel||"sms"),x.locale)M.append("Locale",x.locale);if(x.customMessage)M.append("CustomMessage",x.customMessage);let G=await fetch(`https://verify.twilio.com/v2/Services/${this.verifyServiceSid}/Verifications`,{method:"POST",headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`,"Content-Type":"application/x-www-form-urlencoded"},body:M.toString()}),J=await G.json();if(!G.ok)return{success:!1,status:"denied",error:J.message||`HTTP ${G.status}`};return{success:!0,verificationId:J.sid,status:J.status==="pending"?"pending":"denied"}}catch(M){return{success:!1,status:"denied",error:M instanceof Error?M.message:"Unknown error"}}}async checkVerification(x){if(!this.verifyServiceSid)return{success:!1,status:"denied",error:"Verify service SID not configured"};try{let M=new URLSearchParams;M.append("To",x.to),M.append("Code",x.code);let G=await fetch(`https://verify.twilio.com/v2/Services/${this.verifyServiceSid}/VerificationCheck`,{method:"POST",headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`,"Content-Type":"application/x-www-form-urlencoded"},body:M.toString()}),J=await G.json();if(!G.ok)return{success:!1,status:"denied",error:J.message||`HTTP ${G.status}`};return{success:J.status==="approved",verificationId:J.sid,status:J.status==="approved"?"approved":"denied"}}catch(M){return{success:!1,status:"denied",error:M instanceof Error?M.message:"Unknown error"}}}async cancelVerification(x){if(!this.verifyServiceSid)return!1;try{let M=new URLSearchParams;return M.append("Status","canceled"),(await fetch(`https://verify.twilio.com/v2/Services/${this.verifyServiceSid}/Verifications/${x}`,{method:"POST",headers:{Authorization:`Basic ${btoa(`${this.config.accountSid}:${this.config.authToken}`)}`,"Content-Type":"application/x-www-form-urlencoded"},body:M.toString()})).ok}catch{return!1}}}function P(x){return{queued:"queued",sending:"sending",sent:"sent",delivered:"delivered",undelivered:"undelivered",failed:"failed",canceled:"failed"}[x]||"unknown"}function y(x,M){return new O(x,M)}var T={};B(T,{default:()=>F,createVonageDriver:()=>A,VonageDriver:()=>F});class F{config;useMessagesApi;constructor(x,M=!1){this.config=x,this.useMessagesApi=M}async send(x){let M=Array.isArray(x.to)?x.to:[x.to];if(M.length>1)return(await this.sendBulk(M.map((X)=>({...x,to:X}))))[0]??{success:!1,to:M[0]??"",error:"No recipients supplied",provider:"vonage"};let G=M[0];if(!G)return{success:!1,to:"",error:"No recipient supplied",provider:"vonage"};let J=x.from||this.config.from;if(!J)return{success:!1,to:G,error:'No "from" number configured',provider:"vonage"};if(!/^\+?[1-9]\d{1,14}$/.test(G))return{success:!1,to:G,error:`Invalid E.164 phone number: ${G}. Expected +<country><number> with 8\u201315 digits.`,provider:"vonage"};try{if(this.useMessagesApi)return await this.sendWithMessagesApi(G,J,x);return await this.sendWithSmsApi(G,J,x)}catch(L){return{success:!1,to:G,error:L instanceof Error?L.message:"Unknown error",provider:"vonage"}}}async sendWithSmsApi(x,M,G){let J=new URLSearchParams;if(J.append("api_key",this.config.apiKey),J.append("api_secret",this.config.apiSecret),J.append("to",x),J.append("from",M),J.append("text",G.body),G.statusCallback)J.append("callback",G.statusCallback);let L=await fetch("https://rest.nexmo.com/sms/json",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:J.toString()});if(!L.ok)throw Error(`Vonage SMS API error: ${L.status}`);let X=await L.json(),[Q]=X.messages??[];if(Q){if(Q.status!=="0")return{success:!1,to:x,error:Q["error-text"]||`Status: ${Q.status}`,provider:"vonage"};return{success:!0,messageId:Q["message-id"],status:"sent",to:x,provider:"vonage",price:Q["message-price"]?Number.parseFloat(Q["message-price"]):void 0,currency:"EUR"}}return{success:!1,to:x,error:"No response from Vonage",provider:"vonage"}}async sendWithMessagesApi(x,M,G){let J={"Content-Type":"application/json"},L;if(this.config.applicationId&&this.config.privateKey)L=`Bearer ${await this.generateJwt()}`;else L=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;J.Authorization=L;let X={message_type:"text",channel:"sms",to:x,from:M,text:G.body};if(G.statusCallback)X.webhook_url=G.statusCallback;let Q=await fetch("https://api.nexmo.com/v1/messages",{method:"POST",headers:J,body:JSON.stringify(X)}),z=await Q.json();if(!Q.ok)return{success:!1,to:x,error:z.title||z.detail||`HTTP ${Q.status}`,provider:"vonage"};return{success:!0,messageId:z.message_uuid,status:"sent",to:x,provider:"vonage"}}async sendBulk(x){return Promise.all(x.map((M)=>this.send(M)))}async getStatus(x){let M={};if(this.config.applicationId&&this.config.privateKey){let L=await this.generateJwt();M.Authorization=`Bearer ${L}`}else M.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;let G=await fetch(`https://api.nexmo.com/v1/messages/${x}`,{headers:M});if(!G.ok)throw Error(`Vonage message status API error: ${G.status}`);let J=await G.json();return{messageId:J.message_uuid,to:J.to,status:b(J.status),timestamp:new Date(J.timestamp),errorCode:J.error?.code?.toString(),errorMessage:J.error?.reason}}async verify(x){try{let M=new URLSearchParams;M.append("api_key",this.config.apiKey),M.append("api_secret",this.config.apiSecret),M.append("number",x);let G=await fetch("https://rest.nexmo.com/ni/basic/json",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:M.toString()});if(!G.ok)throw Error(`Vonage number insight API error: ${G.status}`);let J=await G.json();if(J.status!==0)return{valid:!1};return{valid:!0,carrier:J.current_carrier?.name,type:J.current_carrier?.network_type}}catch{return{valid:!1}}}async getBalance(){let x=typeof Buffer<"u"?Buffer.from(`${this.config.apiKey}:${this.config.apiSecret}`).toString("base64"):btoa(`${this.config.apiKey}:${this.config.apiSecret}`),M=await fetch("https://rest.nexmo.com/account/get-balance",{headers:{Authorization:`Basic ${x}`}});if(!M.ok)throw Error(`Vonage balance API error: ${M.status}`);return{balance:(await M.json()).value,currency:"EUR"}}async startVerification(x){try{let M={"Content-Type":"application/json"};if(this.config.applicationId&&this.config.privateKey){let X=await this.generateJwt();M.Authorization=`Bearer ${X}`}else M.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;let G={brand:this.config.from||"Verification",workflow:[{channel:x.channel==="whatsapp"?"whatsapp_interactive":"sms",to:x.to}]};if(x.codeLength)G.code_length=x.codeLength;if(x.locale)G.locale=x.locale;let J=await fetch("https://api.nexmo.com/v2/verify",{method:"POST",headers:M,body:JSON.stringify(G)}),L=await J.json();if(!J.ok)return{success:!1,status:"denied",error:L.title||L.detail||`HTTP ${J.status}`};return{success:!0,verificationId:L.request_id,status:"pending"}}catch(M){return{success:!1,status:"denied",error:M instanceof Error?M.message:"Unknown error"}}}async checkVerification(x){if(!x.verificationId)return{success:!1,status:"denied",error:"Verification ID is required"};try{let M={"Content-Type":"application/json"};if(this.config.applicationId&&this.config.privateKey){let L=await this.generateJwt();M.Authorization=`Bearer ${L}`}else M.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;let G=await fetch(`https://api.nexmo.com/v2/verify/${x.verificationId}`,{method:"POST",headers:M,body:JSON.stringify({code:x.code})}),J=await G.json();if(!G.ok)return{success:!1,status:"denied",error:J.title||J.detail||`HTTP ${G.status}`};return{success:J.status==="completed",verificationId:x.verificationId,status:J.status==="completed"?"approved":"denied"}}catch(M){return{success:!1,status:"denied",error:M instanceof Error?M.message:"Unknown error"}}}async cancelVerification(x){try{let M={};if(this.config.applicationId&&this.config.privateKey){let J=await this.generateJwt();M.Authorization=`Bearer ${J}`}else M.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;return(await fetch(`https://api.nexmo.com/v2/verify/${x}`,{method:"DELETE",headers:M})).ok}catch{return!1}}async generateJwt(){if(!this.config.applicationId||!this.config.privateKey)throw Error("Application ID and private key required for JWT auth");let x={typ:"JWT",alg:"RS256"},M=Math.floor(Date.now()/1000),G={application_id:this.config.applicationId,iat:M,exp:M+900,jti:crypto.randomUUID()},J=btoa(JSON.stringify(x)),L=btoa(JSON.stringify(G));return`${J}.${L}.signature`}}function b(x){return{submitted:"queued",delivered:"delivered",expired:"undelivered",failed:"failed",rejected:"failed",accepted:"sent"}[x]||"unknown"}function A(x,M=!1){return new F(x,M)}var W=null,E=null,Y={},R=null;async function S(){try{Y=(await Promise.resolve().then(() => (H(),_))).default}catch{}}async function $(){if(!R)R=S();await R}function l(x){Y={...Y,...x},W=null,E=null}function U(x){let M=x||Y.provider||"twilio";switch(M){case"twilio":{let G=Y.drivers?.twilio;if(!G?.accountSid||!G?.authToken)throw Error("Twilio configuration is incomplete. Please provide accountSid and authToken.");return new O({...G,from:G.from||Y.from})}case"vonage":{let G=Y.drivers?.vonage;if(!G?.apiKey||!G?.apiSecret)throw Error("Vonage configuration is incomplete. Please provide apiKey and apiSecret.");return new F({...G,from:G.from||Y.from})}default:throw Error(`Unsupported SMS provider: ${M}`)}}function I(x){let M=U(x);if(!("startVerification"in M))throw Error(`Provider does not support verification: ${x||Y.provider}`);return M}function K(){if(!W)W=U();return W}function j(){if(!E)E=I();return E}async function k(x){return await $(),K().send(x)}var p=k;async function v(x){return await $(),K().sendBulk(x)}async function f(x){await $();let M=K();if(M.getStatus)return M.getStatus(x);return null}async function u(x){await $();let M=K();if(M.verify)return M.verify(x);return{valid:!0}}async function m(){await $();let x=K();if(x.getBalance)return x.getBalance();return null}async function g(x){return await $(),j().startVerification(x)}async function d(x){return await $(),j().checkVerification(x)}async function n(x){await $();let M=j();if(M.cancelVerification)return M.cancelVerification(x);return!1}class V{message={};provider;to(x){return this.message.to=x,this}body(x){return this.message.body=x,this}text(x){return this.body(x)}from(x){return this.message.from=x,this}media(x){return this.message.mediaUrls=Array.isArray(x)?x:[x],this}callback(x){return this.message.statusCallback=x,this}via(x){return this.provider=x,this}async send(){if(await $(),!this.message.to)return{success:!1,to:"",error:"Recipient is required",provider:this.provider||Y.provider||"twilio"};if(!this.message.body)return{success:!1,to:Array.isArray(this.message.to)?this.message.to[0]??"":this.message.to,error:"Message body is required",provider:this.provider||Y.provider||"twilio"};return(this.provider?U(this.provider):K()).send(this.message)}}function i(){return new V}async function o(x,M,G={}){await $();let J=Y.templates?.find((X)=>X.name===M);if(!J)return{success:!1,to:Array.isArray(x)?x[0]??"":x,error:`Template not found: ${M}`,provider:Y.provider||"twilio"};let L=J.body;for(let[X,Q]of Object.entries(G)){let z=X.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");L=L.replace(new RegExp(`\\{${z}\\}`,"g"),Q)}return k({to:x,body:L})}function w(x,M){let G=x.replace(/[\s\-()]/g,"");if(G.startsWith("+"))return G;let J=M||Y.defaultCountryCode||"1";if(G.startsWith("00"))G=`+${G.slice(2)}`;else if(!G.startsWith("+"))G=`+${J}${G}`;return G}function t(x){let M=/^\+[1-9]\d{6,14}$/,G=w(x);return M.test(G)}async function a(){await S()}function r(){return Y.enabled===!0}function s(){return{...Y}}var e={init:a,configure:l,isEnabled:r,getConfig:s,send:k,sendSms:p,sendBulk:v,sendTemplate:o,getStatus:f,getBalance:m,verifyNumber:u,startVerification:g,checkVerification:d,cancelVerification:n,formatE164:w,isValidPhoneNumber:t,sms:i,getDriver:U,getVerificationDriver:I},xx=e;export{u as verifyNumber,g as startVerification,i as sms,o as sendTemplate,p as sendSms,v as sendBulk,k as send,T as nexmo,t as isValidPhoneNumber,r as isEnabled,a as init,I as getVerificationDriver,f as getStatus,U as getDriver,s as getConfig,m as getBalance,w as formatE164,A as createVonageDriver,y as createTwilioDriver,l as configure,d as checkVerification,n as cancelVerification,F as VonageDriver,O as TwilioDriver,V as SmsBuilder,xx as SMS};
package/dist/sms.d.ts CHANGED
@@ -79,7 +79,25 @@ export declare const sendSms: unknown;
79
79
  // SMS Facade Object
80
80
  // =============================================================================
81
81
  export declare const SMS: {
82
-
82
+ init: typeof init;
83
+ configure: typeof configure;
84
+ isEnabled: typeof isEnabled;
85
+ getConfig: typeof getConfig;
86
+ send: typeof send;
87
+ sendSms: typeof sendSms;
88
+ sendBulk: typeof sendBulk;
89
+ sendTemplate: typeof sendTemplate;
90
+ getStatus: typeof getStatus;
91
+ getBalance: typeof getBalance;
92
+ verifyNumber: typeof verifyNumber;
93
+ startVerification: typeof startVerification;
94
+ checkVerification: typeof checkVerification;
95
+ cancelVerification: typeof cancelVerification;
96
+ formatE164: typeof formatE164;
97
+ isValidPhoneNumber: typeof isValidPhoneNumber;
98
+ sms: typeof sms;
99
+ getDriver: typeof getDriver;
100
+ getVerificationDriver: typeof getVerificationDriver
83
101
  };
84
102
  // =============================================================================
85
103
  // SMS Builder (Fluent API)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/sms",
3
3
  "type": "module",
4
- "version": "0.70.73",
4
+ "version": "0.70.74",
5
5
  "description": "The Stacks SMS integration. Painlessly create & manage your inboxes, templates, and send sms.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -57,10 +57,10 @@
57
57
  "prepublishOnly": "bun run build"
58
58
  },
59
59
  "devDependencies": {
60
- "@stacksjs/cli": "0.70.73",
61
- "@stacksjs/config": "0.70.73",
62
- "better-dx": "^0.2.12",
63
- "@stacksjs/error-handling": "0.70.73",
64
- "@stacksjs/types": "0.70.73"
60
+ "@stacksjs/cli": "0.70.74",
61
+ "@stacksjs/config": "0.70.74",
62
+ "better-dx": "^0.2.16",
63
+ "@stacksjs/error-handling": "0.70.74",
64
+ "@stacksjs/types": "0.70.74"
65
65
  }
66
66
  }