sently 1.0.1 → 1.2.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.
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +110 -1
- package/README.md +20 -5
- package/dist/chunk-z1589fjk.js.map +2 -2
- package/dist/core/push-types.d.ts +53 -4
- package/dist/transports/hostinger.d.ts +128 -0
- package/dist/transports/hostinger.js +3 -0
- package/dist/transports/hostinger.js.map +10 -0
- package/dist/transports/inbucket.d.ts +196 -0
- package/dist/transports/inbucket.js +3 -0
- package/dist/transports/inbucket.js.map +10 -0
- package/dist/transports/mailpit.d.ts +108 -8
- package/dist/transports/mailpit.js +2 -2
- package/dist/transports/mailpit.js.map +3 -3
- package/dist/transports/taqnyat-sms.d.ts +85 -0
- package/dist/transports/taqnyat-sms.js +2 -2
- package/dist/transports/taqnyat-sms.js.map +3 -3
- package/dist/transports/taqnyat-whatsapp.d.ts +112 -4
- package/dist/transports/taqnyat-whatsapp.js +2 -2
- package/dist/transports/taqnyat-whatsapp.js.map +3 -3
- package/dist/transports/webpush.d.ts +19 -0
- package/dist/transports/webpush.js +2 -2
- package/dist/transports/webpush.js.map +3 -3
- package/dist/webhooks/sndr.js +2 -2
- package/dist/webhooks/sndr.js.map +3 -3
- package/package.json +12 -2
- package/site/content/docs/ai/llms-txt.mdx +2 -0
- package/site/content/docs/channels/email.mdx +2 -1
- package/site/content/docs/channels/push.mdx +3 -1
- package/site/content/docs/decorators/preview.mdx +2 -1
- package/site/content/docs/get-started/entrypoints.mdx +1 -1
- package/site/content/docs/get-started/support-matrix.mdx +2 -2
- package/site/content/docs/guides/vendor-extras-otp.mdx +2 -1
- package/site/content/docs/guides/webhooks.mdx +2 -0
- package/site/content/docs/guides/webpush-interop.mdx +9 -1
- package/site/content/docs/reference/exports.mdx +1 -1
- package/site/content/docs/reference/push-options.mdx +22 -5
- package/site/content/docs/transports/hostinger.mdx +435 -0
- package/site/content/docs/transports/inbucket.mdx +200 -0
- package/site/content/docs/transports/index.mdx +3 -3
- package/site/content/docs/transports/mailpit.mdx +114 -14
- package/site/content/docs/transports/meta.json +6 -4
- package/site/content/docs/transports/smtp.mdx +3 -2
- package/site/content/docs/transports/sndr.mdx +68 -5
- package/site/content/docs/transports/taqnyat.mdx +365 -0
- package/site/content/docs/transports/webpush.mdx +201 -17
- package/site/content/docs/transports/taqnyat-mail.mdx +0 -41
- package/site/content/docs/transports/taqnyat-sms.mdx +0 -41
- package/site/content/docs/transports/taqnyat-whatsapp.mdx +0 -40
|
@@ -29,6 +29,13 @@
|
|
|
29
29
|
* code: "6240",
|
|
30
30
|
* lang: "en",
|
|
31
31
|
* });
|
|
32
|
+
* const balance = await taqnyat.getBalance();
|
|
33
|
+
* await taqnyat.schedule({
|
|
34
|
+
* to: "+9665xxxxxxxx",
|
|
35
|
+
* body: "Later",
|
|
36
|
+
* scheduledDatetime: "2030-01-01T10:00",
|
|
37
|
+
* deleteId: "demo-1",
|
|
38
|
+
* });
|
|
32
39
|
* ```
|
|
33
40
|
*/
|
|
34
41
|
import { SentlyError } from "../core/errors.js";
|
|
@@ -95,6 +102,60 @@ export interface TaqnyatOtpVerifyResult {
|
|
|
95
102
|
/** Provider identifier. */
|
|
96
103
|
provider: "taqnyat-sms";
|
|
97
104
|
}
|
|
105
|
+
/** Account balance from {@link TaqnyatSmsTransport.getBalance}. */
|
|
106
|
+
export interface TaqnyatBalance {
|
|
107
|
+
/** Account status string from Taqnyat (e.g. `active`). */
|
|
108
|
+
accountStatus: string;
|
|
109
|
+
/** Balance amount as returned by the API (string). */
|
|
110
|
+
balance: string;
|
|
111
|
+
/** Currency code (usually `SAR`). */
|
|
112
|
+
currency: string;
|
|
113
|
+
/** Account expiry date when present. */
|
|
114
|
+
accountExpiryDate?: string;
|
|
115
|
+
/** Provider identifier. */
|
|
116
|
+
provider: "taqnyat-sms";
|
|
117
|
+
}
|
|
118
|
+
/** One sender row from {@link TaqnyatSmsTransport.listSenders}. */
|
|
119
|
+
export interface TaqnyatSender {
|
|
120
|
+
/** Sender name exactly as registered. */
|
|
121
|
+
senderName: string;
|
|
122
|
+
/** Status string when present (e.g. `active`). */
|
|
123
|
+
status?: string;
|
|
124
|
+
}
|
|
125
|
+
/** Options for {@link TaqnyatSmsTransport.schedule}. */
|
|
126
|
+
export interface TaqnyatScheduleOptions {
|
|
127
|
+
/** Recipient phone number (E.164 or international digits). */
|
|
128
|
+
to: string;
|
|
129
|
+
/** Message body. */
|
|
130
|
+
body: string;
|
|
131
|
+
/**
|
|
132
|
+
* Local schedule time in Taqnyat format, e.g. `2020-09-30T14:26`.
|
|
133
|
+
* Required by `POST /v1/messages` schedule docs.
|
|
134
|
+
*/
|
|
135
|
+
scheduledDatetime: string;
|
|
136
|
+
/** Sender ID override; defaults to transport `sender`. */
|
|
137
|
+
from?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Client delete key used later with {@link deleteScheduled}.
|
|
140
|
+
* Optional per Taqnyat schedule docs.
|
|
141
|
+
*/
|
|
142
|
+
deleteId?: string | number;
|
|
143
|
+
/** Optional client id mapped to `smsId`. */
|
|
144
|
+
messageId?: string;
|
|
145
|
+
}
|
|
146
|
+
/** Result of a successful {@link TaqnyatSmsTransport.schedule} call. */
|
|
147
|
+
export interface TaqnyatScheduleResult {
|
|
148
|
+
/** Provider message id when present. */
|
|
149
|
+
messageId: string;
|
|
150
|
+
/** Echo of {@link TaqnyatScheduleOptions.deleteId} when set. */
|
|
151
|
+
deleteId?: string;
|
|
152
|
+
/** Recipient as passed in. */
|
|
153
|
+
to: string;
|
|
154
|
+
/** Cost summary when present. */
|
|
155
|
+
response: string;
|
|
156
|
+
/** Provider identifier. */
|
|
157
|
+
provider: "taqnyat-sms";
|
|
158
|
+
}
|
|
98
159
|
/** Error thrown when the Taqnyat SMS / Verify API returns a non-success response. */
|
|
99
160
|
export declare class TaqnyatSmsError extends SentlyError {
|
|
100
161
|
readonly statusCode: number;
|
|
@@ -126,6 +187,30 @@ export declare class TaqnyatSmsTransport implements SmsTransport {
|
|
|
126
187
|
* (`10` = completed; `13` / `19` = already verified).
|
|
127
188
|
*/
|
|
128
189
|
verifyOtp(options: TaqnyatVerifyOtpOptions): Promise<TaqnyatOtpVerifyResult>;
|
|
190
|
+
/**
|
|
191
|
+
* Vendor extra: account balance via `GET /account/balance`.
|
|
192
|
+
* Not part of {@link SmsTransport}.
|
|
193
|
+
*/
|
|
194
|
+
getBalance(): Promise<TaqnyatBalance>;
|
|
195
|
+
/**
|
|
196
|
+
* Vendor extra: list registered sender names via `GET /v1/messages/senders`.
|
|
197
|
+
* Not part of {@link SmsTransport}.
|
|
198
|
+
*/
|
|
199
|
+
listSenders(): Promise<TaqnyatSender[]>;
|
|
200
|
+
/**
|
|
201
|
+
* Vendor extra: schedule an SMS (`POST /v1/messages` + `scheduledDatetime`).
|
|
202
|
+
* Not part of {@link SmsTransport}.
|
|
203
|
+
*/
|
|
204
|
+
schedule(options: TaqnyatScheduleOptions): Promise<TaqnyatScheduleResult>;
|
|
205
|
+
/**
|
|
206
|
+
* Vendor extra: delete a scheduled SMS via `DELETE /v1/messages/delete`.
|
|
207
|
+
* Pass the same `deleteId` used in {@link schedule}.
|
|
208
|
+
*/
|
|
209
|
+
deleteScheduled(deleteId: string | number): Promise<{
|
|
210
|
+
ok: true;
|
|
211
|
+
message: string;
|
|
212
|
+
provider: "taqnyat-sms";
|
|
213
|
+
}>;
|
|
129
214
|
/** Lightweight credential check. */
|
|
130
215
|
verify(): Promise<VerifyResult>;
|
|
131
216
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{j as
|
|
1
|
+
import{j as Q}from"../chunk-5es0v1j7.js";import{ja as X,ka as Y}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";class J extends X{statusCode;apiError;constructor(A,B,F){super(A,Y(B>=100&&B<600?B:400),{statusCode:B,provider:"taqnyat-sms",cause:F});this.statusCode=B;this.apiError=F;this.name="TaqnyatSmsError"}}function U(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 R(A){if(typeof A.message==="string")return A.message;if(typeof A.MessageEn==="string")return A.MessageEn;return}function W(A){if(Array.isArray(A)){let B=A[0];return B&&typeof B==="object"?B:{}}if(A&&typeof A==="object")return A;return{}}class _{provider="taqnyat-sms";bearerToken;sender;constructor(A){this.bearerToken=A.bearerToken,this.sender=A.sender}async send(A){let F={recipients:[Q(A.to)],body:A.body,sender:A.from??this.sender};if(A.messageId!==void 0)F.smsId=A.messageId;let G=await fetch("https://api.taqnyat.sa/v1/messages",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(F)}),D=await G.json();if(G.status!==201)throw new J(D.message??"Taqnyat SMS API error",D.statusCode??G.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",F=Q(A.to),G=[{apiKey:this.bearerToken,numbers:[F],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(G)}),N=await D.text(),O;try{O=JSON.parse(N)}catch{throw new J("Taqnyat Verify returned non-JSON response",D.status,N)}let K=W(O),H=U(K);if(!D.ok||H!==5)throw new J(R(K)??`Taqnyat OTP send failed (code ${H??"unknown"})`,H??D.status,K);return{requestId:A.requestId,to:A.to,code:H,response:N,provider:"taqnyat-sms"}}async verifyOtp(A){let B=A.lang??"ar",F=Q(A.to),G=[{apiKey:this.bearerToken,numbers:[F],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(G)}),N=await D.text(),O;try{O=JSON.parse(N)}catch{throw new J("Taqnyat Verify returned non-JSON response",D.status,N)}let K=W(O),H=U(K),Z=new Set([10,13,19]);if(!D.ok||H===void 0||!Z.has(H))throw new J(R(K)??`Taqnyat OTP verify failed (code ${H??"unknown"})`,H??D.status,K);return{ok:!0,code:H,message:R(K)??"Activation process completed successfully",response:N,provider:"taqnyat-sms"}}async getBalance(){let A=await fetch(`https://api.taqnyat.sa/account/balance?bearerTokens=${encodeURIComponent(this.bearerToken)}`,{headers:{Authorization:`Bearer ${this.bearerToken}`}}),B=await A.json();if(!A.ok||B.statusCode!==200)throw new J(B.message??"Taqnyat balance API error",B.statusCode??A.status,B);return{accountStatus:B.accountStatus??"",balance:B.balance??"0",currency:B.currency??"SAR",...B.accountExpiryDate!==void 0?{accountExpiryDate:B.accountExpiryDate}:{},provider:"taqnyat-sms"}}async listSenders(){let A=await fetch(`https://api.taqnyat.sa/v1/messages/senders?bearerTokens=${encodeURIComponent(this.bearerToken)}`,{headers:{Authorization:`Bearer ${this.bearerToken}`}}),B=await A.json();if(!A.ok||B.statusCode!==200)throw new J(B.message??"Taqnyat senders API error",B.statusCode??A.status,B);return(B.senders??[]).filter((F)=>typeof F.senderName==="string").map((F)=>({senderName:F.senderName,...F.status!==void 0?{status:F.status}:{}}))}async schedule(A){let F={recipients:[Q(A.to)],body:A.body,sender:A.from??this.sender,scheduledDatetime:A.scheduledDatetime};if(A.deleteId!==void 0)F.deleteId=A.deleteId;if(A.messageId!==void 0)F.smsId=A.messageId;let G=await fetch("https://api.taqnyat.sa/v1/messages",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(F)}),D=await G.json();if(G.status!==201)throw new J(D.message??"Taqnyat schedule SMS API error",D.statusCode??G.status,D);return{messageId:String(D.messageId??A.messageId??""),...A.deleteId!==void 0?{deleteId:String(A.deleteId)}:{},to:A.to,response:`cost: ${D.cost??"?"} ${D.currency??"SAR"}`,provider:"taqnyat-sms"}}async deleteScheduled(A){let B=new URL("https://api.taqnyat.sa/v1/messages/delete");B.searchParams.set("bearerTokens",this.bearerToken),B.searchParams.set("deleteId",String(A));let F=await fetch(B,{method:"DELETE",headers:{Authorization:`Bearer ${this.bearerToken}`}}),G=await F.json();if(F.status!==201&&G.statusCode!==201)throw new J(G.message??"Taqnyat delete scheduled SMS API error",G.statusCode??F.status,G);return{ok:!0,message:G.message??"Deleted successfully",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{_ as TaqnyatSmsTransport,J as TaqnyatSmsError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=5FB80A8A78A3002064756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/taqnyat-sms.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
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"
|
|
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 * const balance = await taqnyat.getBalance();\n * await taqnyat.schedule({\n * to: \"+9665xxxxxxxx\",\n * body: \"Later\",\n * scheduledDatetime: \"2030-01-01T10:00\",\n * deleteId: \"demo-1\",\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/** Account balance from {@link TaqnyatSmsTransport.getBalance}. */\nexport interface TaqnyatBalance {\n /** Account status string from Taqnyat (e.g. `active`). */\n accountStatus: string;\n /** Balance amount as returned by the API (string). */\n balance: string;\n /** Currency code (usually `SAR`). */\n currency: string;\n /** Account expiry date when present. */\n accountExpiryDate?: string;\n /** Provider identifier. */\n provider: \"taqnyat-sms\";\n}\n\n/** One sender row from {@link TaqnyatSmsTransport.listSenders}. */\nexport interface TaqnyatSender {\n /** Sender name exactly as registered. */\n senderName: string;\n /** Status string when present (e.g. `active`). */\n status?: string;\n}\n\n/** Options for {@link TaqnyatSmsTransport.schedule}. */\nexport interface TaqnyatScheduleOptions {\n /** Recipient phone number (E.164 or international digits). */\n to: string;\n /** Message body. */\n body: string;\n /**\n * Local schedule time in Taqnyat format, e.g. `2020-09-30T14:26`.\n * Required by `POST /v1/messages` schedule docs.\n */\n scheduledDatetime: string;\n /** Sender ID override; defaults to transport `sender`. */\n from?: string;\n /**\n * Client delete key used later with {@link deleteScheduled}.\n * Optional per Taqnyat schedule docs.\n */\n deleteId?: string | number;\n /** Optional client id mapped to `smsId`. */\n messageId?: string;\n}\n\n/** Result of a successful {@link TaqnyatSmsTransport.schedule} call. */\nexport interface TaqnyatScheduleResult {\n /** Provider message id when present. */\n messageId: string;\n /** Echo of {@link TaqnyatScheduleOptions.deleteId} when set. */\n deleteId?: string;\n /** Recipient as passed in. */\n to: string;\n /** Cost summary when present. */\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 /**\n * Vendor extra: account balance via `GET /account/balance`.\n * Not part of {@link SmsTransport}.\n */\n async getBalance(): Promise<TaqnyatBalance> {\n const response = await fetch(\n `https://api.taqnyat.sa/account/balance?bearerTokens=${encodeURIComponent(this.bearerToken)}`,\n {\n headers: { Authorization: `Bearer ${this.bearerToken}` },\n },\n );\n const payload = (await response.json()) as {\n statusCode?: number;\n accountStatus?: string;\n balance?: string;\n currency?: string;\n accountExpiryDate?: string;\n message?: string;\n };\n\n if (!response.ok || payload.statusCode !== 200) {\n throw new TaqnyatSmsError(\n payload.message ?? \"Taqnyat balance API error\",\n payload.statusCode ?? response.status,\n payload,\n );\n }\n\n return {\n accountStatus: payload.accountStatus ?? \"\",\n balance: payload.balance ?? \"0\",\n currency: payload.currency ?? \"SAR\",\n ...(payload.accountExpiryDate !== undefined\n ? { accountExpiryDate: payload.accountExpiryDate }\n : {}),\n provider: \"taqnyat-sms\",\n };\n }\n\n /**\n * Vendor extra: list registered sender names via `GET /v1/messages/senders`.\n * Not part of {@link SmsTransport}.\n */\n async listSenders(): Promise<TaqnyatSender[]> {\n const response = await fetch(\n `https://api.taqnyat.sa/v1/messages/senders?bearerTokens=${encodeURIComponent(this.bearerToken)}`,\n {\n headers: { Authorization: `Bearer ${this.bearerToken}` },\n },\n );\n const payload = (await response.json()) as {\n statusCode?: number;\n senders?: Array<{ senderName?: string; status?: string }>;\n message?: string;\n };\n\n if (!response.ok || payload.statusCode !== 200) {\n throw new TaqnyatSmsError(\n payload.message ?? \"Taqnyat senders API error\",\n payload.statusCode ?? response.status,\n payload,\n );\n }\n\n return (payload.senders ?? [])\n .filter(\n (row): row is { senderName: string; status?: string } => typeof row.senderName === \"string\",\n )\n .map((row) => ({\n senderName: row.senderName,\n ...(row.status !== undefined ? { status: row.status } : {}),\n }));\n }\n\n /**\n * Vendor extra: schedule an SMS (`POST /v1/messages` + `scheduledDatetime`).\n * Not part of {@link SmsTransport}.\n */\n async schedule(options: TaqnyatScheduleOptions): Promise<TaqnyatScheduleResult> {\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 scheduledDatetime: options.scheduledDatetime,\n };\n if (options.deleteId !== undefined) {\n body.deleteId = options.deleteId;\n }\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 };\n\n if (response.status !== 201) {\n throw new TaqnyatSmsError(\n payload.message ?? \"Taqnyat schedule SMS API error\",\n payload.statusCode ?? response.status,\n payload,\n );\n }\n\n return {\n messageId: String(payload.messageId ?? options.messageId ?? \"\"),\n ...(options.deleteId !== undefined ? { deleteId: String(options.deleteId) } : {}),\n to: options.to,\n response: `cost: ${payload.cost ?? \"?\"} ${payload.currency ?? \"SAR\"}`,\n provider: \"taqnyat-sms\",\n };\n }\n\n /**\n * Vendor extra: delete a scheduled SMS via `DELETE /v1/messages/delete`.\n * Pass the same `deleteId` used in {@link schedule}.\n */\n async deleteScheduled(\n deleteId: string | number,\n ): Promise<{ ok: true; message: string; provider: \"taqnyat-sms\" }> {\n const url = new URL(\"https://api.taqnyat.sa/v1/messages/delete\");\n url.searchParams.set(\"bearerTokens\", this.bearerToken);\n url.searchParams.set(\"deleteId\", String(deleteId));\n\n const response = await fetch(url, {\n method: \"DELETE\",\n headers: { Authorization: `Bearer ${this.bearerToken}` },\n });\n\n const payload = (await response.json()) as {\n statusCode?: number;\n message?: string;\n };\n\n if (response.status !== 201 && payload.statusCode !== 201) {\n throw new TaqnyatSmsError(\n payload.message ?? \"Taqnyat delete scheduled SMS API error\",\n payload.statusCode ?? response.status,\n payload,\n );\n }\n\n return {\n ok: true,\n message: payload.message ?? \"Deleted successfully\",\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
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "5FB80A8A78A3002064756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* Template: `{ to, type: "template", template: { name, language: { code } } }`.
|
|
8
8
|
* Session text: `{ to, type: "text", text: { body } }`.
|
|
9
9
|
*
|
|
10
|
+
* Vendor extras on this class: templates CRUD, opt-in/out, failover send.
|
|
11
|
+
*
|
|
10
12
|
* Sently-first: wire into {@link createWhatsAppSender}.
|
|
11
13
|
*
|
|
12
14
|
* @example
|
|
@@ -14,11 +16,12 @@
|
|
|
14
16
|
* import { createWhatsAppSender } from "sently/whatsapp";
|
|
15
17
|
* import { TaqnyatWhatsAppTransport } from "sently/transports/taqnyat-whatsapp";
|
|
16
18
|
*
|
|
17
|
-
* const
|
|
18
|
-
*
|
|
19
|
-
* bearerToken: process.env.TAQNYAT_WHATSAPP_TOKEN!,
|
|
20
|
-
* }),
|
|
19
|
+
* const transport = new TaqnyatWhatsAppTransport({
|
|
20
|
+
* bearerToken: process.env.TAQNYAT_WHATSAPP_TOKEN!,
|
|
21
21
|
* });
|
|
22
|
+
* const wa = createWhatsAppSender({ transport });
|
|
23
|
+
* await transport.optIn("+9665xxxxxxxx");
|
|
24
|
+
* await wa.send({ to: "+9665xxxxxxxx", template: { name: "welcome", language: "ar" } });
|
|
22
25
|
* ```
|
|
23
26
|
*/
|
|
24
27
|
import { SentlyError } from "../core/errors.js";
|
|
@@ -29,6 +32,59 @@ export interface TaqnyatWhatsAppConfig {
|
|
|
29
32
|
/** Bearer token from a Taqnyat application enabled for WhatsApp. */
|
|
30
33
|
bearerToken: string;
|
|
31
34
|
}
|
|
35
|
+
/** One template row from {@link TaqnyatWhatsAppTransport.listTemplates}. */
|
|
36
|
+
export interface TaqnyatWhatsAppTemplate {
|
|
37
|
+
/** Template name. */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Language code (e.g. `ar`, `en`). */
|
|
40
|
+
language: string;
|
|
41
|
+
/** Status (`approved`, `pending`, …). */
|
|
42
|
+
status: string;
|
|
43
|
+
/** Category when present (`UTILITY`, `MARKETING`, …). */
|
|
44
|
+
category?: string;
|
|
45
|
+
/** Provider template id when present. */
|
|
46
|
+
id?: string;
|
|
47
|
+
}
|
|
48
|
+
/** Options for {@link TaqnyatWhatsAppTransport.createTemplate}. */
|
|
49
|
+
export interface TaqnyatCreateTemplateOptions {
|
|
50
|
+
/** Unique template name without spaces. */
|
|
51
|
+
name: string;
|
|
52
|
+
/** Language code. */
|
|
53
|
+
language: string;
|
|
54
|
+
/** Template category (`UTILITY`, `MARKETING`, `AUTHENTICATION`, …). */
|
|
55
|
+
category: string;
|
|
56
|
+
/** Component array as required by Taqnyat / Meta. */
|
|
57
|
+
components: unknown[];
|
|
58
|
+
/** Allow Meta to adjust category. Defaults to `true`. */
|
|
59
|
+
allowCategoryChange?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** Email fallback nested under SMS/mail failover payloads. */
|
|
62
|
+
export interface TaqnyatFailoverEmailOnFail {
|
|
63
|
+
from: string;
|
|
64
|
+
to: string;
|
|
65
|
+
}
|
|
66
|
+
/** SMS failover branch for {@link TaqnyatWhatsAppTransport.sendWithFailover}. */
|
|
67
|
+
export interface TaqnyatWhatsAppSmsFailover {
|
|
68
|
+
sender: string;
|
|
69
|
+
campaign: string;
|
|
70
|
+
body: string;
|
|
71
|
+
sendEmailIfFail?: TaqnyatFailoverEmailOnFail;
|
|
72
|
+
}
|
|
73
|
+
/** Mail failover branch for {@link TaqnyatWhatsAppTransport.sendWithFailover}. */
|
|
74
|
+
export interface TaqnyatWhatsAppMailFailover {
|
|
75
|
+
from: string;
|
|
76
|
+
to: string;
|
|
77
|
+
campaign: string;
|
|
78
|
+
subject: string;
|
|
79
|
+
msg: string;
|
|
80
|
+
cc?: string;
|
|
81
|
+
sendEmailIfFail?: TaqnyatFailoverEmailOnFail;
|
|
82
|
+
}
|
|
83
|
+
/** Failover channels for {@link TaqnyatWhatsAppTransport.sendWithFailover}. */
|
|
84
|
+
export interface TaqnyatWhatsAppFailover {
|
|
85
|
+
sms?: TaqnyatWhatsAppSmsFailover;
|
|
86
|
+
mail?: TaqnyatWhatsAppMailFailover;
|
|
87
|
+
}
|
|
32
88
|
/** Error thrown when the Taqnyat WhatsApp API reports a failure. */
|
|
33
89
|
export declare class TaqnyatWhatsAppError extends SentlyError {
|
|
34
90
|
readonly statusCode: number;
|
|
@@ -46,6 +102,58 @@ export declare class TaqnyatWhatsAppTransport implements WhatsAppTransport {
|
|
|
46
102
|
constructor(config: TaqnyatWhatsAppConfig);
|
|
47
103
|
/** Sends a WhatsApp template or session text message. */
|
|
48
104
|
send(options: WhatsAppOptions): Promise<WhatsAppSendResult>;
|
|
105
|
+
/**
|
|
106
|
+
* Vendor extra: send with optional SMS / email failover branches
|
|
107
|
+
* (same `POST /wa/v2/messages/` payload shape as Taqnyat failover docs).
|
|
108
|
+
* Not part of {@link WhatsAppTransport}.
|
|
109
|
+
*/
|
|
110
|
+
sendWithFailover(options: WhatsAppOptions, failover: TaqnyatWhatsAppFailover): Promise<WhatsAppSendResult>;
|
|
111
|
+
/**
|
|
112
|
+
* Vendor extra: list templates via `GET /wa/v2/templates/`.
|
|
113
|
+
* Not part of {@link WhatsAppTransport}.
|
|
114
|
+
*/
|
|
115
|
+
listTemplates(): Promise<TaqnyatWhatsAppTemplate[]>;
|
|
116
|
+
/**
|
|
117
|
+
* Vendor extra: create a template via `POST /wa/v2/templates/`.
|
|
118
|
+
* Not part of {@link WhatsAppTransport}.
|
|
119
|
+
*/
|
|
120
|
+
createTemplate(options: TaqnyatCreateTemplateOptions): Promise<{
|
|
121
|
+
id?: string;
|
|
122
|
+
category?: string;
|
|
123
|
+
status: string;
|
|
124
|
+
provider: "taqnyat-whatsapp";
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Vendor extra: delete a template via `DELETE /wa/v2/templates/`.
|
|
128
|
+
* Not part of {@link WhatsAppTransport}.
|
|
129
|
+
*/
|
|
130
|
+
deleteTemplate(options: {
|
|
131
|
+
name: string;
|
|
132
|
+
id: string | number;
|
|
133
|
+
}): Promise<{
|
|
134
|
+
ok: true;
|
|
135
|
+
provider: "taqnyat-whatsapp";
|
|
136
|
+
}>;
|
|
137
|
+
/**
|
|
138
|
+
* Vendor extra: record WhatsApp opt-in via `POST /wa/v1/provision/optin/`.
|
|
139
|
+
* Not part of {@link WhatsAppTransport}.
|
|
140
|
+
*/
|
|
141
|
+
optIn(to: string | readonly string[]): Promise<{
|
|
142
|
+
ok: true;
|
|
143
|
+
numbers: string[];
|
|
144
|
+
provider: "taqnyat-whatsapp";
|
|
145
|
+
}>;
|
|
146
|
+
/**
|
|
147
|
+
* Vendor extra: record WhatsApp opt-out via `DELETE /wa/v1/provision/optin/`.
|
|
148
|
+
* Not part of {@link WhatsAppTransport}.
|
|
149
|
+
*/
|
|
150
|
+
optOut(to: string | readonly string[]): Promise<{
|
|
151
|
+
ok: true;
|
|
152
|
+
numbers: string[];
|
|
153
|
+
provider: "taqnyat-whatsapp";
|
|
154
|
+
}>;
|
|
49
155
|
/** Lightweight credential check. */
|
|
50
156
|
verify(): Promise<VerifyResult>;
|
|
157
|
+
private postMessage;
|
|
158
|
+
private postOptProvision;
|
|
51
159
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{j as
|
|
1
|
+
import{j as K}from"../chunk-5es0v1j7.js";import{ja as V,ka as X}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";class Y extends V{statusCode;apiError;constructor(k,D,F){super(k,X(D>=100&&D<600?D:400),{statusCode:D,provider:"taqnyat-whatsapp",cause:F});this.statusCode=D;this.apiError=F;this.name="TaqnyatWhatsAppError"}}function Z(k){return"template"in k}function _(k){let D=k.statuses;if(Array.isArray(D)){let F=D[0];if(typeof F?.message_id==="string")return F.message_id}else if(D&&typeof D==="object"){let F=D.message_id;if(typeof F==="string")return F;for(let G of Object.values(D))if(G&&typeof G==="object"){let H=G.message_id;if(typeof H==="string")return H}}return}function $(k){return k.statuses==="PENDING"||typeof k.type==="string"&&(k.type==="template"||k.type==="text")&&k.message===void 0}function x(k){if(typeof k.statuses==="string")return k.statuses;if(typeof k.type==="string")return k.type;return"Message sent"}function J(k,D){let F=typeof D.reason==="string"?D.reason:typeof D.message==="string"?`Taqnyat WhatsApp error ${D.message}`:"Taqnyat WhatsApp API error",G=typeof D.message==="string"&&/^\d+$/.test(D.message)?Number(D.message):k.status;throw new Y(F,G||k.status,D)}function U(k){let D=K(k.to);return Z(k)?{to:D,type:"template",template:{name:k.template.name,language:{code:k.template.language},...k.template.components?{components:k.template.components}:{}}}:{to:D,type:"text",text:{body:k.text}}}function N(k){return(typeof k==="string"?[k]:[...k]).map(K)}class Q{provider="taqnyat-whatsapp";bearerToken;constructor(k){this.bearerToken=k.bearerToken}async send(k){return this.postMessage(U(k),k)}async sendWithFailover(k,D){let F=U(k);if(D.sms)F.sms={sender:D.sms.sender,campaign:D.sms.campaign,body:D.sms.body,...D.sms.sendEmailIfFail?{sendEmailIfFail:D.sms.sendEmailIfFail}:{}};if(D.mail)F.mail={from:D.mail.from,to:D.mail.to,campaign:D.mail.campaign,subject:D.mail.subject,msg:D.mail.msg,...D.mail.cc!==void 0?{cc:D.mail.cc}:{},...D.mail.sendEmailIfFail?{sendEmailIfFail:D.mail.sendEmailIfFail}:{}};return this.postMessage(F,k)}async listTemplates(){let k=await fetch("https://api.taqnyat.sa/wa/v2/templates/?limit=5000",{headers:{Authorization:`Bearer ${this.bearerToken}`,Accept:"application/json"}}),D=await k.json();if(!k.ok||!Array.isArray(D.waba_templates))J(k,D);return D.waba_templates.map((F)=>({name:String(F.name??""),language:String(F.language??""),status:String(F.status??""),...typeof F.category==="string"?{category:F.category}:{},...F.id!==void 0?{id:String(F.id)}:{}}))}async createTemplate(k){let D=await fetch("https://api.taqnyat.sa/wa/v2/templates/",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify({name:k.name,language:k.language,allow_category_change:k.allowCategoryChange??!0,category:k.category,components:k.components})}),F=await D.json(),G=typeof F.statuses==="string"?F.statuses:typeof F.status==="string"?F.status:void 0;if(!D.ok||F.message!==void 0&&G===void 0)J(D,F);return{...F.id!==void 0?{id:String(F.id)}:{},...typeof F.category==="string"?{category:F.category}:{},status:G??"PENDING",provider:"taqnyat-whatsapp"}}async deleteTemplate(k){let D=await fetch("https://api.taqnyat.sa/wa/v2/templates/",{method:"DELETE",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify({name:k.name,id:k.id})}),F=await D.json();if(!(D.ok&&(F.message==="201"||F.reason==="success"||F.message===void 0)))J(D,F);return{ok:!0,provider:"taqnyat-whatsapp"}}async optIn(k){return this.postOptProvision("POST",k)}async optOut(k){return this.postOptProvision("DELETE",k)}async verify(){return{ok:Boolean(this.bearerToken),provider:"taqnyat-whatsapp",message:this.bearerToken?"Credentials present":"Missing credentials"}}async postMessage(k,D){let F=await fetch("https://api.taqnyat.sa/wa/v2/messages/",{method:"POST",headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify(k)}),G=await F.json(),H=_(G),L=$(G);if(!F.ok||G.message!==void 0&&H===void 0&&!L)J(F,G);return{messageId:H??D.messageId??"",to:D.to,status:"accepted",response:x(G),provider:"taqnyat-whatsapp"}}async postOptProvision(k,D){let F=N(D),G=await fetch("https://api.taqnyat.sa/wa/v1/provision/optin/",{method:k,headers:{Authorization:`Bearer ${this.bearerToken}`,"Content-Type":"application/json"},body:JSON.stringify({numbers:F})}),H=await G.json();if(!(typeof H.type==="string"&&H.statuses!==void 0))J(G,H);return{ok:!0,numbers:F,provider:"taqnyat-whatsapp"}}}export{Q as TaqnyatWhatsAppTransport,Y as TaqnyatWhatsAppError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=C2A75FE9E50F4F9C64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/taqnyat-whatsapp.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
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"
|
|
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 * Vendor extras on this class: templates CRUD, opt-in/out, failover send.\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 transport = new TaqnyatWhatsAppTransport({\n * bearerToken: process.env.TAQNYAT_WHATSAPP_TOKEN!,\n * });\n * const wa = createWhatsAppSender({ transport });\n * await transport.optIn(\"+9665xxxxxxxx\");\n * await wa.send({ to: \"+9665xxxxxxxx\", template: { name: \"welcome\", language: \"ar\" } });\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/** One template row from {@link TaqnyatWhatsAppTransport.listTemplates}. */\nexport interface TaqnyatWhatsAppTemplate {\n /** Template name. */\n name: string;\n /** Language code (e.g. `ar`, `en`). */\n language: string;\n /** Status (`approved`, `pending`, …). */\n status: string;\n /** Category when present (`UTILITY`, `MARKETING`, …). */\n category?: string;\n /** Provider template id when present. */\n id?: string;\n}\n\n/** Options for {@link TaqnyatWhatsAppTransport.createTemplate}. */\nexport interface TaqnyatCreateTemplateOptions {\n /** Unique template name without spaces. */\n name: string;\n /** Language code. */\n language: string;\n /** Template category (`UTILITY`, `MARKETING`, `AUTHENTICATION`, …). */\n category: string;\n /** Component array as required by Taqnyat / Meta. */\n components: unknown[];\n /** Allow Meta to adjust category. Defaults to `true`. */\n allowCategoryChange?: boolean;\n}\n\n/** Email fallback nested under SMS/mail failover payloads. */\nexport interface TaqnyatFailoverEmailOnFail {\n from: string;\n to: string;\n}\n\n/** SMS failover branch for {@link TaqnyatWhatsAppTransport.sendWithFailover}. */\nexport interface TaqnyatWhatsAppSmsFailover {\n sender: string;\n campaign: string;\n body: string;\n sendEmailIfFail?: TaqnyatFailoverEmailOnFail;\n}\n\n/** Mail failover branch for {@link TaqnyatWhatsAppTransport.sendWithFailover}. */\nexport interface TaqnyatWhatsAppMailFailover {\n from: string;\n to: string;\n campaign: string;\n subject: string;\n msg: string;\n cc?: string;\n sendEmailIfFail?: TaqnyatFailoverEmailOnFail;\n}\n\n/** Failover channels for {@link TaqnyatWhatsAppTransport.sendWithFailover}. */\nexport interface TaqnyatWhatsAppFailover {\n sms?: TaqnyatWhatsAppSmsFailover;\n mail?: TaqnyatWhatsAppMailFailover;\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 const direct = (statuses as Record<string, unknown>).message_id;\n if (typeof direct === \"string\") return direct;\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/** Docs sometimes return `statuses: \"PENDING\"` (queued) with no message_id yet. */\nfunction isQueuedAcceptance(payload: Record<string, unknown>): boolean {\n return (\n payload.statuses === \"PENDING\" ||\n (typeof payload.type === \"string\" &&\n (payload.type === \"template\" || payload.type === \"text\") &&\n payload.message === undefined)\n );\n}\n\nfunction describeWhatsAppResponse(payload: Record<string, unknown>): string {\n if (typeof payload.statuses === \"string\") return payload.statuses;\n if (typeof payload.type === \"string\") return payload.type;\n return \"Message sent\";\n}\n\nfunction throwWhatsAppError(response: Response, payload: Record<string, unknown>): never {\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\nfunction buildMessageBody(options: WhatsAppOptions): Record<string, unknown> {\n const to = normalizeTaqnyatPhone(options.to);\n return 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\nfunction normalizeNumbers(to: string | readonly string[]): string[] {\n const list = typeof to === \"string\" ? [to] : [...to];\n return list.map(normalizeTaqnyatPhone);\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 return this.postMessage(buildMessageBody(options), options);\n }\n\n /**\n * Vendor extra: send with optional SMS / email failover branches\n * (same `POST /wa/v2/messages/` payload shape as Taqnyat failover docs).\n * Not part of {@link WhatsAppTransport}.\n */\n async sendWithFailover(\n options: WhatsAppOptions,\n failover: TaqnyatWhatsAppFailover,\n ): Promise<WhatsAppSendResult> {\n const body: Record<string, unknown> = buildMessageBody(options);\n if (failover.sms) {\n body.sms = {\n sender: failover.sms.sender,\n campaign: failover.sms.campaign,\n body: failover.sms.body,\n ...(failover.sms.sendEmailIfFail ? { sendEmailIfFail: failover.sms.sendEmailIfFail } : {}),\n };\n }\n if (failover.mail) {\n body.mail = {\n from: failover.mail.from,\n to: failover.mail.to,\n campaign: failover.mail.campaign,\n subject: failover.mail.subject,\n msg: failover.mail.msg,\n ...(failover.mail.cc !== undefined ? { cc: failover.mail.cc } : {}),\n ...(failover.mail.sendEmailIfFail\n ? { sendEmailIfFail: failover.mail.sendEmailIfFail }\n : {}),\n };\n }\n return this.postMessage(body, options);\n }\n\n /**\n * Vendor extra: list templates via `GET /wa/v2/templates/`.\n * Not part of {@link WhatsAppTransport}.\n */\n async listTemplates(): Promise<TaqnyatWhatsAppTemplate[]> {\n const response = await fetch(\"https://api.taqnyat.sa/wa/v2/templates/?limit=5000\", {\n headers: {\n Authorization: `Bearer ${this.bearerToken}`,\n Accept: \"application/json\",\n },\n });\n const payload = (await response.json()) as {\n waba_templates?: Array<Record<string, unknown>>;\n message?: string;\n reason?: string;\n };\n\n if (!response.ok || !Array.isArray(payload.waba_templates)) {\n throwWhatsAppError(response, payload as Record<string, unknown>);\n }\n\n return payload.waba_templates.map((row) => ({\n name: String(row.name ?? \"\"),\n language: String(row.language ?? \"\"),\n status: String(row.status ?? \"\"),\n ...(typeof row.category === \"string\" ? { category: row.category } : {}),\n ...(row.id !== undefined ? { id: String(row.id) } : {}),\n }));\n }\n\n /**\n * Vendor extra: create a template via `POST /wa/v2/templates/`.\n * Not part of {@link WhatsAppTransport}.\n */\n async createTemplate(\n options: TaqnyatCreateTemplateOptions,\n ): Promise<{ id?: string; category?: string; status: string; provider: \"taqnyat-whatsapp\" }> {\n const response = await fetch(\"https://api.taqnyat.sa/wa/v2/templates/\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.bearerToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n name: options.name,\n language: options.language,\n allow_category_change: options.allowCategoryChange ?? true,\n category: options.category,\n components: options.components,\n }),\n });\n const payload = (await response.json()) as Record<string, unknown>;\n const status =\n typeof payload.statuses === \"string\"\n ? payload.statuses\n : typeof payload.status === \"string\"\n ? payload.status\n : undefined;\n\n if (!response.ok || (payload.message !== undefined && status === undefined)) {\n throwWhatsAppError(response, payload);\n }\n\n return {\n ...(payload.id !== undefined ? { id: String(payload.id) } : {}),\n ...(typeof payload.category === \"string\" ? { category: payload.category } : {}),\n status: status ?? \"PENDING\",\n provider: \"taqnyat-whatsapp\",\n };\n }\n\n /**\n * Vendor extra: delete a template via `DELETE /wa/v2/templates/`.\n * Not part of {@link WhatsAppTransport}.\n */\n async deleteTemplate(options: {\n name: string;\n id: string | number;\n }): Promise<{ ok: true; provider: \"taqnyat-whatsapp\" }> {\n const response = await fetch(\"https://api.taqnyat.sa/wa/v2/templates/\", {\n method: \"DELETE\",\n headers: {\n Authorization: `Bearer ${this.bearerToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ name: options.name, id: options.id }),\n });\n const payload = (await response.json()) as Record<string, unknown>;\n const ok =\n response.ok &&\n (payload.message === \"201\" || payload.reason === \"success\" || payload.message === undefined);\n\n if (!ok) {\n throwWhatsAppError(response, payload);\n }\n\n return { ok: true, provider: \"taqnyat-whatsapp\" };\n }\n\n /**\n * Vendor extra: record WhatsApp opt-in via `POST /wa/v1/provision/optin/`.\n * Not part of {@link WhatsAppTransport}.\n */\n async optIn(\n to: string | readonly string[],\n ): Promise<{ ok: true; numbers: string[]; provider: \"taqnyat-whatsapp\" }> {\n return this.postOptProvision(\"POST\", to);\n }\n\n /**\n * Vendor extra: record WhatsApp opt-out via `DELETE /wa/v1/provision/optin/`.\n * Not part of {@link WhatsAppTransport}.\n */\n async optOut(\n to: string | readonly string[],\n ): Promise<{ ok: true; numbers: string[]; provider: \"taqnyat-whatsapp\" }> {\n return this.postOptProvision(\"DELETE\", to);\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 private async postMessage(\n body: Record<string, unknown>,\n options: WhatsAppOptions,\n ): Promise<WhatsAppSendResult> {\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 const queued = isQueuedAcceptance(payload);\n\n if (!response.ok || (payload.message !== undefined && messageId === undefined && !queued)) {\n throwWhatsAppError(response, payload);\n }\n\n return {\n messageId: messageId ?? options.messageId ?? \"\",\n to: options.to,\n status: \"accepted\",\n response: describeWhatsAppResponse(payload),\n provider: \"taqnyat-whatsapp\",\n };\n }\n\n private async postOptProvision(\n method: \"POST\" | \"DELETE\",\n to: string | readonly string[],\n ): Promise<{ ok: true; numbers: string[]; provider: \"taqnyat-whatsapp\" }> {\n const numbers = normalizeNumbers(to);\n const response = await fetch(\"https://api.taqnyat.sa/wa/v1/provision/optin/\", {\n method,\n headers: {\n Authorization: `Bearer ${this.bearerToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ numbers }),\n });\n const payload = (await response.json()) as Record<string, unknown>;\n // Success: `{ type: \"Opt-In\"|\"Opt-Out\", statuses: [...] }`. Errors use `message` + `reason`.\n const looksOk = typeof payload.type === \"string\" && payload.statuses !== undefined;\n if (!looksOk) {\n throwWhatsAppError(response, payload);\n }\n\n return { ok: true, numbers, provider: \"taqnyat-whatsapp\" };\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "C2A75FE9E50F4F9C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -22,6 +22,13 @@ export interface WebPushConfig {
|
|
|
22
22
|
*/
|
|
23
23
|
allowedEndpointHosts?: string[];
|
|
24
24
|
}
|
|
25
|
+
/** Generated VAPID key pair in the common web-push raw format. */
|
|
26
|
+
export interface VapidKeys {
|
|
27
|
+
/** Base64url-encoded uncompressed P-256 public key (65 bytes). */
|
|
28
|
+
publicKey: string;
|
|
29
|
+
/** Base64url-encoded raw P-256 private key (32 bytes) — treat as a secret. */
|
|
30
|
+
privateKey: string;
|
|
31
|
+
}
|
|
25
32
|
/** Error thrown when a push service rejects the request. */
|
|
26
33
|
export declare class WebPushError extends SentlyError {
|
|
27
34
|
readonly statusCode: number;
|
|
@@ -29,6 +36,18 @@ export declare class WebPushError extends SentlyError {
|
|
|
29
36
|
/** Creates a Web Push error with HTTP status and response body. */
|
|
30
37
|
constructor(message: string, statusCode: number, apiError: unknown);
|
|
31
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Generate a VAPID key pair in the common web-push raw format
|
|
41
|
+
* (base64url public + base64url private `d`).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { generateVapidKeys } from "sently/transports/webpush";
|
|
46
|
+
*
|
|
47
|
+
* const { publicKey, privateKey } = await generateVapidKeys();
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function generateVapidKeys(): Promise<VapidKeys>;
|
|
32
51
|
/**
|
|
33
52
|
* Web Push transport — VAPID auth + RFC 8291 payload encryption.
|
|
34
53
|
*
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{V as
|
|
1
|
+
import{V as w}from"../chunk-8kpgbrba.js";import{W as T}from"../chunk-fv0bemgf.js";import{_ as U}from"../chunk-z1589fjk.js";import{ga as V,ha as x,ia as H}from"../chunk-hnzmn4s4.js";import{ja as A,ka as S}from"../chunk-ttbwyxmh.js";import"../chunk-th4cwrpb.js";var g=new Set(["very-low","low","normal","high"]),B=/^[\x21-\x7E]{1,32}$/;class F extends A{statusCode;apiError;constructor(q,z,Q){super(q,S(z),{statusCode:z,provider:"webpush",cause:Q});this.statusCode=z;this.apiError=Q;this.name="WebPushError"}}var W=2419200,f=4096,_=4096;function E(q){if(q.startsWith("mailto:")){let z=q.slice(7);if(w(z))return}else if(q.startsWith("https:"))try{let z=new URL(q);if(z.protocol==="https:"&&z.hostname.length>0)return}catch{}throw new F(`WebPushConfig.subject must be a mailto: address or https: URL identifying you to push services, got: ${JSON.stringify(q)}`,400,{field:"subject",value:q})}async function s(){let q=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),z=new Uint8Array(await crypto.subtle.exportKey("raw",q.publicKey)),Q=await crypto.subtle.exportKey("jwk",q.privateKey);if(typeof Q.d!=="string"||Q.d.length===0)throw new F("Failed to export VAPID private key",500,{field:"privateKey"});return{publicKey:V(z),privateKey:Q.d}}function P(q){if(!g.has(q))throw new F(`Web Push urgency must be very-low, low, normal, or high, got: ${JSON.stringify(q)}`,400,{field:"urgency",value:q})}function h(q){if(!B.test(q))throw new F(`Web Push topic must be 1–32 printable ASCII characters, got: ${JSON.stringify(q)}`,400,{field:"topic",value:q})}function l(q){if(q.silent){if(q.data===void 0)throw new F("silent Web Push requires data",400,{field:"data"});return{data:q.data}}let z=typeof q.title==="string"&&q.title.length>0,Q=typeof q.body==="string"&&q.body.length>0;if(!z&&!Q){if(q.data===void 0)throw new F("Web Push requires title and body, or data for a data-only / silent send",400,{fields:["title","body","data"]});return{data:q.data}}if(!z||!Q)throw new F("Visible Web Push notifications require both title and body",400,{fields:["title","body"]});return{title:q.title,body:q.body,...q.data!==void 0?{data:q.data}:{},...q.icon!==void 0?{icon:q.icon}:{},...q.badge!==void 0?{badge:q.badge}:{},...q.image!==void 0?{image:q.image}:{},...q.tag!==void 0?{tag:q.tag}:{},...q.actions!==void 0?{actions:q.actions}:{},...q.requireInteraction!==void 0?{requireInteraction:q.requireInteraction}:{},...q.renotify!==void 0?{renotify:q.renotify}:{}}}function L(q){return q.buffer.slice(q.byteOffset,q.byteOffset+q.byteLength)}function D(...q){let z=q.reduce((G,M)=>G+M.length,0),Q=new Uint8Array(z),$=0;for(let G of q)Q.set(G,$),$+=G.length;return Q}function y(q){let z=new Uint8Array(4);return new DataView(z.buffer).setUint32(0,q,!1),z}function d(q){if(q.length!==65||q[0]!==4)throw new F("Invalid P-256 uncompressed public key",400,{length:q.length});return{kty:"EC",crv:"P-256",x:V(q.subarray(1,33)),y:V(q.subarray(33,65))}}function b(q,z){return{...d(q),d:V(z)}}async function m(q,z){let Q=await crypto.subtle.importKey("raw",L(q),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),$=await crypto.subtle.sign("HMAC",Q,L(z));return new Uint8Array($)}async function v(q,z,Q,$){let G=await m(q,z);return(await m(G,D(Q,new Uint8Array([1])))).subarray(0,$)}async function u(q,z){let Q=x(q.keys.p256dh),$=x(q.keys.auth);if(Q.length!==65||Q[0]!==4)throw new F("Invalid subscription p256dh key",400,{length:Q.length});if($.length<16)throw new F("Invalid subscription auth key",400,{length:$.length});let G=await crypto.subtle.generateKey({name:"ECDH",namedCurve:"P-256"},!0,["deriveBits"]),M=new Uint8Array(await crypto.subtle.exportKey("raw",G.publicKey)),N=await crypto.subtle.importKey("raw",L(Q),{name:"ECDH",namedCurve:"P-256"},!1,[]),Z=new Uint8Array(await crypto.subtle.deriveBits({name:"ECDH",public:N},G.privateKey,256)),X=D(H("WebPush: info\x00"),Q,M),J=await v($,Z,X,32),O=crypto.getRandomValues(new Uint8Array(16)),I=H("Content-Encoding: aes128gcm\x00"),R=H("Content-Encoding: nonce\x00"),Y=await v(O,J,I,16),C=await v(O,J,R,12),j=D(z,new Uint8Array([2])),k=await crypto.subtle.importKey("raw",L(Y),"AES-GCM",!1,["encrypt"]),K=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:L(C)},k,L(j)));return D(O,y(f),new Uint8Array([M.length]),M,K)}async function a(q,z,Q,$){let G=x(Q),M=x($),N=b(G,M),Z=await crypto.subtle.importKey("jwk",N,{name:"ECDSA",namedCurve:"P-256"},!1,["sign"]),X=new URL(q).origin,J={typ:"JWT",alg:"ES256"},O={aud:X,exp:Math.floor(Date.now()/1000)+43200,sub:z},I=V(H(JSON.stringify(J))),R=V(H(JSON.stringify(O))),Y=`${I}.${R}`,C=new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},Z,L(H(Y))));return`${Y}.${V(C)}`}class c{provider="webpush";vapidPublicKey;vapidPrivateKey;subject;allowedEndpointHosts;constructor(q){E(q.subject),this.vapidPublicKey=q.vapidPublicKey,this.vapidPrivateKey=q.vapidPrivateKey,this.subject=q.subject,this.allowedEndpointHosts=q.allowedEndpointHosts??[]}async send(q){if(!U(q))throw new F("WebPushTransport requires PushOptions.subscription; use FcmTransport for device tokens",400,{hint:"fcm"});try{T(q.subscription.endpoint,this.allowedEndpointHosts)}catch(X){let J;try{J=new URL(q.subscription.endpoint).hostname}catch{J=void 0}throw new F(X instanceof Error?X.message:"Unsafe push subscription endpoint",400,{endpointHost:J})}if(q.urgency!==void 0)P(q.urgency);if(q.topic!==void 0)h(q.topic);let z=l(q),Q=H(JSON.stringify(z)),$=await u(q.subscription,Q);if($.length>_)throw new F(`Encrypted push payload exceeds ${_} bytes (RFC 8291)`,413,{size:$.length});let G=await a(q.subscription.endpoint,this.subject,this.vapidPublicKey,this.vapidPrivateKey),M=q.ttl??W,N={Authorization:`vapid t=${G}, k=${this.vapidPublicKey}`,"Content-Encoding":"aes128gcm",TTL:String(M),"Content-Type":"application/octet-stream"};if(q.urgency!==void 0)N.Urgency=q.urgency;if(q.topic!==void 0)N.Topic=q.topic;let Z=await fetch(q.subscription.endpoint,{method:"POST",redirect:"manual",headers:N,body:L($)});if(Z.status>=300&&Z.status<400)throw new F(`Push service returned redirect ${Z.status}; redirects are not followed`,Z.status,{location:Z.headers.get("location")});if(!Z.ok){let X=await Z.text().catch(()=>"");throw new F(X||`Web Push request failed with status ${Z.status}`,Z.status,X||{status:Z.status})}return{messageId:q.messageId??crypto.randomUUID(),status:"accepted",response:String(Z.status),provider:"webpush"}}async verify(){try{let q=x(this.vapidPublicKey),z=x(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{s as generateVapidKeys,c as WebPushTransport,F as WebPushError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=4FE1E8A522321CE464756E2164756E21
|