@lunora/notify 1.0.0-alpha.54 → 1.0.0-alpha.56
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.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{FCM_ENV_KEYS as e,WEB_PUSH_ENV_KEYS as t,fcmFromEnv as i,webPushFromEnv as f}from"./packem_shared/FCM_ENV_KEYS-BbhPScGH.mjs";import{defineNotify as n,isNotifyDefinition as m}from"./packem_shared/defineNotify-DVPpVkU0.mjs";import{createNotify as u}from"./packem_shared/createNotify-
|
|
1
|
+
import{FCM_ENV_KEYS as e,WEB_PUSH_ENV_KEYS as t,fcmFromEnv as i,webPushFromEnv as f}from"./packem_shared/FCM_ENV_KEYS-BbhPScGH.mjs";import{defineNotify as n,isNotifyDefinition as m}from"./packem_shared/defineNotify-DVPpVkU0.mjs";import{createNotify as u}from"./packem_shared/createNotify-f5Yufuip.mjs";import{buildEngine as E,routingPushProvider as d}from"./packem_shared/buildEngine-DRwaLiNy.mjs";import{enqueuePushBroadcast as P,runPushBroadcastPage as c}from"./packem_shared/enqueuePushBroadcast-BnuMQfuG.mjs";import{d1SubscriptionStore as b}from"./packem_shared/d1SubscriptionStore-DoInbQak.mjs";import{memorySubscriptionStore as h}from"./packem_shared/memorySubscriptionStore-C7hyd6ii.mjs";import{f as _,i as y,n as v,t as w,w as B}from"./packem_shared/normalize-BdOnBfM2.mjs";export{e as FCM_ENV_KEYS,t as WEB_PUSH_ENV_KEYS,E as buildEngine,u as createNotify,b as d1SubscriptionStore,n as defineNotify,P as enqueuePushBroadcast,i as fcmFromEnv,_ as fcmId,y as isGoneError,m as isNotifyDefinition,h as memorySubscriptionStore,v as normalizeRegisterInput,d as routingPushProvider,c as runPushBroadcastPage,w as targetOf,f as webPushFromEnv,B as webPushId};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as d,isLunoraError as T}from"@lunora/errors";import{createNotification as _}from"@visulima/notification";import{retryMiddleware as k}from"@visulima/notification/middleware";import{createFcmProvider as S}from"@visulima/notification/providers/fcm";import{createWebPushProvider as C}from"@visulima/notification/providers/web-push";import{a as L,p as m,b as $,d as N,i as O}from"./normalize-BdOnBfM2.mjs";const U=(e,r)=>{if(e.size<r)return;const t=e.keys().next().value;t!==void 0&&e.delete(t)},H="https://cloudflare-dns.com/dns-query",f=1,h=28,M=2e3,B=(e,r)=>{if(r===f){const t=m(e);return t===void 0||$(t)}return N(e.toLowerCase())},w=async(e,r,t)=>{try{const n=await fetch(`${H}?name=${encodeURIComponent(e)}&type=${String(r)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(t)});return n.ok?(await n.json()).Answer??[]:void 0}catch{return}},V=async(e,r=M)=>{const t=L(e);if(t.includes(":")||m(t)!==void 0)return{kind:"unknown"};const[n,s]=await Promise.all([w(t,f,r),w(t,h,r)]);if(n===void 0&&s===void 0)return{kind:"unknown"};for(const o of[...n??[],...s??[]])if((o.type===f||o.type===h)&&B(o.data,o.type))return{address:o.data,kind:"private"};return{kind:"public"}},z=e=>{let r=e;if(typeof e=="string"){if(!e.startsWith("{"))return;try{r=JSON.parse(e)}catch{return}}const t=r?.endpoint;return typeof t=="string"?t:void 0},l=new Map,Y=256,j=async(e,r)=>{if(r!==void 0&&r.length>0)return;let t;try{({hostname:t}=new URL(e))}catch{return}const n=l.get(t),s=n??V(t),o=await s;if(n===void 0&&o.kind!=="unknown"&&(U(l,Y),l.set(t,s)),o.kind==="private")throw new d("FORBIDDEN",`@lunora/notify: web-push endpoint host "${t}" resolves to a private/internal address (${o.address}); refusing to send (DNS-rebinding guard)`)},F=(e,r)=>{const t=[e.provider,r.provider].filter(s=>s!==void 0),n=[...e.recipients??[],...r.recipients??[]];return{...e,messageId:[e.messageId,r.messageId].join(","),response:[e.response,r.response],sent:e.sent&&r.sent,timestamp:new Date(Math.max(e.timestamp.getTime(),r.timestamp.getTime())),...t.length>0?{provider:t.join(",")}:{},...n.length>0?{recipients:n}:{}}},g=e=>e instanceof Error?e.message:typeof e=="string"?e:void 0,y="PUSH_PARTIALLY_DELIVERED",G=e=>T(e)&&e.code===y,W=(e,r)=>{if(!e.success||!r.success){if(e.success||r.success){const[n,s]=e.success?["web-push","fcm"]:["fcm","web-push"],o=e.success?r.error:e.error;return{error:new d(y,`@lunora/notify: push partially delivered — the ${n} group was accepted, the ${s} group failed (${g(o)??"unknown error"}). Not retried: a retry would re-send to the ${n} targets that already received it. Re-send to the ${s} targets only.`,{cause:o}),success:!1}}return{error:new AggregateError([e.error,r.error],"@lunora/notify: both push target groups failed"),success:!1}}const t=e.data===void 0||r.data===void 0?e.data??r.data:F(e.data,r.data);return t===void 0?{success:!0}:{data:t,success:!0}},E=e=>O(g(e)),c=5,q=3e4,x=()=>{const e=new Map;return async(r,t)=>{const n=e.get(r.provider)??{failures:0,openedAt:0};if(e.set(r.provider,n),n.failures>=c){if(Date.now()-n.openedAt<q)return{error:new d("SERVICE_UNAVAILABLE",`@lunora/notify: circuit open for provider "${r.provider}" after ${c.toString()} consecutive failures`),success:!1};n.failures=c-1}const s=await t(r);return s.success?n.failures=0:E(s.error)||(n.failures+=1,n.failures>=c&&(n.openedAt=Date.now())),s}},J=e=>{const r=t=>{const n=t===void 0?e.fcm:e.webPush;if(n===void 0)throw new Error(t===void 0?"@lunora/notify: received an FCM token target but no `fcm` channel is configured":"@lunora/notify: received a web-push target but no `webPush` channel is configured");return n};return{channel:"push",id:"lunora-push-router",initialize:async()=>{await e.webPush?.initialize(),await e.fcm?.initialize()},isAvailable:()=>(e.webPush??e.fcm)!==void 0,send:async t=>{const n=Array.isArray(t.to)?t.to:[t.to];if(n.length===0)throw new d("BAD_REQUEST","@lunora/notify: push send has no recipients — `to` is an empty array");const s=n.map(i=>z(i));for(const i of s)i!==void 0&&await j(i,e.allowedPushOrigins);const o=n.filter((i,a)=>s[a]!==void 0),p=n.filter((i,a)=>s[a]===void 0),u=s.find(i=>i!==void 0);if(p.length===0&&u!==void 0)return r(u).send(t);if(o.length===0)return r(void 0).send(t);const A=i=>({...t,to:i.length===1&&i[0]!==void 0?i[0]:i}),P=r(u),R=r(void 0),v=async(i,a)=>i.send(A(a)),b=await Promise.allSettled([v(P,o),v(R,p)]),[I,D]=b.map(i=>i.status==="fulfilled"?i.value:{error:i.reason,success:!1});return W(I,D)}}},Q=(e,r={})=>e.use(k({baseDelay:r.retryBaseDelay,shouldRetry:t=>!E(t)&&!G(t)})).use(x()),ne=e=>{const r=e.webPush===void 0?void 0:C(e.webPush),t=e.fcm===void 0?void 0:S(e.fcm),n={};return(r!==void 0||t!==void 0)&&(n.push=J({allowedPushOrigins:e.allowedPushOrigins,fcm:t,webPush:r})),e.chat!==void 0&&(n.chat=e.chat),e.inApp!==void 0&&(n.inapp=e.inApp),e.webhook!==void 0&&(n.webhook=e.webhook),Q(_(n))};export{Q as attachResilience,ne as buildEngine,J as routingPushProvider};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as v}from"@lunora/errors";import{buildEngine as z}from"./buildEngine-
|
|
1
|
+
import{LunoraError as v}from"@lunora/errors";import{buildEngine as z}from"./buildEngine-DRwaLiNy.mjs";import{memorySubscriptionStore as F}from"./memorySubscriptionStore-C7hyd6ii.mjs";import{n as R,t as B,i as L}from"./normalize-BdOnBfM2.mjs";const W=250,w=(o,l)=>typeof o=="function"?o(l):o,S=o=>o.successful?void 0:o.errorMessages.join("; "),U=(o,l,d)=>o.successful?"accepted":L(l,d)?"gone":"failed",j=async(o,l,d)=>{const u=Array.from({length:o.length});let h=0;const g=async()=>{for(;h<o.length;){const f=h;h+=1,u[f]=await d(o[f])}};return await Promise.all(Array.from({length:Math.min(l,o.length)},()=>g())),u},G=(o,l)=>({allowedPushOrigins:o.allowedPushOrigins,chat:w(o.chat,l),fcm:w(o.fcm,l),inApp:w(o.inApp,l),webhook:w(o.webhook,l),webPush:w(o.webPush,l)}),x=new WeakMap,Q=(o,l)=>{let d=x.get(o);d===void 0&&(d=new WeakMap,x.set(o,d));let u=d.get(l);return u===void 0&&(u={warnedNoPushOriginAllowlist:!1,warnedNoStore:!1},d.set(l,u)),u},Y=(o,l,d={})=>{const u=Q(o,l);let h;d.engine===void 0?(u.engine??=z(G(o,l)),h=u.engine):h=d.engine,u.store??=o.store?.(l);let{store:g}=u;g===void 0&&(u.fallbackStore??=F(),!d.silent&&!u.warnedNoStore&&(u.warnedNoStore=!0,console.warn("@lunora/notify: no `store` configured — using a non-durable in-memory subscription store. Configure `store: (env) => d1SubscriptionStore(env.DB)` for production.")),g=u.fallbackStore);const f=g,C=Math.max(1,d.concurrency??o.concurrency??10),b=Math.max(1,d.broadcastPageSize??o.broadcastPageSize??W),{log:M,metrics:P}=d,p=(e,t,r,n=1)=>{P?.count("notify.send",n,{channel:e,provider:t??e,status:r})},y=(e,t,r)=>{M?.warn(`notify ${e} delivery failed`,{channel:e,provider:t??e,status:"failed",...r})},A=(e,t)=>{P?.count("notify.skipped",1,{channel:e,reason:t})},I=()=>{const e=o.allowedPushOrigins!==void 0&&o.allowedPushOrigins.length>0;d.silent||e||u.warnedNoPushOriginAllowlist||(u.warnedNoPushOriginAllowlist=!0,console.warn("@lunora/notify: Web Push registered without `allowedPushOrigins` — endpoints are guarded by a string classifier at register time and a best-effort DNS re-check at send time, both of which are defeatable. Set `allowedPushOrigins` to the exact push-service origins for a hard guarantee."))},T=async e=>(await f.list(e)).map(({keys:r,token:n,...a})=>a),_=async e=>{if(typeof e!="string")return e;const t=await f.get(e);if(t===void 0)throw new v("BAD_REQUEST",`@lunora/notify: no registered subscription with id "${e}"`);return t},O=async(e,t,r)=>{let n,a,s;try{n=await h.sendToChannel("push",{...t,to:B(e)}),a=S(n),s=U(n,a,e.kind)}catch(c){s="failed",a=c instanceof Error?c.message:String(c)}try{s==="accepted"?await f.markStatus(e.id,"ok"):s==="gone"?await f.delete(e.id):await f.markStatus(e.id,"failed",a)}catch{}return s==="failed"&&y("push",e.kind,{error:a,subscriptionId:e.id,userId:e.userId??null}),r&&p("push",e.kind,s),{error:a,receipt:n,status:s}},D=async(e,t)=>{const r=await j(t,C,async s=>{const{error:c,status:i}=await O(s,e,!1);return{error:c,kind:s.kind,status:i,subscription:s}}),n=new Map;for(const{kind:s,status:c}of r){const i=`${s} ${c}`,m=n.get(i);m===void 0?n.set(i,{count:1,kind:s,status:c}):m.count+=1}for(const{count:s,kind:c,status:i}of n.values())p("push",c,i,s);const a=r.map(({error:s,status:c,subscription:i})=>c==="accepted"?{id:i.id,status:"ok"}:c==="gone"?{error:s,id:i.id,status:"expired"}:{error:s,id:i.id,status:"failed"});return{failed:a.filter(s=>s.status==="failed").length,outcomes:a,pruned:a.filter(s=>s.status==="expired").length,sent:a.filter(s=>s.status==="ok").length,total:a.length}},E=async(e,t)=>{if(t?.limit!==void 0&&t.limit<=0)return{nextCursor:void 0,result:{failed:0,outcomes:[],pruned:0,sent:0,total:0}};const r=t?.limit!==void 0&&t.limit>0?Math.trunc(t.limit):void 0,n=r===void 0?b:Math.min(r,b),a=await f.list({after:t?.after,kind:t?.kind,limit:n+1,userId:t?.userId}),s=t?.after===void 0?a:a.filter($=>$.id>t.after),c=s.length>n,i=c?s.slice(0,n):s;i.length===0&&t?.after===void 0&&A("push","no-subscriptions-matched");const m=await D(e,i);return{nextCursor:c?i[i.length-1]?.id:void 0,result:m}},N={broadcast:async(e,t)=>{const r={failed:0,outcomes:[],pruned:0,sent:0,total:0};let n=t?.after;const a=t?.limit;if(a!==void 0&&a<=0)return r;for(;;){const s=a===void 0?{...t,after:n}:{...t,after:n,limit:a-r.total},{nextCursor:c,result:i}=await E(e,s);if(r.failed+=i.failed,r.pruned+=i.pruned,r.sent+=i.sent,r.total+=i.total,r.outcomes.push(...i.outcomes),a!==void 0&&r.total>=a||c===void 0||c===n)break;n=c}return r},broadcastPage:E,list:e=>T(e),register:e=>("token"in e||I(),f.put(R(e,void 0,{allowedPushOrigins:o.allowedPushOrigins}))),send:async(e,t)=>{const{error:r,receipt:n}=await O(await _(e),t,!0);if(n===void 0)throw new v("INTERNAL",`@lunora/notify: push send failed: ${r??"unknown error"}`);return n},unregister:async(e,t)=>{await f.deleteOwned(e,t.userId??null)}},k=async(e,t)=>{if(h.getProvider(e)===void 0)throw A(e,"channel-not-configured"),new v("BAD_REQUEST",`@lunora/notify: the "${e}" channel is not configured in defineNotify(...)`);const r=await h.sendToChannel(e,t),n=r.successful?"accepted":"failed";return p(e,r.provider,n),n==="failed"&&y(e,r.provider,{error:S(r)}),r};return{notify:{chat:e=>k("chat",e),inApp:e=>k("inapp",e),push:N,send:async e=>{const t=await h.send(e);for(const r of t){const n=r.channel??"unknown",a=r.successful?"accepted":"failed";p(n,r.provider,a),a==="failed"&&y(n,r.provider,{error:S(r)})}return t},webhook:e=>k("webhook",e)},push:N}};export{Y as createNotify};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/notify",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.56",
|
|
4
4
|
"description": "Multi-channel notifications for Lunora — ctx.notify / ctx.push over @visulima/notification: edge-safe Web Push + FCM, plus chat, in-app inbox and webhook channels, with subscription storage and queue-backed fan-out",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
52
|
+
"@lunora/errors": "1.0.0-alpha.36",
|
|
53
53
|
"@visulima/notification": "1.1.1"
|
|
54
54
|
},
|
|
55
55
|
"engines": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as l}from"@lunora/errors";import{createNotification as I}from"@visulima/notification";import{retryMiddleware as k}from"@visulima/notification/middleware";import{createFcmProvider as T}from"@visulima/notification/providers/fcm";import{createWebPushProvider as D}from"@visulima/notification/providers/web-push";import{a as S,p as w,b as _,d as C,i as N}from"./normalize-BdOnBfM2.mjs";const O=(e,t)=>{if(e.size<t)return;const n=e.keys().next().value;n!==void 0&&e.delete(n)},L="https://cloudflare-dns.com/dns-query",f=1,h=28,M=2e3,U=(e,t)=>{if(t===f){const n=w(e);return n===void 0||_(n)}return C(e.toLowerCase())},m=async(e,t,n)=>{try{const r=await fetch(`${L}?name=${encodeURIComponent(e)}&type=${String(t)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(n)});return r.ok?(await r.json()).Answer??[]:void 0}catch{return}},$=async(e,t=M)=>{const n=S(e);if(n.includes(":")||w(n)!==void 0)return{kind:"unknown"};const[r,i]=await Promise.all([m(n,f,t),m(n,h,t)]);if(r===void 0&&i===void 0)return{kind:"unknown"};for(const o of[...r??[],...i??[]])if((o.type===f||o.type===h)&&U(o.data,o.type))return{address:o.data,kind:"private"};return{kind:"public"}},B=e=>{let t=e;if(typeof e=="string"){if(!e.startsWith("{"))return;try{t=JSON.parse(e)}catch{return}}const n=t?.endpoint;return typeof n=="string"?n:void 0},u=new Map,H=256,z=async(e,t)=>{if(t!==void 0&&t.length>0)return;let n;try{({hostname:n}=new URL(e))}catch{return}const r=u.get(n),i=r??$(n),o=await i;if(r===void 0&&o.kind!=="unknown"&&(O(u,H),u.set(n,i)),o.kind==="private")throw new l("FORBIDDEN",`@lunora/notify: web-push endpoint host "${n}" resolves to a private/internal address (${o.address}); refusing to send (DNS-rebinding guard)`)},j=(e,t)=>{const n=[e.provider,t.provider].filter(i=>i!==void 0),r=[...e.recipients??[],...t.recipients??[]];return{...e,messageId:[e.messageId,t.messageId].join(","),response:[e.response,t.response],sent:e.sent&&t.sent,timestamp:new Date(Math.max(e.timestamp.getTime(),t.timestamp.getTime())),...n.length>0?{provider:n.join(",")}:{},...r.length>0?{recipients:r}:{}}},F=(e,t)=>{if(!e.success||!t.success)return e.success||t.success?e.success?t:e:{error:new AggregateError([e.error,t.error],"@lunora/notify: both push target groups failed"),success:!1};const n=e.data===void 0||t.data===void 0?e.data??t.data:j(e.data,t.data);return n===void 0?{success:!0}:{data:n,success:!0}},V=e=>e instanceof Error?e.message:typeof e=="string"?e:void 0,g=e=>N(V(e)),c=5,Y=3e4,G=()=>{const e=new Map;return async(t,n)=>{const r=e.get(t.provider)??{failures:0,openedAt:0};if(e.set(t.provider,r),r.failures>=c){if(Date.now()-r.openedAt<Y)return{error:new l("SERVICE_UNAVAILABLE",`@lunora/notify: circuit open for provider "${t.provider}" after ${c.toString()} consecutive failures`),success:!1};r.failures=c-1}const i=await n(t);return i.success?r.failures=0:g(i.error)||(r.failures+=1,r.failures>=c&&(r.openedAt=Date.now())),i}},W=e=>{const t=n=>{const r=n===void 0?e.fcm:e.webPush;if(r===void 0)throw new Error(n===void 0?"@lunora/notify: received an FCM token target but no `fcm` channel is configured":"@lunora/notify: received a web-push target but no `webPush` channel is configured");return r};return{channel:"push",id:"lunora-push-router",initialize:async()=>{await e.webPush?.initialize(),await e.fcm?.initialize()},isAvailable:()=>(e.webPush??e.fcm)!==void 0,send:async n=>{const r=Array.isArray(n.to)?n.to:[n.to];if(r.length===0)throw new l("BAD_REQUEST","@lunora/notify: push send has no recipients — `to` is an empty array");const i=r.map(s=>B(s));for(const s of i)s!==void 0&&await z(s,e.allowedPushOrigins);const o=r.filter((s,a)=>i[a]!==void 0),p=r.filter((s,a)=>i[a]===void 0),d=i.find(s=>s!==void 0);if(p.length===0&&d!==void 0)return t(d).send(n);if(o.length===0)return t(void 0).send(n);const y=s=>({...n,to:s.length===1&&s[0]!==void 0?s[0]:s}),b=t(d),E=t(void 0),v=async(s,a)=>s.send(y(a)),P=await Promise.allSettled([v(b,o),v(E,p)]),[A,R]=P.map(s=>s.status==="fulfilled"?s.value:{error:s.reason,success:!1});return F(A,R)}}},q=(e,t={})=>e.use(k({baseDelay:t.retryBaseDelay,shouldRetry:n=>!g(n)})).use(G()),ee=e=>{const t=e.webPush===void 0?void 0:D(e.webPush),n=e.fcm===void 0?void 0:T(e.fcm),r={};return(t!==void 0||n!==void 0)&&(r.push=W({allowedPushOrigins:e.allowedPushOrigins,fcm:n,webPush:t})),e.chat!==void 0&&(r.chat=e.chat),e.inApp!==void 0&&(r.inapp=e.inApp),e.webhook!==void 0&&(r.webhook=e.webhook),q(I(r))};export{q as attachResilience,ee as buildEngine,W as routingPushProvider};
|