ocpp-ws-io 2.1.12 → 2.1.14

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 @@
1
- 'use strict';var l=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;this.sub.on&&this.sub.on("message",(r,i)=>{let n=this._handlers.get(r);n&&n(i);});}_handlers=new Map;async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){this._handlers.set(e,s),await this.sub.subscribe(e);}async unsubscribe(e){await this.sub.unsubscribe(e),this._handlers.delete(e);}async set(e,s,t){t?await this.pub.set(e,s,"EX",t):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mget(...e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){let r=[];t&&r.push("MAXLEN","~",t.toString()),r.push("*");for(let[i,n]of Object.entries(s))r.push(i,n);return await this.pub.xadd(e,...r)}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.pipeline();for(let r of e){let i=[];s&&i.push("MAXLEN","~",s.toString()),i.push("*");for(let[n,a]of Object.entries(r.args))i.push(n,a);t.xadd(r.stream,...i);}await t.exec();}async xread(e,s,t){let r=[];s&&r.push("COUNT",s),typeof t=="number"&&r.push("BLOCK",t),r.push("STREAMS"),e.forEach(a=>{r.push(a.key);}),e.forEach(a=>{r.push(a.id);});let n=await(t&&this.blocking?this.blocking:this.pub).xread(...r);return n?n.map(([a,o])=>({stream:a,messages:o.map(([u,h])=>{let m={};for(let d=0;d<h.length;d+=2)m[h[d]]=h[d+1];return {id:u,data:m}})})):null}async xlen(e){return await this.pub.xlen(e)}async disconnect(){this._handlers.clear();let e=async s=>{s.quit?await s.quit():s.disconnect&&await s.disconnect();};await Promise.all([e(this.pub),e(this.sub)]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.pipeline();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,"EX",i);await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}},g=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;}async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){await this.sub.subscribe(e,s);}async unsubscribe(e){await this.sub.unsubscribe(e);}async set(e,s,t){t?await this.pub.set(e,s,{EX:t}):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mGet(e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){return await this.pub.xAdd(e,"*",s,{TRIM:t?{strategy:"MAXLEN",strategyModifier:"~",threshold:t}:void 0})}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.multi();for(let r of e)t.xAdd(r.stream,"*",r.args,{TRIM:s?{strategy:"MAXLEN",strategyModifier:"~",threshold:s}:void 0});await t.exec();}async xread(e,s,t){let r={};s&&(r.COUNT=s),typeof t=="number"&&(r.BLOCK=t);let i=e.map(o=>({key:o.key,id:o.id})),a=await(t&&this.blocking?this.blocking:this.pub).xRead(i,r);return !a||a.length===0?null:a.map(o=>({stream:o.name,messages:o.messages.map(u=>({id:u.id,data:u.message}))}))}async xlen(e){return await this.pub.xLen(e)}async disconnect(){await Promise.all([this.pub.disconnect(),this.sub.disconnect()]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.multi();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,{EX:i});await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}};function p(c,e,s){return e.isOpen!==void 0&&typeof e.subscribe=="function"?new g(c,e,s):new l(c,e,s)}var b=class{_driver;_prefix;_streamMaxLen;_streamTtlSeconds;_presenceTtlSeconds;_handlers=new Map;_streamOffsets=new Map;_streams=new Set;_polling=false;_closed=false;_sequenceCounters=new Map;_unsubError;_unsubReconnect;_presenceCache=new Map;_driverPool;_nextPoolIndex;constructor(e){this._prefix=e.prefix??"ocpp-ws-io:",this._streamMaxLen=e.streamMaxLen??1e3,this._streamTtlSeconds=e.streamTtlSeconds??300,this._presenceTtlSeconds=e.presenceTtlSeconds??300,this._driver=p(e.pubClient,e.subClient,e.blockingClient);let s=e.poolSize??1;if(this._driverPool=[this._driver],this._nextPoolIndex=0,s>1&&e.driverFactory)for(let t=1;t<s;t++)this._driverPool.push(e.driverFactory());this._driver.onError&&(this._unsubError=this._driver.onError(t=>{console.error("[RedisAdapter] Redis error:",t.message);})),this._driver.onReconnect&&(this._unsubReconnect=this._driver.onReconnect(()=>{this._rehydratePresence().catch(()=>{});}));}_getPoolDriver(){if(this._driverPool.length===1)return this._driver;let e=this._driverPool[this._nextPoolIndex];return this._nextPoolIndex=(this._nextPoolIndex+1)%this._driverPool.length,e}async publish(e,s){let t=this._prefix+e,r=s;if(r&&typeof r=="object"&&e.startsWith("ocpp:node:")){let n=(this._sequenceCounters.get(e)??0)+1;this._sequenceCounters.set(e,n),r.__seq=n;}let i=JSON.stringify(s);if(e.startsWith("ocpp:node:")){let n=this._getPoolDriver();await n.xadd(t,{message:i},this._streamMaxLen),await n.expire(t,this._streamTtlSeconds).catch(()=>{});}else await this._getPoolDriver().publish(t,i);}async publishBatch(e){let s=[],t=[];for(let i of e){let n=this._prefix+i.channel,a=JSON.stringify(i.data);i.channel.startsWith("ocpp:node:")?s.push({stream:n,args:{message:a}}):t.push({channel:n,message:a});}let r=[];s.length>0&&r.push(this._getPoolDriver().xaddBatch(s,this._streamMaxLen)),t.length>0&&r.push(Promise.all(t.map(i=>this._getPoolDriver().publish(i.channel,i.message))).then(()=>{})),await Promise.all(r);}async subscribe(e,s){if(!this._handlers.has(e)){this._handlers.set(e,new Set);let t=this._prefix+e;e.startsWith("ocpp:node:")?this._streams.has(t)||(this._streams.add(t),this._streamOffsets.set(t,"0"),this._ensurePolling()):await this._driver.subscribe(t,r=>{this._handleMessage(e,r);});}this._handlers.get(e)?.add(s);}async unsubscribe(e){let s=this._prefix+e;this._streams.has(s)?(this._streams.delete(s),this._streamOffsets.delete(s)):await this._driver.unsubscribe(s),this._handlers.delete(e);}async disconnect(){this._closed=true,this._handlers.clear(),this._streams.clear(),this._presenceCache.clear(),this._sequenceCounters.clear(),this._unsubError&&this._unsubError(),this._unsubReconnect&&this._unsubReconnect(),await Promise.allSettled(this._driverPool.map(e=>e.disconnect()));}_handleMessage(e,s){let t=this._handlers.get(e);if(!t)return;let r;try{r=JSON.parse(s);}catch{r=s;}for(let i of t)try{i(r);}catch{}}_ensurePolling(){this._polling||this._closed||(this._polling=true,this._pollLoop().catch(()=>{this._polling=false;}));}async _pollLoop(){for(;!this._closed;){if(this._streams.size===0){await new Promise(s=>setTimeout(s,1e3));continue}let e=Array.from(this._streams).map(s=>({key:s,id:this._streamOffsets.get(s)||"$"}));try{let s=await this._driver.xread(e,void 0,1e3);if(s)for(let t of s){let r=t.stream.replace(this._prefix,"");for(let i of t.messages){this._streamOffsets.set(t.stream,i.id);let n=i.data.message;n&&this._handleMessage(r,n);}}}catch{await new Promise(t=>setTimeout(t,1e3));}}this._polling=false;}async setPresence(e,s,t){let r=`${this._prefix}presence:${e}`;this._presenceCache.set(e,{nodeId:s,ttl:t}),await this._driver.set(r,s,t);}async getPresence(e){let s=`${this._prefix}presence:${e}`;return await this._driver.get(s)}async getPresenceBatch(e){if(e.length===0)return [];let s=e.map(t=>`${this._prefix}presence:${t}`);return this._driver.mget?await this._driver.mget(s):await Promise.all(s.map(t=>this._driver.get(t)))}async removePresence(e){let s=`${this._prefix}presence:${e}`;await this._driver.del(s);}async metrics(){let e=0,s={};for(let t of this._streams)try{let r=await this._driver.xlen(t);e+=r,s[t]=r;}catch{s[t]=-1;}return {pendingMessages:e,activeStreams:this._streams.size,streamDetails:s}}async setPresenceBatch(e){if(e.length===0)return;let s=e.map(({identity:t,nodeId:r,ttl:i})=>{let n=`${this._prefix}presence:${t}`,a=i??this._presenceTtlSeconds;return this._presenceCache.set(t,{nodeId:r,ttl:a}),{key:n,value:r,ttlSeconds:a}});await this._driver.setPresenceBatch(s);}async _rehydratePresence(){if(this._presenceCache.size===0)return;let e=Array.from(this._presenceCache.entries()).map(([s,{nodeId:t,ttl:r}])=>({key:`${this._prefix}presence:${s}`,value:t,ttlSeconds:r}));await this._driver.setPresenceBatch(e);}};exports.RedisAdapter=b;//# sourceMappingURL=redis.js.map
2
- //# sourceMappingURL=redis.js.map
1
+ 'use strict';var l=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;this.sub.on&&this.sub.on("message",(r,i)=>{let n=this._handlers.get(r);n&&n(i);});}_handlers=new Map;async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){this._handlers.set(e,s),await this.sub.subscribe(e);}async unsubscribe(e){await this.sub.unsubscribe(e),this._handlers.delete(e);}async set(e,s,t){t?await this.pub.set(e,s,"EX",t):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mget(...e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){let r=[];t&&r.push("MAXLEN","~",t.toString()),r.push("*");for(let[i,n]of Object.entries(s))r.push(i,n);return await this.pub.xadd(e,...r)}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.pipeline();for(let r of e){let i=[];s&&i.push("MAXLEN","~",s.toString()),i.push("*");for(let[n,a]of Object.entries(r.args))i.push(n,a);t.xadd(r.stream,...i);}await t.exec();}async xread(e,s,t){let r=[];s&&r.push("COUNT",s),typeof t=="number"&&r.push("BLOCK",t),r.push("STREAMS"),e.forEach(a=>{r.push(a.key);}),e.forEach(a=>{r.push(a.id);});let n=await(t&&this.blocking?this.blocking:this.pub).xread(...r);return n?n.map(([a,o])=>({stream:a,messages:o.map(([u,h])=>{let m={};for(let d=0;d<h.length;d+=2)m[h[d]]=h[d+1];return {id:u,data:m}})})):null}async xlen(e){return await this.pub.xlen(e)}async disconnect(){this._handlers.clear();let e=async s=>{s.quit?await s.quit():s.disconnect&&await s.disconnect();};await Promise.all([e(this.pub),e(this.sub)]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.pipeline();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,"EX",i);await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}},g=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;}async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){await this.sub.subscribe(e,s);}async unsubscribe(e){await this.sub.unsubscribe(e);}async set(e,s,t){t?await this.pub.set(e,s,{EX:t}):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mGet(e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){return await this.pub.xAdd(e,"*",s,{TRIM:t?{strategy:"MAXLEN",strategyModifier:"~",threshold:t}:void 0})}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.multi();for(let r of e)t.xAdd(r.stream,"*",r.args,{TRIM:s?{strategy:"MAXLEN",strategyModifier:"~",threshold:s}:void 0});await t.exec();}async xread(e,s,t){let r={};s&&(r.COUNT=s),typeof t=="number"&&(r.BLOCK=t);let i=e.map(o=>({key:o.key,id:o.id})),a=await(t&&this.blocking?this.blocking:this.pub).xRead(i,r);return !a||a.length===0?null:a.map(o=>({stream:o.name,messages:o.messages.map(u=>({id:u.id,data:u.message}))}))}async xlen(e){return await this.pub.xLen(e)}async disconnect(){await Promise.all([this.pub.disconnect(),this.sub.disconnect()]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.multi();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,{EX:i});await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}};function p(c,e,s){return e.isOpen!==void 0&&typeof e.subscribe=="function"?new g(c,e,s):new l(c,e,s)}var b=class{_driver;_prefix;_streamMaxLen;_streamTtlSeconds;_presenceTtlSeconds;_handlers=new Map;_streamOffsets=new Map;_streams=new Set;_polling=false;_closed=false;_sequenceCounters=new Map;_unsubError;_unsubReconnect;_presenceCache=new Map;_driverPool;_nextPoolIndex;constructor(e){this._prefix=e.prefix??"ocpp-ws-io:",this._streamMaxLen=e.streamMaxLen??1e3,this._streamTtlSeconds=e.streamTtlSeconds??300,this._presenceTtlSeconds=e.presenceTtlSeconds??300,this._driver=p(e.pubClient,e.subClient,e.blockingClient);let s=e.poolSize??1;if(this._driverPool=[this._driver],this._nextPoolIndex=0,s>1&&e.driverFactory)for(let t=1;t<s;t++)this._driverPool.push(e.driverFactory());this._driver.onError&&(this._unsubError=this._driver.onError(t=>{console.error("[RedisAdapter] Redis error:",t.message);})),this._driver.onReconnect&&(this._unsubReconnect=this._driver.onReconnect(()=>{this._rehydratePresence().catch(()=>{});}));}_getPoolDriver(){if(this._driverPool.length===1)return this._driver;let e=this._driverPool[this._nextPoolIndex];return this._nextPoolIndex=(this._nextPoolIndex+1)%this._driverPool.length,e}async publish(e,s){let t=this._prefix+e,r=s;if(r&&typeof r=="object"&&e.startsWith("ocpp:node:")){let n=(this._sequenceCounters.get(e)??0)+1;this._sequenceCounters.set(e,n),r.__seq=n;}let i=JSON.stringify(s);if(e.startsWith("ocpp:node:")){let n=this._getPoolDriver();await n.xadd(t,{message:i},this._streamMaxLen),await n.expire(t,this._streamTtlSeconds).catch(()=>{});}else await this._getPoolDriver().publish(t,i);}async publishBatch(e){let s=[],t=[];for(let i of e){let n=this._prefix+i.channel,a=JSON.stringify(i.data);i.channel.startsWith("ocpp:node:")?s.push({stream:n,args:{message:a}}):t.push({channel:n,message:a});}let r=[];s.length>0&&r.push(this._getPoolDriver().xaddBatch(s,this._streamMaxLen)),t.length>0&&r.push(Promise.all(t.map(i=>this._getPoolDriver().publish(i.channel,i.message))).then(()=>{})),await Promise.all(r);}async subscribe(e,s){if(!this._handlers.has(e)){this._handlers.set(e,new Set);let t=this._prefix+e;e.startsWith("ocpp:node:")?this._streams.has(t)||(this._streams.add(t),this._streamOffsets.set(t,"0"),this._ensurePolling()):await this._driver.subscribe(t,r=>{this._handleMessage(e,r);});}this._handlers.get(e)?.add(s);}async unsubscribe(e){let s=this._prefix+e;this._streams.has(s)?(this._streams.delete(s),this._streamOffsets.delete(s)):await this._driver.unsubscribe(s),this._handlers.delete(e);}async disconnect(){this._closed=true,this._handlers.clear(),this._streams.clear(),this._presenceCache.clear(),this._sequenceCounters.clear(),this._unsubError&&this._unsubError(),this._unsubReconnect&&this._unsubReconnect(),await Promise.allSettled(this._driverPool.map(e=>e.disconnect()));}_handleMessage(e,s){let t=this._handlers.get(e);if(!t)return;let r;try{r=JSON.parse(s);}catch{r=s;}for(let i of t)try{i(r);}catch{}}_ensurePolling(){this._polling||this._closed||(this._polling=true,this._pollLoop().catch(()=>{this._polling=false;}));}async _pollLoop(){for(;!this._closed;){if(this._streams.size===0){await new Promise(s=>setTimeout(s,1e3));continue}let e=Array.from(this._streams).map(s=>({key:s,id:this._streamOffsets.get(s)||"$"}));try{let s=await this._driver.xread(e,void 0,1e3);if(s)for(let t of s){let r=t.stream.replace(this._prefix,"");for(let i of t.messages){this._streamOffsets.set(t.stream,i.id);let n=i.data.message;n&&this._handleMessage(r,n);}}}catch{await new Promise(t=>setTimeout(t,1e3));}}this._polling=false;}async setPresence(e,s,t){let r=`${this._prefix}presence:${e}`;this._presenceCache.set(e,{nodeId:s,ttl:t}),await this._driver.set(r,s,t);}async getPresence(e){let s=`${this._prefix}presence:${e}`;return await this._driver.get(s)}async getPresenceBatch(e){if(e.length===0)return [];let s=e.map(t=>`${this._prefix}presence:${t}`);return this._driver.mget?await this._driver.mget(s):await Promise.all(s.map(t=>this._driver.get(t)))}async removePresence(e){let s=`${this._prefix}presence:${e}`;await this._driver.del(s);}async metrics(){let e=0,s={};for(let t of this._streams)try{let r=await this._driver.xlen(t);e+=r,s[t]=r;}catch{s[t]=-1;}return {pendingMessages:e,activeStreams:this._streams.size,streamDetails:s}}async setPresenceBatch(e){if(e.length===0)return;let s=e.map(({identity:t,nodeId:r,ttl:i})=>{let n=`${this._prefix}presence:${t}`,a=i??this._presenceTtlSeconds;return this._presenceCache.set(t,{nodeId:r,ttl:a}),{key:n,value:r,ttlSeconds:a}});await this._driver.setPresenceBatch(s);}async _rehydratePresence(){if(this._presenceCache.size===0)return;let e=Array.from(this._presenceCache.entries()).map(([s,{nodeId:t,ttl:r}])=>({key:`${this._prefix}presence:${s}`,value:t,ttlSeconds:r}));await this._driver.setPresenceBatch(e);}};exports.RedisAdapter=b;
@@ -1,2 +1 @@
1
- var l=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;this.sub.on&&this.sub.on("message",(r,i)=>{let n=this._handlers.get(r);n&&n(i);});}_handlers=new Map;async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){this._handlers.set(e,s),await this.sub.subscribe(e);}async unsubscribe(e){await this.sub.unsubscribe(e),this._handlers.delete(e);}async set(e,s,t){t?await this.pub.set(e,s,"EX",t):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mget(...e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){let r=[];t&&r.push("MAXLEN","~",t.toString()),r.push("*");for(let[i,n]of Object.entries(s))r.push(i,n);return await this.pub.xadd(e,...r)}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.pipeline();for(let r of e){let i=[];s&&i.push("MAXLEN","~",s.toString()),i.push("*");for(let[n,a]of Object.entries(r.args))i.push(n,a);t.xadd(r.stream,...i);}await t.exec();}async xread(e,s,t){let r=[];s&&r.push("COUNT",s),typeof t=="number"&&r.push("BLOCK",t),r.push("STREAMS"),e.forEach(a=>{r.push(a.key);}),e.forEach(a=>{r.push(a.id);});let n=await(t&&this.blocking?this.blocking:this.pub).xread(...r);return n?n.map(([a,o])=>({stream:a,messages:o.map(([u,h])=>{let m={};for(let d=0;d<h.length;d+=2)m[h[d]]=h[d+1];return {id:u,data:m}})})):null}async xlen(e){return await this.pub.xlen(e)}async disconnect(){this._handlers.clear();let e=async s=>{s.quit?await s.quit():s.disconnect&&await s.disconnect();};await Promise.all([e(this.pub),e(this.sub)]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.pipeline();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,"EX",i);await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}},g=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;}async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){await this.sub.subscribe(e,s);}async unsubscribe(e){await this.sub.unsubscribe(e);}async set(e,s,t){t?await this.pub.set(e,s,{EX:t}):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mGet(e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){return await this.pub.xAdd(e,"*",s,{TRIM:t?{strategy:"MAXLEN",strategyModifier:"~",threshold:t}:void 0})}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.multi();for(let r of e)t.xAdd(r.stream,"*",r.args,{TRIM:s?{strategy:"MAXLEN",strategyModifier:"~",threshold:s}:void 0});await t.exec();}async xread(e,s,t){let r={};s&&(r.COUNT=s),typeof t=="number"&&(r.BLOCK=t);let i=e.map(o=>({key:o.key,id:o.id})),a=await(t&&this.blocking?this.blocking:this.pub).xRead(i,r);return !a||a.length===0?null:a.map(o=>({stream:o.name,messages:o.messages.map(u=>({id:u.id,data:u.message}))}))}async xlen(e){return await this.pub.xLen(e)}async disconnect(){await Promise.all([this.pub.disconnect(),this.sub.disconnect()]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.multi();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,{EX:i});await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}};function p(c,e,s){return e.isOpen!==void 0&&typeof e.subscribe=="function"?new g(c,e,s):new l(c,e,s)}var b=class{_driver;_prefix;_streamMaxLen;_streamTtlSeconds;_presenceTtlSeconds;_handlers=new Map;_streamOffsets=new Map;_streams=new Set;_polling=false;_closed=false;_sequenceCounters=new Map;_unsubError;_unsubReconnect;_presenceCache=new Map;_driverPool;_nextPoolIndex;constructor(e){this._prefix=e.prefix??"ocpp-ws-io:",this._streamMaxLen=e.streamMaxLen??1e3,this._streamTtlSeconds=e.streamTtlSeconds??300,this._presenceTtlSeconds=e.presenceTtlSeconds??300,this._driver=p(e.pubClient,e.subClient,e.blockingClient);let s=e.poolSize??1;if(this._driverPool=[this._driver],this._nextPoolIndex=0,s>1&&e.driverFactory)for(let t=1;t<s;t++)this._driverPool.push(e.driverFactory());this._driver.onError&&(this._unsubError=this._driver.onError(t=>{console.error("[RedisAdapter] Redis error:",t.message);})),this._driver.onReconnect&&(this._unsubReconnect=this._driver.onReconnect(()=>{this._rehydratePresence().catch(()=>{});}));}_getPoolDriver(){if(this._driverPool.length===1)return this._driver;let e=this._driverPool[this._nextPoolIndex];return this._nextPoolIndex=(this._nextPoolIndex+1)%this._driverPool.length,e}async publish(e,s){let t=this._prefix+e,r=s;if(r&&typeof r=="object"&&e.startsWith("ocpp:node:")){let n=(this._sequenceCounters.get(e)??0)+1;this._sequenceCounters.set(e,n),r.__seq=n;}let i=JSON.stringify(s);if(e.startsWith("ocpp:node:")){let n=this._getPoolDriver();await n.xadd(t,{message:i},this._streamMaxLen),await n.expire(t,this._streamTtlSeconds).catch(()=>{});}else await this._getPoolDriver().publish(t,i);}async publishBatch(e){let s=[],t=[];for(let i of e){let n=this._prefix+i.channel,a=JSON.stringify(i.data);i.channel.startsWith("ocpp:node:")?s.push({stream:n,args:{message:a}}):t.push({channel:n,message:a});}let r=[];s.length>0&&r.push(this._getPoolDriver().xaddBatch(s,this._streamMaxLen)),t.length>0&&r.push(Promise.all(t.map(i=>this._getPoolDriver().publish(i.channel,i.message))).then(()=>{})),await Promise.all(r);}async subscribe(e,s){if(!this._handlers.has(e)){this._handlers.set(e,new Set);let t=this._prefix+e;e.startsWith("ocpp:node:")?this._streams.has(t)||(this._streams.add(t),this._streamOffsets.set(t,"0"),this._ensurePolling()):await this._driver.subscribe(t,r=>{this._handleMessage(e,r);});}this._handlers.get(e)?.add(s);}async unsubscribe(e){let s=this._prefix+e;this._streams.has(s)?(this._streams.delete(s),this._streamOffsets.delete(s)):await this._driver.unsubscribe(s),this._handlers.delete(e);}async disconnect(){this._closed=true,this._handlers.clear(),this._streams.clear(),this._presenceCache.clear(),this._sequenceCounters.clear(),this._unsubError&&this._unsubError(),this._unsubReconnect&&this._unsubReconnect(),await Promise.allSettled(this._driverPool.map(e=>e.disconnect()));}_handleMessage(e,s){let t=this._handlers.get(e);if(!t)return;let r;try{r=JSON.parse(s);}catch{r=s;}for(let i of t)try{i(r);}catch{}}_ensurePolling(){this._polling||this._closed||(this._polling=true,this._pollLoop().catch(()=>{this._polling=false;}));}async _pollLoop(){for(;!this._closed;){if(this._streams.size===0){await new Promise(s=>setTimeout(s,1e3));continue}let e=Array.from(this._streams).map(s=>({key:s,id:this._streamOffsets.get(s)||"$"}));try{let s=await this._driver.xread(e,void 0,1e3);if(s)for(let t of s){let r=t.stream.replace(this._prefix,"");for(let i of t.messages){this._streamOffsets.set(t.stream,i.id);let n=i.data.message;n&&this._handleMessage(r,n);}}}catch{await new Promise(t=>setTimeout(t,1e3));}}this._polling=false;}async setPresence(e,s,t){let r=`${this._prefix}presence:${e}`;this._presenceCache.set(e,{nodeId:s,ttl:t}),await this._driver.set(r,s,t);}async getPresence(e){let s=`${this._prefix}presence:${e}`;return await this._driver.get(s)}async getPresenceBatch(e){if(e.length===0)return [];let s=e.map(t=>`${this._prefix}presence:${t}`);return this._driver.mget?await this._driver.mget(s):await Promise.all(s.map(t=>this._driver.get(t)))}async removePresence(e){let s=`${this._prefix}presence:${e}`;await this._driver.del(s);}async metrics(){let e=0,s={};for(let t of this._streams)try{let r=await this._driver.xlen(t);e+=r,s[t]=r;}catch{s[t]=-1;}return {pendingMessages:e,activeStreams:this._streams.size,streamDetails:s}}async setPresenceBatch(e){if(e.length===0)return;let s=e.map(({identity:t,nodeId:r,ttl:i})=>{let n=`${this._prefix}presence:${t}`,a=i??this._presenceTtlSeconds;return this._presenceCache.set(t,{nodeId:r,ttl:a}),{key:n,value:r,ttlSeconds:a}});await this._driver.setPresenceBatch(s);}async _rehydratePresence(){if(this._presenceCache.size===0)return;let e=Array.from(this._presenceCache.entries()).map(([s,{nodeId:t,ttl:r}])=>({key:`${this._prefix}presence:${s}`,value:t,ttlSeconds:r}));await this._driver.setPresenceBatch(e);}};export{b as RedisAdapter};//# sourceMappingURL=redis.mjs.map
2
- //# sourceMappingURL=redis.mjs.map
1
+ var l=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;this.sub.on&&this.sub.on("message",(r,i)=>{let n=this._handlers.get(r);n&&n(i);});}_handlers=new Map;async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){this._handlers.set(e,s),await this.sub.subscribe(e);}async unsubscribe(e){await this.sub.unsubscribe(e),this._handlers.delete(e);}async set(e,s,t){t?await this.pub.set(e,s,"EX",t):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mget(...e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){let r=[];t&&r.push("MAXLEN","~",t.toString()),r.push("*");for(let[i,n]of Object.entries(s))r.push(i,n);return await this.pub.xadd(e,...r)}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.pipeline();for(let r of e){let i=[];s&&i.push("MAXLEN","~",s.toString()),i.push("*");for(let[n,a]of Object.entries(r.args))i.push(n,a);t.xadd(r.stream,...i);}await t.exec();}async xread(e,s,t){let r=[];s&&r.push("COUNT",s),typeof t=="number"&&r.push("BLOCK",t),r.push("STREAMS"),e.forEach(a=>{r.push(a.key);}),e.forEach(a=>{r.push(a.id);});let n=await(t&&this.blocking?this.blocking:this.pub).xread(...r);return n?n.map(([a,o])=>({stream:a,messages:o.map(([u,h])=>{let m={};for(let d=0;d<h.length;d+=2)m[h[d]]=h[d+1];return {id:u,data:m}})})):null}async xlen(e){return await this.pub.xlen(e)}async disconnect(){this._handlers.clear();let e=async s=>{s.quit?await s.quit():s.disconnect&&await s.disconnect();};await Promise.all([e(this.pub),e(this.sub)]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.pipeline();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,"EX",i);await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}},g=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;}async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){await this.sub.subscribe(e,s);}async unsubscribe(e){await this.sub.unsubscribe(e);}async set(e,s,t){t?await this.pub.set(e,s,{EX:t}):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mGet(e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){return await this.pub.xAdd(e,"*",s,{TRIM:t?{strategy:"MAXLEN",strategyModifier:"~",threshold:t}:void 0})}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.multi();for(let r of e)t.xAdd(r.stream,"*",r.args,{TRIM:s?{strategy:"MAXLEN",strategyModifier:"~",threshold:s}:void 0});await t.exec();}async xread(e,s,t){let r={};s&&(r.COUNT=s),typeof t=="number"&&(r.BLOCK=t);let i=e.map(o=>({key:o.key,id:o.id})),a=await(t&&this.blocking?this.blocking:this.pub).xRead(i,r);return !a||a.length===0?null:a.map(o=>({stream:o.name,messages:o.messages.map(u=>({id:u.id,data:u.message}))}))}async xlen(e){return await this.pub.xLen(e)}async disconnect(){await Promise.all([this.pub.disconnect(),this.sub.disconnect()]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.multi();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,{EX:i});await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}};function p(c,e,s){return e.isOpen!==void 0&&typeof e.subscribe=="function"?new g(c,e,s):new l(c,e,s)}var b=class{_driver;_prefix;_streamMaxLen;_streamTtlSeconds;_presenceTtlSeconds;_handlers=new Map;_streamOffsets=new Map;_streams=new Set;_polling=false;_closed=false;_sequenceCounters=new Map;_unsubError;_unsubReconnect;_presenceCache=new Map;_driverPool;_nextPoolIndex;constructor(e){this._prefix=e.prefix??"ocpp-ws-io:",this._streamMaxLen=e.streamMaxLen??1e3,this._streamTtlSeconds=e.streamTtlSeconds??300,this._presenceTtlSeconds=e.presenceTtlSeconds??300,this._driver=p(e.pubClient,e.subClient,e.blockingClient);let s=e.poolSize??1;if(this._driverPool=[this._driver],this._nextPoolIndex=0,s>1&&e.driverFactory)for(let t=1;t<s;t++)this._driverPool.push(e.driverFactory());this._driver.onError&&(this._unsubError=this._driver.onError(t=>{console.error("[RedisAdapter] Redis error:",t.message);})),this._driver.onReconnect&&(this._unsubReconnect=this._driver.onReconnect(()=>{this._rehydratePresence().catch(()=>{});}));}_getPoolDriver(){if(this._driverPool.length===1)return this._driver;let e=this._driverPool[this._nextPoolIndex];return this._nextPoolIndex=(this._nextPoolIndex+1)%this._driverPool.length,e}async publish(e,s){let t=this._prefix+e,r=s;if(r&&typeof r=="object"&&e.startsWith("ocpp:node:")){let n=(this._sequenceCounters.get(e)??0)+1;this._sequenceCounters.set(e,n),r.__seq=n;}let i=JSON.stringify(s);if(e.startsWith("ocpp:node:")){let n=this._getPoolDriver();await n.xadd(t,{message:i},this._streamMaxLen),await n.expire(t,this._streamTtlSeconds).catch(()=>{});}else await this._getPoolDriver().publish(t,i);}async publishBatch(e){let s=[],t=[];for(let i of e){let n=this._prefix+i.channel,a=JSON.stringify(i.data);i.channel.startsWith("ocpp:node:")?s.push({stream:n,args:{message:a}}):t.push({channel:n,message:a});}let r=[];s.length>0&&r.push(this._getPoolDriver().xaddBatch(s,this._streamMaxLen)),t.length>0&&r.push(Promise.all(t.map(i=>this._getPoolDriver().publish(i.channel,i.message))).then(()=>{})),await Promise.all(r);}async subscribe(e,s){if(!this._handlers.has(e)){this._handlers.set(e,new Set);let t=this._prefix+e;e.startsWith("ocpp:node:")?this._streams.has(t)||(this._streams.add(t),this._streamOffsets.set(t,"0"),this._ensurePolling()):await this._driver.subscribe(t,r=>{this._handleMessage(e,r);});}this._handlers.get(e)?.add(s);}async unsubscribe(e){let s=this._prefix+e;this._streams.has(s)?(this._streams.delete(s),this._streamOffsets.delete(s)):await this._driver.unsubscribe(s),this._handlers.delete(e);}async disconnect(){this._closed=true,this._handlers.clear(),this._streams.clear(),this._presenceCache.clear(),this._sequenceCounters.clear(),this._unsubError&&this._unsubError(),this._unsubReconnect&&this._unsubReconnect(),await Promise.allSettled(this._driverPool.map(e=>e.disconnect()));}_handleMessage(e,s){let t=this._handlers.get(e);if(!t)return;let r;try{r=JSON.parse(s);}catch{r=s;}for(let i of t)try{i(r);}catch{}}_ensurePolling(){this._polling||this._closed||(this._polling=true,this._pollLoop().catch(()=>{this._polling=false;}));}async _pollLoop(){for(;!this._closed;){if(this._streams.size===0){await new Promise(s=>setTimeout(s,1e3));continue}let e=Array.from(this._streams).map(s=>({key:s,id:this._streamOffsets.get(s)||"$"}));try{let s=await this._driver.xread(e,void 0,1e3);if(s)for(let t of s){let r=t.stream.replace(this._prefix,"");for(let i of t.messages){this._streamOffsets.set(t.stream,i.id);let n=i.data.message;n&&this._handleMessage(r,n);}}}catch{await new Promise(t=>setTimeout(t,1e3));}}this._polling=false;}async setPresence(e,s,t){let r=`${this._prefix}presence:${e}`;this._presenceCache.set(e,{nodeId:s,ttl:t}),await this._driver.set(r,s,t);}async getPresence(e){let s=`${this._prefix}presence:${e}`;return await this._driver.get(s)}async getPresenceBatch(e){if(e.length===0)return [];let s=e.map(t=>`${this._prefix}presence:${t}`);return this._driver.mget?await this._driver.mget(s):await Promise.all(s.map(t=>this._driver.get(t)))}async removePresence(e){let s=`${this._prefix}presence:${e}`;await this._driver.del(s);}async metrics(){let e=0,s={};for(let t of this._streams)try{let r=await this._driver.xlen(t);e+=r,s[t]=r;}catch{s[t]=-1;}return {pendingMessages:e,activeStreams:this._streams.size,streamDetails:s}}async setPresenceBatch(e){if(e.length===0)return;let s=e.map(({identity:t,nodeId:r,ttl:i})=>{let n=`${this._prefix}presence:${t}`,a=i??this._presenceTtlSeconds;return this._presenceCache.set(t,{nodeId:r,ttl:a}),{key:n,value:r,ttlSeconds:a}});await this._driver.setPresenceBatch(s);}async _rehydratePresence(){if(this._presenceCache.size===0)return;let e=Array.from(this._presenceCache.entries()).map(([s,{nodeId:t,ttl:r}])=>({key:`${this._prefix}presence:${s}`,value:t,ttlSeconds:r}));await this._driver.setPresenceBatch(e);}};export{b as RedisAdapter};
package/dist/index.js CHANGED
@@ -4823,5 +4823,4 @@ ${d}\r
4823
4823
  \r
4824
4824
  ${r}`);}var {CONNECTING:F,OPEN:M,CLOSING:we,CLOSED:x}=ie,re=class s extends events.EventEmitter{static CONNECTING=F;static OPEN=M;static CLOSING=we;static CLOSED=x;_options;_state=x;_ws=null;_protocol;_identity;_handlers=new Map;_wildcardHandler=null;_pendingCalls=new Map;_pendingResponses=new Set;_callQueue;_pingTimer=null;_pongTimer=null;_closePromise=null;_reconnectAttempt=0;_reconnectTimer=null;_badMessageCount=0;_lastActivity=0;_outboundBuffer=[];_offlineQueue=[];_middleware;_validators=[];_strictProtocols=null;_handshake=null;_logger=q;_exchangeLog=false;_prettify=false;constructor(e){if(super(),this.setMaxListeners(0),!e.identity)throw new Error("identity is required");this._identity=e.identity,this._options={reconnect:true,maxReconnects:1/0,backoffMin:1e3,backoffMax:3e4,callTimeoutMs:3e4,pingIntervalMs:3e4,deferPingsOnActivity:false,callConcurrency:1,maxBadMessages:1/0,respondWithDetailedErrors:false,securityProfile:0,...e},this._callQueue=new ce(this._options.callConcurrency),this._middleware=new pe;let t=this._options.logging,i=de(t,{component:"OCPPClient",identity:this._identity});this._logger=i||q,t&&typeof t=="object"&&(this._exchangeLog=t.exchangeLog??false,this._prettify=t.prettify??false),this._options.logging&&this.use(Se(this._logger,this._identity,this._options.logging)),this._options.strictMode&&this._setupValidators();}_logExchange(e,t,i,n){if(!this._logger)return;let r=e==="OUT"?"\u2192":"\u2190",a=t==="CALLERROR"?"warn":this._exchangeLog?"info":"debug";if(this._exchangeLog&&this._prettify){let o=t==="CALLERROR"?"\u{1F6A8}":t==="CALLRESULT"?"\u2705":"\u26A1",d=i??t,p=`${o} ${this._identity} ${r} ${d} [${e}]`;this._logger?.[a]?.(p,{...n,direction:e});}else this._exchangeLog?this._logger?.[a]?.(`${t} ${r}`,{...n,direction:e}):this._logger?.[a]?.(`${t} ${r}`,n);}get log(){return this._logger||q}get identity(){return this._options.identity}get endpoint(){return this._options.endpoint}get options(){return this._options}get protocol(){return this._protocol}get state(){return this._state}get securityProfile(){return this._options.securityProfile??0}async connect(){if(this._state!==x)throw new Error(`Cannot connect: client is in state ${this._state}`);return this._state=F,this._reconnectAttempt=0,this._connectInternal()}async _connectInternal(){return new Promise((e,t)=>{let i=this._buildEndpoint(),n=this._buildWsOptions();this._logger?.debug?.("Connecting",{url:i}),this.emit("connecting",{url:i});let r=new ne__default.default(i,this._options.protocols??[],n);this._ws=r;let a=()=>{if(p(),this._state=M,this._protocol=r.protocol,this._badMessageCount=0,r.protocol&&this._reconnectAttempt===0&&(this._options.protocols=[r.protocol]),this._attachWebsocket(r),this._startPing(),this._flushOfflineQueue(),this._outboundBuffer.length>0){let m=this._outboundBuffer;this._outboundBuffer=[];for(let h of m)this._ws?.send(h);}this._logger?.info?.("Connected",{protocol:r.protocol});let l={response:r._req?.res};this.emit("open",l),e(l);},o=c=>{p(),this._state=x,this._logger?.error?.("Connection error",{error:c.message}),this.emit("error",c),t(c);},d=(c,l)=>{p(),this._state=x;let m=new H(`Unexpected HTTP response: ${l.statusCode}`,l.statusCode??0,l.headers);this._logger?.error?.("Unexpected HTTP response",{statusCode:l.statusCode}),this.emit("error",m),t(m);},p=()=>{r.removeListener("open",a),r.removeListener("error",o),r.removeListener("unexpected-response",d);};r.on("open",a),r.on("error",o),r.on("unexpected-response",d);})}async close(e={}){let{code:t=1e3,reason:i="",awaitPending:n=true,force:r=false}=e;return this._closePromise?this._closePromise:this._state===x?{code:1e3,reason:""}:(this._reconnectTimer&&(clearTimeout(this._reconnectTimer),this._reconnectTimer=null),this._closePromise=this._closeInternal(t,i,n,r),this._closePromise)}async _closeInternal(e,t,i,n){if(this._state=we,this._stopPing(),!n&&i){let r=Array.from(this._pendingCalls.values()).map(a=>new Promise(o=>{let d=a.resolve,p=a.reject;a.resolve=c=>{d(c),o();},a.reject=c=>{p(c),o();};}));r.length>0&&await Promise.allSettled(r);}return new Promise(r=>{if(!this._ws||this._ws.readyState===ne__default.default.CLOSED){this._state=x,this._cleanup();let o={code:e,reason:t};this.emit("close",o),r(o);return}let a=(o,d)=>{this._ws?.removeListener("close",a),this._state=x,this._cleanup();let p={code:o,reason:d.toString()};this.emit("close",p),r(p);};this._ws.on("close",a),n?this._ws.terminate():this._ws.close(Be(e)?e:1e3,t);})}handle(...e){if(e.length===1&&typeof e[0]=="function"){if(this._wildcardHandler)throw new Error("Wildcard handler is already registered.");this._wildcardHandler=e[0];}else if(e.length===2&&typeof e[0]=="string"&&typeof e[1]=="function"){if(this._handlers.has(e[0]))throw new Error(`Handler for '${e[0]}' is already registered.`);this._handlers.set(e[0],e[1]);}else if(e.length===3&&typeof e[0]=="string"&&typeof e[1]=="string"&&typeof e[2]=="function"){let t=`${e[0]}:${e[1]}`;if(this._handlers.has(t))throw new Error(`Handler for '${e[1]}' (protocol: ${e[0]}) is already registered.`);this._handlers.set(t,e[2]);}else throw new Error("Invalid arguments: provide (version, method, handler), (method, handler), or (wildcardHandler)")}removeHandler(e,t){e&&t?this._handlers.delete(`${e}:${t}`):e?this._handlers.delete(e):this._wildcardHandler=null;}removeAllHandlers(){this._handlers.clear(),this._wildcardHandler=null;}use(e){this._middleware.use(e);}async call(...e){let t,i,n;if(e.length>=3&&typeof e[0]=="string"&&typeof e[1]=="string"?(t=e[1],i=e[2]??{},n=e[3]??{}):(t=e[0],i=e[1]??{},n=e[2]??{}),this._state!==M){if(this._options.offlineQueue&&(this._state===x||this._state===F))return new Promise((a,o)=>{let d=this._options.offlineQueueMaxSize??100;this._offlineQueue.length>=d&&(this._offlineQueue.shift(),this._logger?.warn?.("Offline queue full \u2014 dropping oldest message",{method:t,queueSize:this._offlineQueue.length})),this._offlineQueue.push({method:t,params:i,options:n,resolve:a,reject:o}),this._logger?.debug?.("Call queued offline",{method:t,queueSize:this._offlineQueue.length});});throw new Error(`Cannot call: client is in state ${this._state}`)}let r=n.retries??0;return r>0?this._callWithRetry(t,i,n,r):this._callQueue.push(()=>this._sendCall(t,i,n))}async safeCall(...e){try{return await this.call(...e)}catch(t){if(t.name!=="TimeoutError"){let i={method:e.find(n=>typeof n=="string"&&!n.startsWith("ocpp")),error:t};this._logger?.warn?this._logger.warn("SafeCall failed",i):console.warn("SafeCall failed",i);}return}}async _sendCall(e,t,i){let n=i.idempotencyKey??le(),r=i.timeoutMs??this._options.callTimeoutMs,a={type:"outgoing_call",messageId:n,method:e,params:t,options:i},o;return await this._middleware.execute(a,async d=>{let p=d;this._options.strictMode&&this._protocol&&this._validateOutbound(p.method,p.params,"req");let c=[I.CALL,n,p.method,p.params],l=JSON.stringify(c);return o=await new Promise((m,h)=>{let E=setTimeout(()=>{this._pendingCalls.delete(n),h(new k(`Call to "${p.method}" timed out after ${r}ms`));},r),f=()=>{clearTimeout(E),this._pendingCalls.delete(n),h(new Error("Aborted"));};i.signal&&i.signal.addEventListener("abort",f),this._pendingCalls.set(n,{resolve:m,reject:h,timeoutHandle:E,abortHandler:i.signal?()=>i.signal?.removeEventListener("abort",f):void 0,method:p.method,sentAt:Date.now()}),this._ws?.readyState===ne__default.default.OPEN?this._safeSend(this._ws,l,A=>{A&&(clearTimeout(E),this._pendingCalls.delete(n),h(A));}):this._state===F?(this._logger?.debug?.("Buffering call",{method:p.method}),this._outboundBuffer.push(l)):(clearTimeout(E),this._pendingCalls.delete(n),h(new Error(`WebSocket is not open (state: ${this._state})`)));}),o}),o}sendRaw(e){if(this._state===M&&this._ws)this._ws.send(e);else if(this._state===F)this._outboundBuffer.push(e);else throw new Error("Cannot send: client is not connected")}reconfigure(e){Object.assign(this._options,e),e.callConcurrency!==void 0&&this._callQueue.setConcurrency(e.callConcurrency),(e.strictMode!==void 0||e.strictModeValidators!==void 0)&&this._setupValidators(),e.pingIntervalMs!==void 0&&(this._stopPing(),this._state===M&&this._startPing());}_attachWebsocket(e){e.on("message",t=>this._onMessage(t)),e.on("close",(t,i)=>this._onClose(t,i)),e.on("error",t=>this.emit("error",t)),e.on("ping",()=>{this._recordActivity(),this.emit("ping");}),e.on("pong",()=>{this._pongTimer&&(clearTimeout(this._pongTimer),this._pongTimer=null),this._recordActivity(),this.emit("pong");});}_onMessage(e,t){this._recordActivity();let i;try{if(t!==void 0?i=t:i=JSON.parse(e),!Array.isArray(i))throw new Error("Message is not an array")}catch(d){this._onBadMessage(typeof e=="string"?e:e.toString(),d);return}let n=i[0],r=i[1];if(typeof r!="string"){this._onBadMessage(typeof e=="string"?e:e.toString(),new _(`Invalid MessageId type: ${typeof r} (expected string)`));return}if(n===I.CALL&&i.length<4||n===I.CALLRESULT&&i.length<3||n===I.CALLERROR&&i.length<5){this._onBadMessage(JSON.stringify(i),new _(`Missing payload elements for message type ${n}`));return}let a=n===I.CALLERROR?4:n===I.CALL?3:2,o=i[a];if(typeof o!="object"||o===null||Array.isArray(o)){this._onBadMessage(JSON.stringify(i),new _(`Payload must be a JSON object, got ${o===null?"null":Array.isArray(o)?"array":typeof o}`));return}switch(n){case I.CALL:this._handleIncomingCall(i);break;case I.CALLRESULT:this._handleCallResult(i);break;case I.CALLERROR:this._handleCallError(i);break;default:this._onBadMessage(JSON.stringify(i),new _(`Unknown message type: ${n}`));}}async _handleIncomingCall(e){let[,t,i,n]=e,r={type:"incoming_call",messageId:t,method:i,params:n,protocol:this._protocol};try{await this._middleware.execute(r,async a=>{let o=a,d=[I.CALL,o.messageId,o.method,o.params];if(this.emit("call",d),this._state===M)try{if(this._pendingResponses.has(o.messageId))throw R("RpcFrameworkError",`Already processing call with ID: ${o.messageId}`);let p=(this._protocol?this._handlers.get(`${this._protocol}:${o.method}`):void 0)??this._handlers.get(o.method);if(!p&&!this._wildcardHandler)throw new j(`Method "${o.method}" not implemented`);this._options.strictMode&&this._protocol&&this._validateInbound(o.method,o.params,"req"),this._pendingResponses.add(o.messageId);let c=new AbortController,l={messageId:o.messageId,method:o.method,protocol:this._protocol,params:o.params,signal:c.signal},m;if(p?m=await p(l):this._wildcardHandler&&(m=await this._wildcardHandler(o.method,l)),this._pendingResponses.delete(o.messageId),m===xe)return;this._options.strictMode&&this._protocol&&this._validateOutbound(o.method,m,"conf");let h=[I.CALLRESULT,o.messageId,m];return this._ws?.send(JSON.stringify(h)),this.emit("callResult",h),m}catch(p){this._pendingResponses.delete(o.messageId);let c=p instanceof v||p.rpcErrorCode?p:R("InternalError",p.message),l=this._options.respondWithDetailedErrors?Ie(p):{},m=[I.CALLERROR,o.messageId,c.rpcErrorCode,c.rpcErrorMessage||p.message||"",l];throw this._ws?.send(JSON.stringify(m)),this.emit("callError",m),p}});}catch{}}async _handleCallResult(e){let[,t,i]=e;if(!this._pendingCalls.has(t)){this._logger?.warn?.("Received CallResult for unknown messageId",{messageId:t});return}let n=this._pendingCalls.get(t),r={type:"incoming_result",messageId:t,payload:i,method:n.method};await this._middleware.execute(r,async a=>{let o=a,d=this._pendingCalls.get(o.messageId);d&&(this.emit("callResult",e),clearTimeout(d.timeoutHandle),d.abortHandler,this._pendingCalls.delete(o.messageId),d.resolve(o.payload));});}async _handleCallError(e){let[,t]=e,i=this._pendingCalls.get(t);if(!i){this._logger?.warn?.("Received CallError for unknown messageId",{messageId:t});return}let n={type:"incoming_error",messageId:t,error:e,method:i.method};await this._middleware.execute(n,async r=>{let a=r,[,,o,d,p]=a.error,c=this._pendingCalls.get(a.messageId);if(!c)return;this.emit("callError",a.error),clearTimeout(c.timeoutHandle),this._pendingCalls.delete(a.messageId);let l=R(o,d,p);c.reject(l);});}_onBadMessage(e,t){this._badMessageCount++,this._logger?.warn?.("Bad message",{error:t.message,count:this._badMessageCount}),this.emit("badMessage",{message:e,error:t});let i=e.match(/^\s*\[\s*2\s*,\s*"([^"]+)"/);if(i?.[1]&&this._ws){let n=[I.CALLERROR,i[1],"FormatViolation",t.message||"Invalid message format",{}];this._ws.send(JSON.stringify(n)),this.emit("callError",n);}this._badMessageCount>=this._options.maxBadMessages&&this.close({code:1002,reason:"Too many bad messages"}).catch(()=>{});}_rejectPendingCalls(e){for(let[,t]of this._pendingCalls)clearTimeout(t.timeoutHandle),t.reject(new Error(e));this._pendingCalls.clear(),this._pendingResponses.clear();}_onClose(e,t){this._stopPing();let i=t.toString();this._rejectPendingCalls(`Connection closed (${e}: ${i})`),this._state!==we?(this._logger?.info?.("Disconnected",{code:e,reason:i}),this.emit("disconnect",{code:e,reason:i}),this._options.reconnect&&this._reconnectAttempt<this._options.maxReconnects?this._scheduleReconnect():(this._state=x,this.emit("close",{code:e,reason:i}))):this._state=x;}static _INTOLERABLE_ERRORS=new Set(["Maximum redirects exceeded","Server sent no subprotocol","Server sent an invalid subprotocol","Server sent a subprotocol but none was requested","Invalid Sec-WebSocket-Accept header"]);_scheduleReconnect(){this._reconnectAttempt++,this._state=F;let e=this._options.backoffMin,t=this._options.backoffMax,i=Math.min(t,e*2**(this._reconnectAttempt-1)*(.5+Math.random()*.5));this._logger?.warn?.("Reconnecting",{attempt:this._reconnectAttempt,delayMs:Math.round(i)}),this.emit("reconnect",{attempt:this._reconnectAttempt,delay:i}),this._reconnectTimer=setTimeout(async()=>{this._reconnectTimer=null;try{await this._connectInternal();}catch(n){let r=n instanceof Error?n.message:"";if(s._INTOLERABLE_ERRORS.has(r)){this._logger?.error?.("Intolerable error \u2014 stopping reconnection",{error:r}),this._state=x,this.emit("close",{code:1001,reason:r});return}this._reconnectAttempt<this._options.maxReconnects&&this._options.reconnect?this._scheduleReconnect():(this._state=x,this.emit("close",{code:1001,reason:"Max reconnection attempts exhausted"}));}},i);}_flushOfflineQueue(){if(this._offlineQueue.length===0)return;let e=this._offlineQueue.splice(0,this._offlineQueue.length);this._logger?.info?.("Flushing offline queue",{count:e.length});for(let t of e)this._callQueue.push(()=>this._sendCall(t.method,t.params,t.options)).then(t.resolve).catch(t.reject);}async _callWithRetry(e,t,i,n){let r=i.retryDelayMs??1e3,a=i.retryMaxDelayMs??3e4;for(let o=0;o<=n;o++)try{return await this._callQueue.push(()=>this._sendCall(e,t,i))}catch(d){if(o===n||!(d instanceof k))throw d;let p=Math.min(a,r*2**o),c=Math.random()*p;this._logger?.warn?.("Call retry",{method:e,attempt:o+1,maxRetries:n,delayMs:Math.round(c)}),await new Promise(l=>setTimeout(l,c));}throw new Error("Retry exhausted")}static _BACKPRESSURE_THRESHOLD=512*1024;_safeSend(e,t,i){if(!e||e.readyState!==ne__default.default.OPEN){i?.(new Error("WebSocket is not open"));return}if(e.bufferedAmount>s._BACKPRESSURE_THRESHOLD){this._logger?.warn?.("Backpressure \u2014 pausing send",{identity:this._identity,bufferedAmount:e.bufferedAmount,threshold:s._BACKPRESSURE_THRESHOLD}),this.emit("backpressure",{identity:this._identity,bufferedAmount:e.bufferedAmount});let n=0,r=setInterval(()=>{if(n+=50,!e||e.readyState!==ne__default.default.OPEN){clearInterval(r),i?.(new Error("WebSocket closed during backpressure wait"));return}(e.bufferedAmount<=s._BACKPRESSURE_THRESHOLD||n>=1e4)&&(clearInterval(r),e.send(t,i));},50);}else e.send(t,i);}_startPing(){if(this._options.pingIntervalMs<=0)return;let e=this._options.pongTimeoutMs??this._options.pingIntervalMs+5e3,t=()=>{if(this._state!==M||!this._ws)return;if(this._options.deferPingsOnActivity){let r=Date.now()-this._lastActivity;if(r<this._options.pingIntervalMs){this._pingTimer=setTimeout(t,this._options.pingIntervalMs-r);return}}this._ws.ping(),e>0&&(this._pongTimer=setTimeout(()=>{this._logger?.warn?.("Pong timeout \u2014 terminating dead connection",{identity:this._identity,timeoutMs:e}),this._ws?.terminate();},e));let n=this._options.pingIntervalMs*(.75+Math.random()*.5);this._pingTimer=setTimeout(t,n);},i=this._options.pingIntervalMs*(.75+Math.random()*.5);this._pingTimer=setTimeout(t,i);}_stopPing(){this._pingTimer&&(clearTimeout(this._pingTimer),this._pingTimer=null);}_recordActivity(){this._lastActivity=Date.now();}_setupValidators(){this._options.strictModeValidators?this._validators=this._options.strictModeValidators:this._validators=De(),Array.isArray(this._options.strictMode)?this._strictProtocols=this._options.strictMode:this._strictProtocols=null;}_validateOutbound(e,t,i){let n=this._findValidator();if(!n||this._options.strictModeMethods&&!this._options.strictModeMethods.includes(e))return;let r=`urn:${e}.${i}`;try{n.validate(r,t);}catch(a){throw this.emit("strictValidationFailure",{message:t,error:a}),a}}_validateInbound(e,t,i){let n=this._findValidator();if(!n||this._options.strictModeMethods&&!this._options.strictModeMethods.includes(e))return;let r=`urn:${e}.${i}`;try{n.validate(r,t);}catch(a){throw this.emit("strictValidationFailure",{message:t,error:a}),a}}_findValidator(){return !this._protocol||this._strictProtocols&&!this._strictProtocols.includes(this._protocol)?null:this._validators.find(e=>e.subprotocol===this._protocol)??null}_buildEndpoint(){let e=this._options.endpoint;if(e.endsWith("/")||(e+="/"),e+=encodeURIComponent(this._identity),this._options.query){let t=new URLSearchParams(this._options.query);e+=(e.includes("?")?"&":"?")+t.toString();}return e}_buildWsOptions(){let e={headers:{...this._options.headers,"User-Agent":Ee()}},t=this._options.securityProfile??0;if((t===1||t===2)&&this._options.password){let n=Buffer.from(`${this._identity}:${this._options.password.toString()}`).toString("base64");e?.headers&&(e.headers.Authorization=`Basic ${n}`);}if(t===2||t===3){let n=this._options.tls??{};n.ca&&(e.ca=n.ca),n.rejectUnauthorized!==void 0&&(e.rejectUnauthorized=n.rejectUnauthorized),t===3&&(n.cert&&(e.cert=n.cert),n.key&&(e.key=n.key),n.passphrase&&(e.passphrase=n.passphrase));}let i=this._options.compression;return i&&(e.perMessageDeflate=i===true?{zlibDeflateOptions:{level:6,memLevel:8},zlibInflateOptions:{},clientNoContextTakeover:true,serverNoContextTakeover:true}:{zlibDeflateOptions:{level:i.level??6,memLevel:i.memLevel??8},zlibInflateOptions:{},clientNoContextTakeover:i.clientNoContextTakeover??true,serverNoContextTakeover:i.serverNoContextTakeover??true}),e}_cleanup(){this._stopPing(),this._pongTimer&&(clearTimeout(this._pongTimer),this._pongTimer=null),this._reconnectTimer&&(clearTimeout(this._reconnectTimer),this._reconnectTimer=null),this._closePromise=null,this._ws=null;}};var ae=class extends Map{_maxSize;constructor(e){if(super(),e<1)throw new RangeError("LRUMap maxSize must be >= 1");this._maxSize=e;}get maxSize(){return this._maxSize}set(e,t){if(this.has(e)&&this.delete(e),super.set(e,t),this.size>this._maxSize){let i=this.keys().next().value;i!==void 0&&this.delete(i);}return this}get(e){if(!this.has(e))return;let t=super.get(e);return this.delete(e),super.set(e,t),t}};async function Je(s,e){let t=-1,i=async(n,r)=>{if(r&&(e.state={...e.state||{},...r||{}}),n<=t)throw new Error("next() called multiple times in middleware");t=n;let a=s[n];n!==s.length&&a&&(e.next=i.bind(null,n+1),await a(e));};await i(0);}var $=class extends events.EventEmitter{patterns;middlewares;authCallback=null;_routeCORS;_routeConfig;_regexPatterns=[];constructor(e,t){super(),this.patterns=[],this.middlewares=t??[],e?.length&&this.route(...e);}route(...e){this.patterns.push(...e);for(let t of e)typeof t!="string"&&this._regexPatterns.push({regex:t,paramNames:[]});return this}use(...e){return this.middlewares.push(...e),this}cors(e){return this._routeCORS=e,this}config(e){return this._routeConfig=e,this}auth(e){return this.authCallback=e,this}handle(...e){return this.on("client",t=>{let i=e[e.length-1],n=[...e];typeof i=="function"&&(n[n.length-1]=(...r)=>{let a=r.length-1,o=r[a];return o&&typeof o=="object"&&Object.defineProperty(o,"client",{value:t,enumerable:true,configurable:true}),i(...r)}),t.handle(...n);}),this}};function kt(...s){return new $(s)}function Xe(s){return s.split(".").reduce((e,t)=>(e<<8)+parseInt(t,10),0)>>>0}function Qe(s,e){let t=s;t.startsWith("::ffff:")&&(t=t.substring(7));for(let i of e){if(i===t)return true;if(i.includes("/")){let n=i.split("/"),r=n[0],a=n[1];if(!r||!a)continue;let o=parseInt(a,10);if(net.isIPv4(t)&&net.isIPv4(r)){let d=Xe(t),p=Xe(r),c=o===0?0:-1<<32-o>>>0;if((d&c)===(p&c))return true}}}return false}function _e(s,e){if(e.allowedIPs&&e.allowedIPs.length>0){let t=s.socket.remoteAddress;if(!t||!Qe(t,e.allowedIPs))return {allowed:false,reason:"IP address not allowed"}}if(e.allowedSchemes&&e.allowedSchemes.length>0){let t=s.socket instanceof tls.TLSSocket?"wss":"ws",i=s.headers["x-forwarded-proto"];if(typeof i=="string"&&(t=i==="https"||i==="wss"?"wss":"ws"),!e.allowedSchemes.includes(t))return {allowed:false,reason:"Protocol scheme not allowed"}}if(e.allowedOrigins&&e.allowedOrigins.length>0){let t=s.headers.origin;if(typeof t=="string"&&!e.allowedOrigins.includes(t))return {allowed:false,reason:"Origin not allowed"}}return {allowed:true}}var U=class{children=new Map;paramChild=null;paramName="";wildcardChild=null;routers=[]};function Ye(s){return s.replace(/\/+/g,"/").replace(/^\/|\/$/g,"").split("/").filter(Boolean)}var fe=class{root=new U;_frozen=false;_size=0;get size(){return this._size}insert(e,t){this._frozen&&(this._frozen=false);let i=Ye(e),n=this.root;for(let r of i)if(r==="*"){n.wildcardChild||(n.wildcardChild=new U),n=n.wildcardChild;break}else if(r.startsWith(":")){let a=r.slice(1);if(!n.paramChild)n.paramChild=new U,n.paramChild.paramName=a;else if(n.paramChild.paramName!==a)throw new Error(`Route conflict: param ":${a}" conflicts with existing ":${n.paramChild.paramName}" at the same position in pattern "${e}"`);n=n.paramChild;}else {let a=r.toLowerCase(),o=n.children.get(a);o||(o=new U,n.children.set(a,o)),n=o;}n.routers.push(t),this._size++;}match(e){let t=Ye(e),i=[];if(this._matchRecursive(this.root,t,0,{},i),i.length===0)return null;let n=i[0].params,r=[];for(let a of i)r.push(...a.routers);return {routers:r,params:n}}_matchRecursive(e,t,i,n,r){if(i===t.length){e.routers.length>0&&r.push({routers:[...e.routers],params:{...n}});return}let a=t[i],o=a.toLowerCase(),d=e.children.get(o);if(d&&this._matchRecursive(d,t,i+1,n,r),e.paramChild){let p={...n};p[e.paramChild.paramName]=decodeURIComponent(a),this._matchRecursive(e.paramChild,t,i+1,p,r);}e.wildcardChild&&e.wildcardChild.routers.length>0&&r.push({routers:[...e.wildcardChild.routers],params:{...n}});}freeze(){this._frozen||(this._freezeNode(this.root),this._frozen=true);}_freezeNode(e){Object.freeze(e.routers),e.paramChild&&this._freezeNode(e.paramChild),e.wildcardChild&&this._freezeNode(e.wildcardChild);for(let t of e.children.values())this._freezeNode(t);}get frozen(){return this._frozen}};var oe=class extends re{_serverSession;_serverHandshake;constructor(e,t){super(e),this._serverSession=t.session,this._serverHandshake=t.handshake,this._adaptiveMultiplier=t.adaptiveMultiplier??null,this._workerPool=t.workerPool??null,this._state=ie.OPEN,this._identity=this._options.identity,this._ws=t.ws,this._protocol=t.protocol??t.ws.protocol,this._attachServerWebsocket(t.ws),this._startPing();}_rateLimits={};_adaptiveMultiplier=null;_workerPool=null;_checkRateLimit(e){let t=this._options.rateLimit;if(!t)return true;let i=Date.now(),n=(r,a,o)=>{let d=this._rateLimits[r];if(!d)d={tokens:a,lastRefill:i},this._rateLimits[r]=d;else {let p=i-d.lastRefill,c=this._adaptiveMultiplier?.()??1,l=a/o*c,m=p*l;m>0&&(d.tokens=Math.min(a,d.tokens+m),d.lastRefill=i);}return d.tokens>=1?(d.tokens-=1,true):false};if(!n("global",t.limit,t.windowMs))return false;if(e&&t.methods?.[e]){let r=t.methods[e];if(!n(`method:${e}`,r.limit,r.windowMs))return false}return true}_attachServerWebsocket(e){e.on("message",t=>{this._recordActivity();let i=this._options.rateLimit;if(i){let n,r;if(i.methods)try{r=JSON.parse(t),Array.isArray(r)&&r[0]===2&&(n=r[2]);}catch{}if(!this._checkRateLimit(n)){this._handleRateLimitExceeded(r||t.toString());return}if(r!==void 0){this._onMessage(t,r);return}}if(this._workerPool){let n=t;this._workerPool.parse(n).then(r=>{this._onMessage(t,r.message);}).catch(()=>{this._onMessage(t);});return}this._onMessage(t);}),e.on("close",(t,i)=>this._onClose(t,i)),e.on("error",t=>{this.listenerCount("error")>0?this.emit("error",t):this._logger?.debug?.("WebSocket error (unhandled by client listener)",{error:t.message});}),e.on("ping",()=>{this._recordActivity(),this.emit("ping");}),e.on("pong",()=>{this._pongTimer&&(clearTimeout(this._pongTimer),this._pongTimer=null),this._recordActivity(),this.emit("pong");});}_handleRateLimitExceeded(e){let i=this._options.rateLimit.onLimitExceeded||"ignore";if(i==="disconnect")this._logger?.warn?.("Rate limit exceeded \u2014 disconnecting client",{identity:this.identity}),this._ws?.terminate();else if(typeof i=="function")try{let n=i(this,e);n instanceof Promise&&n.catch(r=>{this._logger?.error?.("Error in custom onLimitExceeded handler",{identity:this.identity,error:r});});}catch(n){this._logger?.error?.("Error in custom onLimitExceeded handler",{identity:this.identity,error:n});}else this._logger?.debug?.("Rate limit exceeded \u2014 ignoring message",{identity:this.identity});}get session(){return this._serverSession}get handshake(){return this._serverHandshake}async connect(){throw new Error("Cannot connect from server client \u2014 connection is managed by the server")}close(e={}){return super.close(e)}};var Le=class{_workers=[];_nextWorker=0;_taskId=0;_pending=new Map;_maxQueueSize;_terminated=false;constructor(e={}){let t=e.poolSize??Math.max(2,os.cpus().length-2);this._maxQueueSize=e.maxQueueSize??1e4;let i=path.resolve(__dirname,"parse-worker.js");for(let n=0;n<t;n++){let r=new worker_threads.Worker(i);r.on("message",a=>{let o=this._pending.get(a.id);o&&(this._pending.delete(a.id),a.error?o.reject(new Error(a.error)):o.resolve({message:a.message,validationError:a.validationError}));}),r.on("error",a=>{console.error(`[WorkerPool] Worker ${n} error:`,a.message);}),this._workers.push(r);}}get size(){return this._workers.length}get pendingTasks(){return this._pending.size}parse(e,t){return this._terminated?Promise.reject(new Error("WorkerPool has been shut down")):this._pending.size>=this._maxQueueSize?Promise.reject(new Error(`WorkerPool queue full (${this._maxQueueSize} pending tasks)`)):new Promise((i,n)=>{let r=this._taskId++;this._pending.set(r,{resolve:i,reject:n});let a=this._workers[this._nextWorker%this._workers.length];this._nextWorker=(this._nextWorker+1)%this._workers.length,a.postMessage({id:r,buffer:e,schemaInfo:t});})}async shutdown(){if(this._terminated)return;this._terminated=true;for(let[t,i]of this._pending)i.reject(new Error("WorkerPool shutting down")),this._pending.delete(t);let e=this._workers.map(async t=>{try{await Promise.race([t.terminate(),new Promise(i=>setTimeout(i,5e3))]);}catch{}});await Promise.allSettled(e),this._workers=[];}};function Ze(s={}){try{return new Le(s)}catch{return null}}var Ae=class extends events.EventEmitter{_options;_trie=new fe;_globalMiddlewareRouters=[];_regexRouters=[];_clients=new Set;_clientsByIdentity=new Map;_httpServers=new Set;_wss=null;_state="OPEN";_adapter=null;_httpServerAbortControllers=new Set;_logger=null;_globalCORS;_connectionBuckets=new Map;_adaptiveLimiter=null;_plugins=[];_workerPool=null;_nodeId=le();_sessions;_gcInterval=null;_sessionTimeoutMs;constructor(e={}){if(super(),this.setMaxListeners(0),e.strictMode&&!e.protocols?.length)throw new Error("strictMode requires protocols to be specified (e.g. protocols: ['ocpp1.6'])");this._options={securityProfile:0,callTimeoutMs:3e4,pingIntervalMs:3e4,deferPingsOnActivity:false,callConcurrency:1,maxBadMessages:1/0,respondWithDetailedErrors:false,handshakeTimeoutMs:3e4,sessionTtlMs:7200*1e3,...e},this._sessionTimeoutMs=this._options.sessionTtlMs;let t=this._options.maxSessions??5e4;this._sessions=new ae(t),this._wss=new ne.WebSocketServer({noServer:true,maxPayload:this._options.maxPayloadBytes??65536,perMessageDeflate:this._buildCompressionConfig()}),this._gcInterval=setInterval(()=>{let r=Date.now();for(let[a,o]of this._sessions.entries())r-o.lastActive>this._sessionTimeoutMs&&this._sessions.delete(a);},60*1e3).unref(),this._logger=de(this._options.logging,{component:"OCPPServer"});let i=this._options.rateLimit;i?.adaptive&&(this._adaptiveLimiter=new W({cpuThresholdPercent:i.cpuThresholdPercent,memThresholdPercent:i.memThresholdPercent,cooldownMs:i.cooldownMs}),this._adaptiveLimiter.on("adapted",r=>{this._logger?.info?.("Adaptive rate limit adjusted",r),this.emit("rateLimit:adapted",r);}),this._adaptiveLimiter.start());let n=this._options.workerThreads;if(n){let r=typeof n=="object"?n:{};this._workerPool=Ze(r),this._workerPool&&this._logger?.info?.("Worker thread pool initialized",{poolSize:this._workerPool.size});}}get log(){return this._logger||q}get clients(){return this._clients}get state(){return this._state}stats(){let e=0;if(this._wss)for(let t of this._wss.clients)e+=t.bufferedAmount;return {connectedClients:this._clients.size,activeSessions:this._sessions.size,uptimeSeconds:process.uptime(),pid:process.pid,memoryUsage:process.memoryUsage(),cpuUsage:process.cpuUsage(),webSockets:this._wss?{total:this._wss.clients.size,bufferedAmount:e}:void 0}}async adapterMetrics(){if(!this._adapter||!this._adapter.metrics)return null;try{return await this._adapter.metrics()}catch(e){return this._logger?.warn?.("Failed to fetch adapter metrics",{error:e}),null}}getLocalClient(e){return this._clientsByIdentity.get(e)}hasLocalClient(e){return this.getLocalClient(e)!==void 0}async isClientConnected(e){if(this.hasLocalClient(e))return true;if(this._adapter?.getPresence){let t=await this._adapter.getPresence(e);return t!=null}return false}cors(e){return this._globalCORS=e,this}route(...e){let t=new $;return t.route(...e),this._registerRouter(t),t}attachRouters(...e){for(let t of e)this._registerRouter(t);return this}plugin(...e){for(let t of e)if(this._plugins.push(t),this._logger?.info?.("Plugin registered",{name:t.name}),t.onInit){let i=t.onInit(this);i instanceof Promise&&i.catch(n=>{this._logger?.error?.("Plugin onInit error",{name:t.name,error:n.message});});}return this}use(...e){let t=new $;return t.use(...e),this._registerRouter(t),t}auth(e){let t=new $;return t.auth(e),this._registerRouter(t),t}_registerRouter(e){let t=e.patterns.filter(n=>typeof n=="string"),i=e._regexPatterns.length>0;if(t.length===0&&!i)this._globalMiddlewareRouters.push(e);else {for(let n of t)this._trie.insert(n,e);i&&this._regexRouters.push(e);}}async listen(e=0,t,i){let n;if(i?.server)n=i.server;else {let a=this._options.securityProfile??0;if(a===2||a===3){let o=this._options.tls??{},d={};o.cert&&(d.cert=o.cert),o.key&&(d.key=o.key),o.ca&&(d.ca=o.ca),o.passphrase&&(d.passphrase=o.passphrase),a===3&&(d.requestCert=true,d.rejectUnauthorized=o.rejectUnauthorized??true),n=https.createServer(d);}else n=qt.createServer();}this._state==="CLOSED"&&(this._state="OPEN");let r=(a,o,d)=>{this._handleUpgrade(a,o,d).catch(p=>{o.destroyed||o.destroy(),this._logger?.error?.("Upgrade error",{error:p.message}),this.emit("upgradeError",{error:p,socket:o});});};if(n.on("upgrade",r),this._httpServers.add(n),this._options.healthEndpoint&&n.on("request",(a,o)=>{let d=a.url??"";if(d==="/health"){let p=this.stats(),c=JSON.stringify({status:this._state==="OPEN"?"ok":"degraded",state:this._state,connectedClients:p.connectedClients,activeSessions:p.activeSessions,uptimeSeconds:Math.round(p.uptimeSeconds),pid:p.pid});o.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-cache"}),o.end(c);return}if(d==="/metrics"){let p=this.stats(),c=["# HELP ocpp_connected_clients Number of currently connected OCPP clients","# TYPE ocpp_connected_clients gauge",`ocpp_connected_clients ${p.connectedClients}`,"","# HELP ocpp_active_sessions Number of active in-memory sessions","# TYPE ocpp_active_sessions gauge",`ocpp_active_sessions ${p.activeSessions}`,"","# HELP ocpp_uptime_seconds Process uptime in seconds","# TYPE ocpp_uptime_seconds gauge",`ocpp_uptime_seconds ${Math.round(p.uptimeSeconds)}`,"","# HELP ocpp_memory_rss_bytes Resident set size in bytes","# TYPE ocpp_memory_rss_bytes gauge",`ocpp_memory_rss_bytes ${p.memoryUsage.rss}`,"","# HELP ocpp_memory_heap_used_bytes V8 heap used in bytes","# TYPE ocpp_memory_heap_used_bytes gauge",`ocpp_memory_heap_used_bytes ${p.memoryUsage.heapUsed}`,"","# HELP ocpp_memory_heap_total_bytes V8 heap total in bytes","# TYPE ocpp_memory_heap_total_bytes gauge",`ocpp_memory_heap_total_bytes ${p.memoryUsage.heapTotal}`,"","# HELP ocpp_ws_buffered_bytes Total buffered WebSocket bytes","# TYPE ocpp_ws_buffered_bytes gauge",`ocpp_ws_buffered_bytes ${p.webSockets?.bufferedAmount??0}`,""];o.writeHead(200,{"Content-Type":"text/plain; version=0.0.4; charset=utf-8","Cache-Control":"no-cache"}),o.end(c.join(`
4825
4825
  `));return}o.writeHead(404,{"Content-Type":"text/plain"}),o.end("Not Found");}),i?.signal){let a=new AbortController;this._httpServerAbortControllers.add(a),i.signal.addEventListener("abort",()=>{a.abort(),n.close(),this._httpServers.delete(n);},{once:true});}return i?.server||await new Promise((a,o)=>{n.on("error",o),n.listen(e,t,()=>{n.removeListener("error",o);let d=n.address();this._logger?.info?.("Server listening",{port:typeof d=="object"?d?.port:e,host:t??"0.0.0.0"}),a();});}),n}updateTLS(e){let t=this._options.securityProfile??0;if(t!==2&&t!==3)throw new Error("updateTLS() requires a TLS Security Profile (TLS_BASIC_AUTH or TLS_CLIENT_CERT)");this._options.tls={...this._options.tls,...e};let i={};e.cert&&(i.cert=e.cert),e.key&&(i.key=e.key),e.ca&&(i.ca=e.ca),e.passphrase&&(i.passphrase=e.passphrase);let n=0;for(let r of this._httpServers)"setSecureContext"in r&&typeof r.setSecureContext=="function"&&(r.setSecureContext(i),n++);this._logger?.info?.(`TLS context hot-reloaded across ${n} active server(s)`);}get handleUpgrade(){return (e,t,i)=>this._handleUpgrade(e,t,i).catch(n=>{t.destroyed||t.destroy(),this._logger?.error?.("Upgrade error",{error:n.message}),this.emit("upgradeError",{error:n,socket:t});})}async _handleUpgrade(e,t,i){if(this._state!=="OPEN"){w(t,503,"Server is shutting down");return}let n=this._options.connectionRateLimit;if(n){let u=e.socket.remoteAddress??"unknown",C=Date.now(),y=this._connectionBuckets.get(u);if(!y)y={tokens:n.limit,lastRefill:C},this._connectionBuckets.set(u,y);else {let T=C-y.lastRefill,P=n.limit/n.windowMs;y.tokens=Math.min(n.limit,y.tokens+T*P),y.lastRefill=C;}if(y.tokens<1){this._logger?.warn?.("Connection rate limit exceeded",{ip:u}),this.emit("securityEvent",{type:"CONNECTION_RATE_LIMIT",ip:u,timestamp:new Date().toISOString(),details:{tokensRemaining:y.tokens}}),w(t,429,"Too Many Requests");return}y.tokens-=1;}if(t.readyState!=="open"){this._logger?.debug?.("Socket not open at upgrade start"),t.destroyed||t.destroy();return}if(e.headers.upgrade?.toLowerCase()!=="websocket"){w(t,400,"Invalid upgrade request");return}if(this._globalCORS){let{allowed:u,reason:C}=_e(e,this._globalCORS);if(!u){this._logger?.warn?.("CORS rejected connection",{reason:C,ip:e.socket.remoteAddress}),w(t,403,"Forbidden");return}}let r=new URL(e.url??"/",`http://${e.headers.host??"localhost"}`),a,o=[],d=[],p={},c=r.pathname,l=false,m=this._trie.size>0||this._regexRouters.length>0,h;!this._trie.frozen&&this._trie.size>0&&this._trie.freeze();for(let u of this._globalMiddlewareRouters)d.push(u),u.middlewares.length>0&&o.push(...u.middlewares),u.authCallback&&!a&&(a=u.authCallback);let E=this._trie.match(c);if(E){Object.assign(p,E.params);for(let u of E.routers)d.push(u),l=true,u._routeConfig&&(h=Object.assign(h||{},u._routeConfig)),u.middlewares.length>0&&o.push(...u.middlewares),u.authCallback&&!a&&(a=u.authCallback);}for(let u of this._regexRouters)for(let C of u._regexPatterns){let y=C.regex.exec(c);if(y){if(d.push(u),l=true,y.groups)for(let[T,P]of Object.entries(y.groups))p[T]=decodeURIComponent(P??"");u._routeConfig&&(h=Object.assign(h||{},u._routeConfig)),u.middlewares.length>0&&o.push(...u.middlewares),u.authCallback&&!a&&(a=u.authCallback);break}}let f=p.identity;if(!f){let u=c.split("/").filter(Boolean);f=decodeURIComponent(u[u.length-1]??"");}if(!f){w(t,400,"Missing identity in URL path");return}for(let u of d)if(u._routeCORS){let{allowed:C,reason:y}=_e(e,u._routeCORS);if(!C){this._logger?.warn?.("Route CORS rejected connection",{reason:y,ip:e.socket.remoteAddress}),w(t,403,"Forbidden");return}}"setKeepAlive"in t&&t.setKeepAlive(true);let A=new Set,je=e.headers["sec-websocket-protocol"];if(je)try{A=ze(je);}catch{w(t,400,"Invalid Sec-WebSocket-Protocol header");return}let Re=h?.protocols??this._options.protocols??[],O;if(Re.length>0){if(A.size===0){w(t,400,"Missing subprotocol");return}if(O=Re.find(u=>A.has(u)),!O){w(t,400,"No matching subprotocol");return}}let et=Ge(e.headers.authorization??"",f),qe,Me=this._options.securityProfile??0;Me===3&&"getPeerCertificate"in t&&(qe=t.getPeerCertificate());let se={identity:f,remoteAddress:e.socket.remoteAddress??"",headers:e.headers,protocols:A,pathname:c,params:p,query:r.searchParams,request:e,password:et,clientCertificate:qe,securityProfile:Me},ge,Oe;if(a||o.length>0){let u=new AbortController,C=()=>{u.abort(new Error("Socket closed during handshake"));};t.on("close",C),t.on("error",C),t.on("end",C);let y=this._options.handshakeTimeoutMs??3e4,T;y>0&&(T=setTimeout(()=>{u.abort(new Error("Handshake timeout"));},y));try{ge={handshake:se,state:{},reject:(S=401,D="Unauthorized")=>{throw {code:S,message:D,_isMiddlewareReject:!0}},next:async S=>{}};let P=[...o],b=!1;if(P.push(async S=>{b=!0,a?Oe=await new Promise((D,L)=>{let V=!1,tt=N=>{V||(V=!0,N?.protocol&&(O=N.protocol),D(N));},it=(N=401,Ve="Unauthorized")=>{throw V||(V=!0,L({code:N,message:Ve})),{code:N,message:Ve,_isMiddlewareReject:!0}};if(u.signal.aborted){L(u.signal.reason);return}u.signal.addEventListener("abort",()=>{V||(V=!0,L(u.signal.reason));},{once:!0}),this._logger?.debug?.("Executing auth callback",{identity:f,pathname:c});let nt={handshake:se,state:S.state,reject:it,signal:u.signal,accept:tt};a(nt);}):O=se.protocols.values().next().value??void 0;}),await Je(P,ge),!b)throw {code:500,message:"Middleware chain halted unexpectedly without rejecting",_isMiddlewareReject:!0}}catch(P){if(u.signal.aborted){let L=P instanceof Error?P.message:"Unknown abort";this._logger?.warn?.("Handshake aborted",{identity:f,reason:L}),this.emit("securityEvent",{type:"UPGRADE_ABORTED",identity:f,ip:e.socket.remoteAddress,timestamp:new Date().toISOString(),details:{reason:L}}),this.emit("upgradeAborted",{identity:f,reason:L,socket:t,request:e}),t.destroyed||t.destroy();return}let b=P,S=typeof b?.code=="number"?b.code:401,D=typeof b?.message=="string"?b.message:"Unauthorized";this._logger?.warn?.("Auth rejected",{identity:f,code:S}),this.emit("securityEvent",{type:"AUTH_FAILED",identity:f,ip:e.socket.remoteAddress,timestamp:new Date().toISOString(),details:{code:S,message:D}}),w(t,S,D);return}finally{T&&clearTimeout(T),t.removeListener("close",C),t.removeListener("error",C),t.removeListener("end",C);}}else if(m&&!l){this._logger?.warn?.("Connection rejected: No matching route found",{pathname:c}),w(t,404,"Endpoint Not Found");return}if(t.readyState!=="open"){this._logger?.debug?.("Socket closed before upgrade completion",{identity:f}),t.destroyed||t.destroy();return}this._wss||(this._wss=new ne.WebSocketServer({noServer:true,maxPayload:this._options.maxPayloadBytes??65536})),this._wss.handleUpgrade(e,t,i,u=>{let C={identity:f,endpoint:"",callTimeoutMs:h?.callTimeoutMs??this._options.callTimeoutMs,pingIntervalMs:h?.pingIntervalMs??this._options.pingIntervalMs,deferPingsOnActivity:h?.deferPingsOnActivity??this._options.deferPingsOnActivity,callConcurrency:h?.callConcurrency??this._options.callConcurrency,maxBadMessages:this._options.maxBadMessages,respondWithDetailedErrors:this._options.respondWithDetailedErrors,strictMode:h?.strictMode??this._options.strictMode,strictModeMethods:h?.strictModeMethods??this._options.strictModeMethods,strictModeValidators:this._options.strictModeValidators,rateLimit:h?.rateLimit??this._options.rateLimit,reconnect:false,logging:this._options.logging},y={...ge?.state||{},...this._sessions.get(f)?.data||{},...Oe?.session||{}},T=new oe(C,{ws:u,handshake:se,session:y,protocol:O,adaptiveMultiplier:this._adaptiveLimiter?()=>this._adaptiveLimiter.multiplier:void 0,workerPool:this._workerPool??void 0});this._updateSessionActivity(f,T.session);let P=this._clientsByIdentity.get(f);P&&P!==T&&(this._logger?.warn?.("Evicting stale connection for identity",{identity:f,reason:"Duplicate identity replaced by new connection"}),P.close({code:4e3,reason:"Evicted by new connection",force:true}).catch(()=>{}),this._clients.delete(P)),this._clients.add(T),this._clientsByIdentity.set(f,T),this._adapter?.setPresence&&this._adapter.setPresence(f,this._nodeId,300).catch(b=>{this._logger?.error?.("Error setting presence",{identity:f,error:b});}),this._logger?.info?.("Client connected",{identity:f,remoteAddress:e.socket.remoteAddress,protocol:O}),T.on("close",({code:b,reason:S})=>{this._clients.delete(T),this._clientsByIdentity.get(f)===T&&this._clientsByIdentity.delete(f),this?._adapter?.removePresence&&this._adapter.removePresence(f).catch(D=>{this._logger?.error?.("Error removing presence",{identity:f,error:D});});for(let D of this._plugins)try{D.onDisconnect?.(T,b,S);}catch(L){this._logger?.error?.("Plugin onDisconnect error",{name:D.name,error:L.message});}this._logger?.info?.("Client disconnected",{identity:f});});for(let b of this._plugins)try{let S=b.onConnection?.(T);S instanceof Promise&&S.catch(D=>{this._logger?.error?.("Plugin onConnection error",{name:b.name,error:D.message});});}catch(S){this._logger?.error?.("Plugin onConnection error",{name:b.name,error:S.message});}this.emit("client",T);for(let b of d)b.emit("client",T);T.on("message",()=>{this._updateSessionActivity(f,T.session);});});}_updateSessionActivity(e,t){this._sessions.set(e,{data:t,lastActive:Date.now()});}async close(e={}){if(this._state!=="OPEN")return;if(this._state="CLOSING",this.emit("closing"),this._logger?.info?.("Server closing",{clientCount:this._clients.size}),this._gcInterval&&(clearInterval(this._gcInterval),this._gcInterval=null),!e.force){let r=Array.from(this._clients).map(async a=>{let o=a._ws;o&&o.bufferedAmount>0&&(this._logger?.debug?.("Waiting for client buffer to drain",{identity:a.identity,bufferedAmount:o.bufferedAmount}),await new Promise(d=>{let p=0,c=setInterval(()=>{p+=50,(!o||o.bufferedAmount===0||p>=5e3)&&(clearInterval(c),d());},50);}));});await Promise.allSettled(r);}let t=Array.from(this._clients).map(n=>n.close(e).catch(()=>{}));await Promise.allSettled(t);for(let n of this._httpServerAbortControllers)n.abort();this._httpServerAbortControllers.clear(),this._wss&&(this._wss.close(),this._wss=new ne.WebSocketServer({noServer:true}));let i=Array.from(this._httpServers).map(n=>new Promise(r=>{n.close(()=>r());}));await Promise.allSettled(i),this._httpServers.clear(),this._adaptiveLimiter&&this._adaptiveLimiter.stop();for(let n of this._plugins)try{let r=n.onClose?.();r instanceof Promise&&await r;}catch(r){this._logger?.error?.("Plugin onClose error",{name:n.name,error:r.message});}this._adapter&&await this._adapter.disconnect(),this._state="CLOSED",this.emit("close");}reconfigure(e){Object.assign(this._options,e);}async sendToClient(...e){let t,i,n,r;e.length>=4&&typeof e[0]=="string"&&typeof e[1]=="string"&&typeof e[2]=="string"?(t=e[0],i=e[2],n=e[3],r=e[4]):(t=e[0],i=e[1],n=e[2],r=e[3]);for(let a of this._clients)if(a.identity===t)return await a.call(i,n,r);if(this._adapter?.getPresence){let a=await this._adapter.getPresence(t);if(a){await this._adapter.publish(`ocpp:node:${a}`,{source:this._nodeId,target:t,method:i,params:n,options:r});return}}throw new Error(`Client ${t} not found`)}async safeSendToClient(...e){try{return await this.sendToClient(...e)}catch(t){this._logger&&typeof this._logger.warn=="function"&&t.name!=="TimeoutError"&&this._logger.warn("SafeSendToClient failed",{identity:e[0],method:e.length>=4&&typeof e[1]=="string"&&typeof e[2]=="string"?e[2]:e.length>=3&&typeof e[1]=="string"?e[1]:"unknown",error:t});return}}async sendBatch(e,t){if(t.length===0)return [];let i=this._clientsByIdentity.get(e);if(!i)return this._logger?.warn?.("sendBatch: client not found locally",{identity:e}),t.map(()=>{});let n=i.options.callConcurrency??1;t.length>n&&i.reconfigure({callConcurrency:t.length});try{return (await Promise.allSettled(t.map(a=>i.call(a.method,a.params,a.options??{})))).map(a=>{if(a.status==="fulfilled")return a.value;this._logger?.warn?.("sendBatch: individual call failed",{identity:e,error:a.reason?.message});})}finally{t.length>n&&i.reconfigure({callConcurrency:n});}}async setAdapter(e){this._adapter=e,await this._adapter.subscribe("ocpp:broadcast",t=>this._onBroadcast(t)),await this._adapter.subscribe(`ocpp:node:${this._nodeId}`,t=>{this._onUnicast(t);});}_onBroadcast(e){try{if(!e||typeof e!="object")return;let t=e;if(t.source===this._nodeId)return;for(let i of this._clients)i.call(t.method,t.params).catch(()=>{});}catch(t){this._logger?.error?.("Error processing broadcast message",{error:t.message});}}_onUnicast(e){try{if(!e||typeof e!="object")return;let t=e;for(let i of this._clients)if(i.identity===t.target){i.call(t.method,t.params,t.options).catch(n=>{n.name!=="TimeoutError"&&this._logger?.error?.("Error delivering unicast to client",{identity:t.target,error:n});});return}this._logger?.warn?.("Received unicast for unknown client",{target:t.target}),this._adapter?.removePresence&&this._adapter.removePresence(t.target).catch(()=>{});}catch(t){this._logger?.error?.("Error processing unicast",{error:t.message});}}async publish(e,t){this._adapter&&await this._adapter.publish(e,t);}async broadcast(e,t){let i=Array.from(this._clients).map(r=>r.call(e,t).catch(()=>{})),n=this._adapter?this._adapter.publish("ocpp:broadcast",{source:this._nodeId,method:e,params:t}):Promise.resolve();await Promise.all([Promise.all(i),n]);}async broadcastBatch(e,t,i,n){let r=new Set,a=[];for(let d of e){let p=this._clientsByIdentity.get(d);p&&(r.add(d),a.push(p.call(t,i,n).catch(()=>{})));}let o=e.filter(d=>!r.has(d));if(o.length>0&&this._adapter){let d=[];this._adapter.getPresenceBatch?d=await this._adapter.getPresenceBatch(o):this._adapter.getPresence&&(d=await Promise.all(o.map(c=>this._adapter.getPresence(c))));let p=[];for(let c=0;c<o.length;c++){let l=d[c];l&&p.push({channel:`ocpp:node:${l}`,data:{source:this._nodeId,target:o[c],method:t,params:i,options:n}});}p.length>0&&(this._adapter.publishBatch?await this._adapter.publishBatch(p):await Promise.all(p.map(c=>this._adapter.publish(c.channel,c.data))));}await Promise.all(a);}_buildCompressionConfig(){let e=this._options.compression;return e?e===true?{threshold:1024,zlibDeflateOptions:{level:6,memLevel:8},zlibInflateOptions:{},serverNoContextTakeover:true,clientNoContextTakeover:true}:{threshold:e.threshold??1024,zlibDeflateOptions:{level:e.level??6,memLevel:e.memLevel??8},zlibInflateOptions:{},serverNoContextTakeover:e.serverNoContextTakeover??true,clientNoContextTakeover:e.clientNoContextTakeover??true}:false}};
4826
- exports.AdaptiveLimiter=W;exports.ClusterDriver=Te;exports.ConnectionState=ie;exports.InMemoryAdapter=ye;exports.LRUMap=ae;exports.MessageType=I;exports.MiddlewareStack=pe;exports.NOREPLY=xe;exports.OCPPClient=re;exports.OCPPRouter=$;exports.OCPPServer=Ae;exports.OCPPServerClient=oe;exports.RPCFormatViolationError=X;exports.RPCFormationViolationError=K;exports.RPCFrameworkError=ee;exports.RPCGenericError=v;exports.RPCInternalError=B;exports.RPCMessageTypeNotSupportedError=_;exports.RPCNotImplementedError=j;exports.RPCNotSupportedError=z;exports.RPCOccurrenceConstraintViolationError=Y;exports.RPCPropertyConstraintViolationError=Q;exports.RPCProtocolError=G;exports.RPCSecurityError=J;exports.RPCTypeConstraintViolationError=Z;exports.RedisAdapter=be;exports.SecurityProfile=he;exports.TimeoutError=k;exports.UnexpectedHttpResponse=H;exports.Validator=ue;exports.WebsocketUpgradeError=Pe;exports.combineAuth=ft;exports.createLoggingMiddleware=Se;exports.createPlugin=ut;exports.createRPCError=R;exports.createRouter=kt;exports.createValidator=te;exports.defineAdapter=at;exports.defineAuth=ht;exports.defineMiddleware=lt;exports.defineRpcMiddleware=mt;exports.getErrorPlainObject=Ie;exports.getPackageIdent=Ee;exports.getStandardValidators=De;//# sourceMappingURL=index.js.map
4827
- //# sourceMappingURL=index.js.map
4826
+ exports.AdaptiveLimiter=W;exports.ClusterDriver=Te;exports.ConnectionState=ie;exports.InMemoryAdapter=ye;exports.LRUMap=ae;exports.MessageType=I;exports.MiddlewareStack=pe;exports.NOREPLY=xe;exports.OCPPClient=re;exports.OCPPRouter=$;exports.OCPPServer=Ae;exports.OCPPServerClient=oe;exports.RPCFormatViolationError=X;exports.RPCFormationViolationError=K;exports.RPCFrameworkError=ee;exports.RPCGenericError=v;exports.RPCInternalError=B;exports.RPCMessageTypeNotSupportedError=_;exports.RPCNotImplementedError=j;exports.RPCNotSupportedError=z;exports.RPCOccurrenceConstraintViolationError=Y;exports.RPCPropertyConstraintViolationError=Q;exports.RPCProtocolError=G;exports.RPCSecurityError=J;exports.RPCTypeConstraintViolationError=Z;exports.RedisAdapter=be;exports.SecurityProfile=he;exports.TimeoutError=k;exports.UnexpectedHttpResponse=H;exports.Validator=ue;exports.WebsocketUpgradeError=Pe;exports.combineAuth=ft;exports.createLoggingMiddleware=Se;exports.createPlugin=ut;exports.createRPCError=R;exports.createRouter=kt;exports.createValidator=te;exports.defineAdapter=at;exports.defineAuth=ht;exports.defineMiddleware=lt;exports.defineRpcMiddleware=mt;exports.getErrorPlainObject=Ie;exports.getPackageIdent=Ee;exports.getStandardValidators=De;
package/dist/index.mjs CHANGED
@@ -4823,5 +4823,4 @@ ${d}\r
4823
4823
  \r
4824
4824
  ${r}`);}var {CONNECTING:U,OPEN:O,CLOSING:_e,CLOSED:w}=ne,ae=class s extends EventEmitter{static CONNECTING=U;static OPEN=O;static CLOSING=_e;static CLOSED=w;_options;_state=w;_ws=null;_protocol;_identity;_handlers=new Map;_wildcardHandler=null;_pendingCalls=new Map;_pendingResponses=new Set;_callQueue;_pingTimer=null;_pongTimer=null;_closePromise=null;_reconnectAttempt=0;_reconnectTimer=null;_badMessageCount=0;_lastActivity=0;_outboundBuffer=[];_offlineQueue=[];_middleware;_validators=[];_strictProtocols=null;_handshake=null;_logger=M;_exchangeLog=false;_prettify=false;constructor(e){if(super(),this.setMaxListeners(0),!e.identity)throw new Error("identity is required");this._identity=e.identity,this._options={reconnect:true,maxReconnects:1/0,backoffMin:1e3,backoffMax:3e4,callTimeoutMs:3e4,pingIntervalMs:3e4,deferPingsOnActivity:false,callConcurrency:1,maxBadMessages:1/0,respondWithDetailedErrors:false,securityProfile:0,...e},this._callQueue=new le(this._options.callConcurrency),this._middleware=new ce;let t=this._options.logging,i=pe(t,{component:"OCPPClient",identity:this._identity});this._logger=i||M,t&&typeof t=="object"&&(this._exchangeLog=t.exchangeLog??false,this._prettify=t.prettify??false),this._options.logging&&this.use(Ie(this._logger,this._identity,this._options.logging)),this._options.strictMode&&this._setupValidators();}_logExchange(e,t,i,n){if(!this._logger)return;let r=e==="OUT"?"\u2192":"\u2190",a=t==="CALLERROR"?"warn":this._exchangeLog?"info":"debug";if(this._exchangeLog&&this._prettify){let o=t==="CALLERROR"?"\u{1F6A8}":t==="CALLRESULT"?"\u2705":"\u26A1",d=i??t,p=`${o} ${this._identity} ${r} ${d} [${e}]`;this._logger?.[a]?.(p,{...n,direction:e});}else this._exchangeLog?this._logger?.[a]?.(`${t} ${r}`,{...n,direction:e}):this._logger?.[a]?.(`${t} ${r}`,n);}get log(){return this._logger||M}get identity(){return this._options.identity}get endpoint(){return this._options.endpoint}get options(){return this._options}get protocol(){return this._protocol}get state(){return this._state}get securityProfile(){return this._options.securityProfile??0}async connect(){if(this._state!==w)throw new Error(`Cannot connect: client is in state ${this._state}`);return this._state=U,this._reconnectAttempt=0,this._connectInternal()}async _connectInternal(){return new Promise((e,t)=>{let i=this._buildEndpoint(),n=this._buildWsOptions();this._logger?.debug?.("Connecting",{url:i}),this.emit("connecting",{url:i});let r=new re(i,this._options.protocols??[],n);this._ws=r;let a=()=>{if(p(),this._state=O,this._protocol=r.protocol,this._badMessageCount=0,r.protocol&&this._reconnectAttempt===0&&(this._options.protocols=[r.protocol]),this._attachWebsocket(r),this._startPing(),this._flushOfflineQueue(),this._outboundBuffer.length>0){let m=this._outboundBuffer;this._outboundBuffer=[];for(let h of m)this._ws?.send(h);}this._logger?.info?.("Connected",{protocol:r.protocol});let l={response:r._req?.res};this.emit("open",l),e(l);},o=c=>{p(),this._state=w,this._logger?.error?.("Connection error",{error:c.message}),this.emit("error",c),t(c);},d=(c,l)=>{p(),this._state=w;let m=new z(`Unexpected HTTP response: ${l.statusCode}`,l.statusCode??0,l.headers);this._logger?.error?.("Unexpected HTTP response",{statusCode:l.statusCode}),this.emit("error",m),t(m);},p=()=>{r.removeListener("open",a),r.removeListener("error",o),r.removeListener("unexpected-response",d);};r.on("open",a),r.on("error",o),r.on("unexpected-response",d);})}async close(e={}){let{code:t=1e3,reason:i="",awaitPending:n=true,force:r=false}=e;return this._closePromise?this._closePromise:this._state===w?{code:1e3,reason:""}:(this._reconnectTimer&&(clearTimeout(this._reconnectTimer),this._reconnectTimer=null),this._closePromise=this._closeInternal(t,i,n,r),this._closePromise)}async _closeInternal(e,t,i,n){if(this._state=_e,this._stopPing(),!n&&i){let r=Array.from(this._pendingCalls.values()).map(a=>new Promise(o=>{let d=a.resolve,p=a.reject;a.resolve=c=>{d(c),o();},a.reject=c=>{p(c),o();};}));r.length>0&&await Promise.allSettled(r);}return new Promise(r=>{if(!this._ws||this._ws.readyState===re.CLOSED){this._state=w,this._cleanup();let o={code:e,reason:t};this.emit("close",o),r(o);return}let a=(o,d)=>{this._ws?.removeListener("close",a),this._state=w,this._cleanup();let p={code:o,reason:d.toString()};this.emit("close",p),r(p);};this._ws.on("close",a),n?this._ws.terminate():this._ws.close(Je(e)?e:1e3,t);})}handle(...e){if(e.length===1&&typeof e[0]=="function"){if(this._wildcardHandler)throw new Error("Wildcard handler is already registered.");this._wildcardHandler=e[0];}else if(e.length===2&&typeof e[0]=="string"&&typeof e[1]=="function"){if(this._handlers.has(e[0]))throw new Error(`Handler for '${e[0]}' is already registered.`);this._handlers.set(e[0],e[1]);}else if(e.length===3&&typeof e[0]=="string"&&typeof e[1]=="string"&&typeof e[2]=="function"){let t=`${e[0]}:${e[1]}`;if(this._handlers.has(t))throw new Error(`Handler for '${e[1]}' (protocol: ${e[0]}) is already registered.`);this._handlers.set(t,e[2]);}else throw new Error("Invalid arguments: provide (version, method, handler), (method, handler), or (wildcardHandler)")}removeHandler(e,t){e&&t?this._handlers.delete(`${e}:${t}`):e?this._handlers.delete(e):this._wildcardHandler=null;}removeAllHandlers(){this._handlers.clear(),this._wildcardHandler=null;}use(e){this._middleware.use(e);}async call(...e){let t,i,n;if(e.length>=3&&typeof e[0]=="string"&&typeof e[1]=="string"?(t=e[1],i=e[2]??{},n=e[3]??{}):(t=e[0],i=e[1]??{},n=e[2]??{}),this._state!==O){if(this._options.offlineQueue&&(this._state===w||this._state===U))return new Promise((a,o)=>{let d=this._options.offlineQueueMaxSize??100;this._offlineQueue.length>=d&&(this._offlineQueue.shift(),this._logger?.warn?.("Offline queue full \u2014 dropping oldest message",{method:t,queueSize:this._offlineQueue.length})),this._offlineQueue.push({method:t,params:i,options:n,resolve:a,reject:o}),this._logger?.debug?.("Call queued offline",{method:t,queueSize:this._offlineQueue.length});});throw new Error(`Cannot call: client is in state ${this._state}`)}let r=n.retries??0;return r>0?this._callWithRetry(t,i,n,r):this._callQueue.push(()=>this._sendCall(t,i,n))}async safeCall(...e){try{return await this.call(...e)}catch(t){if(t.name!=="TimeoutError"){let i={method:e.find(n=>typeof n=="string"&&!n.startsWith("ocpp")),error:t};this._logger?.warn?this._logger.warn("SafeCall failed",i):console.warn("SafeCall failed",i);}return}}async _sendCall(e,t,i){let n=i.idempotencyKey??ue(),r=i.timeoutMs??this._options.callTimeoutMs,a={type:"outgoing_call",messageId:n,method:e,params:t,options:i},o;return await this._middleware.execute(a,async d=>{let p=d;this._options.strictMode&&this._protocol&&this._validateOutbound(p.method,p.params,"req");let c=[E.CALL,n,p.method,p.params],l=JSON.stringify(c);return o=await new Promise((m,h)=>{let D=setTimeout(()=>{this._pendingCalls.delete(n),h(new F(`Call to "${p.method}" timed out after ${r}ms`));},r),f=()=>{clearTimeout(D),this._pendingCalls.delete(n),h(new Error("Aborted"));};i.signal&&i.signal.addEventListener("abort",f),this._pendingCalls.set(n,{resolve:m,reject:h,timeoutHandle:D,abortHandler:i.signal?()=>i.signal?.removeEventListener("abort",f):void 0,method:p.method,sentAt:Date.now()}),this._ws?.readyState===re.OPEN?this._safeSend(this._ws,l,j=>{j&&(clearTimeout(D),this._pendingCalls.delete(n),h(j));}):this._state===U?(this._logger?.debug?.("Buffering call",{method:p.method}),this._outboundBuffer.push(l)):(clearTimeout(D),this._pendingCalls.delete(n),h(new Error(`WebSocket is not open (state: ${this._state})`)));}),o}),o}sendRaw(e){if(this._state===O&&this._ws)this._ws.send(e);else if(this._state===U)this._outboundBuffer.push(e);else throw new Error("Cannot send: client is not connected")}reconfigure(e){Object.assign(this._options,e),e.callConcurrency!==void 0&&this._callQueue.setConcurrency(e.callConcurrency),(e.strictMode!==void 0||e.strictModeValidators!==void 0)&&this._setupValidators(),e.pingIntervalMs!==void 0&&(this._stopPing(),this._state===O&&this._startPing());}_attachWebsocket(e){e.on("message",t=>this._onMessage(t)),e.on("close",(t,i)=>this._onClose(t,i)),e.on("error",t=>this.emit("error",t)),e.on("ping",()=>{this._recordActivity(),this.emit("ping");}),e.on("pong",()=>{this._pongTimer&&(clearTimeout(this._pongTimer),this._pongTimer=null),this._recordActivity(),this.emit("pong");});}_onMessage(e,t){this._recordActivity();let i;try{if(t!==void 0?i=t:i=JSON.parse(e),!Array.isArray(i))throw new Error("Message is not an array")}catch(d){this._onBadMessage(typeof e=="string"?e:e.toString(),d);return}let n=i[0],r=i[1];if(typeof r!="string"){this._onBadMessage(typeof e=="string"?e:e.toString(),new L(`Invalid MessageId type: ${typeof r} (expected string)`));return}if(n===E.CALL&&i.length<4||n===E.CALLRESULT&&i.length<3||n===E.CALLERROR&&i.length<5){this._onBadMessage(JSON.stringify(i),new L(`Missing payload elements for message type ${n}`));return}let a=n===E.CALLERROR?4:n===E.CALL?3:2,o=i[a];if(typeof o!="object"||o===null||Array.isArray(o)){this._onBadMessage(JSON.stringify(i),new L(`Payload must be a JSON object, got ${o===null?"null":Array.isArray(o)?"array":typeof o}`));return}switch(n){case E.CALL:this._handleIncomingCall(i);break;case E.CALLRESULT:this._handleCallResult(i);break;case E.CALLERROR:this._handleCallError(i);break;default:this._onBadMessage(JSON.stringify(i),new L(`Unknown message type: ${n}`));}}async _handleIncomingCall(e){let[,t,i,n]=e,r={type:"incoming_call",messageId:t,method:i,params:n,protocol:this._protocol};try{await this._middleware.execute(r,async a=>{let o=a,d=[E.CALL,o.messageId,o.method,o.params];if(this.emit("call",d),this._state===O)try{if(this._pendingResponses.has(o.messageId))throw q("RpcFrameworkError",`Already processing call with ID: ${o.messageId}`);let p=(this._protocol?this._handlers.get(`${this._protocol}:${o.method}`):void 0)??this._handlers.get(o.method);if(!p&&!this._wildcardHandler)throw new R(`Method "${o.method}" not implemented`);this._options.strictMode&&this._protocol&&this._validateInbound(o.method,o.params,"req"),this._pendingResponses.add(o.messageId);let c=new AbortController,l={messageId:o.messageId,method:o.method,protocol:this._protocol,params:o.params,signal:c.signal},m;if(p?m=await p(l):this._wildcardHandler&&(m=await this._wildcardHandler(o.method,l)),this._pendingResponses.delete(o.messageId),m===we)return;this._options.strictMode&&this._protocol&&this._validateOutbound(o.method,m,"conf");let h=[E.CALLRESULT,o.messageId,m];return this._ws?.send(JSON.stringify(h)),this.emit("callResult",h),m}catch(p){this._pendingResponses.delete(o.messageId);let c=p instanceof C||p.rpcErrorCode?p:q("InternalError",p.message),l=this._options.respondWithDetailedErrors?Ee(p):{},m=[E.CALLERROR,o.messageId,c.rpcErrorCode,c.rpcErrorMessage||p.message||"",l];throw this._ws?.send(JSON.stringify(m)),this.emit("callError",m),p}});}catch{}}async _handleCallResult(e){let[,t,i]=e;if(!this._pendingCalls.has(t)){this._logger?.warn?.("Received CallResult for unknown messageId",{messageId:t});return}let n=this._pendingCalls.get(t),r={type:"incoming_result",messageId:t,payload:i,method:n.method};await this._middleware.execute(r,async a=>{let o=a,d=this._pendingCalls.get(o.messageId);d&&(this.emit("callResult",e),clearTimeout(d.timeoutHandle),d.abortHandler,this._pendingCalls.delete(o.messageId),d.resolve(o.payload));});}async _handleCallError(e){let[,t]=e,i=this._pendingCalls.get(t);if(!i){this._logger?.warn?.("Received CallError for unknown messageId",{messageId:t});return}let n={type:"incoming_error",messageId:t,error:e,method:i.method};await this._middleware.execute(n,async r=>{let a=r,[,,o,d,p]=a.error,c=this._pendingCalls.get(a.messageId);if(!c)return;this.emit("callError",a.error),clearTimeout(c.timeoutHandle),this._pendingCalls.delete(a.messageId);let l=q(o,d,p);c.reject(l);});}_onBadMessage(e,t){this._badMessageCount++,this._logger?.warn?.("Bad message",{error:t.message,count:this._badMessageCount}),this.emit("badMessage",{message:e,error:t});let i=e.match(/^\s*\[\s*2\s*,\s*"([^"]+)"/);if(i?.[1]&&this._ws){let n=[E.CALLERROR,i[1],"FormatViolation",t.message||"Invalid message format",{}];this._ws.send(JSON.stringify(n)),this.emit("callError",n);}this._badMessageCount>=this._options.maxBadMessages&&this.close({code:1002,reason:"Too many bad messages"}).catch(()=>{});}_rejectPendingCalls(e){for(let[,t]of this._pendingCalls)clearTimeout(t.timeoutHandle),t.reject(new Error(e));this._pendingCalls.clear(),this._pendingResponses.clear();}_onClose(e,t){this._stopPing();let i=t.toString();this._rejectPendingCalls(`Connection closed (${e}: ${i})`),this._state!==_e?(this._logger?.info?.("Disconnected",{code:e,reason:i}),this.emit("disconnect",{code:e,reason:i}),this._options.reconnect&&this._reconnectAttempt<this._options.maxReconnects?this._scheduleReconnect():(this._state=w,this.emit("close",{code:e,reason:i}))):this._state=w;}static _INTOLERABLE_ERRORS=new Set(["Maximum redirects exceeded","Server sent no subprotocol","Server sent an invalid subprotocol","Server sent a subprotocol but none was requested","Invalid Sec-WebSocket-Accept header"]);_scheduleReconnect(){this._reconnectAttempt++,this._state=U;let e=this._options.backoffMin,t=this._options.backoffMax,i=Math.min(t,e*2**(this._reconnectAttempt-1)*(.5+Math.random()*.5));this._logger?.warn?.("Reconnecting",{attempt:this._reconnectAttempt,delayMs:Math.round(i)}),this.emit("reconnect",{attempt:this._reconnectAttempt,delay:i}),this._reconnectTimer=setTimeout(async()=>{this._reconnectTimer=null;try{await this._connectInternal();}catch(n){let r=n instanceof Error?n.message:"";if(s._INTOLERABLE_ERRORS.has(r)){this._logger?.error?.("Intolerable error \u2014 stopping reconnection",{error:r}),this._state=w,this.emit("close",{code:1001,reason:r});return}this._reconnectAttempt<this._options.maxReconnects&&this._options.reconnect?this._scheduleReconnect():(this._state=w,this.emit("close",{code:1001,reason:"Max reconnection attempts exhausted"}));}},i);}_flushOfflineQueue(){if(this._offlineQueue.length===0)return;let e=this._offlineQueue.splice(0,this._offlineQueue.length);this._logger?.info?.("Flushing offline queue",{count:e.length});for(let t of e)this._callQueue.push(()=>this._sendCall(t.method,t.params,t.options)).then(t.resolve).catch(t.reject);}async _callWithRetry(e,t,i,n){let r=i.retryDelayMs??1e3,a=i.retryMaxDelayMs??3e4;for(let o=0;o<=n;o++)try{return await this._callQueue.push(()=>this._sendCall(e,t,i))}catch(d){if(o===n||!(d instanceof F))throw d;let p=Math.min(a,r*2**o),c=Math.random()*p;this._logger?.warn?.("Call retry",{method:e,attempt:o+1,maxRetries:n,delayMs:Math.round(c)}),await new Promise(l=>setTimeout(l,c));}throw new Error("Retry exhausted")}static _BACKPRESSURE_THRESHOLD=512*1024;_safeSend(e,t,i){if(!e||e.readyState!==re.OPEN){i?.(new Error("WebSocket is not open"));return}if(e.bufferedAmount>s._BACKPRESSURE_THRESHOLD){this._logger?.warn?.("Backpressure \u2014 pausing send",{identity:this._identity,bufferedAmount:e.bufferedAmount,threshold:s._BACKPRESSURE_THRESHOLD}),this.emit("backpressure",{identity:this._identity,bufferedAmount:e.bufferedAmount});let n=0,r=setInterval(()=>{if(n+=50,!e||e.readyState!==re.OPEN){clearInterval(r),i?.(new Error("WebSocket closed during backpressure wait"));return}(e.bufferedAmount<=s._BACKPRESSURE_THRESHOLD||n>=1e4)&&(clearInterval(r),e.send(t,i));},50);}else e.send(t,i);}_startPing(){if(this._options.pingIntervalMs<=0)return;let e=this._options.pongTimeoutMs??this._options.pingIntervalMs+5e3,t=()=>{if(this._state!==O||!this._ws)return;if(this._options.deferPingsOnActivity){let r=Date.now()-this._lastActivity;if(r<this._options.pingIntervalMs){this._pingTimer=setTimeout(t,this._options.pingIntervalMs-r);return}}this._ws.ping(),e>0&&(this._pongTimer=setTimeout(()=>{this._logger?.warn?.("Pong timeout \u2014 terminating dead connection",{identity:this._identity,timeoutMs:e}),this._ws?.terminate();},e));let n=this._options.pingIntervalMs*(.75+Math.random()*.5);this._pingTimer=setTimeout(t,n);},i=this._options.pingIntervalMs*(.75+Math.random()*.5);this._pingTimer=setTimeout(t,i);}_stopPing(){this._pingTimer&&(clearTimeout(this._pingTimer),this._pingTimer=null);}_recordActivity(){this._lastActivity=Date.now();}_setupValidators(){this._options.strictModeValidators?this._validators=this._options.strictModeValidators:this._validators=xe(),Array.isArray(this._options.strictMode)?this._strictProtocols=this._options.strictMode:this._strictProtocols=null;}_validateOutbound(e,t,i){let n=this._findValidator();if(!n||this._options.strictModeMethods&&!this._options.strictModeMethods.includes(e))return;let r=`urn:${e}.${i}`;try{n.validate(r,t);}catch(a){throw this.emit("strictValidationFailure",{message:t,error:a}),a}}_validateInbound(e,t,i){let n=this._findValidator();if(!n||this._options.strictModeMethods&&!this._options.strictModeMethods.includes(e))return;let r=`urn:${e}.${i}`;try{n.validate(r,t);}catch(a){throw this.emit("strictValidationFailure",{message:t,error:a}),a}}_findValidator(){return !this._protocol||this._strictProtocols&&!this._strictProtocols.includes(this._protocol)?null:this._validators.find(e=>e.subprotocol===this._protocol)??null}_buildEndpoint(){let e=this._options.endpoint;if(e.endsWith("/")||(e+="/"),e+=encodeURIComponent(this._identity),this._options.query){let t=new URLSearchParams(this._options.query);e+=(e.includes("?")?"&":"?")+t.toString();}return e}_buildWsOptions(){let e={headers:{...this._options.headers,"User-Agent":De()}},t=this._options.securityProfile??0;if((t===1||t===2)&&this._options.password){let n=Buffer.from(`${this._identity}:${this._options.password.toString()}`).toString("base64");e?.headers&&(e.headers.Authorization=`Basic ${n}`);}if(t===2||t===3){let n=this._options.tls??{};n.ca&&(e.ca=n.ca),n.rejectUnauthorized!==void 0&&(e.rejectUnauthorized=n.rejectUnauthorized),t===3&&(n.cert&&(e.cert=n.cert),n.key&&(e.key=n.key),n.passphrase&&(e.passphrase=n.passphrase));}let i=this._options.compression;return i&&(e.perMessageDeflate=i===true?{zlibDeflateOptions:{level:6,memLevel:8},zlibInflateOptions:{},clientNoContextTakeover:true,serverNoContextTakeover:true}:{zlibDeflateOptions:{level:i.level??6,memLevel:i.memLevel??8},zlibInflateOptions:{},clientNoContextTakeover:i.clientNoContextTakeover??true,serverNoContextTakeover:i.serverNoContextTakeover??true}),e}_cleanup(){this._stopPing(),this._pongTimer&&(clearTimeout(this._pongTimer),this._pongTimer=null),this._reconnectTimer&&(clearTimeout(this._reconnectTimer),this._reconnectTimer=null),this._closePromise=null,this._ws=null;}};var oe=class extends Map{_maxSize;constructor(e){if(super(),e<1)throw new RangeError("LRUMap maxSize must be >= 1");this._maxSize=e;}get maxSize(){return this._maxSize}set(e,t){if(this.has(e)&&this.delete(e),super.set(e,t),this.size>this._maxSize){let i=this.keys().next().value;i!==void 0&&this.delete(i);}return this}get(e){if(!this.has(e))return;let t=super.get(e);return this.delete(e),super.set(e,t),t}};async function Xe(s,e){let t=-1,i=async(n,r)=>{if(r&&(e.state={...e.state||{},...r||{}}),n<=t)throw new Error("next() called multiple times in middleware");t=n;let a=s[n];n!==s.length&&a&&(e.next=i.bind(null,n+1),await a(e));};await i(0);}var A=class extends EventEmitter{patterns;middlewares;authCallback=null;_routeCORS;_routeConfig;_regexPatterns=[];constructor(e,t){super(),this.patterns=[],this.middlewares=t??[],e?.length&&this.route(...e);}route(...e){this.patterns.push(...e);for(let t of e)typeof t!="string"&&this._regexPatterns.push({regex:t,paramNames:[]});return this}use(...e){return this.middlewares.push(...e),this}cors(e){return this._routeCORS=e,this}config(e){return this._routeConfig=e,this}auth(e){return this.authCallback=e,this}handle(...e){return this.on("client",t=>{let i=e[e.length-1],n=[...e];typeof i=="function"&&(n[n.length-1]=(...r)=>{let a=r.length-1,o=r[a];return o&&typeof o=="object"&&Object.defineProperty(o,"client",{value:t,enumerable:true,configurable:true}),i(...r)}),t.handle(...n);}),this}};function zt(...s){return new A(s)}function Ye(s){return s.split(".").reduce((e,t)=>(e<<8)+parseInt(t,10),0)>>>0}function Ze(s,e){let t=s;t.startsWith("::ffff:")&&(t=t.substring(7));for(let i of e){if(i===t)return true;if(i.includes("/")){let n=i.split("/"),r=n[0],a=n[1];if(!r||!a)continue;let o=parseInt(a,10);if(isIPv4(t)&&isIPv4(r)){let d=Ye(t),p=Ye(r),c=o===0?0:-1<<32-o>>>0;if((d&c)===(p&c))return true}}}return false}function Le(s,e){if(e.allowedIPs&&e.allowedIPs.length>0){let t=s.socket.remoteAddress;if(!t||!Ze(t,e.allowedIPs))return {allowed:false,reason:"IP address not allowed"}}if(e.allowedSchemes&&e.allowedSchemes.length>0){let t=s.socket instanceof TLSSocket?"wss":"ws",i=s.headers["x-forwarded-proto"];if(typeof i=="string"&&(t=i==="https"||i==="wss"?"wss":"ws"),!e.allowedSchemes.includes(t))return {allowed:false,reason:"Protocol scheme not allowed"}}if(e.allowedOrigins&&e.allowedOrigins.length>0){let t=s.headers.origin;if(typeof t=="string"&&!e.allowedOrigins.includes(t))return {allowed:false,reason:"Origin not allowed"}}return {allowed:true}}var W=class{children=new Map;paramChild=null;paramName="";wildcardChild=null;routers=[]};function et(s){return s.replace(/\/+/g,"/").replace(/^\/|\/$/g,"").split("/").filter(Boolean)}var ge=class{root=new W;_frozen=false;_size=0;get size(){return this._size}insert(e,t){this._frozen&&(this._frozen=false);let i=et(e),n=this.root;for(let r of i)if(r==="*"){n.wildcardChild||(n.wildcardChild=new W),n=n.wildcardChild;break}else if(r.startsWith(":")){let a=r.slice(1);if(!n.paramChild)n.paramChild=new W,n.paramChild.paramName=a;else if(n.paramChild.paramName!==a)throw new Error(`Route conflict: param ":${a}" conflicts with existing ":${n.paramChild.paramName}" at the same position in pattern "${e}"`);n=n.paramChild;}else {let a=r.toLowerCase(),o=n.children.get(a);o||(o=new W,n.children.set(a,o)),n=o;}n.routers.push(t),this._size++;}match(e){let t=et(e),i=[];if(this._matchRecursive(this.root,t,0,{},i),i.length===0)return null;let n=i[0].params,r=[];for(let a of i)r.push(...a.routers);return {routers:r,params:n}}_matchRecursive(e,t,i,n,r){if(i===t.length){e.routers.length>0&&r.push({routers:[...e.routers],params:{...n}});return}let a=t[i],o=a.toLowerCase(),d=e.children.get(o);if(d&&this._matchRecursive(d,t,i+1,n,r),e.paramChild){let p={...n};p[e.paramChild.paramName]=decodeURIComponent(a),this._matchRecursive(e.paramChild,t,i+1,p,r);}e.wildcardChild&&e.wildcardChild.routers.length>0&&r.push({routers:[...e.wildcardChild.routers],params:{...n}});}freeze(){this._frozen||(this._freezeNode(this.root),this._frozen=true);}_freezeNode(e){Object.freeze(e.routers),e.paramChild&&this._freezeNode(e.paramChild),e.wildcardChild&&this._freezeNode(e.wildcardChild);for(let t of e.children.values())this._freezeNode(t);}get frozen(){return this._frozen}};var se=class extends ae{_serverSession;_serverHandshake;constructor(e,t){super(e),this._serverSession=t.session,this._serverHandshake=t.handshake,this._adaptiveMultiplier=t.adaptiveMultiplier??null,this._workerPool=t.workerPool??null,this._state=ne.OPEN,this._identity=this._options.identity,this._ws=t.ws,this._protocol=t.protocol??t.ws.protocol,this._attachServerWebsocket(t.ws),this._startPing();}_rateLimits={};_adaptiveMultiplier=null;_workerPool=null;_checkRateLimit(e){let t=this._options.rateLimit;if(!t)return true;let i=Date.now(),n=(r,a,o)=>{let d=this._rateLimits[r];if(!d)d={tokens:a,lastRefill:i},this._rateLimits[r]=d;else {let p=i-d.lastRefill,c=this._adaptiveMultiplier?.()??1,l=a/o*c,m=p*l;m>0&&(d.tokens=Math.min(a,d.tokens+m),d.lastRefill=i);}return d.tokens>=1?(d.tokens-=1,true):false};if(!n("global",t.limit,t.windowMs))return false;if(e&&t.methods?.[e]){let r=t.methods[e];if(!n(`method:${e}`,r.limit,r.windowMs))return false}return true}_attachServerWebsocket(e){e.on("message",t=>{this._recordActivity();let i=this._options.rateLimit;if(i){let n,r;if(i.methods)try{r=JSON.parse(t),Array.isArray(r)&&r[0]===2&&(n=r[2]);}catch{}if(!this._checkRateLimit(n)){this._handleRateLimitExceeded(r||t.toString());return}if(r!==void 0){this._onMessage(t,r);return}}if(this._workerPool){let n=t;this._workerPool.parse(n).then(r=>{this._onMessage(t,r.message);}).catch(()=>{this._onMessage(t);});return}this._onMessage(t);}),e.on("close",(t,i)=>this._onClose(t,i)),e.on("error",t=>{this.listenerCount("error")>0?this.emit("error",t):this._logger?.debug?.("WebSocket error (unhandled by client listener)",{error:t.message});}),e.on("ping",()=>{this._recordActivity(),this.emit("ping");}),e.on("pong",()=>{this._pongTimer&&(clearTimeout(this._pongTimer),this._pongTimer=null),this._recordActivity(),this.emit("pong");});}_handleRateLimitExceeded(e){let i=this._options.rateLimit.onLimitExceeded||"ignore";if(i==="disconnect")this._logger?.warn?.("Rate limit exceeded \u2014 disconnecting client",{identity:this.identity}),this._ws?.terminate();else if(typeof i=="function")try{let n=i(this,e);n instanceof Promise&&n.catch(r=>{this._logger?.error?.("Error in custom onLimitExceeded handler",{identity:this.identity,error:r});});}catch(n){this._logger?.error?.("Error in custom onLimitExceeded handler",{identity:this.identity,error:n});}else this._logger?.debug?.("Rate limit exceeded \u2014 ignoring message",{identity:this.identity});}get session(){return this._serverSession}get handshake(){return this._serverHandshake}async connect(){throw new Error("Cannot connect from server client \u2014 connection is managed by the server")}close(e={}){return super.close(e)}};var $e=class{_workers=[];_nextWorker=0;_taskId=0;_pending=new Map;_maxQueueSize;_terminated=false;constructor(e={}){let t=e.poolSize??Math.max(2,cpus().length-2);this._maxQueueSize=e.maxQueueSize??1e4;let i=resolve(g,"parse-worker.js");for(let n=0;n<t;n++){let r=new Worker(i);r.on("message",a=>{let o=this._pending.get(a.id);o&&(this._pending.delete(a.id),a.error?o.reject(new Error(a.error)):o.resolve({message:a.message,validationError:a.validationError}));}),r.on("error",a=>{console.error(`[WorkerPool] Worker ${n} error:`,a.message);}),this._workers.push(r);}}get size(){return this._workers.length}get pendingTasks(){return this._pending.size}parse(e,t){return this._terminated?Promise.reject(new Error("WorkerPool has been shut down")):this._pending.size>=this._maxQueueSize?Promise.reject(new Error(`WorkerPool queue full (${this._maxQueueSize} pending tasks)`)):new Promise((i,n)=>{let r=this._taskId++;this._pending.set(r,{resolve:i,reject:n});let a=this._workers[this._nextWorker%this._workers.length];this._nextWorker=(this._nextWorker+1)%this._workers.length,a.postMessage({id:r,buffer:e,schemaInfo:t});})}async shutdown(){if(this._terminated)return;this._terminated=true;for(let[t,i]of this._pending)i.reject(new Error("WorkerPool shutting down")),this._pending.delete(t);let e=this._workers.map(async t=>{try{await Promise.race([t.terminate(),new Promise(i=>setTimeout(i,5e3))]);}catch{}});await Promise.allSettled(e),this._workers=[];}};function tt(s={}){try{return new $e(s)}catch{return null}}var je=class extends EventEmitter{_options;_trie=new ge;_globalMiddlewareRouters=[];_regexRouters=[];_clients=new Set;_clientsByIdentity=new Map;_httpServers=new Set;_wss=null;_state="OPEN";_adapter=null;_httpServerAbortControllers=new Set;_logger=null;_globalCORS;_connectionBuckets=new Map;_adaptiveLimiter=null;_plugins=[];_workerPool=null;_nodeId=ue();_sessions;_gcInterval=null;_sessionTimeoutMs;constructor(e={}){if(super(),this.setMaxListeners(0),e.strictMode&&!e.protocols?.length)throw new Error("strictMode requires protocols to be specified (e.g. protocols: ['ocpp1.6'])");this._options={securityProfile:0,callTimeoutMs:3e4,pingIntervalMs:3e4,deferPingsOnActivity:false,callConcurrency:1,maxBadMessages:1/0,respondWithDetailedErrors:false,handshakeTimeoutMs:3e4,sessionTtlMs:7200*1e3,...e},this._sessionTimeoutMs=this._options.sessionTtlMs;let t=this._options.maxSessions??5e4;this._sessions=new oe(t),this._wss=new WebSocketServer({noServer:true,maxPayload:this._options.maxPayloadBytes??65536,perMessageDeflate:this._buildCompressionConfig()}),this._gcInterval=setInterval(()=>{let r=Date.now();for(let[a,o]of this._sessions.entries())r-o.lastActive>this._sessionTimeoutMs&&this._sessions.delete(a);},60*1e3).unref(),this._logger=pe(this._options.logging,{component:"OCPPServer"});let i=this._options.rateLimit;i?.adaptive&&(this._adaptiveLimiter=new H({cpuThresholdPercent:i.cpuThresholdPercent,memThresholdPercent:i.memThresholdPercent,cooldownMs:i.cooldownMs}),this._adaptiveLimiter.on("adapted",r=>{this._logger?.info?.("Adaptive rate limit adjusted",r),this.emit("rateLimit:adapted",r);}),this._adaptiveLimiter.start());let n=this._options.workerThreads;if(n){let r=typeof n=="object"?n:{};this._workerPool=tt(r),this._workerPool&&this._logger?.info?.("Worker thread pool initialized",{poolSize:this._workerPool.size});}}get log(){return this._logger||M}get clients(){return this._clients}get state(){return this._state}stats(){let e=0;if(this._wss)for(let t of this._wss.clients)e+=t.bufferedAmount;return {connectedClients:this._clients.size,activeSessions:this._sessions.size,uptimeSeconds:process.uptime(),pid:process.pid,memoryUsage:process.memoryUsage(),cpuUsage:process.cpuUsage(),webSockets:this._wss?{total:this._wss.clients.size,bufferedAmount:e}:void 0}}async adapterMetrics(){if(!this._adapter||!this._adapter.metrics)return null;try{return await this._adapter.metrics()}catch(e){return this._logger?.warn?.("Failed to fetch adapter metrics",{error:e}),null}}getLocalClient(e){return this._clientsByIdentity.get(e)}hasLocalClient(e){return this.getLocalClient(e)!==void 0}async isClientConnected(e){if(this.hasLocalClient(e))return true;if(this._adapter?.getPresence){let t=await this._adapter.getPresence(e);return t!=null}return false}cors(e){return this._globalCORS=e,this}route(...e){let t=new A;return t.route(...e),this._registerRouter(t),t}attachRouters(...e){for(let t of e)this._registerRouter(t);return this}plugin(...e){for(let t of e)if(this._plugins.push(t),this._logger?.info?.("Plugin registered",{name:t.name}),t.onInit){let i=t.onInit(this);i instanceof Promise&&i.catch(n=>{this._logger?.error?.("Plugin onInit error",{name:t.name,error:n.message});});}return this}use(...e){let t=new A;return t.use(...e),this._registerRouter(t),t}auth(e){let t=new A;return t.auth(e),this._registerRouter(t),t}_registerRouter(e){let t=e.patterns.filter(n=>typeof n=="string"),i=e._regexPatterns.length>0;if(t.length===0&&!i)this._globalMiddlewareRouters.push(e);else {for(let n of t)this._trie.insert(n,e);i&&this._regexRouters.push(e);}}async listen(e=0,t,i){let n;if(i?.server)n=i.server;else {let a=this._options.securityProfile??0;if(a===2||a===3){let o=this._options.tls??{},d={};o.cert&&(d.cert=o.cert),o.key&&(d.key=o.key),o.ca&&(d.ca=o.ca),o.passphrase&&(d.passphrase=o.passphrase),a===3&&(d.requestCert=true,d.rejectUnauthorized=o.rejectUnauthorized??true),n=createServer(d);}else n=createServer$1();}this._state==="CLOSED"&&(this._state="OPEN");let r=(a,o,d)=>{this._handleUpgrade(a,o,d).catch(p=>{o.destroyed||o.destroy(),this._logger?.error?.("Upgrade error",{error:p.message}),this.emit("upgradeError",{error:p,socket:o});});};if(n.on("upgrade",r),this._httpServers.add(n),this._options.healthEndpoint&&n.on("request",(a,o)=>{let d=a.url??"";if(d==="/health"){let p=this.stats(),c=JSON.stringify({status:this._state==="OPEN"?"ok":"degraded",state:this._state,connectedClients:p.connectedClients,activeSessions:p.activeSessions,uptimeSeconds:Math.round(p.uptimeSeconds),pid:p.pid});o.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-cache"}),o.end(c);return}if(d==="/metrics"){let p=this.stats(),c=["# HELP ocpp_connected_clients Number of currently connected OCPP clients","# TYPE ocpp_connected_clients gauge",`ocpp_connected_clients ${p.connectedClients}`,"","# HELP ocpp_active_sessions Number of active in-memory sessions","# TYPE ocpp_active_sessions gauge",`ocpp_active_sessions ${p.activeSessions}`,"","# HELP ocpp_uptime_seconds Process uptime in seconds","# TYPE ocpp_uptime_seconds gauge",`ocpp_uptime_seconds ${Math.round(p.uptimeSeconds)}`,"","# HELP ocpp_memory_rss_bytes Resident set size in bytes","# TYPE ocpp_memory_rss_bytes gauge",`ocpp_memory_rss_bytes ${p.memoryUsage.rss}`,"","# HELP ocpp_memory_heap_used_bytes V8 heap used in bytes","# TYPE ocpp_memory_heap_used_bytes gauge",`ocpp_memory_heap_used_bytes ${p.memoryUsage.heapUsed}`,"","# HELP ocpp_memory_heap_total_bytes V8 heap total in bytes","# TYPE ocpp_memory_heap_total_bytes gauge",`ocpp_memory_heap_total_bytes ${p.memoryUsage.heapTotal}`,"","# HELP ocpp_ws_buffered_bytes Total buffered WebSocket bytes","# TYPE ocpp_ws_buffered_bytes gauge",`ocpp_ws_buffered_bytes ${p.webSockets?.bufferedAmount??0}`,""];o.writeHead(200,{"Content-Type":"text/plain; version=0.0.4; charset=utf-8","Cache-Control":"no-cache"}),o.end(c.join(`
4825
4825
  `));return}o.writeHead(404,{"Content-Type":"text/plain"}),o.end("Not Found");}),i?.signal){let a=new AbortController;this._httpServerAbortControllers.add(a),i.signal.addEventListener("abort",()=>{a.abort(),n.close(),this._httpServers.delete(n);},{once:true});}return i?.server||await new Promise((a,o)=>{n.on("error",o),n.listen(e,t,()=>{n.removeListener("error",o);let d=n.address();this._logger?.info?.("Server listening",{port:typeof d=="object"?d?.port:e,host:t??"0.0.0.0"}),a();});}),n}updateTLS(e){let t=this._options.securityProfile??0;if(t!==2&&t!==3)throw new Error("updateTLS() requires a TLS Security Profile (TLS_BASIC_AUTH or TLS_CLIENT_CERT)");this._options.tls={...this._options.tls,...e};let i={};e.cert&&(i.cert=e.cert),e.key&&(i.key=e.key),e.ca&&(i.ca=e.ca),e.passphrase&&(i.passphrase=e.passphrase);let n=0;for(let r of this._httpServers)"setSecureContext"in r&&typeof r.setSecureContext=="function"&&(r.setSecureContext(i),n++);this._logger?.info?.(`TLS context hot-reloaded across ${n} active server(s)`);}get handleUpgrade(){return (e,t,i)=>this._handleUpgrade(e,t,i).catch(n=>{t.destroyed||t.destroy(),this._logger?.error?.("Upgrade error",{error:n.message}),this.emit("upgradeError",{error:n,socket:t});})}async _handleUpgrade(e,t,i){if(this._state!=="OPEN"){_(t,503,"Server is shutting down");return}let n=this._options.connectionRateLimit;if(n){let u=e.socket.remoteAddress??"unknown",b=Date.now(),T=this._connectionBuckets.get(u);if(!T)T={tokens:n.limit,lastRefill:b},this._connectionBuckets.set(u,T);else {let v=b-T.lastRefill,S=n.limit/n.windowMs;T.tokens=Math.min(n.limit,T.tokens+v*S),T.lastRefill=b;}if(T.tokens<1){this._logger?.warn?.("Connection rate limit exceeded",{ip:u}),this.emit("securityEvent",{type:"CONNECTION_RATE_LIMIT",ip:u,timestamp:new Date().toISOString(),details:{tokensRemaining:T.tokens}}),_(t,429,"Too Many Requests");return}T.tokens-=1;}if(t.readyState!=="open"){this._logger?.debug?.("Socket not open at upgrade start"),t.destroyed||t.destroy();return}if(e.headers.upgrade?.toLowerCase()!=="websocket"){_(t,400,"Invalid upgrade request");return}if(this._globalCORS){let{allowed:u,reason:b}=Le(e,this._globalCORS);if(!u){this._logger?.warn?.("CORS rejected connection",{reason:b,ip:e.socket.remoteAddress}),_(t,403,"Forbidden");return}}let r=new URL(e.url??"/",`http://${e.headers.host??"localhost"}`),a,o=[],d=[],p={},c=r.pathname,l=false,m=this._trie.size>0||this._regexRouters.length>0,h;!this._trie.frozen&&this._trie.size>0&&this._trie.freeze();for(let u of this._globalMiddlewareRouters)d.push(u),u.middlewares.length>0&&o.push(...u.middlewares),u.authCallback&&!a&&(a=u.authCallback);let D=this._trie.match(c);if(D){Object.assign(p,D.params);for(let u of D.routers)d.push(u),l=true,u._routeConfig&&(h=Object.assign(h||{},u._routeConfig)),u.middlewares.length>0&&o.push(...u.middlewares),u.authCallback&&!a&&(a=u.authCallback);}for(let u of this._regexRouters)for(let b of u._regexPatterns){let T=b.regex.exec(c);if(T){if(d.push(u),l=true,T.groups)for(let[v,S]of Object.entries(T.groups))p[v]=decodeURIComponent(S??"");u._routeConfig&&(h=Object.assign(h||{},u._routeConfig)),u.middlewares.length>0&&o.push(...u.middlewares),u.authCallback&&!a&&(a=u.authCallback);break}}let f=p.identity;if(!f){let u=c.split("/").filter(Boolean);f=decodeURIComponent(u[u.length-1]??"");}if(!f){_(t,400,"Missing identity in URL path");return}for(let u of d)if(u._routeCORS){let{allowed:b,reason:T}=Le(e,u._routeCORS);if(!b){this._logger?.warn?.("Route CORS rejected connection",{reason:T,ip:e.socket.remoteAddress}),_(t,403,"Forbidden");return}}"setKeepAlive"in t&&t.setKeepAlive(true);let j=new Set,Re=e.headers["sec-websocket-protocol"];if(Re)try{j=Ge(Re);}catch{_(t,400,"Invalid Sec-WebSocket-Protocol header");return}let qe=h?.protocols??this._options.protocols??[],V;if(qe.length>0){if(j.size===0){_(t,400,"Missing subprotocol");return}if(V=qe.find(u=>j.has(u)),!V){_(t,400,"No matching subprotocol");return}}let it=Ke(e.headers.authorization??"",f),Me,Oe=this._options.securityProfile??0;Oe===3&&"getPeerCertificate"in t&&(Me=t.getPeerCertificate());let de={identity:f,remoteAddress:e.socket.remoteAddress??"",headers:e.headers,protocols:j,pathname:c,params:p,query:r.searchParams,request:e,password:it,clientCertificate:Me,securityProfile:Oe},ye,Ve;if(a||o.length>0){let u=new AbortController,b=()=>{u.abort(new Error("Socket closed during handshake"));};t.on("close",b),t.on("error",b),t.on("end",b);let T=this._options.handshakeTimeoutMs??3e4,v;T>0&&(v=setTimeout(()=>{u.abort(new Error("Handshake timeout"));},T));try{ye={handshake:de,state:{},reject:(I=401,x="Unauthorized")=>{throw {code:I,message:x,_isMiddlewareReject:!0}},next:async I=>{}};let S=[...o],P=!1;if(S.push(async I=>{P=!0,a?Ve=await new Promise((x,$)=>{let N=!1,nt=k=>{N||(N=!0,k?.protocol&&(V=k.protocol),x(k));},rt=(k=401,Ne="Unauthorized")=>{throw N||(N=!0,$({code:k,message:Ne})),{code:k,message:Ne,_isMiddlewareReject:!0}};if(u.signal.aborted){$(u.signal.reason);return}u.signal.addEventListener("abort",()=>{N||(N=!0,$(u.signal.reason));},{once:!0}),this._logger?.debug?.("Executing auth callback",{identity:f,pathname:c});let at={handshake:de,state:I.state,reject:rt,signal:u.signal,accept:nt};a(at);}):V=de.protocols.values().next().value??void 0;}),await Xe(S,ye),!P)throw {code:500,message:"Middleware chain halted unexpectedly without rejecting",_isMiddlewareReject:!0}}catch(S){if(u.signal.aborted){let $=S instanceof Error?S.message:"Unknown abort";this._logger?.warn?.("Handshake aborted",{identity:f,reason:$}),this.emit("securityEvent",{type:"UPGRADE_ABORTED",identity:f,ip:e.socket.remoteAddress,timestamp:new Date().toISOString(),details:{reason:$}}),this.emit("upgradeAborted",{identity:f,reason:$,socket:t,request:e}),t.destroyed||t.destroy();return}let P=S,I=typeof P?.code=="number"?P.code:401,x=typeof P?.message=="string"?P.message:"Unauthorized";this._logger?.warn?.("Auth rejected",{identity:f,code:I}),this.emit("securityEvent",{type:"AUTH_FAILED",identity:f,ip:e.socket.remoteAddress,timestamp:new Date().toISOString(),details:{code:I,message:x}}),_(t,I,x);return}finally{v&&clearTimeout(v),t.removeListener("close",b),t.removeListener("error",b),t.removeListener("end",b);}}else if(m&&!l){this._logger?.warn?.("Connection rejected: No matching route found",{pathname:c}),_(t,404,"Endpoint Not Found");return}if(t.readyState!=="open"){this._logger?.debug?.("Socket closed before upgrade completion",{identity:f}),t.destroyed||t.destroy();return}this._wss||(this._wss=new WebSocketServer({noServer:true,maxPayload:this._options.maxPayloadBytes??65536})),this._wss.handleUpgrade(e,t,i,u=>{let b={identity:f,endpoint:"",callTimeoutMs:h?.callTimeoutMs??this._options.callTimeoutMs,pingIntervalMs:h?.pingIntervalMs??this._options.pingIntervalMs,deferPingsOnActivity:h?.deferPingsOnActivity??this._options.deferPingsOnActivity,callConcurrency:h?.callConcurrency??this._options.callConcurrency,maxBadMessages:this._options.maxBadMessages,respondWithDetailedErrors:this._options.respondWithDetailedErrors,strictMode:h?.strictMode??this._options.strictMode,strictModeMethods:h?.strictModeMethods??this._options.strictModeMethods,strictModeValidators:this._options.strictModeValidators,rateLimit:h?.rateLimit??this._options.rateLimit,reconnect:false,logging:this._options.logging},T={...ye?.state||{},...this._sessions.get(f)?.data||{},...Ve?.session||{}},v=new se(b,{ws:u,handshake:de,session:T,protocol:V,adaptiveMultiplier:this._adaptiveLimiter?()=>this._adaptiveLimiter.multiplier:void 0,workerPool:this._workerPool??void 0});this._updateSessionActivity(f,v.session);let S=this._clientsByIdentity.get(f);S&&S!==v&&(this._logger?.warn?.("Evicting stale connection for identity",{identity:f,reason:"Duplicate identity replaced by new connection"}),S.close({code:4e3,reason:"Evicted by new connection",force:true}).catch(()=>{}),this._clients.delete(S)),this._clients.add(v),this._clientsByIdentity.set(f,v),this._adapter?.setPresence&&this._adapter.setPresence(f,this._nodeId,300).catch(P=>{this._logger?.error?.("Error setting presence",{identity:f,error:P});}),this._logger?.info?.("Client connected",{identity:f,remoteAddress:e.socket.remoteAddress,protocol:V}),v.on("close",({code:P,reason:I})=>{this._clients.delete(v),this._clientsByIdentity.get(f)===v&&this._clientsByIdentity.delete(f),this?._adapter?.removePresence&&this._adapter.removePresence(f).catch(x=>{this._logger?.error?.("Error removing presence",{identity:f,error:x});});for(let x of this._plugins)try{x.onDisconnect?.(v,P,I);}catch($){this._logger?.error?.("Plugin onDisconnect error",{name:x.name,error:$.message});}this._logger?.info?.("Client disconnected",{identity:f});});for(let P of this._plugins)try{let I=P.onConnection?.(v);I instanceof Promise&&I.catch(x=>{this._logger?.error?.("Plugin onConnection error",{name:P.name,error:x.message});});}catch(I){this._logger?.error?.("Plugin onConnection error",{name:P.name,error:I.message});}this.emit("client",v);for(let P of d)P.emit("client",v);v.on("message",()=>{this._updateSessionActivity(f,v.session);});});}_updateSessionActivity(e,t){this._sessions.set(e,{data:t,lastActive:Date.now()});}async close(e={}){if(this._state!=="OPEN")return;if(this._state="CLOSING",this.emit("closing"),this._logger?.info?.("Server closing",{clientCount:this._clients.size}),this._gcInterval&&(clearInterval(this._gcInterval),this._gcInterval=null),!e.force){let r=Array.from(this._clients).map(async a=>{let o=a._ws;o&&o.bufferedAmount>0&&(this._logger?.debug?.("Waiting for client buffer to drain",{identity:a.identity,bufferedAmount:o.bufferedAmount}),await new Promise(d=>{let p=0,c=setInterval(()=>{p+=50,(!o||o.bufferedAmount===0||p>=5e3)&&(clearInterval(c),d());},50);}));});await Promise.allSettled(r);}let t=Array.from(this._clients).map(n=>n.close(e).catch(()=>{}));await Promise.allSettled(t);for(let n of this._httpServerAbortControllers)n.abort();this._httpServerAbortControllers.clear(),this._wss&&(this._wss.close(),this._wss=new WebSocketServer({noServer:true}));let i=Array.from(this._httpServers).map(n=>new Promise(r=>{n.close(()=>r());}));await Promise.allSettled(i),this._httpServers.clear(),this._adaptiveLimiter&&this._adaptiveLimiter.stop();for(let n of this._plugins)try{let r=n.onClose?.();r instanceof Promise&&await r;}catch(r){this._logger?.error?.("Plugin onClose error",{name:n.name,error:r.message});}this._adapter&&await this._adapter.disconnect(),this._state="CLOSED",this.emit("close");}reconfigure(e){Object.assign(this._options,e);}async sendToClient(...e){let t,i,n,r;e.length>=4&&typeof e[0]=="string"&&typeof e[1]=="string"&&typeof e[2]=="string"?(t=e[0],i=e[2],n=e[3],r=e[4]):(t=e[0],i=e[1],n=e[2],r=e[3]);for(let a of this._clients)if(a.identity===t)return await a.call(i,n,r);if(this._adapter?.getPresence){let a=await this._adapter.getPresence(t);if(a){await this._adapter.publish(`ocpp:node:${a}`,{source:this._nodeId,target:t,method:i,params:n,options:r});return}}throw new Error(`Client ${t} not found`)}async safeSendToClient(...e){try{return await this.sendToClient(...e)}catch(t){this._logger&&typeof this._logger.warn=="function"&&t.name!=="TimeoutError"&&this._logger.warn("SafeSendToClient failed",{identity:e[0],method:e.length>=4&&typeof e[1]=="string"&&typeof e[2]=="string"?e[2]:e.length>=3&&typeof e[1]=="string"?e[1]:"unknown",error:t});return}}async sendBatch(e,t){if(t.length===0)return [];let i=this._clientsByIdentity.get(e);if(!i)return this._logger?.warn?.("sendBatch: client not found locally",{identity:e}),t.map(()=>{});let n=i.options.callConcurrency??1;t.length>n&&i.reconfigure({callConcurrency:t.length});try{return (await Promise.allSettled(t.map(a=>i.call(a.method,a.params,a.options??{})))).map(a=>{if(a.status==="fulfilled")return a.value;this._logger?.warn?.("sendBatch: individual call failed",{identity:e,error:a.reason?.message});})}finally{t.length>n&&i.reconfigure({callConcurrency:n});}}async setAdapter(e){this._adapter=e,await this._adapter.subscribe("ocpp:broadcast",t=>this._onBroadcast(t)),await this._adapter.subscribe(`ocpp:node:${this._nodeId}`,t=>{this._onUnicast(t);});}_onBroadcast(e){try{if(!e||typeof e!="object")return;let t=e;if(t.source===this._nodeId)return;for(let i of this._clients)i.call(t.method,t.params).catch(()=>{});}catch(t){this._logger?.error?.("Error processing broadcast message",{error:t.message});}}_onUnicast(e){try{if(!e||typeof e!="object")return;let t=e;for(let i of this._clients)if(i.identity===t.target){i.call(t.method,t.params,t.options).catch(n=>{n.name!=="TimeoutError"&&this._logger?.error?.("Error delivering unicast to client",{identity:t.target,error:n});});return}this._logger?.warn?.("Received unicast for unknown client",{target:t.target}),this._adapter?.removePresence&&this._adapter.removePresence(t.target).catch(()=>{});}catch(t){this._logger?.error?.("Error processing unicast",{error:t.message});}}async publish(e,t){this._adapter&&await this._adapter.publish(e,t);}async broadcast(e,t){let i=Array.from(this._clients).map(r=>r.call(e,t).catch(()=>{})),n=this._adapter?this._adapter.publish("ocpp:broadcast",{source:this._nodeId,method:e,params:t}):Promise.resolve();await Promise.all([Promise.all(i),n]);}async broadcastBatch(e,t,i,n){let r=new Set,a=[];for(let d of e){let p=this._clientsByIdentity.get(d);p&&(r.add(d),a.push(p.call(t,i,n).catch(()=>{})));}let o=e.filter(d=>!r.has(d));if(o.length>0&&this._adapter){let d=[];this._adapter.getPresenceBatch?d=await this._adapter.getPresenceBatch(o):this._adapter.getPresence&&(d=await Promise.all(o.map(c=>this._adapter.getPresence(c))));let p=[];for(let c=0;c<o.length;c++){let l=d[c];l&&p.push({channel:`ocpp:node:${l}`,data:{source:this._nodeId,target:o[c],method:t,params:i,options:n}});}p.length>0&&(this._adapter.publishBatch?await this._adapter.publishBatch(p):await Promise.all(p.map(c=>this._adapter.publish(c.channel,c.data))));}await Promise.all(a);}_buildCompressionConfig(){let e=this._options.compression;return e?e===true?{threshold:1024,zlibDeflateOptions:{level:6,memLevel:8},zlibInflateOptions:{},serverNoContextTakeover:true,clientNoContextTakeover:true}:{threshold:e.threshold??1024,zlibDeflateOptions:{level:e.level??6,memLevel:e.memLevel??8},zlibInflateOptions:{},serverNoContextTakeover:e.serverNoContextTakeover??true,clientNoContextTakeover:e.clientNoContextTakeover??true}:false}};
4826
- export{H as AdaptiveLimiter,ve as ClusterDriver,ne as ConnectionState,Te as InMemoryAdapter,oe as LRUMap,E as MessageType,ce as MiddlewareStack,we as NOREPLY,ae as OCPPClient,A as OCPPRouter,je as OCPPServer,se as OCPPServerClient,Q as RPCFormatViolationError,X as RPCFormationViolationError,te as RPCFrameworkError,C as RPCGenericError,G as RPCInternalError,L as RPCMessageTypeNotSupportedError,R as RPCNotImplementedError,B as RPCNotSupportedError,Z as RPCOccurrenceConstraintViolationError,Y as RPCPropertyConstraintViolationError,J as RPCProtocolError,K as RPCSecurityError,ee as RPCTypeConstraintViolationError,Pe as RedisAdapter,fe as SecurityProfile,F as TimeoutError,z as UnexpectedHttpResponse,me as Validator,Se as WebsocketUpgradeError,Ct as combineAuth,Ie as createLoggingMiddleware,yt as createPlugin,q as createRPCError,zt as createRouter,ie as createValidator,ct as defineAdapter,vt as defineAuth,gt as defineMiddleware,Tt as defineRpcMiddleware,Ee as getErrorPlainObject,De as getPackageIdent,xe as getStandardValidators};//# sourceMappingURL=index.mjs.map
4827
- //# sourceMappingURL=index.mjs.map
4826
+ export{H as AdaptiveLimiter,ve as ClusterDriver,ne as ConnectionState,Te as InMemoryAdapter,oe as LRUMap,E as MessageType,ce as MiddlewareStack,we as NOREPLY,ae as OCPPClient,A as OCPPRouter,je as OCPPServer,se as OCPPServerClient,Q as RPCFormatViolationError,X as RPCFormationViolationError,te as RPCFrameworkError,C as RPCGenericError,G as RPCInternalError,L as RPCMessageTypeNotSupportedError,R as RPCNotImplementedError,B as RPCNotSupportedError,Z as RPCOccurrenceConstraintViolationError,Y as RPCPropertyConstraintViolationError,J as RPCProtocolError,K as RPCSecurityError,ee as RPCTypeConstraintViolationError,Pe as RedisAdapter,fe as SecurityProfile,F as TimeoutError,z as UnexpectedHttpResponse,me as Validator,Se as WebsocketUpgradeError,Ct as combineAuth,Ie as createLoggingMiddleware,yt as createPlugin,q as createRPCError,zt as createRouter,ie as createValidator,ct as defineAdapter,vt as defineAuth,gt as defineMiddleware,Tt as defineRpcMiddleware,Ee as getErrorPlainObject,De as getPackageIdent,xe as getStandardValidators};
package/dist/logger.js CHANGED
@@ -1,2 +1 @@
1
- 'use strict';var voltlogIo=require('voltlog-io');Object.keys(voltlogIo).forEach(function(k){if(k!=='default'&&!Object.prototype.hasOwnProperty.call(exports,k))Object.defineProperty(exports,k,{enumerable:true,get:function(){return voltlogIo[k]}})});//# sourceMappingURL=logger.js.map
2
- //# sourceMappingURL=logger.js.map
1
+ 'use strict';var voltlogIo=require('voltlog-io');Object.keys(voltlogIo).forEach(function(k){if(k!=='default'&&!Object.prototype.hasOwnProperty.call(exports,k))Object.defineProperty(exports,k,{enumerable:true,get:function(){return voltlogIo[k]}})});
package/dist/logger.mjs CHANGED
@@ -1,2 +1 @@
1
- export*from'voltlog-io';//# sourceMappingURL=logger.mjs.map
2
- //# sourceMappingURL=logger.mjs.map
1
+ export*from'voltlog-io';
package/dist/plugins.js CHANGED
@@ -1,2 +1 @@
1
- 'use strict';var crypto=require('crypto');var P=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(i,r)=>(typeof require<"u"?require:i)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});function y(t){let i=t?.reconnectThreshold??5,r=t?.windowMs??6e4,e=new Map,n=null,o=null;function u(a,s){let l=s-r,c=0;for(;c<a.length&&a[c]<l;)c++;return c>0?a.slice(c):a}return {name:"anomaly",onInit(a){n=a,o=setInterval(()=>{let s=Date.now();for(let[l,c]of e){let g=u(c,s);g.length===0?e.delete(l):e.set(l,g);}},r).unref();},onConnection(a){let s=Date.now(),l=a.identity,c=e.get(l)??[];c=u(c,s),c.push(s),e.set(l,c),c.length>i&&n&&n.emit("securityEvent",{type:"ANOMALY_RAPID_RECONNECT",identity:l,ip:a.handshake.remoteAddress,timestamp:new Date().toISOString(),details:{connectionsInWindow:c.length,threshold:i,windowMs:r}});},onClose(){o&&(clearInterval(o),o=null),e.clear(),n=null;}}}function f(t){let i=0;return {name:"connection-guard",onConnection(r){i++,i>t.maxConnections&&r.close({code:4001,reason:"Connection limit reached",force:true}).catch(()=>{});},onDisconnect(){i=Math.max(0,i-1);},onClose(){i=0;}}}function h(){return {name:"heartbeat",onConnection(t){t.handle("Heartbeat",()=>({currentTime:new Date().toISOString()}));}}}function b(t){let i=t?.intervalMs??3e4,r=0,e=0,n=0,o=0,u=0,a=Date.now(),s=null,l=new Map;function c(){return {totalConnections:r,totalDisconnections:e,activeConnections:n,peakConnections:o,connectionDurationAvgMs:e>0?Math.round(u/e):0,uptimeMs:Date.now()-a,timestamp:new Date().toISOString()}}return {name:"metrics",getMetrics:c,onInit(){a=Date.now(),i>0&&t?.onSnapshot&&(s=setInterval(()=>{t.onSnapshot(c());},i),s&&typeof s=="object"&&"unref"in s&&s.unref());},onConnection(m){r++,n++,n>o&&(o=n),l.set(m.identity,Date.now());},onDisconnect(m){e++,n=Math.max(0,n-1);let d=l.get(m.identity);d&&(u+=Date.now()-d,l.delete(m.identity));},onClose(){s&&(clearInterval(s),s=null),l.clear();}}}function O(t){let i=t?.tracer??null,r=new Map;return {name:"otel",onInit(e){if(!i)try{let{createRequire:n}=P("module");i=n(__filename)("@opentelemetry/api").trace.getTracer(t?.serviceName??"ocpp-server","1.0.0");}catch{e.log.warn?.("otelPlugin: @opentelemetry/api not found \u2014 plugin disabled. Install it as a peer dependency."),i=null;}},onConnection(e){if(!i)return;let n=i.startSpan("ocpp.connection",{kind:1});n.setAttribute("ocpp.identity",e.identity),n.setAttribute("ocpp.protocol",e.protocol??"unknown"),n.setAttribute("net.peer.ip",e.handshake.remoteAddress),r.set(e.identity,{span:n,startTime:Date.now()});},onDisconnect(e,n){let o=r.get(e.identity);if(!o)return;let u=Date.now()-o.startTime;o.span.setAttribute("ocpp.close_code",n),o.span.setAttribute("ocpp.duration_ms",u),o.span.setStatus({code:1}),o.span.end(),r.delete(e.identity);},onClose(){for(let[,e]of r)e.span.setStatus({code:2,message:"Server shutdown"}),e.span.end();r.clear();}}}function C(t){let i=t?.logger??console,r=new Map;return {name:"session-log",onConnection(e){r.set(e.identity,Date.now()),i.info("Connected",{identity:e.identity,ip:e.handshake.remoteAddress,protocol:e.protocol});},onDisconnect(e,n,o){let u=r.get(e.identity),a=u?Math.round((Date.now()-u)/1e3):0;r.delete(e.identity),i.info("Disconnected",{identity:e.identity,durationSec:a,code:n,reason:o});},onClose(){r.clear();}}}function v(t){let i=new Set(t.events??["init","connect","disconnect","close"]),r=t.timeout??5e3,e=t.retries??1;async function n(o){if(!i.has(o.event))return;let u=JSON.stringify(o),a={"Content-Type":"application/json",...t.headers};if(t.secret){let s=crypto.createHmac("sha256",t.secret).update(u).digest("hex");a["X-Signature"]=s;}for(let s=0;s<=e;s++)try{let l=new AbortController,c=setTimeout(()=>l.abort(),r);await fetch(t.url,{method:"POST",headers:a,body:u,signal:l.signal}),clearTimeout(c);return}catch{}}return {name:"webhook",onInit(){n({event:"init",timestamp:new Date().toISOString()}).catch(()=>{});},onConnection(o){n({event:"connect",timestamp:new Date().toISOString(),data:{identity:o.identity,ip:o.handshake.remoteAddress,protocol:o.protocol}}).catch(()=>{});},onDisconnect(o,u,a){n({event:"disconnect",timestamp:new Date().toISOString(),data:{identity:o.identity,code:u,reason:a}}).catch(()=>{});},onClose(){n({event:"close",timestamp:new Date().toISOString()}).catch(()=>{});}}}exports.anomalyPlugin=y;exports.connectionGuardPlugin=f;exports.heartbeatPlugin=h;exports.metricsPlugin=b;exports.otelPlugin=O;exports.sessionLogPlugin=C;exports.webhookPlugin=v;//# sourceMappingURL=plugins.js.map
2
- //# sourceMappingURL=plugins.js.map
1
+ 'use strict';var crypto=require('crypto');var P=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(i,r)=>(typeof require<"u"?require:i)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});function y(t){let i=t?.reconnectThreshold??5,r=t?.windowMs??6e4,e=new Map,n=null,o=null;function u(a,s){let l=s-r,c=0;for(;c<a.length&&a[c]<l;)c++;return c>0?a.slice(c):a}return {name:"anomaly",onInit(a){n=a,o=setInterval(()=>{let s=Date.now();for(let[l,c]of e){let g=u(c,s);g.length===0?e.delete(l):e.set(l,g);}},r).unref();},onConnection(a){let s=Date.now(),l=a.identity,c=e.get(l)??[];c=u(c,s),c.push(s),e.set(l,c),c.length>i&&n&&n.emit("securityEvent",{type:"ANOMALY_RAPID_RECONNECT",identity:l,ip:a.handshake.remoteAddress,timestamp:new Date().toISOString(),details:{connectionsInWindow:c.length,threshold:i,windowMs:r}});},onClose(){o&&(clearInterval(o),o=null),e.clear(),n=null;}}}function f(t){let i=0;return {name:"connection-guard",onConnection(r){i++,i>t.maxConnections&&r.close({code:4001,reason:"Connection limit reached",force:true}).catch(()=>{});},onDisconnect(){i=Math.max(0,i-1);},onClose(){i=0;}}}function h(){return {name:"heartbeat",onConnection(t){t.handle("Heartbeat",()=>({currentTime:new Date().toISOString()}));}}}function b(t){let i=t?.intervalMs??3e4,r=0,e=0,n=0,o=0,u=0,a=Date.now(),s=null,l=new Map;function c(){return {totalConnections:r,totalDisconnections:e,activeConnections:n,peakConnections:o,connectionDurationAvgMs:e>0?Math.round(u/e):0,uptimeMs:Date.now()-a,timestamp:new Date().toISOString()}}return {name:"metrics",getMetrics:c,onInit(){a=Date.now(),i>0&&t?.onSnapshot&&(s=setInterval(()=>{t.onSnapshot(c());},i),s&&typeof s=="object"&&"unref"in s&&s.unref());},onConnection(m){r++,n++,n>o&&(o=n),l.set(m.identity,Date.now());},onDisconnect(m){e++,n=Math.max(0,n-1);let d=l.get(m.identity);d&&(u+=Date.now()-d,l.delete(m.identity));},onClose(){s&&(clearInterval(s),s=null),l.clear();}}}function O(t){let i=t?.tracer??null,r=new Map;return {name:"otel",onInit(e){if(!i)try{let{createRequire:n}=P("module");i=n(__filename)("@opentelemetry/api").trace.getTracer(t?.serviceName??"ocpp-server","1.0.0");}catch{e.log.warn?.("otelPlugin: @opentelemetry/api not found \u2014 plugin disabled. Install it as a peer dependency."),i=null;}},onConnection(e){if(!i)return;let n=i.startSpan("ocpp.connection",{kind:1});n.setAttribute("ocpp.identity",e.identity),n.setAttribute("ocpp.protocol",e.protocol??"unknown"),n.setAttribute("net.peer.ip",e.handshake.remoteAddress),r.set(e.identity,{span:n,startTime:Date.now()});},onDisconnect(e,n){let o=r.get(e.identity);if(!o)return;let u=Date.now()-o.startTime;o.span.setAttribute("ocpp.close_code",n),o.span.setAttribute("ocpp.duration_ms",u),o.span.setStatus({code:1}),o.span.end(),r.delete(e.identity);},onClose(){for(let[,e]of r)e.span.setStatus({code:2,message:"Server shutdown"}),e.span.end();r.clear();}}}function C(t){let i=t?.logger??console,r=new Map;return {name:"session-log",onConnection(e){r.set(e.identity,Date.now()),i.info("Connected",{identity:e.identity,ip:e.handshake.remoteAddress,protocol:e.protocol});},onDisconnect(e,n,o){let u=r.get(e.identity),a=u?Math.round((Date.now()-u)/1e3):0;r.delete(e.identity),i.info("Disconnected",{identity:e.identity,durationSec:a,code:n,reason:o});},onClose(){r.clear();}}}function v(t){let i=new Set(t.events??["init","connect","disconnect","close"]),r=t.timeout??5e3,e=t.retries??1;async function n(o){if(!i.has(o.event))return;let u=JSON.stringify(o),a={"Content-Type":"application/json",...t.headers};if(t.secret){let s=crypto.createHmac("sha256",t.secret).update(u).digest("hex");a["X-Signature"]=s;}for(let s=0;s<=e;s++)try{let l=new AbortController,c=setTimeout(()=>l.abort(),r);await fetch(t.url,{method:"POST",headers:a,body:u,signal:l.signal}),clearTimeout(c);return}catch{}}return {name:"webhook",onInit(){n({event:"init",timestamp:new Date().toISOString()}).catch(()=>{});},onConnection(o){n({event:"connect",timestamp:new Date().toISOString(),data:{identity:o.identity,ip:o.handshake.remoteAddress,protocol:o.protocol}}).catch(()=>{});},onDisconnect(o,u,a){n({event:"disconnect",timestamp:new Date().toISOString(),data:{identity:o.identity,code:u,reason:a}}).catch(()=>{});},onClose(){n({event:"close",timestamp:new Date().toISOString()}).catch(()=>{});}}}exports.anomalyPlugin=y;exports.connectionGuardPlugin=f;exports.heartbeatPlugin=h;exports.metricsPlugin=b;exports.otelPlugin=O;exports.sessionLogPlugin=C;exports.webhookPlugin=v;
package/dist/plugins.mjs CHANGED
@@ -1,2 +1 @@
1
- import'path';import {fileURLToPath}from'url';import {createHmac}from'crypto';var y=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(i,r)=>(typeof require<"u"?require:i)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var h=()=>fileURLToPath(import.meta.url);var p=h();function b(t){let i=t?.reconnectThreshold??5,r=t?.windowMs??6e4,e=new Map,n=null,o=null;function l(a,s){let u=s-r,c=0;for(;c<a.length&&a[c]<u;)c++;return c>0?a.slice(c):a}return {name:"anomaly",onInit(a){n=a,o=setInterval(()=>{let s=Date.now();for(let[u,c]of e){let d=l(c,s);d.length===0?e.delete(u):e.set(u,d);}},r).unref();},onConnection(a){let s=Date.now(),u=a.identity,c=e.get(u)??[];c=l(c,s),c.push(s),e.set(u,c),c.length>i&&n&&n.emit("securityEvent",{type:"ANOMALY_RAPID_RECONNECT",identity:u,ip:a.handshake.remoteAddress,timestamp:new Date().toISOString(),details:{connectionsInWindow:c.length,threshold:i,windowMs:r}});},onClose(){o&&(clearInterval(o),o=null),e.clear(),n=null;}}}function O(t){let i=0;return {name:"connection-guard",onConnection(r){i++,i>t.maxConnections&&r.close({code:4001,reason:"Connection limit reached",force:true}).catch(()=>{});},onDisconnect(){i=Math.max(0,i-1);},onClose(){i=0;}}}function C(){return {name:"heartbeat",onConnection(t){t.handle("Heartbeat",()=>({currentTime:new Date().toISOString()}));}}}function w(t){let i=t?.intervalMs??3e4,r=0,e=0,n=0,o=0,l=0,a=Date.now(),s=null,u=new Map;function c(){return {totalConnections:r,totalDisconnections:e,activeConnections:n,peakConnections:o,connectionDurationAvgMs:e>0?Math.round(l/e):0,uptimeMs:Date.now()-a,timestamp:new Date().toISOString()}}return {name:"metrics",getMetrics:c,onInit(){a=Date.now(),i>0&&t?.onSnapshot&&(s=setInterval(()=>{t.onSnapshot(c());},i),s&&typeof s=="object"&&"unref"in s&&s.unref());},onConnection(g){r++,n++,n>o&&(o=n),u.set(g.identity,Date.now());},onDisconnect(g){e++,n=Math.max(0,n-1);let P=u.get(g.identity);P&&(l+=Date.now()-P,u.delete(g.identity));},onClose(){s&&(clearInterval(s),s=null),u.clear();}}}function v(t){let i=t?.tracer??null,r=new Map;return {name:"otel",onInit(e){if(!i)try{let{createRequire:n}=y("module");i=n(p)("@opentelemetry/api").trace.getTracer(t?.serviceName??"ocpp-server","1.0.0");}catch{e.log.warn?.("otelPlugin: @opentelemetry/api not found \u2014 plugin disabled. Install it as a peer dependency."),i=null;}},onConnection(e){if(!i)return;let n=i.startSpan("ocpp.connection",{kind:1});n.setAttribute("ocpp.identity",e.identity),n.setAttribute("ocpp.protocol",e.protocol??"unknown"),n.setAttribute("net.peer.ip",e.handshake.remoteAddress),r.set(e.identity,{span:n,startTime:Date.now()});},onDisconnect(e,n){let o=r.get(e.identity);if(!o)return;let l=Date.now()-o.startTime;o.span.setAttribute("ocpp.close_code",n),o.span.setAttribute("ocpp.duration_ms",l),o.span.setStatus({code:1}),o.span.end(),r.delete(e.identity);},onClose(){for(let[,e]of r)e.span.setStatus({code:2,message:"Server shutdown"}),e.span.end();r.clear();}}}function S(t){let i=t?.logger??console,r=new Map;return {name:"session-log",onConnection(e){r.set(e.identity,Date.now()),i.info("Connected",{identity:e.identity,ip:e.handshake.remoteAddress,protocol:e.protocol});},onDisconnect(e,n,o){let l=r.get(e.identity),a=l?Math.round((Date.now()-l)/1e3):0;r.delete(e.identity),i.info("Disconnected",{identity:e.identity,durationSec:a,code:n,reason:o});},onClose(){r.clear();}}}function x(t){let i=new Set(t.events??["init","connect","disconnect","close"]),r=t.timeout??5e3,e=t.retries??1;async function n(o){if(!i.has(o.event))return;let l=JSON.stringify(o),a={"Content-Type":"application/json",...t.headers};if(t.secret){let s=createHmac("sha256",t.secret).update(l).digest("hex");a["X-Signature"]=s;}for(let s=0;s<=e;s++)try{let u=new AbortController,c=setTimeout(()=>u.abort(),r);await fetch(t.url,{method:"POST",headers:a,body:l,signal:u.signal}),clearTimeout(c);return}catch{}}return {name:"webhook",onInit(){n({event:"init",timestamp:new Date().toISOString()}).catch(()=>{});},onConnection(o){n({event:"connect",timestamp:new Date().toISOString(),data:{identity:o.identity,ip:o.handshake.remoteAddress,protocol:o.protocol}}).catch(()=>{});},onDisconnect(o,l,a){n({event:"disconnect",timestamp:new Date().toISOString(),data:{identity:o.identity,code:l,reason:a}}).catch(()=>{});},onClose(){n({event:"close",timestamp:new Date().toISOString()}).catch(()=>{});}}}export{b as anomalyPlugin,O as connectionGuardPlugin,C as heartbeatPlugin,w as metricsPlugin,v as otelPlugin,S as sessionLogPlugin,x as webhookPlugin};//# sourceMappingURL=plugins.mjs.map
2
- //# sourceMappingURL=plugins.mjs.map
1
+ import'path';import {fileURLToPath}from'url';import {createHmac}from'crypto';var y=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(i,r)=>(typeof require<"u"?require:i)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var h=()=>fileURLToPath(import.meta.url);var p=h();function b(t){let i=t?.reconnectThreshold??5,r=t?.windowMs??6e4,e=new Map,n=null,o=null;function l(a,s){let u=s-r,c=0;for(;c<a.length&&a[c]<u;)c++;return c>0?a.slice(c):a}return {name:"anomaly",onInit(a){n=a,o=setInterval(()=>{let s=Date.now();for(let[u,c]of e){let d=l(c,s);d.length===0?e.delete(u):e.set(u,d);}},r).unref();},onConnection(a){let s=Date.now(),u=a.identity,c=e.get(u)??[];c=l(c,s),c.push(s),e.set(u,c),c.length>i&&n&&n.emit("securityEvent",{type:"ANOMALY_RAPID_RECONNECT",identity:u,ip:a.handshake.remoteAddress,timestamp:new Date().toISOString(),details:{connectionsInWindow:c.length,threshold:i,windowMs:r}});},onClose(){o&&(clearInterval(o),o=null),e.clear(),n=null;}}}function O(t){let i=0;return {name:"connection-guard",onConnection(r){i++,i>t.maxConnections&&r.close({code:4001,reason:"Connection limit reached",force:true}).catch(()=>{});},onDisconnect(){i=Math.max(0,i-1);},onClose(){i=0;}}}function C(){return {name:"heartbeat",onConnection(t){t.handle("Heartbeat",()=>({currentTime:new Date().toISOString()}));}}}function w(t){let i=t?.intervalMs??3e4,r=0,e=0,n=0,o=0,l=0,a=Date.now(),s=null,u=new Map;function c(){return {totalConnections:r,totalDisconnections:e,activeConnections:n,peakConnections:o,connectionDurationAvgMs:e>0?Math.round(l/e):0,uptimeMs:Date.now()-a,timestamp:new Date().toISOString()}}return {name:"metrics",getMetrics:c,onInit(){a=Date.now(),i>0&&t?.onSnapshot&&(s=setInterval(()=>{t.onSnapshot(c());},i),s&&typeof s=="object"&&"unref"in s&&s.unref());},onConnection(g){r++,n++,n>o&&(o=n),u.set(g.identity,Date.now());},onDisconnect(g){e++,n=Math.max(0,n-1);let P=u.get(g.identity);P&&(l+=Date.now()-P,u.delete(g.identity));},onClose(){s&&(clearInterval(s),s=null),u.clear();}}}function v(t){let i=t?.tracer??null,r=new Map;return {name:"otel",onInit(e){if(!i)try{let{createRequire:n}=y("module");i=n(p)("@opentelemetry/api").trace.getTracer(t?.serviceName??"ocpp-server","1.0.0");}catch{e.log.warn?.("otelPlugin: @opentelemetry/api not found \u2014 plugin disabled. Install it as a peer dependency."),i=null;}},onConnection(e){if(!i)return;let n=i.startSpan("ocpp.connection",{kind:1});n.setAttribute("ocpp.identity",e.identity),n.setAttribute("ocpp.protocol",e.protocol??"unknown"),n.setAttribute("net.peer.ip",e.handshake.remoteAddress),r.set(e.identity,{span:n,startTime:Date.now()});},onDisconnect(e,n){let o=r.get(e.identity);if(!o)return;let l=Date.now()-o.startTime;o.span.setAttribute("ocpp.close_code",n),o.span.setAttribute("ocpp.duration_ms",l),o.span.setStatus({code:1}),o.span.end(),r.delete(e.identity);},onClose(){for(let[,e]of r)e.span.setStatus({code:2,message:"Server shutdown"}),e.span.end();r.clear();}}}function S(t){let i=t?.logger??console,r=new Map;return {name:"session-log",onConnection(e){r.set(e.identity,Date.now()),i.info("Connected",{identity:e.identity,ip:e.handshake.remoteAddress,protocol:e.protocol});},onDisconnect(e,n,o){let l=r.get(e.identity),a=l?Math.round((Date.now()-l)/1e3):0;r.delete(e.identity),i.info("Disconnected",{identity:e.identity,durationSec:a,code:n,reason:o});},onClose(){r.clear();}}}function x(t){let i=new Set(t.events??["init","connect","disconnect","close"]),r=t.timeout??5e3,e=t.retries??1;async function n(o){if(!i.has(o.event))return;let l=JSON.stringify(o),a={"Content-Type":"application/json",...t.headers};if(t.secret){let s=createHmac("sha256",t.secret).update(l).digest("hex");a["X-Signature"]=s;}for(let s=0;s<=e;s++)try{let u=new AbortController,c=setTimeout(()=>u.abort(),r);await fetch(t.url,{method:"POST",headers:a,body:l,signal:u.signal}),clearTimeout(c);return}catch{}}return {name:"webhook",onInit(){n({event:"init",timestamp:new Date().toISOString()}).catch(()=>{});},onConnection(o){n({event:"connect",timestamp:new Date().toISOString(),data:{identity:o.identity,ip:o.handshake.remoteAddress,protocol:o.protocol}}).catch(()=>{});},onDisconnect(o,l,a){n({event:"disconnect",timestamp:new Date().toISOString(),data:{identity:o.identity,code:l,reason:a}}).catch(()=>{});},onClose(){n({event:"close",timestamp:new Date().toISOString()}).catch(()=>{});}}}export{b as anomalyPlugin,O as connectionGuardPlugin,C as heartbeatPlugin,w as metricsPlugin,v as otelPlugin,S as sessionLogPlugin,x as webhookPlugin};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocpp-ws-io",
3
- "version": "2.1.12",
3
+ "version": "2.1.14",
4
4
  "description": "Type-safe OCPP 1.6 & 2.0.1 WebSocket RPC client & server. Build EV charging CSMS platforms with Redis clustering, strict validation, and DDoS protection.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -76,7 +76,13 @@
76
76
  "clustering",
77
77
  "pubsub",
78
78
  "browser",
79
- "react-native"
79
+ "simulator",
80
+ "ocpp-simulator",
81
+ "nodejs-library",
82
+ "testing-cli",
83
+ "ocpp-testing",
84
+ "ocpp-implementation",
85
+ "ocpp-docs"
80
86
  ],
81
87
  "author": "Rohit Tiwari <rohit@rohittiwari.me>",
82
88
  "homepage": "https://ocpp-ws-io.rohittiwari.me",
@@ -90,7 +96,7 @@
90
96
  "dependencies": {
91
97
  "ajv": "^8.18.0",
92
98
  "ajv-formats": "^3.0.1",
93
- "voltlog-io": "^1.0.4",
99
+ "voltlog-io": "^1.0.8",
94
100
  "ws": "^8.19.0"
95
101
  },
96
102
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/adapters/redis/helpers.ts","../../src/adapters/redis/index.ts"],"names":["IoRedisDriver","pub","sub","blocking","channel","message","handler","key","value","ttlSeconds","keys","stream","args","maxLen","flatArgs","k","v","messages","pipeline","msg","streams","count","block","s","result","id","fields","data","i","close","c","entries","NodeRedisDriver","multi","options","streamsParam","entry","createDriver","RedisAdapter","poolSize","err","driver","prefixedChannel","payload","seq","poolDriver","streamMessages","broadcastMessages","promises","bm","d","handlers","resolve","streamsArg","messageContent","identity","nodeId","ttl","identities","pendingMessages","streamDetails","streamKey","length","batchEntries"],"mappings":"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","file":"redis.js","sourcesContent":["export interface RedisLikeClient {\n publish(\n channel: string,\n message: string,\n ): Promise<number | unknown | undefined>;\n subscribe(channel: string, ...args: unknown[]): Promise<unknown | undefined>;\n unsubscribe(\n channel: string,\n ...args: unknown[]\n ): Promise<unknown | undefined>;\n on?(\n event: \"message\",\n callback: (channel: string, message: string) => void,\n ): unknown;\n disconnect?(): Promise<void> | void;\n quit?(): Promise<unknown> | undefined;\n // Node Redis v4 specific\n isOpen?: boolean;\n}\n\n// ─── Stream Types ───────────────────────────────────────────────\n\nexport interface StreamMessage {\n id: string;\n data: Record<string, string>;\n}\n\nexport interface StreamEntry {\n stream: string;\n messages: StreamMessage[];\n}\n\n// ─── Extended Redis Driver ──────────────────────────────────────\n\nexport interface RedisPubSubDriver {\n publish(channel: string, message: string): Promise<void>;\n subscribe(channel: string, handler: (message: string) => void): Promise<void>;\n unsubscribe(channel: string): Promise<void>;\n disconnect(): Promise<void>;\n\n // Key-Value Store for Presence\n set(key: string, value: string, ttlSeconds?: number): Promise<void>;\n get(key: string): Promise<string | null>;\n mget(keys: string[]): Promise<(string | null)[]>;\n del(key: string): Promise<void>;\n\n /**\n * Batch set multiple presence keys with TTL in a single pipeline.\n * Falls back to sequential sets if pipelining is unavailable.\n */\n setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void>;\n\n /**\n * Set a TTL on an existing key (for ephemeral stream channel leases).\n */\n expire(key: string, ttlSeconds: number): Promise<void>;\n\n /**\n * Subscribe to connection error events for rehydration.\n * Returns an unsubscribe function.\n */\n onError?(handler: (err: Error) => void): () => void;\n\n /**\n * Subscribe to reconnection events.\n * Returns an unsubscribe function.\n */\n onReconnect?(handler: () => void): () => void;\n\n // Streams\n xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string>;\n xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void>;\n xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null>;\n xlen(stream: string): Promise<number>;\n}\n\nexport class IoRedisDriver implements RedisPubSubDriver {\n private _handlers = new Map<string, (msg: string) => void>();\n\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {\n if (this.sub.on) {\n this.sub.on(\"message\", (channel: string, message: string) => {\n const handler = this._handlers.get(channel);\n if (handler) handler(message);\n });\n }\n }\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n this._handlers.set(channel, handler);\n await this.sub.subscribe(channel);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n this._handlers.delete(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, \"EX\", ttlSeconds);\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mget(...keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\"); // ID = auto\n for (const [k, v] of Object.entries(args)) {\n flatArgs.push(k, v);\n }\n return (await this.pub.xadd(stream, ...flatArgs)) as string;\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const msg of messages) {\n const flatArgs: string[] = [];\n if (maxLen) {\n flatArgs.push(\"MAXLEN\", \"~\", maxLen.toString());\n }\n flatArgs.push(\"*\");\n for (const [k, v] of Object.entries(msg.args)) {\n flatArgs.push(k, v);\n }\n pipeline.xadd(msg.stream, ...flatArgs);\n }\n await pipeline.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n const args: (string | number)[] = [];\n if (count) {\n args.push(\"COUNT\", count);\n }\n if (typeof block === \"number\") {\n args.push(\"BLOCK\", block);\n }\n args.push(\"STREAMS\");\n streams.forEach((s) => {\n args.push(s.key);\n });\n streams.forEach((s) => {\n args.push(s.id);\n });\n\n // Use blocking client if available and blocking is requested\n const client = block && this.blocking ? this.blocking : this.pub;\n\n // ioredis returns [[key, [[id, [k,v,k,v]]]]]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = (await client.xread(...args)) as any;\n\n if (!result) return null;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map(([stream, messages]: any) => ({\n stream,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: messages.map(([id, fields]: any) => {\n const data: Record<string, string> = {};\n for (let i = 0; i < fields.length; i += 2) {\n data[fields[i]] = fields[i + 1];\n }\n return { id, data };\n }),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xlen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n this._handlers.clear();\n const close = async (c: any) => {\n if (c.quit) await c.quit();\n else if (c.disconnect) await c.disconnect();\n };\n await Promise.all([close(this.pub), close(this.sub)]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const pipeline = this.pub.pipeline();\n for (const { key, value, ttlSeconds } of entries) {\n pipeline.set(key, value, \"EX\", ttlSeconds);\n }\n await pipeline.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport class NodeRedisDriver implements RedisPubSubDriver {\n constructor(\n private pub: any,\n private sub: any,\n private blocking?: any,\n ) {}\n\n async publish(channel: string, message: string): Promise<void> {\n await this.pub.publish(channel, message);\n }\n\n async subscribe(\n channel: string,\n handler: (message: string) => void,\n ): Promise<void> {\n await this.sub.subscribe(channel, handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n await this.sub.unsubscribe(channel);\n }\n\n async set(key: string, value: string, ttlSeconds?: number): Promise<void> {\n if (ttlSeconds) {\n await this.pub.set(key, value, { EX: ttlSeconds });\n } else {\n await this.pub.set(key, value);\n }\n }\n\n async get(key: string): Promise<string | null> {\n return (await this.pub.get(key)) || null;\n }\n\n async mget(keys: string[]): Promise<(string | null)[]> {\n if (keys.length === 0) return [];\n return await this.pub.mGet(keys);\n }\n\n async del(key: string): Promise<void> {\n await this.pub.del(key);\n }\n\n async xadd(\n stream: string,\n args: Record<string, string>,\n maxLen?: number,\n ): Promise<string> {\n const options: any = {};\n if (maxLen) {\n options.MKSTREAM = true; // Make sure stream exists\n // node-redis specific options for MAXLEN\n // But basic xadd signature is (key, id, message, options?)\n }\n // Node Redis v4 xAdd: (key, id, message)\n // For trimming, it might be in options.\n // Let's assume standard usage for now.\n // Actually Node Redis v4: .xAdd(key, id, message, options)\n\n // Construct message object\n return await this.pub.xAdd(stream, \"*\", args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n\n async xaddBatch(\n messages: { stream: string; args: Record<string, string> }[],\n maxLen?: number,\n ): Promise<void> {\n if (messages.length === 0) return;\n const multi = this.pub.multi();\n for (const msg of messages) {\n multi.xAdd(msg.stream, \"*\", msg.args, {\n TRIM: maxLen\n ? {\n strategy: \"MAXLEN\",\n strategyModifier: \"~\",\n threshold: maxLen,\n }\n : undefined,\n });\n }\n await multi.exec();\n }\n\n async xread(\n streams: { key: string; id: string }[],\n count?: number,\n block?: number,\n ): Promise<StreamEntry[] | null> {\n // Node Redis v4 .xRead(streams, options)\n const options: any = {};\n if (count) options.COUNT = count;\n if (typeof block === \"number\") options.BLOCK = block;\n\n const streamsParam = streams.map((s) => ({\n key: s.key,\n id: s.id,\n }));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const client = block && this.blocking ? this.blocking : this.pub;\n const result = (await client.xRead(streamsParam, options)) as any;\n\n if (!result || result.length === 0) return null;\n\n // Node Redis v4 returns: { name: string, messages: { id: string, message: Record<string,string> }[] }[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return result.map((entry: any) => ({\n stream: entry.name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messages: entry.messages.map((msg: any) => ({\n id: msg.id,\n data: msg.message,\n })),\n }));\n }\n\n async xlen(stream: string): Promise<number> {\n return (await this.pub.xLen(stream)) as number;\n }\n\n async disconnect(): Promise<void> {\n await Promise.all([this.pub.disconnect(), this.sub.disconnect()]);\n }\n\n async setPresenceBatch(\n entries: { key: string; value: string; ttlSeconds: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n const multi = this.pub.multi();\n for (const { key, value, ttlSeconds } of entries) {\n multi.set(key, value, { EX: ttlSeconds });\n }\n await multi.exec();\n }\n\n async expire(key: string, ttlSeconds: number): Promise<void> {\n await this.pub.expire(key, ttlSeconds);\n }\n\n onError(handler: (err: Error) => void): () => void {\n this.pub.on(\"error\", handler);\n return () => this.pub.removeListener(\"error\", handler);\n }\n\n onReconnect(handler: () => void): () => void {\n this.pub.on(\"connect\", handler);\n return () => this.pub.removeListener(\"connect\", handler);\n }\n}\n\nexport function createDriver(\n pub: any,\n sub: any,\n blocking?: any,\n): RedisPubSubDriver {\n // Simple heuristic: Node Redis v4 clients usually have 'isOpen' boolean\n if (sub.isOpen !== undefined && typeof sub.subscribe === \"function\") {\n return new NodeRedisDriver(pub, sub, blocking);\n }\n // Default to IoRedis / Generic\n return new IoRedisDriver(pub, sub, blocking);\n}\n","import type { EventAdapterInterface } from \"../../types.js\";\nimport {\n createDriver,\n type RedisLikeClient,\n type RedisPubSubDriver,\n} from \"./helpers.js\";\n\nexport interface RedisAdapterOptions {\n /** Redis client for publishing */\n pubClient: RedisLikeClient;\n /** Redis client for subscribing (must be a separate connection) */\n subClient: RedisLikeClient;\n /** Redis client for blocking stream operations (recommended for reliability) */\n blockingClient?: RedisLikeClient;\n /** Optional key prefix for channels (default: 'ocpp-ws-io:') */\n prefix?: string;\n /** StreamMaxLen for trimming (default: 1000) */\n streamMaxLen?: number;\n /**\n * TTL in seconds for ephemeral stream keys (default: 300).\n * Prevents abandoned channel keys from leaking memory in Redis.\n */\n streamTtlSeconds?: number;\n /**\n * Presence TTL in seconds (default: 300).\n * Used for batch presence heartbeat pipeline.\n */\n presenceTtlSeconds?: number;\n /**\n * Number of Redis connections to pool for write operations (default: 1).\n * Higher values distribute load via round-robin, eliminating TCP head-of-line blocking.\n * Pub/Sub subscriptions always use the primary driver (pool index 0).\n * - `1` = current single-connection behavior (backward compatible)\n * - `N` = round-robin across N connections for xadd/set/publish\n *\n * When poolSize > 1, additional pub/blocking clients are created by the factory.\n * Provide `driverFactory` to control how additional drivers are created.\n */\n poolSize?: number;\n /**\n * Factory function to create additional driver instances for the pool.\n * Each call should return a fresh, independent set of Redis connections.\n * Required when `poolSize > 1`.\n */\n driverFactory?: () => RedisPubSubDriver;\n}\n\n/**\n * Redis adapter for cross-process event distribution.\n *\n * Supports `ioredis` and `node-redis` (v4+).\n * Uses Redis Streams for reliable unicast (node-to-node) and Pub/Sub for broadcast.\n */\nexport class RedisAdapter implements EventAdapterInterface {\n private _driver: RedisPubSubDriver;\n private _prefix: string;\n private _streamMaxLen: number;\n private _streamTtlSeconds: number;\n private _presenceTtlSeconds: number;\n private _handlers = new Map<string, Set<(data: unknown) => void>>();\n private _streamOffsets = new Map<string, string>(); // streamKey -> lastId\n private _streams = new Set<string>(); // Active streams to poll\n private _polling = false;\n private _closed = false;\n\n // Per-stream sequence counter for message ordering\n private _sequenceCounters = new Map<string, number>();\n\n // Rehydration callbacks\n private _unsubError?: () => void;\n private _unsubReconnect?: () => void;\n\n // Stored presence entries for rehydration on reconnect\n private _presenceCache = new Map<string, { nodeId: string; ttl: number }>();\n\n // Connection pool\n private _driverPool: RedisPubSubDriver[];\n private _nextPoolIndex: number;\n\n constructor(options: RedisAdapterOptions) {\n this._prefix = options.prefix ?? \"ocpp-ws-io:\";\n this._streamMaxLen = options.streamMaxLen ?? 1000;\n this._streamTtlSeconds = options.streamTtlSeconds ?? 300;\n this._presenceTtlSeconds = options.presenceTtlSeconds ?? 300;\n\n // Primary driver (always created)\n this._driver = createDriver(\n options.pubClient,\n options.subClient,\n options.blockingClient,\n );\n\n // Connection pool — default 1 (backward compatible)\n const poolSize = options.poolSize ?? 1;\n this._driverPool = [this._driver];\n this._nextPoolIndex = 0;\n\n if (poolSize > 1 && options.driverFactory) {\n for (let i = 1; i < poolSize; i++) {\n this._driverPool.push(options.driverFactory());\n }\n }\n\n // Redis Failure Rehydration — listen for errors and re-sync on reconnect\n if (this._driver.onError) {\n this._unsubError = this._driver.onError((err) => {\n // Log for observability — consumers can attach their own logger\n console.error(\"[RedisAdapter] Redis error:\", err.message);\n });\n }\n if (this._driver.onReconnect) {\n this._unsubReconnect = this._driver.onReconnect(() => {\n this._rehydratePresence().catch(() => {});\n });\n }\n }\n\n /** Get the next driver from the pool (round-robin) */\n private _getPoolDriver(): RedisPubSubDriver {\n if (this._driverPool.length === 1) return this._driver;\n const driver = this._driverPool[this._nextPoolIndex];\n this._nextPoolIndex = (this._nextPoolIndex + 1) % this._driverPool.length;\n return driver;\n }\n\n async publish(channel: string, data: unknown): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n // Attach sequence ID to unicast messages for ordering\n const payload = data as Record<string, unknown> | null;\n if (\n payload &&\n typeof payload === \"object\" &&\n channel.startsWith(\"ocpp:node:\")\n ) {\n const seq = (this._sequenceCounters.get(channel) ?? 0) + 1;\n this._sequenceCounters.set(channel, seq);\n (payload as Record<string, unknown>).__seq = seq;\n }\n\n const message = JSON.stringify(data);\n\n // Unicast (Node-to-Node) -> Use Streams\n if (channel.startsWith(\"ocpp:node:\")) {\n const poolDriver = this._getPoolDriver();\n await poolDriver.xadd(prefixedChannel, { message }, this._streamMaxLen);\n // Set TTL lease on ephemeral stream key to prevent memory leaks\n await poolDriver\n .expire(prefixedChannel, this._streamTtlSeconds)\n .catch(() => {});\n } else {\n // Broadcast -> Use Pub/Sub\n await this._getPoolDriver().publish(prefixedChannel, message);\n }\n }\n\n async publishBatch(\n messages: { channel: string; data: unknown }[],\n ): Promise<void> {\n const streamMessages: { stream: string; args: Record<string, string> }[] =\n [];\n const broadcastMessages: { channel: string; message: string }[] = [];\n\n for (const msg of messages) {\n const prefixedChannel = this._prefix + msg.channel;\n const message = JSON.stringify(msg.data);\n\n if (msg.channel.startsWith(\"ocpp:node:\")) {\n streamMessages.push({ stream: prefixedChannel, args: { message } });\n } else {\n broadcastMessages.push({ channel: prefixedChannel, message });\n }\n }\n\n const promises: Promise<void>[] = [];\n\n if (streamMessages.length > 0) {\n promises.push(\n this._getPoolDriver().xaddBatch(streamMessages, this._streamMaxLen),\n );\n }\n\n if (broadcastMessages.length > 0) {\n promises.push(\n Promise.all(\n broadcastMessages.map((bm) =>\n this._getPoolDriver().publish(bm.channel, bm.message),\n ),\n ).then(() => {}), // Map `Promise<void[]>` to `Promise<void>`\n );\n }\n\n await Promise.all(promises);\n }\n\n async subscribe(\n channel: string,\n handler: (data: unknown) => void,\n ): Promise<void> {\n if (!this._handlers.has(channel)) {\n this._handlers.set(channel, new Set());\n const prefixedChannel = this._prefix + channel;\n\n if (channel.startsWith(\"ocpp:node:\")) {\n // Stream subscription\n // Start from '0' (beginning) to pick up missed messages during downtime (persistence).\n // Since we trim the stream (MAXLEN), this will only replay recent pending messages.\n if (!this._streams.has(prefixedChannel)) {\n this._streams.add(prefixedChannel);\n this._streamOffsets.set(prefixedChannel, \"0\");\n this._ensurePolling();\n }\n } else {\n // Pub/Sub subscription\n await this._driver.subscribe(prefixedChannel, (message) => {\n this._handleMessage(channel, message);\n });\n }\n }\n this._handlers.get(channel)?.add(handler);\n }\n\n async unsubscribe(channel: string): Promise<void> {\n const prefixedChannel = this._prefix + channel;\n\n if (this._streams.has(prefixedChannel)) {\n this._streams.delete(prefixedChannel);\n this._streamOffsets.delete(prefixedChannel); // Cleanup offset\n } else {\n await this._driver.unsubscribe(prefixedChannel);\n }\n\n this._handlers.delete(channel);\n }\n\n async disconnect(): Promise<void> {\n this._closed = true;\n this._handlers.clear();\n this._streams.clear();\n this._presenceCache.clear();\n this._sequenceCounters.clear();\n if (this._unsubError) this._unsubError();\n if (this._unsubReconnect) this._unsubReconnect();\n // Disconnect all pool drivers\n await Promise.allSettled(this._driverPool.map((d) => d.disconnect()));\n }\n\n private _handleMessage(channel: string, message: string): void {\n const handlers = this._handlers.get(channel);\n if (!handlers) return;\n\n let data: unknown;\n try {\n data = JSON.parse(message);\n } catch {\n data = message;\n }\n\n for (const handler of handlers) {\n try {\n handler(data);\n } catch {\n // Swallow handler errors\n }\n }\n }\n\n // ─── Stream Polling ───────────────────────────────────────────────\n\n private _ensurePolling() {\n if (this._polling || this._closed) return;\n this._polling = true;\n this._pollLoop().catch(() => {\n this._polling = false;\n });\n }\n\n private async _pollLoop() {\n while (!this._closed) {\n if (this._streams.size === 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n continue;\n }\n\n const streamsArg = Array.from(this._streams).map((key) => ({\n key,\n id: this._streamOffsets.get(key) || \"$\",\n }));\n\n try {\n // Block for 1s. This allows picking up new subscriptions reasonably fast.\n const entries = await this._driver.xread(streamsArg, undefined, 1000);\n\n if (entries) {\n for (const entry of entries) {\n const channel = entry.stream.replace(this._prefix, \"\"); // remove prefix to find handler key\n\n for (const msg of entry.messages) {\n // Update offset\n this._streamOffsets.set(entry.stream, msg.id);\n\n const messageContent = msg.data.message;\n if (messageContent) {\n this._handleMessage(channel, messageContent);\n }\n }\n }\n }\n } catch (_err) {\n // Log error? For now swallow to keep loop alive\n // Avoid tight loop on error\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n this._polling = false;\n }\n\n // ─── Presence Registry ─────────────────────────────────────────────\n\n async setPresence(\n identity: string,\n nodeId: string,\n ttl: number,\n ): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n // Cache for rehydration on reconnect (C3)\n this._presenceCache.set(identity, { nodeId, ttl });\n await this._driver.set(key, nodeId, ttl);\n }\n\n async getPresence(identity: string): Promise<string | null> {\n const key = `${this._prefix}presence:${identity}`;\n return await this._driver.get(key);\n }\n\n async getPresenceBatch(identities: string[]): Promise<(string | null)[]> {\n if (identities.length === 0) return [];\n const keys = identities.map((id) => `${this._prefix}presence:${id}`);\n if (this._driver.mget) {\n return await this._driver.mget(keys);\n }\n // Fallback if mget not available\n return await Promise.all(keys.map((k) => this._driver.get(k)));\n }\n\n async removePresence(identity: string): Promise<void> {\n const key = `${this._prefix}presence:${identity}`;\n await this._driver.del(key);\n }\n\n // ─── Observability Pipeline ────────────────────────────────────────\n\n async metrics(): Promise<Record<string, unknown>> {\n let pendingMessages = 0;\n const streamDetails: Record<string, number> = {};\n\n // Calculate \"consumer lag\" by checking the length of all active streams\n // Since we use MAXLEN for trimming, XLEN directly equals pending unread messages\n for (const streamKey of this._streams) {\n try {\n const length = await this._driver.xlen(streamKey);\n pendingMessages += length;\n streamDetails[streamKey] = length;\n } catch {\n // Ignore failures for individual stream stats\n streamDetails[streamKey] = -1;\n }\n }\n\n return {\n pendingMessages,\n activeStreams: this._streams.size,\n streamDetails,\n };\n }\n\n // ─── C1: Batch Presence Pipeline ────────────────────────────────────\n\n /**\n * Set multiple presence entries in a single Redis pipeline.\n * Reduces N network round-trips to 1 for bulk presence updates.\n */\n async setPresenceBatch(\n entries: { identity: string; nodeId: string; ttl?: number }[],\n ): Promise<void> {\n if (entries.length === 0) return;\n\n const batchEntries = entries.map(({ identity, nodeId, ttl }) => {\n const key = `${this._prefix}presence:${identity}`;\n const ttlSeconds = ttl ?? this._presenceTtlSeconds;\n // Cache for rehydration\n this._presenceCache.set(identity, { nodeId, ttl: ttlSeconds });\n return { key, value: nodeId, ttlSeconds };\n });\n\n await this._driver.setPresenceBatch(batchEntries);\n }\n\n // ─── C3: Redis Failure Rehydration ──────────────────────────────────\n\n /**\n * Re-syncs all cached presence entries to Redis after a reconnection.\n * Called automatically when the Redis client reconnects.\n */\n private async _rehydratePresence(): Promise<void> {\n if (this._presenceCache.size === 0) return;\n\n const entries = Array.from(this._presenceCache.entries()).map(\n ([identity, { nodeId, ttl }]) => ({\n key: `${this._prefix}presence:${identity}`,\n value: nodeId,\n ttlSeconds: ttl,\n }),\n );\n\n await this._driver.setPresenceBatch(entries);\n }\n}\n"]}