@lunora/notify 1.0.0-alpha.69 → 1.0.0-alpha.70

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-CXtPh7d6.mjs";import{buildEngine as E,routingPushProvider as d}from"./packem_shared/buildEngine-_w_yjbLC.mjs";import{enqueuePushBroadcast as P,runPushBroadcastPage as c}from"./packem_shared/enqueuePushBroadcast-BRaUpNK0.mjs";import{d1SubscriptionStore as b}from"./packem_shared/d1SubscriptionStore-CPoFspWn.mjs";import{memorySubscriptionStore as h}from"./packem_shared/memorySubscriptionStore-CkZtdeON.mjs";import{f as _,i as y,n as v,t as w,w as B}from"./packem_shared/normalize-B7YlG5HG.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};
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-D43i75Ao.mjs";import{buildEngine as E,routingPushProvider as d}from"./packem_shared/buildEngine-w3sSGKFi.mjs";import{enqueuePushBroadcast as P,runPushBroadcastPage as c}from"./packem_shared/enqueuePushBroadcast-C9tO0BZj.mjs";import{d1SubscriptionStore as b}from"./packem_shared/d1SubscriptionStore-h6gSRs-m.mjs";import{memorySubscriptionStore as h}from"./packem_shared/memorySubscriptionStore-q8KD05cV.mjs";import{f as _,i as y,n as v,t as w,w as B}from"./packem_shared/normalize-B5B9QUol.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};
@@ -1 +1 @@
1
- import{LunoraError as h,isLunoraError as U}from"@lunora/errors";import{createNotification as N}from"@visulima/notification";import{retryMiddleware as k}from"@visulima/notification/middleware";import{createFcmProvider as H}from"@visulima/notification/providers/fcm";import{createWebPushProvider as B}from"@visulima/notification/providers/web-push";import{a as G,p as S,b as M,d as z,i as F}from"./normalize-B7YlG5HG.mjs";const V=(e,t)=>{if(e.size<t)return;const r=e.keys().next().value;r!==void 0&&e.delete(r)},j="https://cloudflare-dns.com/dns-query",R=1,D=28,Y=2e3,K=(e,t)=>{if(t===R){const r=S(e);return r===void 0||M(r)}return z(e.toLowerCase())},_=async(e,t,r)=>{try{const n=await fetch(`${j}?name=${encodeURIComponent(e)}&type=${String(t)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(r)});return n.ok?(await n.json()).Answer??[]:void 0}catch{return}},W=async(e,t=Y)=>{const r=G(e);if(r.includes(":")||S(r)!==void 0)return{kind:"unknown"};const[n,s]=await Promise.all([_(r,R,t),_(r,D,t)]);if(n===void 0&&s===void 0)return{kind:"unknown"};for(const o of[...n??[],...s??[]])if((o.type===R||o.type===D)&&K(o.data,o.type))return{address:o.data,kind:"private"};return{kind:"public"}},C=e=>{let t=e;if(typeof e=="string"){if(!e.startsWith("{"))return;try{t=JSON.parse(e)}catch{return}}const r=t?.endpoint;return typeof r=="string"?r:void 0},q=e=>C(e)===void 0?"fcm":"web-push",l="lunora-push-router",m=new Map,x=256,J=async(e,t)=>{if(t!==void 0&&t.length>0)return;let r;try{({hostname:r}=new URL(e))}catch{return}const n=m.get(r),s=n??W(r),o=await s;if(n===void 0&&o.kind!=="unknown"&&(V(m,x),m.set(r,s)),o.kind==="private")throw new h("FORBIDDEN",`@lunora/notify: web-push endpoint host "${r}" resolves to a private/internal address (${o.address}); refusing to send (DNS-rebinding guard)`)},Q=(e,t)=>{const r=[e.provider,t.provider].filter(s=>s!==void 0),n=[...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())),...r.length>0?{provider:r.join(",")}:{},...n.length>0?{recipients:n}:{}}},O=e=>e instanceof Error?e.message:typeof e=="string"?e:void 0,$="PUSH_PARTIALLY_DELIVERED",L=e=>U(e)&&e.code===$,X=e=>{if(!L(e))return;const{data:t}=e;return t==="fcm"||t==="web-push"?t:void 0},g=(e,t)=>{if(!e.success||!t.success){if(e.success||t.success){const[n,s]=e.success?["web-push","fcm"]:["fcm","web-push"],o=e.success?t.error:e.error;return{error:new h($,`@lunora/notify: push partially delivered — the ${n} group was accepted, the ${s} group failed and re-attempting it did not recover it (${O(o)??"unknown error"}). The whole send is not retried: that would re-send to the ${n} targets that already received it. Re-send to the ${s} targets only.`,{cause:o,data:s}),success:!1}}return{error:new AggregateError([e.error,t.error],"@lunora/notify: both push target groups failed"),success:!1}}const r=e.data===void 0||t.data===void 0?e.data??t.data:Q(e.data,t.data);return r===void 0?{success:!0}:{data:r,success:!0}},v=e=>F(O(e)),p=5,Z=3e4,ee=e=>{if(e.provider!==l)return[e.provider];const{to:t}=e.payload,r=Array.isArray(t)?t:[t],n=new Set(r.map(s=>q(s)));return n.size===0?[e.provider]:[...n].map(s=>`${e.provider}:${s}`)},te=(e,t)=>e.failures>=p&&t-e.openedAt<Z,re=(e,t,r)=>{const n=X(e),s=n===void 0?void 0:`${l}:${n}`,o=s!==void 0&&t.includes(s);for(const[c,i]of r.entries())o&&t[c]!==s?i.failures=0:(i.failures+=1,i.failures>=p&&(i.openedAt=Date.now()))},se=()=>{const e=new Map,t=r=>{const n=e.get(r);if(n!==void 0)return n;const s={failures:0,openedAt:0};return e.set(r,s),s};return async(r,n)=>{const s=ee(r),o=s.map(u=>t(u)),c=Date.now();if(o.every(u=>te(u,c)))return{error:new h("SERVICE_UNAVAILABLE",`@lunora/notify: circuit open for "${s.join('", "')}" after ${p.toString()} consecutive failures`),success:!1};for(const u of o)u.failures>=p&&(u.failures=p-1);const i=await n(r);if(i.success)for(const u of o)u.failures=0;else v(i.error)||re(i.error,s,o);return i}},ne=2,E=async(e,t)=>{try{return await e.send(t)}catch(r){return{error:r,success:!1}}},oe=e=>{const t=k({baseDelay:e.retryBaseDelay,retries:ne,shouldRetry:s=>!v(s)}),r=async(s,o,c)=>v(c.error)?c:t({channel:"push",payload:o,provider:l},async i=>E(s,i.payload)),n=s=>{const o=s===void 0?e.fcm:e.webPush;if(o===void 0)throw new Error(s===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 o};return{channel:"push",id:l,initialize:async()=>{await e.webPush?.initialize(),await e.fcm?.initialize()},isAvailable:()=>(e.webPush??e.fcm)!==void 0,send:async s=>{const o=Array.isArray(s.to)?s.to:[s.to];if(o.length===0)throw new h("BAD_REQUEST","@lunora/notify: push send has no recipients — `to` is an empty array");const c=o.map(a=>C(a));for(const a of c)a!==void 0&&await J(a,e.allowedPushOrigins);const i=o.filter((a,y)=>c[y]!==void 0),u=o.filter((a,y)=>c[y]===void 0),w=c.find(a=>a!==void 0);if(u.length===0&&w!==void 0)return n(w).send(s);if(i.length===0)return n(void 0).send(s);const b=a=>({...s,to:a.length===1&&a[0]!==void 0?a[0]:a}),A=n(w),P=n(void 0),I=b(i),T=b(u),[d,f]=await Promise.all([E(A,I),E(P,T)]);return d.success&&!f.success?g(d,await r(P,T,f)):f.success&&!d.success?g(await r(A,I,d),f):g(d,f)}}},ie=(e,t={})=>e.use(k({baseDelay:t.retryBaseDelay,shouldRetry:r=>!v(r)&&!L(r)})).use(se()),le=e=>{const t=e.webPush===void 0?void 0:B(e.webPush),r=e.fcm===void 0?void 0:H(e.fcm),n={};return(t!==void 0||r!==void 0)&&(n.push=oe({allowedPushOrigins:e.allowedPushOrigins,fcm:r,webPush:t})),e.chat!==void 0&&(n.chat=e.chat),e.inApp!==void 0&&(n.inapp=e.inApp),e.webhook!==void 0&&(n.webhook=e.webhook),ie(N(n))};export{ie as attachResilience,le as buildEngine,oe as routingPushProvider};
1
+ import{LunoraError as h,isLunoraError as U}from"@lunora/errors";import{createNotification as N}from"@visulima/notification";import{retryMiddleware as k}from"@visulima/notification/middleware";import{createFcmProvider as H}from"@visulima/notification/providers/fcm";import{createWebPushProvider as B}from"@visulima/notification/providers/web-push";import{a as G,p as S,b as M,d as z,i as F}from"./normalize-B5B9QUol.mjs";const V=(e,t)=>{if(e.size<t)return;const r=e.keys().next().value;r!==void 0&&e.delete(r)},j="https://cloudflare-dns.com/dns-query",R=1,D=28,Y=2e3,K=(e,t)=>{if(t===R){const r=S(e);return r===void 0||M(r)}return z(e.toLowerCase())},_=async(e,t,r)=>{try{const n=await fetch(`${j}?name=${encodeURIComponent(e)}&type=${String(t)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(r)});return n.ok?(await n.json()).Answer??[]:void 0}catch{return}},W=async(e,t=Y)=>{const r=G(e);if(r.includes(":")||S(r)!==void 0)return{kind:"unknown"};const[n,s]=await Promise.all([_(r,R,t),_(r,D,t)]);if(n===void 0&&s===void 0)return{kind:"unknown"};for(const o of[...n??[],...s??[]])if((o.type===R||o.type===D)&&K(o.data,o.type))return{address:o.data,kind:"private"};return{kind:"public"}},C=e=>{let t=e;if(typeof e=="string"){if(!e.startsWith("{"))return;try{t=JSON.parse(e)}catch{return}}const r=t?.endpoint;return typeof r=="string"?r:void 0},q=e=>C(e)===void 0?"fcm":"web-push",l="lunora-push-router",m=new Map,x=256,J=async(e,t)=>{if(t!==void 0&&t.length>0)return;let r;try{({hostname:r}=new URL(e))}catch{return}const n=m.get(r),s=n??W(r),o=await s;if(n===void 0&&o.kind!=="unknown"&&(V(m,x),m.set(r,s)),o.kind==="private")throw new h("FORBIDDEN",`@lunora/notify: web-push endpoint host "${r}" resolves to a private/internal address (${o.address}); refusing to send (DNS-rebinding guard)`)},Q=(e,t)=>{const r=[e.provider,t.provider].filter(s=>s!==void 0),n=[...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())),...r.length>0?{provider:r.join(",")}:{},...n.length>0?{recipients:n}:{}}},O=e=>e instanceof Error?e.message:typeof e=="string"?e:void 0,$="PUSH_PARTIALLY_DELIVERED",L=e=>U(e)&&e.code===$,X=e=>{if(!L(e))return;const{data:t}=e;return t==="fcm"||t==="web-push"?t:void 0},g=(e,t)=>{if(!e.success||!t.success){if(e.success||t.success){const[n,s]=e.success?["web-push","fcm"]:["fcm","web-push"],o=e.success?t.error:e.error;return{error:new h($,`@lunora/notify: push partially delivered — the ${n} group was accepted, the ${s} group failed and re-attempting it did not recover it (${O(o)??"unknown error"}). The whole send is not retried: that would re-send to the ${n} targets that already received it. Re-send to the ${s} targets only.`,{cause:o,data:s}),success:!1}}return{error:new AggregateError([e.error,t.error],"@lunora/notify: both push target groups failed"),success:!1}}const r=e.data===void 0||t.data===void 0?e.data??t.data:Q(e.data,t.data);return r===void 0?{success:!0}:{data:r,success:!0}},v=e=>F(O(e)),p=5,Z=3e4,ee=e=>{if(e.provider!==l)return[e.provider];const{to:t}=e.payload,r=Array.isArray(t)?t:[t],n=new Set(r.map(s=>q(s)));return n.size===0?[e.provider]:[...n].map(s=>`${e.provider}:${s}`)},te=(e,t)=>e.failures>=p&&t-e.openedAt<Z,re=(e,t,r)=>{const n=X(e),s=n===void 0?void 0:`${l}:${n}`,o=s!==void 0&&t.includes(s);for(const[c,i]of r.entries())o&&t[c]!==s?i.failures=0:(i.failures+=1,i.failures>=p&&(i.openedAt=Date.now()))},se=()=>{const e=new Map,t=r=>{const n=e.get(r);if(n!==void 0)return n;const s={failures:0,openedAt:0};return e.set(r,s),s};return async(r,n)=>{const s=ee(r),o=s.map(u=>t(u)),c=Date.now();if(o.every(u=>te(u,c)))return{error:new h("SERVICE_UNAVAILABLE",`@lunora/notify: circuit open for "${s.join('", "')}" after ${p.toString()} consecutive failures`),success:!1};for(const u of o)u.failures>=p&&(u.failures=p-1);const i=await n(r);if(i.success)for(const u of o)u.failures=0;else v(i.error)||re(i.error,s,o);return i}},ne=2,E=async(e,t)=>{try{return await e.send(t)}catch(r){return{error:r,success:!1}}},oe=e=>{const t=k({baseDelay:e.retryBaseDelay,retries:ne,shouldRetry:s=>!v(s)}),r=async(s,o,c)=>v(c.error)?c:t({channel:"push",payload:o,provider:l},async i=>E(s,i.payload)),n=s=>{const o=s===void 0?e.fcm:e.webPush;if(o===void 0)throw new Error(s===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 o};return{channel:"push",id:l,initialize:async()=>{await e.webPush?.initialize(),await e.fcm?.initialize()},isAvailable:()=>(e.webPush??e.fcm)!==void 0,send:async s=>{const o=Array.isArray(s.to)?s.to:[s.to];if(o.length===0)throw new h("BAD_REQUEST","@lunora/notify: push send has no recipients — `to` is an empty array");const c=o.map(a=>C(a));for(const a of c)a!==void 0&&await J(a,e.allowedPushOrigins);const i=o.filter((a,y)=>c[y]!==void 0),u=o.filter((a,y)=>c[y]===void 0),w=c.find(a=>a!==void 0);if(u.length===0&&w!==void 0)return n(w).send(s);if(i.length===0)return n(void 0).send(s);const b=a=>({...s,to:a.length===1&&a[0]!==void 0?a[0]:a}),A=n(w),P=n(void 0),I=b(i),T=b(u),[d,f]=await Promise.all([E(A,I),E(P,T)]);return d.success&&!f.success?g(d,await r(P,T,f)):f.success&&!d.success?g(await r(A,I,d),f):g(d,f)}}},ie=(e,t={})=>e.use(k({baseDelay:t.retryBaseDelay,shouldRetry:r=>!v(r)&&!L(r)})).use(se()),le=e=>{const t=e.webPush===void 0?void 0:B(e.webPush),r=e.fcm===void 0?void 0:H(e.fcm),n={};return(t!==void 0||r!==void 0)&&(n.push=oe({allowedPushOrigins:e.allowedPushOrigins,fcm:r,webPush:t})),e.chat!==void 0&&(n.chat=e.chat),e.inApp!==void 0&&(n.inapp=e.inApp),e.webhook!==void 0&&(n.webhook=e.webhook),ie(N(n))};export{ie as attachResilience,le as buildEngine,oe as routingPushProvider};
@@ -1 +1 @@
1
- import{LunoraError as v}from"@lunora/errors";import{buildEngine as z}from"./buildEngine-_w_yjbLC.mjs";import{memorySubscriptionStore as F}from"./memorySubscriptionStore-CkZtdeON.mjs";import{n as R,t as B,i as L}from"./normalize-B7YlG5HG.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};
1
+ import{LunoraError as v}from"@lunora/errors";import{buildEngine as z}from"./buildEngine-w3sSGKFi.mjs";import{memorySubscriptionStore as F}from"./memorySubscriptionStore-q8KD05cV.mjs";import{n as R,t as B,i as L}from"./normalize-B5B9QUol.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};
@@ -1 +1 @@
1
- import{LunoraError as h}from"@lunora/errors";import{c as S,l as N}from"./normalize-B7YlG5HG.mjs";const R=e=>/^[A-Z_]\w*$/i.test(e),T=e=>{const i={createdAt:e.created_at,id:e.id,kind:e.kind,lastSeenAt:e.last_seen_at,userId:e.user_id};if(e.endpoint!==null&&(i.endpoint=e.endpoint),e.p256dh!==null&&e.auth!==null&&(i.keys={auth:e.auth,p256dh:e.p256dh}),e.token!==null&&(i.token=e.token),e.last_status!==null&&(i.lastStatus=e.last_status),e.last_error!==null&&(i.lastError=e.last_error),e.metadata!==null)try{i.metadata=JSON.parse(e.metadata)}catch{}return i},A=(e,i={})=>{const n=i.tableName??"lunora_push_subscriptions";if(!R(n))throw new h("BAD_REQUEST",`@lunora/notify: d1SubscriptionStore tableName "${n}" is not a bare SQL identifier`);let s;const r=()=>(s===void 0&&(s=e.prepare(`CREATE TABLE IF NOT EXISTS ${n} (id TEXT PRIMARY KEY, kind TEXT NOT NULL, endpoint TEXT, p256dh TEXT, auth TEXT, token TEXT, user_id TEXT, metadata TEXT, created_at INTEGER NOT NULL, last_seen_at INTEGER NOT NULL, last_status TEXT, last_error TEXT)`).run().then(()=>e.prepare(`CREATE INDEX IF NOT EXISTS ${n}_user_id_idx ON ${n} (user_id)`).run()).then(()=>e.prepare(`CREATE INDEX IF NOT EXISTS ${n}_kind_idx ON ${n} (kind)`).run()).then(()=>{}),s.catch(()=>{s=void 0})),s),E=async t=>{await r();const a=await e.prepare(`SELECT * FROM ${n} WHERE id = ?1`).bind(t).first();return a===null?void 0:T(a)};return{delete:async t=>{await r(),await e.prepare(`DELETE FROM ${n} WHERE id = ?1`).bind(t).run()},deleteOwned:async(t,a)=>(await r(),await(a===null?e.prepare(`DELETE FROM ${n} WHERE id = ?1 AND user_id IS NULL RETURNING id`).bind(t):e.prepare(`DELETE FROM ${n} WHERE id = ?1 AND user_id = ?2 RETURNING id`).bind(t,a)).first()!==null),get:E,list:async t=>{await r();const a=[],d=[];t?.kind!==void 0&&(d.push(t.kind),a.push(`kind = ?${d.length.toString()}`)),t?.userId!==void 0&&(t.userId===null?a.push("user_id IS NULL"):(d.push(t.userId),a.push(`user_id = ?${d.length.toString()}`))),t?.after!==void 0&&(d.push(t.after),a.push(`id > ?${d.length.toString()}`));const l=a.length===0?"":` WHERE ${a.join(" AND ")}`,u=" ORDER BY id ASC";let o="";t?.limit!==void 0&&t.limit>0&&(d.push(Math.trunc(t.limit)),o=` LIMIT ?${d.length.toString()}`);const{results:_}=await e.prepare(`SELECT * FROM ${n}${l}${u}${o}`).bind(...d).all();return _.map(p=>T(p))},markStatus:async(t,a,d)=>{await r(),await e.prepare(`UPDATE ${n} SET last_status = ?2, last_error = ?3, last_seen_at = ?4 WHERE id = ?1`).bind(t,a,d??null,Date.now()).run()},put:async t=>{await r(),await e.prepare(`INSERT INTO ${n} (id, kind, endpoint, p256dh, auth, token, user_id, metadata, created_at, last_seen_at, last_status, last_error) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12) ON CONFLICT(id) DO UPDATE SET kind = ?2, endpoint = ?3, p256dh = ?4, auth = ?5, token = ?6, user_id = ?7, metadata = ?8, last_seen_at = ?10 WHERE ${n}.user_id IS NULL OR ${n}.user_id = ?7`).bind(t.id,t.kind,t.endpoint??null,t.keys?.p256dh??null,t.keys?.auth??null,t.token??null,t.userId??null,t.metadata===void 0?null:JSON.stringify(t.metadata),t.createdAt,t.lastSeenAt,t.lastStatus??null,t.lastError??null).run();const a=await E(t.id),d=S(a,t);if(d!==void 0)throw d;const l=N(t);if(l!==void 0&&l!==t.id){const u=t.userId??null;await(u===null?e.prepare(`DELETE FROM ${n} WHERE id = ?1 AND user_id IS NULL`).bind(l):e.prepare(`DELETE FROM ${n} WHERE id = ?1 AND (user_id IS NULL OR user_id = ?2)`).bind(l,u)).run()}return a??t}}};export{A as d1SubscriptionStore};
1
+ import{LunoraError as h}from"@lunora/errors";import{c as S,l as N}from"./normalize-B5B9QUol.mjs";const R=e=>/^[A-Z_]\w*$/i.test(e),T=e=>{const i={createdAt:e.created_at,id:e.id,kind:e.kind,lastSeenAt:e.last_seen_at,userId:e.user_id};if(e.endpoint!==null&&(i.endpoint=e.endpoint),e.p256dh!==null&&e.auth!==null&&(i.keys={auth:e.auth,p256dh:e.p256dh}),e.token!==null&&(i.token=e.token),e.last_status!==null&&(i.lastStatus=e.last_status),e.last_error!==null&&(i.lastError=e.last_error),e.metadata!==null)try{i.metadata=JSON.parse(e.metadata)}catch{}return i},A=(e,i={})=>{const n=i.tableName??"lunora_push_subscriptions";if(!R(n))throw new h("BAD_REQUEST",`@lunora/notify: d1SubscriptionStore tableName "${n}" is not a bare SQL identifier`);let s;const r=()=>(s===void 0&&(s=e.prepare(`CREATE TABLE IF NOT EXISTS ${n} (id TEXT PRIMARY KEY, kind TEXT NOT NULL, endpoint TEXT, p256dh TEXT, auth TEXT, token TEXT, user_id TEXT, metadata TEXT, created_at INTEGER NOT NULL, last_seen_at INTEGER NOT NULL, last_status TEXT, last_error TEXT)`).run().then(()=>e.prepare(`CREATE INDEX IF NOT EXISTS ${n}_user_id_idx ON ${n} (user_id)`).run()).then(()=>e.prepare(`CREATE INDEX IF NOT EXISTS ${n}_kind_idx ON ${n} (kind)`).run()).then(()=>{}),s.catch(()=>{s=void 0})),s),E=async t=>{await r();const a=await e.prepare(`SELECT * FROM ${n} WHERE id = ?1`).bind(t).first();return a===null?void 0:T(a)};return{delete:async t=>{await r(),await e.prepare(`DELETE FROM ${n} WHERE id = ?1`).bind(t).run()},deleteOwned:async(t,a)=>(await r(),await(a===null?e.prepare(`DELETE FROM ${n} WHERE id = ?1 AND user_id IS NULL RETURNING id`).bind(t):e.prepare(`DELETE FROM ${n} WHERE id = ?1 AND user_id = ?2 RETURNING id`).bind(t,a)).first()!==null),get:E,list:async t=>{await r();const a=[],d=[];t?.kind!==void 0&&(d.push(t.kind),a.push(`kind = ?${d.length.toString()}`)),t?.userId!==void 0&&(t.userId===null?a.push("user_id IS NULL"):(d.push(t.userId),a.push(`user_id = ?${d.length.toString()}`))),t?.after!==void 0&&(d.push(t.after),a.push(`id > ?${d.length.toString()}`));const l=a.length===0?"":` WHERE ${a.join(" AND ")}`,u=" ORDER BY id ASC";let o="";t?.limit!==void 0&&t.limit>0&&(d.push(Math.trunc(t.limit)),o=` LIMIT ?${d.length.toString()}`);const{results:_}=await e.prepare(`SELECT * FROM ${n}${l}${u}${o}`).bind(...d).all();return _.map(p=>T(p))},markStatus:async(t,a,d)=>{await r(),await e.prepare(`UPDATE ${n} SET last_status = ?2, last_error = ?3, last_seen_at = ?4 WHERE id = ?1`).bind(t,a,d??null,Date.now()).run()},put:async t=>{await r(),await e.prepare(`INSERT INTO ${n} (id, kind, endpoint, p256dh, auth, token, user_id, metadata, created_at, last_seen_at, last_status, last_error) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12) ON CONFLICT(id) DO UPDATE SET kind = ?2, endpoint = ?3, p256dh = ?4, auth = ?5, token = ?6, user_id = ?7, metadata = ?8, last_seen_at = ?10 WHERE ${n}.user_id IS NULL OR ${n}.user_id = ?7`).bind(t.id,t.kind,t.endpoint??null,t.keys?.p256dh??null,t.keys?.auth??null,t.token??null,t.userId??null,t.metadata===void 0?null:JSON.stringify(t.metadata),t.createdAt,t.lastSeenAt,t.lastStatus??null,t.lastError??null).run();const a=await E(t.id),d=S(a,t);if(d!==void 0)throw d;const l=N(t);if(l!==void 0&&l!==t.id){const u=t.userId??null;await(u===null?e.prepare(`DELETE FROM ${n} WHERE id = ?1 AND user_id IS NULL`).bind(l):e.prepare(`DELETE FROM ${n} WHERE id = ?1 AND (user_id IS NULL OR user_id = ?2)`).bind(l,u)).run()}return a??t}}};export{A as d1SubscriptionStore};
@@ -1 +1 @@
1
- import{LunoraError as d}from"@lunora/errors";import{i as c,k as f}from"./normalize-B7YlG5HG.mjs";const u=r=>r.filter(t=>t.status==="failed").map(t=>t.id),v=(r,t)=>r.send({...t,type:"lunora.push.broadcast"}),g=(r,t,s)=>{if(t===void 0)return;if(r?.limit===void 0)return{...r,after:t};const e=r.limit-s;return e>0?{...r,after:t,limit:e}:void 0},h=/no registered subscription/u,p=async(r,t,s)=>{try{const e=await r.send(s,t);if(e.successful)return{id:s,status:"ok"};const n=e.errorMessages.join("; ");return{error:n,id:s,status:c(n,f(s))?"expired":"failed"}}catch(e){const n=e instanceof Error?e.message:String(e);return{error:n,id:s,status:h.test(n)?"expired":"failed"}}},y=async(r,t,s)=>{const e=[];for(const o of s)e.push(await p(r,t,o));const n=u(e),a=e.filter(o=>o.status==="expired").length,i=e.length-n.length-a,l={failed:n.length,outcomes:e,pruned:a,sent:i,total:e.length};if(i===0&&n.length>0)throw new d("INTERNAL",`@lunora/notify: push retry failed for ${n.length.toString()} of ${e.length.toString()} subscription(s) — throwing so the queue retries and eventually dead-letters them`);return{failedIds:n,nextFilter:void 0,result:l}},w=async(r,t)=>{if(t.retryIds!==void 0&&t.retryIds.length>0)return y(r,t.payload,t.retryIds);const s=await r.broadcastPage(t.payload,t.filter);return{failedIds:u(s.result.outcomes),nextFilter:g(t.filter,s.nextCursor,s.result.total),result:s.result}};export{v as enqueuePushBroadcast,w as runPushBroadcastPage};
1
+ import{LunoraError as d}from"@lunora/errors";import{i as c,k as f}from"./normalize-B5B9QUol.mjs";const u=r=>r.filter(t=>t.status==="failed").map(t=>t.id),v=(r,t)=>r.send({...t,type:"lunora.push.broadcast"}),g=(r,t,s)=>{if(t===void 0)return;if(r?.limit===void 0)return{...r,after:t};const e=r.limit-s;return e>0?{...r,after:t,limit:e}:void 0},h=/no registered subscription/u,p=async(r,t,s)=>{try{const e=await r.send(s,t);if(e.successful)return{id:s,status:"ok"};const n=e.errorMessages.join("; ");return{error:n,id:s,status:c(n,f(s))?"expired":"failed"}}catch(e){const n=e instanceof Error?e.message:String(e);return{error:n,id:s,status:h.test(n)?"expired":"failed"}}},y=async(r,t,s)=>{const e=[];for(const o of s)e.push(await p(r,t,o));const n=u(e),a=e.filter(o=>o.status==="expired").length,i=e.length-n.length-a,l={failed:n.length,outcomes:e,pruned:a,sent:i,total:e.length};if(i===0&&n.length>0)throw new d("INTERNAL",`@lunora/notify: push retry failed for ${n.length.toString()} of ${e.length.toString()} subscription(s) — throwing so the queue retries and eventually dead-letters them`);return{failedIds:n,nextFilter:void 0,result:l}},w=async(r,t)=>{if(t.retryIds!==void 0&&t.retryIds.length>0)return y(r,t.payload,t.retryIds);const s=await r.broadcastPage(t.payload,t.filter);return{failedIds:u(s.result.outcomes),nextFilter:g(t.filter,s.nextCursor,s.result.total),result:s.result}};export{v as enqueuePushBroadcast,w as runPushBroadcastPage};
@@ -1 +1 @@
1
- import{c as e,f as r,i as c,k as d,e as l,l as I,g as f,n as g,t as i,w as o}from"./normalize-B7YlG5HG.mjs";export{e as claimRefusal,r as fcmId,c as isGoneError,d as kindOfId,l as legacyFcmId,I as legacyIdFor,f as legacyWebPushId,g as normalizeRegisterInput,i as targetOf,o as webPushId};
1
+ import{c as e,f as r,i as c,k as d,e as l,l as I,g as f,n as g,t as i,w as o}from"./normalize-B5B9QUol.mjs";export{e as claimRefusal,r as fcmId,c as isGoneError,d as kindOfId,l as legacyFcmId,I as legacyIdFor,f as legacyWebPushId,g as normalizeRegisterInput,i as targetOf,o as webPushId};
@@ -1 +1 @@
1
- import{c as n,l as a}from"./normalize-B7YlG5HG.mjs";const l=(t,e)=>t.id<e.id?-1:t.id>e.id?1:0,u=(t,e)=>e===void 0?!0:!(e.kind!==void 0&&t.kind!==e.kind||e.userId!==void 0&&(t.userId??null)!==e.userId),m=()=>{const t=new Map;return{delete:e=>(t.delete(e),Promise.resolve()),deleteOwned:(e,r)=>{const o=t.get(e);return o===void 0||(o.userId??null)!==r?Promise.resolve(!1):(t.delete(e),Promise.resolve(!0))},get:e=>Promise.resolve(t.get(e)),list:e=>{const r=[];for(const d of t.values())u(d,e)&&r.push(d);r.sort(l);const o=e?.after===void 0?r:r.filter(d=>d.id>e.after),s=e?.limit!==void 0&&e.limit>0?o.slice(0,Math.trunc(e.limit)):o;return Promise.resolve(s)},markStatus:(e,r,o)=>{const s=t.get(e);return s!==void 0&&t.set(e,{...s,lastError:o,lastSeenAt:Date.now(),lastStatus:r}),Promise.resolve()},put:e=>{const r=t.get(e.id),o=n(r,e);if(o!==void 0)return Promise.reject(o);const s=r===void 0?e:{...r,...e,createdAt:r.createdAt};t.set(s.id,s);const d=a(e),i=d===void 0||d===e.id?void 0:t.get(d);return i!==void 0&&n(i,e)===void 0&&t.delete(i.id),Promise.resolve(s)}}};export{m as memorySubscriptionStore};
1
+ import{c as n,l as a}from"./normalize-B5B9QUol.mjs";const l=(t,e)=>t.id<e.id?-1:t.id>e.id?1:0,u=(t,e)=>e===void 0?!0:!(e.kind!==void 0&&t.kind!==e.kind||e.userId!==void 0&&(t.userId??null)!==e.userId),m=()=>{const t=new Map;return{delete:e=>(t.delete(e),Promise.resolve()),deleteOwned:(e,r)=>{const o=t.get(e);return o===void 0||(o.userId??null)!==r?Promise.resolve(!1):(t.delete(e),Promise.resolve(!0))},get:e=>Promise.resolve(t.get(e)),list:e=>{const r=[];for(const d of t.values())u(d,e)&&r.push(d);r.sort(l);const o=e?.after===void 0?r:r.filter(d=>d.id>e.after),s=e?.limit!==void 0&&e.limit>0?o.slice(0,Math.trunc(e.limit)):o;return Promise.resolve(s)},markStatus:(e,r,o)=>{const s=t.get(e);return s!==void 0&&t.set(e,{...s,lastError:o,lastSeenAt:Date.now(),lastStatus:r}),Promise.resolve()},put:e=>{const r=t.get(e.id),o=n(r,e);if(o!==void 0)return Promise.reject(o);const s=r===void 0?e:{...r,...e,createdAt:r.createdAt};t.set(s.id,s);const d=a(e),i=d===void 0||d===e.id?void 0:t.get(d);return i!==void 0&&n(i,e)===void 0&&t.delete(i.id),Promise.resolve(s)}}};export{m as memorySubscriptionStore};
@@ -0,0 +1 @@
1
+ import{LunoraError as f}from"@lunora/errors";const b=/^\d{1,3}$/u,S=/^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u,P=/^::ffff:0:([\da-f]{1,4}):([\da-f]{1,4})$/u,N=/^::[\da-f]{1,4}$/u,O=/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u,D=/^::(\d{1,3}(?:\.\d{1,3}){3})$/u,$=/^::([\da-f]{1,4}):([\da-f]{1,4})$/u,W=/^64:ff9b::[\da-f]{1,4}:[\da-f]{1,4}$/u,k=/^[\da-f]{1,4}$/u,B=[[[8193],23],[[16383],20],[[24320],16],[[256,0,0,1],64]],R=[[[8193,1,0,0,0,0,0,1],128],[[8193,1,0,0,0,0,0,2],128],[[8193,1,0,0,0,0,0,3],128],[[8193,3],32],[[8193,4,274],48],[[8193,5],32],[[8193,32],28],[[8193,48],28]],x=t=>{const e=t.split("::");if(e.length>2)return;const n=a=>a===void 0||a===""?[]:a.split(":").map(o=>k.test(o)?Number.parseInt(o,16):Number.NaN),r=n(e[0]),i=n(e[1]),s=8-r.length-i.length;if(s<0||e.length===1&&s!==0)return;const c=[...r,...Array.from({length:s},()=>0),...i];return c.some(Number.isNaN)?void 0:c},_=(t,[e,n])=>{for(let r=0;r*16<n;r+=1){const s=65535<<16-Math.min(16,n-r*16)&65535;if(((t[r]??0)&s)!==((e[r]??0)&s))return!1}return!0},L=/^\[|\]$/gu,M=/\.$/u,p=t=>{const e=t.split(".");if(e.length!==4)return;const n=e.map(r=>b.test(r)?Number(r):-1);if(!n.some(r=>r<0||r>255))return[n[0],n[1],n[2],n[3]]},l=([t,e,n])=>t===0||t===10||t===127||t===100&&e>=64&&e<=127||t===169&&e===254||t===172&&e>=16&&e<=31||t===192&&e===0&&(n===0||n===2)||t===192&&e===88&&n===99||t===192&&e===168||t===198&&(e===18||e===19)||t===198&&e===51&&n===100||t===203&&e===0&&n===113||t>=224,h=(t,e)=>{const n=Number.parseInt(t??"",16),r=Number.parseInt(e??"",16);return!Number.isFinite(n)||!Number.isFinite(r)?!0:l([Math.floor(n/256),n%256,Math.floor(r/256),r%256])},F=t=>{const e=t.toLowerCase(),n=S.exec(e);if(n)return h(n[1],n[2]);const r=P.exec(e);if(r)return h(r[1],r[2]);const i=O.exec(e);if(i){const o=p(i[1]??"");return o===void 0||l(o)}const s=D.exec(e);if(s){const o=p(s[1]??"");return o===void 0||l(o)}const c=$.exec(e);if(c)return h(c[1],c[2]);if(N.test(e)||W.test(e)||e.startsWith("64:ff9b:1:")||e.startsWith("2002:"))return!0;const a=x(e);return a!==void 0&&B.some(o=>_(a,o))&&!R.some(o=>_(a,o))?!0:e==="::"||e==="::1"||e.startsWith("fc")||e.startsWith("fd")||e.startsWith("fe8")||e.startsWith("fe9")||e.startsWith("fea")||e.startsWith("feb")||e.startsWith("fec")||e.startsWith("fed")||e.startsWith("fee")||e.startsWith("fef")||e.startsWith("ff")||e.startsWith("2001:db8:")||e.startsWith("100::")},V=t=>t==="localhost"||t.endsWith(".localhost")||t.endsWith(".local")||t.endsWith(".internal")||t.endsWith(".home.arpa"),U=t=>t.replaceAll(L,"").replace(M,"").toLowerCase(),H=t=>{const e=U(t);if(e.includes(":"))return F(e);const n=p(e);return n===void 0?V(e):l(n)},C=2166136261,Q=16777619,X=(t,e=C)=>{let n=e;for(let r=0;r<t.length;r+=1)n^=t.charCodeAt(r),n=Math.imul(n,Q);return(n>>>0).toString(16).padStart(8,"0")},u=t=>t.toString(16).padStart(4,"0"),w=t=>{let e=8997,n=33826,r=40164,i=52210;for(let s=0;s<t.length;s+=1){e^=t.charCodeAt(s);const c=e*435,a=n*435,o=r*435+e*256,I=i*435+n*256,g=a+(c>>>16),E=o+(g>>>16),T=I+(E>>>16);e=c&65535,n=g&65535,r=E&65535,i=T&65535}return u(i)+u(r)+u(n)+u(e)},y=4096,G=2048,j=2048,m=512,d=(t,e,n)=>{const r=new TextEncoder().encode(t).length;if(r>e)throw new f("BAD_REQUEST",`@lunora/notify: register() \`${n}\` is ${r.toString()} bytes, exceeding the ${e.toString()}-byte cap`)},v=t=>{if(t===void 0)return;const e=typeof t=="object"&&t!==null?Object.getPrototypeOf(t):void 0;if(!(typeof t=="object"&&t!==null&&!Array.isArray(t)&&(e===Object.prototype||e===null)))throw new f("BAD_REQUEST","@lunora/notify: register() `metadata` must be a plain object");let r;try{r=JSON.stringify(t)}catch(s){throw new f("BAD_REQUEST",`@lunora/notify: register() \`metadata\` is not JSON-serialisable: ${s instanceof Error?s.message:String(s)}`)}const i=new TextEncoder().encode(r).length;if(i>y)throw new f("BAD_REQUEST",`@lunora/notify: register() \`metadata\` is ${i.toString()} bytes, exceeding the ${y.toString()}-byte cap`);return t},Y=t=>`wp2_${w(t)}`,z=t=>`fcm2_${w(t)}`,st=t=>t.startsWith("fcm2_")||t.startsWith("fcm_")?"fcm":t.startsWith("wp2_")||t.startsWith("wp_")?"web-push":void 0,A=t=>X(t),J=t=>`wp_${A(t)}`,K=t=>`fcm_${A(t)}`,ot=t=>t.kind==="fcm"?t.token===void 0?void 0:K(t.token):t.endpoint===void 0?void 0:J(t.endpoint),it=(t,e)=>{const n=t?.userId??null;if(!(t===void 0||n===null||n===(e.userId??null)))return new f("FORBIDDEN",`@lunora/notify: subscription "${e.id}" is registered to a different user; a device must be unregistered by its owner before another account can claim it`)},q=t=>{if(typeof t!="string")return t??{};try{return JSON.parse(t)}catch(e){throw new f("BAD_REQUEST",`@lunora/notify: register() web-push subscription is not valid JSON: ${e instanceof Error?e.message:String(e)}`)}},Z=(t,e)=>{let n;try{n=new URL(t)}catch{throw new f("BAD_REQUEST",`@lunora/notify: register() web-push \`endpoint\` must be an absolute https URL (got "${t}")`)}if(n.protocol!=="https:")throw new f("BAD_REQUEST",`@lunora/notify: register() web-push \`endpoint\` must use https (got "${n.protocol}")`);if(e!==void 0&&e.length>0){if(!e.includes(n.origin))throw new f("FORBIDDEN",`@lunora/notify: register() web-push endpoint origin "${n.origin}" is not in the configured allowedPushOrigins allowlist`);return}if(H(n.hostname))throw new f("FORBIDDEN",`@lunora/notify: register() web-push endpoint host "${n.hostname}" is a private/internal address; configure allowedPushOrigins to permit a specific origin`)},ct=(t,e=Date.now(),n={})=>{if(t.kind===void 0?t.token!==void 0:t.kind==="fcm"){const{token:o}=t;if(typeof o!="string"||o==="")throw new f("BAD_REQUEST","@lunora/notify: register() fcm input requires a non-empty `token`");return d(o,j,"token"),{createdAt:e,id:z(o),kind:"fcm",lastSeenAt:e,metadata:v(t.metadata),token:o,userId:t.userId??null}}const i=q(t.subscription),{endpoint:s}=i,c=i.keys?.p256dh,a=i.keys?.auth;if(typeof s!="string"||s===""||typeof c!="string"||typeof a!="string")throw new f("BAD_REQUEST","@lunora/notify: register() web-push subscription requires `endpoint` and `keys.{p256dh, auth}`");return d(s,G,"endpoint"),d(a,m,"keys.auth"),d(c,m,"keys.p256dh"),Z(s,n.allowedPushOrigins),{createdAt:e,endpoint:s,id:Y(s),keys:{auth:a,p256dh:c},kind:"web-push",lastSeenAt:e,metadata:v(t.metadata),userId:t.userId??null}},at=t=>t.kind==="fcm"?t.token??"":JSON.stringify({endpoint:t.endpoint,keys:t.keys}),tt=/\bhttp\s*4(?:04|10)\b/iu,et=/\b(?:unregistered|not[\s-]?registered|registration-token-not-registered|requested entity was not found)\b/iu,nt=/\bsubscription (?:is )?(?:gone|expired|no longer valid)\b/iu,ft=(t,e)=>t===void 0?!1:tt.test(t)||nt.test(t)?!0:e!=="web-push"&&et.test(t);export{U as a,l as b,it as c,F as d,K as e,z as f,J as g,ft as i,st as k,ot as l,ct as n,p,at as t,Y as w};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/notify",
3
- "version": "1.0.0-alpha.69",
3
+ "version": "1.0.0-alpha.70",
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.47",
52
+ "@lunora/errors": "1.0.0-alpha.48",
53
53
  "@visulima/notification": "1.1.1"
54
54
  },
55
55
  "engines": {
@@ -1 +0,0 @@
1
- import{LunoraError as i}from"@lunora/errors";const I=/^\d{1,3}$/u,S=/^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u,b=/^::ffff:0:([\da-f]{1,4}):([\da-f]{1,4})$/u,P=/^::[\da-f]{1,4}$/u,O=/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u,D=/^::(\d{1,3}(?:\.\d{1,3}){3})$/u,N=/^::([\da-f]{1,4}):([\da-f]{1,4})$/u,W=/^64:ff9b::[\da-f]{1,4}:[\da-f]{1,4}$/u,$=/^\[|\]$/gu,k=/\.$/u,p=t=>{const e=t.split(".");if(e.length!==4)return;const n=e.map(r=>I.test(r)?Number(r):-1);if(!n.some(r=>r<0||r>255))return[n[0],n[1],n[2],n[3]]},l=([t,e,n])=>t===0||t===10||t===127||t===100&&e>=64&&e<=127||t===169&&e===254||t===172&&e>=16&&e<=31||t===192&&e===0&&(n===0||n===2)||t===192&&e===88&&n===99||t===192&&e===168||t===198&&(e===18||e===19)||t===198&&e===51&&n===100||t===203&&e===0&&n===113||t>=224,h=(t,e)=>{const n=Number.parseInt(t??"",16),r=Number.parseInt(e??"",16);return!Number.isFinite(n)||!Number.isFinite(r)?!0:l([Math.floor(n/256),n%256,Math.floor(r/256),r%256])},B=t=>{const e=t.toLowerCase(),n=S.exec(e);if(n)return h(n[1],n[2]);const r=b.exec(e);if(r)return h(r[1],r[2]);const o=O.exec(e);if(o){const a=p(o[1]??"");return a===void 0||l(a)}const s=D.exec(e);if(s){const a=p(s[1]??"");return a===void 0||l(a)}const c=N.exec(e);return c?h(c[1],c[2]):P.test(e)||W.test(e)||e.startsWith("64:ff9b:1:")||e.startsWith("2002:")||e.startsWith("2001:0:")?!0:e==="::"||e==="::1"||e.startsWith("fc")||e.startsWith("fd")||e.startsWith("fe8")||e.startsWith("fe9")||e.startsWith("fea")||e.startsWith("feb")||e.startsWith("fec")||e.startsWith("fed")||e.startsWith("fee")||e.startsWith("fef")||e.startsWith("ff")||e.startsWith("2001:db8:")||e.startsWith("100::")},R=t=>t==="localhost"||t.endsWith(".localhost")||t.endsWith(".local")||t.endsWith(".internal")||t.endsWith(".home.arpa"),x=t=>t.replaceAll($,"").replace(k,"").toLowerCase(),F=t=>{const e=x(t);if(e.includes(":"))return B(e);const n=p(e);return n===void 0?R(e):l(n)},L=2166136261,M=16777619,H=(t,e=L)=>{let n=e;for(let r=0;r<t.length;r+=1)n^=t.charCodeAt(r),n=Math.imul(n,M);return(n>>>0).toString(16).padStart(8,"0")},d=t=>t.toString(16).padStart(4,"0"),A=t=>{let e=8997,n=33826,r=40164,o=52210;for(let s=0;s<t.length;s+=1){e^=t.charCodeAt(s);const c=e*435,a=n*435,f=r*435+e*256,m=o*435+n*256,g=a+(c>>>16),E=f+(g>>>16),v=m+(E>>>16);e=c&65535,n=g&65535,r=E&65535,o=v&65535}return d(o)+d(r)+d(n)+d(e)},_=4096,U=2048,C=2048,y=512,u=(t,e,n)=>{const r=new TextEncoder().encode(t).length;if(r>e)throw new i("BAD_REQUEST",`@lunora/notify: register() \`${n}\` is ${r.toString()} bytes, exceeding the ${e.toString()}-byte cap`)},w=t=>{if(t===void 0)return;const e=typeof t=="object"&&t!==null?Object.getPrototypeOf(t):void 0;if(!(typeof t=="object"&&t!==null&&!Array.isArray(t)&&(e===Object.prototype||e===null)))throw new i("BAD_REQUEST","@lunora/notify: register() `metadata` must be a plain object");let r;try{r=JSON.stringify(t)}catch(s){throw new i("BAD_REQUEST",`@lunora/notify: register() \`metadata\` is not JSON-serialisable: ${s instanceof Error?s.message:String(s)}`)}const o=new TextEncoder().encode(r).length;if(o>_)throw new i("BAD_REQUEST",`@lunora/notify: register() \`metadata\` is ${o.toString()} bytes, exceeding the ${_.toString()}-byte cap`);return t},V=t=>`wp2_${A(t)}`,Q=t=>`fcm2_${A(t)}`,Z=t=>t.startsWith("fcm2_")||t.startsWith("fcm_")?"fcm":t.startsWith("wp2_")||t.startsWith("wp_")?"web-push":void 0,T=t=>H(t),X=t=>`wp_${T(t)}`,j=t=>`fcm_${T(t)}`,tt=t=>t.kind==="fcm"?t.token===void 0?void 0:j(t.token):t.endpoint===void 0?void 0:X(t.endpoint),et=(t,e)=>{const n=t?.userId??null;if(!(t===void 0||n===null||n===(e.userId??null)))return new i("FORBIDDEN",`@lunora/notify: subscription "${e.id}" is registered to a different user; a device must be unregistered by its owner before another account can claim it`)},Y=t=>{if(typeof t!="string")return t??{};try{return JSON.parse(t)}catch(e){throw new i("BAD_REQUEST",`@lunora/notify: register() web-push subscription is not valid JSON: ${e instanceof Error?e.message:String(e)}`)}},G=(t,e)=>{let n;try{n=new URL(t)}catch{throw new i("BAD_REQUEST",`@lunora/notify: register() web-push \`endpoint\` must be an absolute https URL (got "${t}")`)}if(n.protocol!=="https:")throw new i("BAD_REQUEST",`@lunora/notify: register() web-push \`endpoint\` must use https (got "${n.protocol}")`);if(e!==void 0&&e.length>0){if(!e.includes(n.origin))throw new i("FORBIDDEN",`@lunora/notify: register() web-push endpoint origin "${n.origin}" is not in the configured allowedPushOrigins allowlist`);return}if(F(n.hostname))throw new i("FORBIDDEN",`@lunora/notify: register() web-push endpoint host "${n.hostname}" is a private/internal address; configure allowedPushOrigins to permit a specific origin`)},nt=(t,e=Date.now(),n={})=>{if(t.kind===void 0?t.token!==void 0:t.kind==="fcm"){const{token:f}=t;if(typeof f!="string"||f==="")throw new i("BAD_REQUEST","@lunora/notify: register() fcm input requires a non-empty `token`");return u(f,C,"token"),{createdAt:e,id:Q(f),kind:"fcm",lastSeenAt:e,metadata:w(t.metadata),token:f,userId:t.userId??null}}const o=Y(t.subscription),{endpoint:s}=o,c=o.keys?.p256dh,a=o.keys?.auth;if(typeof s!="string"||s===""||typeof c!="string"||typeof a!="string")throw new i("BAD_REQUEST","@lunora/notify: register() web-push subscription requires `endpoint` and `keys.{p256dh, auth}`");return u(s,U,"endpoint"),u(a,y,"keys.auth"),u(c,y,"keys.p256dh"),G(s,n.allowedPushOrigins),{createdAt:e,endpoint:s,id:V(s),keys:{auth:a,p256dh:c},kind:"web-push",lastSeenAt:e,metadata:w(t.metadata),userId:t.userId??null}},rt=t=>t.kind==="fcm"?t.token??"":JSON.stringify({endpoint:t.endpoint,keys:t.keys}),J=/\bhttp\s*4(?:04|10)\b/iu,z=/\b(?:unregistered|not[\s-]?registered|registration-token-not-registered|requested entity was not found)\b/iu,K=/\bsubscription (?:is )?(?:gone|expired|no longer valid)\b/iu,st=(t,e)=>t===void 0?!1:J.test(t)||K.test(t)?!0:e!=="web-push"&&z.test(t);export{x as a,l as b,et as c,B as d,j as e,Q as f,X as g,st as i,Z as k,tt as l,nt as n,p,rt as t,V as w};