@stacksjs/push 0.70.87 → 0.70.88

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/push",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.87",
5
+ "version": "0.70.88",
6
6
  "description": "The Stacks Push integration",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -51,9 +51,9 @@
51
51
  "prepublishOnly": "bun run build"
52
52
  },
53
53
  "devDependencies": {
54
- "@stacksjs/cli": "0.70.87",
55
- "@stacksjs/config": "0.70.87",
54
+ "@stacksjs/cli": "0.70.88",
55
+ "@stacksjs/config": "0.70.88",
56
56
  "better-dx": "^0.2.16",
57
- "@stacksjs/error-handling": "0.70.87"
57
+ "@stacksjs/error-handling": "0.70.88"
58
58
  }
59
59
  }
@@ -1,51 +0,0 @@
1
- import type { PushResult } from '@stacksjs/types';
2
- /**
3
- * Validates an Expo push token
4
- */
5
- export declare function isExpoPushToken(token: string): boolean;
6
- /**
7
- * Send a push notification via Expo Push Service
8
- */
9
- export declare function send(message: ExpoPushMessage): Promise<PushResult>;
10
- /**
11
- * Get push notification receipts from Expo
12
- */
13
- export declare function getReceipts(ticketIds: string[]): Promise<Record<string, ExpoPushReceipt>>;
14
- /**
15
- * Send multiple push notifications in chunks (Expo recommends max 100 per request)
16
- */
17
- export declare function sendBatch(messages: ExpoPushMessage[], chunkSize?: number): Promise<PushResult[]>;
18
- declare const __dtsx_default_export__: {
19
- send: typeof send;
20
- sendBatch: typeof sendBatch;
21
- getReceipts: typeof getReceipts;
22
- isExpoPushToken: typeof isExpoPushToken
23
- };
24
- export declare interface ExpoPushMessage {
25
- to: string | string[]
26
- title?: string
27
- body: string
28
- data?: Record<string, any>
29
- sound?: 'default' | null
30
- badge?: number
31
- channelId?: string
32
- priority?: 'default' | 'normal' | 'high'
33
- ttl?: number
34
- }
35
- export declare interface ExpoPushTicket {
36
- id?: string
37
- status: 'ok' | 'error'
38
- message?: string
39
- details?: {
40
- error?: 'DeviceNotRegistered' | 'MessageTooBig' | 'MessageRateExceeded' | 'InvalidCredentials'
41
- }
42
- }
43
- export declare interface ExpoPushReceipt {
44
- status: 'ok' | 'error'
45
- message?: string
46
- details?: {
47
- error?: string
48
- }
49
- }
50
- export { send as Send };
51
- export default __dtsx_default_export__;
@@ -1,77 +0,0 @@
1
- import type { PushResult } from '@stacksjs/types';
2
- /**
3
- * Configure FCM with server key or service account
4
- */
5
- export declare function configure(options: FCMConfig): void;
6
- /**
7
- * Send push notification via FCM Legacy API (using server key)
8
- */
9
- export declare function sendLegacy(message: FCMMessage): Promise<PushResult>;
10
- /**
11
- * Send push notification via FCM v1 API (using service account)
12
- */
13
- export declare function send(message: FCMMessage): Promise<PushResult>;
14
- /**
15
- * Send to multiple tokens
16
- */
17
- export declare function sendMulticast(tokens: string[], message: Omit<FCMMessage, 'to' | 'registrationIds'>): Promise<PushResult[]>;
18
- /**
19
- * Send to a topic
20
- */
21
- export declare function sendToTopic(topic: string, message: Omit<FCMMessage, 'to' | 'registrationIds' | 'topic'>): Promise<PushResult>;
22
- /**
23
- * Subscribe tokens to a topic
24
- */
25
- export declare function subscribeToTopic(tokens: string[], topic: string): Promise<boolean>;
26
- /**
27
- * Unsubscribe tokens from a topic
28
- */
29
- export declare function unsubscribeFromTopic(tokens: string[], topic: string): Promise<boolean>;
30
- declare const __dtsx_default_export__: {
31
- send: typeof send;
32
- sendLegacy: typeof sendLegacy;
33
- sendMulticast: typeof sendMulticast;
34
- sendToTopic: typeof sendToTopic;
35
- subscribeToTopic: typeof subscribeToTopic;
36
- unsubscribeFromTopic: typeof unsubscribeFromTopic;
37
- configure: typeof configure
38
- };
39
- export declare interface FCMMessage {
40
- to?: string
41
- registrationIds?: string[]
42
- topic?: string
43
- condition?: string
44
- notification?: {
45
- title: string
46
- body: string
47
- icon?: string
48
- image?: string
49
- sound?: string
50
- badge?: string
51
- clickAction?: string
52
- tag?: string
53
- }
54
- data?: Record<string, string>
55
- priority?: 'high' | 'normal'
56
- ttl?: number
57
- collapseKey?: string
58
- }
59
- export declare interface FCMConfig {
60
- serverKey?: string
61
- projectId?: string
62
- serviceAccount?: {
63
- clientEmail: string
64
- privateKey: string
65
- }
66
- }
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
- export { send as Send };
77
- export default __dtsx_default_export__;
@@ -1,2 +0,0 @@
1
- export * as expo from './expo';
2
- export * as fcm from './fcm';
package/dist/index.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import * as expo from './drivers/expo';
2
- import * as fcm from './drivers/fcm';
3
- import type { PushResult } from '@stacksjs/types';
4
- /**
5
- * Send a push notification using the specified driver
6
- */
7
- export declare function send(to: string | string[], notification: PushNotification, options?: SendOptions): Promise<PushResult>;
8
- /**
9
- * Configure FCM with credentials
10
- */
11
- export declare function configureFCM(config: fcm.FCMConfig): void;
12
- export declare interface PushNotification {
13
- title?: string
14
- body: string
15
- data?: Record<string, any>
16
- badge?: number
17
- sound?: 'default' | null
18
- priority?: 'high' | 'normal' | 'default'
19
- }
20
- export declare interface SendOptions {
21
- driver?: PushDriver
22
- }
23
- export type PushDriver = 'expo' | 'fcm';
24
- export * from './drivers/index';
25
- export { expo, fcm };
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- // @bun
2
- var y=Object.defineProperty;var h=(G)=>G;function T(G,J){this[G]=h.bind(null,J)}var z=(G,J)=>{for(var N in J)y(G,N,{get:J[N],enumerable:!0,configurable:!0,set:T.bind(J,N)})};var E={};z(E,{sendBatch:()=>C,send:()=>j,isExpoPushToken:()=>w,getReceipts:()=>B,default:()=>u,Send:()=>j});import{log as X}from"@stacksjs/cli";var d="https://exp.host/--/api/v2/push/send";function w(G){return/^Expo(?:nent)?PushToken\[.+\]$/.test(G)||/^[a-zA-Z0-9-_]+$/.test(G)}async function j(G){let J=Array.isArray(G.to)?G.to:[G.to],N=J.filter((W)=>!w(W));if(N.length>0)X.warn(`Invalid Expo push tokens found: ${N.join(", ")}`);let Q=J.filter(w);if(Q.length===0)return{success:!1,provider:"expo",message:"No valid Expo push tokens provided"};try{let W=Q.map(($)=>({to:$,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})),Z=4096;for(let $ of W){let K=new TextEncoder().encode(JSON.stringify($)).length;if(K>4096)return{success:!1,provider:"expo",message:`Expo message payload (${K} bytes) exceeds 4 KiB per-message limit`}}let q=await fetch(d,{method:"POST",headers:{Accept:"application/json","Accept-Encoding":"gzip, deflate","Content-Type":"application/json"},body:JSON.stringify(W)});if(!q.ok){let $=await q.text();return X.error(`Expo push failed: ${$}`),{success:!1,provider:"expo",message:`HTTP ${q.status}: ${$}`}}let A=(await q.json()).data,V=A.filter(($)=>$.status==="error");if(V.length>0)X.warn(`Some push notifications failed: ${JSON.stringify(V)}`);let I=A.filter(($)=>$.status==="ok").length;return X.info(`Expo push sent: ${I}/${A.length} successful`),{success:V.length===0,provider:"expo",message:`Sent ${I}/${A.length} notifications`,messageId:A.find(($)=>$.id)?.id,data:{tickets:A}}}catch(W){let Z=W instanceof Error?W:Error(String(W));return X.error(`Expo push error: ${Z.message}`),{success:!1,provider:"expo",message:Z.message}}}async function B(G){let J=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(!J.ok)throw Error(`Failed to get receipts: ${J.status}`);return(await J.json()).data}async function C(G,J=100){let N=[];for(let Q=0;Q<G.length;Q+=J){let W=G.slice(Q,Q+J),Z=await Promise.all(W.map((q)=>j(q)));N.push(...Z)}return N}var u={send:j,sendBatch:C,getReceipts:B,isExpoPushToken:w};var M={};z(M,{unsubscribeFromTopic:()=>b,subscribeToTopic:()=>S,sendToTopic:()=>L,sendMulticast:()=>U,sendLegacy:()=>P,send:()=>O,default:()=>f,configure:()=>F,Send:()=>O});import{log as H}from"@stacksjs/cli";var l="https://fcm.googleapis.com/fcm/send",p="https://fcm.googleapis.com/v1/projects",Y={};function F(G){Y={...Y,...G}}async function k(){if(!Y.serviceAccount)throw Error("Service account not configured for FCM v1 API");let{clientEmail:G,privateKey:J}=Y.serviceAccount,N=Math.floor(Date.now()/1000),Q={alg:"RS256",typ:"JWT"},W={iss:G,scope:"https://www.googleapis.com/auth/firebase.messaging",aud:"https://oauth2.googleapis.com/token",iat:N,exp:N+3600},Z=new TextEncoder,q=btoa(JSON.stringify(Q)),D=btoa(JSON.stringify(W)),A=`${q}.${D}`,V=J.replace(/-----BEGIN PRIVATE KEY-----/,"").replace(/-----END PRIVATE KEY-----/,"").replace(/\n/g,""),I=Uint8Array.from(atob(V),(x)=>x.charCodeAt(0)),$=await crypto.subtle.importKey("pkcs8",I,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),K=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",$,Z.encode(A)),R=btoa(String.fromCharCode(...new Uint8Array(K))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),v=`${q}.${D}.${R}`,_=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=${v}`});if(!_.ok)throw Error(`Failed to get access token: ${_.status}`);return(await _.json()).access_token}async function P(G){if(!Y.serverKey)return{success:!1,provider:"fcm",message:"FCM server key not configured"};try{let J=await fetch(l,{method:"POST",headers:{Authorization:`key=${Y.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(!J.ok){let Q=await J.text();return H.error(`FCM push failed: ${Q}`),{success:!1,provider:"fcm",message:`HTTP ${J.status}: ${Q}`}}let N=await J.json();if(N.failure>0)H.warn(`FCM: ${N.failure} notifications failed`);return H.info(`FCM push sent: ${N.success} successful, ${N.failure} failed`),{success:N.failure===0,provider:"fcm",message:`Sent ${N.success} notifications`,messageId:N.results?.[0]?.messageId,data:N}}catch(J){let N=J instanceof Error?J:Error(String(J));return H.error(`FCM push error: ${N.message}`),{success:!1,provider:"fcm",message:N.message}}}async function O(G){if(!Y.serviceAccount||!Y.projectId)return P(G);try{let J=await k(),N=`${p}/${Y.projectId}/messages:send`,Q={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)Q.message.token=G.to;else if(G.topic)Q.message.topic=G.topic;else if(G.condition)Q.message.condition=G.condition;let W=await fetch(N,{method:"POST",headers:{Authorization:`Bearer ${J}`,"Content-Type":"application/json"},body:JSON.stringify(Q)});if(!W.ok){let q=await W.text();return H.error(`FCM v1 push failed: ${q}`),{success:!1,provider:"fcm",message:`HTTP ${W.status}: ${q}`}}let Z=await W.json();return H.info(`FCM v1 push sent: ${Z.name}`),{success:!0,provider:"fcm",message:"Notification sent successfully",messageId:Z.name}}catch(J){let N=J instanceof Error?J:Error(String(J));return H.error(`FCM v1 push error: ${N.message}`),{success:!1,provider:"fcm",message:N.message}}}async function U(G,J){if(Y.serviceAccount&&Y.projectId)return Promise.all(G.map((N)=>O({...J,to:N})));return[await P({...J,registrationIds:G})]}async function L(G,J){return O({...J,topic:G})}async function S(G,J){if(!Y.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=${Y.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${J}`,registration_tokens:G})})).ok}async function b(G,J){if(!Y.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=${Y.serverKey}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${J}`,registration_tokens:G})})).ok}var f={send:O,sendLegacy:P,sendMulticast:U,sendToTopic:L,subscribeToTopic:S,unsubscribeFromTopic:b,configure:F};async function a(G,J,N={}){let Q=N.driver??"expo";if(Q==="expo")return j({to:G,title:J.title,body:J.body,data:J.data,badge:J.badge,sound:J.sound,priority:J.priority});if(Q==="fcm"){let W=Array.isArray(G)?G:[G];if(W.length===1)return O({to:W[0],notification:{title:J.title??"",body:J.body},data:J.data,priority:J.priority==="default"?"normal":J.priority});let Z=await U(W,{notification:{title:J.title??"",body:J.body},data:J.data,priority:J.priority==="default"?"normal":J.priority});return{success:Z.every((D)=>D.success),provider:"fcm",message:`Sent to ${Z.filter((D)=>D.success).length}/${Z.length} devices`}}return{success:!1,provider:Q,message:`Unknown push driver: ${Q}`}}function t(G){F(G)}export{a as send,M as fcm,E as expo,t as configureFCM};