@stacksjs/sms 0.72.38 → 0.72.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- const API_BASE="https://rest.nexmo.com",MESSAGES_API_BASE="https://api.nexmo.com/v1/messages",_VERIFY_API_BASE="https://api.nexmo.com/verify",VERIFY_V2_API_BASE="https://api.nexmo.com/v2/verify";export class VonageDriver{config;useMessagesApi;constructor(config,useMessagesApi=!1){this.config=config;this.useMessagesApi=useMessagesApi}async send(message){const recipients=Array.isArray(message.to)?message.to:[message.to];if(recipients.length>1)return(await this.sendBulk(recipients.map((to)=>({...message,to}))))[0]??{success:!1,to:recipients[0]??"",error:"No recipients supplied",provider:"vonage"};const to=recipients[0];if(!to)return{success:!1,to:"",error:"No recipient supplied",provider:"vonage"};const from=message.from||this.config.from;if(!from)return{success:!1,to,error:'No "from" number configured',provider:"vonage"};if(!/^\+?[1-9]\d{1,14}$/.test(to))return{success:!1,to,error:`Invalid E.164 phone number: ${to}. Expected +<country><number> with 8\u201315 digits.`,provider:"vonage"};try{if(this.useMessagesApi)return await this.sendWithMessagesApi(to,from,message);return await this.sendWithSmsApi(to,from,message)}catch(error){return{success:!1,to,error:error instanceof Error?error.message:"Unknown error",provider:"vonage"}}}async sendWithSmsApi(to,from,message){const body=new URLSearchParams;body.append("api_key",this.config.apiKey);body.append("api_secret",this.config.apiSecret);body.append("to",to);body.append("from",from);body.append("text",message.body);if(message.statusCallback)body.append("callback",message.statusCallback);const response=await fetch(`${API_BASE}/sms/json`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:body.toString()});if(!response.ok)throw Error(`Vonage SMS API error: ${response.status}`);const data=await response.json(),[msg]=data.messages??[];if(msg){if(msg.status!=="0")return{success:!1,to,error:msg["error-text"]||`Status: ${msg.status}`,provider:"vonage"};return{success:!0,messageId:msg["message-id"],status:"sent",to,provider:"vonage",price:msg["message-price"]?Number.parseFloat(msg["message-price"]):void 0,currency:"EUR"}}return{success:!1,to,error:"No response from Vonage",provider:"vonage"}}async sendWithMessagesApi(to,from,message){const headers={"Content-Type":"application/json"};let authHeader;if(this.config.applicationId&&this.config.privateKey)authHeader=`Bearer ${await this.generateJwt()}`;else authHeader=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;headers.Authorization=authHeader;const body={message_type:"text",channel:"sms",to,from,text:message.body};if(message.statusCallback)body.webhook_url=message.statusCallback;const response=await fetch(MESSAGES_API_BASE,{method:"POST",headers,body:JSON.stringify(body)}),data=await response.json();if(!response.ok)return{success:!1,to,error:data.title||data.detail||`HTTP ${response.status}`,provider:"vonage"};return{success:!0,messageId:data.message_uuid,status:"sent",to,provider:"vonage"}}async sendBulk(messages){return Promise.all(messages.map((msg)=>this.send(msg)))}async getStatus(messageId){const headers={};if(this.config.applicationId&&this.config.privateKey){const jwt=await this.generateJwt();headers.Authorization=`Bearer ${jwt}`}else headers.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;const response=await fetch(`${MESSAGES_API_BASE}/${messageId}`,{headers});if(!response.ok)throw Error(`Vonage message status API error: ${response.status}`);const data=await response.json();return{messageId:data.message_uuid,to:data.to,status:mapVonageStatus(data.status),timestamp:new Date(data.timestamp),errorCode:data.error?.code?.toString(),errorMessage:data.error?.reason}}async verify(phoneNumber){try{const body=new URLSearchParams;body.append("api_key",this.config.apiKey);body.append("api_secret",this.config.apiSecret);body.append("number",phoneNumber);const response=await fetch(`${API_BASE}/ni/basic/json`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:body.toString()});if(!response.ok)throw Error(`Vonage number insight API error: ${response.status}`);const data=await response.json();if(data.status!==0)return{valid:!1};return{valid:!0,carrier:data.current_carrier?.name,type:data.current_carrier?.network_type}}catch{return{valid:!1}}}async getBalance(){const auth=typeof Buffer<"u"?Buffer.from(`${this.config.apiKey}:${this.config.apiSecret}`).toString("base64"):btoa(`${this.config.apiKey}:${this.config.apiSecret}`),response=await fetch(`${API_BASE}/account/get-balance`,{headers:{Authorization:`Basic ${auth}`}});if(!response.ok)throw Error(`Vonage balance API error: ${response.status}`);return{balance:(await response.json()).value,currency:"EUR"}}async startVerification(request){try{const headers={"Content-Type":"application/json"};if(this.config.applicationId&&this.config.privateKey){const jwt=await this.generateJwt();headers.Authorization=`Bearer ${jwt}`}else headers.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;const body={brand:this.config.from||"Verification",workflow:[{channel:request.channel==="whatsapp"?"whatsapp_interactive":"sms",to:request.to}]};if(request.codeLength)body.code_length=request.codeLength;if(request.locale)body.locale=request.locale;const response=await fetch(VERIFY_V2_API_BASE,{method:"POST",headers,body:JSON.stringify(body)}),data=await response.json();if(!response.ok)return{success:!1,status:"denied",error:data.title||data.detail||`HTTP ${response.status}`};return{success:!0,verificationId:data.request_id,status:"pending"}}catch(error){return{success:!1,status:"denied",error:error instanceof Error?error.message:"Unknown error"}}}async checkVerification(request){if(!request.verificationId)return{success:!1,status:"denied",error:"Verification ID is required"};try{const headers={"Content-Type":"application/json"};if(this.config.applicationId&&this.config.privateKey){const jwt=await this.generateJwt();headers.Authorization=`Bearer ${jwt}`}else headers.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;const response=await fetch(`${VERIFY_V2_API_BASE}/${request.verificationId}`,{method:"POST",headers,body:JSON.stringify({code:request.code})}),data=await response.json();if(!response.ok)return{success:!1,status:"denied",error:data.title||data.detail||`HTTP ${response.status}`};return{success:data.status==="completed",verificationId:request.verificationId,status:data.status==="completed"?"approved":"denied"}}catch(error){return{success:!1,status:"denied",error:error instanceof Error?error.message:"Unknown error"}}}async cancelVerification(verificationId){try{const headers={};if(this.config.applicationId&&this.config.privateKey){const jwt=await this.generateJwt();headers.Authorization=`Bearer ${jwt}`}else headers.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;return(await fetch(`${VERIFY_V2_API_BASE}/${verificationId}`,{method:"DELETE",headers})).ok}catch{return!1}}async generateJwt(){if(!this.config.applicationId||!this.config.privateKey)throw Error("Application ID and private key required for JWT auth");const header={typ:"JWT",alg:"RS256"},now=Math.floor(Date.now()/1000),payload={application_id:this.config.applicationId,iat:now,exp:now+900,jti:crypto.randomUUID()},encodedHeader=btoa(JSON.stringify(header)),encodedPayload=btoa(JSON.stringify(payload));return`${encodedHeader}.${encodedPayload}.signature`}}function mapVonageStatus(status){return{submitted:"queued",delivered:"delivered",expired:"undelivered",failed:"failed",rejected:"failed",accepted:"sent"}[status]||"unknown"}export function createVonageDriver(config,useMessagesApi=!1){return new VonageDriver(config,useMessagesApi)}export{VonageDriver as default};
1
+ const API_BASE="https://rest.nexmo.com",MESSAGES_API_BASE="https://api.nexmo.com/v1/messages",_VERIFY_API_BASE="https://api.nexmo.com/verify",VERIFY_V2_API_BASE="https://api.nexmo.com/v2/verify";export class VonageDriver{config;useMessagesApi;constructor(config,useMessagesApi=!1){this.config=config;this.useMessagesApi=useMessagesApi}async send(message){const recipients=Array.isArray(message.to)?message.to:[message.to];if(recipients.length>1)return(await this.sendBulk(recipients.map((to)=>({...message,to}))))[0]??{success:!1,to:recipients[0]??"",error:"No recipients supplied",provider:"vonage"};const to=recipients[0];if(!to)return{success:!1,to:"",error:"No recipient supplied",provider:"vonage"};const from=message.from||this.config.from;if(!from)return{success:!1,to,error:'No "from" number configured',provider:"vonage"};if(!/^\+?[1-9]\d{1,14}$/.test(to))return{success:!1,to,error:`Invalid E.164 phone number: ${to}. Expected +<country><number> with 8-15 digits.`,provider:"vonage"};try{if(this.useMessagesApi)return await this.sendWithMessagesApi(to,from,message);return await this.sendWithSmsApi(to,from,message)}catch(error){return{success:!1,to,error:error instanceof Error?error.message:"Unknown error",provider:"vonage"}}}async sendWithSmsApi(to,from,message){const body=new URLSearchParams;body.append("api_key",this.config.apiKey);body.append("api_secret",this.config.apiSecret);body.append("to",to);body.append("from",from);body.append("text",message.body);if(message.statusCallback)body.append("callback",message.statusCallback);const response=await fetch(`${API_BASE}/sms/json`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:body.toString()});if(!response.ok)throw Error(`Vonage SMS API error: ${response.status}`);const data=await response.json(),[msg]=data.messages??[];if(msg){if(msg.status!=="0")return{success:!1,to,error:msg["error-text"]||`Status: ${msg.status}`,provider:"vonage"};return{success:!0,messageId:msg["message-id"],status:"sent",to,provider:"vonage",price:msg["message-price"]?Number.parseFloat(msg["message-price"]):void 0,currency:"EUR"}}return{success:!1,to,error:"No response from Vonage",provider:"vonage"}}async sendWithMessagesApi(to,from,message){const headers={"Content-Type":"application/json"};let authHeader;if(this.config.applicationId&&this.config.privateKey)authHeader=`Bearer ${await this.generateJwt()}`;else authHeader=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;headers.Authorization=authHeader;const body={message_type:"text",channel:"sms",to,from,text:message.body};if(message.statusCallback)body.webhook_url=message.statusCallback;const response=await fetch(MESSAGES_API_BASE,{method:"POST",headers,body:JSON.stringify(body)}),data=await response.json();if(!response.ok)return{success:!1,to,error:data.title||data.detail||`HTTP ${response.status}`,provider:"vonage"};return{success:!0,messageId:data.message_uuid,status:"sent",to,provider:"vonage"}}async sendBulk(messages){return Promise.all(messages.map((msg)=>this.send(msg)))}async getStatus(messageId){const headers={};if(this.config.applicationId&&this.config.privateKey){const jwt=await this.generateJwt();headers.Authorization=`Bearer ${jwt}`}else headers.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;const response=await fetch(`${MESSAGES_API_BASE}/${messageId}`,{headers});if(!response.ok)throw Error(`Vonage message status API error: ${response.status}`);const data=await response.json();return{messageId:data.message_uuid,to:data.to,status:mapVonageStatus(data.status),timestamp:new Date(data.timestamp),errorCode:data.error?.code?.toString(),errorMessage:data.error?.reason}}async verify(phoneNumber){try{const body=new URLSearchParams;body.append("api_key",this.config.apiKey);body.append("api_secret",this.config.apiSecret);body.append("number",phoneNumber);const response=await fetch(`${API_BASE}/ni/basic/json`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:body.toString()});if(!response.ok)throw Error(`Vonage number insight API error: ${response.status}`);const data=await response.json();if(data.status!==0)return{valid:!1};return{valid:!0,carrier:data.current_carrier?.name,type:data.current_carrier?.network_type}}catch{return{valid:!1}}}async getBalance(){const auth=typeof Buffer<"u"?Buffer.from(`${this.config.apiKey}:${this.config.apiSecret}`).toString("base64"):btoa(`${this.config.apiKey}:${this.config.apiSecret}`),response=await fetch(`${API_BASE}/account/get-balance`,{headers:{Authorization:`Basic ${auth}`}});if(!response.ok)throw Error(`Vonage balance API error: ${response.status}`);return{balance:(await response.json()).value,currency:"EUR"}}async startVerification(request){try{const headers={"Content-Type":"application/json"};if(this.config.applicationId&&this.config.privateKey){const jwt=await this.generateJwt();headers.Authorization=`Bearer ${jwt}`}else headers.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;const body={brand:this.config.from||"Verification",workflow:[{channel:request.channel==="whatsapp"?"whatsapp_interactive":"sms",to:request.to}]};if(request.codeLength)body.code_length=request.codeLength;if(request.locale)body.locale=request.locale;const response=await fetch(VERIFY_V2_API_BASE,{method:"POST",headers,body:JSON.stringify(body)}),data=await response.json();if(!response.ok)return{success:!1,status:"denied",error:data.title||data.detail||`HTTP ${response.status}`};return{success:!0,verificationId:data.request_id,status:"pending"}}catch(error){return{success:!1,status:"denied",error:error instanceof Error?error.message:"Unknown error"}}}async checkVerification(request){if(!request.verificationId)return{success:!1,status:"denied",error:"Verification ID is required"};try{const headers={"Content-Type":"application/json"};if(this.config.applicationId&&this.config.privateKey){const jwt=await this.generateJwt();headers.Authorization=`Bearer ${jwt}`}else headers.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;const response=await fetch(`${VERIFY_V2_API_BASE}/${request.verificationId}`,{method:"POST",headers,body:JSON.stringify({code:request.code})}),data=await response.json();if(!response.ok)return{success:!1,status:"denied",error:data.title||data.detail||`HTTP ${response.status}`};return{success:data.status==="completed",verificationId:request.verificationId,status:data.status==="completed"?"approved":"denied"}}catch(error){return{success:!1,status:"denied",error:error instanceof Error?error.message:"Unknown error"}}}async cancelVerification(verificationId){try{const headers={};if(this.config.applicationId&&this.config.privateKey){const jwt=await this.generateJwt();headers.Authorization=`Bearer ${jwt}`}else headers.Authorization=`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`;return(await fetch(`${VERIFY_V2_API_BASE}/${verificationId}`,{method:"DELETE",headers})).ok}catch{return!1}}async generateJwt(){if(!this.config.applicationId||!this.config.privateKey)throw Error("Application ID and private key required for JWT auth");const header={typ:"JWT",alg:"RS256"},now=Math.floor(Date.now()/1000),payload={application_id:this.config.applicationId,iat:now,exp:now+900,jti:crypto.randomUUID()},encodedHeader=btoa(JSON.stringify(header)),encodedPayload=btoa(JSON.stringify(payload));return`${encodedHeader}.${encodedPayload}.signature`}}function mapVonageStatus(status){return{submitted:"queued",delivered:"delivered",expired:"undelivered",failed:"failed",rejected:"failed",accepted:"sent"}[status]||"unknown"}export function createVonageDriver(config,useMessagesApi=!1){return new VonageDriver(config,useMessagesApi)}export{VonageDriver as default};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/sms",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.72.38",
5
+ "version": "0.72.40",
6
6
  "description": "The Stacks SMS integration. Painlessly create & manage your inboxes, templates, and send sms.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -63,10 +63,10 @@
63
63
  "prepublishOnly": "bun run build"
64
64
  },
65
65
  "devDependencies": {
66
- "@stacksjs/cli": "0.72.38",
67
- "@stacksjs/config": "0.72.38",
66
+ "@stacksjs/cli": "0.72.40",
67
+ "@stacksjs/config": "0.72.40",
68
68
  "better-dx": "^0.2.24",
69
- "@stacksjs/error-handling": "0.72.38",
70
- "@stacksjs/types": "0.72.38"
69
+ "@stacksjs/error-handling": "0.72.40",
70
+ "@stacksjs/types": "0.72.40"
71
71
  }
72
72
  }