@stacksjs/sms
Version:
The Stacks SMS integration. Painlessly create & manage your inboxes, templates, and send sms.
3 lines (2 loc) • 16.7 kB
JavaScript
// @bun
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};