@candypoets/nipworker 0.89.1 → 0.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- export { createRelayProxyServer, attachRelayProxyToServer, createExpressRelayProxyMiddleware, type RelayProxyServerOptions, type RelayProxyServer, type AttachRelayProxyOptions, type AttachedRelayProxy } from './relayProxyServer';
1
+ export {};
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,sBAAsB,EACtB,wBAAwB,EACxB,iCAAiC,EACjC,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC"}
@@ -1 +0,0 @@
1
- import{attachRelayProxyToServer as e,createExpressRelayProxyMiddleware as r,createRelayProxyServer as a}from"../relayProxyServer.js";export{e as attachRelayProxyToServer,r as createExpressRelayProxyMiddleware,a as createRelayProxyServer};
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -68,7 +68,7 @@ export declare function createRelayProxyWebSocketServer(options: Omit<AttachRela
68
68
  * // SvelteKit with adapter-node
69
69
  * import { createServer } from 'http';
70
70
  * import { handler } from './build/handler.js';
71
- * import { attachRelayProxyToServer } from '@candypoets/nipworker/proxy';
71
+ * import { attachRelayProxyToServer } from '@candypoets/nipworker/proxy/server';
72
72
  *
73
73
  * const server = createServer(handler);
74
74
  * attachRelayProxyToServer({ server, path: '/ws-proxy' });
@@ -81,7 +81,7 @@ export declare function attachRelayProxyToServer(options: AttachRelayProxyOption
81
81
  *
82
82
  * @example
83
83
  * import express from 'express';
84
- * import { createExpressRelayProxyMiddleware } from '@candypoets/nipworker/proxy';
84
+ * import { createExpressRelayProxyMiddleware } from '@candypoets/nipworker/proxy/server';
85
85
  *
86
86
  * const app = express();
87
87
  *
@@ -1 +1 @@
1
- {"version":3,"file":"relayProxyServer.d.ts","sourceRoot":"","sources":["../../src/proxy/relayProxyServer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,OAAO,EAAa,eAAe,EAAE,MAAM,IAAI,CAAC;AAWhD,KAAK,sBAAsB,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEvE,MAAM,MAAM,uBAAuB,GAAG;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,yCAAyC;IACzC,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;IACjC,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,qEAAqE;IACrE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,GAAG,EAAE,eAAe,CAAC;IACrB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,kDAAkD;IAClD,aAAa,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChF,CAAC;AAmCF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CA4E9F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,+BAA+B,CAC9C,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAAA;CAAE,GACtF,mBAAmB,CAkFrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,uBAAuB,GAAG,kBAAkB,CAoD7F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iCAAiC,CAChD,CAAC,SAAS;IAAE,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,GAAG,WAAW,CAAA;CAAE,EACjE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAAG,kBAAkB,CAoF9E"}
1
+ {"version":3,"file":"relayProxyServer.d.ts","sourceRoot":"","sources":["../../src/proxy/relayProxyServer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,OAAO,EAAa,eAAe,EAAE,MAAM,IAAI,CAAC;AAWhD,KAAK,sBAAsB,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEvE,MAAM,MAAM,uBAAuB,GAAG;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,yCAAyC;IACzC,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;IACjC,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,qEAAqE;IACrE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,GAAG,EAAE,eAAe,CAAC;IACrB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,kDAAkD;IAClD,aAAa,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChF,CAAC;AAwCF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CA4E9F;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,+BAA+B,CAC9C,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAAA;CAAE,GACtF,mBAAmB,CAkFrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,uBAAuB,GAAG,kBAAkB,CAoD7F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iCAAiC,CAChD,CAAC,SAAS;IAAE,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,GAAG,WAAW,CAAA;CAAE,EACjE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAAG,kBAAkB,CAoF9E"}
@@ -1 +1 @@
1
- import*as b from"flatbuffers";import{WebSocketServer as m,WebSocket as d}from"ws";import{f as h,M as E,d as B,R as x,N as F,S as C,g as _}from"./worker-message.js";function $(t={}){const e=t.host??"127.0.0.1",s=t.port??7777,o=t.path??"/",n=t.logger??console;let r;try{r=new m({host:e,port:s,path:o})}catch(f){throw n.error(`[relay-proxy] failed to create WebSocketServer: ${String(f)}`),f}let i=s;const a=r.address();return a&&typeof a=="object"&&(i=a.port),r.on("error",f=>{n.error(`[relay-proxy] WebSocketServer error: ${String(f)}`)}),r.on("connection",f=>{const c={relaySockets:new Map,pendingFrames:new Map,dedupBySubId:new Map,lastSubIdByRelay:new Map};f.on("message",(u,l)=>{if(l){const p=N(u);if(!p)return;O(c,f,p,n);return}const w=S(u);w&&k(c,w,n)}),f.on("close",()=>{y(c)}),f.on("error",()=>{y(c)})}),n.info(`[relay-proxy] listening on ws://${e}:${i}${o}`),{port:i,close:()=>new Promise((f,c)=>{r.close(u=>{if(u){c(u);return}f()})})}}function P(t){const{server:e,path:s="/",logger:o=console}=t,n=new m({server:e,path:s});return n.on("connection",r=>{const i={relaySockets:new Map,pendingFrames:new Map,dedupBySubId:new Map,lastSubIdByRelay:new Map};r.on("message",(a,f)=>{if(f){const u=N(a);if(!u)return;O(i,r,u,o);return}const c=S(a);c&&k(i,c,o)}),r.on("close",()=>{y(i)}),r.on("error",()=>{y(i)})}),o.info(`[relay-proxy] attached to server at path: ${s}`),{close:()=>new Promise((r,i)=>{n.close(a=>{if(a){i(a);return}r()})})}}function T(t,e){const s=e.path??"/ws-proxy",o=e.logger??console;let n=null;const r=new Map,i=t.listen.bind(t);return t.listen=(...a)=>{const f=i(...a);return n=new m({server:f,path:s}),n.on("connection",c=>{const u={relaySockets:new Map,pendingFrames:new Map,dedupBySubId:new Map,lastSubIdByRelay:new Map};r.set(c,u),c.on("message",(l,w)=>{const p=r.get(c);if(!p)return;if(w){const I=N(l);if(!I)return;O(p,c,I,o);return}const A=S(l);A&&k(p,A,o)}),c.on("close",()=>{const l=r.get(c);l&&(y(l),r.delete(c))}),c.on("error",()=>{const l=r.get(c);l&&(y(l),r.delete(c))})}),o.info(`[relay-proxy] attached to Express server at path: ${s}`),f},{close:()=>new Promise((a,f)=>{if(!n){a();return}r.forEach(c=>y(c)),r.clear(),n.close(c=>{if(c){f(c);return}a()})})}}function k(t,e,s){let o;try{o=JSON.parse(e)}catch{return}if(j(o)){const n=JSON.stringify(["AUTH",o.event]);R(t,o.relay,n,s);return}if(H(o)){t.dedupBySubId.delete(o.subscription_id);for(const[n,r]of t.relaySockets.entries())r.readyState===d.OPEN&&r.send(JSON.stringify(["CLOSE",o.subscription_id])),t.lastSubIdByRelay.set(n,o.subscription_id)}}function O(t,e,s,o){for(const n of s.relays){U(t,e,n,o);for(const r of s.frames)D(t,n,r),R(t,n,r,o)}}function U(t,e,s,o){const n=t.relaySockets.get(s);if(n&&n.readyState!==d.CLOSED)return;const r=new d(s);t.relaySockets.set(s,r),t.pendingFrames.set(s,[]),r.on("open",()=>{const i=t.pendingFrames.get(s);if(i){for(const a of i)r.send(a);t.pendingFrames.set(s,[])}}),r.on("message",i=>{const a=S(i);if(!a||e.readyState!==d.OPEN)return;const f=t.lastSubIdByRelay.get(s),c=J(t,s,a,f);c&&e.send(c,{binary:!0})}),r.on("close",()=>{t.pendingFrames.delete(s)}),r.on("error",i=>{if(!t.pendingFrames.has(s))return;const a=String(i);!a.includes("ECONNREFUSED")&&!a.includes("ENOTFOUND")&&o.warn(`[relay-proxy] relay socket error for ${s}: ${a.slice(0,100)}`),t.pendingFrames.delete(s)})}function D(t,e,s){const o=M(s);if(!o)return;const n=o[0];if(n!=="REQ"&&n!=="CLOSE")return;const r=typeof o[1]=="string"?o[1]:null;if(r){if(t.lastSubIdByRelay.set(e,r),n==="REQ"){t.dedupBySubId.has(r)||t.dedupBySubId.set(r,new Set);return}t.dedupBySubId.delete(r)}}function R(t,e,s,o){const n=t.relaySockets.get(e);if(n){if(n.readyState===d.OPEN){n.send(s);return}if(n.readyState===d.CONNECTING){const r=t.pendingFrames.get(e)??[];r.push(s),t.pendingFrames.set(e,r);return}o.warn(`[relay-proxy] dropping frame for closed relay socket ${e}`)}}function J(t,e,s,o){const n=M(s);if(!n||n.length<1||typeof n[0]!="string")return v(o??"",e,s);const r=n[0];if(r==="EVENT"){const i=typeof n[1]=="string"?n[1]:"",a=W(n[2]);if(!i||!a)return v(i,e,s);const f=t.dedupBySubId.get(i);return f&&f.has(a.id)?null:(f&&f.add(a.id),L(i,e,a))}if(r==="NOTICE"){const i=n[1]===void 0?null:String(n[1]);return g("",e,"NOTICE",i)}if(r==="AUTH"){const i=n[1]===void 0?null:String(n[1]);return g(o??"",e,"AUTH",i)}if(r==="CLOSED"){const i=typeof n[1]=="string"?n[1]:"",a=n[2]===void 0?null:String(n[2]);return g(i,e,"CLOSED",a)}if(r==="OK"){const i=typeof n[1]=="string"?n[1]:"",a=n[2]===void 0?"false":String(n[2]),f=n[3]===void 0?null:String(n[3]);return g(i,e,a,f)}if(r==="EOSE"){const i=typeof n[1]=="string"?n[1]:"";return g(i,e,"EOSE",null)}return v(o??"",e,s)}function L(t,e,s){const o=new b.Builder(1024),n=new h(t||null,e,E.NostrEvent,B.NostrEvent,new F(s.id,s.pubkey,s.kind,s.content,s.tags.map(r=>new C(r)),s.created_at,s.sig));return o.finish(n.pack(o)),o.asUint8Array()}function g(t,e,s,o){const n=new b.Builder(256),r=new h(t||null,e,E.ConnectionStatus,B.ConnectionStatus,new _(e,s,o));return n.finish(r.pack(n)),n.asUint8Array()}function v(t,e,s){const o=new b.Builder(256),n=new h(t||null,e,E.Raw,B.Raw,new x(s));return o.finish(n.pack(o)),o.asUint8Array()}function W(t){if(!t||typeof t!="object")return null;const e=t;if(typeof e.id!="string"||typeof e.pubkey!="string"||typeof e.kind!="number"||typeof e.content!="string"||typeof e.created_at!="number"||typeof e.sig!="string"||!Array.isArray(e.tags))return null;const s=e.tags.map(o=>Array.isArray(o)?o.filter(n=>typeof n=="string"):[]).filter(o=>o.length>0);return{id:e.id,pubkey:e.pubkey,kind:e.kind,content:e.content,created_at:e.created_at,sig:e.sig,tags:s}}function M(t){try{const e=JSON.parse(t);return Array.isArray(e)?e:null}catch{return null}}function N(t){const e=S(t);if(!e)return null;try{const s=JSON.parse(e);if(!Array.isArray(s.relays)||!Array.isArray(s.frames))return null;const o=s.relays.filter(r=>typeof r=="string"),n=s.frames.filter(r=>typeof r=="string");return o.length===0||n.length===0?null:{relays:o,frames:n}}catch{return null}}function S(t){return typeof t=="string"?t:t instanceof ArrayBuffer?Buffer.from(t).toString("utf8"):Buffer.isBuffer(t)?t.toString("utf8"):t instanceof Uint8Array?Buffer.from(t).toString("utf8"):Array.isArray(t)?Buffer.concat(t.filter(e=>Buffer.isBuffer(e))).toString("utf8"):""}function j(t){if(!t||typeof t!="object")return!1;const e=t;return e.type==="auth_response"&&typeof e.relay=="string"&&e.event!==void 0}function H(t){if(!t||typeof t!="object")return!1;const e=t;return e.type==="close_sub"&&typeof e.subscription_id=="string"}function y(t){t.dedupBySubId.clear(),t.lastSubIdByRelay.clear();for(const e of t.relaySockets.values())try{e.close()}catch{}t.relaySockets.clear(),t.pendingFrames.clear()}export{P as attachRelayProxyToServer,T as createExpressRelayProxyMiddleware,$ as createRelayProxyServer};
1
+ import*as b from"flatbuffers";import{WebSocketServer as v,WebSocket as l}from"ws";import{R as C,W as m,M as h,e as E,N as B,d as R,S as N}from"./worker-message.js";function F(e={}){const r=e.host??"127.0.0.1",n=e.port??7777,o=e.path??"/",t=e.logger??console;let s;try{s=new v({host:r,port:n,path:o})}catch(c){throw t.error(`[relay-proxy] failed to create WebSocketServer: ${String(c)}`),c}let i=n;const a=s.address();return a&&typeof a=="object"&&(i=a.port),s.on("error",c=>{t.error(`[relay-proxy] WebSocketServer error: ${String(c)}`)}),s.on("connection",c=>{const f={relaySockets:new Map,pendingFrames:new Map,dedupBySubId:new Map,lastSubIdByRelay:new Map};c.on("message",(u,S)=>{if(S){const d=L(u);if(!d)return;T(f,c,d,t);return}const g=A(u);g&&_(f,g,t)}),c.on("close",()=>{I(f)}),c.on("error",()=>{I(f)})}),t.info(`[relay-proxy] listening on ws://${r}:${i}${o}`),{port:i,close:()=>new Promise((c,f)=>{s.close(u=>{if(u){f(u);return}c()})})}}function _(e,r,n){let o;try{o=JSON.parse(r)}catch{return}if(P(o)){const t=JSON.stringify(["AUTH",o.event]);O(e,o.relay,t,n);return}if(V(o)){e.dedupBySubId.delete(o.subscription_id);for(const[t,s]of e.relaySockets.entries())s.readyState===l.OPEN&&s.send(JSON.stringify(["CLOSE",o.subscription_id])),e.lastSubIdByRelay.set(t,o.subscription_id)}}function T(e,r,n,o){const t=n.frames.map(s=>({frame:s,state:W(s)}));for(const s of n.relays){M(e,r,s,o);for(const i of t){U(e,s,i.state);const a=i.frame;O(e,s,a,o)}}}function M(e,r,n,o){const t=e.relaySockets.get(n);if(t&&t.readyState!==l.CLOSED)return;const s=new l(n);e.relaySockets.set(n,s),e.pendingFrames.set(n,[]),s.on("open",()=>{const i=e.pendingFrames.get(n);if(i){for(const a of i)s.send(a);e.pendingFrames.set(n,[])}}),s.on("message",i=>{const a=A(i);if(!a||r.readyState!==l.OPEN)return;const c=e.lastSubIdByRelay.get(n),f=$(e,n,a,c);f&&r.send(f,{binary:!0})}),s.on("close",()=>{e.pendingFrames.delete(n)}),s.on("error",i=>{if(!e.pendingFrames.has(n))return;const a=String(i);!a.includes("ECONNREFUSED")&&!a.includes("ENOTFOUND")&&o.warn(`[relay-proxy] relay socket error for ${n}: ${a.slice(0,100)}`),e.pendingFrames.delete(n)})}function U(e,r,n){if(n){if(e.lastSubIdByRelay.set(r,n.subId),n.type==="REQ"){const o=n.subId;e.dedupBySubId.has(o)||e.dedupBySubId.set(o,new Set);return}e.dedupBySubId.delete(n.subId)}}function W(e){const r=w(e);if(!r)return null;const n=r[0];if(n!=="REQ"&&n!=="CLOSE")return null;const o=typeof r[1]=="string"?r[1]:null;return o?{type:n,subId:o}:null}function O(e,r,n,o){const t=e.relaySockets.get(r);if(t){if(t.readyState===l.OPEN){t.send(n);return}if(t.readyState===l.CONNECTING){const s=e.pendingFrames.get(r)??[];s.push(n),e.pendingFrames.set(r,s);return}o.warn(`[relay-proxy] dropping frame for closed relay socket ${r}`)}}function $(e,r,n,o){const t=w(n);if(!t||t.length<1||typeof t[0]!="string")return k(o??"",r,n);const s=t[0];if(s==="EVENT"){const i=typeof t[1]=="string"?t[1]:"",a=D(t[2]);if(!i||!a)return k(i,r,n);const c=e.dedupBySubId.get(i);return c&&c.has(a.id)?null:(c&&c.add(a.id),x(i,r,a))}if(s==="NOTICE"){const i=t[1]===void 0?null:String(t[1]);return y("",r,"NOTICE",i)}if(s==="AUTH"){const i=t[1]===void 0?null:String(t[1]);return y(o??"",r,"AUTH",i)}if(s==="CLOSED"){const i=typeof t[1]=="string"?t[1]:"",a=t[2]===void 0?null:String(t[2]);return y(i,r,"CLOSED",a)}if(s==="OK"){const i=typeof t[1]=="string"?t[1]:"",a=t[2]===void 0?"false":String(t[2]),c=t[3]===void 0?null:String(t[3]);return y(i,r,a,c)}if(s==="EOSE"){const i=typeof t[1]=="string"?t[1]:"";return y(i,r,"EOSE",null)}return k(o??"",r,n)}function x(e,r,n){const o=new b.Builder(1024),t=e?o.createString(e):0,s=o.createString(r),i=o.createString(n.id),a=o.createString(n.pubkey),c=o.createString(n.content),f=o.createString(n.sig),u=new Array(n.tags.length);for(let p=0;p<n.tags.length;p++)u[p]=J(o,n.tags[p]);const S=B.createTagsVector(o,u),g=B.createNostrEvent(o,i,a,n.kind,c,S,n.created_at,f),d=m.createWorkerMessage(o,t,s,h.NostrEvent,E.NostrEvent,g);return o.finish(d),o.asUint8Array()}function y(e,r,n,o){const t=new b.Builder(256),s=e?t.createString(e):0,i=t.createString(r),a=t.createString(n),c=o===null?0:t.createString(o),f=R.createConnectionStatus(t,i,a,c),u=m.createWorkerMessage(t,s,i,h.ConnectionStatus,E.ConnectionStatus,f);return t.finish(u),t.asUint8Array()}function k(e,r,n){const o=new b.Builder(256),t=e?o.createString(e):0,s=o.createString(r),i=o.createString(n),a=C.createRaw(o,i),c=m.createWorkerMessage(o,t,s,h.Raw,E.Raw,a);return o.finish(c),o.asUint8Array()}function D(e){if(!e||typeof e!="object")return null;const r=e;if(typeof r.id!="string"||typeof r.pubkey!="string"||typeof r.kind!="number"||typeof r.content!="string"||typeof r.created_at!="number"||typeof r.sig!="string"||!Array.isArray(r.tags))return null;const n=r.tags;let o=!1;for(const s of n){if(!Array.isArray(s)||s.length===0){o=!0;continue}for(const i of s)if(typeof i!="string"){o=!0;break}}let t;if(!o)t=n;else{t=[];for(const s of n){if(!Array.isArray(s))continue;const i=[];for(const a of s)typeof a=="string"&&i.push(a);i.length>0&&t.push(i)}}return{id:r.id,pubkey:r.pubkey,kind:r.kind,content:r.content,created_at:r.created_at,sig:r.sig,tags:t}}function J(e,r){const n=new Array(r.length);for(let t=0;t<r.length;t++)n[t]=e.createString(r[t]);const o=N.createItemsVector(e,n);return N.createStringVec(e,o)}function w(e){try{const r=JSON.parse(e);return Array.isArray(r)?r:null}catch{return null}}function L(e){const r=A(e);if(!r)return null;try{const n=JSON.parse(r);if(!Array.isArray(n.relays)||!Array.isArray(n.frames))return null;const o=n.relays.filter(s=>typeof s=="string"),t=n.frames.filter(s=>typeof s=="string");return o.length===0||t.length===0?null:{relays:o,frames:t}}catch{return null}}function A(e){return typeof e=="string"?e:e instanceof ArrayBuffer?Buffer.from(e).toString("utf8"):Buffer.isBuffer(e)?e.toString("utf8"):e instanceof Uint8Array?Buffer.from(e).toString("utf8"):Array.isArray(e)?Buffer.concat(e.filter(r=>Buffer.isBuffer(r))).toString("utf8"):""}function P(e){if(!e||typeof e!="object")return!1;const r=e;return r.type==="auth_response"&&typeof r.relay=="string"&&r.event!==void 0}function V(e){if(!e||typeof e!="object")return!1;const r=e;return r.type==="close_sub"&&typeof r.subscription_id=="string"}function I(e){e.dedupBySubId.clear(),e.lastSubIdByRelay.clear();for(const r of e.relaySockets.values())try{r.close()}catch{}e.relaySockets.clear(),e.pendingFrames.clear()}export{F as createRelayProxyServer};
@@ -1 +1 @@
1
- {"version":3,"file":"relayProxyServer.js","sources":["../src/proxy/relayProxyServer.ts"],"sourcesContent":["/// <reference types=\"node\" />\n\nimport type { Server as HttpServer } from 'http';\nimport type { Server as HttpsServer } from 'https';\nimport type { IncomingMessage } from 'http';\nimport type { Duplex } from 'stream';\nimport * as flatbuffers from 'flatbuffers';\nimport { WebSocket, WebSocketServer } from 'ws';\nimport {\n\tConnectionStatusT,\n\tMessage,\n\tMessageType,\n\tNostrEventT,\n\tRawT,\n\tStringVecT,\n\tWorkerMessageT\n} from '../generated/nostr/fb';\n\ntype RelayProxyServerLogger = Pick<Console, 'info' | 'warn' | 'error'>;\n\nexport type RelayProxyServerOptions = {\n\thost?: string;\n\tport?: number;\n\tpath?: string;\n\tlogger?: RelayProxyServerLogger;\n};\n\nexport type RelayProxyServer = {\n\tport: number;\n\tclose: () => Promise<void>;\n};\n\nexport type AttachRelayProxyOptions = {\n\t/** The HTTP/HTTPS server to attach to */\n\tserver: HttpServer | HttpsServer;\n\t/** The path to mount the WebSocket endpoint on (e.g., '/ws-proxy') */\n\tpath?: string;\n\tlogger?: RelayProxyServerLogger;\n};\n\nexport type AttachedRelayProxy = {\n\t/** Stop accepting new connections and close all existing sessions */\n\tclose: () => Promise<void>;\n};\n\nexport type WebSocketRelayProxy = {\n\twss: WebSocketServer;\n\tclose: () => Promise<void>;\n\t/** Manually handle a WebSocket upgrade request */\n\thandleUpgrade: (request: IncomingMessage, socket: Duplex, head: Buffer) => void;\n};\n\ntype Envelope = {\n\trelays: string[];\n\tframes: string[];\n};\n\ntype AuthResponseCommand = {\n\ttype: 'auth_response';\n\trelay: string;\n\tevent: unknown;\n};\n\ntype CloseSubCommand = {\n\ttype: 'close_sub';\n\tsubscription_id: string;\n};\n\ntype NostrEventJson = {\n\tid: string;\n\tpubkey: string;\n\tkind: number;\n\tcontent: string;\n\ttags: string[][];\n\tcreated_at: number;\n\tsig: string;\n};\n\ntype Session = {\n\trelaySockets: Map<string, WebSocket>;\n\tpendingFrames: Map<string, string[]>;\n\tdedupBySubId: Map<string, Set<string>>;\n\tlastSubIdByRelay: Map<string, string>;\n};\n\n/**\n * Create a standalone relay proxy server on its own port.\n * Use this for simple deployments or when you don't have an existing HTTP server.\n */\nexport function createRelayProxyServer(options: RelayProxyServerOptions = {}): RelayProxyServer {\n\tconst host = options.host ?? '127.0.0.1';\n\tconst port = options.port ?? 7777;\n\tconst path = options.path ?? '/';\n\tconst logger = options.logger ?? console;\n\n\tlet wss: WebSocketServer;\n\ttry {\n\t\twss = new WebSocketServer({\n\t\t\thost,\n\t\t\tport,\n\t\t\tpath\n\t\t});\n\t} catch (err) {\n\t\tlogger.error(`[relay-proxy] failed to create WebSocketServer: ${String(err)}`);\n\t\tthrow err;\n\t}\n\n\t// Get the actual port (in case port 0 was passed)\n\t// If the server is not yet listening, wss.address() returns null\n\t// In that case, wait for the 'listening' event\n\tlet actualPort = port;\n\tconst address = wss.address();\n\tif (address && typeof address === 'object') {\n\t\tactualPort = address.port;\n\t}\n\n\twss.on('error', (err) => {\n\t\tlogger.error(`[relay-proxy] WebSocketServer error: ${String(err)}`);\n\t});\n\n\twss.on('connection', (clientSocket) => {\n\t\tconst session: Session = {\n\t\t\trelaySockets: new Map(),\n\t\t\tpendingFrames: new Map(),\n\t\t\tdedupBySubId: new Map(),\n\t\t\tlastSubIdByRelay: new Map()\n\t\t};\n\n\t\tclientSocket.on('message', (data, isBinary) => {\n\t\t\tif (isBinary) {\n\t\t\t\tconst envelope = parseEnvelope(data);\n\t\t\t\tif (!envelope) return;\n\t\t\t\thandleEnvelope(session, clientSocket, envelope, logger);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst text = toUtf8(data);\n\t\t\tif (!text) return;\n\t\t\thandleClientCommand(session, text, logger);\n\t\t});\n\n\t\tclientSocket.on('close', () => {\n\t\t\tcloseSession(session);\n\t\t});\n\n\t\tclientSocket.on('error', () => {\n\t\t\tcloseSession(session);\n\t\t});\n\t});\n\n\tlogger.info(`[relay-proxy] listening on ws://${host}:${actualPort}${path}`);\n\n\treturn {\n\t\tport: actualPort,\n\t\tclose: () =>\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\twss.close((err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t})\n\t};\n}\n\n/**\n * Create a WebSocket relay proxy that supports manual upgrade handling.\n * Use this for Vite development servers where SvelteKit middleware might interfere\n * with the standard WebSocket upgrade mechanism.\n *\n * @example\n * // In a Vite plugin\n * const relayProxy = createRelayProxyWebSocketServer({ path: '/ws-proxy' });\n *\n * // In configureServer middleware\n * server.middlewares.use((req, res, next) => {\n * if (req.url?.startsWith('/ws-proxy') && req.headers.upgrade === 'websocket') {\n * server.httpServer.once('upgrade', (request, socket, head) => {\n * if (request.url?.startsWith('/ws-proxy')) {\n * relayProxy.handleUpgrade(request, socket, head);\n * }\n * });\n * }\n * next();\n * });\n */\nexport function createRelayProxyWebSocketServer(\n\toptions: Omit<AttachRelayProxyOptions, 'server'> & { server?: HttpServer | HttpsServer }\n): WebSocketRelayProxy {\n\tconst path = options.path ?? '/';\n\tconst logger = options.logger ?? console;\n\n\t// Create WebSocket server with noServer mode to handle upgrades manually\n\tconst wss = new WebSocketServer({\n\t\tnoServer: true\n\t});\n\n\tconst sessions = new Map<WebSocket, Session>();\n\n\twss.on('connection', (clientSocket) => {\n\t\tconst session: Session = {\n\t\t\trelaySockets: new Map(),\n\t\t\tpendingFrames: new Map(),\n\t\t\tdedupBySubId: new Map(),\n\t\t\tlastSubIdByRelay: new Map()\n\t\t};\n\t\tsessions.set(clientSocket, session);\n\n\t\tclientSocket.on('message', (data, isBinary) => {\n\t\t\tconst session = sessions.get(clientSocket);\n\t\t\tif (!session) return;\n\n\t\t\tif (isBinary) {\n\t\t\t\tconst envelope = parseEnvelope(data);\n\t\t\t\tif (!envelope) return;\n\t\t\t\thandleEnvelope(session, clientSocket, envelope, logger);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst text = toUtf8(data);\n\t\t\tif (!text) return;\n\t\t\thandleClientCommand(session, text, logger);\n\t\t});\n\n\t\tclientSocket.on('close', () => {\n\t\t\tconst session = sessions.get(clientSocket);\n\t\t\tif (session) {\n\t\t\t\tcloseSession(session);\n\t\t\t\tsessions.delete(clientSocket);\n\t\t\t}\n\t\t});\n\n\t\tclientSocket.on('error', () => {\n\t\t\tconst session = sessions.get(clientSocket);\n\t\t\tif (session) {\n\t\t\t\tcloseSession(session);\n\t\t\t\tsessions.delete(clientSocket);\n\t\t\t}\n\t\t});\n\t});\n\n\tconst handleUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer) => {\n\t\t// Verify path matches\n\t\tconst url = request.url ?? '';\n\t\tif (!url.startsWith(path)) {\n\t\t\treturn;\n\t\t}\n\n\t\twss.handleUpgrade(request, socket, head, (ws) => {\n\t\t\twss.emit('connection', ws, request);\n\t\t});\n\t};\n\n\treturn {\n\t\twss,\n\t\tclose: () =>\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\t// Close all sessions first\n\t\t\t\tsessions.forEach((session) => closeSession(session));\n\t\t\t\tsessions.clear();\n\t\t\t\twss.close((err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t}),\n\t\thandleUpgrade\n\t};\n}\n\n/**\n * Attach the relay proxy to an existing HTTP/HTTPS server.\n * Use this for embedding in SvelteKit (adapter-node), Express, or any Node.js server.\n *\n * @example\n * // SvelteKit with adapter-node\n * import { createServer } from 'http';\n * import { handler } from './build/handler.js';\n * import { attachRelayProxyToServer } from '@candypoets/nipworker/proxy';\n *\n * const server = createServer(handler);\n * attachRelayProxyToServer({ server, path: '/ws-proxy' });\n * server.listen(3000);\n */\nexport function attachRelayProxyToServer(options: AttachRelayProxyOptions): AttachedRelayProxy {\n\tconst { server, path = '/', logger = console } = options;\n\n\tconst wss = new WebSocketServer({\n\t\tserver,\n\t\tpath\n\t});\n\n\twss.on('connection', (clientSocket) => {\n\t\tconst session: Session = {\n\t\t\trelaySockets: new Map(),\n\t\t\tpendingFrames: new Map(),\n\t\t\tdedupBySubId: new Map(),\n\t\t\tlastSubIdByRelay: new Map()\n\t\t};\n\n\t\tclientSocket.on('message', (data, isBinary) => {\n\t\t\tif (isBinary) {\n\t\t\t\tconst envelope = parseEnvelope(data);\n\t\t\t\tif (!envelope) return;\n\t\t\t\thandleEnvelope(session, clientSocket, envelope, logger);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst text = toUtf8(data);\n\t\t\tif (!text) return;\n\t\t\thandleClientCommand(session, text, logger);\n\t\t});\n\n\t\tclientSocket.on('close', () => {\n\t\t\tcloseSession(session);\n\t\t});\n\n\t\tclientSocket.on('error', () => {\n\t\t\tcloseSession(session);\n\t\t});\n\t});\n\n\tlogger.info(`[relay-proxy] attached to server at path: ${path}`);\n\n\treturn {\n\t\tclose: () =>\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\twss.close((err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t})\n\t};\n}\n\n/**\n * Create an Express middleware that attaches the relay proxy to the Express server's underlying HTTP server.\n * Call this after setting up your Express app but before calling app.listen().\n *\n * @example\n * import express from 'express';\n * import { createExpressRelayProxyMiddleware } from '@candypoets/nipworker/proxy';\n *\n * const app = express();\n *\n * // Your Express routes...\n * app.get('/api/health', (req, res) => res.json({ ok: true }));\n *\n * // Attach relay proxy\n * const relayProxy = createExpressRelayProxyMiddleware(app, { path: '/ws-proxy' });\n *\n * const server = app.listen(3000, () => {\n * console.log('Server with relay proxy running on port 3000');\n * });\n *\n * // Cleanup on shutdown\n * process.on('SIGTERM', async () => {\n * await relayProxy.close();\n * server.close();\n * });\n */\nexport function createExpressRelayProxyMiddleware<\n\tT extends { listen: (...args: any[]) => HttpServer | HttpsServer }\n>(app: T, options: Omit<AttachRelayProxyOptions, 'server'>): AttachedRelayProxy {\n\tconst path = options.path ?? '/ws-proxy';\n\tconst logger = options.logger ?? console;\n\n\t// Store reference to the server once it's created\n\tlet wss: WebSocketServer | null = null;\n\tconst sessions = new Map<WebSocket, Session>();\n\n\t// Monkey-patch app.listen to capture the server instance\n\tconst originalListen = app.listen.bind(app);\n\t(app as any).listen = (...args: any[]) => {\n\t\tconst server = originalListen(...args);\n\n\t\twss = new WebSocketServer({\n\t\t\tserver,\n\t\t\tpath\n\t\t});\n\n\t\twss.on('connection', (clientSocket) => {\n\t\t\tconst session: Session = {\n\t\t\t\trelaySockets: new Map(),\n\t\t\t\tpendingFrames: new Map(),\n\t\t\t\tdedupBySubId: new Map(),\n\t\t\t\tlastSubIdByRelay: new Map()\n\t\t\t};\n\t\t\tsessions.set(clientSocket, session);\n\n\t\t\tclientSocket.on('message', (data, isBinary) => {\n\t\t\t\tconst session = sessions.get(clientSocket);\n\t\t\t\tif (!session) return;\n\n\t\t\t\tif (isBinary) {\n\t\t\t\t\tconst envelope = parseEnvelope(data);\n\t\t\t\t\tif (!envelope) return;\n\t\t\t\t\thandleEnvelope(session, clientSocket, envelope, logger);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst text = toUtf8(data);\n\t\t\t\tif (!text) return;\n\t\t\t\thandleClientCommand(session, text, logger);\n\t\t\t});\n\n\t\t\tclientSocket.on('close', () => {\n\t\t\t\tconst session = sessions.get(clientSocket);\n\t\t\t\tif (session) {\n\t\t\t\t\tcloseSession(session);\n\t\t\t\t\tsessions.delete(clientSocket);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tclientSocket.on('error', () => {\n\t\t\t\tconst session = sessions.get(clientSocket);\n\t\t\t\tif (session) {\n\t\t\t\t\tcloseSession(session);\n\t\t\t\t\tsessions.delete(clientSocket);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\tlogger.info(`[relay-proxy] attached to Express server at path: ${path}`);\n\n\t\treturn server;\n\t};\n\n\treturn {\n\t\tclose: () =>\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\tif (!wss) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Close all sessions first\n\t\t\t\tsessions.forEach((session) => closeSession(session));\n\t\t\t\tsessions.clear();\n\t\t\t\twss.close((err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t})\n\t};\n}\n\nfunction handleClientCommand(session: Session, text: string, logger: RelayProxyServerLogger) {\n\tlet command: unknown;\n\ttry {\n\t\tcommand = JSON.parse(text);\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (isAuthResponseCommand(command)) {\n\t\tconst frame = JSON.stringify(['AUTH', command.event]);\n\t\tsendFrameToRelay(session, command.relay, frame, logger);\n\t\treturn;\n\t}\n\n\tif (isCloseSubCommand(command)) {\n\t\tsession.dedupBySubId.delete(command.subscription_id);\n\t\tfor (const [relayUrl, relaySocket] of session.relaySockets.entries()) {\n\t\t\tif (relaySocket.readyState === WebSocket.OPEN) {\n\t\t\t\trelaySocket.send(JSON.stringify(['CLOSE', command.subscription_id]));\n\t\t\t}\n\t\t\tsession.lastSubIdByRelay.set(relayUrl, command.subscription_id);\n\t\t}\n\t}\n}\n\nfunction handleEnvelope(\n\tsession: Session,\n\tclientSocket: WebSocket,\n\tenvelope: Envelope,\n\tlogger: RelayProxyServerLogger\n) {\n\tfor (const relay of envelope.relays) {\n\t\tensureRelaySocket(session, clientSocket, relay, logger);\n\t\tfor (const frame of envelope.frames) {\n\t\t\ttrackSubscriptionState(session, relay, frame);\n\t\t\tsendFrameToRelay(session, relay, frame, logger);\n\t\t}\n\t}\n}\n\nfunction ensureRelaySocket(\n\tsession: Session,\n\tclientSocket: WebSocket,\n\trelayUrl: string,\n\tlogger: RelayProxyServerLogger\n) {\n\tconst existing = session.relaySockets.get(relayUrl);\n\tif (existing && existing.readyState !== WebSocket.CLOSED) return;\n\n\tconst upstream = new WebSocket(relayUrl);\n\tsession.relaySockets.set(relayUrl, upstream);\n\tsession.pendingFrames.set(relayUrl, []);\n\n\tupstream.on('open', () => {\n\t\tconst pending = session.pendingFrames.get(relayUrl);\n\t\tif (!pending) return;\n\t\tfor (const frame of pending) {\n\t\t\tupstream.send(frame);\n\t\t}\n\t\tsession.pendingFrames.set(relayUrl, []);\n\t});\n\n\tupstream.on('message', (data) => {\n\t\tconst raw = toUtf8(data);\n\t\tif (!raw || clientSocket.readyState !== WebSocket.OPEN) return;\n\t\tconst subIdHint = session.lastSubIdByRelay.get(relayUrl);\n\t\tconst workerMessage = relayFrameToWorkerMessage(session, relayUrl, raw, subIdHint);\n\t\tif (!workerMessage) return;\n\t\tclientSocket.send(workerMessage, { binary: true });\n\t});\n\n\tupstream.on('close', () => {\n\t\t// Mark as closed but don't delete - ensureRelaySocket will reconnect on next use\n\t\t// This keeps subscriptions alive across reconnects\n\t\tsession.pendingFrames.delete(relayUrl);\n\t});\n\n\tupstream.on('error', (err) => {\n\t\t// Only log the first error per relay to reduce spam\n\t\tif (!session.pendingFrames.has(relayUrl)) return;\n\t\tconst errorMsg = String(err);\n\t\t// Skip common repetitive errors\n\t\tif (!errorMsg.includes('ECONNREFUSED') && !errorMsg.includes('ENOTFOUND')) {\n\t\t\tlogger.warn(`[relay-proxy] relay socket error for ${relayUrl}: ${errorMsg.slice(0, 100)}`);\n\t\t}\n\t\tsession.pendingFrames.delete(relayUrl);\n\t});\n}\n\nfunction trackSubscriptionState(session: Session, relayUrl: string, frame: string) {\n\tconst parsed = parseRelayFrame(frame);\n\tif (!parsed) return;\n\tconst type = parsed[0];\n\tif (type !== 'REQ' && type !== 'CLOSE') return;\n\n\tconst subId = typeof parsed[1] === 'string' ? parsed[1] : null;\n\tif (!subId) return;\n\tsession.lastSubIdByRelay.set(relayUrl, subId);\n\n\tif (type === 'REQ') {\n\t\tif (!session.dedupBySubId.has(subId)) {\n\t\t\tsession.dedupBySubId.set(subId, new Set());\n\t\t}\n\t\treturn;\n\t}\n\n\tsession.dedupBySubId.delete(subId);\n}\n\nfunction sendFrameToRelay(\n\tsession: Session,\n\trelayUrl: string,\n\tframe: string,\n\tlogger: RelayProxyServerLogger\n) {\n\tconst relaySocket = session.relaySockets.get(relayUrl);\n\tif (!relaySocket) return;\n\n\tif (relaySocket.readyState === WebSocket.OPEN) {\n\t\trelaySocket.send(frame);\n\t\treturn;\n\t}\n\n\tif (relaySocket.readyState === WebSocket.CONNECTING) {\n\t\tconst pending = session.pendingFrames.get(relayUrl) ?? [];\n\t\tpending.push(frame);\n\t\tsession.pendingFrames.set(relayUrl, pending);\n\t\treturn;\n\t}\n\n\tlogger.warn(`[relay-proxy] dropping frame for closed relay socket ${relayUrl}`);\n}\n\nfunction relayFrameToWorkerMessage(\n\tsession: Session,\n\trelayUrl: string,\n\trawFrame: string,\n\tsubIdHint?: string\n): Uint8Array | null {\n\tconst frame = parseRelayFrame(rawFrame);\n\tif (!frame || frame.length < 1 || typeof frame[0] !== 'string') {\n\t\treturn buildRawWorkerMessage(subIdHint ?? '', relayUrl, rawFrame);\n\t}\n\n\tconst kind = frame[0];\n\tif (kind === 'EVENT') {\n\t\tconst subId = typeof frame[1] === 'string' ? frame[1] : '';\n\t\tconst event = asNostrEvent(frame[2]);\n\t\tif (!subId || !event) {\n\t\t\treturn buildRawWorkerMessage(subId, relayUrl, rawFrame);\n\t\t}\n\n\t\tconst dedupSet = session.dedupBySubId.get(subId);\n\t\tif (dedupSet && dedupSet.has(event.id)) {\n\t\t\treturn null;\n\t\t}\n\t\tif (dedupSet) dedupSet.add(event.id);\n\n\t\treturn buildNostrEventWorkerMessage(subId, relayUrl, event);\n\t}\n\n\tif (kind === 'NOTICE') {\n\t\tconst message = frame[1] === undefined ? null : String(frame[1]);\n\t\treturn buildConnectionStatusWorkerMessage('', relayUrl, 'NOTICE', message);\n\t}\n\n\tif (kind === 'AUTH') {\n\t\tconst challenge = frame[1] === undefined ? null : String(frame[1]);\n\t\treturn buildConnectionStatusWorkerMessage(subIdHint ?? '', relayUrl, 'AUTH', challenge);\n\t}\n\n\tif (kind === 'CLOSED') {\n\t\tconst subId = typeof frame[1] === 'string' ? frame[1] : '';\n\t\tconst message = frame[2] === undefined ? null : String(frame[2]);\n\t\treturn buildConnectionStatusWorkerMessage(subId, relayUrl, 'CLOSED', message);\n\t}\n\n\tif (kind === 'OK') {\n\t\tconst eventId = typeof frame[1] === 'string' ? frame[1] : '';\n\t\tconst accepted = frame[2] === undefined ? 'false' : String(frame[2]);\n\t\tconst reason = frame[3] === undefined ? null : String(frame[3]);\n\t\treturn buildConnectionStatusWorkerMessage(eventId, relayUrl, accepted, reason);\n\t}\n\n\tif (kind === 'EOSE') {\n\t\tconst subId = typeof frame[1] === 'string' ? frame[1] : '';\n\t\treturn buildConnectionStatusWorkerMessage(subId, relayUrl, 'EOSE', null);\n\t}\n\n\treturn buildRawWorkerMessage(subIdHint ?? '', relayUrl, rawFrame);\n}\n\nfunction buildNostrEventWorkerMessage(subId: string, relayUrl: string, event: NostrEventJson): Uint8Array {\n\tconst builder = new flatbuffers.Builder(1024);\n\tconst workerMessage = new WorkerMessageT(\n\t\tsubId || null,\n\t\trelayUrl,\n\t\tMessageType.NostrEvent,\n\t\tMessage.NostrEvent,\n\t\tnew NostrEventT(\n\t\t\tevent.id,\n\t\t\tevent.pubkey,\n\t\t\tevent.kind,\n\t\t\tevent.content,\n\t\t\tevent.tags.map((tag) => new StringVecT(tag)),\n\t\t\tevent.created_at,\n\t\t\tevent.sig\n\t\t)\n\t);\n\tbuilder.finish(workerMessage.pack(builder));\n\treturn builder.asUint8Array();\n}\n\nfunction buildConnectionStatusWorkerMessage(\n\tsubId: string,\n\trelayUrl: string,\n\tstatus: string,\n\tmessage: string | null\n): Uint8Array {\n\tconst builder = new flatbuffers.Builder(256);\n\tconst workerMessage = new WorkerMessageT(\n\t\tsubId || null,\n\t\trelayUrl,\n\t\tMessageType.ConnectionStatus,\n\t\tMessage.ConnectionStatus,\n\t\tnew ConnectionStatusT(relayUrl, status, message)\n\t);\n\tbuilder.finish(workerMessage.pack(builder));\n\treturn builder.asUint8Array();\n}\n\nfunction buildRawWorkerMessage(subId: string, relayUrl: string, rawFrame: string): Uint8Array {\n\tconst builder = new flatbuffers.Builder(256);\n\tconst workerMessage = new WorkerMessageT(\n\t\tsubId || null,\n\t\trelayUrl,\n\t\tMessageType.Raw,\n\t\tMessage.Raw,\n\t\tnew RawT(rawFrame)\n\t);\n\tbuilder.finish(workerMessage.pack(builder));\n\treturn builder.asUint8Array();\n}\n\nfunction asNostrEvent(value: unknown): NostrEventJson | null {\n\tif (!value || typeof value !== 'object') return null;\n\tconst candidate = value as Partial<NostrEventJson>;\n\tif (\n\t\ttypeof candidate.id !== 'string' ||\n\t\ttypeof candidate.pubkey !== 'string' ||\n\t\ttypeof candidate.kind !== 'number' ||\n\t\ttypeof candidate.content !== 'string' ||\n\t\ttypeof candidate.created_at !== 'number' ||\n\t\ttypeof candidate.sig !== 'string' ||\n\t\t!Array.isArray(candidate.tags)\n\t) {\n\t\treturn null;\n\t}\n\n\tconst tags = candidate.tags\n\t\t.map((tag) => (Array.isArray(tag) ? tag.filter((item) => typeof item === 'string') : []))\n\t\t.filter((tag) => tag.length > 0);\n\n\treturn {\n\t\tid: candidate.id,\n\t\tpubkey: candidate.pubkey,\n\t\tkind: candidate.kind,\n\t\tcontent: candidate.content,\n\t\tcreated_at: candidate.created_at,\n\t\tsig: candidate.sig,\n\t\ttags\n\t};\n}\n\nfunction parseRelayFrame(rawFrame: string): unknown[] | null {\n\ttry {\n\t\tconst parsed = JSON.parse(rawFrame);\n\t\tif (!Array.isArray(parsed)) return null;\n\t\treturn parsed;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction parseEnvelope(data: unknown): Envelope | null {\n\tconst text = toUtf8(data);\n\tif (!text) return null;\n\n\ttry {\n\t\tconst parsed = JSON.parse(text) as Partial<Envelope>;\n\t\tif (!Array.isArray(parsed.relays) || !Array.isArray(parsed.frames)) return null;\n\t\tconst relays = parsed.relays.filter((relay): relay is string => typeof relay === 'string');\n\t\tconst frames = parsed.frames.filter((frame): frame is string => typeof frame === 'string');\n\t\tif (relays.length === 0 || frames.length === 0) return null;\n\t\treturn { relays, frames };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction toUtf8(data: unknown): string {\n\tif (typeof data === 'string') return data;\n\tif (data instanceof ArrayBuffer) return Buffer.from(data).toString('utf8');\n\tif (Buffer.isBuffer(data)) return data.toString('utf8');\n\tif (data instanceof Uint8Array) return Buffer.from(data).toString('utf8');\n\tif (Array.isArray(data)) {\n\t\treturn Buffer.concat(data.filter((item): item is Buffer => Buffer.isBuffer(item))).toString('utf8');\n\t}\n\treturn '';\n}\n\nfunction isAuthResponseCommand(value: unknown): value is AuthResponseCommand {\n\tif (!value || typeof value !== 'object') return false;\n\tconst candidate = value as Partial<AuthResponseCommand>;\n\treturn (\n\t\tcandidate.type === 'auth_response' &&\n\t\ttypeof candidate.relay === 'string' &&\n\t\tcandidate.event !== undefined\n\t);\n}\n\nfunction isCloseSubCommand(value: unknown): value is CloseSubCommand {\n\tif (!value || typeof value !== 'object') return false;\n\tconst candidate = value as Partial<CloseSubCommand>;\n\treturn candidate.type === 'close_sub' && typeof candidate.subscription_id === 'string';\n}\n\nfunction closeSession(session: Session) {\n\tsession.dedupBySubId.clear();\n\tsession.lastSubIdByRelay.clear();\n\tfor (const socket of session.relaySockets.values()) {\n\t\ttry {\n\t\t\tsocket.close();\n\t\t} catch {\n\t\t\t// Best effort.\n\t\t}\n\t}\n\tsession.relaySockets.clear();\n\tsession.pendingFrames.clear();\n}\n"],"names":["createRelayProxyServer","options","host","port","path","logger","wss","WebSocketServer","err","actualPort","address","clientSocket","session","data","isBinary","envelope","parseEnvelope","handleEnvelope","text","toUtf8","handleClientCommand","closeSession","resolve","reject","attachRelayProxyToServer","server","createExpressRelayProxyMiddleware","app","sessions","originalListen","args","command","isAuthResponseCommand","frame","sendFrameToRelay","isCloseSubCommand","relayUrl","relaySocket","WebSocket","relay","ensureRelaySocket","trackSubscriptionState","existing","upstream","pending","raw","subIdHint","workerMessage","relayFrameToWorkerMessage","errorMsg","parsed","parseRelayFrame","type","subId","rawFrame","buildRawWorkerMessage","kind","event","asNostrEvent","dedupSet","buildNostrEventWorkerMessage","message","buildConnectionStatusWorkerMessage","challenge","eventId","accepted","reason","builder","flatbuffers","WorkerMessageT","MessageType","Message","NostrEventT","tag","StringVecT","status","ConnectionStatusT","RawT","value","candidate","tags","item","relays","frames","socket"],"mappings":";;;AAyFO,SAASA,EAAuBC,IAAmC,IAAsB;AAC/F,QAAMC,IAAOD,EAAQ,QAAQ,aACvBE,IAAOF,EAAQ,QAAQ,MACvBG,IAAOH,EAAQ,QAAQ,KACvBI,IAASJ,EAAQ,UAAU;AAEjC,MAAIK;AACJ,MAAI;AACH,IAAAA,IAAM,IAAIC,EAAgB;AAAA,MACzB,MAAAL;AAAA,MACA,MAAAC;AAAA,MACA,MAAAC;AAAA,IAAA,CACA;AAAA,EACF,SAASI,GAAK;AACb,UAAAH,EAAO,MAAM,mDAAmD,OAAOG,CAAG,CAAC,EAAE,GACvEA;AAAA,EACP;AAKA,MAAIC,IAAaN;AACjB,QAAMO,IAAUJ,EAAI,QAAA;AACpB,SAAII,KAAW,OAAOA,KAAY,aACjCD,IAAaC,EAAQ,OAGtBJ,EAAI,GAAG,SAAS,CAACE,MAAQ;AACxB,IAAAH,EAAO,MAAM,wCAAwC,OAAOG,CAAG,CAAC,EAAE;AAAA,EACnE,CAAC,GAEDF,EAAI,GAAG,cAAc,CAACK,MAAiB;AACtC,UAAMC,IAAmB;AAAA,MACxB,kCAAkB,IAAA;AAAA,MAClB,mCAAmB,IAAA;AAAA,MACnB,kCAAkB,IAAA;AAAA,MAClB,sCAAsB,IAAA;AAAA,IAAI;AAG3B,IAAAD,EAAa,GAAG,WAAW,CAACE,GAAMC,MAAa;AAC9C,UAAIA,GAAU;AACb,cAAMC,IAAWC,EAAcH,CAAI;AACnC,YAAI,CAACE,EAAU;AACf,QAAAE,EAAeL,GAASD,GAAcI,GAAUV,CAAM;AACtD;AAAA,MACD;AAEA,YAAMa,IAAOC,EAAON,CAAI;AACxB,MAAKK,KACLE,EAAoBR,GAASM,GAAMb,CAAM;AAAA,IAC1C,CAAC,GAEDM,EAAa,GAAG,SAAS,MAAM;AAC9B,MAAAU,EAAaT,CAAO;AAAA,IACrB,CAAC,GAEDD,EAAa,GAAG,SAAS,MAAM;AAC9B,MAAAU,EAAaT,CAAO;AAAA,IACrB,CAAC;AAAA,EACF,CAAC,GAEDP,EAAO,KAAK,mCAAmCH,CAAI,IAAIO,CAAU,GAAGL,CAAI,EAAE,GAEnE;AAAA,IACN,MAAMK;AAAA,IACN,OAAO,MACN,IAAI,QAAc,CAACa,GAASC,MAAW;AACtC,MAAAjB,EAAI,MAAM,CAACE,MAAQ;AAClB,YAAIA,GAAK;AACR,UAAAe,EAAOf,CAAG;AACV;AAAA,QACD;AACA,QAAAc,EAAA;AAAA,MACD,CAAC;AAAA,IACF,CAAC;AAAA,EAAA;AAEJ;AA2HO,SAASE,EAAyBvB,GAAsD;AAC9F,QAAM,EAAE,QAAAwB,GAAQ,MAAArB,IAAO,KAAK,QAAAC,IAAS,YAAYJ,GAE3CK,IAAM,IAAIC,EAAgB;AAAA,IAC/B,QAAAkB;AAAA,IACA,MAAArB;AAAA,EAAA,CACA;AAED,SAAAE,EAAI,GAAG,cAAc,CAACK,MAAiB;AACtC,UAAMC,IAAmB;AAAA,MACxB,kCAAkB,IAAA;AAAA,MAClB,mCAAmB,IAAA;AAAA,MACnB,kCAAkB,IAAA;AAAA,MAClB,sCAAsB,IAAA;AAAA,IAAI;AAG3B,IAAAD,EAAa,GAAG,WAAW,CAACE,GAAMC,MAAa;AAC9C,UAAIA,GAAU;AACb,cAAMC,IAAWC,EAAcH,CAAI;AACnC,YAAI,CAACE,EAAU;AACf,QAAAE,EAAeL,GAASD,GAAcI,GAAUV,CAAM;AACtD;AAAA,MACD;AAEA,YAAMa,IAAOC,EAAON,CAAI;AACxB,MAAKK,KACLE,EAAoBR,GAASM,GAAMb,CAAM;AAAA,IAC1C,CAAC,GAEDM,EAAa,GAAG,SAAS,MAAM;AAC9B,MAAAU,EAAaT,CAAO;AAAA,IACrB,CAAC,GAEDD,EAAa,GAAG,SAAS,MAAM;AAC9B,MAAAU,EAAaT,CAAO;AAAA,IACrB,CAAC;AAAA,EACF,CAAC,GAEDP,EAAO,KAAK,6CAA6CD,CAAI,EAAE,GAExD;AAAA,IACN,OAAO,MACN,IAAI,QAAc,CAACkB,GAASC,MAAW;AACtC,MAAAjB,EAAI,MAAM,CAACE,MAAQ;AAClB,YAAIA,GAAK;AACR,UAAAe,EAAOf,CAAG;AACV;AAAA,QACD;AACA,QAAAc,EAAA;AAAA,MACD,CAAC;AAAA,IACF,CAAC;AAAA,EAAA;AAEJ;AA4BO,SAASI,EAEdC,GAAQ1B,GAAsE;AAC/E,QAAMG,IAAOH,EAAQ,QAAQ,aACvBI,IAASJ,EAAQ,UAAU;AAGjC,MAAIK,IAA8B;AAClC,QAAMsB,wBAAe,IAAA,GAGfC,IAAiBF,EAAI,OAAO,KAAKA,CAAG;AACzC,SAAAA,EAAY,SAAS,IAAIG,MAAgB;AACzC,UAAML,IAASI,EAAe,GAAGC,CAAI;AAErC,WAAAxB,IAAM,IAAIC,EAAgB;AAAA,MACzB,QAAAkB;AAAA,MACA,MAAArB;AAAA,IAAA,CACA,GAEDE,EAAI,GAAG,cAAc,CAACK,MAAiB;AACtC,YAAMC,IAAmB;AAAA,QACxB,kCAAkB,IAAA;AAAA,QAClB,mCAAmB,IAAA;AAAA,QACnB,kCAAkB,IAAA;AAAA,QAClB,sCAAsB,IAAA;AAAA,MAAI;AAE3B,MAAAgB,EAAS,IAAIjB,GAAcC,CAAO,GAElCD,EAAa,GAAG,WAAW,CAACE,GAAMC,MAAa;AAC9C,cAAMF,IAAUgB,EAAS,IAAIjB,CAAY;AACzC,YAAI,CAACC,EAAS;AAEd,YAAIE,GAAU;AACb,gBAAMC,IAAWC,EAAcH,CAAI;AACnC,cAAI,CAACE,EAAU;AACf,UAAAE,EAAeL,GAASD,GAAcI,GAAUV,CAAM;AACtD;AAAA,QACD;AAEA,cAAMa,IAAOC,EAAON,CAAI;AACxB,QAAKK,KACLE,EAAoBR,GAASM,GAAMb,CAAM;AAAA,MAC1C,CAAC,GAEDM,EAAa,GAAG,SAAS,MAAM;AAC9B,cAAMC,IAAUgB,EAAS,IAAIjB,CAAY;AACzC,QAAIC,MACHS,EAAaT,CAAO,GACpBgB,EAAS,OAAOjB,CAAY;AAAA,MAE9B,CAAC,GAEDA,EAAa,GAAG,SAAS,MAAM;AAC9B,cAAMC,IAAUgB,EAAS,IAAIjB,CAAY;AACzC,QAAIC,MACHS,EAAaT,CAAO,GACpBgB,EAAS,OAAOjB,CAAY;AAAA,MAE9B,CAAC;AAAA,IACF,CAAC,GAEDN,EAAO,KAAK,qDAAqDD,CAAI,EAAE,GAEhEqB;AAAA,EACR,GAEO;AAAA,IACN,OAAO,MACN,IAAI,QAAc,CAACH,GAASC,MAAW;AACtC,UAAI,CAACjB,GAAK;AACT,QAAAgB,EAAA;AACA;AAAA,MACD;AAEA,MAAAM,EAAS,QAAQ,CAAChB,MAAYS,EAAaT,CAAO,CAAC,GACnDgB,EAAS,MAAA,GACTtB,EAAI,MAAM,CAACE,MAAQ;AAClB,YAAIA,GAAK;AACR,UAAAe,EAAOf,CAAG;AACV;AAAA,QACD;AACA,QAAAc,EAAA;AAAA,MACD,CAAC;AAAA,IACF,CAAC;AAAA,EAAA;AAEJ;AAEA,SAASF,EAAoBR,GAAkBM,GAAcb,GAAgC;AAC5F,MAAI0B;AACJ,MAAI;AACH,IAAAA,IAAU,KAAK,MAAMb,CAAI;AAAA,EAC1B,QAAQ;AACP;AAAA,EACD;AAEA,MAAIc,EAAsBD,CAAO,GAAG;AACnC,UAAME,IAAQ,KAAK,UAAU,CAAC,QAAQF,EAAQ,KAAK,CAAC;AACpD,IAAAG,EAAiBtB,GAASmB,EAAQ,OAAOE,GAAO5B,CAAM;AACtD;AAAA,EACD;AAEA,MAAI8B,EAAkBJ,CAAO,GAAG;AAC/B,IAAAnB,EAAQ,aAAa,OAAOmB,EAAQ,eAAe;AACnD,eAAW,CAACK,GAAUC,CAAW,KAAKzB,EAAQ,aAAa;AAC1D,MAAIyB,EAAY,eAAeC,EAAU,QACxCD,EAAY,KAAK,KAAK,UAAU,CAAC,SAASN,EAAQ,eAAe,CAAC,CAAC,GAEpEnB,EAAQ,iBAAiB,IAAIwB,GAAUL,EAAQ,eAAe;AAAA,EAEhE;AACD;AAEA,SAASd,EACRL,GACAD,GACAI,GACAV,GACC;AACD,aAAWkC,KAASxB,EAAS,QAAQ;AACpC,IAAAyB,EAAkB5B,GAASD,GAAc4B,GAAOlC,CAAM;AACtD,eAAW4B,KAASlB,EAAS;AAC5B,MAAA0B,EAAuB7B,GAAS2B,GAAON,CAAK,GAC5CC,EAAiBtB,GAAS2B,GAAON,GAAO5B,CAAM;AAAA,EAEhD;AACD;AAEA,SAASmC,EACR5B,GACAD,GACAyB,GACA/B,GACC;AACD,QAAMqC,IAAW9B,EAAQ,aAAa,IAAIwB,CAAQ;AAClD,MAAIM,KAAYA,EAAS,eAAeJ,EAAU,OAAQ;AAE1D,QAAMK,IAAW,IAAIL,EAAUF,CAAQ;AACvC,EAAAxB,EAAQ,aAAa,IAAIwB,GAAUO,CAAQ,GAC3C/B,EAAQ,cAAc,IAAIwB,GAAU,CAAA,CAAE,GAEtCO,EAAS,GAAG,QAAQ,MAAM;AACzB,UAAMC,IAAUhC,EAAQ,cAAc,IAAIwB,CAAQ;AAClD,QAAKQ,GACL;AAAA,iBAAWX,KAASW;AACnB,QAAAD,EAAS,KAAKV,CAAK;AAEpB,MAAArB,EAAQ,cAAc,IAAIwB,GAAU,CAAA,CAAE;AAAA;AAAA,EACvC,CAAC,GAEDO,EAAS,GAAG,WAAW,CAAC9B,MAAS;AAChC,UAAMgC,IAAM1B,EAAON,CAAI;AACvB,QAAI,CAACgC,KAAOlC,EAAa,eAAe2B,EAAU,KAAM;AACxD,UAAMQ,IAAYlC,EAAQ,iBAAiB,IAAIwB,CAAQ,GACjDW,IAAgBC,EAA0BpC,GAASwB,GAAUS,GAAKC,CAAS;AACjF,IAAKC,KACLpC,EAAa,KAAKoC,GAAe,EAAE,QAAQ,IAAM;AAAA,EAClD,CAAC,GAEDJ,EAAS,GAAG,SAAS,MAAM;AAG1B,IAAA/B,EAAQ,cAAc,OAAOwB,CAAQ;AAAA,EACtC,CAAC,GAEDO,EAAS,GAAG,SAAS,CAACnC,MAAQ;AAE7B,QAAI,CAACI,EAAQ,cAAc,IAAIwB,CAAQ,EAAG;AAC1C,UAAMa,IAAW,OAAOzC,CAAG;AAE3B,IAAI,CAACyC,EAAS,SAAS,cAAc,KAAK,CAACA,EAAS,SAAS,WAAW,KACvE5C,EAAO,KAAK,wCAAwC+B,CAAQ,KAAKa,EAAS,MAAM,GAAG,GAAG,CAAC,EAAE,GAE1FrC,EAAQ,cAAc,OAAOwB,CAAQ;AAAA,EACtC,CAAC;AACF;AAEA,SAASK,EAAuB7B,GAAkBwB,GAAkBH,GAAe;AAClF,QAAMiB,IAASC,EAAgBlB,CAAK;AACpC,MAAI,CAACiB,EAAQ;AACb,QAAME,IAAOF,EAAO,CAAC;AACrB,MAAIE,MAAS,SAASA,MAAS,QAAS;AAExC,QAAMC,IAAQ,OAAOH,EAAO,CAAC,KAAM,WAAWA,EAAO,CAAC,IAAI;AAC1D,MAAKG,GAGL;AAAA,QAFAzC,EAAQ,iBAAiB,IAAIwB,GAAUiB,CAAK,GAExCD,MAAS,OAAO;AACnB,MAAKxC,EAAQ,aAAa,IAAIyC,CAAK,KAClCzC,EAAQ,aAAa,IAAIyC,GAAO,oBAAI,KAAK;AAE1C;AAAA,IACD;AAEA,IAAAzC,EAAQ,aAAa,OAAOyC,CAAK;AAAA;AAClC;AAEA,SAASnB,EACRtB,GACAwB,GACAH,GACA5B,GACC;AACD,QAAMgC,IAAczB,EAAQ,aAAa,IAAIwB,CAAQ;AACrD,MAAKC,GAEL;AAAA,QAAIA,EAAY,eAAeC,EAAU,MAAM;AAC9C,MAAAD,EAAY,KAAKJ,CAAK;AACtB;AAAA,IACD;AAEA,QAAII,EAAY,eAAeC,EAAU,YAAY;AACpD,YAAMM,IAAUhC,EAAQ,cAAc,IAAIwB,CAAQ,KAAK,CAAA;AACvD,MAAAQ,EAAQ,KAAKX,CAAK,GAClBrB,EAAQ,cAAc,IAAIwB,GAAUQ,CAAO;AAC3C;AAAA,IACD;AAEA,IAAAvC,EAAO,KAAK,wDAAwD+B,CAAQ,EAAE;AAAA;AAC/E;AAEA,SAASY,EACRpC,GACAwB,GACAkB,GACAR,GACoB;AACpB,QAAMb,IAAQkB,EAAgBG,CAAQ;AACtC,MAAI,CAACrB,KAASA,EAAM,SAAS,KAAK,OAAOA,EAAM,CAAC,KAAM;AACrD,WAAOsB,EAAsBT,KAAa,IAAIV,GAAUkB,CAAQ;AAGjE,QAAME,IAAOvB,EAAM,CAAC;AACpB,MAAIuB,MAAS,SAAS;AACrB,UAAMH,IAAQ,OAAOpB,EAAM,CAAC,KAAM,WAAWA,EAAM,CAAC,IAAI,IAClDwB,IAAQC,EAAazB,EAAM,CAAC,CAAC;AACnC,QAAI,CAACoB,KAAS,CAACI;AACd,aAAOF,EAAsBF,GAAOjB,GAAUkB,CAAQ;AAGvD,UAAMK,IAAW/C,EAAQ,aAAa,IAAIyC,CAAK;AAC/C,WAAIM,KAAYA,EAAS,IAAIF,EAAM,EAAE,IAC7B,QAEJE,KAAUA,EAAS,IAAIF,EAAM,EAAE,GAE5BG,EAA6BP,GAAOjB,GAAUqB,CAAK;AAAA,EAC3D;AAEA,MAAID,MAAS,UAAU;AACtB,UAAMK,IAAU5B,EAAM,CAAC,MAAM,SAAY,OAAO,OAAOA,EAAM,CAAC,CAAC;AAC/D,WAAO6B,EAAmC,IAAI1B,GAAU,UAAUyB,CAAO;AAAA,EAC1E;AAEA,MAAIL,MAAS,QAAQ;AACpB,UAAMO,IAAY9B,EAAM,CAAC,MAAM,SAAY,OAAO,OAAOA,EAAM,CAAC,CAAC;AACjE,WAAO6B,EAAmChB,KAAa,IAAIV,GAAU,QAAQ2B,CAAS;AAAA,EACvF;AAEA,MAAIP,MAAS,UAAU;AACtB,UAAMH,IAAQ,OAAOpB,EAAM,CAAC,KAAM,WAAWA,EAAM,CAAC,IAAI,IAClD4B,IAAU5B,EAAM,CAAC,MAAM,SAAY,OAAO,OAAOA,EAAM,CAAC,CAAC;AAC/D,WAAO6B,EAAmCT,GAAOjB,GAAU,UAAUyB,CAAO;AAAA,EAC7E;AAEA,MAAIL,MAAS,MAAM;AAClB,UAAMQ,IAAU,OAAO/B,EAAM,CAAC,KAAM,WAAWA,EAAM,CAAC,IAAI,IACpDgC,IAAWhC,EAAM,CAAC,MAAM,SAAY,UAAU,OAAOA,EAAM,CAAC,CAAC,GAC7DiC,IAASjC,EAAM,CAAC,MAAM,SAAY,OAAO,OAAOA,EAAM,CAAC,CAAC;AAC9D,WAAO6B,EAAmCE,GAAS5B,GAAU6B,GAAUC,CAAM;AAAA,EAC9E;AAEA,MAAIV,MAAS,QAAQ;AACpB,UAAMH,IAAQ,OAAOpB,EAAM,CAAC,KAAM,WAAWA,EAAM,CAAC,IAAI;AACxD,WAAO6B,EAAmCT,GAAOjB,GAAU,QAAQ,IAAI;AAAA,EACxE;AAEA,SAAOmB,EAAsBT,KAAa,IAAIV,GAAUkB,CAAQ;AACjE;AAEA,SAASM,EAA6BP,GAAejB,GAAkBqB,GAAmC;AACzG,QAAMU,IAAU,IAAIC,EAAY,QAAQ,IAAI,GACtCrB,IAAgB,IAAIsB;AAAA,IACzBhB,KAAS;AAAA,IACTjB;AAAA,IACAkC,EAAY;AAAA,IACZC,EAAQ;AAAA,IACR,IAAIC;AAAA,MACHf,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM;AAAA,MACNA,EAAM,KAAK,IAAI,CAACgB,MAAQ,IAAIC,EAAWD,CAAG,CAAC;AAAA,MAC3ChB,EAAM;AAAA,MACNA,EAAM;AAAA,IAAA;AAAA,EACP;AAED,SAAAU,EAAQ,OAAOpB,EAAc,KAAKoB,CAAO,CAAC,GACnCA,EAAQ,aAAA;AAChB;AAEA,SAASL,EACRT,GACAjB,GACAuC,GACAd,GACa;AACb,QAAMM,IAAU,IAAIC,EAAY,QAAQ,GAAG,GACrCrB,IAAgB,IAAIsB;AAAA,IACzBhB,KAAS;AAAA,IACTjB;AAAA,IACAkC,EAAY;AAAA,IACZC,EAAQ;AAAA,IACR,IAAIK,EAAkBxC,GAAUuC,GAAQd,CAAO;AAAA,EAAA;AAEhD,SAAAM,EAAQ,OAAOpB,EAAc,KAAKoB,CAAO,CAAC,GACnCA,EAAQ,aAAA;AAChB;AAEA,SAASZ,EAAsBF,GAAejB,GAAkBkB,GAA8B;AAC7F,QAAMa,IAAU,IAAIC,EAAY,QAAQ,GAAG,GACrCrB,IAAgB,IAAIsB;AAAA,IACzBhB,KAAS;AAAA,IACTjB;AAAA,IACAkC,EAAY;AAAA,IACZC,EAAQ;AAAA,IACR,IAAIM,EAAKvB,CAAQ;AAAA,EAAA;AAElB,SAAAa,EAAQ,OAAOpB,EAAc,KAAKoB,CAAO,CAAC,GACnCA,EAAQ,aAAA;AAChB;AAEA,SAAST,EAAaoB,GAAuC;AAC5D,MAAI,CAACA,KAAS,OAAOA,KAAU,SAAU,QAAO;AAChD,QAAMC,IAAYD;AAClB,MACC,OAAOC,EAAU,MAAO,YACxB,OAAOA,EAAU,UAAW,YAC5B,OAAOA,EAAU,QAAS,YAC1B,OAAOA,EAAU,WAAY,YAC7B,OAAOA,EAAU,cAAe,YAChC,OAAOA,EAAU,OAAQ,YACzB,CAAC,MAAM,QAAQA,EAAU,IAAI;AAE7B,WAAO;AAGR,QAAMC,IAAOD,EAAU,KACrB,IAAI,CAACN,MAAS,MAAM,QAAQA,CAAG,IAAIA,EAAI,OAAO,CAACQ,MAAS,OAAOA,KAAS,QAAQ,IAAI,CAAA,CAAG,EACvF,OAAO,CAACR,MAAQA,EAAI,SAAS,CAAC;AAEhC,SAAO;AAAA,IACN,IAAIM,EAAU;AAAA,IACd,QAAQA,EAAU;AAAA,IAClB,MAAMA,EAAU;AAAA,IAChB,SAASA,EAAU;AAAA,IACnB,YAAYA,EAAU;AAAA,IACtB,KAAKA,EAAU;AAAA,IACf,MAAAC;AAAA,EAAA;AAEF;AAEA,SAAS7B,EAAgBG,GAAoC;AAC5D,MAAI;AACH,UAAMJ,IAAS,KAAK,MAAMI,CAAQ;AAClC,WAAK,MAAM,QAAQJ,CAAM,IAClBA,IAD4B;AAAA,EAEpC,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAASlC,EAAcH,GAAgC;AACtD,QAAMK,IAAOC,EAAON,CAAI;AACxB,MAAI,CAACK,EAAM,QAAO;AAElB,MAAI;AACH,UAAMgC,IAAS,KAAK,MAAMhC,CAAI;AAC9B,QAAI,CAAC,MAAM,QAAQgC,EAAO,MAAM,KAAK,CAAC,MAAM,QAAQA,EAAO,MAAM,EAAG,QAAO;AAC3E,UAAMgC,IAAShC,EAAO,OAAO,OAAO,CAACX,MAA2B,OAAOA,KAAU,QAAQ,GACnF4C,IAASjC,EAAO,OAAO,OAAO,CAACjB,MAA2B,OAAOA,KAAU,QAAQ;AACzF,WAAIiD,EAAO,WAAW,KAAKC,EAAO,WAAW,IAAU,OAChD,EAAE,QAAAD,GAAQ,QAAAC,EAAA;AAAA,EAClB,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,SAAShE,EAAON,GAAuB;AACtC,SAAI,OAAOA,KAAS,WAAiBA,IACjCA,aAAgB,cAAoB,OAAO,KAAKA,CAAI,EAAE,SAAS,MAAM,IACrE,OAAO,SAASA,CAAI,IAAUA,EAAK,SAAS,MAAM,IAClDA,aAAgB,aAAmB,OAAO,KAAKA,CAAI,EAAE,SAAS,MAAM,IACpE,MAAM,QAAQA,CAAI,IACd,OAAO,OAAOA,EAAK,OAAO,CAACoE,MAAyB,OAAO,SAASA,CAAI,CAAC,CAAC,EAAE,SAAS,MAAM,IAE5F;AACR;AAEA,SAASjD,EAAsB8C,GAA8C;AAC5E,MAAI,CAACA,KAAS,OAAOA,KAAU,SAAU,QAAO;AAChD,QAAMC,IAAYD;AAClB,SACCC,EAAU,SAAS,mBACnB,OAAOA,EAAU,SAAU,YAC3BA,EAAU,UAAU;AAEtB;AAEA,SAAS5C,EAAkB2C,GAA0C;AACpE,MAAI,CAACA,KAAS,OAAOA,KAAU,SAAU,QAAO;AAChD,QAAMC,IAAYD;AAClB,SAAOC,EAAU,SAAS,eAAe,OAAOA,EAAU,mBAAoB;AAC/E;AAEA,SAAS1D,EAAaT,GAAkB;AACvC,EAAAA,EAAQ,aAAa,MAAA,GACrBA,EAAQ,iBAAiB,MAAA;AACzB,aAAWwE,KAAUxE,EAAQ,aAAa,OAAA;AACzC,QAAI;AACH,MAAAwE,EAAO,MAAA;AAAA,IACR,QAAQ;AAAA,IAER;AAED,EAAAxE,EAAQ,aAAa,MAAA,GACrBA,EAAQ,cAAc,MAAA;AACvB;"}
1
+ {"version":3,"file":"relayProxyServer.js","sources":["../src/proxy/relayProxyServer.ts"],"sourcesContent":["/// <reference types=\"node\" />\n\nimport type { Server as HttpServer } from 'http';\nimport type { Server as HttpsServer } from 'https';\nimport type { IncomingMessage } from 'http';\nimport type { Duplex } from 'stream';\nimport * as flatbuffers from 'flatbuffers';\nimport { WebSocket, WebSocketServer } from 'ws';\nimport {\n\tConnectionStatus,\n\tMessage,\n\tMessageType,\n\tNostrEvent,\n\tRaw,\n\tStringVec,\n\tWorkerMessage\n} from '../generated/nostr/fb';\n\ntype RelayProxyServerLogger = Pick<Console, 'info' | 'warn' | 'error'>;\n\nexport type RelayProxyServerOptions = {\n\thost?: string;\n\tport?: number;\n\tpath?: string;\n\tlogger?: RelayProxyServerLogger;\n};\n\nexport type RelayProxyServer = {\n\tport: number;\n\tclose: () => Promise<void>;\n};\n\nexport type AttachRelayProxyOptions = {\n\t/** The HTTP/HTTPS server to attach to */\n\tserver: HttpServer | HttpsServer;\n\t/** The path to mount the WebSocket endpoint on (e.g., '/ws-proxy') */\n\tpath?: string;\n\tlogger?: RelayProxyServerLogger;\n};\n\nexport type AttachedRelayProxy = {\n\t/** Stop accepting new connections and close all existing sessions */\n\tclose: () => Promise<void>;\n};\n\nexport type WebSocketRelayProxy = {\n\twss: WebSocketServer;\n\tclose: () => Promise<void>;\n\t/** Manually handle a WebSocket upgrade request */\n\thandleUpgrade: (request: IncomingMessage, socket: Duplex, head: Buffer) => void;\n};\n\ntype Envelope = {\n\trelays: string[];\n\tframes: string[];\n};\n\ntype AuthResponseCommand = {\n\ttype: 'auth_response';\n\trelay: string;\n\tevent: unknown;\n};\n\ntype CloseSubCommand = {\n\ttype: 'close_sub';\n\tsubscription_id: string;\n};\n\ntype NostrEventJson = {\n\tid: string;\n\tpubkey: string;\n\tkind: number;\n\tcontent: string;\n\ttags: string[][];\n\tcreated_at: number;\n\tsig: string;\n};\n\ntype Session = {\n\trelaySockets: Map<string, WebSocket>;\n\tpendingFrames: Map<string, string[]>;\n\tdedupBySubId: Map<string, Set<string>>;\n\tlastSubIdByRelay: Map<string, string>;\n};\n\ntype SubscriptionFrameState = {\n\ttype: 'REQ' | 'CLOSE';\n\tsubId: string;\n} | null;\n\n/**\n * Create a standalone relay proxy server on its own port.\n * Use this for simple deployments or when you don't have an existing HTTP server.\n */\nexport function createRelayProxyServer(options: RelayProxyServerOptions = {}): RelayProxyServer {\n\tconst host = options.host ?? '127.0.0.1';\n\tconst port = options.port ?? 7777;\n\tconst path = options.path ?? '/';\n\tconst logger = options.logger ?? console;\n\n\tlet wss: WebSocketServer;\n\ttry {\n\t\twss = new WebSocketServer({\n\t\t\thost,\n\t\t\tport,\n\t\t\tpath\n\t\t});\n\t} catch (err) {\n\t\tlogger.error(`[relay-proxy] failed to create WebSocketServer: ${String(err)}`);\n\t\tthrow err;\n\t}\n\n\t// Get the actual port (in case port 0 was passed)\n\t// If the server is not yet listening, wss.address() returns null\n\t// In that case, wait for the 'listening' event\n\tlet actualPort = port;\n\tconst address = wss.address();\n\tif (address && typeof address === 'object') {\n\t\tactualPort = address.port;\n\t}\n\n\twss.on('error', (err) => {\n\t\tlogger.error(`[relay-proxy] WebSocketServer error: ${String(err)}`);\n\t});\n\n\twss.on('connection', (clientSocket) => {\n\t\tconst session: Session = {\n\t\t\trelaySockets: new Map(),\n\t\t\tpendingFrames: new Map(),\n\t\t\tdedupBySubId: new Map(),\n\t\t\tlastSubIdByRelay: new Map()\n\t\t};\n\n\t\tclientSocket.on('message', (data, isBinary) => {\n\t\t\tif (isBinary) {\n\t\t\t\tconst envelope = parseEnvelope(data);\n\t\t\t\tif (!envelope) return;\n\t\t\t\thandleEnvelope(session, clientSocket, envelope, logger);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst text = toUtf8(data);\n\t\t\tif (!text) return;\n\t\t\thandleClientCommand(session, text, logger);\n\t\t});\n\n\t\tclientSocket.on('close', () => {\n\t\t\tcloseSession(session);\n\t\t});\n\n\t\tclientSocket.on('error', () => {\n\t\t\tcloseSession(session);\n\t\t});\n\t});\n\n\tlogger.info(`[relay-proxy] listening on ws://${host}:${actualPort}${path}`);\n\n\treturn {\n\t\tport: actualPort,\n\t\tclose: () =>\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\twss.close((err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t})\n\t};\n}\n\n/**\n * Create a WebSocket relay proxy that supports manual upgrade handling.\n * Use this for Vite development servers where SvelteKit middleware might interfere\n * with the standard WebSocket upgrade mechanism.\n *\n * @example\n * // In a Vite plugin\n * const relayProxy = createRelayProxyWebSocketServer({ path: '/ws-proxy' });\n *\n * // In configureServer middleware\n * server.middlewares.use((req, res, next) => {\n * if (req.url?.startsWith('/ws-proxy') && req.headers.upgrade === 'websocket') {\n * server.httpServer.once('upgrade', (request, socket, head) => {\n * if (request.url?.startsWith('/ws-proxy')) {\n * relayProxy.handleUpgrade(request, socket, head);\n * }\n * });\n * }\n * next();\n * });\n */\nexport function createRelayProxyWebSocketServer(\n\toptions: Omit<AttachRelayProxyOptions, 'server'> & { server?: HttpServer | HttpsServer }\n): WebSocketRelayProxy {\n\tconst path = options.path ?? '/';\n\tconst logger = options.logger ?? console;\n\n\t// Create WebSocket server with noServer mode to handle upgrades manually\n\tconst wss = new WebSocketServer({\n\t\tnoServer: true\n\t});\n\n\tconst sessions = new Map<WebSocket, Session>();\n\n\twss.on('connection', (clientSocket) => {\n\t\tconst session: Session = {\n\t\t\trelaySockets: new Map(),\n\t\t\tpendingFrames: new Map(),\n\t\t\tdedupBySubId: new Map(),\n\t\t\tlastSubIdByRelay: new Map()\n\t\t};\n\t\tsessions.set(clientSocket, session);\n\n\t\tclientSocket.on('message', (data, isBinary) => {\n\t\t\tconst session = sessions.get(clientSocket);\n\t\t\tif (!session) return;\n\n\t\t\tif (isBinary) {\n\t\t\t\tconst envelope = parseEnvelope(data);\n\t\t\t\tif (!envelope) return;\n\t\t\t\thandleEnvelope(session, clientSocket, envelope, logger);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst text = toUtf8(data);\n\t\t\tif (!text) return;\n\t\t\thandleClientCommand(session, text, logger);\n\t\t});\n\n\t\tclientSocket.on('close', () => {\n\t\t\tconst session = sessions.get(clientSocket);\n\t\t\tif (session) {\n\t\t\t\tcloseSession(session);\n\t\t\t\tsessions.delete(clientSocket);\n\t\t\t}\n\t\t});\n\n\t\tclientSocket.on('error', () => {\n\t\t\tconst session = sessions.get(clientSocket);\n\t\t\tif (session) {\n\t\t\t\tcloseSession(session);\n\t\t\t\tsessions.delete(clientSocket);\n\t\t\t}\n\t\t});\n\t});\n\n\tconst handleUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer) => {\n\t\t// Verify path matches\n\t\tconst url = request.url ?? '';\n\t\tif (!url.startsWith(path)) {\n\t\t\treturn;\n\t\t}\n\n\t\twss.handleUpgrade(request, socket, head, (ws) => {\n\t\t\twss.emit('connection', ws, request);\n\t\t});\n\t};\n\n\treturn {\n\t\twss,\n\t\tclose: () =>\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\t// Close all sessions first\n\t\t\t\tsessions.forEach((session) => closeSession(session));\n\t\t\t\tsessions.clear();\n\t\t\t\twss.close((err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t}),\n\t\thandleUpgrade\n\t};\n}\n\n/**\n * Attach the relay proxy to an existing HTTP/HTTPS server.\n * Use this for embedding in SvelteKit (adapter-node), Express, or any Node.js server.\n *\n * @example\n * // SvelteKit with adapter-node\n * import { createServer } from 'http';\n * import { handler } from './build/handler.js';\n * import { attachRelayProxyToServer } from '@candypoets/nipworker/proxy/server';\n *\n * const server = createServer(handler);\n * attachRelayProxyToServer({ server, path: '/ws-proxy' });\n * server.listen(3000);\n */\nexport function attachRelayProxyToServer(options: AttachRelayProxyOptions): AttachedRelayProxy {\n\tconst { server, path = '/', logger = console } = options;\n\n\tconst wss = new WebSocketServer({\n\t\tserver,\n\t\tpath\n\t});\n\n\twss.on('connection', (clientSocket) => {\n\t\tconst session: Session = {\n\t\t\trelaySockets: new Map(),\n\t\t\tpendingFrames: new Map(),\n\t\t\tdedupBySubId: new Map(),\n\t\t\tlastSubIdByRelay: new Map()\n\t\t};\n\n\t\tclientSocket.on('message', (data, isBinary) => {\n\t\t\tif (isBinary) {\n\t\t\t\tconst envelope = parseEnvelope(data);\n\t\t\t\tif (!envelope) return;\n\t\t\t\thandleEnvelope(session, clientSocket, envelope, logger);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst text = toUtf8(data);\n\t\t\tif (!text) return;\n\t\t\thandleClientCommand(session, text, logger);\n\t\t});\n\n\t\tclientSocket.on('close', () => {\n\t\t\tcloseSession(session);\n\t\t});\n\n\t\tclientSocket.on('error', () => {\n\t\t\tcloseSession(session);\n\t\t});\n\t});\n\n\tlogger.info(`[relay-proxy] attached to server at path: ${path}`);\n\n\treturn {\n\t\tclose: () =>\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\twss.close((err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t})\n\t};\n}\n\n/**\n * Create an Express middleware that attaches the relay proxy to the Express server's underlying HTTP server.\n * Call this after setting up your Express app but before calling app.listen().\n *\n * @example\n * import express from 'express';\n * import { createExpressRelayProxyMiddleware } from '@candypoets/nipworker/proxy/server';\n *\n * const app = express();\n *\n * // Your Express routes...\n * app.get('/api/health', (req, res) => res.json({ ok: true }));\n *\n * // Attach relay proxy\n * const relayProxy = createExpressRelayProxyMiddleware(app, { path: '/ws-proxy' });\n *\n * const server = app.listen(3000, () => {\n * console.log('Server with relay proxy running on port 3000');\n * });\n *\n * // Cleanup on shutdown\n * process.on('SIGTERM', async () => {\n * await relayProxy.close();\n * server.close();\n * });\n */\nexport function createExpressRelayProxyMiddleware<\n\tT extends { listen: (...args: any[]) => HttpServer | HttpsServer }\n>(app: T, options: Omit<AttachRelayProxyOptions, 'server'>): AttachedRelayProxy {\n\tconst path = options.path ?? '/ws-proxy';\n\tconst logger = options.logger ?? console;\n\n\t// Store reference to the server once it's created\n\tlet wss: WebSocketServer | null = null;\n\tconst sessions = new Map<WebSocket, Session>();\n\n\t// Monkey-patch app.listen to capture the server instance\n\tconst originalListen = app.listen.bind(app);\n\t(app as any).listen = (...args: any[]) => {\n\t\tconst server = originalListen(...args);\n\n\t\twss = new WebSocketServer({\n\t\t\tserver,\n\t\t\tpath\n\t\t});\n\n\t\twss.on('connection', (clientSocket) => {\n\t\t\tconst session: Session = {\n\t\t\t\trelaySockets: new Map(),\n\t\t\t\tpendingFrames: new Map(),\n\t\t\t\tdedupBySubId: new Map(),\n\t\t\t\tlastSubIdByRelay: new Map()\n\t\t\t};\n\t\t\tsessions.set(clientSocket, session);\n\n\t\t\tclientSocket.on('message', (data, isBinary) => {\n\t\t\t\tconst session = sessions.get(clientSocket);\n\t\t\t\tif (!session) return;\n\n\t\t\t\tif (isBinary) {\n\t\t\t\t\tconst envelope = parseEnvelope(data);\n\t\t\t\t\tif (!envelope) return;\n\t\t\t\t\thandleEnvelope(session, clientSocket, envelope, logger);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst text = toUtf8(data);\n\t\t\t\tif (!text) return;\n\t\t\t\thandleClientCommand(session, text, logger);\n\t\t\t});\n\n\t\t\tclientSocket.on('close', () => {\n\t\t\t\tconst session = sessions.get(clientSocket);\n\t\t\t\tif (session) {\n\t\t\t\t\tcloseSession(session);\n\t\t\t\t\tsessions.delete(clientSocket);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tclientSocket.on('error', () => {\n\t\t\t\tconst session = sessions.get(clientSocket);\n\t\t\t\tif (session) {\n\t\t\t\t\tcloseSession(session);\n\t\t\t\t\tsessions.delete(clientSocket);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\tlogger.info(`[relay-proxy] attached to Express server at path: ${path}`);\n\n\t\treturn server;\n\t};\n\n\treturn {\n\t\tclose: () =>\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\tif (!wss) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Close all sessions first\n\t\t\t\tsessions.forEach((session) => closeSession(session));\n\t\t\t\tsessions.clear();\n\t\t\t\twss.close((err) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treject(err);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve();\n\t\t\t\t});\n\t\t\t})\n\t};\n}\n\nfunction handleClientCommand(session: Session, text: string, logger: RelayProxyServerLogger) {\n\tlet command: unknown;\n\ttry {\n\t\tcommand = JSON.parse(text);\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (isAuthResponseCommand(command)) {\n\t\tconst frame = JSON.stringify(['AUTH', command.event]);\n\t\tsendFrameToRelay(session, command.relay, frame, logger);\n\t\treturn;\n\t}\n\n\tif (isCloseSubCommand(command)) {\n\t\tsession.dedupBySubId.delete(command.subscription_id);\n\t\tfor (const [relayUrl, relaySocket] of session.relaySockets.entries()) {\n\t\t\tif (relaySocket.readyState === WebSocket.OPEN) {\n\t\t\t\trelaySocket.send(JSON.stringify(['CLOSE', command.subscription_id]));\n\t\t\t}\n\t\t\tsession.lastSubIdByRelay.set(relayUrl, command.subscription_id);\n\t\t}\n\t}\n}\n\nfunction handleEnvelope(\n\tsession: Session,\n\tclientSocket: WebSocket,\n\tenvelope: Envelope,\n\tlogger: RelayProxyServerLogger\n) {\n\tconst trackedFrames = envelope.frames.map((frame) => ({\n\t\tframe,\n\t\tstate: parseSubscriptionFrameState(frame)\n\t}));\n\n\tfor (const relay of envelope.relays) {\n\t\tensureRelaySocket(session, clientSocket, relay, logger);\n\t\tfor (const tracked of trackedFrames) {\n\t\t\ttrackSubscriptionState(session, relay, tracked.state);\n\t\t\tconst frame = tracked.frame;\n\t\t\tsendFrameToRelay(session, relay, frame, logger);\n\t\t}\n\t}\n}\n\nfunction ensureRelaySocket(\n\tsession: Session,\n\tclientSocket: WebSocket,\n\trelayUrl: string,\n\tlogger: RelayProxyServerLogger\n) {\n\tconst existing = session.relaySockets.get(relayUrl);\n\tif (existing && existing.readyState !== WebSocket.CLOSED) return;\n\n\tconst upstream = new WebSocket(relayUrl);\n\tsession.relaySockets.set(relayUrl, upstream);\n\tsession.pendingFrames.set(relayUrl, []);\n\n\tupstream.on('open', () => {\n\t\tconst pending = session.pendingFrames.get(relayUrl);\n\t\tif (!pending) return;\n\t\tfor (const frame of pending) {\n\t\t\tupstream.send(frame);\n\t\t}\n\t\tsession.pendingFrames.set(relayUrl, []);\n\t});\n\n\tupstream.on('message', (data) => {\n\t\tconst raw = toUtf8(data);\n\t\tif (!raw || clientSocket.readyState !== WebSocket.OPEN) return;\n\t\tconst subIdHint = session.lastSubIdByRelay.get(relayUrl);\n\t\tconst workerMessage = relayFrameToWorkerMessage(session, relayUrl, raw, subIdHint);\n\t\tif (!workerMessage) return;\n\t\tclientSocket.send(workerMessage, { binary: true });\n\t});\n\n\tupstream.on('close', () => {\n\t\t// Mark as closed but don't delete - ensureRelaySocket will reconnect on next use\n\t\t// This keeps subscriptions alive across reconnects\n\t\tsession.pendingFrames.delete(relayUrl);\n\t});\n\n\tupstream.on('error', (err) => {\n\t\t// Only log the first error per relay to reduce spam\n\t\tif (!session.pendingFrames.has(relayUrl)) return;\n\t\tconst errorMsg = String(err);\n\t\t// Skip common repetitive errors\n\t\tif (!errorMsg.includes('ECONNREFUSED') && !errorMsg.includes('ENOTFOUND')) {\n\t\t\tlogger.warn(`[relay-proxy] relay socket error for ${relayUrl}: ${errorMsg.slice(0, 100)}`);\n\t\t}\n\t\tsession.pendingFrames.delete(relayUrl);\n\t});\n}\n\nfunction trackSubscriptionState(session: Session, relayUrl: string, state: SubscriptionFrameState) {\n\tif (!state) return;\n\tsession.lastSubIdByRelay.set(relayUrl, state.subId);\n\n\tif (state.type === 'REQ') {\n\t\tconst subId = state.subId;\n\t\tif (!session.dedupBySubId.has(subId)) {\n\t\t\tsession.dedupBySubId.set(subId, new Set());\n\t\t}\n\t\treturn;\n\t}\n\n\tsession.dedupBySubId.delete(state.subId);\n}\n\nfunction parseSubscriptionFrameState(frame: string): SubscriptionFrameState {\n\tconst parsed = parseRelayFrame(frame);\n\tif (!parsed) return null;\n\tconst type = parsed[0];\n\tif (type !== 'REQ' && type !== 'CLOSE') return null;\n\n\tconst subId = typeof parsed[1] === 'string' ? parsed[1] : null;\n\tif (!subId) return null;\n\treturn { type, subId };\n}\n\nfunction sendFrameToRelay(\n\tsession: Session,\n\trelayUrl: string,\n\tframe: string,\n\tlogger: RelayProxyServerLogger\n) {\n\tconst relaySocket = session.relaySockets.get(relayUrl);\n\tif (!relaySocket) return;\n\n\tif (relaySocket.readyState === WebSocket.OPEN) {\n\t\trelaySocket.send(frame);\n\t\treturn;\n\t}\n\n\tif (relaySocket.readyState === WebSocket.CONNECTING) {\n\t\tconst pending = session.pendingFrames.get(relayUrl) ?? [];\n\t\tpending.push(frame);\n\t\tsession.pendingFrames.set(relayUrl, pending);\n\t\treturn;\n\t}\n\n\tlogger.warn(`[relay-proxy] dropping frame for closed relay socket ${relayUrl}`);\n}\n\nfunction relayFrameToWorkerMessage(\n\tsession: Session,\n\trelayUrl: string,\n\trawFrame: string,\n\tsubIdHint?: string\n): Uint8Array | null {\n\tconst frame = parseRelayFrame(rawFrame);\n\tif (!frame || frame.length < 1 || typeof frame[0] !== 'string') {\n\t\treturn buildRawWorkerMessage(subIdHint ?? '', relayUrl, rawFrame);\n\t}\n\n\tconst kind = frame[0];\n\tif (kind === 'EVENT') {\n\t\tconst subId = typeof frame[1] === 'string' ? frame[1] : '';\n\t\tconst event = asNostrEvent(frame[2]);\n\t\tif (!subId || !event) {\n\t\t\treturn buildRawWorkerMessage(subId, relayUrl, rawFrame);\n\t\t}\n\n\t\tconst dedupSet = session.dedupBySubId.get(subId);\n\t\tif (dedupSet && dedupSet.has(event.id)) {\n\t\t\treturn null;\n\t\t}\n\t\tif (dedupSet) dedupSet.add(event.id);\n\n\t\treturn buildNostrEventWorkerMessage(subId, relayUrl, event);\n\t}\n\n\tif (kind === 'NOTICE') {\n\t\tconst message = frame[1] === undefined ? null : String(frame[1]);\n\t\treturn buildConnectionStatusWorkerMessage('', relayUrl, 'NOTICE', message);\n\t}\n\n\tif (kind === 'AUTH') {\n\t\tconst challenge = frame[1] === undefined ? null : String(frame[1]);\n\t\treturn buildConnectionStatusWorkerMessage(subIdHint ?? '', relayUrl, 'AUTH', challenge);\n\t}\n\n\tif (kind === 'CLOSED') {\n\t\tconst subId = typeof frame[1] === 'string' ? frame[1] : '';\n\t\tconst message = frame[2] === undefined ? null : String(frame[2]);\n\t\treturn buildConnectionStatusWorkerMessage(subId, relayUrl, 'CLOSED', message);\n\t}\n\n\tif (kind === 'OK') {\n\t\tconst eventId = typeof frame[1] === 'string' ? frame[1] : '';\n\t\tconst accepted = frame[2] === undefined ? 'false' : String(frame[2]);\n\t\tconst reason = frame[3] === undefined ? null : String(frame[3]);\n\t\treturn buildConnectionStatusWorkerMessage(eventId, relayUrl, accepted, reason);\n\t}\n\n\tif (kind === 'EOSE') {\n\t\tconst subId = typeof frame[1] === 'string' ? frame[1] : '';\n\t\treturn buildConnectionStatusWorkerMessage(subId, relayUrl, 'EOSE', null);\n\t}\n\n\treturn buildRawWorkerMessage(subIdHint ?? '', relayUrl, rawFrame);\n}\n\nfunction buildNostrEventWorkerMessage(subId: string, relayUrl: string, event: NostrEventJson): Uint8Array {\n\tconst builder = new flatbuffers.Builder(1024);\n\n\tconst subIdOffset = subId ? builder.createString(subId) : 0;\n\tconst relayUrlOffset = builder.createString(relayUrl);\n\tconst idOffset = builder.createString(event.id);\n\tconst pubkeyOffset = builder.createString(event.pubkey);\n\tconst contentOffset = builder.createString(event.content);\n\tconst sigOffset = builder.createString(event.sig);\n\n\tconst tagOffsets = new Array<flatbuffers.Offset>(event.tags.length);\n\tfor (let i = 0; i < event.tags.length; i++) {\n\t\ttagOffsets[i] = createStringVecOffset(builder, event.tags[i]!);\n\t}\n\tconst tagsOffset = NostrEvent.createTagsVector(builder, tagOffsets);\n\n\tconst eventOffset = NostrEvent.createNostrEvent(\n\t\tbuilder,\n\t\tidOffset,\n\t\tpubkeyOffset,\n\t\tevent.kind,\n\t\tcontentOffset,\n\t\ttagsOffset,\n\t\tevent.created_at,\n\t\tsigOffset\n\t);\n\n\tconst workerMessageOffset = WorkerMessage.createWorkerMessage(\n\t\tbuilder,\n\t\tsubIdOffset,\n\t\trelayUrlOffset,\n\t\tMessageType.NostrEvent,\n\t\tMessage.NostrEvent,\n\t\teventOffset\n\t);\n\n\tbuilder.finish(workerMessageOffset);\n\treturn builder.asUint8Array();\n}\n\nfunction buildConnectionStatusWorkerMessage(\n\tsubId: string,\n\trelayUrl: string,\n\tstatus: string,\n\tmessage: string | null\n): Uint8Array {\n\tconst builder = new flatbuffers.Builder(256);\n\n\tconst subIdOffset = subId ? builder.createString(subId) : 0;\n\tconst relayUrlOffset = builder.createString(relayUrl);\n\tconst statusOffset = builder.createString(status);\n\tconst messageOffset = message === null ? 0 : builder.createString(message);\n\n\tconst contentOffset = ConnectionStatus.createConnectionStatus(\n\t\tbuilder,\n\t\trelayUrlOffset,\n\t\tstatusOffset,\n\t\tmessageOffset\n\t);\n\n\tconst workerMessageOffset = WorkerMessage.createWorkerMessage(\n\t\tbuilder,\n\t\tsubIdOffset,\n\t\trelayUrlOffset,\n\t\tMessageType.ConnectionStatus,\n\t\tMessage.ConnectionStatus,\n\t\tcontentOffset\n\t);\n\n\tbuilder.finish(workerMessageOffset);\n\treturn builder.asUint8Array();\n}\n\nfunction buildRawWorkerMessage(subId: string, relayUrl: string, rawFrame: string): Uint8Array {\n\tconst builder = new flatbuffers.Builder(256);\n\n\tconst subIdOffset = subId ? builder.createString(subId) : 0;\n\tconst relayUrlOffset = builder.createString(relayUrl);\n\tconst rawOffset = builder.createString(rawFrame);\n\tconst contentOffset = Raw.createRaw(builder, rawOffset);\n\n\tconst workerMessageOffset = WorkerMessage.createWorkerMessage(\n\t\tbuilder,\n\t\tsubIdOffset,\n\t\trelayUrlOffset,\n\t\tMessageType.Raw,\n\t\tMessage.Raw,\n\t\tcontentOffset\n\t);\n\n\tbuilder.finish(workerMessageOffset);\n\treturn builder.asUint8Array();\n}\n\nfunction asNostrEvent(value: unknown): NostrEventJson | null {\n\tif (!value || typeof value !== 'object') return null;\n\tconst candidate = value as Partial<NostrEventJson>;\n\tif (\n\t\ttypeof candidate.id !== 'string' ||\n\t\ttypeof candidate.pubkey !== 'string' ||\n\t\ttypeof candidate.kind !== 'number' ||\n\t\ttypeof candidate.content !== 'string' ||\n\t\ttypeof candidate.created_at !== 'number' ||\n\t\ttypeof candidate.sig !== 'string' ||\n\t\t!Array.isArray(candidate.tags)\n\t) {\n\t\treturn null;\n\t}\n\n\tconst rawTags = candidate.tags;\n\tlet needsSanitization = false;\n\tfor (const tag of rawTags) {\n\t\tif (!Array.isArray(tag) || tag.length === 0) {\n\t\t\tneedsSanitization = true;\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const item of tag) {\n\t\t\tif (typeof item !== 'string') {\n\t\t\t\tneedsSanitization = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tlet tags: string[][];\n\tif (!needsSanitization) {\n\t\ttags = rawTags as string[][];\n\t} else {\n\t\ttags = [];\n\t\tfor (const tag of rawTags) {\n\t\t\tif (!Array.isArray(tag)) continue;\n\t\t\tconst sanitizedTag: string[] = [];\n\t\t\tfor (const item of tag) {\n\t\t\t\tif (typeof item === 'string') {\n\t\t\t\t\tsanitizedTag.push(item);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (sanitizedTag.length > 0) {\n\t\t\t\ttags.push(sanitizedTag);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tid: candidate.id,\n\t\tpubkey: candidate.pubkey,\n\t\tkind: candidate.kind,\n\t\tcontent: candidate.content,\n\t\tcreated_at: candidate.created_at,\n\t\tsig: candidate.sig,\n\t\ttags\n\t};\n}\n\nfunction createStringVecOffset(builder: flatbuffers.Builder, values: string[]): flatbuffers.Offset {\n\tconst itemOffsets = new Array<flatbuffers.Offset>(values.length);\n\tfor (let i = 0; i < values.length; i++) {\n\t\titemOffsets[i] = builder.createString(values[i]!);\n\t}\n\tconst itemsOffset = StringVec.createItemsVector(builder, itemOffsets);\n\treturn StringVec.createStringVec(builder, itemsOffset);\n}\n\nfunction parseRelayFrame(rawFrame: string): unknown[] | null {\n\ttry {\n\t\tconst parsed = JSON.parse(rawFrame);\n\t\tif (!Array.isArray(parsed)) return null;\n\t\treturn parsed;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction parseEnvelope(data: unknown): Envelope | null {\n\tconst text = toUtf8(data);\n\tif (!text) return null;\n\n\ttry {\n\t\tconst parsed = JSON.parse(text) as Partial<Envelope>;\n\t\tif (!Array.isArray(parsed.relays) || !Array.isArray(parsed.frames)) return null;\n\t\tconst relays = parsed.relays.filter((relay): relay is string => typeof relay === 'string');\n\t\tconst frames = parsed.frames.filter((frame): frame is string => typeof frame === 'string');\n\t\tif (relays.length === 0 || frames.length === 0) return null;\n\t\treturn { relays, frames };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction toUtf8(data: unknown): string {\n\tif (typeof data === 'string') return data;\n\tif (data instanceof ArrayBuffer) return Buffer.from(data).toString('utf8');\n\tif (Buffer.isBuffer(data)) return data.toString('utf8');\n\tif (data instanceof Uint8Array) return Buffer.from(data).toString('utf8');\n\tif (Array.isArray(data)) {\n\t\treturn Buffer.concat(data.filter((item): item is Buffer => Buffer.isBuffer(item))).toString('utf8');\n\t}\n\treturn '';\n}\n\nfunction isAuthResponseCommand(value: unknown): value is AuthResponseCommand {\n\tif (!value || typeof value !== 'object') return false;\n\tconst candidate = value as Partial<AuthResponseCommand>;\n\treturn (\n\t\tcandidate.type === 'auth_response' &&\n\t\ttypeof candidate.relay === 'string' &&\n\t\tcandidate.event !== undefined\n\t);\n}\n\nfunction isCloseSubCommand(value: unknown): value is CloseSubCommand {\n\tif (!value || typeof value !== 'object') return false;\n\tconst candidate = value as Partial<CloseSubCommand>;\n\treturn candidate.type === 'close_sub' && typeof candidate.subscription_id === 'string';\n}\n\nfunction closeSession(session: Session) {\n\tsession.dedupBySubId.clear();\n\tsession.lastSubIdByRelay.clear();\n\tfor (const socket of session.relaySockets.values()) {\n\t\ttry {\n\t\t\tsocket.close();\n\t\t} catch {\n\t\t\t// Best effort.\n\t\t}\n\t}\n\tsession.relaySockets.clear();\n\tsession.pendingFrames.clear();\n}\n"],"names":["createRelayProxyServer","options","host","port","path","logger","wss","WebSocketServer","err","actualPort","address","clientSocket","session","data","isBinary","envelope","parseEnvelope","handleEnvelope","text","toUtf8","handleClientCommand","closeSession","resolve","reject","command","isAuthResponseCommand","frame","sendFrameToRelay","isCloseSubCommand","relayUrl","relaySocket","WebSocket","trackedFrames","parseSubscriptionFrameState","relay","ensureRelaySocket","tracked","trackSubscriptionState","existing","upstream","pending","raw","subIdHint","workerMessage","relayFrameToWorkerMessage","errorMsg","state","subId","parsed","parseRelayFrame","type","rawFrame","buildRawWorkerMessage","kind","event","asNostrEvent","dedupSet","buildNostrEventWorkerMessage","message","buildConnectionStatusWorkerMessage","challenge","eventId","accepted","reason","builder","flatbuffers","subIdOffset","relayUrlOffset","idOffset","pubkeyOffset","contentOffset","sigOffset","tagOffsets","i","createStringVecOffset","tagsOffset","NostrEvent","eventOffset","workerMessageOffset","WorkerMessage","MessageType","Message","status","statusOffset","messageOffset","ConnectionStatus","rawOffset","Raw","value","candidate","rawTags","needsSanitization","tag","item","tags","sanitizedTag","values","itemOffsets","itemsOffset","StringVec","relays","frames","socket"],"mappings":"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"}
package/dist/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import{nip19 as T,generateSecretKey as R,getPublicKey as I}from"nostr-tools";import{g as V}from"./index2.js";import{b as E}from"./NarrowTypes.js";import{l as j,Y as z,W as B,f as G,j as Z,g as H,X as _,Z as F,p as O,r as Q,F as U,H as W,D as q,B as J,J as X,t as Y,v as ee,x as se,z as ae,L as ne,N as te,P as oe,R as re,T as ie,a1 as de,$ as ce,V as le,a0 as ue,e as pe,d as ge,n as he,_ as fe,k as ye,h as me,c as we,o as Ke,q as ke,E as Se,G as ve,C as be,A as xe,I as Ce,s as De,u as Ae,w as Me,y as Re,K as Ie,M as Ee,O as $e,Q as Ne,S as Pe,U as Le,a as Te,i as Ve,m as je}from"./NarrowTypes.js";import{h,b as c,i as K,I as A,j as M,k as $,l as ze,m as Be,H as Ge,L as Ze,n as He}from"./worker-message.js";class _e{constructor(){this.knownRelays=new Map,this.incomingCount=0,this.resolvedCount=0}handleMessage(r){const i=E(r);if(!i)return;const l=i.relayUrl();l&&!this.knownRelays.has(l)&&(this.incomingCount++,this.knownRelays.set(l,i)),this.isResolved(i)&&this.resolvedCount++}isResolved(r){return r.status()?.toString()==="EOSE"}get totalIncoming(){return this.incomingCount}get totalResolved(){return this.resolvedCount}get resolutionRate(){return this.incomingCount===0?0:this.resolvedCount/this.incomingCount}}const a=new TextEncoder;async function Fe(d){const r=[],i=e=>new h(a.encode("text"),a.encode(e),c.NONE,null),l=e=>new h(a.encode("image"),a.encode(e),c.ImageData,new A(a.encode(e),null)),f=e=>new h(a.encode("video"),a.encode(e),c.VideoData,new M(a.encode(e),null)),m=(e,s)=>{const n=e.indexOf(`
1
+ import{nip19 as T,generateSecretKey as R,getPublicKey as I}from"nostr-tools";import{g as V}from"./index2.js";import{b as E}from"./NarrowTypes.js";import{l as j,Y as z,W as B,f as G,j as Z,g as H,X as _,Z as F,p as O,r as Q,F as U,H as W,D as q,B as J,J as X,t as Y,v as ee,x as se,z as ae,L as ne,N as te,P as oe,R as re,T as ie,a1 as de,$ as ce,V as le,a0 as ue,e as pe,d as ge,n as he,_ as fe,k as ye,h as me,c as we,o as Ke,q as ke,E as Se,G as ve,C as be,A as xe,I as Ce,s as De,u as Ae,w as Me,y as Re,K as Ie,M as Ee,O as $e,Q as Ne,S as Pe,U as Le,a as Te,i as Ve,m as je}from"./NarrowTypes.js";import{f as h,b as c,g as K,I as A,h as M,i as $,j as ze,k as Be,H as Ge,L as Ze,l as He}from"./worker-message.js";class _e{constructor(){this.knownRelays=new Map,this.incomingCount=0,this.resolvedCount=0}handleMessage(r){const i=E(r);if(!i)return;const l=i.relayUrl();l&&!this.knownRelays.has(l)&&(this.incomingCount++,this.knownRelays.set(l,i)),this.isResolved(i)&&this.resolvedCount++}isResolved(r){return r.status()?.toString()==="EOSE"}get totalIncoming(){return this.incomingCount}get totalResolved(){return this.resolvedCount}get resolutionRate(){return this.incomingCount===0?0:this.resolvedCount/this.incomingCount}}const a=new TextEncoder;async function Fe(d){const r=[],i=e=>new h(a.encode("text"),a.encode(e),c.NONE,null),l=e=>new h(a.encode("image"),a.encode(e),c.ImageData,new A(a.encode(e),null)),f=e=>new h(a.encode("video"),a.encode(e),c.VideoData,new M(a.encode(e),null)),m=(e,s)=>{const n=e.indexOf(`
2
2
  `);let p=null,t=e;if(n!==-1){const g=e.slice(0,n).trim(),b=e.slice(n+1);g&&/^[a-zA-Z0-9+#\.\-_]+$/.test(g)&&(p=g,t=b)}return new h(a.encode("code"),a.encode(s),c.CodeData,new ze(a.encode(p||""),a.encode(t)))},y=e=>new h(a.encode("cashu"),a.encode(e),c.CashuData,new Be(a.encode(e))),k=e=>new h(a.encode("hashtag"),a.encode(`#${e}`),c.HashtagData,new Ge(a.encode(e))),S=e=>new h(a.encode("link"),a.encode(e),c.LinkPreviewData,new Ze(a.encode(e),null,null,null)),N=(e,s)=>{try{const n=T.decode(e),p=n.type;let t=null,g=[],b=null,D=BigInt(0);const o=n.data;switch(p){case"npub":t=o;break;case"nprofile":t=o.pubkey,g=Array.isArray(o.relays)?o.relays:[];break;case"note":t=o;break;case"nevent":t=o.id,g=Array.isArray(o.relays)?o.relays:[],b=typeof o.author=="string"?o.author:null,typeof o.kind=="number"&&(D=BigInt(o.kind));break;case"naddr":t=`${o.kind}:${o.pubkey}:${o.identifier}`,g=Array.isArray(o.relays)?o.relays:[],typeof o.kind=="number"&&(D=BigInt(o.kind)),b=typeof o.pubkey=="string"?o.pubkey:null;break}return t||(t=e),new h(a.encode(p),a.encode(s),c.NostrData,new He(a.encode(t),a.encode(e),g,a.encode(b||""),D))}catch{return i(s)}},P=[{type:"code",regex:/```([\s\S]*?)```/g,processMatch:e=>m(e[1]||"",e[0])},{type:"cashu",regex:/(cashuA[A-Za-z0-9_-]+)/g,processMatch:e=>y(e[0])},{type:"hashtag",regex:/(?<![^\s"'(])(#[a-zA-Z0-9_]+)(?![a-zA-Z0-9_])/g,processMatch:e=>k(e[0].substring(1))},{type:"image",regex:/(https?:\/\/\S+\.(?:jpg|jpeg|png|gif|webp|svg|ico)(?:\?\S*)?)/gi,processMatch:e=>l(e[0])},{type:"video",regex:/(https?:\/\/\S+\.(?:mp4|mov|avi|mkv|webm|m4v)(?:\?\S*)?)/gi,processMatch:e=>f(e[0])},{type:"nostr",regex:/nostr:([a-z0-9]+)/gi,processMatch:e=>{const s=e[1];return N(s||"",e[0])}},{type:"link",regex:/(https?:\/\/\S+)(?![\)])/gi,processMatch:async e=>S(e[0])}],C=[];for(const e of P){let s;for(e.regex.lastIndex=0;(s=e.regex.exec(d))!==null;){const n=s.index,p=n+s[0].length,t=await e.processMatch(s);C.push({start:n,end:p,block:t})}}C.sort((e,s)=>e.start-s.start);const x=[];for(const e of C)x.some(s=>e.start>=s.start&&e.start<s.end||e.end>s.start&&e.end<=s.end||e.start<=s.start&&e.end>=s.end)||x.push(e);x.sort((e,s)=>e.start-s.start);let v=0;for(const{start:e,end:s,block:n}of x)e>v&&r.push(i(d.substring(v,e))),r.push(n),v=s;v<d.length&&r.push(i(d.substring(v)));const w=[];let u=[];const L=e=>/^\s+$/.test(e);for(let e=0;e<r.length;e++){const s=r[e];if(s?.type?.toString()==="image"||s?.type?.toString()==="video"){u.push(s);continue}if(!(s?.type?.toString()==="text"&&typeof s?.text?.toString()=="string"&&L(s.text?.toString())&&u.length>0&&e+1<r.length&&(r[e+1]?.type?.toString()==="image"||r[e+1]?.type?.toString()==="video"))){if(u.length>0){if(u.length>1){const n=u.map(t=>{if(t.dataType===c.ImageData){const g=t.data;return new K(new A(a.encode(g.url??t.text),null),null)}else if(t.dataType===c.VideoData){const g=t.data;return new K(null,new M(a.encode(g.url??t.text),null))}return new K(null,null)}),p=u.map(t=>String(t.text??"")).join(`
3
3
  `);w.push(new h(a.encode("mediaGrid"),a.encode(p),c.MediaGroupData,new $(n)))}else w.push(u[0]);u=[]}w.push(s)}}if(u.length>0)if(u.length>1){const e=u.map(n=>{if(n.dataType===c.ImageData){const p=n.data;return new K(new A(a.encode(p.url??n.text),null),null)}else if(n.dataType===c.VideoData){const p=n.data;return new K(null,new M(a.encode(p.url??n.text),null))}return new K(null,null)}),s=u.map(n=>String(n.text??"")).join(`
4
4
  `);w.push(new h(a.encode("mediaGrid"),a.encode(s),c.MediaGroupData,new $(e)))}else w.push(u[0]);return w}function Oe(d,r){const i=d[`${String(r)}Length`]();return{[Symbol.iterator](){let l=0;return{next(){for(;l<i;){const f=d[r](l);if(l++,f!=null)return{value:f,done:!1}}return{value:void 0,done:!0}}}}}}function Qe(d,r){if(!d)return[];const i=d[`${String(r)}Length`]?.bind(d);if(!i)return[];const l=i(),f=[];for(let m=0;m<l;m++){const y=d[r](m);y!=null&&f.push(y)}return f}async function Ue(d,r){const i=R(),l=I(i),f=Array.from(i).map(S=>S.toString(16).padStart(2,"0")).join(""),m=I(R()),y=new URLSearchParams;for(const S of r)y.append("relay",S);y.set("secret",m),y.set("name",d);const k=`nostrconnect://${l}?${y.toString()}`;return console.log("connect url:",k),V().setNip46QR(k,f),k}export{_e as ConnectionTracker,j as asBufferFull,z as asCashuData,B as asCodeData,G as asConnectionStatus,Z as asCountResponse,H as asEoce,_ as asHashtagData,F as asImageData,O as asKind0,Q as asKind1,U as asKind10002,W as asKind10019,q as asKind1311,J as asKind17,X as asKind17375,Y as asKind3,ee as asKind4,se as asKind6,ae as asKind7,ne as asKind7374,te as asKind7375,oe as asKind7376,re as asKind9321,ie as asKind9735,de as asLinkPreview,ce as asMediaGroupData,le as asNip51,ue as asNostrData,pe as asNostrEvent,ge as asParsedEvent,he as asValidProofs,fe as asVideoData,Ue as connectWithQRCode,Qe as fbArray,Oe as fbIterable,ye as isBufferFull,E as isConnectionStatus,me as isCountResponse,we as isEoce,Ke as isKind0,ke as isKind1,Se as isKind10002,ve as isKind10019,be as isKind1311,xe as isKind17,Ce as isKind17375,De as isKind3,Ae as isKind4,Me as isKind6,Re as isKind7,Ie as isKind7374,Ee as isKind7375,$e as isKind7376,Ne as isKind9321,Pe as isKind9735,Le as isNip51,Te as isNostrEvent,Ve as isParsedEvent,je as isValidProofs,Fe as parseContent};