@apostlejs/whatsapp 0.0.20 → 0.1.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/dist/index.cjs CHANGED
@@ -1 +1,791 @@
1
- "use strict";function e(e){return e&&e.__esModule?e:{default:e}}var t=e(require("node:crypto")),a=Object.defineProperty,r=(e,t)=>{for(var r in t)a(e,r,{get:t[r],enumerable:!0})},n={};r(n,{WhatsappErrorCode:()=>d,endpoints:()=>l,flowsEndpoints:()=>s,mediaEndpoints:()=>c,messagesEndpoints:()=>o,wabaEndpoints:()=>i});var s={create:{url:"/{waba_id}/flows",method:"post",headers:{"Content-Type":"application/json"},request:{body:null},response:null},updateMetadata:{url:"/{flow_id}",method:"post",headers:{"Content-Type":"application/json"},request:{body:null},response:null},readMany:{url:"/{waba_id}/flows",method:"get",response:null},delete:{url:"/{flow_id}",method:"delete",response:null},read:{url:"/{flow_id}",method:"get",request:{query:null},response:null},updateJson:{url:"/{flow_id}/assets",method:"post",request:{body:null},response:null},getPreview:{url:"/{flow_id}?fields=preview.invalidate(false)",method:"get",response:null},publish:{url:"/{flow_id}/publish",method:"post",response:null}},o={send:{url:"/{number_id}/messages",method:"post",headers:{"Content-Type":"application/json"},request:{body:null},response:null},showTypingIndicator:{url:"/{number_id}/messages",method:"post",headers:{"Content-Type":"application/json"},request:{body:null},response:null}},i={updateEncryption:{url:"/{number_id}/whatsapp_business_encryption",method:"post",headers:{"Content-Type":"application/x-www-form-urlencoded"},request:{body:null}},registerNumber:{url:"/{number_id}/register",method:"post",headers:{"Content-Type":"application/json"},request:{body:null}}},c={fetch:{url:"/{media_id}",method:"get",request:{query:null},response:null}},l={flows:s,messages:o,waba:i,media:c},d=(e=>(e[e.e0AuthException=0]="e0AuthException",e[e.e3ApiMethod=3]="e3ApiMethod",e[e.e10PermissionDenied=10]="e10PermissionDenied",e[e.e190AccessTokenExpired=190]="e190AccessTokenExpired",e[e.e2xxApiPermission=200]="e2xxApiPermission",e[e.e368TemporarilyBlockedForPolicyViolations=368]="e368TemporarilyBlockedForPolicyViolations",e[e.e130497BusinessAccountRestrictedByCountry=130497]="e130497BusinessAccountRestrictedByCountry",e[e.e131031AccountLocked=131031]="e131031AccountLocked",e[e.e1ApiUnknown=1]="e1ApiUnknown",e[e.e2ApiService=2]="e2ApiService",e[e.e33ParameterValueNotValid=33]="e33ParameterValueNotValid",e[e.e100InvalidParameter=100]="e100InvalidParameter",e[e.e130472UserNumberPartOfExperiment=130472]="e130472UserNumberPartOfExperiment",e[e.e131000SomethingWentWrong=131e3]="e131000SomethingWentWrong",e[e.e131005AccessDenied=131005]="e131005AccessDenied",e[e.e131008RequiredParameterMissing=131008]="e131008RequiredParameterMissing",e[e.e131009ParameterValueNotValid=131009]="e131009ParameterValueNotValid",e[e.e131016ServiceUnavailable=131016]="e131016ServiceUnavailable",e[e.e131021RecipientCannotBeSender=131021]="e131021RecipientCannotBeSender",e[e.e131026MessageUndeliverable=131026]="e131026MessageUndeliverable",e[e.e131030RecipientNotAllowedInList=131030]="e131030RecipientNotAllowedInList",e[e.e131037DisplayNameApprovalNeeded=131037]="e131037DisplayNameApprovalNeeded",e[e.e131042BusinessEligibilityPaymentIssue=131042]="e131042BusinessEligibilityPaymentIssue",e[e.e131045IncorrectCertificate=131045]="e131045IncorrectCertificate",e[e.e131047ReEngagementMessage=131047]="e131047ReEngagementMessage",e[e.e131049MetaChoseNotToDeliver=131049]="e131049MetaChoseNotToDeliver",e[e.e131050UserStoppedMarketingMessages=131050]="e131050UserStoppedMarketingMessages",e[e.e131051UnsupportedMessageType=131051]="e131051UnsupportedMessageType",e[e.e131052MediaDownloadError=131052]="e131052MediaDownloadError",e[e.e131053MediaUploadError=131053]="e131053MediaUploadError",e[e.e131057AccountInMaintenanceMode=131057]="e131057AccountInMaintenanceMode",e[e.e132000TemplateParamCountMismatch=132e3]="e132000TemplateParamCountMismatch",e[e.e132001TemplateDoesNotExist=132001]="e132001TemplateDoesNotExist",e[e.e132005TemplateHydratedTextTooLong=132005]="e132005TemplateHydratedTextTooLong",e[e.e132007TemplateFormatCharacterPolicyViolated=132007]="e132007TemplateFormatCharacterPolicyViolated",e[e.e132012TemplateParameterFormatMismatch=132012]="e132012TemplateParameterFormatMismatch",e[e.e132015TemplateIsPaused=132015]="e132015TemplateIsPaused",e[e.e132016TemplateIsDisabled=132016]="e132016TemplateIsDisabled",e[e.e132068FlowIsBlocked=132068]="e132068FlowIsBlocked",e[e.e132069FlowIsThrottled=132069]="e132069FlowIsThrottled",e[e.e133000IncompleteDeregistration=133e3]="e133000IncompleteDeregistration",e[e.e133004ServerTemporarilyUnavailable=133004]="e133004ServerTemporarilyUnavailable",e[e.e133005TwoStepVerificationPinMismatch=133005]="e133005TwoStepVerificationPinMismatch",e[e.e133006PhoneNumberReVerificationNeeded=133006]="e133006PhoneNumberReVerificationNeeded",e[e.e133008TooManyTwoStepVerificationPinGuesses=133008]="e133008TooManyTwoStepVerificationPinGuesses",e[e.e133009TwoStepVerificationPinGuessedTooFast=133009]="e133009TwoStepVerificationPinGuessedTooFast",e[e.e133010PhoneNumberNotRegistered=133010]="e133010PhoneNumberNotRegistered",e[e.e133015PhoneNumberDeletionPending=133015]="e133015PhoneNumberDeletionPending",e[e.e134011PaymentsTermsNotAccepted=134011]="e134011PaymentsTermsNotAccepted",e[e.e135000GenericUserError=135e3]="e135000GenericUserError",e[e.e2593107SynchronizationRequestLimitExceeded=2593107]="e2593107SynchronizationRequestLimitExceeded",e[e.e2593108SynchronizationRequestOutsideTimeWindow=2593108]="e2593108SynchronizationRequestOutsideTimeWindow",e[e.e4ApiTooManyCalls=4]="e4ApiTooManyCalls",e[e.e80007RateLimitIssues=80007]="e80007RateLimitIssues",e[e.e130429RateLimitHit=130429]="e130429RateLimitHit",e[e.e131048SpamRateLimitHit=131048]="e131048SpamRateLimitHit",e[e.e131056BusinessConsumerPairRateLimitHit=131056]="e131056BusinessConsumerPairRateLimitHit",e[e.e133016AccountRegisterDeregisterRateLimitExceeded=133016]="e133016AccountRegisterDeregisterRateLimitExceeded",e))(d||{}),p={};r(p,{actions:()=>D,createContactUrl:()=>k,flows:()=>A,parsers:()=>L,security:()=>O,toGraphLanguageTag:()=>R,utils:()=>K});var u={};r(u,{create:()=>h,delete:()=>b,get:()=>v,getMany:()=>T,getPreview:()=>E,publish:()=>P,updateJson:()=>w,updateMetadata:()=>g});var m={};r(m,{settings:()=>y});var f={},y={setup:e=>{Object.assign(f,e)},get:e=>{const t=f[e]??process.env[e];if(!t)throw new Error(`Missing environment variable: ${e}`);return t}};async function _(e,t,a){const r=`https://graph.facebook.com/v${y.get("GRAPH_API_VERSION")}`,n={Authorization:`Bearer ${y.get("META_APP_ACCESS_TOKEN")}`};let s=e.url;const o={...n,...e.headers,...t.headers},i=Object.fromEntries(Object.entries(t.query??{}).filter((([,e])=>null!=e))),c=new URLSearchParams(i).toString();"/"===s[0]&&(s=s.slice(1)),s=`${r}/${s}`,c&&(s=`${s}?${c}`);const l={waba_id:y.get("WHATSAPP_ACCOUNT_ID"),number_id:y.get("WHATSAPP_NUMBER_ID"),...t.params};for(const e in l)s=s.replace(`{${e}}`,l[e]);const d=t.body;let p;if(d&&a?.asFormData){const e=new FormData;for(const t in d)e.append(t,d[t]);p=e}else d&&a?.asUrlEncoded?p=new URLSearchParams(d).toString():d&&(p=JSON.stringify(d));return await fetch(s,{method:e.method,headers:o,body:p}).then((e=>e.json())).then((e=>{if("error"in e)throw{response:{data:e.error}};return e})).catch((async t=>{if(!t.response)throw new Error(t.message,{cause:{method:e.method,url:s,body:p}});const a=t.response.data.message,r=t.response.data.error_data?.details||"",n=a.match(/^\(#\d+\)/),o=n?n[0]:"",i=a.replace(/^\(#\d+\)\s*/,""),c=r?r?.startsWith?.(i)?`${o} ${r}`:`${a}: ${r}`:a;t.response.data.message=c;const{message:l,...d}=t.response.data;throw new Error(l,{cause:d})}))}async function h(e){const t=l.flows.create,a=e.flow_json?JSON.stringify(e.flow_json,null,2):void 0;return await _(t,{body:{name:e.name,categories:e.categories,clone_flow_id:e.clone_flow_id,endpoint_uri:e.endpoint_uri,publish:e.publish,flow_json:a}})}async function g(e,t){const a=l.flows.updateMetadata;return await _(a,{body:t,params:{flow_id:e}})}async function w(e,t){const a=l.flows.updateJson,r={name:"flow.json",asset_type:"FLOW_JSON",file:new Blob([JSON.stringify(t,null,2)],{type:"application/json"})};return await _(a,{params:{flow_id:e},body:r},{asFormData:!0}).catch((e=>{const t=e?.error_user_msg??e?.cause?.error_user_msg;if(t?.startsWith?.("Flow JSON has been saved"))return{success:!0,validation_errors:[]};throw e}))}async function E(e,t){const a=l.flows.getPreview,{preview:r}=await _(a,{params:{flow_id:e}}),n=r.preview_url.replaceAll("\\",""),s=Object.entries(t??{}).reduce(((e,[t,a])=>(e[t]="flow_action_payload"===t?encodeURIComponent(JSON.stringify(a)):a.toString(),e)),{});return`${n}&${new URLSearchParams(s).toString()}`}async function b(e){const t=l.flows.delete;return await _(t,{params:{flow_id:e}})}async function v(e,t){const a=l.flows.read;return await _(a,{params:{flow_id:e},query:{fields:t?.fields.join(",")??""}})}async function T(){const e=l.flows.readMany;return await _(e,{})}async function P(e){const t=l.flows.publish;return await _(t,{params:{flow_id:e}})}var S=e=>e.split("?")[0],A={createToken:({chatId:e,flow_name:t,flow_parameters:a,flow_identifier:r})=>{const n=new URLSearchParams({chat_id:e});if(n.set("flow_identifier",r??crypto.randomUUID()),a)for(const[e,t]of Object.entries(a))n.set(e,t.toString());return`${t}?${decodeURIComponent(n.toString())}`},getName:S,destructureFlowToken:e=>{const t=S(e);let a=e.split("?")?.[1];"&"===a?.[0]&&(a=a.slice(1));const r=new URLSearchParams(a),n=r.get("chat_id")||r.get("chatId"),s=r.get("flow_identifier")||r.get("flowIdentifier"),o={};for(const[e,t]of r.entries())"chat_id"!==e&&"chatId"!==e&&"flow_identifier"!==e&&"flowIdentifier"!==e&&(o[e]=t);return{paramsString:a,flowName:t,chatId:n,flowIdentifier:s,flowParameters:o}}},N=(e=>(e.AR_SA="ar-SA",e.BN_BD="bn-BD",e.BN_IN="bn-IN",e.CS_CZ="cs-CZ",e.DA_DK="da-DK",e.DE_AT="de-AT",e.DE_CH="de-CH",e.DE_DE="de-DE",e.EL_GR="el-GR",e.EN_AU="en-AU",e.EN_CA="en-CA",e.EN_GB="en-GB",e.EN_IE="en-IE",e.EN_IN="en-IN",e.EN_NZ="en-NZ",e.EN_US="en-US",e.EN_ZA="en-ZA",e.ES_AR="es-AR",e.ES_CL="es-CL",e.ES_CO="es-CO",e.ES_ES="es-ES",e.ES_MX="es-MX",e.ES_US="es-US",e.FI_FI="fi-FI",e.FR_BE="fr-BE",e.FR_CA="fr-CA",e.FR_CH="fr-CH",e.FR_FR="fr-FR",e.HE_IL="he-IL",e.HI_IN="hi-IN",e.HU_HU="hu-HU",e.ID_ID="id-ID",e.IT_CH="it-CH",e.IT_IT="it-IT",e.JA_JP="ja-JP",e.KO_KR="ko-KR",e.NL_BE="nl-BE",e.NL_NL="nl-NL",e.NO_NO="no-NO",e.PL_PL="pl-PL",e.PT_BR="pt-BR",e.PT_PT="pt-PT",e.RO_RO="ro-RO",e.RU_RU="ru-RU",e.SK_SK="sk-SK",e.SV_SE="sv-SE",e.TA_IN="ta-IN",e.TA_LK="ta-LK",e.TH_TH="th-TH",e.TR_TR="tr-TR",e.ZH_CN="zh-CN",e.ZH_HK="zh-HK",e.ZH_TW="zh-TW",e))(N||{}),I={"ar-SA":"ar","bn-BD":"bn","bn-IN":"bn","cs-CZ":"cs","da-DK":"da","de-AT":"de","de-CH":"de","de-DE":"de","el-GR":"el","en-AU":"en","en-CA":"en","en-GB":"en_GB","en-IE":"en","en-IN":"en","en-NZ":"en","en-ZA":"en","en-US":"en_US","es-AR":"es_AR","es-CL":"es","es-CO":"es","es-ES":"es_ES","es-MX":"es_MX","es-US":"es","fi-FI":"fi","fr-BE":"fr","fr-CA":"fr","fr-CH":"fr","fr-FR":"fr","he-IL":"he","hi-IN":"hi","hu-HU":"hu","id-ID":"id","it-CH":"it","it-IT":"it","ja-JP":"ja","ko-KR":"ko","nl-BE":"nl","nl-NL":"nl","no-NO":"nb","pl-PL":"pl","pt-PT":"pt_PT","pt-BR":"pt_BR","ro-RO":"ro","ru-RU":"ru","sk-SK":"sk","sv-SE":"sv","ta-IN":"ta","ta-LK":"ta","th-TH":"th","tr-TR":"tr","zh-CN":"zh_CN","zh-HK":"zh_HK","zh-TW":"zh_TW"},R=e=>{const t=I[e];return void 0===t?"en":t},C=e=>{const t={to:e.to,messaging_product:"whatsapp",recipient_type:"individual"};return e.reply&&(t.context={message_id:e.reply}),e.showUrlPreviewImage&&(t.preview_url=e.showUrlPreviewImage),t},U={flow:e=>{if("flow"!==e.message.type)throw new Error("Invalid type");const{message:t,...a}=e,{flow:r,...n}=t,s=C(a);r.mode||(r.mode=y.get("WHATSAPP_FLOWS_MODE"));const o=A.createToken({chatId:s.to,flow_name:r.name,flow_parameters:r.parameters,flow_identifier:r.identifier});return{type:"interactive",...s,interactive:{...n,type:"flow",action:{name:"flow",parameters:{flow_name:r.name,flow_token:o,flow_message_version:"3",flow_cta:r.button,flow_action:r.action??"navigate",mode:r.mode,...r.payload&&{flow_action_payload:{...r.payload,screen:r.payload.screen}}}}}}},text:e=>{if("text"!==e.message.type)throw new Error("Invalid type");const{message:{text:t},...a}=e,r={type:"text",text:{body:t},...C(a)};return e.message.previewUrl&&r.text&&(r.text.preview_url=e.message.previewUrl),r},list:e=>{if("list"!==e.message.type)throw new Error("Invalid type");const{message:t,...a}=e,{list:r,type:n,...s}=t;return{...C(a),type:"interactive",interactive:{...s,type:n,action:r}}},button:e=>{if("button"!==e.message.type)throw new Error("Invalid type");const{message:t,...a}=e,{buttons:r,type:n,...s}=t;return{...C(a),type:"interactive",interactive:{...s,type:n,action:{buttons:r.map((e=>({reply:{id:e.id,title:e.text},type:"reply"})))}}}},template:e=>{if("template"!==e.message.type)throw new Error("Invalid type");const{message:{type:t,...a},...r}=e,n={...C(r),type:t,template:{...a,language:{code:R(a.language)}}};return n.template&&(n.template.namespace=process.env.WHATSAPP_MESSAGE_NAMESPACE),n},media:e=>{if("media"!==e.message.type)throw new Error("Invalid type");const{message:{type:t,...a},...r}=e,n=Object.entries(a)[0],[s,{ref:o,...i}]=n,c=i,l=C(r);return Number.isNaN(Number(o))?c.link=o:c.id=o,{type:s,[s]:c,...l}},contact:e=>{if("contact"!==e.message.type)throw new Error("Invalid type");const{message:{contacts:t},...a}=e;return{type:"contacts",contacts:t,...C(a)}}};function B(e){const{encrypted_aes_key:a,encrypted_flow_data:r,initial_vector:n}=e,s=t.default.createPrivateKey({key:y.get("WHATSAPP_ACCOUNT_ENCRYPTION_PRIVATE_KEY"),passphrase:y.get("WHATSAPP_ACCOUNT_ENCRYPTION_PASSPHRASE")}),o=t.default.privateDecrypt({key:s,padding:t.default.constants.RSA_PKCS1_OAEP_PADDING,oaepHash:"sha256"},Buffer.from(a,"base64")),i=Buffer.from(r,"base64"),c=Buffer.from(n,"base64"),l=i.subarray(0,-16),d=i.subarray(-16),p=t.default.createDecipheriv("aes-128-gcm",o,c);p.setAuthTag(d);const u=Buffer.concat([p.update(l),p.final()]).toString("utf-8");return{payload:JSON.parse(u),encryptionMetadata:{aesKeyBuffer:o,initialVectorBuffer:c}}}function M(e){const t=new URL(e.url).searchParams,a=t.get("hub.verify_token"),r=t.get("hub.challenge");return!(!a||!r)&&a===y.get("WHATSAPP_WEBHOOK_KEY")&&r}async function x(e,a){const r=e.headers.get("x-hub-signature-256");if(!r)return!1;const n=r.replace("sha256=",""),s=t.default.createHmac("sha256",y.get("WHATSAPP_WEBHOOK_KEY")).update(a,"utf-8").digest("hex");return!!t.default.timingSafeEqual(Buffer.from(n),Buffer.from(s))}var L={toGraph:{sendMessage:e=>U[e.message.type](e),flowResponse:(e,t)=>{if(!t.data)throw new Error("Missing data in flow response");if(!t.screen)throw new Error("Missing screen in flow response");if("SUCCESS"===t.screen){const a=t.data??{};t.data={extension_message_response:{params:{flow_token:e,...a}}}}return t}},toSDK:{webhook:async function(e){if("GET"===e.method){const t=M(e);if(!t)throw new Error("Invalid health check hub");return{type:"healthCheck",payload:t}}const t=await e.text(),a=JSON.parse(t);if("entry"in a){if(!x(e,t))throw new Error("Invalid application webhook signature");const n=a.entry.flatMap((e=>e.changes)).filter((e=>"messages"===e.field)).flatMap((e=>e.value.messages)).filter(Boolean).map((e=>function(e){const t=e.from,a={id:e.id,type:e.type,timestamp:e.timestamp,metadata:{forwarded:e.context?.forwarded,frequentlyForwarded:e.context?.frequently_forwarded}},r=function(e){if(!(e.audio||e.document||e.video||e.image))return null;const t=e.audio?.id??e.document?.id??e.image?.id??e.video?.id,a=e.audio?.mime_type??e.document?.mime_type??e.image?.mime_type??e.video?.mime_type,r=["audio","document","image","video"].find((t=>e[t])),n=e.image?.sha256??e.document?.sha256??e.video?.sha256;return{id:t,type:r,caption:e.document?.caption??e.image?.caption??e.video?.caption??null,mime_type:a,sha256:n,filename:e.document?.filename??e.video?.filename}}(e),n=function(e){return e.text?.body??e.button?.text??e.interactive?.button_reply?.title??e.interactive?.list_reply?.title??e.document?.caption??null}(e),s=function(e){return e.interactive?{...e.interactive?.button_reply||e.button?{button:{id:e.interactive?.button_reply?.id??null,title:e.button?.text??e.interactive.button_reply?.title,payload:e.button?.payload??null}}:{},...e.interactive.list_reply?{selectedOption:{id:e.interactive.list_reply.id,title:e.interactive.list_reply.title,description:e.interactive.list_reply.description}}:{},...e.interactive.nfm_reply?{flowResponse:JSON.parse(e.interactive.nfm_reply.response_json)}:{}}:null}(e);return{...a,...s&&{interaction:s},...r&&{media:r},text:n,chatId:t}}(e)));return{type:"application",payload:{messageReceived:(r=n,r.length?r:void 0)}}}var r;if("encrypted_flow_data"in a)return{type:"flowExchange",payload:{...B(a),pingResponse:{data:{status:"active"}}}};throw new Error("Unrecognized event")}}},D={messages:{send:async function(e){const t=L.toGraph.sendMessage(e),a=l.messages.send;return await _(a,{body:t})},showTypingIndicator:async function(e){const t=l.messages.showTypingIndicator;return await _(t,{body:{messaging_product:"whatsapp",status:"read",typing_indicator:{type:"text"},message_id:e.messageId}})}},media:{retrieve:async function({id:e}){const t=l.media.fetch;return await _(t,{query:{phone_number_id:y.get("WHATSAPP_NUMBER_ID")},params:{media_id:e}})}},flows:{...u},waba:{registerNumber:async function({dataRegion:e,pin:t}){const a=l.waba.registerNumber,r={messaging_product:"whatsapp",pin:t};return e&&(r.data_localization_region=e),await _(a,{body:r},{asUrlEncoded:!0})},encryption:{upload:async function(e){const t=l.waba.updateEncryption,a={business_public_key:e};return await _(t,{body:a},{asUrlEncoded:!0})}}}};async function H(e){return await fetch(e).then((async e=>{const t=await e.arrayBuffer();return Buffer.from(t)}))}var O={verifyHub:M,verifySignature:x,generateWabaEncryption:async function(){const e=t.default.randomUUID(),{publicKey:a,privateKey:r}=t.default.generateKeyPairSync("rsa",{modulusLength:2048,publicKeyEncoding:{type:"spki",format:"pem"},privateKeyEncoding:{type:"pkcs8",format:"pem",cipher:"aes-256-cbc",passphrase:e}});return{passphrase:e,publicKey:a,privateKey:r}},decryptFlowBody:B,encryptFlowResponse:function(e,a){const r=[];for(const e of Buffer.from(a.initialVectorBuffer).entries())r.push(~e[1]);const n=t.default.createCipheriv("aes-128-gcm",Buffer.from(a.aesKeyBuffer),Buffer.from(r));return Buffer.concat([n.update(JSON.stringify(e),"utf-8"),n.final(),n.getAuthTag()]).toString("base64")},decryptFlowMedia:async function(e,a="https://picsum.photos/seed/picsum/200"){let{cdn_url:r,encryption_metadata:n}=e;if("EXAMPLE_DATA__CDN_URL_WILL_COME_IN_THIS_FIELD"===r)return r=a,await H(r);const s=await H(r),{iv:o,encryption_key:i,hmac_key:c,encrypted_hash:l,plaintext_hash:d}=n,p={iv:Buffer.from(o,"base64"),encryption_key:Buffer.from(i,"base64"),hmac_key:Buffer.from(c,"base64")};if(t.default.createHash("sha256").update(s).digest("base64")!==l)throw new Error("Encrypted hash validation failed");const u=s.subarray(0,s.length-10),m=s.subarray(-10);if(!t.default.createHmac("sha256",p.hmac_key).update(Buffer.concat([p.iv,u])).digest().subarray(0,10).equals(m))throw new Error("HMAC validation failed");const f=t.default.createDecipheriv("aes-256-cbc",p.encryption_key,p.iv),y=f.update(u),_=Buffer.concat([y,f.final()]);if(t.default.createHash("sha256").update(_).digest("base64")!==d)throw new Error("Decrypted media hash validation failed");return _}},k=(e,t)=>{const a=new URL("/send","https://api.whatsapp.com/"),r=e.replace(/[()\-+ ]/g,"");return a.searchParams.append("phone",r),t&&a.searchParams.append("text",t),a.href},K={flows:A,createContactUrl:k},F=null;exports.BCP47LanguageTag=N,exports.WhatsappErrorCode=d,exports.actions=D,exports.createContactUrl=k,exports.createWhatsapp=()=>F||(F=(()=>{const{settings:e}=m,{actions:t,flows:a,parsers:r,security:s,utils:o,toGraphLanguageTag:i}=p,{endpoints:c}=n;return{settings:e,sdk:{actions:t,flows:a,parsers:r,security:s,utils:o,toGraphLanguageTag:i},graph:{endpoints:c}}})()),exports.endpoints=l,exports.flows=A,exports.flowsEndpoints=s,exports.mediaEndpoints=c,exports.messagesEndpoints=o,exports.parsers=L,exports.security=O,exports.toGraphLanguageTag=R,exports.utils=K,exports.wabaEndpoints=i;
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
20
+ key = keys[i];
21
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
22
+ get: ((k) => from[k]).bind(null, key),
23
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
24
+ });
25
+ }
26
+ return to;
27
+ };
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
29
+ value: mod,
30
+ enumerable: true
31
+ }) : target, mod));
32
+ //#endregion
33
+ let node_crypto = require("node:crypto");
34
+ node_crypto = __toESM(node_crypto);
35
+ let _apostlejs_whatsapp_api = require("@apostlejs/whatsapp-api");
36
+ //#region src/models/chat/value-objects/chat-url.v-object.ts
37
+ var ChatURL = class {
38
+ constructor(phoneNumber, prefilled) {
39
+ this.phoneNumber = phoneNumber;
40
+ this.prefilled = prefilled;
41
+ }
42
+ toString() {
43
+ const url = new URL("/send", "https://api.whatsapp.com/");
44
+ const normalizedPhone = this.phoneNumber.replace(/[()\-+ ]/g, "");
45
+ url.searchParams.append("phone", normalizedPhone);
46
+ if (this.prefilled) url.searchParams.append("text", this.prefilled);
47
+ return url.href;
48
+ }
49
+ };
50
+ //#endregion
51
+ //#region src/models/flow/models/flow-token.model.ts
52
+ var FlowToken = class FlowToken {
53
+ chatId;
54
+ flowName;
55
+ flowParameters;
56
+ flowIdentifier;
57
+ constructor(args) {
58
+ this.chatId = args.chatId;
59
+ this.flowName = args.flowName;
60
+ this.flowParameters = args.flowParameters ?? {};
61
+ this.flowIdentifier = args.flowIdentifier ?? crypto.randomUUID();
62
+ }
63
+ static fromString(token) {
64
+ const flowName = token.split("?")[0];
65
+ let paramsString = token.split("?")?.[1];
66
+ if (paramsString?.[0] === "&") paramsString = paramsString.slice(1);
67
+ const params = new URLSearchParams(paramsString);
68
+ const chatId = params.get("chatId");
69
+ const flowIdentifier = params.get("flowIdentifier");
70
+ const flowParameters = {};
71
+ for (const [key, value] of params.entries()) if (key !== "chatId" && key !== "flowIdentifier") flowParameters[key] = value;
72
+ return new FlowToken({
73
+ flowName,
74
+ chatId,
75
+ flowIdentifier,
76
+ flowParameters
77
+ });
78
+ }
79
+ toString() {
80
+ const params = new URLSearchParams({ chatId: this.chatId });
81
+ params.set("flowIdentifier", this.flowIdentifier);
82
+ for (const [key, value] of Object.entries(this.flowParameters)) params.set(key, value.toString());
83
+ return `${this.flowName}?${decodeURIComponent(params.toString())}`;
84
+ }
85
+ };
86
+ //#endregion
87
+ //#region src/models/i18n/enums/language-tag.model.ts
88
+ let WhatsappLanguageTag = /* @__PURE__ */ function(WhatsappLanguageTag) {
89
+ WhatsappLanguageTag["AR_SA"] = "ar-SA";
90
+ WhatsappLanguageTag["BN_BD"] = "bn-BD";
91
+ WhatsappLanguageTag["BN_IN"] = "bn-IN";
92
+ WhatsappLanguageTag["CS_CZ"] = "cs-CZ";
93
+ WhatsappLanguageTag["DA_DK"] = "da-DK";
94
+ WhatsappLanguageTag["DE_AT"] = "de-AT";
95
+ WhatsappLanguageTag["DE_CH"] = "de-CH";
96
+ WhatsappLanguageTag["DE_DE"] = "de-DE";
97
+ WhatsappLanguageTag["EL_GR"] = "el-GR";
98
+ WhatsappLanguageTag["EN_AU"] = "en-AU";
99
+ WhatsappLanguageTag["EN_CA"] = "en-CA";
100
+ WhatsappLanguageTag["EN_GB"] = "en-GB";
101
+ WhatsappLanguageTag["EN_IE"] = "en-IE";
102
+ WhatsappLanguageTag["EN_IN"] = "en-IN";
103
+ WhatsappLanguageTag["EN_NZ"] = "en-NZ";
104
+ WhatsappLanguageTag["EN_US"] = "en-US";
105
+ WhatsappLanguageTag["EN_ZA"] = "en-ZA";
106
+ WhatsappLanguageTag["ES_AR"] = "es-AR";
107
+ WhatsappLanguageTag["ES_CL"] = "es-CL";
108
+ WhatsappLanguageTag["ES_CO"] = "es-CO";
109
+ WhatsappLanguageTag["ES_ES"] = "es-ES";
110
+ WhatsappLanguageTag["ES_MX"] = "es-MX";
111
+ WhatsappLanguageTag["ES_US"] = "es-US";
112
+ WhatsappLanguageTag["FI_FI"] = "fi-FI";
113
+ WhatsappLanguageTag["FR_BE"] = "fr-BE";
114
+ WhatsappLanguageTag["FR_CA"] = "fr-CA";
115
+ WhatsappLanguageTag["FR_CH"] = "fr-CH";
116
+ WhatsappLanguageTag["FR_FR"] = "fr-FR";
117
+ WhatsappLanguageTag["HE_IL"] = "he-IL";
118
+ WhatsappLanguageTag["HI_IN"] = "hi-IN";
119
+ WhatsappLanguageTag["HU_HU"] = "hu-HU";
120
+ WhatsappLanguageTag["ID_ID"] = "id-ID";
121
+ WhatsappLanguageTag["IT_CH"] = "it-CH";
122
+ WhatsappLanguageTag["IT_IT"] = "it-IT";
123
+ WhatsappLanguageTag["JA_JP"] = "ja-JP";
124
+ WhatsappLanguageTag["KO_KR"] = "ko-KR";
125
+ WhatsappLanguageTag["NL_BE"] = "nl-BE";
126
+ WhatsappLanguageTag["NL_NL"] = "nl-NL";
127
+ WhatsappLanguageTag["NO_NO"] = "no-NO";
128
+ WhatsappLanguageTag["PL_PL"] = "pl-PL";
129
+ WhatsappLanguageTag["PT_BR"] = "pt-BR";
130
+ WhatsappLanguageTag["PT_PT"] = "pt-PT";
131
+ WhatsappLanguageTag["RO_RO"] = "ro-RO";
132
+ WhatsappLanguageTag["RU_RU"] = "ru-RU";
133
+ WhatsappLanguageTag["SK_SK"] = "sk-SK";
134
+ WhatsappLanguageTag["SV_SE"] = "sv-SE";
135
+ WhatsappLanguageTag["TA_IN"] = "ta-IN";
136
+ WhatsappLanguageTag["TA_LK"] = "ta-LK";
137
+ WhatsappLanguageTag["TH_TH"] = "th-TH";
138
+ WhatsappLanguageTag["TR_TR"] = "tr-TR";
139
+ WhatsappLanguageTag["ZH_CN"] = "zh-CN";
140
+ WhatsappLanguageTag["ZH_HK"] = "zh-HK";
141
+ WhatsappLanguageTag["ZH_TW"] = "zh-TW";
142
+ return WhatsappLanguageTag;
143
+ }({});
144
+ //#endregion
145
+ //#region src/models/waba/models/waba-encryption.model.ts
146
+ function normalizePem(key) {
147
+ const normalized = key.replace(/\\n/g, "\n");
148
+ return normalized.endsWith("\n") ? normalized : `${normalized}\n`;
149
+ }
150
+ var WabaEncryption = class WabaEncryption {
151
+ passphrase;
152
+ privateKey;
153
+ publicKey;
154
+ constructor(args) {
155
+ this.passphrase = args.passphrase;
156
+ this.privateKey = normalizePem(args.privateKey);
157
+ if (args.publicKey) this.publicKey = normalizePem(args.publicKey);
158
+ }
159
+ static generate() {
160
+ const passphrase = node_crypto.default.randomUUID();
161
+ const { publicKey, privateKey } = node_crypto.default.generateKeyPairSync("rsa", {
162
+ modulusLength: 2048,
163
+ publicKeyEncoding: {
164
+ type: "spki",
165
+ format: "pem"
166
+ },
167
+ privateKeyEncoding: {
168
+ type: "pkcs8",
169
+ format: "pem",
170
+ cipher: "aes-256-cbc",
171
+ passphrase
172
+ }
173
+ });
174
+ return new WabaEncryption({
175
+ passphrase,
176
+ publicKey,
177
+ privateKey
178
+ });
179
+ }
180
+ };
181
+ //#endregion
182
+ //#region src/context/context.ts
183
+ let ctx;
184
+ function setContext(context) {
185
+ ctx = context;
186
+ }
187
+ function getContext() {
188
+ if (!ctx) throw new Error("Whatsapp context is not initialized. Call createWhatsapp() first.");
189
+ return ctx;
190
+ }
191
+ //#endregion
192
+ //#region src/features/flows/utils/sanitize-flow.ts
193
+ const removeStringTypeFromUpdateData = (flow) => flow.replace(/^\s*"type":\s*"string",?\n(?=\s*"const":\s*"update_data")/gm, "");
194
+ const lowercaseForbiddenKeys = (flow) => flow.replaceAll(/"(Then|Else|Cases)":/g, (_, key) => `"${key.toLowerCase()}":`);
195
+ const sanitizeFlow = (json) => {
196
+ let flow = json;
197
+ flow = removeStringTypeFromUpdateData(flow);
198
+ flow = lowercaseForbiddenKeys(flow);
199
+ return flow;
200
+ };
201
+ //#endregion
202
+ //#region src/features/flows/actions/create.ts
203
+ async function create(body) {
204
+ const { config, client } = getContext();
205
+ if (!config.whatsappAccountId) throw new Error("WABA ID is required to create a flow");
206
+ const flow_json = body.flow_json ? sanitizeFlow(JSON.stringify(body.flow_json, null, 2)) : void 0;
207
+ return await client.flows.createFlow(config.whatsappAccountId, {
208
+ name: body.name,
209
+ categories: body.categories,
210
+ clone_flow_id: body.clone_flow_id,
211
+ endpoint_uri: body.endpoint_uri,
212
+ publish: body.publish,
213
+ flow_json
214
+ });
215
+ }
216
+ //#endregion
217
+ //#region src/features/flows/actions/delete.ts
218
+ async function deleteFlow(flow_id) {
219
+ const { client } = getContext();
220
+ return await client.flows.deleteFlow(flow_id);
221
+ }
222
+ //#endregion
223
+ //#region src/features/flows/actions/get.ts
224
+ async function get(flow_id, query) {
225
+ const { client } = getContext();
226
+ return await client.flows.getFlow(flow_id, { fields: query?.fields.join(",") });
227
+ }
228
+ //#endregion
229
+ //#region src/features/flows/actions/get-many.ts
230
+ async function getMany() {
231
+ const { config, client } = getContext();
232
+ if (!config.whatsappAccountId) throw new Error("WABA ID is required to list flows");
233
+ return await client.flows.listFlows(config.whatsappAccountId);
234
+ }
235
+ //#endregion
236
+ //#region src/features/flows/actions/get-preview.ts
237
+ async function getPreview(flow_id, query) {
238
+ const { client } = getContext();
239
+ const previewUrl = (await client.flows.getFlowPreview(flow_id, { fields: "preview.invalidate(false)" })).preview.preview_url.replace(/\\/g, "");
240
+ if (!query) return previewUrl;
241
+ const urlParams = {};
242
+ urlParams.flow_token = new FlowToken({
243
+ chatId: query.recipientId,
244
+ flowName: query.name,
245
+ flowParameters: query.parameters
246
+ }).toString();
247
+ urlParams.flow_action = query.action;
248
+ if (query.payload) urlParams.flow_action_payload = encodeURIComponent(JSON.stringify(query.payload));
249
+ urlParams.phone_number = query.phoneNumber;
250
+ if (query.interactive !== void 0) urlParams.interactive = String(query.interactive);
251
+ return `${previewUrl}&${new URLSearchParams(urlParams).toString()}`;
252
+ }
253
+ //#endregion
254
+ //#region src/features/flows/actions/publish.ts
255
+ async function publish(flow_id) {
256
+ const { client } = getContext();
257
+ return await client.flows.publishFlow(flow_id);
258
+ }
259
+ //#endregion
260
+ //#region src/features/flows/actions/update-json.ts
261
+ async function updateJson(flow_id, json) {
262
+ const { config } = getContext();
263
+ const url = `${`https://graph.facebook.com/v${config.metaGraphApiVersion ?? "25.0"}`}/${flow_id}/assets`;
264
+ const blob = new Blob([sanitizeFlow(JSON.stringify(json, null, 2))], { type: "application/json" });
265
+ const form = new FormData();
266
+ form.append("name", "flow.json");
267
+ form.append("asset_type", "FLOW_JSON");
268
+ form.append("file", blob, "flow.json");
269
+ const response = await fetch(url, {
270
+ method: "POST",
271
+ headers: { Authorization: `Bearer ${config.metaAppAccessToken}` },
272
+ body: form
273
+ });
274
+ if (!response.ok) {
275
+ const errorBody = await response.text();
276
+ throw new Error(`updateFlowJson failed (${response.status}): ${errorBody}`);
277
+ }
278
+ return response.json();
279
+ }
280
+ //#endregion
281
+ //#region src/features/flows/actions/update-metadata.ts
282
+ async function updateMetadata(flow_id, metadata) {
283
+ const { client } = getContext();
284
+ return await client.flows.updateFlowMetadata(flow_id, {
285
+ application_id: metadata.applicationId,
286
+ name: metadata.name,
287
+ categories: metadata.categories,
288
+ endpoint_uri: metadata.endpointUri
289
+ });
290
+ }
291
+ //#endregion
292
+ //#region src/features/flows/crypto/decrypt-flow-body.ts
293
+ const decryptFlowBody = (encryptedBody) => {
294
+ const { encryption } = getContext();
295
+ if (!encryption) throw new Error("WABA encryption private key and passphrase are required to decrypt flow body");
296
+ const { encrypted_aes_key, encrypted_flow_data, initial_vector } = encryptedBody;
297
+ const privateKey = node_crypto.default.createPrivateKey({
298
+ key: encryption.privateKey,
299
+ passphrase: encryption.passphrase
300
+ });
301
+ const decryptedAesKey = node_crypto.default.privateDecrypt({
302
+ key: privateKey,
303
+ padding: node_crypto.default.constants.RSA_PKCS1_OAEP_PADDING,
304
+ oaepHash: "sha256"
305
+ }, Buffer.from(encrypted_aes_key, "base64"));
306
+ const flowDataBuffer = Buffer.from(encrypted_flow_data, "base64");
307
+ const initialVectorBuffer = Buffer.from(initial_vector, "base64");
308
+ const TAG_LENGTH = 16;
309
+ const encrypted_flow_data_body = flowDataBuffer.subarray(0, -TAG_LENGTH);
310
+ const encrypted_flow_data_tag = flowDataBuffer.subarray(-TAG_LENGTH);
311
+ const decipher = node_crypto.default.createDecipheriv("aes-128-gcm", decryptedAesKey, initialVectorBuffer);
312
+ decipher.setAuthTag(encrypted_flow_data_tag);
313
+ const decryptedJSONString = Buffer.concat([decipher.update(encrypted_flow_data_body), decipher.final()]).toString("utf-8");
314
+ return {
315
+ body: JSON.parse(decryptedJSONString),
316
+ encryptionMetadata: {
317
+ initialVectorBuffer,
318
+ aesKeyBuffer: decryptedAesKey
319
+ }
320
+ };
321
+ };
322
+ //#endregion
323
+ //#region src/features/flows/crypto/decrypt-flow-media.ts
324
+ async function fetchMedia(url) {
325
+ return await fetch(url).then(async (response) => {
326
+ const arrayBuffer = await response.arrayBuffer();
327
+ return Buffer.from(arrayBuffer);
328
+ });
329
+ }
330
+ const decryptFlowMedia = async (media, defaultUrl = "https://picsum.photos/seed/picsum/200") => {
331
+ let { cdn_url, encryption_metadata } = media;
332
+ if (cdn_url === "EXAMPLE_DATA__CDN_URL_WILL_COME_IN_THIS_FIELD") {
333
+ cdn_url = defaultUrl;
334
+ return await fetchMedia(cdn_url);
335
+ }
336
+ const fileBuffer = await fetchMedia(cdn_url);
337
+ const { iv, encryption_key, hmac_key, encrypted_hash, plaintext_hash } = encryption_metadata;
338
+ const base64Metadata = {
339
+ iv: Buffer.from(iv, "base64"),
340
+ encryption_key: Buffer.from(encryption_key, "base64"),
341
+ hmac_key: Buffer.from(hmac_key, "base64")
342
+ };
343
+ if (node_crypto.default.createHash("sha256").update(fileBuffer).digest("base64") !== encrypted_hash) throw new Error("Encrypted hash validation failed");
344
+ const ciphertext = fileBuffer.subarray(0, fileBuffer.length - 10);
345
+ const hmac10 = fileBuffer.subarray(-10);
346
+ if (!node_crypto.default.createHmac("sha256", base64Metadata.hmac_key).update(Buffer.concat([base64Metadata.iv, ciphertext])).digest().subarray(0, 10).equals(hmac10)) throw new Error("HMAC validation failed");
347
+ const decipher = node_crypto.default.createDecipheriv("aes-256-cbc", base64Metadata.encryption_key, base64Metadata.iv);
348
+ const decrypted = decipher.update(ciphertext);
349
+ const decryptedMedia = Buffer.concat([decrypted, decipher.final()]);
350
+ if (node_crypto.default.createHash("sha256").update(decryptedMedia).digest("base64") !== plaintext_hash) throw new Error("Decrypted media hash validation failed");
351
+ return decryptedMedia;
352
+ };
353
+ //#endregion
354
+ //#region src/features/flows/crypto/encrypt-flow-response.ts
355
+ const encryptFlowResponse = (response, encryptionMetadata) => {
356
+ const flipped_iv = [];
357
+ for (const pair of Buffer.from(encryptionMetadata.initialVectorBuffer).entries()) flipped_iv.push(~pair[1]);
358
+ const cipher = node_crypto.default.createCipheriv("aes-128-gcm", Buffer.from(encryptionMetadata.aesKeyBuffer), Buffer.from(flipped_iv));
359
+ return Buffer.concat([
360
+ cipher.update(JSON.stringify(response), "utf-8"),
361
+ cipher.final(),
362
+ cipher.getAuthTag()
363
+ ]).toString("base64");
364
+ };
365
+ //#endregion
366
+ //#region src/features/flows/adapters/flow-response.adapter.ts
367
+ const flowResponseAdapter = { toGraph(flow_token, response) {
368
+ if (!response.data) throw new Error("Missing data in flow response");
369
+ if (!response.screen) throw new Error("Missing screen in flow response");
370
+ if (response.screen === "SUCCESS") response.data = { extension_message_response: { params: {
371
+ flow_token,
372
+ ...response.data ?? {}
373
+ } } };
374
+ return response;
375
+ } };
376
+ //#endregion
377
+ //#region src/features/flows/index.ts
378
+ var flows_exports = /* @__PURE__ */ __exportAll({
379
+ create: () => create,
380
+ decryptFlowBody: () => decryptFlowBody,
381
+ decryptFlowMedia: () => decryptFlowMedia,
382
+ deleteFlow: () => deleteFlow,
383
+ encryptFlowResponse: () => encryptFlowResponse,
384
+ flowResponseAdapter: () => flowResponseAdapter,
385
+ get: () => get,
386
+ getMany: () => getMany,
387
+ getPreview: () => getPreview,
388
+ publish: () => publish,
389
+ updateJson: () => updateJson,
390
+ updateMetadata: () => updateMetadata
391
+ });
392
+ //#endregion
393
+ //#region src/features/i18n/adapters/language-tag.adapter.ts
394
+ const languageTagMapping = {
395
+ [WhatsappLanguageTag.AR_SA]: "ar",
396
+ [WhatsappLanguageTag.BN_BD]: "bn",
397
+ [WhatsappLanguageTag.BN_IN]: "bn",
398
+ [WhatsappLanguageTag.CS_CZ]: "cs",
399
+ [WhatsappLanguageTag.DA_DK]: "da",
400
+ [WhatsappLanguageTag.DE_AT]: "de",
401
+ [WhatsappLanguageTag.DE_CH]: "de",
402
+ [WhatsappLanguageTag.DE_DE]: "de",
403
+ [WhatsappLanguageTag.EL_GR]: "el",
404
+ [WhatsappLanguageTag.EN_AU]: "en",
405
+ [WhatsappLanguageTag.EN_CA]: "en",
406
+ [WhatsappLanguageTag.EN_GB]: "en_GB",
407
+ [WhatsappLanguageTag.EN_IE]: "en",
408
+ [WhatsappLanguageTag.EN_IN]: "en",
409
+ [WhatsappLanguageTag.EN_NZ]: "en",
410
+ [WhatsappLanguageTag.EN_ZA]: "en",
411
+ [WhatsappLanguageTag.EN_US]: "en_US",
412
+ [WhatsappLanguageTag.ES_AR]: "es_AR",
413
+ [WhatsappLanguageTag.ES_CL]: "es",
414
+ [WhatsappLanguageTag.ES_CO]: "es",
415
+ [WhatsappLanguageTag.ES_ES]: "es_ES",
416
+ [WhatsappLanguageTag.ES_MX]: "es_MX",
417
+ [WhatsappLanguageTag.ES_US]: "es",
418
+ [WhatsappLanguageTag.FI_FI]: "fi",
419
+ [WhatsappLanguageTag.FR_BE]: "fr",
420
+ [WhatsappLanguageTag.FR_CA]: "fr",
421
+ [WhatsappLanguageTag.FR_CH]: "fr",
422
+ [WhatsappLanguageTag.FR_FR]: "fr",
423
+ [WhatsappLanguageTag.HE_IL]: "he",
424
+ [WhatsappLanguageTag.HI_IN]: "hi",
425
+ [WhatsappLanguageTag.HU_HU]: "hu",
426
+ [WhatsappLanguageTag.ID_ID]: "id",
427
+ [WhatsappLanguageTag.IT_CH]: "it",
428
+ [WhatsappLanguageTag.IT_IT]: "it",
429
+ [WhatsappLanguageTag.JA_JP]: "ja",
430
+ [WhatsappLanguageTag.KO_KR]: "ko",
431
+ [WhatsappLanguageTag.NL_BE]: "nl",
432
+ [WhatsappLanguageTag.NL_NL]: "nl",
433
+ [WhatsappLanguageTag.NO_NO]: "nb",
434
+ [WhatsappLanguageTag.PL_PL]: "pl",
435
+ [WhatsappLanguageTag.PT_PT]: "pt_PT",
436
+ [WhatsappLanguageTag.PT_BR]: "pt_BR",
437
+ [WhatsappLanguageTag.RO_RO]: "ro",
438
+ [WhatsappLanguageTag.RU_RU]: "ru",
439
+ [WhatsappLanguageTag.SK_SK]: "sk",
440
+ [WhatsappLanguageTag.SV_SE]: "sv",
441
+ [WhatsappLanguageTag.TA_IN]: "ta",
442
+ [WhatsappLanguageTag.TA_LK]: "ta",
443
+ [WhatsappLanguageTag.TH_TH]: "th",
444
+ [WhatsappLanguageTag.TR_TR]: "tr",
445
+ [WhatsappLanguageTag.ZH_CN]: "zh_CN",
446
+ [WhatsappLanguageTag.ZH_HK]: "zh_HK",
447
+ [WhatsappLanguageTag.ZH_TW]: "zh_TW"
448
+ };
449
+ const languageTagAdapter = { toGraph(languageTag) {
450
+ const waTag = languageTagMapping[languageTag];
451
+ if (waTag === void 0) return "en";
452
+ return waTag;
453
+ } };
454
+ //#endregion
455
+ //#region src/features/i18n/index.ts
456
+ var i18n_exports = /* @__PURE__ */ __exportAll({ languageTagAdapter: () => languageTagAdapter });
457
+ //#endregion
458
+ //#region src/features/messages/adapters/message.adapter.ts
459
+ const messageAdapter = { toGraph(message, chatId) {
460
+ const { config: { whatsappMessageNamespace } } = getContext();
461
+ switch (message.type) {
462
+ case "text": return {
463
+ type: "text",
464
+ text: {
465
+ body: message.text,
466
+ preview_url: message.previewUrl
467
+ }
468
+ };
469
+ case "flow": {
470
+ const { flow, type, ...interactiveBase } = message;
471
+ return {
472
+ type: "interactive",
473
+ interactive: {
474
+ ...interactiveBase,
475
+ type: "flow",
476
+ action: {
477
+ name: "flow",
478
+ parameters: {
479
+ flow_token: new FlowToken({
480
+ chatId,
481
+ flowName: flow.name,
482
+ flowIdentifier: flow.identifier,
483
+ flowParameters: flow.parameters
484
+ }).toString(),
485
+ flow_message_version: "3",
486
+ flow_cta: flow.button ?? "",
487
+ flow_action: flow.action ?? "navigate",
488
+ mode: flow.mode ?? "draft",
489
+ ...flow.name && { flow_name: flow.name },
490
+ ...flow.payload && { flow_action_payload: {
491
+ screen: flow.payload.screen,
492
+ data: {
493
+ __type__: "data",
494
+ ...flow.payload.data
495
+ }
496
+ } }
497
+ }
498
+ }
499
+ }
500
+ };
501
+ }
502
+ case "list": {
503
+ const { list, type, ...interactiveBase } = message;
504
+ return {
505
+ type: "interactive",
506
+ interactive: {
507
+ ...interactiveBase,
508
+ type: "list",
509
+ action: list
510
+ }
511
+ };
512
+ }
513
+ case "button": {
514
+ const { buttons, type, ...interactiveBase } = message;
515
+ return {
516
+ type: "interactive",
517
+ interactive: {
518
+ ...interactiveBase,
519
+ type: "button",
520
+ action: { buttons: buttons.map((b) => ({
521
+ type: "reply",
522
+ reply: {
523
+ id: b.id,
524
+ title: b.text
525
+ }
526
+ })) }
527
+ }
528
+ };
529
+ }
530
+ case "template": {
531
+ const { type, language, ...rest } = message;
532
+ return {
533
+ type: "template",
534
+ template: {
535
+ ...rest,
536
+ namespace: whatsappMessageNamespace,
537
+ language: { code: languageTagAdapter.toGraph(language) }
538
+ }
539
+ };
540
+ }
541
+ case "media": {
542
+ const { type, ...mediaFields } = message;
543
+ const [mediaType, mediaData] = Object.entries(mediaFields)[0];
544
+ const { ref, ...rest } = mediaData;
545
+ const resolvedMedia = !Number.isNaN(Number(ref)) ? {
546
+ id: ref,
547
+ ...rest
548
+ } : {
549
+ link: ref,
550
+ ...rest
551
+ };
552
+ return {
553
+ type: mediaType,
554
+ [mediaType]: resolvedMedia
555
+ };
556
+ }
557
+ case "contact": return {
558
+ type: "contacts",
559
+ contacts: message.contacts
560
+ };
561
+ }
562
+ } };
563
+ //#endregion
564
+ //#region src/features/messages/actions/send.ts
565
+ async function send(body) {
566
+ const { config, client } = getContext();
567
+ if (!config.whatsappNumberId) throw new Error("WABA Number Id is required to run send");
568
+ const fragment = messageAdapter.toGraph(body.message, body.to);
569
+ return await client.messages.sendMessage(config.whatsappNumberId, {
570
+ messaging_product: "whatsapp",
571
+ to: body.to,
572
+ recipient_type: "individual",
573
+ ...body.reply && { context: { message_id: body.reply } },
574
+ ...body.showUrlPreviewImage && { preview_url: body.showUrlPreviewImage },
575
+ ...body.metadata && { biz_opaque_callback_data: body.metadata },
576
+ ...fragment
577
+ });
578
+ }
579
+ //#endregion
580
+ //#region src/features/messages/actions/show-typing-indicator.ts
581
+ async function showTypingIndicator(params) {
582
+ const { config, client } = getContext();
583
+ if (!config.whatsappNumberId) throw new Error("WABA Number Id is required to run send");
584
+ return await client.messages.showTypingIndicator(config.whatsappNumberId, {
585
+ messaging_product: "whatsapp",
586
+ status: "read",
587
+ message_id: params.messageId,
588
+ typing_indicator: { type: "text" }
589
+ });
590
+ }
591
+ //#endregion
592
+ //#region src/features/messages/index.ts
593
+ var messages_exports = /* @__PURE__ */ __exportAll({
594
+ messageAdapter: () => messageAdapter,
595
+ send: () => send,
596
+ showTypingIndicator: () => showTypingIndicator
597
+ });
598
+ //#endregion
599
+ //#region src/features/waba/actions/register-number.ts
600
+ async function registerNumber({ pin, dataRegion }) {
601
+ const { config, client } = getContext();
602
+ if (!config.whatsappNumberId) throw new Error("WABA Number Id is required to run send");
603
+ return await client.waba.registerNumber(config.whatsappNumberId, {
604
+ messaging_product: "whatsapp",
605
+ pin,
606
+ ...dataRegion && { data_localization_region: dataRegion }
607
+ });
608
+ }
609
+ //#endregion
610
+ //#region src/features/waba/actions/upload-encryption.ts
611
+ async function uploadEncryption(publicKey) {
612
+ const { config, client } = getContext();
613
+ if (!config.whatsappNumberId) throw new Error("WABA Number Id is required to run send");
614
+ return await client.waba.uploadEncryptionKey(config.whatsappNumberId, { business_public_key: publicKey });
615
+ }
616
+ //#endregion
617
+ //#region src/features/waba/index.ts
618
+ var waba_exports = /* @__PURE__ */ __exportAll({
619
+ registerNumber: () => registerNumber,
620
+ uploadEncryption: () => uploadEncryption
621
+ });
622
+ //#endregion
623
+ //#region src/features/webhook/adapters/message-received.adapter.ts
624
+ function textParser(message) {
625
+ return message.text?.body ?? message.button?.text ?? message.interactive?.button_reply?.title ?? message.interactive?.list_reply?.title ?? message.document?.caption ?? null;
626
+ }
627
+ function mediaParser(message) {
628
+ if (!(message.audio || message.document || message.video || message.image)) return void 0;
629
+ const id = message.audio?.id ?? message.document?.id ?? message.image?.id ?? message.video?.id;
630
+ const mime_type = message.audio?.mime_type ?? message.document?.mime_type ?? message.image?.mime_type ?? message.video?.mime_type;
631
+ const type = [
632
+ "audio",
633
+ "document",
634
+ "image",
635
+ "video"
636
+ ].find((k) => message[k]);
637
+ const sha256 = message.image?.sha256 ?? message.document?.sha256 ?? message.video?.sha256;
638
+ return {
639
+ id,
640
+ type,
641
+ caption: message.document?.caption ?? message.image?.caption ?? message.video?.caption ?? null,
642
+ mime_type,
643
+ sha256,
644
+ filename: message.document?.filename ?? message.video?.filename
645
+ };
646
+ }
647
+ function interactionParser(message) {
648
+ if (!message.interactive) return void 0;
649
+ const button = message.interactive.button_reply || message.button ? { button: {
650
+ id: message.interactive.button_reply?.id ?? null,
651
+ title: message.button?.text ?? message.interactive.button_reply?.title,
652
+ payload: message.button?.payload ?? null
653
+ } } : {};
654
+ const selectedOption = message.interactive.list_reply ? { selectedOption: {
655
+ id: message.interactive.list_reply.id,
656
+ title: message.interactive.list_reply.title,
657
+ description: message.interactive.list_reply.description
658
+ } } : {};
659
+ const flowResponse = message.interactive.nfm_reply ? { flowResponse: JSON.parse(message.interactive.nfm_reply.response_json) } : {};
660
+ return {
661
+ ...button,
662
+ ...selectedOption,
663
+ ...flowResponse
664
+ };
665
+ }
666
+ const messageReceivedAdapter = { fromGraph(message) {
667
+ return {
668
+ id: message.id,
669
+ type: message.type,
670
+ timestamp: message.timestamp,
671
+ metadata: {
672
+ forwarded: message.context?.forwarded,
673
+ frequentlyForwarded: message.context?.frequently_forwarded
674
+ },
675
+ chatId: message.from,
676
+ text: textParser(message),
677
+ reply: message.context?.id,
678
+ media: mediaParser(message),
679
+ interaction: interactionParser(message)
680
+ };
681
+ } };
682
+ //#endregion
683
+ //#region src/features/webhook/guards/guard-hub.ts
684
+ const guardHub = (request) => {
685
+ const { config } = getContext();
686
+ if (!config.whatsappWebhookToken) throw new Error("Webhook token is required to verify signature");
687
+ const params = new URL(request.url).searchParams;
688
+ const untrustedToken = params.get("hub.verify_token");
689
+ const challenge = params.get("hub.challenge");
690
+ if (!untrustedToken || !challenge) return false;
691
+ if (untrustedToken !== config.whatsappWebhookToken) return false;
692
+ return challenge;
693
+ };
694
+ //#endregion
695
+ //#region src/features/webhook/guards/guard-signature.ts
696
+ async function guardSignature(request, rawBody) {
697
+ const { config } = getContext();
698
+ if (!config.metaAppSecretKey) throw new Error("Meta App Secret Key is required for guarding signature.");
699
+ const headerSignature = request.headers.get("x-hub-signature-256");
700
+ if (!headerSignature) return false;
701
+ const untrustedSignature = headerSignature.replace("sha256=", "");
702
+ const trustedSignature = node_crypto.default.createHmac("sha256", config.metaAppSecretKey).update(rawBody, "utf-8").digest("hex");
703
+ return node_crypto.default.timingSafeEqual(Buffer.from(untrustedSignature), Buffer.from(trustedSignature));
704
+ }
705
+ //#endregion
706
+ //#region src/features/webhook/webhook.ts
707
+ async function webhook(request) {
708
+ if (request.method === "GET") {
709
+ const challenge = guardHub(request);
710
+ if (!challenge) throw new Error("Invalid health check hub");
711
+ return { health: challenge };
712
+ }
713
+ const rawBody = await request.text();
714
+ const body = JSON.parse(rawBody);
715
+ if ("entry" in body) {
716
+ if (!await guardSignature(request, rawBody)) throw new Error("Invalid application webhook signature");
717
+ return { message: {
718
+ received: body.entry.flatMap((entry) => entry.changes).filter((change) => change.field === "messages").flatMap((change) => change.value.messages ?? []).map((message) => messageReceivedAdapter.fromGraph(message)),
719
+ status: void 0
720
+ } };
721
+ }
722
+ if ("encrypted_flow_data" in body) {
723
+ const { body: payload, encryptionMetadata } = decryptFlowBody(body);
724
+ return { flow: {
725
+ payload,
726
+ pingResponse: { data: { status: "active" } },
727
+ encryptionMetadata
728
+ } };
729
+ }
730
+ throw new Error("Unrecognized webhook event");
731
+ }
732
+ //#endregion
733
+ //#region src/features/webhook/index.ts
734
+ var webhook_exports = /* @__PURE__ */ __exportAll({
735
+ guardHub: () => guardHub,
736
+ guardSignature: () => guardSignature,
737
+ messageReceivedAdapter: () => messageReceivedAdapter,
738
+ webhook: () => webhook
739
+ });
740
+ //#endregion
741
+ //#region src/client/normalize-pem-key.ts
742
+ function normalizePemKey(key) {
743
+ if (!key) return key;
744
+ return key.replace(/\\\n/g, "\n");
745
+ }
746
+ //#endregion
747
+ //#region src/client/client.ts
748
+ const createWhatsapp = (config) => {
749
+ config = {
750
+ metaAppAccessToken: process.env.META_APP_ACCESS_TOKEN,
751
+ metaAppSecretKey: process.env.META_APP_SECRET_KEY,
752
+ metaGraphApiVersion: process.env.META_GRAPH_API_VERSION ?? "25.0",
753
+ whatsappWebhookToken: process.env.WHATSAPP_WEBHOOK_TOKEN,
754
+ whatsappAccountEncryptionPassphrase: process.env.WHATSAPP_ACCOUNT_ENCRYPTION_PASSPHRASE,
755
+ whatsappAccountEncryptionPrivateKey: process.env.WHATSAPP_ACCOUNT_ENCRYPTION_PRIVATE_KEY,
756
+ whatsappAccountId: process.env.WHATSAPP_ACCOUNT_ID,
757
+ whatsappFlowsMode: process.env.WHATSAPP_FLOWS_MODE,
758
+ whatsappMessageNamespace: process.env.WHATSAPP_MESSAGE_NAMESPACE,
759
+ whatsappNumberId: process.env.WHATSAPP_NUBMER_ID,
760
+ ...config
761
+ };
762
+ config.whatsappAccountEncryptionPrivateKey = normalizePemKey(config.whatsappAccountEncryptionPrivateKey);
763
+ const client = new _apostlejs_whatsapp_api.WhatsappApiClient({
764
+ accessToken: config.metaAppAccessToken,
765
+ baseUrl: `https://graph.facebook.com/v${config.metaGraphApiVersion}`
766
+ });
767
+ let encryption;
768
+ if (config.whatsappAccountEncryptionPassphrase && config.whatsappAccountEncryptionPrivateKey) encryption = new WabaEncryption({
769
+ passphrase: config.whatsappAccountEncryptionPassphrase,
770
+ privateKey: config.whatsappAccountEncryptionPrivateKey
771
+ });
772
+ setContext({
773
+ config,
774
+ client,
775
+ encryption
776
+ });
777
+ return {
778
+ flows: flows_exports,
779
+ i18n: i18n_exports,
780
+ messages: messages_exports,
781
+ waba: waba_exports,
782
+ webhook: webhook_exports
783
+ };
784
+ };
785
+ //#endregion
786
+ exports.ChatURL = ChatURL;
787
+ exports.FlowToken = FlowToken;
788
+ exports.WabaEncryption = WabaEncryption;
789
+ exports.WhatsappLanguageTag = WhatsappLanguageTag;
790
+ exports.createWhatsapp = createWhatsapp;
791
+ exports.normalizePemKey = normalizePemKey;