@lunora/notify 1.0.0-alpha.21 → 1.0.0-alpha.22

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 u}from"./packem_shared/defineNotify-CUi2k7pP.mjs";import{createNotify as p}from"./packem_shared/createNotify-GgvLs28y.mjs";import{buildEngine as d,routingPushProvider as x}from"./packem_shared/buildEngine-Cl_c6i9_.mjs";import{enqueuePushBroadcast as c,runPushBroadcastJob as P}from"./packem_shared/enqueuePushBroadcast-tf1LqgzA.mjs";import{d1SubscriptionStore as b}from"./packem_shared/d1SubscriptionStore-D5VVGBxm.mjs";import{memorySubscriptionStore as N}from"./packem_shared/memorySubscriptionStore-B60C_I7v.mjs";import{fcmId as g,isGoneError as y,normalizeRegisterInput as v,targetOf as B,webPushId as F}from"./packem_shared/fcmId-ggkSOUHd.mjs";export{e as FCM_ENV_KEYS,t as WEB_PUSH_ENV_KEYS,d as buildEngine,p as createNotify,b as d1SubscriptionStore,n as defineNotify,c as enqueuePushBroadcast,i as fcmFromEnv,g as fcmId,y as isGoneError,u as isNotifyDefinition,N as memorySubscriptionStore,v as normalizeRegisterInput,x as routingPushProvider,P as runPushBroadcastJob,B as targetOf,f as webPushFromEnv,F 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 u}from"./packem_shared/defineNotify-CUi2k7pP.mjs";import{createNotify as p}from"./packem_shared/createNotify-qHVmV2yH.mjs";import{buildEngine as d,routingPushProvider as x}from"./packem_shared/buildEngine-oDWs9Pom.mjs";import{enqueuePushBroadcast as c,runPushBroadcastJob as P}from"./packem_shared/enqueuePushBroadcast-tf1LqgzA.mjs";import{d1SubscriptionStore as b}from"./packem_shared/d1SubscriptionStore-D5VVGBxm.mjs";import{memorySubscriptionStore as N}from"./packem_shared/memorySubscriptionStore-B60C_I7v.mjs";import{fcmId as g,isGoneError as y,normalizeRegisterInput as v,targetOf as B,webPushId as F}from"./packem_shared/fcmId-ggkSOUHd.mjs";export{e as FCM_ENV_KEYS,t as WEB_PUSH_ENV_KEYS,d as buildEngine,p as createNotify,b as d1SubscriptionStore,n as defineNotify,c as enqueuePushBroadcast,i as fcmFromEnv,g as fcmId,y as isGoneError,u as isNotifyDefinition,N as memorySubscriptionStore,v as normalizeRegisterInput,x as routingPushProvider,P as runPushBroadcastJob,B as targetOf,f as webPushFromEnv,F as webPushId};
@@ -0,0 +1 @@
1
+ import{LunoraError as k}from"@lunora/errors";import{createNotification as A}from"@visulima/notification";import{retryMiddleware as E,circuitBreakerMiddleware as I}from"@visulima/notification/middleware";import{fcmProvider as R}from"@visulima/notification/providers/fcm";import{webPushProvider as T}from"@visulima/notification/providers/web-push";import{n as D,p as h,a as _,b as S}from"./ssrf-host-BCpHorGa.mjs";const N=(e,n)=>{if(e.size<n)return;const t=e.keys().next().value;t!==void 0&&e.delete(t)},O="https://cloudflare-dns.com/dns-query",u=1,l=28,C=2e3,M=(e,n)=>{if(n===u){const t=h(e);return t===void 0||_(t)}return S(e.toLowerCase())},f=async(e,n,t)=>{try{const r=await fetch(`${O}?name=${encodeURIComponent(e)}&type=${String(n)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(t)});return r.ok?(await r.json()).Answer??[]:void 0}catch{return}},z=async(e,n=C)=>{const t=D(e);if(t.includes(":")||h(t)!==void 0)return{kind:"unknown"};const[r,i]=await Promise.all([f(t,u,n),f(t,l,n)]);if(r===void 0&&i===void 0)return{kind:"unknown"};for(const o of[...r??[],...i??[]])if((o.type===u||o.type===l)&&M(o.data,o.type))return{address:o.data,kind:"private"};return{kind:"public"}},$=e=>{let n=e;if(typeof e=="string"){if(!e.startsWith("{"))return;try{n=JSON.parse(e)}catch{return}}const t=n?.endpoint;return typeof t=="string"?t:void 0},d=new Map,j=256,H=async(e,n)=>{if(n!==void 0&&n.length>0)return;let t;try{({hostname:t}=new URL(e))}catch{return}const r=d.get(t),i=r??z(t),o=await i;if(r===void 0&&o.kind!=="unknown"&&(N(d,j),d.set(t,i)),o.kind==="private")throw new k("FORBIDDEN",`@lunora/notify: web-push endpoint host "${t}" resolves to a private/internal address (${o.address}); refusing to send (DNS-rebinding guard)`)},L=(e,n)=>{const t=[e.provider,n.provider].filter(i=>i!==void 0),r=[...e.recipients??[],...n.recipients??[]];return{...e,messageId:[e.messageId,n.messageId].join(","),response:[e.response,n.response],sent:e.sent&&n.sent,timestamp:new Date(Math.max(e.timestamp.getTime(),n.timestamp.getTime())),...t.length>0?{provider:t.join(",")}:{},...r.length>0?{recipients:r}:{}}},x=(e,n)=>{if(!e.success||!n.success)return e.success||n.success?e.success?n:e:{error:new AggregateError([e.error,n.error],"@lunora/notify: both push target groups failed"),success:!1};const t=e.data===void 0||n.data===void 0?e.data??n.data:L(e.data,n.data);return t===void 0?{success:!0}:{data:t,success:!0}},B=e=>{const n=t=>{const r=t===void 0?e.fcm:e.webPush;if(r===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 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 t=>{const r=Array.isArray(t.to)?t.to:[t.to],i=r.map(s=>$(s));for(const s of i)s!==void 0&&await H(s,e.allowedPushOrigins);const o=r.filter((s,a)=>i[a]!==void 0),p=r.filter((s,a)=>i[a]===void 0),c=i.find(s=>s!==void 0);if(p.length===0&&c!==void 0)return n(c).send(t);if(o.length===0)return n(void 0).send(t);const m=s=>({...t,to:s.length===1&&s[0]!==void 0?s[0]:s}),w=n(c),g=n(void 0),v=async(s,a)=>s.send(m(a)),b=await Promise.allSettled([v(w,o),v(g,p)]),[P,y]=b.map(s=>s.status==="fulfilled"?s.value:{error:s.reason,success:!1});return x(P,y)}}},J=e=>{const n=e.webPush===void 0?void 0:T(e.webPush),t=e.fcm===void 0?void 0:R(e.fcm),r={};(n!==void 0||t!==void 0)&&(r.push=B({allowedPushOrigins:e.allowedPushOrigins,fcm:t,webPush:n})),e.chat!==void 0&&(r.chat=e.chat),e.inApp!==void 0&&(r.inapp=e.inApp),e.webhook!==void 0&&(r.webhook=e.webhook);const i=A(r);return i.use(E()).use(I()),i};export{J as buildEngine,B as routingPushProvider};
@@ -1 +1 @@
1
- import{LunoraError as v}from"@lunora/errors";import{buildEngine as F}from"./buildEngine-Cl_c6i9_.mjs";import{memorySubscriptionStore as R}from"./memorySubscriptionStore-B60C_I7v.mjs";import{normalizeRegisterInput as z,targetOf as B,isGoneError as L}from"./fcmId-ggkSOUHd.mjs";const W=250,w=(s,l)=>typeof s=="function"?s(l):s,S=s=>s.successful?void 0:s.errorMessages.join("; "),U=(s,l)=>s.successful?"accepted":L(l)?"gone":"failed",j=async(s,l,d)=>{const u=Array.from({length:s.length});let h=0;const g=async()=>{for(;h<s.length;){const f=h;h+=1,u[f]=await d(s[f])}};return await Promise.all(Array.from({length:Math.min(l,s.length)},()=>g())),u},G=(s,l)=>({allowedPushOrigins:s.allowedPushOrigins,chat:w(s.chat,l),fcm:w(s.fcm,l),inApp:w(s.inApp,l),webhook:w(s.webhook,l),webPush:w(s.webPush,l)}),x=new WeakMap,Q=(s,l)=>{let d=x.get(s);d===void 0&&(d=new WeakMap,x.set(s,d));let u=d.get(l);return u===void 0&&(u={warnedNoPushOriginAllowlist:!1,warnedNoStore:!1},d.set(l,u)),u},Y=(s,l,d={})=>{const u=Q(s,l);let h;d.engine===void 0?(u.engine??=F(G(s,l)),h=u.engine):h=d.engine,u.store??=s.store?.(l);let{store:g}=u;g===void 0&&(u.fallbackStore??=R(),!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??10),b=Math.max(1,d.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=s.allowedPushOrigins!==void 0&&s.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},E=async(e,t,r)=>{let n,a,o;try{n=await h.sendToChannel("push",{...t,to:B(e)}),a=S(n),o=U(n,a)}catch(c){o="failed",a=c instanceof Error?c.message:String(c)}try{o==="accepted"?await f.markStatus(e.id,"ok"):o==="gone"?await f.delete(e.id):await f.markStatus(e.id,"failed",a)}catch{}return o==="failed"&&y("push",e.kind,{error:a,subscriptionId:e.id,userId:e.userId??null}),r&&p("push",e.kind,o),{error:a,receipt:n,status:o}},D=async(e,t)=>{const r=await j(t,C,async o=>{const{error:c,status:i}=await E(o,e,!1);return{error:c,kind:o.kind,status:i,subscription:o}}),n=new Map;for(const{kind:o,status:c}of r){const i=`${o} ${c}`,m=n.get(i);m===void 0?n.set(i,{count:1,kind:o,status:c}):m.count+=1}for(const{count:o,kind:c,status:i}of n.values())p("push",c,i,o);const a=r.map(({error:o,status:c,subscription:i})=>c==="accepted"?{id:i.id,status:"ok"}:c==="gone"?{error:o,id:i.id,status:"expired"}:{error:o,id:i.id,status:"failed"});return{failed:a.filter(o=>o.status==="failed").length,outcomes:a,pruned:a.filter(o=>o.status==="expired").length,sent:a.filter(o=>o.status==="ok").length,total:a.length}},O=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}),o=t?.after===void 0?a:a.filter($=>$.id>t.after),c=o.length>n,i=c?o.slice(0,n):o;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 o=a===void 0?{...t,after:n}:{...t,after:n,limit:a-r.total},{nextCursor:c,result:i}=await O(e,o);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:O,list:e=>T(e),register:e=>("token"in e||I(),f.put(z(e,void 0,{allowedPushOrigins:s.allowedPushOrigins}))),send:async(e,t)=>{const{error:r,receipt:n}=await E(await _(e),t,!0);if(n===void 0)throw new v("INTERNAL",`@lunora/notify: push send failed: ${r??"unknown error"}`);return n},unregister:e=>f.delete(e)},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 F}from"./buildEngine-oDWs9Pom.mjs";import{memorySubscriptionStore as R}from"./memorySubscriptionStore-B60C_I7v.mjs";import{normalizeRegisterInput as z,targetOf as B,isGoneError as L}from"./fcmId-ggkSOUHd.mjs";const W=250,w=(s,l)=>typeof s=="function"?s(l):s,S=s=>s.successful?void 0:s.errorMessages.join("; "),U=(s,l)=>s.successful?"accepted":L(l)?"gone":"failed",j=async(s,l,d)=>{const u=Array.from({length:s.length});let h=0;const g=async()=>{for(;h<s.length;){const f=h;h+=1,u[f]=await d(s[f])}};return await Promise.all(Array.from({length:Math.min(l,s.length)},()=>g())),u},G=(s,l)=>({allowedPushOrigins:s.allowedPushOrigins,chat:w(s.chat,l),fcm:w(s.fcm,l),inApp:w(s.inApp,l),webhook:w(s.webhook,l),webPush:w(s.webPush,l)}),x=new WeakMap,Q=(s,l)=>{let d=x.get(s);d===void 0&&(d=new WeakMap,x.set(s,d));let u=d.get(l);return u===void 0&&(u={warnedNoPushOriginAllowlist:!1,warnedNoStore:!1},d.set(l,u)),u},Y=(s,l,d={})=>{const u=Q(s,l);let h;d.engine===void 0?(u.engine??=F(G(s,l)),h=u.engine):h=d.engine,u.store??=s.store?.(l);let{store:g}=u;g===void 0&&(u.fallbackStore??=R(),!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??10),b=Math.max(1,d.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=s.allowedPushOrigins!==void 0&&s.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},E=async(e,t,r)=>{let n,a,o;try{n=await h.sendToChannel("push",{...t,to:B(e)}),a=S(n),o=U(n,a)}catch(c){o="failed",a=c instanceof Error?c.message:String(c)}try{o==="accepted"?await f.markStatus(e.id,"ok"):o==="gone"?await f.delete(e.id):await f.markStatus(e.id,"failed",a)}catch{}return o==="failed"&&y("push",e.kind,{error:a,subscriptionId:e.id,userId:e.userId??null}),r&&p("push",e.kind,o),{error:a,receipt:n,status:o}},D=async(e,t)=>{const r=await j(t,C,async o=>{const{error:c,status:i}=await E(o,e,!1);return{error:c,kind:o.kind,status:i,subscription:o}}),n=new Map;for(const{kind:o,status:c}of r){const i=`${o} ${c}`,m=n.get(i);m===void 0?n.set(i,{count:1,kind:o,status:c}):m.count+=1}for(const{count:o,kind:c,status:i}of n.values())p("push",c,i,o);const a=r.map(({error:o,status:c,subscription:i})=>c==="accepted"?{id:i.id,status:"ok"}:c==="gone"?{error:o,id:i.id,status:"expired"}:{error:o,id:i.id,status:"failed"});return{failed:a.filter(o=>o.status==="failed").length,outcomes:a,pruned:a.filter(o=>o.status==="expired").length,sent:a.filter(o=>o.status==="ok").length,total:a.length}},O=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}),o=t?.after===void 0?a:a.filter($=>$.id>t.after),c=o.length>n,i=c?o.slice(0,n):o;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 o=a===void 0?{...t,after:n}:{...t,after:n,limit:a-r.total},{nextCursor:c,result:i}=await O(e,o);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:O,list:e=>T(e),register:e=>("token"in e||I(),f.put(z(e,void 0,{allowedPushOrigins:s.allowedPushOrigins}))),send:async(e,t)=>{const{error:r,receipt:n}=await E(await _(e),t,!0);if(n===void 0)throw new v("INTERNAL",`@lunora/notify: push send failed: ${r??"unknown error"}`);return n},unregister:e=>f.delete(e)},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.21",
3
+ "version": "1.0.0-alpha.22",
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",
@@ -1 +0,0 @@
1
- import{LunoraError as f}from"@lunora/errors";import{createNotification as p}from"@visulima/notification";import{retryMiddleware as h,circuitBreakerMiddleware as v}from"@visulima/notification/middleware";import{fcmProvider as w}from"@visulima/notification/providers/fcm";import{webPushProvider as l}from"@visulima/notification/providers/web-push";import{n as b,p as u,a as m,b as P}from"./ssrf-host-BCpHorGa.mjs";const y=(n,i)=>{if(n.size<i)return;const t=n.keys().next().value;t!==void 0&&n.delete(t)},g="https://cloudflare-dns.com/dns-query",a=1,c=28,k=2e3,A=(n,i)=>{if(i===a){const t=u(n);return t===void 0||m(t)}return P(n.toLowerCase())},d=async(n,i,t)=>{try{const e=await fetch(`${g}?name=${encodeURIComponent(n)}&type=${String(i)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(t)});return e.ok?(await e.json()).Answer??[]:void 0}catch{return}},E=async(n,i=k)=>{const t=b(n);if(t.includes(":")||u(t)!==void 0)return{kind:"unknown"};const[e,r]=await Promise.all([d(t,a,i),d(t,c,i)]);if(e===void 0&&r===void 0)return{kind:"unknown"};for(const o of[...e??[],...r??[]])if((o.type===a||o.type===c)&&A(o.data,o.type))return{address:o.data,kind:"private"};return{kind:"public"}},I=n=>{let i=n;if(typeof n=="string"){if(!n.startsWith("{"))return;try{i=JSON.parse(n)}catch{return}}const t=i?.endpoint;return typeof t=="string"?t:void 0},s=new Map,D=256,N=async(n,i)=>{if(i!==void 0&&i.length>0)return;let t;try{({hostname:t}=new URL(n))}catch{return}const e=s.get(t),r=e??E(t),o=await r;if(e===void 0&&o.kind!=="unknown"&&(y(s,D),s.set(t,r)),o.kind==="private")throw new f("FORBIDDEN",`@lunora/notify: web-push endpoint host "${t}" resolves to a private/internal address (${o.address}); refusing to send (DNS-rebinding guard)`)},O=n=>{const i=t=>{const e=t===void 0?n.fcm:n.webPush;if(e===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 e};return{channel:"push",id:"lunora-push-router",initialize:async()=>{await n.webPush?.initialize(),await n.fcm?.initialize()},isAvailable:()=>(n.webPush??n.fcm)!==void 0,send:async t=>{const r=(Array.isArray(t.to)?t.to:[t.to]).map(o=>I(o));for(const o of r)o!==void 0&&await N(o,n.allowedPushOrigins);return i(r[0]).send(t)}}},M=n=>{const i=n.webPush===void 0?void 0:l(n.webPush),t=n.fcm===void 0?void 0:w(n.fcm),e={};(i!==void 0||t!==void 0)&&(e.push=O({allowedPushOrigins:n.allowedPushOrigins,fcm:t,webPush:i})),n.chat!==void 0&&(e.chat=n.chat),n.inApp!==void 0&&(e.inapp=n.inApp),n.webhook!==void 0&&(e.webhook=n.webhook);const r=p(e);return r.use(h()).use(v()),r};export{M as buildEngine,O as routingPushProvider};