@stacksjs/push 0.70.308 → 0.70.310

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.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import * as expo from './drivers/expo';
2
2
  import * as fcm from './drivers/fcm';
3
3
  import type { PushResult } from '@stacksjs/types';
4
+ export type {
5
+ SendWebPushOptions,
6
+ VapidKeys,
7
+ WebPushResult,
8
+ WebPushSubscription,
9
+ } from './drivers/web-push';
4
10
  /**
5
11
  * Send a push notification using the specified driver
6
12
  */
@@ -22,4 +28,19 @@ export declare interface SendOptions {
22
28
  }
23
29
  export type PushDriver = 'expo' | 'fcm';
24
30
  export * from './drivers/index';
31
+ /*
32
+ * Named rather than `export * from './drivers/index'`.
33
+ *
34
+ * The declaration build rewrites a star re-export into imports of whatever the
35
+ * file happens to use, and everything else silently vanishes from the published
36
+ * types - so `sendWebPush` worked at runtime and did not exist to TypeScript.
37
+ * Naming them states the package's surface rather than inferring it, and it
38
+ * survives the build.
39
+ */
40
+ export {
41
+ buildVapidHeaders,
42
+ encryptPayload,
43
+ generateVapidKeys,
44
+ sendWebPush,
45
+ } from './drivers/web-push';
25
46
  export { expo, fcm };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var u=Object.defineProperty;var f=(G)=>G;function m(G,Q){this[G]=f.bind(null,Q)}var w=(G,Q)=>{for(var W in Q)u(G,W,{get:Q[W],enumerable:!0,configurable:!0,set:m.bind(Q,W)})};var v={};w(v,{sendBatch:()=>T,send:()=>L,isExpoPushToken:()=>E,getReceipts:()=>x,default:()=>g,Send:()=>L});import{log as I}from"@stacksjs/cli";var p="https://exp.host/--/api/v2/push/send";function E(G){return/^Expo(?:nent)?PushToken\[.+\]$/.test(G)||/^[a-zA-Z0-9-_]+$/.test(G)}async function L(G){let Q=Array.isArray(G.to)?G.to:[G.to],W=Q.filter((Z)=>!E(Z));if(W.length>0)I.warn(`Invalid Expo push tokens found: ${W.join(", ")}`);let Y=Q.filter(E);if(Y.length===0)return{success:!1,provider:"expo",message:"No valid Expo push tokens provided"};try{let Z=Y.map((A)=>({to:A,title:G.title,body:G.body,data:G.data,sound:G.sound??"default",badge:G.badge,channelId:G.channelId,priority:G.priority??"high",ttl:G.ttl})),q=4096;for(let A of Z){let V=new TextEncoder().encode(JSON.stringify(A)).length;if(V>4096)return{success:!1,provider:"expo",message:`Expo message payload (${V} bytes) exceeds 4 KiB per-message limit`}}let $=await fetch(p,{method:"POST",headers:{Accept:"application/json","Accept-Encoding":"gzip, deflate","Content-Type":"application/json"},body:JSON.stringify(Z)});if(!$.ok){let A=await $.text();return I.error(`Expo push failed: ${A}`),{success:!1,provider:"expo",message:`HTTP ${$.status}: ${A}`}}let N=(await $.json()).data,O=N.filter((A)=>A.status==="error");if(O.length>0)I.warn(`Some push notifications failed: ${JSON.stringify(O)}`);let F=N.filter((A)=>A.status==="ok").length;return I.info(`Expo push sent: ${F}/${N.length} successful`),{success:O.length===0,provider:"expo",message:`Sent ${F}/${N.length} notifications`,messageId:N.find((A)=>A.id)?.id,data:{tickets:N}}}catch(Z){let q=Z instanceof Error?Z:Error(String(Z));return I.error(`Expo push error: ${q.message}`),{success:!1,provider:"expo",message:q.message}}}async function x(G){let Q=await fetch("https://exp.host/--/api/v2/push/getReceipts",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({ids:G})});if(!Q.ok)throw Error(`Failed to get receipts: ${Q.status}`);return(await Q.json()).data}async function T(G,Q=100){let W=[];for(let Y=0;Y<G.length;Y+=Q){let Z=G.slice(Y,Y+Q),q=await Promise.all(Z.map(($)=>L($)));W.push(...q)}return W}var g={send:L,sendBatch:T,getReceipts:x,isExpoPushToken:E};var b={};w(b,{unsubscribeFromTopic:()=>h,subscribeToTopic:()=>y,sendToTopic:()=>B,sendMulticast:()=>C,sendLegacy:()=>z,send:()=>U,default:()=>t,configure:()=>K,Send:()=>U});import{log as j}from"@stacksjs/cli";var c="https://fcm.googleapis.com/fcm/send",a="https://fcm.googleapis.com/v1/projects",X={};function K(G){X={...X,...G}}async function r(){if(!X.serviceAccount)throw Error("Service account not configured for FCM v1 API");let{clientEmail:G,privateKey:Q}=X.serviceAccount,W=Math.floor(Date.now()/1000),Y={alg:"RS256",typ:"JWT"},Z={iss:G,scope:"https://www.googleapis.com/auth/firebase.messaging",aud:"https://oauth2.googleapis.com/token",iat:W,exp:W+3600},q=new TextEncoder,$=btoa(JSON.stringify(Y)),D=btoa(JSON.stringify(Z)),N=`${$}.${D}`,O=Q.replace(/-----BEGIN PRIVATE KEY-----/,"").replace(/-----END PRIVATE KEY-----/,"").replace(/\n/g,""),F=Uint8Array.from(atob(O),(l)=>l.charCodeAt(0)),A=await crypto.subtle.importKey("pkcs8",F,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),V=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",A,q.encode(N)),R=btoa(String.fromCharCode(...new Uint8Array(V))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),S=`${$}.${D}.${R}`,H=await fetch("https://oauth2.googleapis.com/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${S}`});if(!H.ok)throw Error(`Failed to get access token: ${H.status}`);return(await H.json()).access_token}async function z(G){if(!X.serverKey)return{success:!1,provider:"fcm",message:"FCM server key not configured"};try{let Q=await fetch(c,{method:"POST",headers:{Authorization:`key=${X.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:G.to,registration_ids:G.registrationIds,topic:G.topic?`/topics/${G.topic}`:void 0,condition:G.condition,notification:G.notification,data:G.data,priority:G.priority??"high",time_to_live:G.ttl,collapse_key:G.collapseKey})});if(!Q.ok){let Y=await Q.text();return j.error(`FCM push failed: ${Y}`),{success:!1,provider:"fcm",message:`HTTP ${Q.status}: ${Y}`}}let W=await Q.json();if(W.failure>0)j.warn(`FCM: ${W.failure} notifications failed`);return j.info(`FCM push sent: ${W.success} successful, ${W.failure} failed`),{success:W.failure===0,provider:"fcm",message:`Sent ${W.success} notifications`,messageId:W.results?.[0]?.messageId,data:W}}catch(Q){let W=Q instanceof Error?Q:Error(String(Q));return j.error(`FCM push error: ${W.message}`),{success:!1,provider:"fcm",message:W.message}}}async function U(G){if(!X.serviceAccount||!X.projectId)return z(G);try{let Q=await r(),W=`${a}/${X.projectId}/messages:send`,Y={message:{notification:G.notification,data:G.data,android:{priority:G.priority??"high",ttl:G.ttl?`${G.ttl}s`:void 0,collapseKey:G.collapseKey},apns:{headers:{"apns-priority":G.priority==="high"?"10":"5"}}}};if(G.to)Y.message.token=G.to;else if(G.topic)Y.message.topic=G.topic;else if(G.condition)Y.message.condition=G.condition;let Z=await fetch(W,{method:"POST",headers:{Authorization:`Bearer ${Q}`,"Content-Type":"application/json"},body:JSON.stringify(Y)});if(!Z.ok){let $=await Z.text();return j.error(`FCM v1 push failed: ${$}`),{success:!1,provider:"fcm",message:`HTTP ${Z.status}: ${$}`}}let q=await Z.json();return j.info(`FCM v1 push sent: ${q.name}`),{success:!0,provider:"fcm",message:"Notification sent successfully",messageId:q.name}}catch(Q){let W=Q instanceof Error?Q:Error(String(Q));return j.error(`FCM v1 push error: ${W.message}`),{success:!1,provider:"fcm",message:W.message}}}async function C(G,Q){if(X.serviceAccount&&X.projectId)return Promise.all(G.map((W)=>U({...Q,to:W})));return[await z({...Q,registrationIds:G})]}async function B(G,Q){return U({...Q,topic:G})}async function y(G,Q){if(!X.serverKey)throw Error("FCM server key required for topic subscription");return(await fetch("https://iid.googleapis.com/iid/v1:batchAdd",{method:"POST",headers:{Authorization:`key=${X.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${Q}`,registration_tokens:G})})).ok}async function h(G,Q){if(!X.serverKey)throw Error("FCM server key required for topic unsubscription");return(await fetch("https://iid.googleapis.com/iid/v1:batchRemove",{method:"POST",headers:{Authorization:`key=${X.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${Q}`,registration_tokens:G})})).ok}var t={send:U,sendLegacy:z,sendMulticast:C,sendToTopic:B,subscribeToTopic:y,unsubscribeFromTopic:h,configure:K};import{Buffer as J}from"buffer";import{createCipheriv as n,createECDH as i,createHmac as k,createPrivateKey as o,createSign as s,generateKeyPairSync as e,randomBytes as GG}from"crypto";var d="prime256v1";function _(G){return G.toString("base64url")}function M(G){return J.from(G,"base64url")}function DG(){let{publicKey:G,privateKey:Q}=e("ec",{namedCurve:d}),W=G.export({type:"spki",format:"der"}),Y=Q.export({format:"jwk"});return{publicKey:_(W.subarray(W.length-65)),privateKey:String(Y.d)}}function QG(G){let Q=new URL(G);return`${Q.protocol}//${Q.host}`}function WG(G){let W=Math.floor(G.nowSeconds??Date.now()/1000)+Math.min(G.expiresInSeconds??43200,86400),Y=_(J.from(JSON.stringify({typ:"JWT",alg:"ES256"}))),Z=_(J.from(JSON.stringify({aud:QG(G.endpoint),exp:W,sub:G.subject}))),q=`${Y}.${Z}`,$=o({key:{kty:"EC",crv:"P-256",d:G.vapid.privateKey,x:_(M(G.vapid.publicKey).subarray(1,33)),y:_(M(G.vapid.publicKey).subarray(33,65))},format:"jwk"}),D=s("SHA256");D.update(q);let N=D.sign({key:$,dsaEncoding:"ieee-p1363"});return{Authorization:`vapid t=${q}.${_(N)}, k=${G.vapid.publicKey}`,"Crypto-Key":`p256ecdsa=${G.vapid.publicKey}`}}function P(G,Q,W,Y){let Z=k("sha256",G).update(Q).digest();return k("sha256",Z).update(J.concat([W,J.from([1])])).digest().subarray(0,Y)}function YG(G,Q,W=GG(16)){let Y=M(G.keys.p256dh),Z=M(G.keys.auth),q=i(d);q.generateKeys();let $=q.getPublicKey(),D=q.computeSecret(Y),N=J.concat([J.from("WebPush: info\x00"),Y,$]),O=P(Z,D,N,32),F=P(W,O,J.from("Content-Encoding: aes128gcm\x00"),16),A=P(W,O,J.from("Content-Encoding: nonce\x00"),12),V=n("aes-128-gcm",F,A),R=J.concat([J.from(Q,"utf8"),J.from([2])]),S=J.concat([V.update(R),V.final(),V.getAuthTag()]),H=J.alloc(4);return H.writeUInt32BE(4096,0),J.concat([W,H,J.from([$.length]),$,S])}async function NG(G){let{subscription:Q,vapid:W,subject:Y}=G;if(!Q?.endpoint)return{success:!1,status:0,expired:!1,error:"no endpoint"};let Z={...WG({endpoint:Q.endpoint,vapid:W,subject:Y}),TTL:String(G.ttl??86400),Urgency:G.urgency??"normal"};if(G.topic)Z.Topic=G.topic;let q;if(G.payload){if(!Q.keys?.p256dh||!Q.keys?.auth)return{success:!1,status:0,expired:!0,error:"subscription has no keys"};q=YG(Q,G.payload),Z["Content-Encoding"]="aes128gcm",Z["Content-Type"]="application/octet-stream",Z["Content-Length"]=String(q.length)}try{let $=await fetch(Q.endpoint,{method:"POST",headers:Z,body:q}),D=$.status===404||$.status===410;if($.status>=200&&$.status<300)return{success:!0,status:$.status,expired:!1};return{success:!1,status:$.status,expired:D,error:(await $.text().catch(()=>"")).slice(0,500)||`push service answered ${$.status}`}}catch($){return{success:!1,status:0,expired:!1,error:$ instanceof Error?$.message:String($)}}}async function VG(G,Q,W={}){let Y=W.driver??"expo";if(Y==="expo")return L({to:G,title:Q.title,body:Q.body,data:Q.data,badge:Q.badge,sound:Q.sound,priority:Q.priority});if(Y==="fcm"){let Z=Array.isArray(G)?G:[G];if(Z.length===1)return U({to:Z[0],notification:{title:Q.title??"",body:Q.body},data:Q.data,priority:Q.priority==="default"?"normal":Q.priority});let q=await C(Z,{notification:{title:Q.title??"",body:Q.body},data:Q.data,priority:Q.priority==="default"?"normal":Q.priority});return{success:q.every((D)=>D.success),provider:"fcm",message:`Sent to ${q.filter((D)=>D.success).length}/${q.length} devices`}}return{success:!1,provider:Y,message:`Unknown push driver: ${Y}`}}function jG(G){K(G)}export{NG as sendWebPush,VG as send,DG as generateVapidKeys,b as fcm,v as expo,YG as encryptPayload,jG as configureFCM,WG as buildVapidHeaders};
2
+ var m=Object.defineProperty;var p=(G)=>G;function g(G,Q){this[G]=p.bind(null,Q)}var w=(G,Q)=>{for(var W in Q)m(G,W,{get:Q[W],enumerable:!0,configurable:!0,set:g.bind(Q,W)})};var v={};w(v,{sendBatch:()=>T,send:()=>L,isExpoPushToken:()=>E,getReceipts:()=>x,default:()=>a,Send:()=>L});import{log as I}from"@stacksjs/cli";var c="https://exp.host/--/api/v2/push/send";function E(G){return/^Expo(?:nent)?PushToken\[.+\]$/.test(G)||/^[a-zA-Z0-9-_]+$/.test(G)}async function L(G){let Q=Array.isArray(G.to)?G.to:[G.to],W=Q.filter((Z)=>!E(Z));if(W.length>0)I.warn(`Invalid Expo push tokens found: ${W.join(", ")}`);let Y=Q.filter(E);if(Y.length===0)return{success:!1,provider:"expo",message:"No valid Expo push tokens provided"};try{let Z=Y.map((A)=>({to:A,title:G.title,body:G.body,data:G.data,sound:G.sound??"default",badge:G.badge,channelId:G.channelId,priority:G.priority??"high",ttl:G.ttl})),q=4096;for(let A of Z){let V=new TextEncoder().encode(JSON.stringify(A)).length;if(V>4096)return{success:!1,provider:"expo",message:`Expo message payload (${V} bytes) exceeds 4 KiB per-message limit`}}let $=await fetch(c,{method:"POST",headers:{Accept:"application/json","Accept-Encoding":"gzip, deflate","Content-Type":"application/json"},body:JSON.stringify(Z)});if(!$.ok){let A=await $.text();return I.error(`Expo push failed: ${A}`),{success:!1,provider:"expo",message:`HTTP ${$.status}: ${A}`}}let N=(await $.json()).data,O=N.filter((A)=>A.status==="error");if(O.length>0)I.warn(`Some push notifications failed: ${JSON.stringify(O)}`);let F=N.filter((A)=>A.status==="ok").length;return I.info(`Expo push sent: ${F}/${N.length} successful`),{success:O.length===0,provider:"expo",message:`Sent ${F}/${N.length} notifications`,messageId:N.find((A)=>A.id)?.id,data:{tickets:N}}}catch(Z){let q=Z instanceof Error?Z:Error(String(Z));return I.error(`Expo push error: ${q.message}`),{success:!1,provider:"expo",message:q.message}}}async function x(G){let Q=await fetch("https://exp.host/--/api/v2/push/getReceipts",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({ids:G})});if(!Q.ok)throw Error(`Failed to get receipts: ${Q.status}`);return(await Q.json()).data}async function T(G,Q=100){let W=[];for(let Y=0;Y<G.length;Y+=Q){let Z=G.slice(Y,Y+Q),q=await Promise.all(Z.map(($)=>L($)));W.push(...q)}return W}var a={send:L,sendBatch:T,getReceipts:x,isExpoPushToken:E};var b={};w(b,{unsubscribeFromTopic:()=>h,subscribeToTopic:()=>y,sendToTopic:()=>B,sendMulticast:()=>C,sendLegacy:()=>z,send:()=>U,default:()=>i,configure:()=>K,Send:()=>U});import{log as j}from"@stacksjs/cli";var r="https://fcm.googleapis.com/fcm/send",t="https://fcm.googleapis.com/v1/projects",X={};function K(G){X={...X,...G}}async function n(){if(!X.serviceAccount)throw Error("Service account not configured for FCM v1 API");let{clientEmail:G,privateKey:Q}=X.serviceAccount,W=Math.floor(Date.now()/1000),Y={alg:"RS256",typ:"JWT"},Z={iss:G,scope:"https://www.googleapis.com/auth/firebase.messaging",aud:"https://oauth2.googleapis.com/token",iat:W,exp:W+3600},q=new TextEncoder,$=btoa(JSON.stringify(Y)),D=btoa(JSON.stringify(Z)),N=`${$}.${D}`,O=Q.replace(/-----BEGIN PRIVATE KEY-----/,"").replace(/-----END PRIVATE KEY-----/,"").replace(/\n/g,""),F=Uint8Array.from(atob(O),(f)=>f.charCodeAt(0)),A=await crypto.subtle.importKey("pkcs8",F,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),V=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",A,q.encode(N)),R=btoa(String.fromCharCode(...new Uint8Array(V))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),S=`${$}.${D}.${R}`,H=await fetch("https://oauth2.googleapis.com/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${S}`});if(!H.ok)throw Error(`Failed to get access token: ${H.status}`);return(await H.json()).access_token}async function z(G){if(!X.serverKey)return{success:!1,provider:"fcm",message:"FCM server key not configured"};try{let Q=await fetch(r,{method:"POST",headers:{Authorization:`key=${X.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:G.to,registration_ids:G.registrationIds,topic:G.topic?`/topics/${G.topic}`:void 0,condition:G.condition,notification:G.notification,data:G.data,priority:G.priority??"high",time_to_live:G.ttl,collapse_key:G.collapseKey})});if(!Q.ok){let Y=await Q.text();return j.error(`FCM push failed: ${Y}`),{success:!1,provider:"fcm",message:`HTTP ${Q.status}: ${Y}`}}let W=await Q.json();if(W.failure>0)j.warn(`FCM: ${W.failure} notifications failed`);return j.info(`FCM push sent: ${W.success} successful, ${W.failure} failed`),{success:W.failure===0,provider:"fcm",message:`Sent ${W.success} notifications`,messageId:W.results?.[0]?.messageId,data:W}}catch(Q){let W=Q instanceof Error?Q:Error(String(Q));return j.error(`FCM push error: ${W.message}`),{success:!1,provider:"fcm",message:W.message}}}async function U(G){if(!X.serviceAccount||!X.projectId)return z(G);try{let Q=await n(),W=`${t}/${X.projectId}/messages:send`,Y={message:{notification:G.notification,data:G.data,android:{priority:G.priority??"high",ttl:G.ttl?`${G.ttl}s`:void 0,collapseKey:G.collapseKey},apns:{headers:{"apns-priority":G.priority==="high"?"10":"5"}}}};if(G.to)Y.message.token=G.to;else if(G.topic)Y.message.topic=G.topic;else if(G.condition)Y.message.condition=G.condition;let Z=await fetch(W,{method:"POST",headers:{Authorization:`Bearer ${Q}`,"Content-Type":"application/json"},body:JSON.stringify(Y)});if(!Z.ok){let $=await Z.text();return j.error(`FCM v1 push failed: ${$}`),{success:!1,provider:"fcm",message:`HTTP ${Z.status}: ${$}`}}let q=await Z.json();return j.info(`FCM v1 push sent: ${q.name}`),{success:!0,provider:"fcm",message:"Notification sent successfully",messageId:q.name}}catch(Q){let W=Q instanceof Error?Q:Error(String(Q));return j.error(`FCM v1 push error: ${W.message}`),{success:!1,provider:"fcm",message:W.message}}}async function C(G,Q){if(X.serviceAccount&&X.projectId)return Promise.all(G.map((W)=>U({...Q,to:W})));return[await z({...Q,registrationIds:G})]}async function B(G,Q){return U({...Q,topic:G})}async function y(G,Q){if(!X.serverKey)throw Error("FCM server key required for topic subscription");return(await fetch("https://iid.googleapis.com/iid/v1:batchAdd",{method:"POST",headers:{Authorization:`key=${X.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${Q}`,registration_tokens:G})})).ok}async function h(G,Q){if(!X.serverKey)throw Error("FCM server key required for topic unsubscription");return(await fetch("https://iid.googleapis.com/iid/v1:batchRemove",{method:"POST",headers:{Authorization:`key=${X.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${Q}`,registration_tokens:G})})).ok}var i={send:U,sendLegacy:z,sendMulticast:C,sendToTopic:B,subscribeToTopic:y,unsubscribeFromTopic:h,configure:K};import{Buffer as J}from"buffer";import{createCipheriv as o,createECDH as s,createHmac as k,createPrivateKey as e,createSign as GG,generateKeyPairSync as QG,randomBytes as WG}from"crypto";var d="prime256v1";function _(G){return G.toString("base64url")}function M(G){return J.from(G,"base64url")}function YG(){let{publicKey:G,privateKey:Q}=QG("ec",{namedCurve:d}),W=G.export({type:"spki",format:"der"}),Y=Q.export({format:"jwk"});return{publicKey:_(W.subarray(W.length-65)),privateKey:String(Y.d)}}function ZG(G){let Q=new URL(G);return`${Q.protocol}//${Q.host}`}function l(G){let W=Math.floor(G.nowSeconds??Date.now()/1000)+Math.min(G.expiresInSeconds??43200,86400),Y=_(J.from(JSON.stringify({typ:"JWT",alg:"ES256"}))),Z=_(J.from(JSON.stringify({aud:ZG(G.endpoint),exp:W,sub:G.subject}))),q=`${Y}.${Z}`,$=e({key:{kty:"EC",crv:"P-256",d:G.vapid.privateKey,x:_(M(G.vapid.publicKey).subarray(1,33)),y:_(M(G.vapid.publicKey).subarray(33,65))},format:"jwk"}),D=GG("SHA256");D.update(q);let N=D.sign({key:$,dsaEncoding:"ieee-p1363"});return{Authorization:`vapid t=${q}.${_(N)}, k=${G.vapid.publicKey}`,"Crypto-Key":`p256ecdsa=${G.vapid.publicKey}`}}function P(G,Q,W,Y){let Z=k("sha256",G).update(Q).digest();return k("sha256",Z).update(J.concat([W,J.from([1])])).digest().subarray(0,Y)}function u(G,Q,W=WG(16)){let Y=M(G.keys.p256dh),Z=M(G.keys.auth),q=s(d);q.generateKeys();let $=q.getPublicKey(),D=q.computeSecret(Y),N=J.concat([J.from("WebPush: info\x00"),Y,$]),O=P(Z,D,N,32),F=P(W,O,J.from("Content-Encoding: aes128gcm\x00"),16),A=P(W,O,J.from("Content-Encoding: nonce\x00"),12),V=o("aes-128-gcm",F,A),R=J.concat([J.from(Q,"utf8"),J.from([2])]),S=J.concat([V.update(R),V.final(),V.getAuthTag()]),H=J.alloc(4);return H.writeUInt32BE(4096,0),J.concat([W,H,J.from([$.length]),$,S])}async function $G(G){let{subscription:Q,vapid:W,subject:Y}=G;if(!Q?.endpoint)return{success:!1,status:0,expired:!1,error:"no endpoint"};let Z={...l({endpoint:Q.endpoint,vapid:W,subject:Y}),TTL:String(G.ttl??86400),Urgency:G.urgency??"normal"};if(G.topic)Z.Topic=G.topic;let q;if(G.payload){if(!Q.keys?.p256dh||!Q.keys?.auth)return{success:!1,status:0,expired:!0,error:"subscription has no keys"};q=u(Q,G.payload),Z["Content-Encoding"]="aes128gcm",Z["Content-Type"]="application/octet-stream",Z["Content-Length"]=String(q.length)}try{let $=await fetch(Q.endpoint,{method:"POST",headers:Z,body:q}),D=$.status===404||$.status===410;if($.status>=200&&$.status<300)return{success:!0,status:$.status,expired:!1};return{success:!1,status:$.status,expired:D,error:(await $.text().catch(()=>"")).slice(0,500)||`push service answered ${$.status}`}}catch($){return{success:!1,status:0,expired:!1,error:$ instanceof Error?$.message:String($)}}}async function VG(G,Q,W={}){let Y=W.driver??"expo";if(Y==="expo")return L({to:G,title:Q.title,body:Q.body,data:Q.data,badge:Q.badge,sound:Q.sound,priority:Q.priority});if(Y==="fcm"){let Z=Array.isArray(G)?G:[G];if(Z.length===1)return U({to:Z[0],notification:{title:Q.title??"",body:Q.body},data:Q.data,priority:Q.priority==="default"?"normal":Q.priority});let q=await C(Z,{notification:{title:Q.title??"",body:Q.body},data:Q.data,priority:Q.priority==="default"?"normal":Q.priority});return{success:q.every((D)=>D.success),provider:"fcm",message:`Sent to ${q.filter((D)=>D.success).length}/${q.length} devices`}}return{success:!1,provider:Y,message:`Unknown push driver: ${Y}`}}function jG(G){K(G)}export{$G as sendWebPush,VG as send,YG as generateVapidKeys,b as fcm,v as expo,u as encryptPayload,jG as configureFCM,l as buildVapidHeaders};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/push",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.308",
5
+ "version": "0.70.310",
6
6
  "description": "The Stacks Push integration",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -56,9 +56,9 @@
56
56
  "prepublishOnly": "bun run build"
57
57
  },
58
58
  "devDependencies": {
59
- "@stacksjs/cli": "0.70.308",
60
- "@stacksjs/config": "0.70.308",
59
+ "@stacksjs/cli": "0.70.310",
60
+ "@stacksjs/config": "0.70.310",
61
61
  "better-dx": "^0.2.17",
62
- "@stacksjs/error-handling": "0.70.308"
62
+ "@stacksjs/error-handling": "0.70.310"
63
63
  }
64
64
  }