sently 0.9.2 → 0.10.0

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.
Files changed (219) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +18 -16
  4. package/dist/adapters/bun.js +1 -1
  5. package/dist/adapters/cf.js +2 -2
  6. package/dist/adapters/cf.js.map +1 -1
  7. package/dist/adapters/deno.js +1 -1
  8. package/dist/adapters/node.js +1 -1
  9. package/dist/auth/oauth2.js +2 -2
  10. package/dist/auth/oauth2.js.map +1 -1
  11. package/dist/channel-result.d.ts +6 -0
  12. package/dist/channel-result.js +1 -0
  13. package/dist/{chunk-skqhj0rm.js → chunk-1ke3nmyg.js} +3 -3
  14. package/dist/{chunk-skqhj0rm.js.map → chunk-1ke3nmyg.js.map} +1 -1
  15. package/dist/{chunk-7gy2q9sh.js → chunk-5es0v1j7.js} +2 -2
  16. package/dist/{chunk-7gy2q9sh.js.map → chunk-5es0v1j7.js.map} +1 -1
  17. package/dist/{chunk-0qxws3kj.js → chunk-7wtfjx0d.js} +2 -2
  18. package/dist/{chunk-0qxws3kj.js.map → chunk-7wtfjx0d.js.map} +1 -1
  19. package/dist/chunk-8kpgbrba.js +4 -0
  20. package/dist/{chunk-93vqxxj2.js.map → chunk-8kpgbrba.js.map} +2 -2
  21. package/dist/{chunk-vjpds2pf.js → chunk-a057jzsy.js} +2 -2
  22. package/dist/{chunk-vjpds2pf.js.map → chunk-a057jzsy.js.map} +1 -1
  23. package/dist/{chunk-8njyga62.js → chunk-bhr7w3gg.js} +2 -2
  24. package/dist/{chunk-8njyga62.js.map → chunk-bhr7w3gg.js.map} +1 -1
  25. package/dist/chunk-cs1tvehn.js +4 -0
  26. package/dist/chunk-cs1tvehn.js.map +10 -0
  27. package/dist/{chunk-29z7fkzy.js → chunk-d5aw5vag.js} +3 -3
  28. package/dist/{chunk-29z7fkzy.js.map → chunk-d5aw5vag.js.map} +1 -1
  29. package/dist/chunk-f5x8vesr.js +4 -0
  30. package/dist/{chunk-b602dhck.js.map → chunk-f5x8vesr.js.map} +2 -2
  31. package/dist/chunk-fv0bemgf.js +4 -0
  32. package/dist/{chunk-yp311efr.js.map → chunk-fv0bemgf.js.map} +3 -3
  33. package/dist/{chunk-ywq6s10g.js → chunk-hnzmn4s4.js} +2 -2
  34. package/dist/{chunk-ywq6s10g.js.map → chunk-hnzmn4s4.js.map} +1 -1
  35. package/dist/chunk-htdg075t.js +4 -0
  36. package/dist/{chunk-qtkd5bak.js.map → chunk-htdg075t.js.map} +4 -3
  37. package/dist/{chunk-0gqy32pe.js → chunk-q2mahnb7.js} +3 -3
  38. package/dist/chunk-q2mahnb7.js.map +10 -0
  39. package/dist/chunk-sbgt4165.js +4 -0
  40. package/dist/chunk-sbgt4165.js.map +10 -0
  41. package/dist/{chunk-6npp3x3c.js → chunk-th4cwrpb.js} +2 -2
  42. package/dist/{chunk-6npp3x3c.js.map → chunk-th4cwrpb.js.map} +1 -1
  43. package/dist/{chunk-tamww15j.js → chunk-ttbwyxmh.js} +2 -2
  44. package/dist/{chunk-tamww15j.js.map → chunk-ttbwyxmh.js.map} +1 -1
  45. package/dist/chunk-w4c9kj70.js +13 -0
  46. package/dist/{chunk-n0qeyzqm.js.map → chunk-w4c9kj70.js.map} +2 -2
  47. package/dist/chunk-w9sabmb0.js +4 -0
  48. package/dist/chunk-w9sabmb0.js.map +10 -0
  49. package/dist/{chunk-8dty8c4p.js → chunk-yg3kj723.js} +3 -3
  50. package/dist/{chunk-8dty8c4p.js.map → chunk-yg3kj723.js.map} +2 -2
  51. package/dist/chunk-z1589fjk.js +4 -0
  52. package/dist/chunk-z1589fjk.js.map +10 -0
  53. package/dist/core/channel-result.d.ts +36 -0
  54. package/dist/core/channel-result.js +3 -0
  55. package/dist/core/channel-result.js.map +10 -0
  56. package/dist/core/decorated-transport.d.ts +30 -0
  57. package/dist/core/decorator-hooks.d.ts +17 -0
  58. package/dist/core/errors.js +2 -2
  59. package/dist/core/errors.js.map +1 -1
  60. package/dist/core/provider-label.d.ts +7 -3
  61. package/dist/core/push-endpoint.d.ts +5 -0
  62. package/dist/core/push-types.d.ts +48 -14
  63. package/dist/core/sms-types.d.ts +12 -0
  64. package/dist/core/smtp.js +2 -2
  65. package/dist/core/smtp.js.map +1 -1
  66. package/dist/core/whatsapp-types.d.ts +12 -0
  67. package/dist/detect.js +2 -2
  68. package/dist/detect.js.map +1 -1
  69. package/dist/dkim.js +2 -2
  70. package/dist/dkim.js.map +2 -2
  71. package/dist/idempotency.js +2 -2
  72. package/dist/idempotency.js.map +1 -1
  73. package/dist/index.d.ts +4 -0
  74. package/dist/index.js +5 -0
  75. package/dist/mailer.js +2 -2
  76. package/dist/mailer.js.map +1 -1
  77. package/dist/observability/console.js +1 -1
  78. package/dist/plugins/react.js +2 -2
  79. package/dist/plugins/react.js.map +1 -1
  80. package/dist/plugins/template.js +1 -1
  81. package/dist/pool/pool.js +2 -2
  82. package/dist/pool/pool.js.map +1 -1
  83. package/dist/push.d.ts +1 -0
  84. package/dist/push.js +2 -2
  85. package/dist/push.js.map +3 -3
  86. package/dist/sms.d.ts +1 -0
  87. package/dist/sms.js +2 -2
  88. package/dist/sms.js.map +3 -3
  89. package/dist/smtp-mailer.js +2 -2
  90. package/dist/smtp-mailer.js.map +1 -1
  91. package/dist/transports/brevo.js +2 -2
  92. package/dist/transports/brevo.js.map +2 -2
  93. package/dist/transports/cloudflare-email.js +2 -2
  94. package/dist/transports/cloudflare-email.js.map +2 -2
  95. package/dist/transports/fallback.d.ts +32 -6
  96. package/dist/transports/fallback.js +2 -2
  97. package/dist/transports/fallback.js.map +1 -1
  98. package/dist/transports/fcm.d.ts +55 -0
  99. package/dist/transports/fcm.js +4 -0
  100. package/dist/transports/fcm.js.map +10 -0
  101. package/dist/transports/loops.js +2 -2
  102. package/dist/transports/loops.js.map +1 -1
  103. package/dist/transports/mailersend.js +2 -2
  104. package/dist/transports/mailersend.js.map +2 -2
  105. package/dist/transports/mailgun.js +2 -2
  106. package/dist/transports/mailgun.js.map +2 -2
  107. package/dist/transports/mailtrap.js +2 -2
  108. package/dist/transports/mailtrap.js.map +2 -2
  109. package/dist/transports/msegat.js +2 -2
  110. package/dist/transports/msegat.js.map +1 -1
  111. package/dist/transports/plunk.js +2 -2
  112. package/dist/transports/plunk.js.map +1 -1
  113. package/dist/transports/postmark.js +2 -2
  114. package/dist/transports/postmark.js.map +2 -2
  115. package/dist/transports/preview.js +2 -2
  116. package/dist/transports/preview.js.map +1 -1
  117. package/dist/transports/resend.js +2 -2
  118. package/dist/transports/resend.js.map +2 -2
  119. package/dist/transports/retry.d.ts +28 -9
  120. package/dist/transports/retry.js +2 -2
  121. package/dist/transports/retry.js.map +3 -3
  122. package/dist/transports/sendgrid.js +2 -2
  123. package/dist/transports/sendgrid.js.map +2 -2
  124. package/dist/transports/ses.js +2 -2
  125. package/dist/transports/ses.js.map +2 -2
  126. package/dist/transports/smtp.js +2 -2
  127. package/dist/transports/smtp.js.map +1 -1
  128. package/dist/transports/sndr.js +2 -2
  129. package/dist/transports/sndr.js.map +1 -1
  130. package/dist/transports/sparkpost.js +2 -2
  131. package/dist/transports/sparkpost.js.map +2 -2
  132. package/dist/transports/taqnyat-mail.js +2 -2
  133. package/dist/transports/taqnyat-mail.js.map +1 -1
  134. package/dist/transports/taqnyat-sms.js +2 -2
  135. package/dist/transports/taqnyat-sms.js.map +1 -1
  136. package/dist/transports/taqnyat-whatsapp.js +2 -2
  137. package/dist/transports/taqnyat-whatsapp.js.map +1 -1
  138. package/dist/transports/twilio-sms.js +2 -2
  139. package/dist/transports/twilio-sms.js.map +2 -2
  140. package/dist/transports/unifonic.d.ts +81 -0
  141. package/dist/transports/unifonic.js +3 -0
  142. package/dist/transports/unifonic.js.map +10 -0
  143. package/dist/transports/webpush.js +2 -2
  144. package/dist/transports/webpush.js.map +3 -3
  145. package/dist/transports/weighted-fallback.d.ts +11 -9
  146. package/dist/transports/weighted-fallback.js +2 -2
  147. package/dist/transports/weighted-fallback.js.map +3 -3
  148. package/dist/transports/whatsapp-cloud.js +2 -2
  149. package/dist/transports/whatsapp-cloud.js.map +1 -1
  150. package/dist/webhooks/brevo.js +2 -2
  151. package/dist/webhooks/brevo.js.map +1 -1
  152. package/dist/webhooks/mailgun.js +2 -2
  153. package/dist/webhooks/mailgun.js.map +1 -1
  154. package/dist/webhooks/postmark.js +2 -2
  155. package/dist/webhooks/postmark.js.map +1 -1
  156. package/dist/webhooks/resend.js +2 -2
  157. package/dist/webhooks/resend.js.map +1 -1
  158. package/dist/webhooks/sendgrid.js +2 -2
  159. package/dist/webhooks/sendgrid.js.map +1 -1
  160. package/dist/webhooks/ses.js +2 -2
  161. package/dist/webhooks/ses.js.map +1 -1
  162. package/dist/webhooks/sndr.js +2 -2
  163. package/dist/webhooks/sndr.js.map +1 -1
  164. package/dist/webhooks/timing-safe-equal.js +1 -1
  165. package/dist/webhooks/twilio-sms.d.ts +20 -0
  166. package/dist/webhooks/twilio-sms.js +3 -0
  167. package/dist/webhooks/twilio-sms.js.map +10 -0
  168. package/dist/webhooks/types.d.ts +37 -3
  169. package/dist/webhooks/types.js +3 -0
  170. package/dist/webhooks/types.js.map +9 -0
  171. package/dist/webhooks/unifonic.d.ts +13 -0
  172. package/dist/webhooks/unifonic.js +3 -0
  173. package/dist/webhooks/unifonic.js.map +10 -0
  174. package/dist/webhooks/whatsapp-cloud.d.ts +14 -0
  175. package/dist/webhooks/whatsapp-cloud.js +3 -0
  176. package/dist/webhooks/whatsapp-cloud.js.map +10 -0
  177. package/dist/webhooks.d.ts +10 -5
  178. package/dist/webhooks.js +11 -0
  179. package/dist/whatsapp.d.ts +1 -0
  180. package/dist/whatsapp.js +2 -2
  181. package/dist/whatsapp.js.map +3 -3
  182. package/package.json +25 -1
  183. package/site/content/docs/channels/hooks.mdx +6 -5
  184. package/site/content/docs/channels/index.mdx +2 -2
  185. package/site/content/docs/channels/push.mdx +27 -21
  186. package/site/content/docs/channels/sms.mdx +4 -1
  187. package/site/content/docs/decorators/fallback.mdx +41 -14
  188. package/site/content/docs/decorators/index.mdx +29 -12
  189. package/site/content/docs/decorators/retry.mdx +32 -9
  190. package/site/content/docs/decorators/weighted-fallback.mdx +27 -6
  191. package/site/content/docs/get-started/entrypoints.mdx +6 -5
  192. package/site/content/docs/get-started/installation.mdx +3 -2
  193. package/site/content/docs/get-started/introduction.mdx +1 -1
  194. package/site/content/docs/guides/webhooks.mdx +35 -20
  195. package/site/content/docs/index.mdx +1 -1
  196. package/site/content/docs/quick-start/push.mdx +22 -5
  197. package/site/content/docs/reference/channel-result.mdx +82 -0
  198. package/site/content/docs/reference/exports.mdx +5 -3
  199. package/site/content/docs/reference/meta.json +1 -0
  200. package/site/content/docs/reference/push-options.mdx +44 -3
  201. package/site/content/docs/reference/sms-options.mdx +18 -2
  202. package/site/content/docs/reference/webhook-events.mdx +38 -14
  203. package/site/content/docs/reference/whatsapp-options.mdx +19 -1
  204. package/site/content/docs/transports/fcm.mdx +80 -0
  205. package/site/content/docs/transports/index.mdx +3 -1
  206. package/site/content/docs/transports/meta.json +3 -1
  207. package/site/content/docs/transports/unifonic.mdx +85 -0
  208. package/dist/chunk-0gqy32pe.js.map +0 -10
  209. package/dist/chunk-5915vbcj.js +0 -4
  210. package/dist/chunk-5915vbcj.js.map +0 -10
  211. package/dist/chunk-93vqxxj2.js +0 -4
  212. package/dist/chunk-b602dhck.js +0 -4
  213. package/dist/chunk-gp4fwrs8.js +0 -4
  214. package/dist/chunk-gp4fwrs8.js.map +0 -10
  215. package/dist/chunk-n0qeyzqm.js +0 -13
  216. package/dist/chunk-q8k3m6qe.js +0 -4
  217. package/dist/chunk-q8k3m6qe.js.map +0 -10
  218. package/dist/chunk-qtkd5bak.js +0 -4
  219. package/dist/chunk-yp311efr.js +0 -4
@@ -1,3 +1,3 @@
1
- import{Q as H,S as I}from"../chunk-93vqxxj2.js";import"../chunk-ywq6s10g.js";import{ca as J,da as K}from"../chunk-tamww15j.js";import"../chunk-6npp3x3c.js";class D extends J{statusCode;apiError;constructor(b,z,B){super(b,K(z>=100&&z<600?z:400),{statusCode:z,provider:"taqnyat-mail",cause:B});this.statusCode=z;this.apiError=B;this.name="TaqnyatMailError"}}class P{provider="taqnyat-mail";bearerToken;campaignName;constructor(b){this.bearerToken=b.bearerToken,this.campaignName=b.campaignName}async send(b){let z=H(b.from)[0],B=b.html??b.text;if(!B)throw new D("Taqnyat mail requires html or text body",400,{message:"Missing body"});if(!z?.address)throw new D("Taqnyat mail requires a from address",400,{message:"Missing from"});let F=I(b.to),N=new URLSearchParams({bearerTokens:this.bearerToken,campaignName:this.campaignName,subject:b.subject,from:z.address,to:F.join(","),msg:B}),G=await fetch("https://api.taqnyat.sa/mailSend.php",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/x-www-form-urlencoded"},body:N}),v=await G.json();if(!(v.ResponseStatus==="success"&&v.Data!=null&&v.Error==null)){let O=v.Error?.MessageEn?.trim()||v.Error?.MessageAr?.trim()||"Taqnyat Email API error";throw new D(O,v.Error?.ErrorCode??(G.ok?400:G.status),v)}return{messageId:String(v.Data?.msgId??b.messageId??""),accepted:F,rejected:[],response:v.Data?.MessageEn??"sent successfully",envelope:{from:z.address,to:F},provider:"taqnyat-mail"}}async verify(){return{ok:Boolean(this.bearerToken&&this.campaignName),provider:"taqnyat-mail",message:this.bearerToken&&this.campaignName?"Credentials present":"Missing credentials"}}}export{P as TaqnyatMailTransport,D as TaqnyatMailError};
1
+ import{S as H,U as I}from"../chunk-8kpgbrba.js";import"../chunk-hnzmn4s4.js";import{ja as J,ka as K}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";class D extends J{statusCode;apiError;constructor(b,z,B){super(b,K(z>=100&&z<600?z:400),{statusCode:z,provider:"taqnyat-mail",cause:B});this.statusCode=z;this.apiError=B;this.name="TaqnyatMailError"}}class P{provider="taqnyat-mail";bearerToken;campaignName;constructor(b){this.bearerToken=b.bearerToken,this.campaignName=b.campaignName}async send(b){let z=H(b.from)[0],B=b.html??b.text;if(!B)throw new D("Taqnyat mail requires html or text body",400,{message:"Missing body"});if(!z?.address)throw new D("Taqnyat mail requires a from address",400,{message:"Missing from"});let F=I(b.to),N=new URLSearchParams({bearerTokens:this.bearerToken,campaignName:this.campaignName,subject:b.subject,from:z.address,to:F.join(","),msg:B}),G=await fetch("https://api.taqnyat.sa/mailSend.php",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/x-www-form-urlencoded"},body:N}),v=await G.json();if(!(v.ResponseStatus==="success"&&v.Data!=null&&v.Error==null)){let O=v.Error?.MessageEn?.trim()||v.Error?.MessageAr?.trim()||"Taqnyat Email API error";throw new D(O,v.Error?.ErrorCode??(G.ok?400:G.status),v)}return{messageId:String(v.Data?.msgId??b.messageId??""),accepted:F,rejected:[],response:v.Data?.MessageEn??"sent successfully",envelope:{from:z.address,to:F},provider:"taqnyat-mail"}}async verify(){return{ok:Boolean(this.bearerToken&&this.campaignName),provider:"taqnyat-mail",message:this.bearerToken&&this.campaignName?"Credentials present":"Missing credentials"}}}export{P as TaqnyatMailTransport,D as TaqnyatMailError};
2
2
 
3
- //# debugId=BC80D2F97553209A64756E2164756E21
3
+ //# debugId=9C656A01E86E9E8164756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "/**\n * @module\n * Taqnyat Email API transport (`mailSend.php`).\n *\n * Follows [Taqnyat Email](https://dev.taqnyat.sa/ar/doc/mail/):\n * `POST https://api.taqnyat.sa/mailSend.php` with `bearerTokens`, `campaignName`,\n * `subject`, `from`, `to`, `msg`. Success: HTTP 201 + `ResponseStatus: \"success\"`\n * and `Data.msgId`.\n *\n * Sently-first: wire into {@link createMailer}.\n *\n * @example\n * ```ts\n * import { createMailer } from \"sently/mailer\";\n * import { TaqnyatMailTransport } from \"sently/transports/taqnyat-mail\";\n *\n * const mailer = await createMailer({\n * transport: new TaqnyatMailTransport({\n * bearerToken: process.env.TAQNYAT_MAIL_TOKEN!,\n * campaignName: \"transactional\",\n * }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\n\n/** Taqnyat Email API configuration. */\nexport interface TaqnyatMailConfig {\n /** Bearer token from a Taqnyat application enabled for Email (`bearerTokens`). */\n bearerToken: string;\n /**\n * Campaign name required by `mailSend.php`.\n * Must be consistent across channels when using Taqnyat failover features.\n */\n campaignName: string;\n}\n\n/** Error thrown when the Taqnyat Email API reports a failure. */\nexport class TaqnyatMailError extends SentlyError {\n /** Creates a Taqnyat Email API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(\n message,\n httpStatusToSentlyCode(statusCode >= 100 && statusCode < 600 ? statusCode : 400),\n {\n statusCode,\n provider: \"taqnyat-mail\",\n cause: apiError,\n },\n );\n this.name = \"TaqnyatMailError\";\n }\n}\n\n/**\n * Taqnyat Email transport via `mailSend.php`.\n */\nexport class TaqnyatMailTransport implements Transport {\n readonly provider = \"taqnyat-mail\";\n\n private readonly bearerToken: string;\n private readonly campaignName: string;\n\n /** Creates a Taqnyat Email transport. */\n constructor(config: TaqnyatMailConfig) {\n this.bearerToken = config.bearerToken;\n this.campaignName = config.campaignName;\n }\n\n /** Sends an email via Taqnyat `mailSend.php`. */\n async send(options: MailOptions): Promise<SendResult> {\n const from = parseAddresses(options.from)[0];\n const msg = options.html ?? options.text;\n if (!msg) {\n throw new TaqnyatMailError(\"Taqnyat mail requires html or text body\", 400, {\n message: \"Missing body\",\n });\n }\n if (!from?.address) {\n throw new TaqnyatMailError(\"Taqnyat mail requires a from address\", 400, {\n message: \"Missing from\",\n });\n }\n\n const toList = extractEmails(options.to);\n // Docs examples: plain email addresses + comma-separated `to`, `bearerTokens` param.\n const params = new URLSearchParams({\n bearerTokens: this.bearerToken,\n campaignName: this.campaignName,\n subject: options.subject,\n from: from.address,\n to: toList.join(\",\"),\n msg,\n });\n\n const response = await fetch(\"https://api.taqnyat.sa/mailSend.php\", {\n method: \"POST\",\n headers: {\n // Auth section: Bearer for REST; examples also pass bearerTokens in the body.\n Authorization: `Bearer ${this.bearerToken}`,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params,\n });\n\n const payload = (await response.json()) as {\n status?: number;\n ResponseStatus?: string;\n Data?: {\n msgId?: number | string;\n MessageEn?: string;\n MessageAr?: string;\n price?: string;\n } | null;\n Error?: {\n ErrorCode?: number;\n MessageEn?: string;\n MessageAr?: string;\n } | null;\n };\n\n const succeeded =\n payload.ResponseStatus === \"success\" && payload.Data != null && payload.Error == null;\n\n if (!succeeded) {\n const message =\n payload.Error?.MessageEn?.trim() ||\n payload.Error?.MessageAr?.trim() ||\n \"Taqnyat Email API error\";\n throw new TaqnyatMailError(\n message,\n payload.Error?.ErrorCode ?? (response.ok ? 400 : response.status),\n payload,\n );\n }\n\n return {\n messageId: String(payload.Data?.msgId ?? options.messageId ?? \"\"),\n accepted: toList,\n rejected: [],\n response: payload.Data?.MessageEn ?? \"sent successfully\",\n envelope: {\n from: from.address,\n to: toList,\n },\n provider: \"taqnyat-mail\",\n };\n }\n\n /** Lightweight credential check. */\n async verify(): Promise<VerifyResult> {\n return {\n ok: Boolean(this.bearerToken && this.campaignName),\n provider: \"taqnyat-mail\",\n message:\n this.bearerToken && this.campaignName ? \"Credentials present\" : \"Missing credentials\",\n };\n }\n}\n"
6
6
  ],
7
7
  "mappings": "2KAwCO,CAAM,KAAyB,JAAY,CAI9B,WACA,SAHlB,WAAW,CACT,EACgB,EACA,EAChB,CACA,MACE,EACA,EAAuB,GAAc,KAAO,EAAa,IAAM,EAAa,GAAG,EAC/E,CACE,aACA,SAAU,eACV,MAAO,CACT,CACF,EAXgB,kBACA,gBAWhB,KAAK,KAAO,mBAEhB,CAKO,MAAM,CAA0C,CAC5C,SAAW,eAEH,YACA,aAGjB,WAAW,CAAC,EAA2B,CACrC,KAAK,YAAc,EAAO,YAC1B,KAAK,aAAe,EAAO,kBAIvB,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAO,EAAe,EAAQ,IAAI,EAAE,GACpC,EAAM,EAAQ,MAAQ,EAAQ,KACpC,GAAI,CAAC,EACH,MAAM,IAAI,EAAiB,0CAA2C,IAAK,CACzE,QAAS,cACX,CAAC,EAEH,GAAI,CAAC,GAAM,QACT,MAAM,IAAI,EAAiB,uCAAwC,IAAK,CACtE,QAAS,cACX,CAAC,EAGH,IAAM,EAAS,EAAc,EAAQ,EAAE,EAEjC,EAAS,IAAI,gBAAgB,CACjC,aAAc,KAAK,YACnB,aAAc,KAAK,aACnB,QAAS,EAAQ,QACjB,KAAM,EAAK,QACX,GAAI,EAAO,KAAK,GAAG,EACnB,KACF,CAAC,EAEK,EAAW,MAAM,MAAM,sCAAuC,CAClE,OAAQ,OACR,QAAS,CAEP,cAAe,UAAU,KAAK,cAC9B,eAAgB,mCAClB,EACA,KAAM,CACR,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAmBrC,GAAI,EAFF,EAAQ,iBAAmB,WAAa,EAAQ,MAAQ,MAAQ,EAAQ,OAAS,MAEnE,CACd,IAAM,EACJ,EAAQ,OAAO,WAAW,KAAK,GAC/B,EAAQ,OAAO,WAAW,KAAK,GAC/B,0BACF,MAAM,IAAI,EACR,EACA,EAAQ,OAAO,YAAc,EAAS,GAAK,IAAM,EAAS,QAC1D,CACF,EAGF,MAAO,CACL,UAAW,OAAO,EAAQ,MAAM,OAAS,EAAQ,WAAa,EAAE,EAChE,SAAU,EACV,SAAU,CAAC,EACX,SAAU,EAAQ,MAAM,WAAa,oBACrC,SAAU,CACR,KAAM,EAAK,QACX,GAAI,CACN,EACA,SAAU,cACZ,OAII,OAAM,EAA0B,CACpC,MAAO,CACL,GAAI,QAAQ,KAAK,aAAe,KAAK,YAAY,EACjD,SAAU,eACV,QACE,KAAK,aAAe,KAAK,aAAe,sBAAwB,qBACpE,EAEJ",
8
- "debugId": "BC80D2F97553209A64756E2164756E21",
8
+ "debugId": "9C656A01E86E9E8164756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,3 +1,3 @@
1
- import{h as O}from"../chunk-7gy2q9sh.js";import{ca as W,da as X}from"../chunk-tamww15j.js";import"../chunk-6npp3x3c.js";class K extends W{statusCode;apiError;constructor(A,B,G){super(A,X(B>=100&&B<600?B:400),{statusCode:B,provider:"taqnyat-sms",cause:G});this.statusCode=B;this.apiError=G;this.name="TaqnyatSmsError"}}function R(A){let B=A.code??A.statusCode??A.status;if(typeof B==="number")return B;if(typeof B==="string"&&/^-?\d+$/.test(B))return Number(B);return}function Q(A){if(typeof A.message==="string")return A.message;if(typeof A.MessageEn==="string")return A.MessageEn;return}function U(A){if(Array.isArray(A)){let B=A[0];return B&&typeof B==="object"?B:{}}if(A&&typeof A==="object")return A;return{}}class Z{provider="taqnyat-sms";bearerToken;sender;constructor(A){this.bearerToken=A.bearerToken,this.sender=A.sender}async send(A){let G={recipients:[O(A.to)],body:A.body,sender:A.from??this.sender};if(A.messageId!==void 0)G.smsId=A.messageId;let I=await fetch("https://api.taqnyat.sa/v1/messages",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(G)}),D=await I.json();if(I.status!==201)throw new K(D.message??"Taqnyat SMS API error",D.statusCode??I.status,D);return{messageId:String(D.messageId??A.messageId??""),to:A.to,status:"accepted",response:`cost: ${D.cost??"?"} ${D.currency??"SAR"}`,provider:"taqnyat-sms"}}async sendOtp(A){let B=A.lang??"ar",G=O(A.to),I=[{apiKey:this.bearerToken,numbers:[G],method:"sms",sender:A.from??this.sender,lang:B,requestId:A.requestId,returnJson:1,...A.note!==void 0?{note:A.note}:{}}],D=await fetch("https://api.taqnyat.sa/verify.php/",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(I)}),J=await D.text(),N;try{N=JSON.parse(J)}catch{throw new K("Taqnyat Verify returned non-JSON response",D.status,J)}let H=U(N),F=R(H);if(!D.ok||F!==5)throw new K(Q(H)??`Taqnyat OTP send failed (code ${F??"unknown"})`,F??D.status,H);return{requestId:A.requestId,to:A.to,code:F,response:J,provider:"taqnyat-sms"}}async verifyOtp(A){let B=A.lang??"ar",G=O(A.to),I=[{apiKey:this.bearerToken,numbers:[G],method:"sms",sender:A.from??this.sender,lang:B,requestId:A.requestId,activeKey:A.code,returnJson:1,...A.note!==void 0?{note:A.note}:{}}],D=await fetch("https://api.taqnyat.sa/verify.php/",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(I)}),J=await D.text(),N;try{N=JSON.parse(J)}catch{throw new K("Taqnyat Verify returned non-JSON response",D.status,J)}let H=U(N),F=R(H),Y=new Set([10,13,19]);if(!D.ok||F===void 0||!Y.has(F))throw new K(Q(H)??`Taqnyat OTP verify failed (code ${F??"unknown"})`,F??D.status,H);return{ok:!0,code:F,message:Q(H)??"Activation process completed successfully",response:J,provider:"taqnyat-sms"}}async verify(){return{ok:Boolean(this.bearerToken&&this.sender),provider:"taqnyat-sms",message:this.bearerToken&&this.sender?"Credentials present":"Missing credentials"}}}export{Z as TaqnyatSmsTransport,K as TaqnyatSmsError};
1
+ import{j as O}from"../chunk-5es0v1j7.js";import{ja as W,ka as X}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";class K extends W{statusCode;apiError;constructor(A,B,G){super(A,X(B>=100&&B<600?B:400),{statusCode:B,provider:"taqnyat-sms",cause:G});this.statusCode=B;this.apiError=G;this.name="TaqnyatSmsError"}}function R(A){let B=A.code??A.statusCode??A.status;if(typeof B==="number")return B;if(typeof B==="string"&&/^-?\d+$/.test(B))return Number(B);return}function Q(A){if(typeof A.message==="string")return A.message;if(typeof A.MessageEn==="string")return A.MessageEn;return}function U(A){if(Array.isArray(A)){let B=A[0];return B&&typeof B==="object"?B:{}}if(A&&typeof A==="object")return A;return{}}class Z{provider="taqnyat-sms";bearerToken;sender;constructor(A){this.bearerToken=A.bearerToken,this.sender=A.sender}async send(A){let G={recipients:[O(A.to)],body:A.body,sender:A.from??this.sender};if(A.messageId!==void 0)G.smsId=A.messageId;let I=await fetch("https://api.taqnyat.sa/v1/messages",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(G)}),D=await I.json();if(I.status!==201)throw new K(D.message??"Taqnyat SMS API error",D.statusCode??I.status,D);return{messageId:String(D.messageId??A.messageId??""),to:A.to,status:"accepted",response:`cost: ${D.cost??"?"} ${D.currency??"SAR"}`,provider:"taqnyat-sms"}}async sendOtp(A){let B=A.lang??"ar",G=O(A.to),I=[{apiKey:this.bearerToken,numbers:[G],method:"sms",sender:A.from??this.sender,lang:B,requestId:A.requestId,returnJson:1,...A.note!==void 0?{note:A.note}:{}}],D=await fetch("https://api.taqnyat.sa/verify.php/",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(I)}),J=await D.text(),N;try{N=JSON.parse(J)}catch{throw new K("Taqnyat Verify returned non-JSON response",D.status,J)}let H=U(N),F=R(H);if(!D.ok||F!==5)throw new K(Q(H)??`Taqnyat OTP send failed (code ${F??"unknown"})`,F??D.status,H);return{requestId:A.requestId,to:A.to,code:F,response:J,provider:"taqnyat-sms"}}async verifyOtp(A){let B=A.lang??"ar",G=O(A.to),I=[{apiKey:this.bearerToken,numbers:[G],method:"sms",sender:A.from??this.sender,lang:B,requestId:A.requestId,activeKey:A.code,returnJson:1,...A.note!==void 0?{note:A.note}:{}}],D=await fetch("https://api.taqnyat.sa/verify.php/",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(I)}),J=await D.text(),N;try{N=JSON.parse(J)}catch{throw new K("Taqnyat Verify returned non-JSON response",D.status,J)}let H=U(N),F=R(H),Y=new Set([10,13,19]);if(!D.ok||F===void 0||!Y.has(F))throw new K(Q(H)??`Taqnyat OTP verify failed (code ${F??"unknown"})`,F??D.status,H);return{ok:!0,code:F,message:Q(H)??"Activation process completed successfully",response:J,provider:"taqnyat-sms"}}async verify(){return{ok:Boolean(this.bearerToken&&this.sender),provider:"taqnyat-sms",message:this.bearerToken&&this.sender?"Credentials present":"Missing credentials"}}}export{Z as TaqnyatSmsTransport,K as TaqnyatSmsError};
2
2
 
3
- //# debugId=18DB31EDCA321AE964756E2164756E21
3
+ //# debugId=8ED03B5D386F317B64756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "/**\n * @module\n * Taqnyat SMS API transport (Saudi Arabia).\n *\n * Request/response shapes follow\n * [Taqnyat SMS](https://dev.taqnyat.sa/en/doc/sms/) and\n * [Verify](https://dev.taqnyat.sa/en/doc/verify/):\n * - SMS: `POST /v1/messages` with Bearer + JSON (`recipients`, `body`, `sender`)\n * - OTP: `POST /verify.php/` with JSON array (`apiKey`, `numbers`, `method`, …)\n *\n * Sently-first: wire SMS into {@link createSmsSender}; OTP helpers stay on this class.\n *\n * @example\n * ```ts\n * import { createSmsSender } from \"sently/sms\";\n * import { TaqnyatSmsTransport } from \"sently/transports/taqnyat-sms\";\n *\n * const taqnyat = new TaqnyatSmsTransport({\n * bearerToken: process.env.TAQNYAT_TOKEN!,\n * sender: \"MyBrand\",\n * });\n * const sms = createSmsSender({ transport: taqnyat });\n *\n * await sms.send({ to: \"+9665xxxxxxxx\", body: \"Hello\" });\n * await taqnyat.sendOtp({ to: \"+9665xxxxxxxx\", requestId: \"login-1\", lang: \"en\" });\n * await taqnyat.verifyOtp({\n * to: \"+9665xxxxxxxx\",\n * requestId: \"login-1\",\n * code: \"6240\",\n * lang: \"en\",\n * });\n * ```\n */\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type { SmsOptions, SmsSendResult, SmsTransport } from \"../core/sms-types.js\";\nimport type { VerifyResult } from \"../core/types.js\";\nimport { normalizeTaqnyatPhone } from \"./taqnyat-phone.js\";\n\n/** Taqnyat SMS API configuration. */\nexport interface TaqnyatSmsConfig {\n /** Bearer token from the Taqnyat dashboard. */\n bearerToken: string;\n /** Pre-approved sender name. */\n sender: string;\n}\n\n/** Options for {@link TaqnyatSmsTransport.sendOtp}. */\nexport interface TaqnyatSendOtpOptions {\n /** Recipient phone number (E.164 or international digits). */\n to: string;\n /** Unique id for this verification flow (required again on {@link verifyOtp}). */\n requestId: string;\n /** Message language (`en` or `ar`). Defaults to `\"ar\"` per Taqnyat docs. */\n lang?: \"en\" | \"ar\";\n /** Optional note appended to the OTP SMS. */\n note?: string;\n /** Sender ID override; defaults to transport `sender`. */\n from?: string;\n}\n\n/** Result of a successful {@link TaqnyatSmsTransport.sendOtp} call. */\nexport interface TaqnyatOtpSendResult {\n /** Echo of {@link TaqnyatSendOtpOptions.requestId}. */\n requestId: string;\n /** Recipient as passed in. */\n to: string;\n /** Provider status code (`5` = code sent). */\n code: number;\n /** Raw response body text. */\n response: string;\n /** Provider identifier. */\n provider: \"taqnyat-sms\";\n}\n\n/** Options for {@link TaqnyatSmsTransport.verifyOtp}. */\nexport interface TaqnyatVerifyOtpOptions {\n /** Recipient phone number (same as send). */\n to: string;\n /** Same {@link TaqnyatSendOtpOptions.requestId} used when sending. */\n requestId: string;\n /** OTP code the user entered (`activeKey` in Taqnyat docs). */\n code: string;\n /** Message language (`en` or `ar`). Defaults to `\"ar\"`. */\n lang?: \"en\" | \"ar\";\n /** Sender ID override; defaults to transport `sender`. */\n from?: string;\n /** Optional note (documented on check OTP). */\n note?: string;\n}\n\n/** Result of a successful {@link TaqnyatSmsTransport.verifyOtp} call. */\nexport interface TaqnyatOtpVerifyResult {\n /** Always `true` when the call resolves (failures throw). */\n ok: true;\n /** Provider status code (`10` = completed, `13`/`19` = already verified). */\n code: number;\n /** Provider message when present. */\n message: string;\n /** Raw response body text. */\n response: string;\n /** Provider identifier. */\n provider: \"taqnyat-sms\";\n}\n\n/** Error thrown when the Taqnyat SMS / Verify API returns a non-success response. */\nexport class TaqnyatSmsError extends SentlyError {\n /** Creates a Taqnyat SMS API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(\n message,\n httpStatusToSentlyCode(statusCode >= 100 && statusCode < 600 ? statusCode : 400),\n {\n statusCode,\n provider: \"taqnyat-sms\",\n cause: apiError,\n },\n );\n this.name = \"TaqnyatSmsError\";\n }\n}\n\nfunction verifyPayloadCode(payload: Record<string, unknown>): number | undefined {\n const raw = payload.code ?? payload.statusCode ?? payload.status;\n if (typeof raw === \"number\") return raw;\n if (typeof raw === \"string\" && /^-?\\d+$/.test(raw)) return Number(raw);\n return undefined;\n}\n\nfunction verifyPayloadMessage(payload: Record<string, unknown>): string | undefined {\n if (typeof payload.message === \"string\") return payload.message;\n if (typeof payload.MessageEn === \"string\") return payload.MessageEn;\n return undefined;\n}\n\nfunction unwrapVerifyPayload(payload: unknown): Record<string, unknown> {\n if (Array.isArray(payload)) {\n const first = payload[0];\n return first && typeof first === \"object\" ? (first as Record<string, unknown>) : {};\n }\n if (payload && typeof payload === \"object\") {\n return payload as Record<string, unknown>;\n }\n return {};\n}\n\n/**\n * Taqnyat SMS transport via JSON Bearer API, plus Verify OTP helpers.\n */\nexport class TaqnyatSmsTransport implements SmsTransport {\n readonly provider = \"taqnyat-sms\";\n\n private readonly bearerToken: string;\n private readonly sender: string;\n\n /** Creates a Taqnyat SMS transport. */\n constructor(config: TaqnyatSmsConfig) {\n this.bearerToken = config.bearerToken;\n this.sender = config.sender;\n }\n\n /**\n * Sends an SMS via `POST https://api.taqnyat.sa/v1/messages`\n * (Bearer + JSON: `recipients`, `body`, `sender`; optional `smsId`).\n */\n async send(options: SmsOptions): Promise<SmsSendResult> {\n const recipients = [normalizeTaqnyatPhone(options.to)];\n const body: Record<string, unknown> = {\n recipients,\n body: options.body,\n sender: options.from ?? this.sender,\n };\n // Official PHP SDK maps a client id to `smsId`.\n if (options.messageId !== undefined) {\n body.smsId = options.messageId;\n }\n\n const response = await fetch(\"https://api.taqnyat.sa/v1/messages\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.bearerToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n statusCode?: number;\n messageId?: number;\n cost?: number;\n currency?: string;\n message?: string;\n accepted?: string;\n rejected?: string;\n };\n\n // Docs: successful send returns HTTP 201 with statusCode 201.\n if (response.status !== 201) {\n throw new TaqnyatSmsError(\n payload.message ?? \"Taqnyat SMS API error\",\n payload.statusCode ?? response.status,\n payload,\n );\n }\n\n return {\n messageId: String(payload.messageId ?? options.messageId ?? \"\"),\n to: options.to,\n status: \"accepted\",\n response: `cost: ${payload.cost ?? \"?\"} ${payload.currency ?? \"SAR\"}`,\n provider: \"taqnyat-sms\",\n };\n }\n\n /**\n * Vendor extra: generate/send OTP via `POST /verify.php/` (success code `5`).\n * Not part of {@link SmsTransport} — call on {@link TaqnyatSmsTransport} directly.\n */\n async sendOtp(options: TaqnyatSendOtpOptions): Promise<TaqnyatOtpSendResult> {\n const lang = options.lang ?? \"ar\";\n const number = normalizeTaqnyatPhone(options.to);\n const body = [\n {\n apiKey: this.bearerToken,\n numbers: [number],\n method: \"sms\",\n sender: options.from ?? this.sender,\n lang,\n requestId: options.requestId,\n returnJson: 1,\n ...(options.note !== undefined ? { note: options.note } : {}),\n },\n ];\n\n const response = await fetch(\"https://api.taqnyat.sa/verify.php/\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.bearerToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new TaqnyatSmsError(\"Taqnyat Verify returned non-JSON response\", response.status, text);\n }\n\n const result = unwrapVerifyPayload(parsed);\n const code = verifyPayloadCode(result);\n\n // Docs: 5 = Activation code sent successfully.\n if (!response.ok || code !== 5) {\n throw new TaqnyatSmsError(\n verifyPayloadMessage(result) ?? `Taqnyat OTP send failed (code ${code ?? \"unknown\"})`,\n code ?? response.status,\n result,\n );\n }\n\n return {\n requestId: options.requestId,\n to: options.to,\n code,\n response: text,\n provider: \"taqnyat-sms\",\n };\n }\n\n /**\n * Vendor extra: check OTP via `POST /verify.php/` with `activeKey`\n * (`10` = completed; `13` / `19` = already verified).\n */\n async verifyOtp(options: TaqnyatVerifyOtpOptions): Promise<TaqnyatOtpVerifyResult> {\n const lang = options.lang ?? \"ar\";\n const number = normalizeTaqnyatPhone(options.to);\n const body = [\n {\n apiKey: this.bearerToken,\n numbers: [number],\n method: \"sms\",\n sender: options.from ?? this.sender,\n lang,\n requestId: options.requestId,\n activeKey: options.code,\n returnJson: 1,\n ...(options.note !== undefined ? { note: options.note } : {}),\n },\n ];\n\n const response = await fetch(\"https://api.taqnyat.sa/verify.php/\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.bearerToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new TaqnyatSmsError(\"Taqnyat Verify returned non-JSON response\", response.status, text);\n }\n\n const result = unwrapVerifyPayload(parsed);\n const code = verifyPayloadCode(result);\n const okCodes = new Set([10, 13, 19]);\n\n if (!response.ok || code === undefined || !okCodes.has(code)) {\n throw new TaqnyatSmsError(\n verifyPayloadMessage(result) ?? `Taqnyat OTP verify failed (code ${code ?? \"unknown\"})`,\n code ?? response.status,\n result,\n );\n }\n\n return {\n ok: true,\n code,\n message: verifyPayloadMessage(result) ?? \"Activation process completed successfully\",\n response: text,\n provider: \"taqnyat-sms\",\n };\n }\n\n /** Lightweight credential check. */\n async verify(): Promise<VerifyResult> {\n return {\n ok: Boolean(this.bearerToken && this.sender),\n provider: \"taqnyat-sms\",\n message: this.bearerToken && this.sender ? \"Credentials present\" : \"Missing credentials\",\n };\n }\n}\n"
6
6
  ],
7
7
  "mappings": "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",
8
- "debugId": "18DB31EDCA321AE964756E2164756E21",
8
+ "debugId": "8ED03B5D386F317B64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,3 +1,3 @@
1
- import{h as O}from"../chunk-7gy2q9sh.js";import{ca as K,da as N}from"../chunk-tamww15j.js";import"../chunk-6npp3x3c.js";class Q extends K{statusCode;apiError;constructor(B,D,G){super(B,N(D>=100&&D<600?D:400),{statusCode:D,provider:"taqnyat-whatsapp",cause:G});this.statusCode=D;this.apiError=G;this.name="TaqnyatWhatsAppError"}}function V(B){return"template"in B}function X(B){let D=B.statuses;if(Array.isArray(D)){let G=D[0];if(typeof G?.message_id==="string")return G.message_id}else if(D&&typeof D==="object"){for(let G of Object.values(D))if(G&&typeof G==="object"){let H=G.message_id;if(typeof H==="string")return H}}return}class Y{provider="taqnyat-whatsapp";bearerToken;constructor(B){this.bearerToken=B.bearerToken}async send(B){let D=O(B.to),G=V(B)?{to:D,type:"template",template:{name:B.template.name,language:{code:B.template.language},...B.template.components?{components:B.template.components}:{}}}:{to:D,type:"text",text:{body:B.text}},H=await fetch("https://api.taqnyat.sa/wa/v2/messages/",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(G)}),F=await H.json(),J=X(F);if(!H.ok||F.message!==void 0&&J===void 0){let R=typeof F.reason==="string"?F.reason:typeof F.message==="string"?`Taqnyat WhatsApp error ${F.message}`:"Taqnyat WhatsApp API error",U=typeof F.message==="string"&&/^\d+$/.test(F.message)?Number(F.message):H.status;throw new Q(R,U||H.status,F)}return{messageId:J??B.messageId??"",to:B.to,status:"accepted",response:typeof F.type==="string"?F.type:"Message sent",provider:"taqnyat-whatsapp"}}async verify(){return{ok:Boolean(this.bearerToken),provider:"taqnyat-whatsapp",message:this.bearerToken?"Credentials present":"Missing credentials"}}}export{Y as TaqnyatWhatsAppTransport,Q as TaqnyatWhatsAppError};
1
+ import{j as O}from"../chunk-5es0v1j7.js";import{ja as K,ka as N}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";class Q extends K{statusCode;apiError;constructor(B,D,G){super(B,N(D>=100&&D<600?D:400),{statusCode:D,provider:"taqnyat-whatsapp",cause:G});this.statusCode=D;this.apiError=G;this.name="TaqnyatWhatsAppError"}}function V(B){return"template"in B}function X(B){let D=B.statuses;if(Array.isArray(D)){let G=D[0];if(typeof G?.message_id==="string")return G.message_id}else if(D&&typeof D==="object"){for(let G of Object.values(D))if(G&&typeof G==="object"){let H=G.message_id;if(typeof H==="string")return H}}return}class Y{provider="taqnyat-whatsapp";bearerToken;constructor(B){this.bearerToken=B.bearerToken}async send(B){let D=O(B.to),G=V(B)?{to:D,type:"template",template:{name:B.template.name,language:{code:B.template.language},...B.template.components?{components:B.template.components}:{}}}:{to:D,type:"text",text:{body:B.text}},H=await fetch("https://api.taqnyat.sa/wa/v2/messages/",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(G)}),F=await H.json(),J=X(F);if(!H.ok||F.message!==void 0&&J===void 0){let R=typeof F.reason==="string"?F.reason:typeof F.message==="string"?`Taqnyat WhatsApp error ${F.message}`:"Taqnyat WhatsApp API error",U=typeof F.message==="string"&&/^\d+$/.test(F.message)?Number(F.message):H.status;throw new Q(R,U||H.status,F)}return{messageId:J??B.messageId??"",to:B.to,status:"accepted",response:typeof F.type==="string"?F.type:"Message sent",provider:"taqnyat-whatsapp"}}async verify(){return{ok:Boolean(this.bearerToken),provider:"taqnyat-whatsapp",message:this.bearerToken?"Credentials present":"Missing credentials"}}}export{Y as TaqnyatWhatsAppTransport,Q as TaqnyatWhatsAppError};
2
2
 
3
- //# debugId=5657D7720E9F16CB64756E2164756E21
3
+ //# debugId=E4D853E15FDA606B64756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "/**\n * @module\n * Taqnyat WhatsApp Business API transport.\n *\n * Follows [Taqnyat WhatsApp](https://dev.taqnyat.sa/en/doc/whatsapp/):\n * `POST https://api.taqnyat.sa/wa/v2/messages/` with Bearer + JSON.\n * Template: `{ to, type: \"template\", template: { name, language: { code } } }`.\n * Session text: `{ to, type: \"text\", text: { body } }`.\n *\n * Sently-first: wire into {@link createWhatsAppSender}.\n *\n * @example\n * ```ts\n * import { createWhatsAppSender } from \"sently/whatsapp\";\n * import { TaqnyatWhatsAppTransport } from \"sently/transports/taqnyat-whatsapp\";\n *\n * const wa = createWhatsAppSender({\n * transport: new TaqnyatWhatsAppTransport({\n * bearerToken: process.env.TAQNYAT_WHATSAPP_TOKEN!,\n * }),\n * });\n * ```\n */\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type { VerifyResult } from \"../core/types.js\";\nimport type {\n WhatsAppOptions,\n WhatsAppSendResult,\n WhatsAppTransport,\n} from \"../core/whatsapp-types.js\";\nimport { normalizeTaqnyatPhone } from \"./taqnyat-phone.js\";\n\n/** Taqnyat WhatsApp API configuration. */\nexport interface TaqnyatWhatsAppConfig {\n /** Bearer token from a Taqnyat application enabled for WhatsApp. */\n bearerToken: string;\n}\n\n/** Error thrown when the Taqnyat WhatsApp API reports a failure. */\nexport class TaqnyatWhatsAppError extends SentlyError {\n /** Creates a Taqnyat WhatsApp API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(\n message,\n httpStatusToSentlyCode(statusCode >= 100 && statusCode < 600 ? statusCode : 400),\n {\n statusCode,\n provider: \"taqnyat-whatsapp\",\n cause: apiError,\n },\n );\n this.name = \"TaqnyatWhatsAppError\";\n }\n}\n\nfunction isTemplateMessage(\n options: WhatsAppOptions,\n): options is Extract<WhatsAppOptions, { template: unknown }> {\n return \"template\" in options;\n}\n\nfunction extractMessageId(payload: Record<string, unknown>): string | undefined {\n const statuses = payload.statuses;\n if (Array.isArray(statuses)) {\n const first = statuses[0] as Record<string, unknown> | undefined;\n if (typeof first?.message_id === \"string\") return first.message_id;\n } else if (statuses && typeof statuses === \"object\") {\n for (const value of Object.values(statuses as Record<string, unknown>)) {\n if (value && typeof value === \"object\") {\n const id = (value as Record<string, unknown>).message_id;\n if (typeof id === \"string\") return id;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Taqnyat WhatsApp transport via JSON Bearer `wa/v2/messages/`.\n */\nexport class TaqnyatWhatsAppTransport implements WhatsAppTransport {\n readonly provider = \"taqnyat-whatsapp\";\n\n private readonly bearerToken: string;\n\n /** Creates a Taqnyat WhatsApp transport. */\n constructor(config: TaqnyatWhatsAppConfig) {\n this.bearerToken = config.bearerToken;\n }\n\n /** Sends a WhatsApp template or session text message. */\n async send(options: WhatsAppOptions): Promise<WhatsAppSendResult> {\n const to = normalizeTaqnyatPhone(options.to);\n const body = isTemplateMessage(options)\n ? {\n to,\n type: \"template\" as const,\n template: {\n name: options.template.name,\n language: { code: options.template.language },\n ...(options.template.components ? { components: options.template.components } : {}),\n },\n }\n : {\n to,\n type: \"text\" as const,\n text: { body: options.text },\n };\n\n // Docs: base URL must end with `/`.\n const response = await fetch(\"https://api.taqnyat.sa/wa/v2/messages/\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.bearerToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as Record<string, unknown>;\n const messageId = extractMessageId(payload);\n\n // Docs return `{ message, reason }` on failure (often still HTTP 200).\n if (!response.ok || (payload.message !== undefined && messageId === undefined)) {\n const reason =\n typeof payload.reason === \"string\"\n ? payload.reason\n : typeof payload.message === \"string\"\n ? `Taqnyat WhatsApp error ${payload.message}`\n : \"Taqnyat WhatsApp API error\";\n const status =\n typeof payload.message === \"string\" && /^\\d+$/.test(payload.message)\n ? Number(payload.message)\n : response.status;\n throw new TaqnyatWhatsAppError(reason, status || response.status, payload);\n }\n\n return {\n messageId: messageId ?? options.messageId ?? \"\",\n to: options.to,\n status: \"accepted\",\n response: typeof payload.type === \"string\" ? payload.type : \"Message sent\",\n provider: \"taqnyat-whatsapp\",\n };\n }\n\n /** Lightweight credential check. */\n async verify(): Promise<VerifyResult> {\n return {\n ok: Boolean(this.bearerToken),\n provider: \"taqnyat-whatsapp\",\n message: this.bearerToken ? \"Credentials present\" : \"Missing credentials\",\n };\n }\n}\n"
6
6
  ],
7
7
  "mappings": "kIAuCO,CAAM,KAA6B,CAAY,CAIlC,WACA,SAHlB,WAAW,CACT,EACgB,EACA,EAChB,CACA,MACE,EACA,EAAuB,GAAc,KAAO,EAAa,IAAM,EAAa,GAAG,EAC/E,CACE,aACA,SAAU,mBACV,MAAO,CACT,CACF,EAXgB,kBACA,gBAWhB,KAAK,KAAO,uBAEhB,CAEA,SAAS,CAAiB,CACxB,EAC4D,CAC5D,MAAO,aAAc,EAGvB,SAAS,CAAgB,CAAC,EAAsD,CAC9E,IAAM,EAAW,EAAQ,SACzB,GAAI,MAAM,QAAQ,CAAQ,EAAG,CAC3B,IAAM,EAAQ,EAAS,GACvB,GAAI,OAAO,GAAO,aAAe,SAAU,OAAO,EAAM,WACnD,QAAI,GAAY,OAAO,IAAa,UACzC,QAAW,KAAS,OAAO,OAAO,CAAmC,EACnE,GAAI,GAAS,OAAO,IAAU,SAAU,CACtC,IAAM,EAAM,EAAkC,WAC9C,GAAI,OAAO,IAAO,SAAU,OAAO,GAIzC,OAMK,MAAM,CAAsD,CACxD,SAAW,mBAEH,YAGjB,WAAW,CAAC,EAA+B,CACzC,KAAK,YAAc,EAAO,iBAItB,KAAI,CAAC,EAAuD,CAChE,IAAM,EAAK,EAAsB,EAAQ,EAAE,EACrC,EAAO,EAAkB,CAAO,EAClC,CACE,KACA,KAAM,WACN,SAAU,CACR,KAAM,EAAQ,SAAS,KACvB,SAAU,CAAE,KAAM,EAAQ,SAAS,QAAS,KACxC,EAAQ,SAAS,WAAa,CAAE,WAAY,EAAQ,SAAS,UAAW,EAAI,CAAC,CACnF,CACF,EACA,CACE,KACA,KAAM,OACN,KAAM,CAAE,KAAM,EAAQ,IAAK,CAC7B,EAGE,EAAW,MAAM,MAAM,yCAA0C,CACrE,OAAQ,OACR,QAAS,CACP,cAAe,UAAU,KAAK,cAC9B,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CAAI,CAC3B,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAC/B,EAAY,EAAiB,CAAO,EAG1C,GAAI,CAAC,EAAS,IAAO,EAAQ,UAAY,QAAa,IAAc,OAAY,CAC9E,IAAM,EACJ,OAAO,EAAQ,SAAW,SACtB,EAAQ,OACR,OAAO,EAAQ,UAAY,SACzB,0BAA0B,EAAQ,UAClC,6BACF,EACJ,OAAO,EAAQ,UAAY,UAAY,QAAQ,KAAK,EAAQ,OAAO,EAC/D,OAAO,EAAQ,OAAO,EACtB,EAAS,OACf,MAAM,IAAI,EAAqB,EAAQ,GAAU,EAAS,OAAQ,CAAO,EAG3E,MAAO,CACL,UAAW,GAAa,EAAQ,WAAa,GAC7C,GAAI,EAAQ,GACZ,OAAQ,WACR,SAAU,OAAO,EAAQ,OAAS,SAAW,EAAQ,KAAO,eAC5D,SAAU,kBACZ,OAII,OAAM,EAA0B,CACpC,MAAO,CACL,GAAI,QAAQ,KAAK,WAAW,EAC5B,SAAU,mBACV,QAAS,KAAK,YAAc,sBAAwB,qBACtD,EAEJ",
8
- "debugId": "5657D7720E9F16CB64756E2164756E21",
8
+ "debugId": "E4D853E15FDA606B64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,3 +1,3 @@
1
- import{Y as J}from"../chunk-ywq6s10g.js";import{ca as K,da as L}from"../chunk-tamww15j.js";import"../chunk-6npp3x3c.js";class H extends K{statusCode;apiError;constructor(k,q,x){super(k,L(q),{statusCode:q,provider:"twilio-sms",cause:x});this.statusCode=q;this.apiError=x;this.name="TwilioSmsError"}}class N{provider="twilio-sms";accountSid;authToken;apiKey;from;messagingServiceSid;statusCallback;constructor(k){if(this.accountSid=k.accountSid,this.authToken=k.authToken,k.apiKey!==void 0)this.apiKey=k.apiKey;if(k.from!==void 0)this.from=k.from;if(k.messagingServiceSid!==void 0)this.messagingServiceSid=k.messagingServiceSid;if(k.statusCallback!==void 0)this.statusCallback=k.statusCallback}async send(k){let q=k.from??this.from;if(!q&&!this.messagingServiceSid)throw new H("Twilio SMS requires From or MessagingServiceSid",400,{message:"Missing from / messagingServiceSid"});let x=new URLSearchParams({To:k.to,Body:k.body});if(q)x.set("From",q);if(this.messagingServiceSid)x.set("MessagingServiceSid",this.messagingServiceSid);if(this.statusCallback)x.set("StatusCallback",this.statusCallback);let I=`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`,z=await fetch(I,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:x}),D=await this.readJson(z);if(!z.ok){let M=typeof D==="object"&&D!==null&&"message"in D&&typeof D.message==="string"?D.message:"Twilio SMS API error";throw new H(M,z.status,D)}let F=D;return{messageId:F.sid??k.messageId??"",to:k.to,status:F.status??"queued",response:F.status??"queued",provider:"twilio-sms"}}async verify(){if(!this.accountSid||!this.authToken)return{ok:!1,provider:"twilio-sms",message:"Missing credentials"};try{let k=`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}.json`,q=await fetch(k,{method:"GET",headers:{...this.authHeaders(),Accept:"application/json"}});if(!q.ok){let z=await this.readJson(q);return{ok:!1,provider:"twilio-sms",message:typeof z==="object"&&z!==null&&"message"in z&&typeof z.message==="string"?z.message:`HTTP ${q.status}`}}let x=await this.readJson(q);return{ok:!0,provider:"twilio-sms",message:`Account ${x.friendly_name??x.status??"ok"}`}}catch(k){return{ok:!1,provider:"twilio-sms",message:k instanceof Error?k.message:String(k)}}}authHeaders(){let k=this.apiKey??this.accountSid;return{Authorization:`Basic ${J(`${k}:${this.authToken}`).replace(/\r\n/g,"")}`}}async readJson(k){return k.json().catch(()=>({}))}}export{N as TwilioSmsTransport,H as TwilioSmsError};
1
+ import{da as J}from"../chunk-hnzmn4s4.js";import{ja as K,ka as L}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";class H extends K{statusCode;apiError;constructor(k,q,x){super(k,L(q),{statusCode:q,provider:"twilio-sms",cause:x});this.statusCode=q;this.apiError=x;this.name="TwilioSmsError"}}class N{provider="twilio-sms";accountSid;authToken;apiKey;from;messagingServiceSid;statusCallback;constructor(k){if(this.accountSid=k.accountSid,this.authToken=k.authToken,k.apiKey!==void 0)this.apiKey=k.apiKey;if(k.from!==void 0)this.from=k.from;if(k.messagingServiceSid!==void 0)this.messagingServiceSid=k.messagingServiceSid;if(k.statusCallback!==void 0)this.statusCallback=k.statusCallback}async send(k){let q=k.from??this.from;if(!q&&!this.messagingServiceSid)throw new H("Twilio SMS requires From or MessagingServiceSid",400,{message:"Missing from / messagingServiceSid"});let x=new URLSearchParams({To:k.to,Body:k.body});if(q)x.set("From",q);if(this.messagingServiceSid)x.set("MessagingServiceSid",this.messagingServiceSid);if(this.statusCallback)x.set("StatusCallback",this.statusCallback);let I=`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`,z=await fetch(I,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:x}),D=await this.readJson(z);if(!z.ok){let M=typeof D==="object"&&D!==null&&"message"in D&&typeof D.message==="string"?D.message:"Twilio SMS API error";throw new H(M,z.status,D)}let F=D;return{messageId:F.sid??k.messageId??"",to:k.to,status:F.status??"queued",response:F.status??"queued",provider:"twilio-sms"}}async verify(){if(!this.accountSid||!this.authToken)return{ok:!1,provider:"twilio-sms",message:"Missing credentials"};try{let k=`https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}.json`,q=await fetch(k,{method:"GET",headers:{...this.authHeaders(),Accept:"application/json"}});if(!q.ok){let z=await this.readJson(q);return{ok:!1,provider:"twilio-sms",message:typeof z==="object"&&z!==null&&"message"in z&&typeof z.message==="string"?z.message:`HTTP ${q.status}`}}let x=await this.readJson(q);return{ok:!0,provider:"twilio-sms",message:`Account ${x.friendly_name??x.status??"ok"}`}}catch(k){return{ok:!1,provider:"twilio-sms",message:k instanceof Error?k.message:String(k)}}}authHeaders(){let k=this.apiKey??this.accountSid;return{Authorization:`Basic ${J(`${k}:${this.authToken}`).replace(/\r\n/g,"")}`}}async readJson(k){return k.json().catch(()=>({}))}}export{N as TwilioSmsTransport,H as TwilioSmsError};
2
2
 
3
- //# debugId=54563D7C95D0712D64756E2164756E21
3
+ //# debugId=68862E4E64033D4164756E2164756E21
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * @module\n * Twilio Messages API transport for SMS.\n *\n * Aligned with Twilio Programmable Messaging\n * ([Message resource](https://www.twilio.com/docs/messaging/api/message-resource)):\n * `POST /2010-04-01/Accounts/{AccountSid}/Messages.json` with\n * `application/x-www-form-urlencoded` and HTTP Basic auth.\n *\n * Sender must be {@link TwilioSmsConfig.from} / `options.from` **or**\n * {@link TwilioSmsConfig.messagingServiceSid} (Twilio selects from the Sender Pool\n * when `From` is omitted).\n *\n * Sently-first: wire into {@link createSmsSender}. Twilio-only products (Verify,\n * Voice, Conversations, etc.) stay off this transport unless added as concrete\n * helpers later.\n *\n * Production auth: set `apiKey` + `authToken` (API Key Secret) with `accountSid`\n * still required in the URL path. Account SID + Auth Token alone is fine for\n * local testing — never hardcode credentials in source.\n *\n * @example\n * ```ts\n * import { createSmsSender } from \"sently/sms\";\n * import { TwilioSmsTransport } from \"sently/transports/twilio-sms\";\n *\n * const sms = createSmsSender({\n * transport: new TwilioSmsTransport({\n * accountSid: process.env.TWILIO_ACCOUNT_SID!,\n * apiKey: process.env.TWILIO_API_KEY!,\n * authToken: process.env.TWILIO_API_KEY_SECRET!,\n * from: \"+15557654321\",\n * }),\n * });\n * ```\n */\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type { SmsOptions, SmsSendResult, SmsTransport } from \"../core/sms-types.js\";\nimport type { VerifyResult } from \"../core/types.js\";\n\n/** Twilio Messages API configuration. */\nexport interface TwilioSmsConfig {\n /**\n * Twilio Account SID — always used in the Messages API URL path.\n * Store in environment variables — never hardcode.\n */\n accountSid: string;\n /**\n * Basic-auth password: API Key Secret when {@link apiKey} is set, otherwise Auth Token.\n * Store in environment variables — never hardcode.\n */\n authToken: string;\n /**\n * Optional Twilio API Key SID used as Basic-auth username (recommended in production).\n * When omitted, {@link accountSid} is used as the Basic-auth username.\n */\n apiKey?: string;\n /**\n * Default From phone number, alphanumeric sender ID, short code, or channel address\n * (E.164 recommended, e.g. `+15557654321`). Either this / `options.from` or\n * {@link messagingServiceSid} is required.\n */\n from?: string;\n /**\n * Messaging Service SID (`MG…`). When set and `From` is omitted, Twilio selects\n * the optimal sender from the service's Sender Pool.\n */\n messagingServiceSid?: string;\n /**\n * Optional status callback URL (`StatusCallback`). Twilio POSTs delivery updates here.\n */\n statusCallback?: string;\n}\n\n/** Error thrown when the Twilio SMS API returns a non-success response. */\nexport class TwilioSmsError extends SentlyError {\n /** Creates a Twilio SMS API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"twilio-sms\",\n cause: apiError,\n });\n this.name = \"TwilioSmsError\";\n }\n}\n\n/**\n * Twilio SMS transport via the Messages REST API (form-urlencoded).\n */\nexport class TwilioSmsTransport implements SmsTransport {\n readonly provider = \"twilio-sms\";\n\n private readonly accountSid: string;\n private readonly authToken: string;\n private readonly apiKey?: string;\n private readonly from?: string;\n private readonly messagingServiceSid?: string;\n private readonly statusCallback?: string;\n\n /** Creates a Twilio SMS transport. */\n constructor(config: TwilioSmsConfig) {\n this.accountSid = config.accountSid;\n this.authToken = config.authToken;\n if (config.apiKey !== undefined) {\n this.apiKey = config.apiKey;\n }\n if (config.from !== undefined) {\n this.from = config.from;\n }\n if (config.messagingServiceSid !== undefined) {\n this.messagingServiceSid = config.messagingServiceSid;\n }\n if (config.statusCallback !== undefined) {\n this.statusCallback = config.statusCallback;\n }\n }\n\n /** Sends an SMS via Twilio Messages API. */\n async send(options: SmsOptions): Promise<SmsSendResult> {\n const from = options.from ?? this.from;\n if (!from && !this.messagingServiceSid) {\n throw new TwilioSmsError(\"Twilio SMS requires From or MessagingServiceSid\", 400, {\n message: \"Missing from / messagingServiceSid\",\n });\n }\n\n // Keep E.164 `+` — Twilio expects it (unlike Taqnyat/Msegat).\n const body = new URLSearchParams({\n To: options.to,\n Body: options.body,\n });\n if (from) {\n body.set(\"From\", from);\n }\n if (this.messagingServiceSid) {\n body.set(\"MessagingServiceSid\", this.messagingServiceSid);\n }\n if (this.statusCallback) {\n body.set(\"StatusCallback\", this.statusCallback);\n }\n\n const url = `https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}/Messages.json`;\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n ...this.authHeaders(),\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body,\n });\n\n const payload = await this.readJson(response);\n\n if (!response.ok) {\n const message =\n typeof payload === \"object\" &&\n payload !== null &&\n \"message\" in payload &&\n typeof (payload as { message: unknown }).message === \"string\"\n ? (payload as { message: string }).message\n : \"Twilio SMS API error\";\n throw new TwilioSmsError(message, response.status, payload);\n }\n\n const record = payload as {\n sid?: string;\n status?: string;\n };\n\n return {\n messageId: record.sid ?? options.messageId ?? \"\",\n to: options.to,\n status: record.status ?? \"queued\",\n response: record.status ?? \"queued\",\n provider: \"twilio-sms\",\n };\n }\n\n /**\n * Validates credentials via Twilio Account fetch\n * (`GET /2010-04-01/Accounts/{AccountSid}.json`).\n */\n async verify(): Promise<VerifyResult> {\n if (!this.accountSid || !this.authToken) {\n return { ok: false, provider: \"twilio-sms\", message: \"Missing credentials\" };\n }\n\n try {\n const url = `https://api.twilio.com/2010-04-01/Accounts/${this.accountSid}.json`;\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n ...this.authHeaders(),\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n const payload = await this.readJson(response);\n const message =\n typeof payload === \"object\" &&\n payload !== null &&\n \"message\" in payload &&\n typeof (payload as { message: unknown }).message === \"string\"\n ? (payload as { message: string }).message\n : `HTTP ${response.status}`;\n return { ok: false, provider: \"twilio-sms\", message };\n }\n\n const payload = (await this.readJson(response)) as {\n friendly_name?: string;\n status?: string;\n };\n const label = payload.friendly_name ?? payload.status ?? \"ok\";\n return { ok: true, provider: \"twilio-sms\", message: `Account ${label}` };\n } catch (error) {\n return {\n ok: false,\n provider: \"twilio-sms\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n private authHeaders(): { Authorization: string } {\n const authUser = this.apiKey ?? this.accountSid;\n const credentials = encodeBase64(`${authUser}:${this.authToken}`).replace(/\\r\\n/g, \"\");\n return { Authorization: `Basic ${credentials}` };\n }\n\n private async readJson(response: Response): Promise<unknown> {\n return response.json().catch(() => ({}));\n }\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "54563D7C95D0712D64756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "68862E4E64033D4164756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @module
3
+ * Unifonic SMS REST transport (el.cloud).
4
+ *
5
+ * Aligned with Unifonic Send Message
6
+ * (`POST https://el.cloud.unifonic.com/rest/SMS/messages`):
7
+ * JSON body with `AppSid`, `SenderID`, `Recipient`, `Body`.
8
+ *
9
+ * Sently-first: wire into {@link createSmsSender}.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { createSmsSender } from "sently/sms";
14
+ * import { UnifonicTransport } from "sently/transports/unifonic";
15
+ *
16
+ * const sms = createSmsSender({
17
+ * transport: new UnifonicTransport({
18
+ * appSid: process.env.UNIFONIC_APPSID!,
19
+ * senderId: "MyBrand",
20
+ * }),
21
+ * });
22
+ *
23
+ * await sms.send({ to: "+9665xxxxxxxx", body: "Hello" });
24
+ * ```
25
+ */
26
+ import { SentlyError } from "../core/errors.js";
27
+ import type { SmsOptions, SmsSendResult, SmsTransport } from "../core/sms-types.js";
28
+ import type { VerifyResult } from "../core/types.js";
29
+ /** Unifonic SMS API configuration. */
30
+ export interface UnifonicConfig {
31
+ /**
32
+ * Application SID (`AppSid`) from the Unifonic dashboard.
33
+ * Store in environment variables — never hardcode.
34
+ */
35
+ appSid: string;
36
+ /**
37
+ * Default SenderID (brand / approved sender). Required unless every send
38
+ * passes `options.from` (mapped to `SenderID`).
39
+ */
40
+ senderId?: string;
41
+ /**
42
+ * Optional delivery status callback URL forwarded as `statusCallback`.
43
+ * Unifonic requires a public HTTPS URL (el.cloud OpenAPI).
44
+ */
45
+ statusCallback?: string;
46
+ /**
47
+ * When true, request async execution (`async: true`). Default: false.
48
+ */
49
+ async?: boolean;
50
+ }
51
+ /** Error thrown when the Unifonic SMS API returns a non-success response. */
52
+ export declare class UnifonicError extends SentlyError {
53
+ readonly statusCode: number;
54
+ readonly apiError: unknown;
55
+ /** Creates a Unifonic SMS API error with status code and response payload. */
56
+ constructor(message: string, statusCode: number, apiError: unknown);
57
+ }
58
+ /**
59
+ * Normalize a phone number for Unifonic: international digits without `+` or
60
+ * leading `00`.
61
+ */
62
+ export declare function normalizeUnifonicPhone(to: string): string;
63
+ /**
64
+ * Unifonic SMS transport via el.cloud REST (`AppSid` + JSON).
65
+ */
66
+ export declare class UnifonicTransport implements SmsTransport {
67
+ readonly provider = "unifonic";
68
+ private readonly appSid;
69
+ private readonly senderId;
70
+ private readonly statusCallback;
71
+ private readonly async;
72
+ /** Creates an Unifonic SMS transport. */
73
+ constructor(config: UnifonicConfig);
74
+ /**
75
+ * Sends an SMS via `POST https://el.cloud.unifonic.com/rest/SMS/messages`
76
+ * (JSON: `AppSid`, `SenderID`, `Recipient`, `Body`; optional `CorrelationID`).
77
+ */
78
+ send(options: SmsOptions): Promise<SmsSendResult>;
79
+ /** Lightweight credential check. */
80
+ verify(): Promise<VerifyResult>;
81
+ }
@@ -0,0 +1,3 @@
1
+ import{ja as V,ka as W}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";var Z="https://el.cloud.unifonic.com/rest/SMS/messages";class G extends V{statusCode;apiError;constructor(q,v,K){super(q,W(v>=100&&v<600?v:400),{statusCode:v,provider:"unifonic",cause:K});this.statusCode=v;this.apiError=K;this.name="UnifonicError"}}function $(q){let v=q.trim();if(v.startsWith("+"))return v.slice(1).replace(/\D/g,"");if(v.startsWith("00"))return v.slice(2).replace(/\D/g,"");return v.replace(/\D/g,"")}function j(q){let v;try{v=new URL(q)}catch{throw new G("Unifonic statusCallback must be a valid HTTPS URL",400,{field:"statusCallback",value:q})}if(v.protocol!=="https:")throw new G("Unifonic statusCallback must use https:",400,{field:"statusCallback",protocol:v.protocol});if(v.username||v.password)throw new G("Unifonic statusCallback must not include credentials",400,{field:"statusCallback"})}class w{provider="unifonic";appSid;senderId;statusCallback;async;constructor(q){if(this.appSid=q.appSid,this.senderId=q.senderId,q.statusCallback!==void 0)j(q.statusCallback),this.statusCallback=q.statusCallback;else this.statusCallback=void 0;this.async=q.async??!1}async send(q){let v=q.from??this.senderId;if(v===void 0||v.length===0)throw new G("Unifonic requires SenderID via config.senderId or options.from",400,{field:"SenderID"});let K={AppSid:this.appSid,SenderID:v,Recipient:$(q.to),Body:q.body,responseType:"JSON",async:this.async};if(q.messageId!==void 0)K.CorrelationID=q.messageId;if(this.statusCallback!==void 0)K.statusCallback=this.statusCallback;let J=await fetch(Z,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(K)}),M=await J.text(),A;try{A=M.length>0?JSON.parse(M):{}}catch{throw new G("Unifonic returned non-JSON response",J.status,M)}let X=A.success===!0||A.success==="true",Y=A.errorCode==="ER-00"||A.errorCode==="ER00";if(!(J.ok&&(X||Y)))throw new G(A.message||`Unifonic SMS API error (${A.errorCode??J.status})`,J.status>=400?J.status:400,A);let B=A.data??{},Q=typeof B.Status==="string"?B.Status:"accepted";return{messageId:String(B.MessageID??q.messageId??B.CorrelationID??""),to:q.to,status:Q.toLowerCase()==="sent"?"accepted":Q,response:B.Cost!==void 0?`cost: ${B.Cost} ${B.CurrencyCode??""}`.trim():A.message??Q,provider:"unifonic"}}async verify(){let q=Boolean(this.appSid);return{ok:q,provider:"unifonic",message:q?"Credentials present":"Missing appSid"}}}export{$ as normalizeUnifonicPhone,w as UnifonicTransport,G as UnifonicError};
2
+
3
+ //# debugId=EDBC65716C96690264756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/transports/unifonic.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * @module\n * Unifonic SMS REST transport (el.cloud).\n *\n * Aligned with Unifonic Send Message\n * (`POST https://el.cloud.unifonic.com/rest/SMS/messages`):\n * JSON body with `AppSid`, `SenderID`, `Recipient`, `Body`.\n *\n * Sently-first: wire into {@link createSmsSender}.\n *\n * @example\n * ```ts\n * import { createSmsSender } from \"sently/sms\";\n * import { UnifonicTransport } from \"sently/transports/unifonic\";\n *\n * const sms = createSmsSender({\n * transport: new UnifonicTransport({\n * appSid: process.env.UNIFONIC_APPSID!,\n * senderId: \"MyBrand\",\n * }),\n * });\n *\n * await sms.send({ to: \"+9665xxxxxxxx\", body: \"Hello\" });\n * ```\n */\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type { SmsOptions, SmsSendResult, SmsTransport } from \"../core/sms-types.js\";\nimport type { VerifyResult } from \"../core/types.js\";\n\nconst UNIFONIC_SEND_URL = \"https://el.cloud.unifonic.com/rest/SMS/messages\";\n\n/** Unifonic SMS API configuration. */\nexport interface UnifonicConfig {\n /**\n * Application SID (`AppSid`) from the Unifonic dashboard.\n * Store in environment variables — never hardcode.\n */\n appSid: string;\n /**\n * Default SenderID (brand / approved sender). Required unless every send\n * passes `options.from` (mapped to `SenderID`).\n */\n senderId?: string;\n /**\n * Optional delivery status callback URL forwarded as `statusCallback`.\n * Unifonic requires a public HTTPS URL (el.cloud OpenAPI).\n */\n statusCallback?: string;\n /**\n * When true, request async execution (`async: true`). Default: false.\n */\n async?: boolean;\n}\n\n/** Error thrown when the Unifonic SMS API returns a non-success response. */\nexport class UnifonicError extends SentlyError {\n /** Creates a Unifonic SMS API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(\n message,\n httpStatusToSentlyCode(statusCode >= 100 && statusCode < 600 ? statusCode : 400),\n {\n statusCode,\n provider: \"unifonic\",\n cause: apiError,\n },\n );\n this.name = \"UnifonicError\";\n }\n}\n\n/**\n * Normalize a phone number for Unifonic: international digits without `+` or\n * leading `00`.\n */\nexport function normalizeUnifonicPhone(to: string): string {\n const trimmed = to.trim();\n if (trimmed.startsWith(\"+\")) {\n return trimmed.slice(1).replace(/\\D/g, \"\");\n }\n if (trimmed.startsWith(\"00\")) {\n return trimmed.slice(2).replace(/\\D/g, \"\");\n }\n return trimmed.replace(/\\D/g, \"\");\n}\n\n/** Unifonic OpenAPI: statusCallback must be a public HTTPS URL. */\nfunction assertHttpsStatusCallback(url: string): void {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new UnifonicError(\"Unifonic statusCallback must be a valid HTTPS URL\", 400, {\n field: \"statusCallback\",\n value: url,\n });\n }\n if (parsed.protocol !== \"https:\") {\n throw new UnifonicError(\"Unifonic statusCallback must use https:\", 400, {\n field: \"statusCallback\",\n protocol: parsed.protocol,\n });\n }\n if (parsed.username || parsed.password) {\n throw new UnifonicError(\"Unifonic statusCallback must not include credentials\", 400, {\n field: \"statusCallback\",\n });\n }\n}\n\n/**\n * Unifonic SMS transport via el.cloud REST (`AppSid` + JSON).\n */\nexport class UnifonicTransport implements SmsTransport {\n readonly provider = \"unifonic\";\n\n private readonly appSid: string;\n private readonly senderId: string | undefined;\n private readonly statusCallback: string | undefined;\n private readonly async: boolean;\n\n /** Creates an Unifonic SMS transport. */\n constructor(config: UnifonicConfig) {\n this.appSid = config.appSid;\n this.senderId = config.senderId;\n if (config.statusCallback !== undefined) {\n assertHttpsStatusCallback(config.statusCallback);\n this.statusCallback = config.statusCallback;\n } else {\n this.statusCallback = undefined;\n }\n this.async = config.async ?? false;\n }\n\n /**\n * Sends an SMS via `POST https://el.cloud.unifonic.com/rest/SMS/messages`\n * (JSON: `AppSid`, `SenderID`, `Recipient`, `Body`; optional `CorrelationID`).\n */\n async send(options: SmsOptions): Promise<SmsSendResult> {\n const senderId = options.from ?? this.senderId;\n if (senderId === undefined || senderId.length === 0) {\n throw new UnifonicError(\n \"Unifonic requires SenderID via config.senderId or options.from\",\n 400,\n { field: \"SenderID\" },\n );\n }\n\n const body: Record<string, unknown> = {\n AppSid: this.appSid,\n SenderID: senderId,\n Recipient: normalizeUnifonicPhone(options.to),\n Body: options.body,\n responseType: \"JSON\",\n async: this.async,\n };\n\n if (options.messageId !== undefined) {\n body.CorrelationID = options.messageId;\n }\n if (this.statusCallback !== undefined) {\n body.statusCallback = this.statusCallback;\n }\n\n const response = await fetch(UNIFONIC_SEND_URL, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const text = await response.text();\n let payload: {\n /** OpenAPI examples use both boolean `true` and string `\"true\"`. */\n success?: boolean | string;\n message?: string;\n errorCode?: string;\n data?: {\n MessageID?: number | string;\n CorrelationID?: string;\n Status?: string;\n Cost?: number;\n Balance?: number;\n Recipient?: string;\n CurrencyCode?: string;\n };\n };\n try {\n payload = text.length > 0 ? (JSON.parse(text) as typeof payload) : {};\n } catch {\n throw new UnifonicError(\"Unifonic returned non-JSON response\", response.status, text);\n }\n\n // OpenAPI examples use both boolean `true` and string `\"true\"` for success.\n const successFlag = payload.success === true || payload.success === \"true\";\n const successCode = payload.errorCode === \"ER-00\" || payload.errorCode === \"ER00\";\n const success = response.ok && (successFlag || successCode);\n\n if (!success) {\n throw new UnifonicError(\n payload.message || `Unifonic SMS API error (${payload.errorCode ?? response.status})`,\n response.status >= 400 ? response.status : 400,\n payload,\n );\n }\n\n const data = payload.data ?? {};\n const status = typeof data.Status === \"string\" ? data.Status : \"accepted\";\n\n return {\n messageId: String(data.MessageID ?? options.messageId ?? data.CorrelationID ?? \"\"),\n to: options.to,\n status: status.toLowerCase() === \"sent\" ? \"accepted\" : status,\n response:\n data.Cost !== undefined\n ? `cost: ${data.Cost} ${data.CurrencyCode ?? \"\"}`.trim()\n : (payload.message ?? status),\n provider: \"unifonic\",\n };\n }\n\n /** Lightweight credential check. */\n async verify(): Promise<VerifyResult> {\n const ok = Boolean(this.appSid);\n return {\n ok,\n provider: \"unifonic\",\n message: ok ? \"Credentials present\" : \"Missing appSid\",\n };\n }\n}\n"
6
+ ],
7
+ "mappings": "oFA6BA,DAAM,EAAoB,kDA0BnB,MAAM,UAAsB,CAAY,CAI3B,WACA,SAHlB,WAAW,CACT,EACgB,EACA,EAChB,CACA,MACE,EACA,EAAuB,GAAc,KAAO,EAAa,IAAM,EAAa,GAAG,EAC/E,CACE,aACA,SAAU,WACV,MAAO,CACT,CACF,EAXgB,kBACA,gBAWhB,KAAK,KAAO,gBAEhB,CAMO,SAAS,CAAsB,CAAC,EAAoB,CACzD,IAAM,EAAU,EAAG,KAAK,EACxB,GAAI,EAAQ,WAAW,GAAG,EACxB,OAAO,EAAQ,MAAM,CAAC,EAAE,QAAQ,MAAO,EAAE,EAE3C,GAAI,EAAQ,WAAW,IAAI,EACzB,OAAO,EAAQ,MAAM,CAAC,EAAE,QAAQ,MAAO,EAAE,EAE3C,OAAO,EAAQ,QAAQ,MAAO,EAAE,EAIlC,SAAS,CAAyB,CAAC,EAAmB,CACpD,IAAI,EACJ,GAAI,CACF,EAAS,IAAI,IAAI,CAAG,EACpB,KAAM,CACN,MAAM,IAAI,EAAc,oDAAqD,IAAK,CAChF,MAAO,iBACP,MAAO,CACT,CAAC,EAEH,GAAI,EAAO,WAAa,SACtB,MAAM,IAAI,EAAc,0CAA2C,IAAK,CACtE,MAAO,iBACP,SAAU,EAAO,QACnB,CAAC,EAEH,GAAI,EAAO,UAAY,EAAO,SAC5B,MAAM,IAAI,EAAc,uDAAwD,IAAK,CACnF,MAAO,gBACT,CAAC,EAOE,MAAM,CAA0C,CAC5C,SAAW,WAEH,OACA,SACA,eACA,MAGjB,WAAW,CAAC,EAAwB,CAGlC,GAFA,KAAK,OAAS,EAAO,OACrB,KAAK,SAAW,EAAO,SACnB,EAAO,iBAAmB,OAC5B,EAA0B,EAAO,cAAc,EAC/C,KAAK,eAAiB,EAAO,eAE7B,UAAK,eAAiB,OAExB,KAAK,MAAQ,EAAO,OAAS,QAOzB,KAAI,CAAC,EAA6C,CACtD,IAAM,EAAW,EAAQ,MAAQ,KAAK,SACtC,GAAI,IAAa,QAAa,EAAS,SAAW,EAChD,MAAM,IAAI,EACR,iEACA,IACA,CAAE,MAAO,UAAW,CACtB,EAGF,IAAM,EAAgC,CACpC,OAAQ,KAAK,OACb,SAAU,EACV,UAAW,EAAuB,EAAQ,EAAE,EAC5C,KAAM,EAAQ,KACd,aAAc,OACd,MAAO,KAAK,KACd,EAEA,GAAI,EAAQ,YAAc,OACxB,EAAK,cAAgB,EAAQ,UAE/B,GAAI,KAAK,iBAAmB,OAC1B,EAAK,eAAiB,KAAK,eAG7B,IAAM,EAAW,MAAM,MAAM,EAAmB,CAC9C,OAAQ,OACR,QAAS,CACP,OAAQ,mBACR,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CAAI,CAC3B,CAAC,EAEK,EAAO,MAAM,EAAS,KAAK,EAC7B,EAeJ,GAAI,CACF,EAAU,EAAK,OAAS,EAAK,KAAK,MAAM,CAAI,EAAuB,CAAC,EACpE,KAAM,CACN,MAAM,IAAI,EAAc,sCAAuC,EAAS,OAAQ,CAAI,EAItF,IAAM,EAAc,EAAQ,UAAY,IAAQ,EAAQ,UAAY,OAC9D,EAAc,EAAQ,YAAc,SAAW,EAAQ,YAAc,OAG3E,GAAI,EAFY,EAAS,KAAO,GAAe,IAG7C,MAAM,IAAI,EACR,EAAQ,SAAW,2BAA2B,EAAQ,WAAa,EAAS,UAC5E,EAAS,QAAU,IAAM,EAAS,OAAS,IAC3C,CACF,EAGF,IAAM,EAAO,EAAQ,MAAQ,CAAC,EACxB,EAAS,OAAO,EAAK,SAAW,SAAW,EAAK,OAAS,WAE/D,MAAO,CACL,UAAW,OAAO,EAAK,WAAa,EAAQ,WAAa,EAAK,eAAiB,EAAE,EACjF,GAAI,EAAQ,GACZ,OAAQ,EAAO,YAAY,IAAM,OAAS,WAAa,EACvD,SACE,EAAK,OAAS,OACV,SAAS,EAAK,QAAQ,EAAK,cAAgB,KAAK,KAAK,EACpD,EAAQ,SAAW,EAC1B,SAAU,UACZ,OAII,OAAM,EAA0B,CACpC,IAAM,EAAK,QAAQ,KAAK,MAAM,EAC9B,MAAO,CACL,KACA,SAAU,WACV,QAAS,EAAK,sBAAwB,gBACxC,EAEJ",
8
+ "debugId": "EDBC65716C96690264756E2164756E21",
9
+ "names": []
10
+ }
@@ -1,3 +1,3 @@
1
- import{U as T}from"../chunk-yp311efr.js";import{$ as N,aa as H,ba as J}from"../chunk-ywq6s10g.js";import{ca as m,da as v}from"../chunk-tamww15j.js";import"../chunk-6npp3x3c.js";class V extends m{statusCode;apiError;constructor(q,z,Q){super(q,v(z),{statusCode:z,provider:"webpush",cause:Q});this.statusCode=z;this.apiError=Q;this.name="WebPushError"}}var k=2419200,U=4096,C=4096;function X(q){return q.buffer.slice(q.byteOffset,q.byteOffset+q.byteLength)}function j(...q){let z=q.reduce((G,Z)=>G+Z.length,0),Q=new Uint8Array(z),$=0;for(let G of q)Q.set(G,$),$+=G.length;return Q}function g(q){let z=new Uint8Array(4);return new DataView(z.buffer).setUint32(0,q,!1),z}function K(q){if(q.length!==65||q[0]!==4)throw new V("Invalid P-256 uncompressed public key",400,{length:q.length});return{kty:"EC",crv:"P-256",x:N(q.subarray(1,33)),y:N(q.subarray(33,65))}}function E(q,z){return{...K(q),d:N(z)}}async function _(q,z){let Q=await crypto.subtle.importKey("raw",X(q),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),$=await crypto.subtle.sign("HMAC",Q,X(z));return new Uint8Array($)}async function w(q,z,Q,$){let G=await _(q,z);return(await _(G,j(Q,new Uint8Array([1])))).subarray(0,$)}async function B(q,z){let Q=H(q.keys.p256dh),$=H(q.keys.auth);if(Q.length!==65||Q[0]!==4)throw new V("Invalid subscription p256dh key",400,{length:Q.length});if($.length<16)throw new V("Invalid subscription auth key",400,{length:$.length});let G=await crypto.subtle.generateKey({name:"ECDH",namedCurve:"P-256"},!0,["deriveBits"]),Z=new Uint8Array(await crypto.subtle.exportKey("raw",G.publicKey)),F=await crypto.subtle.importKey("raw",X(Q),{name:"ECDH",namedCurve:"P-256"},!1,[]),M=new Uint8Array(await crypto.subtle.deriveBits({name:"ECDH",public:F},G.privateKey,256)),L=j(J("WebPush: info\x00"),Q,Z),x=await w($,M,L,32),O=crypto.getRandomValues(new Uint8Array(16)),I=J("Content-Encoding: aes128gcm\x00"),R=J("Content-Encoding: nonce\x00"),Y=await w(O,x,I,16),D=await w(O,x,R,12),A=j(z,new Uint8Array([2])),W=await crypto.subtle.importKey("raw",X(Y),"AES-GCM",!1,["encrypt"]),S=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:X(D)},W,X(A)));return j(O,g(U),new Uint8Array([Z.length]),Z,S)}async function P(q,z,Q,$){let G=H(Q),Z=H($),F=E(G,Z),M=await crypto.subtle.importKey("jwk",F,{name:"ECDSA",namedCurve:"P-256"},!1,["sign"]),L=new URL(q).origin,x={typ:"JWT",alg:"ES256"},O={aud:L,exp:Math.floor(Date.now()/1000)+43200,sub:z},I=N(J(JSON.stringify(x))),R=N(J(JSON.stringify(O))),Y=`${I}.${R}`,D=new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},M,X(J(Y))));return`${Y}.${N(D)}`}class h{provider="webpush";vapidPublicKey;vapidPrivateKey;subject;allowedEndpointHosts;constructor(q){this.vapidPublicKey=q.vapidPublicKey,this.vapidPrivateKey=q.vapidPrivateKey,this.subject=q.subject,this.allowedEndpointHosts=q.allowedEndpointHosts??[]}async send(q){try{T(q.subscription.endpoint,this.allowedEndpointHosts)}catch(M){let L;try{L=new URL(q.subscription.endpoint).hostname}catch{L=void 0}throw new V(M instanceof Error?M.message:"Unsafe push subscription endpoint",400,{endpointHost:L})}let z={title:q.title,body:q.body,...q.data!==void 0?{data:q.data}:{},...q.icon!==void 0?{icon:q.icon}:{}},Q=J(JSON.stringify(z)),$=await B(q.subscription,Q);if($.length>C)throw new V(`Encrypted push payload exceeds ${C} bytes (RFC 8291)`,413,{size:$.length});let G=await P(q.subscription.endpoint,this.subject,this.vapidPublicKey,this.vapidPrivateKey),Z=q.ttl??k,F=await fetch(q.subscription.endpoint,{method:"POST",redirect:"manual",headers:{Authorization:`vapid t=${G}, k=${this.vapidPublicKey}`,"Content-Encoding":"aes128gcm",TTL:String(Z),"Content-Type":"application/octet-stream"},body:X($)});if(F.status>=300&&F.status<400)throw new V(`Push service returned redirect ${F.status}; redirects are not followed`,F.status,{location:F.headers.get("location")});if(!F.ok){let M=await F.text().catch(()=>"");throw new V(M||`Web Push request failed with status ${F.status}`,F.status,M||{status:F.status})}return{messageId:q.messageId??crypto.randomUUID(),status:"accepted",response:String(F.status),provider:"webpush"}}async verify(){try{let q=H(this.vapidPublicKey),z=H(this.vapidPrivateKey),Q=q.length===65&&z.length===32&&this.subject.length>0;return{ok:Q,provider:"webpush",message:Q?"VAPID credentials present":"Invalid VAPID key lengths"}}catch(q){return{ok:!1,provider:"webpush",message:q instanceof Error?q.message:"Invalid VAPID credentials"}}}}export{h as WebPushTransport,V as WebPushError};
1
+ import{W as T}from"../chunk-fv0bemgf.js";import{_ as A}from"../chunk-z1589fjk.js";import{ga as N,ha as H,ia as J}from"../chunk-hnzmn4s4.js";import{ja as m,ka as v}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";class V extends m{statusCode;apiError;constructor(q,z,Q){super(q,v(z),{statusCode:z,provider:"webpush",cause:Q});this.statusCode=z;this.apiError=Q;this.name="WebPushError"}}var U=2419200,g=4096,C=4096;function X(q){return q.buffer.slice(q.byteOffset,q.byteOffset+q.byteLength)}function I(...q){let z=q.reduce((G,Z)=>G+Z.length,0),Q=new Uint8Array(z),$=0;for(let G of q)Q.set(G,$),$+=G.length;return Q}function K(q){let z=new Uint8Array(4);return new DataView(z.buffer).setUint32(0,q,!1),z}function B(q){if(q.length!==65||q[0]!==4)throw new V("Invalid P-256 uncompressed public key",400,{length:q.length});return{kty:"EC",crv:"P-256",x:N(q.subarray(1,33)),y:N(q.subarray(33,65))}}function E(q,z){return{...B(q),d:N(z)}}async function _(q,z){let Q=await crypto.subtle.importKey("raw",X(q),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),$=await crypto.subtle.sign("HMAC",Q,X(z));return new Uint8Array($)}async function w(q,z,Q,$){let G=await _(q,z);return(await _(G,I(Q,new Uint8Array([1])))).subarray(0,$)}async function P(q,z){let Q=H(q.keys.p256dh),$=H(q.keys.auth);if(Q.length!==65||Q[0]!==4)throw new V("Invalid subscription p256dh key",400,{length:Q.length});if($.length<16)throw new V("Invalid subscription auth key",400,{length:$.length});let G=await crypto.subtle.generateKey({name:"ECDH",namedCurve:"P-256"},!0,["deriveBits"]),Z=new Uint8Array(await crypto.subtle.exportKey("raw",G.publicKey)),F=await crypto.subtle.importKey("raw",X(Q),{name:"ECDH",namedCurve:"P-256"},!1,[]),M=new Uint8Array(await crypto.subtle.deriveBits({name:"ECDH",public:F},G.privateKey,256)),L=I(J("WebPush: info\x00"),Q,Z),x=await w($,M,L,32),O=crypto.getRandomValues(new Uint8Array(16)),R=J("Content-Encoding: aes128gcm\x00"),j=J("Content-Encoding: nonce\x00"),Y=await w(O,x,R,16),D=await w(O,x,j,12),S=I(z,new Uint8Array([2])),W=await crypto.subtle.importKey("raw",X(Y),"AES-GCM",!1,["encrypt"]),k=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:X(D)},W,X(S)));return I(O,K(g),new Uint8Array([Z.length]),Z,k)}async function h(q,z,Q,$){let G=H(Q),Z=H($),F=E(G,Z),M=await crypto.subtle.importKey("jwk",F,{name:"ECDSA",namedCurve:"P-256"},!1,["sign"]),L=new URL(q).origin,x={typ:"JWT",alg:"ES256"},O={aud:L,exp:Math.floor(Date.now()/1000)+43200,sub:z},R=N(J(JSON.stringify(x))),j=N(J(JSON.stringify(O))),Y=`${R}.${j}`,D=new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},M,X(J(Y))));return`${Y}.${N(D)}`}class f{provider="webpush";vapidPublicKey;vapidPrivateKey;subject;allowedEndpointHosts;constructor(q){this.vapidPublicKey=q.vapidPublicKey,this.vapidPrivateKey=q.vapidPrivateKey,this.subject=q.subject,this.allowedEndpointHosts=q.allowedEndpointHosts??[]}async send(q){if(!A(q))throw new V("WebPushTransport requires PushOptions.subscription; use FcmTransport for device tokens",400,{hint:"fcm"});try{T(q.subscription.endpoint,this.allowedEndpointHosts)}catch(M){let L;try{L=new URL(q.subscription.endpoint).hostname}catch{L=void 0}throw new V(M instanceof Error?M.message:"Unsafe push subscription endpoint",400,{endpointHost:L})}let z={title:q.title,body:q.body,...q.data!==void 0?{data:q.data}:{},...q.icon!==void 0?{icon:q.icon}:{}},Q=J(JSON.stringify(z)),$=await P(q.subscription,Q);if($.length>C)throw new V(`Encrypted push payload exceeds ${C} bytes (RFC 8291)`,413,{size:$.length});let G=await h(q.subscription.endpoint,this.subject,this.vapidPublicKey,this.vapidPrivateKey),Z=q.ttl??U,F=await fetch(q.subscription.endpoint,{method:"POST",redirect:"manual",headers:{Authorization:`vapid t=${G}, k=${this.vapidPublicKey}`,"Content-Encoding":"aes128gcm",TTL:String(Z),"Content-Type":"application/octet-stream"},body:X($)});if(F.status>=300&&F.status<400)throw new V(`Push service returned redirect ${F.status}; redirects are not followed`,F.status,{location:F.headers.get("location")});if(!F.ok){let M=await F.text().catch(()=>"");throw new V(M||`Web Push request failed with status ${F.status}`,F.status,M||{status:F.status})}return{messageId:q.messageId??crypto.randomUUID(),status:"accepted",response:String(F.status),provider:"webpush"}}async verify(){try{let q=H(this.vapidPublicKey),z=H(this.vapidPrivateKey),Q=q.length===65&&z.length===32&&this.subject.length>0;return{ok:Q,provider:"webpush",message:Q?"VAPID credentials present":"Invalid VAPID key lengths"}}catch(q){return{ok:!1,provider:"webpush",message:q instanceof Error?q.message:"Invalid VAPID credentials"}}}}export{f as WebPushTransport,V as WebPushError};
2
2
 
3
- //# debugId=43FC53C9208BBE5564756E2164756E21
3
+ //# debugId=DFA1A46FC61CEB2264756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/transports/webpush.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * @module\n * Web Push transport (RFC 8292 VAPID + RFC 8291 aes128gcm).\n *\n * VAPID keys are the common web-push raw format: base64url-encoded\n * uncompressed P-256 public key (65 bytes) and raw private key (32 bytes).\n *\n * Store `vapidPrivateKey` in a secrets manager / environment variable — never\n * hardcode it or commit it to source control.\n *\n * @example\n * ```ts\n * import { createPushSender } from \"sently/push\";\n * import { WebPushTransport } from \"sently/transports/webpush\";\n *\n * const push = createPushSender({\n * transport: new WebPushTransport({\n * vapidPublicKey: process.env.VAPID_PUBLIC_KEY!,\n * vapidPrivateKey: process.env.VAPID_PRIVATE_KEY!,\n * subject: \"mailto:you@example.com\",\n * }),\n * });\n * ```\n */\nimport { decodeBase64Url, encodeBase64Url, encodeUtf8 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport { assertSafePushEndpoint } from \"../core/push-endpoint.js\";\nimport type {\n PushOptions,\n PushSendResult,\n PushSubscription,\n PushTransport,\n} from \"../core/push-types.js\";\nimport type { VerifyResult } from \"../core/types.js\";\n\n/** Web Push / VAPID configuration. */\nexport interface WebPushConfig {\n /** Base64url-encoded uncompressed P-256 public key (65 bytes). */\n vapidPublicKey: string;\n /**\n * Base64url-encoded raw P-256 private key (32 bytes).\n * Treat as a tier-1 secret — inject from env / secrets manager only.\n */\n vapidPrivateKey: string;\n /** Contact URI for the VAPID `sub` claim (`mailto:` or `https:`). */\n subject: string;\n /**\n * Extra exact hostnames allowed for `subscription.endpoint` beyond the\n * built-in FCM / Mozilla / Apple / WNS allowlist. Use for private push relays.\n */\n allowedEndpointHosts?: string[];\n}\n\n/** Error thrown when a push service rejects the request. */\nexport class WebPushError extends SentlyError {\n /** Creates a Web Push error with HTTP status and response body. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"webpush\",\n cause: apiError,\n });\n this.name = \"WebPushError\";\n }\n}\n\n/** Default TTL (28 days) in seconds. */\nconst DEFAULT_TTL_SECONDS = 2419200;\n/** RFC 8188 / Web Push record size. */\nconst RECORD_SIZE = 4096;\n/**\n * Maximum encrypted body size push services are required to accept (RFC 8291).\n * Includes salt, rs, keyid, and ciphertext+tag.\n */\nconst MAX_ENCRYPTED_BODY_BYTES = 4096;\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nfunction concatBytes(...parts: Uint8Array[]): Uint8Array {\n const total = parts.reduce((sum, part) => sum + part.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.length;\n }\n return out;\n}\n\nfunction uint32Be(value: number): Uint8Array {\n const out = new Uint8Array(4);\n new DataView(out.buffer).setUint32(0, value, false);\n return out;\n}\n\nfunction ecPublicToJwk(publicKey: Uint8Array): JsonWebKey {\n if (publicKey.length !== 65 || publicKey[0] !== 0x04) {\n throw new WebPushError(\"Invalid P-256 uncompressed public key\", 400, {\n length: publicKey.length,\n });\n }\n return {\n kty: \"EC\",\n crv: \"P-256\",\n x: encodeBase64Url(publicKey.subarray(1, 33)),\n y: encodeBase64Url(publicKey.subarray(33, 65)),\n };\n}\n\nfunction ecKeyPairToJwk(publicKey: Uint8Array, privateKey: Uint8Array): JsonWebKey {\n return {\n ...ecPublicToJwk(publicKey),\n d: encodeBase64Url(privateKey),\n };\n}\n\nasync function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n toArrayBuffer(key),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const sig = await crypto.subtle.sign(\"HMAC\", cryptoKey, toArrayBuffer(data));\n return new Uint8Array(sig);\n}\n\n/** HKDF-Extract + Expand (HMAC-SHA-256) for a single block (L ≤ 32). */\nasync function hkdf(\n salt: Uint8Array,\n ikm: Uint8Array,\n info: Uint8Array,\n length: number,\n): Promise<Uint8Array> {\n const prk = await hmacSha256(salt, ikm);\n const block = await hmacSha256(prk, concatBytes(info, new Uint8Array([0x01])));\n return block.subarray(0, length);\n}\n\n/**\n * Encrypt a payload per RFC 8291 (aes128gcm content coding).\n *\n * Confidence note: Implemented from RFC 8291 / RFC 8188. Tests assert header\n * shapes, framing, and size limits; full known-vector fixtures were not\n * available in-repo — treat as high-scrutiny crypto.\n */\nasync function encryptAes128Gcm(\n subscription: PushSubscription,\n plaintext: Uint8Array,\n): Promise<Uint8Array> {\n const uaPublic = decodeBase64Url(subscription.keys.p256dh);\n const authSecret = decodeBase64Url(subscription.keys.auth);\n\n if (uaPublic.length !== 65 || uaPublic[0] !== 0x04) {\n throw new WebPushError(\"Invalid subscription p256dh key\", 400, {\n length: uaPublic.length,\n });\n }\n if (authSecret.length < 16) {\n throw new WebPushError(\"Invalid subscription auth key\", 400, {\n length: authSecret.length,\n });\n }\n\n const asKeyPair = await crypto.subtle.generateKey({ name: \"ECDH\", namedCurve: \"P-256\" }, true, [\n \"deriveBits\",\n ]);\n const asPublicRaw = new Uint8Array(await crypto.subtle.exportKey(\"raw\", asKeyPair.publicKey));\n\n const uaKey = await crypto.subtle.importKey(\n \"raw\",\n toArrayBuffer(uaPublic),\n { name: \"ECDH\", namedCurve: \"P-256\" },\n false,\n [],\n );\n\n const ecdhSecret = new Uint8Array(\n await crypto.subtle.deriveBits({ name: \"ECDH\", public: uaKey }, asKeyPair.privateKey, 256),\n );\n\n const keyInfo = concatBytes(encodeUtf8(\"WebPush: info\\0\"), uaPublic, asPublicRaw);\n const ikm = await hkdf(authSecret, ecdhSecret, keyInfo, 32);\n\n const salt = crypto.getRandomValues(new Uint8Array(16));\n const cekInfo = encodeUtf8(\"Content-Encoding: aes128gcm\\0\");\n const nonceInfo = encodeUtf8(\"Content-Encoding: nonce\\0\");\n const cek = await hkdf(salt, ikm, cekInfo, 16);\n const nonce = await hkdf(salt, ikm, nonceInfo, 12);\n\n // RFC 8188 final-record padding delimiter\n const padded = concatBytes(plaintext, new Uint8Array([0x02]));\n\n const aesKey = await crypto.subtle.importKey(\"raw\", toArrayBuffer(cek), \"AES-GCM\", false, [\n \"encrypt\",\n ]);\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: toArrayBuffer(nonce) },\n aesKey,\n toArrayBuffer(padded),\n ),\n );\n\n // salt || rs || idlen || keyid || ciphertext\n return concatBytes(\n salt,\n uint32Be(RECORD_SIZE),\n new Uint8Array([asPublicRaw.length]),\n asPublicRaw,\n ciphertext,\n );\n}\n\nasync function signVapidJwt(\n endpoint: string,\n subject: string,\n vapidPublicKey: string,\n vapidPrivateKey: string,\n): Promise<string> {\n const publicKey = decodeBase64Url(vapidPublicKey);\n const privateKey = decodeBase64Url(vapidPrivateKey);\n const jwk = ecKeyPairToJwk(publicKey, privateKey);\n\n const key = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n false,\n [\"sign\"],\n );\n\n const origin = new URL(endpoint).origin;\n const header = { typ: \"JWT\", alg: \"ES256\" };\n const payload = {\n aud: origin,\n exp: Math.floor(Date.now() / 1000) + 12 * 60 * 60,\n sub: subject,\n };\n\n const encodedHeader = encodeBase64Url(encodeUtf8(JSON.stringify(header)));\n const encodedPayload = encodeBase64Url(encodeUtf8(JSON.stringify(payload)));\n const signingInput = `${encodedHeader}.${encodedPayload}`;\n\n const signature = new Uint8Array(\n await crypto.subtle.sign(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n key,\n toArrayBuffer(encodeUtf8(signingInput)),\n ),\n );\n\n return `${signingInput}.${encodeBase64Url(signature)}`;\n}\n\n/**\n * Web Push transport — VAPID auth + RFC 8291 payload encryption.\n *\n * Endpoint URLs are validated against an allowlist before fetch to mitigate SSRF.\n * Redirects are not followed.\n */\nexport class WebPushTransport implements PushTransport {\n readonly provider = \"webpush\";\n\n private readonly vapidPublicKey: string;\n private readonly vapidPrivateKey: string;\n private readonly subject: string;\n private readonly allowedEndpointHosts: readonly string[];\n\n /** Creates a Web Push transport with VAPID credentials. */\n constructor(config: WebPushConfig) {\n this.vapidPublicKey = config.vapidPublicKey;\n this.vapidPrivateKey = config.vapidPrivateKey;\n this.subject = config.subject;\n this.allowedEndpointHosts = config.allowedEndpointHosts ?? [];\n }\n\n /** Encrypts and POSTs a notification to the subscription endpoint. */\n async send(options: PushOptions): Promise<PushSendResult> {\n try {\n assertSafePushEndpoint(options.subscription.endpoint, this.allowedEndpointHosts);\n } catch (error) {\n let endpointHost: string | undefined;\n try {\n endpointHost = new URL(options.subscription.endpoint).hostname;\n } catch {\n endpointHost = undefined;\n }\n throw new WebPushError(\n error instanceof Error ? error.message : \"Unsafe push subscription endpoint\",\n 400,\n { endpointHost },\n );\n }\n\n const notification = {\n title: options.title,\n body: options.body,\n ...(options.data !== undefined ? { data: options.data } : {}),\n ...(options.icon !== undefined ? { icon: options.icon } : {}),\n };\n const plaintext = encodeUtf8(JSON.stringify(notification));\n const encrypted = await encryptAes128Gcm(options.subscription, plaintext);\n\n if (encrypted.length > MAX_ENCRYPTED_BODY_BYTES) {\n throw new WebPushError(\n `Encrypted push payload exceeds ${MAX_ENCRYPTED_BODY_BYTES} bytes (RFC 8291)`,\n 413,\n { size: encrypted.length },\n );\n }\n\n const jwt = await signVapidJwt(\n options.subscription.endpoint,\n this.subject,\n this.vapidPublicKey,\n this.vapidPrivateKey,\n );\n\n const ttl = options.ttl ?? DEFAULT_TTL_SECONDS;\n const response = await fetch(options.subscription.endpoint, {\n method: \"POST\",\n redirect: \"manual\",\n headers: {\n Authorization: `vapid t=${jwt}, k=${this.vapidPublicKey}`,\n \"Content-Encoding\": \"aes128gcm\",\n TTL: String(ttl),\n \"Content-Type\": \"application/octet-stream\",\n },\n body: toArrayBuffer(encrypted),\n });\n\n // Opaque redirect responses (3xx) must not be followed — SSRF mitigation.\n if (response.status >= 300 && response.status < 400) {\n throw new WebPushError(\n `Push service returned redirect ${response.status}; redirects are not followed`,\n response.status,\n { location: response.headers.get(\"location\") },\n );\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => \"\");\n throw new WebPushError(\n bodyText || `Web Push request failed with status ${response.status}`,\n response.status,\n bodyText || { status: response.status },\n );\n }\n\n return {\n messageId: options.messageId ?? crypto.randomUUID(),\n status: \"accepted\",\n response: String(response.status),\n provider: \"webpush\",\n };\n }\n\n /** Lightweight VAPID credential shape check. */\n async verify(): Promise<VerifyResult> {\n try {\n const pub = decodeBase64Url(this.vapidPublicKey);\n const priv = decodeBase64Url(this.vapidPrivateKey);\n const ok = pub.length === 65 && priv.length === 32 && this.subject.length > 0;\n return {\n ok,\n provider: \"webpush\",\n message: ok ? \"VAPID credentials present\" : \"Invalid VAPID key lengths\",\n };\n } catch (error) {\n return {\n ok: false,\n provider: \"webpush\",\n message: error instanceof Error ? error.message : \"Invalid VAPID credentials\",\n };\n }\n }\n}\n"
5
+ "/**\n * @module\n * Web Push transport (RFC 8292 VAPID + RFC 8291 aes128gcm).\n *\n * VAPID keys are the common web-push raw format: base64url-encoded\n * uncompressed P-256 public key (65 bytes) and raw private key (32 bytes).\n *\n * Store `vapidPrivateKey` in a secrets manager / environment variable — never\n * hardcode it or commit it to source control.\n *\n * @example\n * ```ts\n * import { createPushSender } from \"sently/push\";\n * import { WebPushTransport } from \"sently/transports/webpush\";\n *\n * const push = createPushSender({\n * transport: new WebPushTransport({\n * vapidPublicKey: process.env.VAPID_PUBLIC_KEY!,\n * vapidPrivateKey: process.env.VAPID_PRIVATE_KEY!,\n * subject: \"mailto:you@example.com\",\n * }),\n * });\n * ```\n */\nimport { decodeBase64Url, encodeBase64Url, encodeUtf8 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport { assertSafePushEndpoint } from \"../core/push-endpoint.js\";\nimport type {\n PushOptions,\n PushSendResult,\n PushSubscription,\n PushTransport,\n} from \"../core/push-types.js\";\nimport { isWebPushOptions } from \"../core/push-types.js\";\nimport type { VerifyResult } from \"../core/types.js\";\n\n/** Web Push / VAPID configuration. */\nexport interface WebPushConfig {\n /** Base64url-encoded uncompressed P-256 public key (65 bytes). */\n vapidPublicKey: string;\n /**\n * Base64url-encoded raw P-256 private key (32 bytes).\n * Treat as a tier-1 secret — inject from env / secrets manager only.\n */\n vapidPrivateKey: string;\n /** Contact URI for the VAPID `sub` claim (`mailto:` or `https:`). */\n subject: string;\n /**\n * Extra exact hostnames allowed for `subscription.endpoint` beyond the\n * built-in FCM / Mozilla / Apple / WNS allowlist. Use for private push relays.\n */\n allowedEndpointHosts?: string[];\n}\n\n/** Error thrown when a push service rejects the request. */\nexport class WebPushError extends SentlyError {\n /** Creates a Web Push error with HTTP status and response body. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"webpush\",\n cause: apiError,\n });\n this.name = \"WebPushError\";\n }\n}\n\n/** Default TTL (28 days) in seconds. */\nconst DEFAULT_TTL_SECONDS = 2419200;\n/** RFC 8188 / Web Push record size. */\nconst RECORD_SIZE = 4096;\n/**\n * Maximum encrypted body size push services are required to accept (RFC 8291).\n * Includes salt, rs, keyid, and ciphertext+tag.\n */\nconst MAX_ENCRYPTED_BODY_BYTES = 4096;\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nfunction concatBytes(...parts: Uint8Array[]): Uint8Array {\n const total = parts.reduce((sum, part) => sum + part.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.length;\n }\n return out;\n}\n\nfunction uint32Be(value: number): Uint8Array {\n const out = new Uint8Array(4);\n new DataView(out.buffer).setUint32(0, value, false);\n return out;\n}\n\nfunction ecPublicToJwk(publicKey: Uint8Array): JsonWebKey {\n if (publicKey.length !== 65 || publicKey[0] !== 0x04) {\n throw new WebPushError(\"Invalid P-256 uncompressed public key\", 400, {\n length: publicKey.length,\n });\n }\n return {\n kty: \"EC\",\n crv: \"P-256\",\n x: encodeBase64Url(publicKey.subarray(1, 33)),\n y: encodeBase64Url(publicKey.subarray(33, 65)),\n };\n}\n\nfunction ecKeyPairToJwk(publicKey: Uint8Array, privateKey: Uint8Array): JsonWebKey {\n return {\n ...ecPublicToJwk(publicKey),\n d: encodeBase64Url(privateKey),\n };\n}\n\nasync function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n toArrayBuffer(key),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const sig = await crypto.subtle.sign(\"HMAC\", cryptoKey, toArrayBuffer(data));\n return new Uint8Array(sig);\n}\n\n/** HKDF-Extract + Expand (HMAC-SHA-256) for a single block (L ≤ 32). */\nasync function hkdf(\n salt: Uint8Array,\n ikm: Uint8Array,\n info: Uint8Array,\n length: number,\n): Promise<Uint8Array> {\n const prk = await hmacSha256(salt, ikm);\n const block = await hmacSha256(prk, concatBytes(info, new Uint8Array([0x01])));\n return block.subarray(0, length);\n}\n\n/**\n * Encrypt a payload per RFC 8291 (aes128gcm content coding).\n *\n * Confidence note: Implemented from RFC 8291 / RFC 8188. Tests assert header\n * shapes, framing, and size limits; full known-vector fixtures were not\n * available in-repo — treat as high-scrutiny crypto.\n */\nasync function encryptAes128Gcm(\n subscription: PushSubscription,\n plaintext: Uint8Array,\n): Promise<Uint8Array> {\n const uaPublic = decodeBase64Url(subscription.keys.p256dh);\n const authSecret = decodeBase64Url(subscription.keys.auth);\n\n if (uaPublic.length !== 65 || uaPublic[0] !== 0x04) {\n throw new WebPushError(\"Invalid subscription p256dh key\", 400, {\n length: uaPublic.length,\n });\n }\n if (authSecret.length < 16) {\n throw new WebPushError(\"Invalid subscription auth key\", 400, {\n length: authSecret.length,\n });\n }\n\n const asKeyPair = await crypto.subtle.generateKey({ name: \"ECDH\", namedCurve: \"P-256\" }, true, [\n \"deriveBits\",\n ]);\n const asPublicRaw = new Uint8Array(await crypto.subtle.exportKey(\"raw\", asKeyPair.publicKey));\n\n const uaKey = await crypto.subtle.importKey(\n \"raw\",\n toArrayBuffer(uaPublic),\n { name: \"ECDH\", namedCurve: \"P-256\" },\n false,\n [],\n );\n\n const ecdhSecret = new Uint8Array(\n await crypto.subtle.deriveBits({ name: \"ECDH\", public: uaKey }, asKeyPair.privateKey, 256),\n );\n\n const keyInfo = concatBytes(encodeUtf8(\"WebPush: info\\0\"), uaPublic, asPublicRaw);\n const ikm = await hkdf(authSecret, ecdhSecret, keyInfo, 32);\n\n const salt = crypto.getRandomValues(new Uint8Array(16));\n const cekInfo = encodeUtf8(\"Content-Encoding: aes128gcm\\0\");\n const nonceInfo = encodeUtf8(\"Content-Encoding: nonce\\0\");\n const cek = await hkdf(salt, ikm, cekInfo, 16);\n const nonce = await hkdf(salt, ikm, nonceInfo, 12);\n\n // RFC 8188 final-record padding delimiter\n const padded = concatBytes(plaintext, new Uint8Array([0x02]));\n\n const aesKey = await crypto.subtle.importKey(\"raw\", toArrayBuffer(cek), \"AES-GCM\", false, [\n \"encrypt\",\n ]);\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: toArrayBuffer(nonce) },\n aesKey,\n toArrayBuffer(padded),\n ),\n );\n\n // salt || rs || idlen || keyid || ciphertext\n return concatBytes(\n salt,\n uint32Be(RECORD_SIZE),\n new Uint8Array([asPublicRaw.length]),\n asPublicRaw,\n ciphertext,\n );\n}\n\nasync function signVapidJwt(\n endpoint: string,\n subject: string,\n vapidPublicKey: string,\n vapidPrivateKey: string,\n): Promise<string> {\n const publicKey = decodeBase64Url(vapidPublicKey);\n const privateKey = decodeBase64Url(vapidPrivateKey);\n const jwk = ecKeyPairToJwk(publicKey, privateKey);\n\n const key = await crypto.subtle.importKey(\n \"jwk\",\n jwk,\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n false,\n [\"sign\"],\n );\n\n const origin = new URL(endpoint).origin;\n const header = { typ: \"JWT\", alg: \"ES256\" };\n const payload = {\n aud: origin,\n exp: Math.floor(Date.now() / 1000) + 12 * 60 * 60,\n sub: subject,\n };\n\n const encodedHeader = encodeBase64Url(encodeUtf8(JSON.stringify(header)));\n const encodedPayload = encodeBase64Url(encodeUtf8(JSON.stringify(payload)));\n const signingInput = `${encodedHeader}.${encodedPayload}`;\n\n const signature = new Uint8Array(\n await crypto.subtle.sign(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n key,\n toArrayBuffer(encodeUtf8(signingInput)),\n ),\n );\n\n return `${signingInput}.${encodeBase64Url(signature)}`;\n}\n\n/**\n * Web Push transport — VAPID auth + RFC 8291 payload encryption.\n *\n * Endpoint URLs are validated against an allowlist before fetch to mitigate SSRF.\n * Redirects are not followed.\n */\nexport class WebPushTransport implements PushTransport {\n readonly provider = \"webpush\";\n\n private readonly vapidPublicKey: string;\n private readonly vapidPrivateKey: string;\n private readonly subject: string;\n private readonly allowedEndpointHosts: readonly string[];\n\n /** Creates a Web Push transport with VAPID credentials. */\n constructor(config: WebPushConfig) {\n this.vapidPublicKey = config.vapidPublicKey;\n this.vapidPrivateKey = config.vapidPrivateKey;\n this.subject = config.subject;\n this.allowedEndpointHosts = config.allowedEndpointHosts ?? [];\n }\n\n /** Encrypts and POSTs a notification to the subscription endpoint. */\n async send(options: PushOptions): Promise<PushSendResult> {\n if (!isWebPushOptions(options)) {\n throw new WebPushError(\n \"WebPushTransport requires PushOptions.subscription; use FcmTransport for device tokens\",\n 400,\n { hint: \"fcm\" },\n );\n }\n\n try {\n assertSafePushEndpoint(options.subscription.endpoint, this.allowedEndpointHosts);\n } catch (error) {\n let endpointHost: string | undefined;\n try {\n endpointHost = new URL(options.subscription.endpoint).hostname;\n } catch {\n endpointHost = undefined;\n }\n throw new WebPushError(\n error instanceof Error ? error.message : \"Unsafe push subscription endpoint\",\n 400,\n { endpointHost },\n );\n }\n\n const notification = {\n title: options.title,\n body: options.body,\n ...(options.data !== undefined ? { data: options.data } : {}),\n ...(options.icon !== undefined ? { icon: options.icon } : {}),\n };\n const plaintext = encodeUtf8(JSON.stringify(notification));\n const encrypted = await encryptAes128Gcm(options.subscription, plaintext);\n\n if (encrypted.length > MAX_ENCRYPTED_BODY_BYTES) {\n throw new WebPushError(\n `Encrypted push payload exceeds ${MAX_ENCRYPTED_BODY_BYTES} bytes (RFC 8291)`,\n 413,\n { size: encrypted.length },\n );\n }\n\n const jwt = await signVapidJwt(\n options.subscription.endpoint,\n this.subject,\n this.vapidPublicKey,\n this.vapidPrivateKey,\n );\n\n const ttl = options.ttl ?? DEFAULT_TTL_SECONDS;\n const response = await fetch(options.subscription.endpoint, {\n method: \"POST\",\n redirect: \"manual\",\n headers: {\n Authorization: `vapid t=${jwt}, k=${this.vapidPublicKey}`,\n \"Content-Encoding\": \"aes128gcm\",\n TTL: String(ttl),\n \"Content-Type\": \"application/octet-stream\",\n },\n body: toArrayBuffer(encrypted),\n });\n\n // Opaque redirect responses (3xx) must not be followed — SSRF mitigation.\n if (response.status >= 300 && response.status < 400) {\n throw new WebPushError(\n `Push service returned redirect ${response.status}; redirects are not followed`,\n response.status,\n { location: response.headers.get(\"location\") },\n );\n }\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => \"\");\n throw new WebPushError(\n bodyText || `Web Push request failed with status ${response.status}`,\n response.status,\n bodyText || { status: response.status },\n );\n }\n\n return {\n messageId: options.messageId ?? crypto.randomUUID(),\n status: \"accepted\",\n response: String(response.status),\n provider: \"webpush\",\n };\n }\n\n /** Lightweight VAPID credential shape check. */\n async verify(): Promise<VerifyResult> {\n try {\n const pub = decodeBase64Url(this.vapidPublicKey);\n const priv = decodeBase64Url(this.vapidPrivateKey);\n const ok = pub.length === 65 && priv.length === 32 && this.subject.length > 0;\n return {\n ok,\n provider: \"webpush\",\n message: ok ? \"VAPID credentials present\" : \"Invalid VAPID key lengths\",\n };\n } catch (error) {\n return {\n ok: false,\n provider: \"webpush\",\n message: error instanceof Error ? error.message : \"Invalid VAPID credentials\",\n };\n }\n }\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "43FC53C9208BBE5564756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "DFA1A46FC61CEB2264756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -2,7 +2,7 @@
2
2
  * @module
3
3
  * WeightedFallbackTransport — routes each send through a weighted-random
4
4
  * primary provider, then fails over through the remaining providers on error.
5
- * Useful for gradual traffic shifting, migrations, and A/B testing providers.
5
+ * Works with email, SMS, WhatsApp, and push transports.
6
6
  *
7
7
  * @example
8
8
  * ```ts
@@ -19,20 +19,22 @@
19
19
  * );
20
20
  * ```
21
21
  */
22
- import type { MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
22
+ import type { DecoratedTransport, FallbackAugmentedResult } from "../core/decorated-transport.js";
23
+ import type { MailOptions, SendResult, VerifyResult } from "../core/types.js";
23
24
  import { type FallbackOptions, FallbackTransport, type FallbackVerifyAllResult } from "./fallback.js";
24
25
  /** A transport entry with a relative routing weight. */
25
- export interface WeightedTransportEntry {
26
+ export interface WeightedTransportEntry<TOptions = MailOptions, TResult = SendResult> {
26
27
  /** Transport instance. */
27
- transport: Transport;
28
+ transport: DecoratedTransport<TOptions, TResult>;
28
29
  /** Relative weight (e.g. 80 vs 20 → ~80% of sends try this provider first). */
29
30
  weight: number;
30
31
  }
31
32
  type MailerOnFallback = Parameters<FallbackTransport["setMailerOnFallback"]>[0];
32
33
  /**
33
34
  * Weighted provider routing with failover on error.
35
+ * Defaults preserve email {@link Transport} compatibility.
34
36
  */
35
- export declare class WeightedFallbackTransport implements Transport {
37
+ export declare class WeightedFallbackTransport<TOptions = MailOptions, TResult = SendResult> implements DecoratedTransport<TOptions, FallbackAugmentedResult<TResult>> {
36
38
  readonly provider = "weighted-fallback";
37
39
  private readonly entries;
38
40
  private readonly fallbackOptions;
@@ -43,18 +45,18 @@ export declare class WeightedFallbackTransport implements Transport {
43
45
  * Creates a weighted fallback transport.
44
46
  * @throws {Error} When entries is empty or total weight is zero.
45
47
  */
46
- constructor(entries: WeightedTransportEntry[], options?: FallbackOptions & {
48
+ constructor(entries: WeightedTransportEntry<TOptions, TResult>[], options?: FallbackOptions & {
47
49
  random?: () => number;
48
50
  });
49
51
  /**
50
- * Register a per-send onFallback callback from mailer lifecycle hooks.
51
- * @internal Used by {@link MailerImpl}; cleared after each send.
52
+ * Register a per-send onFallback callback from sender lifecycle hooks.
53
+ * @internal Cleared after each send.
52
54
  */
53
55
  setMailerOnFallback(callback: MailerOnFallback | undefined): void;
54
56
  private buildOrder;
55
57
  private pickWeightedIndex;
56
58
  /** Sends via a weighted-random primary provider, failing over on error. */
57
- send(message: MailOptions): Promise<SendResult>;
59
+ send(message: TOptions): Promise<FallbackAugmentedResult<TResult>>;
58
60
  /** Returns the first healthy provider in the chain. */
59
61
  verify(): Promise<VerifyResult>;
60
62
  /** Verifies every weighted provider in definition order. */
@@ -1,3 +1,3 @@
1
- import{k as E}from"../chunk-0gqy32pe.js";import"../chunk-gp4fwrs8.js";import"../chunk-tamww15j.js";import"../chunk-6npp3x3c.js";class J{provider="weighted-fallback";entries;fallbackOptions;random;inner;mailerOnFallback;constructor(h,z){if(h.length===0)throw Error("WeightedFallbackTransport requires at least one entry");if(h.reduce((D,H)=>D+H.weight,0)<=0)throw Error("WeightedFallbackTransport requires positive total weight");this.entries=h,this.random=z?.random??Math.random;let q=new Map,{random:B,...G}=z??{};this.fallbackOptions={...G,cooldownState:q},this.inner=new E(h.map((D)=>D.transport),this.fallbackOptions)}setMailerOnFallback(h){this.mailerOnFallback=h}buildOrder(){let h=this.pickWeightedIndex(),z=[this.entries[h]?.transport],A=this.entries.map((q,B)=>({entry:q,index:B})).filter(({index:q})=>q!==h).sort((q,B)=>B.entry.weight-q.entry.weight);for(let{entry:q}of A)z.push(q.transport);return z}pickWeightedIndex(){let h=this.entries.reduce((A,q)=>A+q.weight,0),z=this.random()*h;for(let A=0;A<this.entries.length;A++){let q=this.entries[A];if(z-=q.weight,z<=0)return A}return this.entries.length-1}async send(h){let z=new E(this.buildOrder(),this.fallbackOptions);if(this.mailerOnFallback!==void 0)z.setMailerOnFallback(this.mailerOnFallback);return z.send(h)}async verify(){return this.inner.verify()}async verifyAll(){return this.inner.verifyAll()}async close(){await this.inner.close()}}export{J as WeightedFallbackTransport};
1
+ import{m as E}from"../chunk-q2mahnb7.js";import"../chunk-cs1tvehn.js";import"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";class J{provider="weighted-fallback";entries;fallbackOptions;random;inner;mailerOnFallback;constructor(h,z){if(h.length===0)throw Error("WeightedFallbackTransport requires at least one entry");if(h.reduce((D,H)=>D+H.weight,0)<=0)throw Error("WeightedFallbackTransport requires positive total weight");this.entries=h,this.random=z?.random??Math.random;let q=new Map,{random:B,...G}=z??{};this.fallbackOptions={...G,cooldownState:q},this.inner=new E(h.map((D)=>D.transport),this.fallbackOptions)}setMailerOnFallback(h){this.mailerOnFallback=h}buildOrder(){let h=this.pickWeightedIndex(),z=[this.entries[h]?.transport],A=this.entries.map((q,B)=>({entry:q,index:B})).filter(({index:q})=>q!==h).sort((q,B)=>B.entry.weight-q.entry.weight);for(let{entry:q}of A)z.push(q.transport);return z}pickWeightedIndex(){let h=this.entries.reduce((A,q)=>A+q.weight,0),z=this.random()*h;for(let A=0;A<this.entries.length;A++){let q=this.entries[A];if(z-=q.weight,z<=0)return A}return this.entries.length-1}async send(h){let z=new E(this.buildOrder(),this.fallbackOptions);if(this.mailerOnFallback!==void 0)z.setMailerOnFallback(this.mailerOnFallback);return z.send(h)}async verify(){return this.inner.verify()}async verifyAll(){return this.inner.verifyAll()}async close(){await this.inner.close()}}export{J as WeightedFallbackTransport};
2
2
 
3
- //# debugId=1AE5329B4BB01DFC64756E2164756E21
3
+ //# debugId=462DBBA4FC2C14C364756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/transports/weighted-fallback.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * @module\n * WeightedFallbackTransport — routes each send through a weighted-random\n * primary provider, then fails over through the remaining providers on error.\n * Useful for gradual traffic shifting, migrations, and A/B testing providers.\n *\n * @example\n * ```ts\n * import { WeightedFallbackTransport } from \"sently/transports/weighted-fallback\";\n * import { ResendTransport } from \"sently/transports/resend\";\n * import { SESTransport } from \"sently/transports/ses\";\n *\n * const transport = new WeightedFallbackTransport(\n * [\n * { transport: new ResendTransport({ apiKey }), weight: 80 },\n * { transport: new SESTransport({ accessKeyId, secretAccessKey }), weight: 20 },\n * ],\n * { cooldownMs: 300_000 },\n * );\n * ```\n */\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport {\n type FallbackOptions,\n FallbackTransport,\n type FallbackVerifyAllResult,\n} from \"./fallback.js\";\n\n/** A transport entry with a relative routing weight. */\nexport interface WeightedTransportEntry {\n /** Transport instance. */\n transport: Transport;\n /** Relative weight (e.g. 80 vs 20 → ~80% of sends try this provider first). */\n weight: number;\n}\n\ntype MailerOnFallback = Parameters<FallbackTransport[\"setMailerOnFallback\"]>[0];\n\n/**\n * Weighted provider routing with failover on error.\n */\nexport class WeightedFallbackTransport implements Transport {\n readonly provider = \"weighted-fallback\";\n\n private readonly entries: WeightedTransportEntry[];\n private readonly fallbackOptions: FallbackOptions;\n private readonly random: () => number;\n private readonly inner: FallbackTransport;\n private mailerOnFallback: MailerOnFallback | undefined;\n\n /**\n * Creates a weighted fallback transport.\n * @throws {Error} When entries is empty or total weight is zero.\n */\n constructor(\n entries: WeightedTransportEntry[],\n options?: FallbackOptions & { random?: () => number },\n ) {\n if (entries.length === 0) {\n throw new Error(\"WeightedFallbackTransport requires at least one entry\");\n }\n\n const totalWeight = entries.reduce((sum, entry) => sum + entry.weight, 0);\n if (totalWeight <= 0) {\n throw new Error(\"WeightedFallbackTransport requires positive total weight\");\n }\n\n this.entries = entries;\n this.random = options?.random ?? Math.random;\n\n const cooldownState = new Map<string, number>();\n const { random: _random, ...rest } = options ?? {};\n this.fallbackOptions = { ...rest, cooldownState };\n\n this.inner = new FallbackTransport(\n entries.map((entry) => entry.transport),\n this.fallbackOptions,\n );\n }\n\n /**\n * Register a per-send onFallback callback from mailer lifecycle hooks.\n * @internal Used by {@link MailerImpl}; cleared after each send.\n */\n setMailerOnFallback(callback: MailerOnFallback | undefined): void {\n this.mailerOnFallback = callback;\n }\n\n private buildOrder(): Transport[] {\n const primaryIndex = this.pickWeightedIndex();\n const order: Transport[] = [this.entries[primaryIndex]?.transport as Transport];\n\n const remaining = this.entries\n .map((entry, index) => ({ entry, index }))\n .filter(({ index }) => index !== primaryIndex)\n .sort((a, b) => b.entry.weight - a.entry.weight);\n\n for (const { entry } of remaining) {\n order.push(entry.transport);\n }\n\n return order;\n }\n\n private pickWeightedIndex(): number {\n const total = this.entries.reduce((sum, entry) => sum + entry.weight, 0);\n let roll = this.random() * total;\n\n for (let i = 0; i < this.entries.length; i++) {\n const entry = this.entries[i] as WeightedTransportEntry;\n roll -= entry.weight;\n if (roll <= 0) {\n return i;\n }\n }\n\n return this.entries.length - 1;\n }\n\n /** Sends via a weighted-random primary provider, failing over on error. */\n async send(message: MailOptions): Promise<SendResult> {\n const transport = new FallbackTransport(this.buildOrder(), this.fallbackOptions);\n if (this.mailerOnFallback !== undefined) {\n transport.setMailerOnFallback(this.mailerOnFallback);\n }\n return transport.send(message);\n }\n\n /** Returns the first healthy provider in the chain. */\n async verify(): Promise<VerifyResult> {\n return this.inner.verify();\n }\n\n /** Verifies every weighted provider in definition order. */\n async verifyAll(): Promise<FallbackVerifyAllResult> {\n return this.inner.verifyAll();\n }\n\n /** Closes every underlying transport. */\n async close(): Promise<void> {\n await this.inner.close();\n }\n}\n"
5
+ "/**\n * @module\n * WeightedFallbackTransport — routes each send through a weighted-random\n * primary provider, then fails over through the remaining providers on error.\n * Works with email, SMS, WhatsApp, and push transports.\n *\n * @example\n * ```ts\n * import { WeightedFallbackTransport } from \"sently/transports/weighted-fallback\";\n * import { ResendTransport } from \"sently/transports/resend\";\n * import { SESTransport } from \"sently/transports/ses\";\n *\n * const transport = new WeightedFallbackTransport(\n * [\n * { transport: new ResendTransport({ apiKey }), weight: 80 },\n * { transport: new SESTransport({ accessKeyId, secretAccessKey }), weight: 20 },\n * ],\n * { cooldownMs: 300_000 },\n * );\n * ```\n */\nimport type { DecoratedTransport, FallbackAugmentedResult } from \"../core/decorated-transport.js\";\nimport type { MailOptions, SendResult, VerifyResult } from \"../core/types.js\";\nimport {\n type FallbackOptions,\n FallbackTransport,\n type FallbackVerifyAllResult,\n} from \"./fallback.js\";\n\n/** A transport entry with a relative routing weight. */\nexport interface WeightedTransportEntry<TOptions = MailOptions, TResult = SendResult> {\n /** Transport instance. */\n transport: DecoratedTransport<TOptions, TResult>;\n /** Relative weight (e.g. 80 vs 20 → ~80% of sends try this provider first). */\n weight: number;\n}\n\ntype MailerOnFallback = Parameters<FallbackTransport[\"setMailerOnFallback\"]>[0];\n\n/**\n * Weighted provider routing with failover on error.\n * Defaults preserve email {@link Transport} compatibility.\n */\nexport class WeightedFallbackTransport<TOptions = MailOptions, TResult = SendResult>\n implements DecoratedTransport<TOptions, FallbackAugmentedResult<TResult>>\n{\n readonly provider = \"weighted-fallback\";\n\n private readonly entries: WeightedTransportEntry<TOptions, TResult>[];\n private readonly fallbackOptions: FallbackOptions;\n private readonly random: () => number;\n private readonly inner: FallbackTransport<TOptions, TResult>;\n private mailerOnFallback: MailerOnFallback | undefined;\n\n /**\n * Creates a weighted fallback transport.\n * @throws {Error} When entries is empty or total weight is zero.\n */\n constructor(\n entries: WeightedTransportEntry<TOptions, TResult>[],\n options?: FallbackOptions & { random?: () => number },\n ) {\n if (entries.length === 0) {\n throw new Error(\"WeightedFallbackTransport requires at least one entry\");\n }\n\n const totalWeight = entries.reduce((sum, entry) => sum + entry.weight, 0);\n if (totalWeight <= 0) {\n throw new Error(\"WeightedFallbackTransport requires positive total weight\");\n }\n\n this.entries = entries;\n this.random = options?.random ?? Math.random;\n\n const cooldownState = new Map<string, number>();\n const { random: _random, ...rest } = options ?? {};\n this.fallbackOptions = { ...rest, cooldownState };\n\n this.inner = new FallbackTransport(\n entries.map((entry) => entry.transport),\n this.fallbackOptions,\n );\n }\n\n /**\n * Register a per-send onFallback callback from sender lifecycle hooks.\n * @internal Cleared after each send.\n */\n setMailerOnFallback(callback: MailerOnFallback | undefined): void {\n this.mailerOnFallback = callback;\n }\n\n private buildOrder(): DecoratedTransport<TOptions, TResult>[] {\n const primaryIndex = this.pickWeightedIndex();\n const order: DecoratedTransport<TOptions, TResult>[] = [\n this.entries[primaryIndex]?.transport as DecoratedTransport<TOptions, TResult>,\n ];\n\n const remaining = this.entries\n .map((entry, index) => ({ entry, index }))\n .filter(({ index }) => index !== primaryIndex)\n .sort((a, b) => b.entry.weight - a.entry.weight);\n\n for (const { entry } of remaining) {\n order.push(entry.transport);\n }\n\n return order;\n }\n\n private pickWeightedIndex(): number {\n const total = this.entries.reduce((sum, entry) => sum + entry.weight, 0);\n let roll = this.random() * total;\n\n for (let i = 0; i < this.entries.length; i++) {\n const entry = this.entries[i] as WeightedTransportEntry<TOptions, TResult>;\n roll -= entry.weight;\n if (roll <= 0) {\n return i;\n }\n }\n\n return this.entries.length - 1;\n }\n\n /** Sends via a weighted-random primary provider, failing over on error. */\n async send(message: TOptions): Promise<FallbackAugmentedResult<TResult>> {\n const transport = new FallbackTransport(this.buildOrder(), this.fallbackOptions);\n if (this.mailerOnFallback !== undefined) {\n transport.setMailerOnFallback(this.mailerOnFallback);\n }\n return transport.send(message);\n }\n\n /** Returns the first healthy provider in the chain. */\n async verify(): Promise<VerifyResult> {\n return this.inner.verify();\n }\n\n /** Verifies every weighted provider in definition order. */\n async verifyAll(): Promise<FallbackVerifyAllResult> {\n return this.inner.verifyAll();\n }\n\n /** Closes every underlying transport. */\n async close(): Promise<void> {\n await this.inner.close();\n }\n}\n"
6
6
  ],
7
- "mappings": "+IAyCO,CAAM,JAA+C,JACjD,SAAW,oBAEH,QACA,gBACA,OACA,MACT,iBAMR,WAAW,CACT,EACA,EACA,CACA,GAAI,EAAQ,SAAW,EACrB,MAAU,MAAM,uDAAuD,EAIzE,GADoB,EAAQ,OAAO,CAAC,EAAK,IAAU,EAAM,EAAM,OAAQ,CAAC,GACrD,EACjB,MAAU,MAAM,0DAA0D,EAG5E,KAAK,QAAU,EACf,KAAK,OAAS,GAAS,QAAU,KAAK,OAEtC,IAAM,EAAgB,IAAI,KAClB,OAAQ,KAAY,GAAS,GAAW,CAAC,EACjD,KAAK,gBAAkB,IAAK,EAAM,eAAc,EAEhD,KAAK,MAAQ,IAAI,EACf,EAAQ,IAAI,CAAC,IAAU,EAAM,SAAS,EACtC,KAAK,eACP,EAOF,mBAAmB,CAAC,EAA8C,CAChE,KAAK,iBAAmB,EAGlB,UAAU,EAAgB,CAChC,IAAM,EAAe,KAAK,kBAAkB,EACtC,EAAqB,CAAC,KAAK,QAAQ,IAAe,SAAsB,EAExE,EAAY,KAAK,QACpB,IAAI,CAAC,EAAO,KAAW,CAAE,QAAO,OAAM,EAAE,EACxC,OAAO,EAAG,WAAY,IAAU,CAAY,EAC5C,KAAK,CAAC,EAAG,IAAM,EAAE,MAAM,OAAS,EAAE,MAAM,MAAM,EAEjD,QAAa,WAAW,EACtB,EAAM,KAAK,EAAM,SAAS,EAG5B,OAAO,EAGD,iBAAiB,EAAW,CAClC,IAAM,EAAQ,KAAK,QAAQ,OAAO,CAAC,EAAK,IAAU,EAAM,EAAM,OAAQ,CAAC,EACnE,EAAO,KAAK,OAAO,EAAI,EAE3B,QAAS,EAAI,EAAG,EAAI,KAAK,QAAQ,OAAQ,IAAK,CAC5C,IAAM,EAAQ,KAAK,QAAQ,GAE3B,GADA,GAAQ,EAAM,OACV,GAAQ,EACV,OAAO,EAIX,OAAO,KAAK,QAAQ,OAAS,OAIzB,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAY,IAAI,EAAkB,KAAK,WAAW,EAAG,KAAK,eAAe,EAC/E,GAAI,KAAK,mBAAqB,OAC5B,EAAU,oBAAoB,KAAK,gBAAgB,EAErD,OAAO,EAAU,KAAK,CAAO,OAIzB,OAAM,EAA0B,CACpC,OAAO,KAAK,MAAM,OAAO,OAIrB,UAAS,EAAqC,CAClD,OAAO,KAAK,MAAM,UAAU,OAIxB,MAAK,EAAkB,CAC3B,MAAM,KAAK,MAAM,MAAM,EAE3B",
8
- "debugId": "1AE5329B4BB01DFC64756E2164756E21",
7
+ "mappings": "+IA2CO,CAAM,JAEb,JACW,SAAW,oBAEH,QACA,gBACA,OACA,MACT,iBAMR,WAAW,CACT,EACA,EACA,CACA,GAAI,EAAQ,SAAW,EACrB,MAAU,MAAM,uDAAuD,EAIzE,GADoB,EAAQ,OAAO,CAAC,EAAK,IAAU,EAAM,EAAM,OAAQ,CAAC,GACrD,EACjB,MAAU,MAAM,0DAA0D,EAG5E,KAAK,QAAU,EACf,KAAK,OAAS,GAAS,QAAU,KAAK,OAEtC,IAAM,EAAgB,IAAI,KAClB,OAAQ,KAAY,GAAS,GAAW,CAAC,EACjD,KAAK,gBAAkB,IAAK,EAAM,eAAc,EAEhD,KAAK,MAAQ,IAAI,EACf,EAAQ,IAAI,CAAC,IAAU,EAAM,SAAS,EACtC,KAAK,eACP,EAOF,mBAAmB,CAAC,EAA8C,CAChE,KAAK,iBAAmB,EAGlB,UAAU,EAA4C,CAC5D,IAAM,EAAe,KAAK,kBAAkB,EACtC,EAAiD,CACrD,KAAK,QAAQ,IAAe,SAC9B,EAEM,EAAY,KAAK,QACpB,IAAI,CAAC,EAAO,KAAW,CAAE,QAAO,OAAM,EAAE,EACxC,OAAO,EAAG,WAAY,IAAU,CAAY,EAC5C,KAAK,CAAC,EAAG,IAAM,EAAE,MAAM,OAAS,EAAE,MAAM,MAAM,EAEjD,QAAa,WAAW,EACtB,EAAM,KAAK,EAAM,SAAS,EAG5B,OAAO,EAGD,iBAAiB,EAAW,CAClC,IAAM,EAAQ,KAAK,QAAQ,OAAO,CAAC,EAAK,IAAU,EAAM,EAAM,OAAQ,CAAC,EACnE,EAAO,KAAK,OAAO,EAAI,EAE3B,QAAS,EAAI,EAAG,EAAI,KAAK,QAAQ,OAAQ,IAAK,CAC5C,IAAM,EAAQ,KAAK,QAAQ,GAE3B,GADA,GAAQ,EAAM,OACV,GAAQ,EACV,OAAO,EAIX,OAAO,KAAK,QAAQ,OAAS,OAIzB,KAAI,CAAC,EAA8D,CACvE,IAAM,EAAY,IAAI,EAAkB,KAAK,WAAW,EAAG,KAAK,eAAe,EAC/E,GAAI,KAAK,mBAAqB,OAC5B,EAAU,oBAAoB,KAAK,gBAAgB,EAErD,OAAO,EAAU,KAAK,CAAO,OAIzB,OAAM,EAA0B,CACpC,OAAO,KAAK,MAAM,OAAO,OAIrB,UAAS,EAAqC,CAClD,OAAO,KAAK,MAAM,UAAU,OAIxB,MAAK,EAAkB,CAC3B,MAAM,KAAK,MAAM,MAAM,EAE3B",
8
+ "debugId": "462DBBA4FC2C14C364756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,3 +1,3 @@
1
- import{ca as x,da as z}from"../chunk-tamww15j.js";import"../chunk-6npp3x3c.js";var G="v26.0";class B extends x{statusCode;apiError;constructor(j,k,q){super(j,z(k),{statusCode:k,provider:"whatsapp-cloud",cause:q});this.statusCode=k;this.apiError=q;this.name="WhatsAppCloudError"}}function J(j){return"template"in j}class K{provider="whatsapp-cloud";accessToken;phoneNumberId;apiVersion;constructor(j){this.accessToken=j.accessToken,this.phoneNumberId=j.phoneNumberId,this.apiVersion=j.apiVersion??G}async send(j){let k=`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`,q=J(j)?{messaging_product:"whatsapp",to:j.to,type:"template",template:{name:j.template.name,language:{code:j.template.language},...j.template.components?{components:j.template.components}:{}}}:{messaging_product:"whatsapp",to:j.to,type:"text",text:{body:j.text}},v=await fetch(k,{method:"POST",headers:{Authorization:`Bearer ${this.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(q)}),w=await v.json();if(!v.ok)throw new B(w.error?.message??"WhatsApp Cloud API error",v.status,w);return{messageId:w.messages?.[0]?.id??j.messageId??"",to:j.to,status:"accepted",response:"Message sent",provider:"whatsapp-cloud"}}async verify(){return{ok:Boolean(this.accessToken&&this.phoneNumberId),provider:"whatsapp-cloud",message:this.accessToken&&this.phoneNumberId?"Credentials present":"Missing credentials"}}}export{K as WhatsAppCloudTransport,B as WhatsAppCloudError,G as WHATSAPP_CLOUD_DEFAULT_API_VERSION};
1
+ import{ja as x,ka as z}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";var G="v26.0";class B extends x{statusCode;apiError;constructor(j,k,q){super(j,z(k),{statusCode:k,provider:"whatsapp-cloud",cause:q});this.statusCode=k;this.apiError=q;this.name="WhatsAppCloudError"}}function J(j){return"template"in j}class K{provider="whatsapp-cloud";accessToken;phoneNumberId;apiVersion;constructor(j){this.accessToken=j.accessToken,this.phoneNumberId=j.phoneNumberId,this.apiVersion=j.apiVersion??G}async send(j){let k=`https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`,q=J(j)?{messaging_product:"whatsapp",to:j.to,type:"template",template:{name:j.template.name,language:{code:j.template.language},...j.template.components?{components:j.template.components}:{}}}:{messaging_product:"whatsapp",to:j.to,type:"text",text:{body:j.text}},v=await fetch(k,{method:"POST",headers:{Authorization:`Bearer ${this.accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(q)}),w=await v.json();if(!v.ok)throw new B(w.error?.message??"WhatsApp Cloud API error",v.status,w);return{messageId:w.messages?.[0]?.id??j.messageId??"",to:j.to,status:"accepted",response:"Message sent",provider:"whatsapp-cloud"}}async verify(){return{ok:Boolean(this.accessToken&&this.phoneNumberId),provider:"whatsapp-cloud",message:this.accessToken&&this.phoneNumberId?"Credentials present":"Missing credentials"}}}export{K as WhatsAppCloudTransport,B as WhatsAppCloudError,G as WHATSAPP_CLOUD_DEFAULT_API_VERSION};
2
2
 
3
- //# debugId=D51060C2D4F4831264756E2164756E21
3
+ //# debugId=B754BE00883090E464756E2164756E21