@syncar/server 1.0.0-alpha.2 → 1.0.0-alpha.3

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.js CHANGED
@@ -1,2 +1,2 @@
1
- function V(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function z(){return`client-${Date.now()}-${Math.random().toString(36).substring(2,9)}`}var K=1,j=128,J="__";function ie(i){return typeof i=="string"&&i.length>=K&&i.length<=j}function L(i){return i.startsWith(J)}function f(i){if(!ie(i))throw new Error(`Invalid channel name: must be between ${K} and ${j} characters`);if(L(i))throw new Error(`Reserved channel name: channel names starting with '${J}' are reserved for system use`)}function P(i){return i.type==="data"}function Y(i,e,t){return{id:t||V(),type:"data",channel:i,data:e,timestamp:Date.now()}}function O(i,e,t,n){return{id:n||V(),type:"signal",channel:i,signal:e,data:t,timestamp:Date.now()}}var oe={info:"INFO",warn:"WARN",error:"ERROR",debug:"DEBUG"};function ae(){return new Date().toISOString()}function X(i="Syncar",e={}){let t={debug:!1,info:!0,warn:!0,error:!0,...e},n=(r,s)=>{let o=ae();return`[${i} ${o}] [${oe[r]}] ${s}`};return{debug:(r,...s)=>{t.debug&&console.log(n("debug",r),...s)},info:(r,...s)=>{t.info&&console.log(n("info",r),...s)},warn:(r,...s)=>{t.warn&&console.warn(n("warn",r),...s)},error:(r,...s)=>{t.error&&console.error(n("error",r),...s)}}}var y="__broadcast__",H={NORMAL:1e3,GOING_AWAY:1001,PROTOCOL_ERROR:1002,UNSUPPORTED_DATA:1003,NO_STATUS:1005,ABNORMAL:1006,INVALID_PAYLOAD:1007,POLICY_VIOLATION:1008,MESSAGE_TOO_BIG:1009,MISSING_EXTENSION:1010,INTERNAL_ERROR:1011,SERVICE_RESTART:1012,TRY_AGAIN_LATER:1013,REJECTED:4001,RATE_LIMITED:4002,CHANNEL_NOT_FOUND:4003,UNAUTHORIZED:4005},ce={REJECTED:"REJECTED",MISSING_CHANNEL:"MISSING_CHANNEL",SUBSCRIBE_REJECTED:"SUBSCRIBE_REJECTED",UNSUBSCRIBE_REJECTED:"UNSUBSCRIBE_REJECTED",RATE_LIMITED:"RATE_LIMITED",AUTH_FAILED:"AUTH_FAILED",NOT_AUTHORIZED:"NOT_AUTHORIZED",CHANNEL_NOT_ALLOWED:"CHANNEL_NOT_ALLOWED",INVALID_MESSAGE:"INVALID_MESSAGE",SERVER_ERROR:"SERVER_ERROR"},Z="/syncar",D=1048576,U=3e4,B=5e3,le=3e3,de="0.0.0.0",ge="/syncar",he=!0,Q={port:le,host:de,path:ge,enablePing:he,pingInterval:U,pingTimeout:B,broadcastChunkSize:500};var k=class{constructor(e,t,n=500){this.name=e;this.registry=t;this.chunkSize=n}publish(e,t){let n=this.getTargetClients(t);n.length>this.chunkSize?this.publishInChunks(e,n,t):this.publishToClients(e,n,t)}async dispatch(e,t,n){}publishToClients(e,t,n){let r=Y(this.name,e);for(let s of t){if(n?.to&&!n.to.includes(s)||n?.exclude&&n.exclude.includes(s))continue;let o=this.registry.connections.get(s);if(o)try{o.socket.send(JSON.stringify(r))}catch(a){this.registry.logger?.error(`[${this.name}] Failed to send to ${s}:`,a)}}}publishInChunks(e,t,n){let r=0,s=()=>{let o=t.slice(r,r+this.chunkSize);o.length!==0&&(this.publishToClients(e,o,n),r+=this.chunkSize,r<t.length&&setImmediate(s))};s()}},w=class extends k{constructor(e,t=500){super(y,e,t)}getTargetClients(e){return Array.from(this.registry.connections.keys())}get subscriberCount(){return this.registry.connections.size}isEmpty(){return this.registry.connections.size===0}getMiddlewares(){return[]}},E=class extends k{constructor(t){f(t.name);super(t.name,t.registry,t.options?.chunkSize);this.middlewares=[];this.messageHandlers=new Set}getTargetClients(t){return Array.from(this.registry.getChannelSubscribers(this.name))}use(t){this.middlewares.push(t)}getMiddlewares(){return[...this.middlewares]}get subscriberCount(){return this.registry.getChannelSubscribers(this.name).size}onMessage(t){return this.messageHandlers.add(t),()=>this.messageHandlers.delete(t)}subscribe(t){return this.registry.subscribe(t,this.name)}unsubscribe(t){return this.registry.unsubscribe(t,this.name)}async dispatch(t,n,r){if(this.messageHandlers.size>0)for(let s of this.messageHandlers)try{await s(t,n,r)}catch(o){this.registry.logger?.error(`[${this.name}] Error in message handler:`,o)}else this.publish(t,{exclude:[n.id]})}hasSubscriber(t){return this.registry.getChannelSubscribers(this.name).has(t)}getSubscribers(){return new Set(this.registry.getChannelSubscribers(this.name))}isEmpty(){return this.registry.getChannelSubscribers(this.name).size===0}};var g=class i extends Error{constructor(e,t="SYNNEL_ERROR",n){super(e),this.name="SyncarError",this.code=t,this.context=n,Error.captureStackTrace&&Error.captureStackTrace(this,i)}toJSON(){return{name:this.name,message:this.message,code:this.code,context:this.context,stack:this.stack}}toString(){return`[${this.name}:${this.code}] ${this.message}`}},G=class extends g{constructor(e,t){super(e,"CONFIG_ERROR",t),this.name="ConfigError"}},$=class extends g{constructor(e,t){super(e,"TRANSPORT_ERROR",t),this.name="TransportError"}},h=class extends g{constructor(e,t){super(e,"CHANNEL_ERROR",t),this.name="ChannelError"}},q=class extends g{constructor(e,t){super(e,"CLIENT_ERROR",t),this.name="ClientError"}},u=class extends g{constructor(e,t){super(e,"MESSAGE_ERROR",t),this.name="MessageError"}},F=class extends g{constructor(e,t){super(e,"VALIDATION_ERROR",t),this.name="ValidationError"}},p=class extends g{constructor(e,t){super(e,"STATE_ERROR",t),this.name="StateError"}},S=class i extends Error{constructor(t,n,r,s){super(`Action '${n}' rejected: ${t}`);this.name="MiddlewareRejectionError";this.reason=t,this.action=n,this.name="MiddlewareRejectionError",this.code=r,this.context=s,Error.captureStackTrace&&Error.captureStackTrace(this,i)}toJSON(){return{name:this.name,reason:this.reason,action:this.action,code:this.code,context:this.context,message:this.message,stack:this.stack}}toString(){return`[${this.name}:${this.action}] ${this.reason}`}},I=class i extends Error{constructor(e,t,n){super(`Middleware execution error in ${t} during ${e}: ${n.message}`),this.name="MiddlewareExecutionError",this.action=e,this.middleware=t,this.cause=n,Error.captureStackTrace&&Error.captureStackTrace(this,i)}getCause(){return this.cause}toString(){return`[${this.name}] ${this.middleware} failed during ${this.action}: ${this.cause.message}`}};var x=class{constructor(e){this.registry=e.registry,this.context=e.context,this.options={requireChannel:e.options?.requireChannel??!1,allowReservedChannels:e.options?.allowReservedChannels??!1,sendAcknowledgments:e.options?.sendAcknowledgments??!0,autoRespondToPing:e.options?.autoRespondToPing??!0}}async handleSignal(e,t){let n;t.signal==="subscribe"?n=this.context.createSubscribeContext(e,t.channel):t.signal==="unsubscribe"?n=this.context.createUnsubscribeContext(e,t.channel):n=this.context.createMessageContext(e,t);let r=async()=>{switch(t.signal){case"subscribe":await this.handleSubscribe(e,t);break;case"unsubscribe":await this.handleUnsubscribe(e,t);break;case"ping":await this.handlePing(e,t);break;case"pong":await this.handlePong(e,t);break;default:throw new u(`Unknown signal type: ${t.signal}`)}},s;t.channel&&t.channel!==y&&(s=this.registry.getChannel(t.channel));let o=this.context.getPipeline(s);await this.context.execute(n,o,r)}async handleSubscribe(e,t){let{channel:n}=t;if(!this.options.allowReservedChannels&&L(n))throw new h(`Cannot subscribe to reserved channel: ${n}`);if(n===y)throw new h("Cannot subscribe to broadcast channel");if(!this.registry.subscribe(e.id,n))throw new h(`Failed to subscribe client ${e.id} to channel ${n}`);if(this.options.sendAcknowledgments){let s=O(n,"subscribed",void 0,t.id);e.socket.send(JSON.stringify(s),()=>{})}}async handleUnsubscribe(e,t){let{channel:n}=t;if(!this.registry.isSubscribed(e.id,n))throw new h(`Client not subscribed to channel: ${n}`);if(this.registry.unsubscribe(e.id,n),this.options.sendAcknowledgments){let r=O(n,"unsubscribed",void 0,t.id);e.socket.send(JSON.stringify(r),()=>{})}}async handlePing(e,t){if(e.lastPingAt=Date.now(),this.options.autoRespondToPing){let n=O(t.channel,"pong",void 0,t.id);e.socket.send(JSON.stringify(n),()=>{})}}async handlePong(e,t){e.lastPingAt=Date.now()}getOptions(){return this.options}};var M=class{constructor(e){this.registry=e.registry,this.context=e.context,this.options={requireChannel:e.options?.requireChannel??!0}}async handleMessage(e,t){if(!P(t))throw new u("Invalid message type: expected DATA message");let n=this.registry.getChannel(t.channel);if(this.options.requireChannel&&!n)throw new h(`Channel not found: ${t.channel}`);let r=this.context.getPipeline(n),s=this.context.createMessageContext(e,t),o=async()=>{n&&await n.dispatch(t.data,e,t)};await this.context.execute(s,r,o)}canProcessMessage(e){return P(e)?this.options.requireChannel?!!this.registry.getChannel(e.channel):!0:!1}getChannelForMessage(e){return this.registry.getChannel(e.channel)}getOptions(){return this.options}};var T=class{constructor(e){this.registry=e.registry,this.options={rejectionCloseCode:e.options?.rejectionCloseCode??H.REJECTED}}async handleConnection(e){return this.registry.register(e)}async handleDisconnection(e,t){this.registry.get(e)&&this.registry.unregister(e)}getOptions(){return this.options}};var ee=i=>(e,t)=>{let n=-1,r=async s=>{if(s<=n)throw new Error("next() called multiple times");n=s;let o,a=i[s];if(a)o=await a(e,async()=>{await r(s+1)});else if(s===i.length&&t)o=await t();else return e;return o!==void 0&&!e.finalized&&(e.res=o,e.finalized=!0),e};return r(0)};function R(i){let{action:e,client:t,message:n,channel:r,initialState:s={}}=i,o=s;return{req:{action:e,client:t,message:n,channel:r},var:o,finalized:!1,get:a=>o[a],set:(a,c)=>{o[a]=c},reject:a=>{throw new S(a,e)}}}var A=class{constructor(){this.middlewares=[]}use(e){this.middlewares.push(e)}remove(e){let t=this.middlewares.indexOf(e);return t!==-1?(this.middlewares.splice(t,1),!0):!1}clear(){this.middlewares.length=0}getMiddlewares(){return[...this.middlewares]}getPipeline(e){let t=this.getMiddlewares(),n=e?.getMiddlewares?.();return n&&n.length>0&&(t=[...t,...n]),t}async executeConnection(e,t){let n=this.createConnectionContext(e,t);return await this.execute(n)}async executeMessage(e,t){let n=this.createMessageContext(e,t);return await this.execute(n)}async executeSubscribe(e,t,n){let r=this.createSubscribeContext(e,t);return await this.execute(r,this.middlewares,n)}async executeUnsubscribe(e,t,n){let r=this.createUnsubscribeContext(e,t);return await this.execute(r,this.middlewares,n)}async execute(e,t=this.middlewares,n){let r=e.req.action||"unknown",s=t.map((o,a)=>async(c,d)=>{try{await o(c,d)}catch(l){if(l instanceof S||l instanceof I)throw l;let m=o.name||`middleware[${a}]`;throw new I(r,m,l instanceof Error?l:new Error(String(l)))}});return await ee(s)(e,n)}createConnectionContext(e,t){return R({client:e,action:t})}createMessageContext(e,t){return R({client:e,message:t,action:"message"})}createSubscribeContext(e,t){return R({client:e,channel:t,action:"subscribe"})}createUnsubscribeContext(e,t){return R({client:e,channel:t,action:"unsubscribe"})}getCount(){return this.middlewares.length}hasMiddleware(){return this.middlewares.length>0}};var _=class{constructor(e){this.connections=new Map;this.subscriptions=new Map;this.channels=new Map;this.channelInstances=new Map;this.logger=e}register(e){return this.connections.set(e.id,e),e}unregister(e){let t=this.get(e);return t?(this.clearSubscriptions(t),this.connections.delete(e)):!1}clearSubscriptions(e){let t=this.subscriptions.get(e.id);if(t){for(let n of t)this.channels.get(n)?.delete(e.id);this.subscriptions.delete(e.id)}}get(e){return this.connections.get(e)}getAll(){return Array.from(this.connections.values())}getCount(){return this.connections.size}registerChannel(e){this.channels.has(e.name)||this.channels.set(e.name,new Set),this.channelInstances.set(e.name,e)}getChannel(e){return this.channelInstances.get(e)}removeChannel(e){let t=this.channels.get(e);if(!t)return!1;for(let n of t){let r=this.subscriptions.get(n);r&&r.delete(e)}return this.channelInstances.delete(e),this.channels.delete(e)}subscribe(e,t){if(f(t),!this.connections.has(e))return!1;this.channels.has(t)||this.channels.set(t,new Set);let n=this.channels.get(t);return n.has(e)||(n.add(e),this.subscriptions.has(e)||this.subscriptions.set(e,new Set),this.subscriptions.get(e).add(t)),!0}unsubscribe(e,t){let n=this.channels.get(t);if(!n||!n.has(e))return!1;n.delete(e);let r=this.subscriptions.get(e);return r&&(r.delete(t),r.size===0&&this.subscriptions.delete(e)),!0}getSubscribers(e){let t=this.channels.get(e);if(!t)return[];let n=[];for(let r of t){let s=this.connections.get(r);s&&n.push(s)}return n}getSubscriberCount(e){return this.channels.get(e)?.size??0}getChannels(){return Array.from(this.channels.keys())}getTotalSubscriptionCount(){let e=0;for(let t of this.channels.values())e+=t.size;return e}isSubscribed(e,t){return this.channels.get(t)?.has(e)??!1}getClientChannels(e){return this.subscriptions.get(e)??new Set}getChannelSubscribers(e){return this.channels.get(e)??new Set}clear(){this.connections.clear(),this.subscriptions.clear(),this.channels.clear(),this.channelInstances.clear()}};import{EventEmitter as pe}from"events";import{WebSocketServer as Ce}from"ws";var v=class extends pe{constructor(e){super(),this.setMaxListeners(100),this.connections=e.connections??new Map,this.config={...e,path:e.path??Z,maxPayload:e.maxPayload??D,enablePing:e.enablePing??!0,pingInterval:e.pingInterval??U,pingTimeout:e.pingTimeout??B,connections:this.connections};let t=e.ServerConstructor??Ce;this.wsServer=new t({server:this.config.server,path:this.config.path,maxPayload:this.config.maxPayload}),this.setupEventHandlers(),this.config.enablePing&&this.startPingTimer()}setAuthenticator(e){this.authenticator=e}setupEventHandlers(){this.wsServer.on("connection",(e,t)=>{this.handleConnection(e,t)}),this.wsServer.on("error",e=>{this.config.logger?.error("WebSocket Server Error:",e),this.emit("error",e)})}async handleConnection(e,t){let n;try{this.authenticator?n=await this.authenticator(t):this.config.generateId?n=await this.config.generateId(t):n=z()}catch(o){try{e.close(4001,o instanceof Error?o.message:"Unauthorized")}catch{}return}let r=Date.now(),s={socket:e,id:n,connectedAt:r,lastPingAt:r};this.connections.set(n,s),e.on("message",o=>{this.handleMessage(n,o)}),e.on("close",(o,a)=>{this.handleDisconnection(n)}),e.on("error",o=>{this.emit("error",o)}),this.config.enablePing&&this.setupPingPong(n,e),this.emit("connection",s)}handleMessage(e,t){try{let n=JSON.parse(t.toString()),r=this.connections.get(e);r&&n.type==="signal"&&n.signal==="pong"&&(r.lastPingAt=Date.now()),this.emit("message",e,n)}catch(n){this.config.logger?.error(`Failed to parse message from ${e}:`,n),this.emit("error",n)}}handleDisconnection(e){this.emit("disconnection",e),this.connections.delete(e)}setupPingPong(e,t){t.on("pong",()=>{let n=this.connections.get(e);n&&(n.lastPingAt=Date.now())})}startPingTimer(){this.pingTimer&&clearInterval(this.pingTimer),this.pingTimer=setInterval(()=>{this.checkConnections()},this.config.pingInterval)}checkConnections(){let e=Date.now(),t=Array.from(this.connections.values());for(let n of t){let r=n.socket,s=n.lastPingAt??n.connectedAt;if(e-s>this.config.pingInterval+this.config.pingTimeout){r.close(1e3,"Ping timeout");continue}r.readyState===1&&r.ping()}}};var N=class{constructor(e){this.status={started:!1,startedAt:void 0};if(this.config=e,this.registry=this.config.registry,this.context=new A,this.config.middleware)for(let t of this.config.middleware)this.context.use(t)}async start(){if(this.status.started)throw new p("Server is already started");this.transport=this.config.transport,this.connectionHandler=new T({registry:this.registry}),this.messageHandler=new M({registry:this.registry,context:this.context}),this.signalHandler=new x({registry:this.registry,context:this.context}),this.setupTransportHandlers(),this.broadcastChannel=new w(this.registry,this.config.broadcastChunkSize),this.registry.registerChannel(this.broadcastChannel),this.status.started=!0,this.status.startedAt=Date.now()}async stop(){!this.status.started||!this.transport||(this.connectionHandler=void 0,this.messageHandler=void 0,this.signalHandler=void 0,this.registry.clear(),this.broadcastChannel=void 0,this.status.started=!1,this.status.startedAt=void 0)}createBroadcast(){if(!this.status.started||!this.broadcastChannel)throw new p("Server must be started before creating channels");return this.broadcastChannel}createMulticast(e){if(f(e),!this.status.started||!this.transport)throw new p("Server must be started before creating channels");let t=this.registry.getChannel(e);if(t)return t;let n=new E({name:e,registry:this.registry,options:{chunkSize:this.config.broadcastChunkSize}});return this.registry.registerChannel(n),n}hasChannel(e){return!!this.registry.getChannel(e)}getChannels(){return this.registry.getChannels()}use(e){this.context.use(e)}authenticate(e){let t=this.transport||this.config.transport;t&&"setAuthenticator"in t?t.setAuthenticator(e):this.config.logger.warn("Current transport does not support setting an authenticator.")}getStats(){return{startedAt:this.status.startedAt,clientCount:this.registry.getCount(),channelCount:this.registry.getChannels().length,subscriptionCount:this.registry.getTotalSubscriptionCount()}}getConfig(){return this.config}getRegistry(){return this.registry}setupTransportHandlers(){let e=this.transport;e.on("connection",async t=>{try{await this.context.executeConnection(t,"connect"),await this.connectionHandler.handleConnection(t)}catch(n){this.config.logger.error("Error handling connection:",n)}}),e.on("disconnection",async t=>{try{let n=this.registry.get(t);n&&(await this.context.executeConnection(n,"disconnect"),await this.connectionHandler.handleDisconnection(t))}catch(n){this.config.logger.error("Error handling disconnection:",n)}}),e.on("message",async(t,n)=>{try{let r=this.registry.get(t);if(!r)return;n.type==="data"?await this.messageHandler.handleMessage(r,n):n.type==="signal"&&await this.signalHandler.handleSignal(r,n)}catch(r){this.config.logger.error("Error handling message:",r)}}),e.on("error",t=>{this.config.logger.error("Transport error:",t)})}};function me(i={}){let e=i.registry??new _,t=i.logger??X(),n={...Q,middleware:[],...i,registry:e,logger:t};return n.transport||(n.server||import("http").then(r=>{let s=r.createServer();s.listen(n.port,n.host),n.server=s}),n.transport=new v({server:n.server,path:n.path,maxPayload:i.maxPayload??D,enablePing:n.enablePing,pingInterval:n.pingInterval,pingTimeout:n.pingTimeout,connections:e.connections,generateId:n.generateId,logger:n.logger})),new N(n)}function te(i){let{verifyToken:e,getToken:t=s=>s.req.message?.data?.token,attachProperty:n="user",actions:r}=i;return async(s,o)=>{if(r&&!r.includes(s.req.action))return o();let a=t(s);a||s.reject("Authentication token required");try{let c=await e(a);s.req.client&&(s.req.client[n]=c),s.set(n,c),await o()}catch{s.reject("Authentication failed: Invalid token")}}}function ne(i={}){let{logger:e=console,logLevel:t="info",includeMessageData:n=!1,format:r,actions:s}=i;return async(o,a)=>{if(s&&!s.includes(o.req.action))return a();let c=Date.now();await a();let d=Date.now()-c,l={action:o.req.action,clientId:o.req.client?.id,channel:o.req.channel,message:n?o.req.message:void 0,duration:d},m=r?r(l):`[${l.action}] Client: ${l.clientId??"unknown"}${l.channel?` Channel: ${l.channel}`:""} (${d}ms)`;e[t](m)}}var C=new Map;function re(i={}){let{maxRequests:e=100,windowMs:t=6e4,getMessageId:n=a=>a.req.client?.id??"",actions:r=["message"]}=i,s=setInterval(()=>{let a=Date.now();for(let[c,d]of C)d.resetTime<a&&C.delete(c)},t*10),o=async(a,c)=>{if(!r.includes(a.req.action))return c();let d=n(a);if(!d)return c();let l=Date.now(),m=C.get(d);m&&m.resetTime<l&&C.delete(d);let b=C.get(d);b||(b={count:0,resetTime:l+t},C.set(d,b)),b.count>=e&&a.reject(`Rate limit exceeded. Max ${e} requests per ${t}ms`),b.count++,await c()};return o.cleanup=()=>{clearInterval(s),C.clear()},o}function se(i={}){let{allowedChannels:e=[],isDynamic:t,restrictUnsubscribe:n=!1}=i;return async(r,s)=>{if(r.req.action!=="subscribe"&&r.req.action!=="unsubscribe"||r.req.action==="unsubscribe"&&!n||!r.req.channel)return s();if(t)return t(r.req.channel,r.req.client)||r.reject(`Channel '${r.req.channel}' is not allowed`),s();e.includes(r.req.channel)||r.reject(`Channel '${r.req.channel}' is not allowed`),await s()}}export{y as BROADCAST_CHANNEL,w as BroadcastChannel,H as CLOSE_CODES,h as ChannelError,q as ClientError,G as ConfigError,A as ContextManager,ce as ERROR_CODES,u as MessageError,I as MiddlewareExecutionError,S as MiddlewareRejectionError,E as MulticastChannel,p as StateError,N as Syncar,g as SyncarError,N as SyncarServer,$ as TransportError,F as ValidationError,v as WebSocketServerTransport,te as createAuthMiddleware,se as createChannelWhitelistMiddleware,R as createContext,ne as createLoggingMiddleware,re as createRateLimitMiddleware,me as createSyncarServer};
1
+ var c=class s extends Error{constructor(e,n="SYNNEL_ERROR",t){super(e),this.name="SyncarError",this.code=n,this.context=t,Error.captureStackTrace&&Error.captureStackTrace(this,s)}toJSON(){return{name:this.name,message:this.message,code:this.code,context:this.context,stack:this.stack}}toString(){return`[${this.name}:${this.code}] ${this.message}`}},N=class extends c{constructor(e,n){super(e,"CONFIG_ERROR",n),this.name="ConfigError"}},A=class extends c{constructor(e,n){super(e,"TRANSPORT_ERROR",n),this.name="TransportError"}},l=class extends c{constructor(e,n){super(e,"CHANNEL_ERROR",n),this.name="ChannelError"}},O=class extends c{constructor(e,n){super(e,"CLIENT_ERROR",n),this.name="ClientError"}},g=class extends c{constructor(e,n){super(e,"MESSAGE_ERROR",n),this.name="MessageError"}},_=class extends c{constructor(e,n){super(e,"VALIDATION_ERROR",n),this.name="ValidationError"}},p=class extends c{constructor(e,n){super(e,"STATE_ERROR",n),this.name="StateError"}},u=class s extends Error{constructor(n,t,r,i){super(`Action '${t}' rejected: ${n}`);this.name="MiddlewareRejectionError";this.reason=n,this.action=t,this.name="MiddlewareRejectionError",this.code=r,this.context=i,Error.captureStackTrace&&Error.captureStackTrace(this,s)}toJSON(){return{name:this.name,reason:this.reason,action:this.action,code:this.code,context:this.context,message:this.message,stack:this.stack}}toString(){return`[${this.name}:${this.action}] ${this.reason}`}},C=class s extends Error{constructor(e,n,t){super(`Middleware execution error in ${n} during ${e}: ${t.message}`),this.name="MiddlewareExecutionError",this.action=e,this.middleware=n,this.cause=t,Error.captureStackTrace&&Error.captureStackTrace(this,s)}getCause(){return this.cause}toString(){return`[${this.name}] ${this.middleware} failed during ${this.action}: ${this.cause.message}`}};var U=s=>(e,n)=>{let t=-1,r=async i=>{if(i<=t)throw new Error("next() called multiple times");t=i;let o,a=s[i];if(a)o=await a(e,async()=>{await r(i+1)});else if(i===s.length&&n)o=await n();else return e;return o!==void 0&&!e.finalized&&(e.res=o,e.finalized=!0),e};return r(0)};function m(s){let{action:e,client:n,message:t,channel:r,initialState:i={}}=s,o=i;return{req:{action:e,client:n,message:t,channel:r},finalized:!1,get:a=>o[a],set:(a,h)=>{o[a]=h},reject:a=>{throw new u(a,e)}}}var S=class{constructor(){this.middlewares=[]}use(e){this.middlewares.push(e)}remove(e){let n=this.middlewares.indexOf(e);return n!==-1?(this.middlewares.splice(n,1),!0):!1}clear(){this.middlewares.length=0}getMiddlewares(){return[...this.middlewares]}getPipeline(e){let n=this.getMiddlewares(),t=e?.getMiddlewares?.();return t&&t.length>0&&(n=[...n,...t]),n}async executeConnection(e,n){let t=this.createConnectionContext(e,n);return await this.execute(t)}async executeMessage(e,n){let t=this.createMessageContext(e,n);return await this.execute(t)}async executeSubscribe(e,n,t){let r=this.createSubscribeContext(e,n);return await this.execute(r,this.middlewares,t)}async executeUnsubscribe(e,n,t){let r=this.createUnsubscribeContext(e,n);return await this.execute(r,this.middlewares,t)}async execute(e,n=this.middlewares,t){let r=e.req.action||"unknown",i=n.map((o,a)=>async(h,x)=>{try{await o(h,x)}catch(d){if(d instanceof u||d instanceof C)throw d;let H=o.name||`middleware[${a}]`;throw new C(r,H,d instanceof Error?d:new Error(String(d)))}});return await U(i)(e,t)}createConnectionContext(e,n){return m({client:e,action:n})}createMessageContext(e,n){return m({client:e,message:n,action:"message"})}createSubscribeContext(e,n){return m({client:e,channel:n,action:"subscribe"})}createUnsubscribeContext(e,n){return m({client:e,channel:n,action:"unsubscribe"})}getCount(){return this.middlewares.length}hasMiddleware(){return this.middlewares.length>0}};function B(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function F(){return`client-${Date.now()}-${Math.random().toString(36).substring(2,9)}`}var $=1,W=128,j="__";function Y(s){return typeof s=="string"&&s.length>=$&&s.length<=W}function z(s){return s.startsWith(j)}function V(s){if(!Y(s))throw new Error(`Invalid channel name: must be between ${$} and ${W} characters`)}function q(s){return s.type==="data"}function X(s,e,n){return{id:n||B(),type:"data",channel:s,data:e,timestamp:Date.now()}}function M(s,e,n,t){return{id:t||B(),type:"signal",channel:s,signal:e,data:n,timestamp:Date.now()}}function v(s){let{data:e,connections:n,chunkSize:t,channel:r}=s,i=X(r,e),o=JSON.stringify(i),a=0,h=()=>{let x=n.slice(a,a+t);if(x.length!==0){for(let d of x)try{d.socket.send(o)}catch{console.error(`Failed to send message to ${d.id}`)}a+=t,a<n.length&&setImmediate(h)}};h()}var Z={info:"INFO",warn:"WARN",error:"ERROR",debug:"DEBUG"};function Q(){return new Date().toISOString()}function K(s="Syncar",e={}){let n={debug:!1,info:!0,warn:!0,error:!0,...e},t=(r,i)=>{let o=Q();return`[${s} ${o}] [${Z[r]}] ${i}`};return{debug:(r,...i)=>{n.debug&&console.log(t("debug",r),...i)},info:(r,...i)=>{n.info&&console.log(t("info",r),...i)},warn:(r,...i)=>{n.warn&&console.warn(t("warn",r),...i)},error:(r,...i)=>{n.error&&console.error(t("error",r),...i)}}}var R=class{constructor(e){this.connections=new Map;this.subscriptions=new Map;this.channels=new Map;this.channelInstances=new Map;this.logger=e}register(e){return this.connections.set(e.id,e),e}unregister(e){let n=this.get(e);return n?(this.clearSubscriptions(n),this.connections.delete(e)):!1}clearSubscriptions(e){let n=this.subscriptions.get(e.id);if(n){for(let t of n)this.channels.get(t)?.delete(e.id);this.subscriptions.delete(e.id)}}get(e){return this.connections.get(e)}getAll(){return Array.from(this.connections.values())}getCount(){return this.connections.size}registerChannel(e){this.channels.has(e.name)||this.channels.set(e.name,new Set),this.channelInstances.set(e.name,e)}getChannel(e){return this.channelInstances.get(e)}removeChannel(e){let n=this.channels.get(e);if(!n)return!1;for(let t of n){let r=this.subscriptions.get(t);r&&r.delete(e)}return this.channelInstances.delete(e),this.channels.delete(e)}subscribe(e,n){if(V(n),!this.connections.has(e))return!1;this.channels.has(n)||this.channels.set(n,new Set);let t=this.channels.get(n);return t.has(e)||(t.add(e),this.subscriptions.has(e)||this.subscriptions.set(e,new Set),this.subscriptions.get(e).add(n)),!0}unsubscribe(e,n){let t=this.channels.get(n);if(!t||!t.has(e))return!1;t.delete(e);let r=this.subscriptions.get(e);return r&&(r.delete(n),r.size===0&&this.subscriptions.delete(e)),!0}getSubscribers(e){let n=this.channels.get(e);if(!n)return[];let t=[];for(let r of n){let i=this.connections.get(r);i&&t.push(i)}return t}getSubscriberCount(e){return this.channels.get(e)?.size??0}getChannels(){return Array.from(this.channels.keys())}getTotalSubscriptionCount(){let e=0;for(let n of this.channels.values())e+=n.size;return e}isSubscribed(e,n){return this.channels.get(n)?.has(e)??!1}getClientChannels(e){return this.subscriptions.get(e)??new Set}getChannelSubscribers(e){return this.channels.get(e)??new Set}clear(){this.connections.clear(),this.subscriptions.clear(),this.channels.clear(),this.channelInstances.clear()}};var f=class{constructor(e){this.middlewares=[];this.messageHandlers=new Set;let{name:n,registry:t,options:r,chunkSize:i=500}=e;this.name=n,this.registry=t,this.chunkSize=i,this.flow=r?.flow??"bidirectional",this._createdAt=Date.now()}subscribe(e){return this.registry.subscribe(e,this.name)}unsubscribe(e){return this.registry.unsubscribe(e,this.name)}hasSubscriber(e){return this.registry.getChannelSubscribers(this.name).has(e)}getSubscribers(){return new Set(this.registry.getChannelSubscribers(this.name))}get subscriberCount(){return this.registry.getChannelSubscribers(this.name).size}isEmpty(){return this.subscriberCount===0}publish(e,n=[]){this._lastMessageAt=Date.now();let t=this.registry.getSubscribers(this.name);if(n.length>0){let r=new Set(n);t=t.filter(i=>!r.has(i.id))}t.length!==0&&v({channel:this.name,data:e,connections:t,chunkSize:this.chunkSize})}onMessage(e){if(this.flow==="send-only")throw new Error(`Cannot register message handler on channel '${this.name}': onMessage is not available in send-only mode.`);return this.messageHandlers.add(e),()=>this.messageHandlers.delete(e)}async dispatch(e,n,t){if(this.flow!=="send-only")if(this.messageHandlers.size>0)for(let r of this.messageHandlers)try{await r(e,n,t)}catch(i){this.registry.logger?.error(`[${this.name}] Error in message handler:`,i)}else this.flow==="bidirectional"&&this.publish(e,[n.id])}use(e){this.middlewares.push(e)}getMiddlewares(){return[...this.middlewares]}getState(){return{name:this.name,subscriberCount:this.subscriberCount,createdAt:this._createdAt,lastMessageAt:this._lastMessageAt}}};import{EventEmitter as ie}from"events";import{WebSocketServer as oe}from"ws";var k={NORMAL:1e3,GOING_AWAY:1001,PROTOCOL_ERROR:1002,UNSUPPORTED_DATA:1003,NO_STATUS:1005,ABNORMAL:1006,INVALID_PAYLOAD:1007,POLICY_VIOLATION:1008,MESSAGE_TOO_BIG:1009,MISSING_EXTENSION:1010,INTERNAL_ERROR:1011,SERVICE_RESTART:1012,TRY_AGAIN_LATER:1013,REJECTED:4001,RATE_LIMITED:4002,CHANNEL_NOT_FOUND:4003,UNAUTHORIZED:4005},ee={REJECTED:"REJECTED",MISSING_CHANNEL:"MISSING_CHANNEL",SUBSCRIBE_REJECTED:"SUBSCRIBE_REJECTED",UNSUBSCRIBE_REJECTED:"UNSUBSCRIBE_REJECTED",RATE_LIMITED:"RATE_LIMITED",AUTH_FAILED:"AUTH_FAILED",NOT_AUTHORIZED:"NOT_AUTHORIZED",CHANNEL_NOT_ALLOWED:"CHANNEL_NOT_ALLOWED",INVALID_MESSAGE:"INVALID_MESSAGE",SERVER_ERROR:"SERVER_ERROR"},T=1048576,D=3e4,L=5e3,ne=3e3,te="0.0.0.0",P="/syncar",re=!0,J={port:ne,host:te,path:P,enablePing:re,pingInterval:D,pingTimeout:L,chunkSize:500};var y=class extends ie{constructor(e){super(),this.setMaxListeners(100),this.connections=e.connections??new Map,this.config={...e,path:e.path??P,maxPayload:e.maxPayload??T,enablePing:e.enablePing??!0,pingInterval:e.pingInterval??D,pingTimeout:e.pingTimeout??L,connections:this.connections};let n=e.ServerConstructor??oe;this.wsServer=new n({server:this.config.server,path:this.config.path,maxPayload:this.config.maxPayload}),this.setupEventHandlers(),this.config.enablePing&&this.startPingTimer()}setupEventHandlers(){this.wsServer.on("connection",(e,n)=>{this.handleConnection(e,n)}),this.wsServer.on("error",e=>{this.emit("error",e)})}async handleConnection(e,n){let t=F(),r=Date.now(),i={socket:e,id:t,connectedAt:r,lastPingAt:r};this.connections.set(t,i),e.on("message",o=>{this.handleMessage(t,o)}),e.on("close",(o,a)=>{this.handleDisconnection(t)}),e.on("error",o=>{this.emit("error",o)}),this.config.enablePing&&this.setupPingPong(t,e),this.emit("connection",i,n)}handleMessage(e,n){try{let t=JSON.parse(n.toString()),r=this.connections.get(e);r&&t.type==="signal"&&t.signal==="pong"&&(r.lastPingAt=Date.now()),this.emit("message",e,t)}catch(t){this.emit("error",t)}}handleDisconnection(e){this.emit("disconnection",e),this.connections.delete(e)}setupPingPong(e,n){n.on("pong",()=>{let t=this.connections.get(e);t&&(t.lastPingAt=Date.now())})}startPingTimer(){this.pingTimer&&clearInterval(this.pingTimer),this.pingTimer=setInterval(()=>{this.checkConnections()},this.config.pingInterval)}checkConnections(){let e=Date.now(),n=Array.from(this.connections.values());for(let t of n){let r=t.socket,i=t.lastPingAt??t.connectedAt;if(e-i>this.config.pingInterval+this.config.pingTimeout){r.close(1e3,"Ping timeout");continue}r.readyState===1&&r.ping()}}};var I=class{constructor(e){this.registry=e.registry,this.context=e.context,this.options={requireChannel:e.options?.requireChannel??!1,allowReservedChannels:e.options?.allowReservedChannels??!1,sendAcknowledgments:e.options?.sendAcknowledgments??!0,autoRespondToPing:e.options?.autoRespondToPing??!0}}async handleSignal(e,n){let t;n.signal==="subscribe"?t=this.context.createSubscribeContext(e,n.channel):n.signal==="unsubscribe"?t=this.context.createUnsubscribeContext(e,n.channel):t=this.context.createMessageContext(e,n);let r=async()=>{switch(n.signal){case"subscribe":await this.handleSubscribe(e,n);break;case"unsubscribe":await this.handleUnsubscribe(e,n);break;case"ping":await this.handlePing(e,n);break;case"pong":await this.handlePong(e,n);break;default:throw new g(`Unknown signal type: ${n.signal}`)}},i;n.channel&&(i=this.registry.getChannel(n.channel));let o=this.context.getPipeline(i);await this.context.execute(t,o,r)}async handleSubscribe(e,n){let{channel:t}=n;if(!this.options.allowReservedChannels&&z(t))throw new l(`Cannot subscribe to reserved channel: ${t}`);if(this.registry.getChannel(t)?.scope==="broadcast")throw new l("Cannot subscribe to broadcast channel");if(!this.registry.subscribe(e.id,t))throw new l(`Failed to subscribe client ${e.id} to channel ${t}`);if(this.options.sendAcknowledgments){let o=M(t,"subscribed",void 0,n.id);e.socket.send(JSON.stringify(o))}}async handleUnsubscribe(e,n){let{channel:t}=n;if(!this.registry.isSubscribed(e.id,t))throw new l(`Client not subscribed to channel: ${t}`);if(this.registry.unsubscribe(e.id,t),this.options.sendAcknowledgments){let r=M(t,"unsubscribed",void 0,n.id);e.socket.send(JSON.stringify(r),()=>{})}}async handlePing(e,n){if(e.lastPingAt=Date.now(),this.options.autoRespondToPing){let t=M(n.channel,"pong",void 0,n.id);e.socket.send(JSON.stringify(t),()=>{})}}async handlePong(e,n){e.lastPingAt=Date.now()}getOptions(){return this.options}};var b=class{constructor(e){this.registry=e.registry,this.context=e.context,this.options={requireChannel:e.options?.requireChannel??!0}}async handleMessage(e,n){if(!q(n))throw new g("Invalid message type: expected DATA message");let t=this.registry.getChannel(n.channel);if(this.options.requireChannel&&!t)throw new l(`Channel not found: ${n.channel}`);let r=this.context.getPipeline(t),i=this.context.createMessageContext(e,n),o=async()=>{t&&await t.dispatch(n.data,e,n)};await this.context.execute(i,r,o)}getOptions(){return this.options}};var E=class{constructor(e){this.registry=e.registry,this.options={rejectionCloseCode:e.options?.rejectionCloseCode??k.REJECTED}}async handleConnection(e){this.registry.register(e)}async handleDisconnection(e,n){this.registry.get(e)&&this.registry.unregister(e)}getOptions(){return this.options}};var w=class{constructor(e){this.status={started:!1,startedAt:void 0};if(this.config=e,this.registry=this.config.registry,this.context=new S,this.config.middleware)for(let n of this.config.middleware)this.context.use(n)}start(){if(this.status.started)throw new p("Server is already started");this.transport=this.config.transport,this.connectionHandler=new E({registry:this.registry}),this.messageHandler=new b({registry:this.registry,context:this.context}),this.signalHandler=new I({registry:this.registry,context:this.context}),this.setupTransportHandlers(),this.status.started=!0,this.status.startedAt=Date.now()}stop(){!this.status.started||!this.transport||(this.connectionHandler=void 0,this.messageHandler=void 0,this.signalHandler=void 0,this.registry.clear(),this.status.started=!1,this.status.startedAt=void 0)}createChannel(e,n){if(!this.status.started||!this.transport)throw new p("Server must be started before creating channels");let t=this.registry.getChannel(e);if(t)return t;let r=new f({name:e,registry:this.registry,options:n,chunkSize:this.config.chunkSize});return this.registry.registerChannel(r),r}hasChannel(e){return!!this.registry.getChannel(e)}broadcast(e){let n=this.registry.getAll();v({data:e,connections:n,chunkSize:this.config.chunkSize,channel:"__broadcast__"})}getChannels(){return this.registry.getChannels()}use(e){this.context.use(e)}getStats(){return{startedAt:this.status.startedAt,clientCount:this.registry.getCount(),channelCount:this.registry.getChannels().length,subscriptionCount:this.registry.getTotalSubscriptionCount()}}getConfig(){return this.config}setupTransportHandlers(){let e=this.transport;e.on("connection",async n=>{try{await this.context.executeConnection(n,"connect"),await this.connectionHandler.handleConnection(n)}catch(t){this.config.logger.error("Error handling connection:",t)}}),e.on("disconnection",async n=>{try{let t=this.registry.get(n);t&&(await this.context.executeConnection(t,"disconnect"),await this.connectionHandler.handleDisconnection(n))}catch(t){this.config.logger.error("Error handling disconnection:",t)}}),e.on("message",async(n,t)=>{try{let r=this.registry.get(n);if(!r)return;t.type==="data"?await this.messageHandler.handleMessage(r,t):t.type==="signal"&&await this.signalHandler.handleSignal(r,t)}catch(r){this.config.logger.error(r.message)}}),e.on("error",n=>{this.config.logger.error(n.message)})}};function ae(s={}){let e=s.registry??new R,n=s.logger??K(),t={...J,middleware:[],...s,registry:e,logger:n};return t.transport||(t.server||import("http").then(r=>{let i=r.createServer();i.listen(t.port,t.host),t.server=i}),t.transport=new y({server:t.server,path:t.path,maxPayload:s.maxPayload??T,enablePing:t.enablePing,pingInterval:t.pingInterval,pingTimeout:t.pingTimeout,connections:e.connections})),new w(t)}export{k as CLOSE_CODES,f as Channel,l as ChannelError,O as ClientError,N as ConfigError,S as ContextManager,ee as ERROR_CODES,g as MessageError,C as MiddlewareExecutionError,u as MiddlewareRejectionError,p as StateError,w as Syncar,c as SyncarError,w as SyncarServer,A as TransportError,_ as ValidationError,y as WebSocketServerTransport,m as createContext,ae as createSyncarServer};
2
2
  //# sourceMappingURL=index.js.map