@stacksjs/push 0.75.63 → 0.75.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,16 @@
1
1
  import type { PushResult } from '@stacksjs/types';
2
2
  /**
3
- * Configure FCM with server key or service account
3
+ * Configure FCM with a service account
4
4
  */
5
5
  export declare function configure(options: FCMConfig): void;
6
6
  /**
7
- * Send push notification via FCM Legacy API (using server key)
7
+ * Build the signed JWT that Google's OAuth2 endpoint accepts as a service
8
+ * account assertion.
9
+ *
10
+ * `nowSeconds` is a parameter rather than a `Date.now()` read so the encoding
11
+ * can be asserted against a fixed claim set.
8
12
  */
9
- export declare function sendLegacy(message: FCMMessage): Promise<PushResult>;
13
+ export declare function buildServiceAccountAssertion(clientEmail: string, privateKey: string, nowSeconds: number): Promise<string>;
10
14
  /**
11
15
  * Send push notification via FCM v1 API (using service account)
12
16
  */
@@ -14,11 +18,11 @@ export declare function send(message: FCMMessage): Promise<PushResult>;
14
18
  /**
15
19
  * Send to multiple tokens
16
20
  */
17
- export declare function sendMulticast(tokens: string[], message: Omit<FCMMessage, 'to' | 'registrationIds'>): Promise<PushResult[]>;
21
+ export declare function sendMulticast(tokens: string[], message: Omit<FCMMessage, 'to'>): Promise<PushResult[]>;
18
22
  /**
19
23
  * Send to a topic
20
24
  */
21
- export declare function sendToTopic(topic: string, message: Omit<FCMMessage, 'to' | 'registrationIds' | 'topic'>): Promise<PushResult>;
25
+ export declare function sendToTopic(topic: string, message: Omit<FCMMessage, 'to' | 'topic'>): Promise<PushResult>;
22
26
  /**
23
27
  * Subscribe tokens to a topic
24
28
  */
@@ -29,7 +33,6 @@ export declare function subscribeToTopic(tokens: string[], topic: string): Promi
29
33
  export declare function unsubscribeFromTopic(tokens: string[], topic: string): Promise<boolean>;
30
34
  declare const __dtsx_default_export__: {
31
35
  send: typeof send;
32
- sendLegacy: typeof sendLegacy;
33
36
  sendMulticast: typeof sendMulticast;
34
37
  sendToTopic: typeof sendToTopic;
35
38
  subscribeToTopic: typeof subscribeToTopic;
@@ -38,7 +41,6 @@ declare const __dtsx_default_export__: {
38
41
  };
39
42
  export declare interface FCMMessage {
40
43
  to?: string
41
- registrationIds?: string[]
42
44
  topic?: string
43
45
  condition?: string
44
46
  notification?: {
@@ -57,21 +59,11 @@ export declare interface FCMMessage {
57
59
  collapseKey?: string
58
60
  }
59
61
  export declare interface FCMConfig {
60
- serverKey?: string
61
62
  projectId?: string
62
63
  serviceAccount?: {
63
64
  clientEmail: string
64
65
  privateKey: string
65
66
  }
66
67
  }
67
- export declare interface FCMResponse {
68
- multicastId?: number
69
- success: number
70
- failure: number
71
- results?: Array<{
72
- messageId?: string
73
- error?: string
74
- }>
75
- }
76
68
  export { send as Send };
77
69
  export default __dtsx_default_export__;
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var U=Object.defineProperty;var D=(e)=>e;function H(e,t){this[e]=D.bind(null,t)}var M=(e,t)=>{for(var r in t)U(e,r,{get:t[r],enumerable:!0,configurable:!0,set:H.bind(t,r)})};var I={};M(I,{Send:()=>v,default:()=>z,getReceipts:()=>K,isExpoPushToken:()=>P,send:()=>v,sendBatch:()=>A});import{log as x}from"@stacksjs/cli";var L="https://exp.host/--/api/v2/push/send";function P(e){return/^Expo(?:nent)?PushToken\[.+\]$/.test(e)||/^[a-zA-Z0-9-_]+$/.test(e)}async function v(e){let t=Array.isArray(e.to)?e.to:[e.to],r=t.filter((o)=>!P(o));if(r.length>0)x.warn(`Invalid Expo push tokens found: ${r.join(", ")}`);let s=t.filter(P);if(s.length===0)return{success:!1,provider:"expo",message:"No valid Expo push tokens provided"};try{let o=s.map((a)=>({to:a,title:e.title,body:e.body,data:e.data,sound:e.sound??"default",badge:e.badge,channelId:e.channelId,priority:e.priority??"high",ttl:e.ttl})),i=4096;for(let a of o){let l=new TextEncoder().encode(JSON.stringify(a)).length;if(l>4096)return{success:!1,provider:"expo",message:`Expo message payload (${l} bytes) exceeds 4 KiB per-message limit`}}let n=await fetch(L,{method:"POST",headers:{Accept:"application/json","Accept-Encoding":"gzip, deflate","Content-Type":"application/json"},body:JSON.stringify(o)});if(!n.ok){let a=await n.text();return x.error(`Expo push failed: ${a}`),{success:!1,provider:"expo",message:`HTTP ${n.status}: ${a}`}}let d=(await n.json()).data,f=d.filter((a)=>a.status==="error");if(f.length>0)x.warn(`Some push notifications failed: ${JSON.stringify(f)}`);let y=d.filter((a)=>a.status==="ok").length;return x.info(`Expo push sent: ${y}/${d.length} successful`),{success:f.length===0,provider:"expo",message:`Sent ${y}/${d.length} notifications`,messageId:d.find((a)=>a.id)?.id,data:{tickets:d}}}catch(o){let i=o instanceof Error?o:Error(String(o));return x.error(`Expo push error: ${i.message}`),{success:!1,provider:"expo",message:i.message}}}async function K(e){let t=await fetch("https://exp.host/--/api/v2/push/getReceipts",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({ids:e})});if(!t.ok)throw Error(`Failed to get receipts: ${t.status}`);return(await t.json()).data}async function A(e,t=100){let r=[];for(let s=0;s<e.length;s+=t){let o=e.slice(s,s+t),i=await Promise.all(o.map((n)=>v(n)));r.push(...i)}return r}var z={send:v,sendBatch:A,getReceipts:K,isExpoPushToken:P};var _={};M(_,{Send:()=>m,configure:()=>w,default:()=>Y,send:()=>m,sendLegacy:()=>S,sendMulticast:()=>k,sendToTopic:()=>O,subscribeToTopic:()=>j,unsubscribeFromTopic:()=>F});import{log as g}from"@stacksjs/cli";var G="https://fcm.googleapis.com/fcm/send",X="https://fcm.googleapis.com/v1/projects",u={};function w(e){u={...u,...e}}async function q(){if(!u.serviceAccount)throw Error("Service account not configured for FCM v1 API");let{clientEmail:e,privateKey:t}=u.serviceAccount,r=Math.floor(Date.now()/1000),s={alg:"RS256",typ:"JWT"},o={iss:e,scope:"https://www.googleapis.com/auth/firebase.messaging",aud:"https://oauth2.googleapis.com/token",iat:r,exp:r+3600},i=new TextEncoder,n=btoa(JSON.stringify(s)),p=btoa(JSON.stringify(o)),d=`${n}.${p}`,f=t.replace(/-----BEGIN PRIVATE KEY-----/,"").replace(/-----END PRIVATE KEY-----/,"").replace(/\n/g,""),y=Uint8Array.from(atob(f),(V)=>V.charCodeAt(0)),a=await crypto.subtle.importKey("pkcs8",y,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),l=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",a,i.encode(d)),C=btoa(String.fromCharCode(...new Uint8Array(l))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),T=`${n}.${p}.${C}`,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=${T}`});if(!h.ok)throw Error(`Failed to get access token: ${h.status}`);return(await h.json()).access_token}async function S(e){if(!u.serverKey)return{success:!1,provider:"fcm",message:"FCM server key not configured"};try{let t=await fetch(G,{method:"POST",headers:{Authorization:`key=${u.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:e.to,registration_ids:e.registrationIds,topic:e.topic?`/topics/${e.topic}`:void 0,condition:e.condition,notification:e.notification,data:e.data,priority:e.priority??"high",time_to_live:e.ttl,collapse_key:e.collapseKey})});if(!t.ok){let s=await t.text();return g.error(`FCM push failed: ${s}`),{success:!1,provider:"fcm",message:`HTTP ${t.status}: ${s}`}}let r=await t.json();if(r.failure>0)g.warn(`FCM: ${r.failure} notifications failed`);return g.info(`FCM push sent: ${r.success} successful, ${r.failure} failed`),{success:r.failure===0,provider:"fcm",message:`Sent ${r.success} notifications`,messageId:r.results?.[0]?.messageId,data:r}}catch(t){let r=t instanceof Error?t:Error(String(t));return g.error(`FCM push error: ${r.message}`),{success:!1,provider:"fcm",message:r.message}}}async function m(e){if(!u.serviceAccount||!u.projectId)return S(e);try{let t=await q(),r=`${X}/${u.projectId}/messages:send`,s={message:{notification:e.notification,data:e.data,android:{priority:e.priority??"high",ttl:e.ttl?`${e.ttl}s`:void 0,collapseKey:e.collapseKey},apns:{headers:{"apns-priority":e.priority==="high"?"10":"5"}}}};if(e.to)s.message.token=e.to;else if(e.topic)s.message.topic=e.topic;else if(e.condition)s.message.condition=e.condition;let o=await fetch(r,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify(s)});if(!o.ok){let n=await o.text();return g.error(`FCM v1 push failed: ${n}`),{success:!1,provider:"fcm",message:`HTTP ${o.status}: ${n}`}}let i=await o.json();return g.info(`FCM v1 push sent: ${i.name}`),{success:!0,provider:"fcm",message:"Notification sent successfully",messageId:i.name}}catch(t){let r=t instanceof Error?t:Error(String(t));return g.error(`FCM v1 push error: ${r.message}`),{success:!1,provider:"fcm",message:r.message}}}async function k(e,t){if(u.serviceAccount&&u.projectId)return Promise.all(e.map((r)=>m({...t,to:r})));return[await S({...t,registrationIds:e})]}async function O(e,t){return m({...t,topic:e})}async function j(e,t){if(!u.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=${u.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${t}`,registration_tokens:e})})).ok}async function F(e,t){if(!u.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=${u.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${t}`,registration_tokens:e})})).ok}var Y={send:m,sendLegacy:S,sendMulticast:k,sendToTopic:O,subscribeToTopic:j,unsubscribeFromTopic:F,configure:w};import{Buffer as c}from"buffer";import{createCipheriv as Z,createECDH as Q,createHmac as B,createPrivateKey as ee,createSign as te,generateKeyPairSync as re,randomBytes as se}from"crypto";var N="prime256v1";function b(e){return e.toString("base64url")}function E(e){return c.from(e,"base64url")}function oe(){let{publicKey:e,privateKey:t}=re("ec",{namedCurve:N}),r=e.export({type:"spki",format:"der"}),s=t.export({format:"jwk"});return{publicKey:b(r.subarray(r.length-65)),privateKey:String(s.d)}}function ne(e){let t=new URL(e);return`${t.protocol}//${t.host}`}function J(e){let r=Math.floor(e.nowSeconds??Date.now()/1000)+Math.min(e.expiresInSeconds??43200,86400),s=b(c.from(JSON.stringify({typ:"JWT",alg:"ES256"}))),o=b(c.from(JSON.stringify({aud:ne(e.endpoint),exp:r,sub:e.subject}))),i=`${s}.${o}`,n=ee({key:{kty:"EC",crv:"P-256",d:e.vapid.privateKey,x:b(E(e.vapid.publicKey).subarray(1,33)),y:b(E(e.vapid.publicKey).subarray(33,65))},format:"jwk"}),p=te("SHA256");p.update(i);let d=p.sign({key:n,dsaEncoding:"ieee-p1363"});return{Authorization:`vapid t=${i}.${b(d)}, k=${e.vapid.publicKey}`,"Crypto-Key":`p256ecdsa=${e.vapid.publicKey}`}}function R(e,t,r,s){let o=B("sha256",e).update(t).digest();return B("sha256",o).update(c.concat([r,c.from([1])])).digest().subarray(0,s)}function W(e,t,r=se(16)){let s=E(e.keys.p256dh),o=E(e.keys.auth),i=Q(N);i.generateKeys();let n=i.getPublicKey(),p=i.computeSecret(s),d=c.concat([c.from("WebPush: info\x00"),s,n]),f=R(o,p,d,32),y=R(r,f,c.from("Content-Encoding: aes128gcm\x00"),16),a=R(r,f,c.from("Content-Encoding: nonce\x00"),12),l=Z("aes-128-gcm",y,a),C=c.concat([c.from(t,"utf8"),c.from([2])]),T=c.concat([l.update(C),l.final(),l.getAuthTag()]),h=c.alloc(4);return h.writeUInt32BE(4096,0),c.concat([r,h,c.from([n.length]),n,T])}async function ie(e){let{subscription:t,vapid:r,subject:s}=e;if(!t?.endpoint)return{success:!1,status:0,expired:!1,error:"no endpoint"};let o={...J({endpoint:t.endpoint,vapid:r,subject:s}),TTL:String(e.ttl??86400),Urgency:e.urgency??"normal"};if(e.topic)o.Topic=e.topic;let i;if(e.payload){if(!t.keys?.p256dh||!t.keys?.auth)return{success:!1,status:0,expired:!0,error:"subscription has no keys"};i=W(t,e.payload),o["Content-Encoding"]="aes128gcm",o["Content-Type"]="application/octet-stream",o["Content-Length"]=String(i.length)}try{let n=await fetch(t.endpoint,{method:"POST",headers:o,body:i?new Uint8Array(i):void 0}),p=n.status===404||n.status===410;if(n.status>=200&&n.status<300)return{success:!0,status:n.status,expired:!1};return{success:!1,status:n.status,expired:p,error:(await n.text().catch(()=>"")).slice(0,500)||`push service answered ${n.status}`}}catch(n){return{success:!1,status:0,expired:!1,error:n instanceof Error?n.message:String(n)}}}async function ge(e,t,r={}){let s=r.driver??"expo";if(s==="expo")return v({to:e,title:t.title,body:t.body,data:t.data,badge:t.badge,sound:t.sound,priority:t.priority});if(s==="fcm"){let o=Array.isArray(e)?e:[e];if(o.length===1)return m({to:o[0],notification:{title:t.title??"",body:t.body},data:t.data,priority:t.priority==="default"?"normal":t.priority});let i=await k(o,{notification:{title:t.title??"",body:t.body},data:t.data,priority:t.priority==="default"?"normal":t.priority});return{success:i.every((p)=>p.success),provider:"fcm",message:`Sent to ${i.filter((p)=>p.success).length}/${i.length} devices`}}return{success:!1,provider:s,message:`Unknown push driver: ${s}`}}function ye(e){w(e)}export{J as buildVapidHeaders,ye as configureFCM,W as encryptPayload,I as expo,_ as fcm,oe as generateVapidKeys,ge as send,ie as sendWebPush};
2
+ var z=Object.defineProperty;var G=(e)=>e;function X(e,t){this[e]=G.bind(null,t)}var A=(e,t)=>{for(var r in t)z(e,r,{get:t[r],enumerable:!0,configurable:!0,set:X.bind(t,r)})};var I={};A(I,{Send:()=>b,default:()=>Y,getReceipts:()=>K,isExpoPushToken:()=>x,send:()=>b,sendBatch:()=>M});import{log as m}from"@stacksjs/cli";var q="https://exp.host/--/api/v2/push/send";function x(e){return/^Expo(?:nent)?PushToken\[.+\]$/.test(e)||/^[a-zA-Z0-9-_]+$/.test(e)}async function b(e){let t=Array.isArray(e.to)?e.to:[e.to],r=t.filter((n)=>!x(n));if(r.length>0)m.warn(`Invalid Expo push tokens found: ${r.join(", ")}`);let s=t.filter(x);if(s.length===0)return{success:!1,provider:"expo",message:"No valid Expo push tokens provided"};try{let n=s.map((a)=>({to:a,title:e.title,body:e.body,data:e.data,sound:e.sound??"default",badge:e.badge,channelId:e.channelId,priority:e.priority??"high",ttl:e.ttl})),i=4096;for(let a of n){let g=new TextEncoder().encode(JSON.stringify(a)).length;if(g>4096)return{success:!1,provider:"expo",message:`Expo message payload (${g} bytes) exceeds 4 KiB per-message limit`}}let o=await fetch(q,{method:"POST",headers:{Accept:"application/json","Accept-Encoding":"gzip, deflate","Content-Type":"application/json"},body:JSON.stringify(n)});if(!o.ok){let a=await o.text();return m.error(`Expo push failed: ${a}`),{success:!1,provider:"expo",message:`HTTP ${o.status}: ${a}`}}let p=(await o.json()).data,l=p.filter((a)=>a.status==="error");if(l.length>0)m.warn(`Some push notifications failed: ${JSON.stringify(l)}`);let f=p.filter((a)=>a.status==="ok").length;return m.info(`Expo push sent: ${f}/${p.length} successful`),{success:l.length===0,provider:"expo",message:`Sent ${f}/${p.length} notifications`,messageId:p.find((a)=>a.id)?.id,data:{tickets:p}}}catch(n){let i=n instanceof Error?n:Error(String(n));return m.error(`Expo push error: ${i.message}`),{success:!1,provider:"expo",message:i.message}}}async function K(e){let t=await fetch("https://exp.host/--/api/v2/push/getReceipts",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({ids:e})});if(!t.ok)throw Error(`Failed to get receipts: ${t.status}`);return(await t.json()).data}async function M(e,t=100){let r=[];for(let s=0;s<e.length;s+=t){let n=e.slice(s,s+t),i=await Promise.all(n.map((o)=>b(o)));r.push(...i)}return r}var Y={send:b,sendBatch:M,getReceipts:K,isExpoPushToken:x};var J={};A(J,{Send:()=>y,buildServiceAccountAssertion:()=>O,configure:()=>v,default:()=>Z,send:()=>y,sendMulticast:()=>S,sendToTopic:()=>F,subscribeToTopic:()=>W,unsubscribeFromTopic:()=>U});import{log as P}from"@stacksjs/cli";var j="https://fcm.googleapis.com/v1/projects",d={};function v(e){d={...d,...e}}function T(e){return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}async function O(e,t,r){let s={alg:"RS256",typ:"JWT"},n={iss:e,scope:"https://www.googleapis.com/auth/firebase.messaging",aud:"https://oauth2.googleapis.com/token",iat:r,exp:r+3600},i=new TextEncoder,o=T(JSON.stringify(s)),u=T(JSON.stringify(n)),p=`${o}.${u}`,l=t.replace(/-----BEGIN PRIVATE KEY-----/,"").replace(/-----END PRIVATE KEY-----/,"").replace(/\s/g,""),f=Uint8Array.from(atob(l),(k)=>k.charCodeAt(0)),a=await crypto.subtle.importKey("pkcs8",f,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),g=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",a,i.encode(p)),E=T(String.fromCharCode(...new Uint8Array(g)));return`${o}.${u}.${E}`}async function B(){if(!d.serviceAccount)throw Error("Service account not configured for FCM v1 API");let{clientEmail:e,privateKey:t}=d.serviceAccount,r=await O(e,t,Math.floor(Date.now()/1000)),s=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=${r}`});if(!s.ok)throw Error(`Failed to get access token: ${s.status}`);return(await s.json()).access_token}async function y(e){if(!d.serviceAccount||!d.projectId)return{success:!1,provider:"fcm",message:"FCM service account and projectId not configured"};try{let t=await B(),r=`${j}/${d.projectId}/messages:send`,s={message:{notification:e.notification,data:e.data,android:{priority:e.priority??"high",ttl:e.ttl?`${e.ttl}s`:void 0,collapseKey:e.collapseKey},apns:{headers:{"apns-priority":e.priority==="high"?"10":"5"}}}};if(e.to)s.message.token=e.to;else if(e.topic)s.message.topic=e.topic;else if(e.condition)s.message.condition=e.condition;let n=await fetch(r,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify(s)});if(!n.ok){let o=await n.text();return P.error(`FCM v1 push failed: ${o}`),{success:!1,provider:"fcm",message:`HTTP ${n.status}: ${o}`}}let i=await n.json();return P.info(`FCM v1 push sent: ${i.name}`),{success:!0,provider:"fcm",message:"Notification sent successfully",messageId:i.name}}catch(t){let r=t instanceof Error?t:Error(String(t));return P.error(`FCM v1 push error: ${r.message}`),{success:!1,provider:"fcm",message:r.message}}}async function S(e,t){return Promise.all(e.map((r)=>y({...t,to:r})))}async function F(e,t){return y({...t,topic:e})}async function _(e,t,r){let s=await B(),n=`${j}/${d.projectId}/registrations/${encodeURIComponent(e)}/topicSubscriptions`,i=r?`${n}?topic_name=${encodeURIComponent(t)}`:`${n}/${encodeURIComponent(t)}`,o=await fetch(i,{method:r?"POST":"DELETE",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:"{}"});if(o.ok||r&&o.status===409)return!0;return P.error(`FCM topic ${r?"subscribe":"unsubscribe"} failed for topic ${t}: HTTP ${o.status}`),!1}function N(e){if(!d.serviceAccount||!d.projectId)throw Error(`FCM service account and projectId required for ${e}`)}async function W(e,t){return N("topic subscription"),(await Promise.all(e.map((s)=>_(s,t,!0)))).every(Boolean)}async function U(e,t){return N("topic unsubscription"),(await Promise.all(e.map((s)=>_(s,t,!1)))).every(Boolean)}var Z={send:y,sendMulticast:S,sendToTopic:F,subscribeToTopic:W,unsubscribeFromTopic:U,configure:v};import{Buffer as c}from"buffer";import{createCipheriv as Q,createECDH as ee,createHmac as V,createPrivateKey as te,createSign as re,generateKeyPairSync as se,randomBytes as ne}from"crypto";var D="prime256v1";function h(e){return e.toString("base64url")}function w(e){return c.from(e,"base64url")}function oe(){let{publicKey:e,privateKey:t}=se("ec",{namedCurve:D}),r=e.export({type:"spki",format:"der"}),s=t.export({format:"jwk"});return{publicKey:h(r.subarray(r.length-65)),privateKey:String(s.d)}}function ie(e){let t=new URL(e);return`${t.protocol}//${t.host}`}function H(e){let r=Math.floor(e.nowSeconds??Date.now()/1000)+Math.min(e.expiresInSeconds??43200,86400),s=h(c.from(JSON.stringify({typ:"JWT",alg:"ES256"}))),n=h(c.from(JSON.stringify({aud:ie(e.endpoint),exp:r,sub:e.subject}))),i=`${s}.${n}`,o=te({key:{kty:"EC",crv:"P-256",d:e.vapid.privateKey,x:h(w(e.vapid.publicKey).subarray(1,33)),y:h(w(e.vapid.publicKey).subarray(33,65))},format:"jwk"}),u=re("SHA256");u.update(i);let p=u.sign({key:o,dsaEncoding:"ieee-p1363"});return{Authorization:`vapid t=${i}.${h(p)}, k=${e.vapid.publicKey}`,"Crypto-Key":`p256ecdsa=${e.vapid.publicKey}`}}function C(e,t,r,s){let n=V("sha256",e).update(t).digest();return V("sha256",n).update(c.concat([r,c.from([1])])).digest().subarray(0,s)}function L(e,t,r=ne(16)){let s=w(e.keys.p256dh),n=w(e.keys.auth),i=ee(D);i.generateKeys();let o=i.getPublicKey(),u=i.computeSecret(s),p=c.concat([c.from("WebPush: info\x00"),s,o]),l=C(n,u,p,32),f=C(r,l,c.from("Content-Encoding: aes128gcm\x00"),16),a=C(r,l,c.from("Content-Encoding: nonce\x00"),12),g=Q("aes-128-gcm",f,a),E=c.concat([c.from(t,"utf8"),c.from([2])]),k=c.concat([g.update(E),g.final(),g.getAuthTag()]),R=c.alloc(4);return R.writeUInt32BE(4096,0),c.concat([r,R,c.from([o.length]),o,k])}async function ae(e){let{subscription:t,vapid:r,subject:s}=e;if(!t?.endpoint)return{success:!1,status:0,expired:!1,error:"no endpoint"};let n={...H({endpoint:t.endpoint,vapid:r,subject:s}),TTL:String(e.ttl??86400),Urgency:e.urgency??"normal"};if(e.topic)n.Topic=e.topic;let i;if(e.payload){if(!t.keys?.p256dh||!t.keys?.auth)return{success:!1,status:0,expired:!0,error:"subscription has no keys"};i=L(t,e.payload),n["Content-Encoding"]="aes128gcm",n["Content-Type"]="application/octet-stream",n["Content-Length"]=String(i.length)}try{let o=await fetch(t.endpoint,{method:"POST",headers:n,body:i?new Uint8Array(i):void 0}),u=o.status===404||o.status===410;if(o.status>=200&&o.status<300)return{success:!0,status:o.status,expired:!1};return{success:!1,status:o.status,expired:u,error:(await o.text().catch(()=>"")).slice(0,500)||`push service answered ${o.status}`}}catch(o){return{success:!1,status:0,expired:!1,error:o instanceof Error?o.message:String(o)}}}async function fe(e,t,r={}){let s=r.driver??"expo";if(s==="expo")return b({to:e,title:t.title,body:t.body,data:t.data,badge:t.badge,sound:t.sound,priority:t.priority});if(s==="fcm"){let n=Array.isArray(e)?e:[e];if(n.length===1)return y({to:n[0],notification:{title:t.title??"",body:t.body},data:t.data,priority:t.priority==="default"?"normal":t.priority});let i=await S(n,{notification:{title:t.title??"",body:t.body},data:t.data,priority:t.priority==="default"?"normal":t.priority});return{success:i.every((u)=>u.success),provider:"fcm",message:`Sent to ${i.filter((u)=>u.success).length}/${i.length} devices`}}return{success:!1,provider:s,message:`Unknown push driver: ${s}`}}function ye(e){v(e)}export{H as buildVapidHeaders,ye as configureFCM,L as encryptPayload,I as expo,J as fcm,oe as generateVapidKeys,fe as send,ae as sendWebPush};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/push",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.63",
5
+ "version": "0.75.65",
6
6
  "description": "The Stacks Push integration",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -56,11 +56,11 @@
56
56
  "prepublishOnly": "bun run build"
57
57
  },
58
58
  "dependencies": {
59
- "@stacksjs/cli": "0.75.63"
59
+ "@stacksjs/cli": "0.75.65"
60
60
  },
61
61
  "devDependencies": {
62
- "@stacksjs/config": "0.75.63",
63
- "@stacksjs/error-handling": "0.75.63",
62
+ "@stacksjs/config": "0.75.65",
63
+ "@stacksjs/error-handling": "0.75.65",
64
64
  "better-dx": "^0.2.24"
65
65
  }
66
66
  }