@erebus-sh/sdk
Version:
To install dependencies:
1 lines • 57.8 kB
JavaScript
import{ErebusError}from"./error-CVJkstUc.js";import{createRpcClient}from"./rpc-DQjy8_nn.js";import{MessageBodySchema,logger}from"./messageBody-DvuZs_xm.js";import consola from"consola";import{nanoid}from"nanoid";import{z}from"zod";var Authorize=class{client;constructor(p){this.client=createRpcClient(p)}async generateToken(p){try{let L=await this.client.api.erebus.pubsub.grant.$post({json:{channel:p}});if(!L.ok)throw Error(`The auth call failed: ${L.status} ${L.statusText}\nresponse: ${await L.text()}\nPath: ${L.url}`);return(await L.json()).grant_jwt}catch(p){throw Error(`Failed to generate token: ${p instanceof Error?p.message:`Unknown error`}`)}}async healthCheck(){try{return await(await this.client.api[`health-not-meaningful`].$get()).json()}catch(p){throw Error(`Health check failed: ${p instanceof Error?p.message:`Unknown error`}`)}}async generateTestToken(){try{let p=await this.client.api[`generate-token-test`].$get();return p.ok,await p.json()}catch(p){throw Error(`Failed to generate test token: ${p instanceof Error?p.message:`Unknown error`}`)}}};const VERSION=`1`,RequestId=z.string().min(1).optional(),ConnectPacket=z.object({packetType:z.literal(`connect`),version:z.literal(`1`),grantJWT:z.string().min(1)}),SubscribePacket=z.object({packetType:z.literal(`subscribe`),requestId:RequestId,topic:z.string().min(1)}),UnsubscribePacket=z.object({packetType:z.literal(`unsubscribe`),requestId:RequestId,topic:z.string().min(1)}),PresencePacket=z.object({packetType:z.literal(`presence`),clientId:z.string().min(1),topic:z.string().min(1),status:z.enum([`online`,`offline`])}),PublishPacket=z.object({packetType:z.literal(`publish`),ack:z.boolean().optional(),requestId:RequestId,topic:z.string().min(1),payload:MessageBodySchema,clientMsgId:z.string().min(1)}),BaseAck=z.object({type:z.literal(`ack`),path:z.enum([`publish`,`subscribe`,`unsubscribe`]),seq:z.string().min(1),serverAssignedId:z.string().min(1),clientMsgId:z.string().min(1)}),AckPublishOk=BaseAck.extend({path:z.literal(`publish`),topic:z.string().min(1),result:z.object({ok:z.literal(!0),t_ingress:z.number()})}),AckPublishErr=BaseAck.extend({path:z.literal(`publish`),topic:z.string().min(1),result:z.object({ok:z.literal(!1),code:z.enum([`UNAUTHORIZED`,`FORBIDDEN`,`INVALID`,`RATE_LIMITED`,`INTERNAL`]),message:z.string().min(1)})}),AckSubscription=BaseAck.extend({path:z.enum([`subscribe`,`unsubscribe`]),topic:z.string().min(1),result:z.object({ok:z.literal(!0),status:z.enum([`subscribed`,`unsubscribed`])})}),AckResultSchema=z.discriminatedUnion(`path`,[AckPublishOk,AckPublishErr,AckSubscription]),AckPacket=z.object({packetType:z.literal(`ack`),clientMsgId:RequestId,result:AckResultSchema}),PacketEnvelopeSchema=z.discriminatedUnion(`packetType`,[ConnectPacket,SubscribePacket,UnsubscribePacket,PresencePacket,PublishPacket,AckPacket]);function encodeEnvelope(p){logger.info(`[encodeEnvelope] called`,{packetType:p.packetType}),logger.info(`[encodeEnvelope] validating packet`,{packetType:p.packetType}),PacketEnvelopeSchema.parse(p),logger.info(`[encodeEnvelope] packet validated`,{packetType:p.packetType});let L=JSON.stringify(p);return logger.info(`[encodeEnvelope] packet encoded`,{packetType:p.packetType,encodedLength:L.length}),L}function parseServerFrame(p){if(logger.info(`[parseServerFrame] called`,{rawLength:p.length}),!p||typeof p!=`string`||p.trim().length===0)return logger.warn(`[parseServerFrame] Invalid raw data`,{raw:p}),null;try{logger.info(`[parseServerFrame] parsing JSON`);let L=JSON.parse(p);if(logger.info(`[parseServerFrame] JSON parsed`,{keys:Object.keys(L)}),!L||typeof L!=`object`)return logger.warn(`[parseServerFrame] Parsed data is not an object`,{data:L}),null;if(L.packetType===`ack`){logger.info(`[parseServerFrame] validating ACK packet schema`);let p=parseAckPacket(L);return p?(logger.info(`[parseServerFrame] ACK packet validated`,{clientMsgId:L.clientMsgId}),p):(logger.warn(`[parseServerFrame] ACK packet parsing failed`),null)}else if(L.packetType===`presence`){logger.info(`[parseServerFrame] validating presence packet schema`);let p=PresencePacket.parse(L);return logger.info(`[parseServerFrame] presence packet validated`,{topic:p.topic,clientId:p.clientId}),p}else{if(!L.topic||typeof L.topic!=`string`)return logger.warn(`[parseServerFrame] Missing or invalid topic`,{data:L}),null;logger.info(`[parseServerFrame] validating message schema`);let p=MessageBodySchema.parse(L);return logger.info(`[parseServerFrame] message schema validated`,{topic:p.topic}),{packetType:`publish`,topic:p.topic,payload:p}}}catch(L){return logger.warn(`[parseServerFrame] failed`,{error:L instanceof Error?L.message:String(L)},p),null}}function parseAckPacket(p){try{if(p.packetType!==`ack`||!p.result||typeof p.result!=`object`)return null;let L=p.result,R=L.path;if(R===`subscribe`||R===`unsubscribe`){let R=AckSubscription.parse(L);return{packetType:`ack`,clientMsgId:p.clientMsgId,result:R}}else if(R===`publish`&&L.result&&typeof L.result==`object`&&`ok`in L.result)if(L.result.ok){let R=AckPublishOk.parse(L);return{packetType:`ack`,clientMsgId:p.clientMsgId,result:R}}else{let R=AckPublishErr.parse(L);return{packetType:`ack`,clientMsgId:p.clientMsgId,result:R}}return logger.warn(`[parseAckPacket] Unknown ACK type`,{path:R}),null}catch(p){return logger.warn(`[parseAckPacket] Failed to parse ACK packet`,{error:p}),null}}function parseMessageBody(p){logger.info(`[parseMessageBody] called (legacy)`,{rawLength:p.length});let L=parseServerFrame(p);return L&&L.packetType===`publish`?L.payload:null}function validateWebSocketUrl(p){if(!p)return!1;try{let L=new URL(p);return!(L.protocol!==`ws:`&&L.protocol!==`wss:`||!L.hostname.includes(`localhost`)&&L.protocol===`ws:`)}catch{return!1}}function backoff(p,L=5e3){let R=250*2**p,V=Math.floor(Math.random()*200),H=Math.min(L,R)+V;return logger.info(`backoff computed`,{attempt:p,capMs:L,base:R,jitter:V,delay:H}),H}let WsErrors=function(p){return p[p.BadRequest=4400]=`BadRequest`,p[p.Unauthorized=4401]=`Unauthorized`,p[p.Forbidden=4403]=`Forbidden`,p[p.NotFound=4404]=`NotFound`,p[p.MethodNotAllowed=4405]=`MethodNotAllowed`,p[p.NotAcceptable=4406]=`NotAcceptable`,p[p.RequestTimeout=4408]=`RequestTimeout`,p[p.Conflict=4409]=`Conflict`,p[p.PreconditionFailed=4412]=`PreconditionFailed`,p[p.InternalServerError=4500]=`InternalServerError`,p[p.VersionMismatch=4413]=`VersionMismatch`,p}({});var BackpressureError=class extends Error{},NotConnectedError=class extends Error{},ConnectionManager=class{#url;#channel;#ws;#retry=0;#state=`idle`;#connectionId;#grant;#autoReconnect;#onMessage;#log;#debugHexDump;constructor(p){if(this.#connectionId=`conn_${Math.random().toString(36).substring(2,8)}`,logger.info(`[${this.#connectionId}] ConnectionManager created`,{url:p.url}),!validateWebSocketUrl(p.url))throw logger.error(`[${this.#connectionId}] Invalid WebSocket URL`,{url:p.url}),Error(`Invalid WebSocket URL`);this.#url=p.url,this.#channel=p.channel,this.#grant=``,this.#onMessage=p.onMessage,this.#log=p.log??(()=>{}),this.#autoReconnect=p.autoReconnect??!1,this.#debugHexDump=p.debugHexDump??!1}get state(){return this.#state}get isConnected(){return this.#state===`open`}get isConnecting(){return this.#state===`connecting`}get isClosed(){return this.#state===`closed`}get isIdle(){return this.#state===`idle`}get connectionId(){return this.#connectionId}get url(){return this.#url}get bufferedAmount(){return this.#ws?.bufferedAmount??0}get readyState(){return this.#ws?.readyState}get channel(){return this.#channel}async open(p){if(logger.info(`[${this.#connectionId}] Opening connection`,{timeout:p.timeout}),this.#log(`info`,`connection.open called`),this.#state===`connecting`||this.#state===`open`){logger.info(`[${this.#connectionId}] Connection already ${this.#state}, returning early`);return}if(!this.#channel||this.#channel.trim().length===0){let p=`Channel must be set before opening connection`;throw logger.error(`[${this.#connectionId}] ${p}`,{channel:this.#channel}),Error(p)}logger.info(`[${this.#connectionId}] Setting state to connecting`),this.#state=`connecting`,this.#grant=p.grant;let L=await this.#createWebSocket(p.grant);if(this.#ws=L,this.#setupWebSocketListeners(L),p.timeout){logger.info(`[${this.#connectionId}] Setting connection timeout`,{timeout:p.timeout});let R=setTimeout(()=>{throw logger.error(`[${this.#connectionId}] Connection timeout reached`,{timeout:p.timeout}),L.close(),Error(`Connection timeout`)},p.timeout);await new Promise(p=>L.addEventListener(`open`,()=>{logger.info(`[${this.#connectionId}] Connection opened within timeout, clearing timeout`),clearTimeout(R),p()},{once:!0}))}else logger.info(`[${this.#connectionId}] Waiting for connection to open (no timeout)`),await new Promise(p=>L.addEventListener(`open`,()=>{logger.info(`[${this.#connectionId}] Connection opened successfully`),p()},{once:!0}))}close(){if(logger.info(`[${this.#connectionId}] Connection close called (url: ${this.#url})`),this.#log(`info`,`connection close called (url: ${this.#url})`),this.#state=`closed`,logger.info(`[${this.#connectionId}] Connection state set to closed (url: ${this.#url})`),this.#ws){try{(this.#ws.readyState===WebSocket.OPEN||this.#ws.readyState===WebSocket.CONNECTING)&&this.#ws.close()}catch(p){logger.warn(`[${this.#connectionId}] Error closing WebSocket`,{error:p})}this.#ws=void 0}logger.info(`[${this.#connectionId}] Connection closed and cleaned up`)}send(p){if(logger.info(`[${this.#connectionId}] Sending packet`,{packetType:p.packetType}),!p||typeof p!=`object`){let L=`Invalid packet: must be an object`;throw logger.error(`[${this.#connectionId}] ${L}`,{pkt:p}),Error(L)}if(!this.#ws||this.#ws.readyState!==WebSocket.OPEN)throw logger.error(`[${this.#connectionId}] Cannot send packet - WebSocket not ready`,{hasWs:!!this.#ws,readyState:this.#ws?.readyState,state:this.#state}),new NotConnectedError(`Not connected readyState: `+this.#ws?.readyState);let L=this.#ws.bufferedAmount;if(L>1e6)throw logger.error(`[${this.#connectionId}] Critical backpressure - closing connection`,{buffered:L,limit:1e6}),this.#log(`error`,`critical backpressure; reconnecting`,{buffered:L}),this.#ws.close(),new BackpressureError(`Critical backpressure`);try{let L=encodeEnvelope(p);logger.info(`[ConnectionManager] [${this.#connectionId}] Packet encoded, sending via WebSocket`,{packetType:p.packetType,encodedLength:L.length}),this.#ws.send(L)}catch(L){throw logger.error(`[${this.#connectionId}] Error sending packet`,{error:L,packetType:p.packetType}),L}}sendRaw(p){if(logger.info(`[${this.#connectionId}] Sending raw data`,{dataType:typeof p,dataLength:p.length}),!this.#ws||this.#ws.readyState!==WebSocket.OPEN)throw logger.error(`[${this.#connectionId}] Cannot send raw data - WebSocket not ready`,{hasWs:!!this.#ws,readyState:this.#ws?.readyState,state:this.#state}),new NotConnectedError(`Not connected readyState: `+this.#ws?.readyState);try{this.#ws.send(p),logger.info(`[${this.#connectionId}] Raw data sent successfully`)}catch(L){throw logger.error(`[${this.#connectionId}] Error sending raw data`,{error:L,dataLength:p.length}),L}}setChannel(p){if(logger.info(`[${this.#connectionId}] Setting channel`,{channel:p}),!p||typeof p!=`string`||p.trim().length===0){let L=`Invalid channel: must be a non-empty string`;throw logger.error(`[${this.#connectionId}] ${L}`,{channel:p}),Error(L)}this.#channel=p,logger.info(`[${this.#connectionId}] Channel set successfully`,{channel:this.#channel})}async#createWebSocket(p){let L=new URL(this.#url);L.searchParams.set(`grant`,p),logger.info(`[${this.#connectionId}] Creating WebSocket connection`,{url:this.#url,grant:p.substring(0,10)+`...`});let R=new WebSocket(L.toString());try{`binaryType`in R&&(R.binaryType=`arraybuffer`)}catch{}return R}#setupWebSocketListeners(L){L.addEventListener(`open`,()=>{logger.info(`[${this.#connectionId}] WebSocket opened successfully`),this.#retry=0,this.#state=`open`,this.#log(`info`,`ws open`)},{once:!0}),L.addEventListener(`message`,p=>{this.#handleMessage(p)}),L.addEventListener(`close`,L=>{if(this.#log(`warn`,`ws close encountered`,{retry:this.#retry}),logger.warn(`[${this.#connectionId}] WebSocket close encountered`,{retry:this.#retry,state:this.#state}),L.code===WsErrors.VersionMismatch)throw new ErebusError(`Version mismatch: ${L.reason} current client version: 1`);this.#state!==`closed`&&this.#autoReconnect?this.#reconnect():this.#state=`closed`}),L.addEventListener(`error`,p=>{this.#log(`warn`,`ws error`,p),logger.warn(`[${this.#connectionId}] WebSocket error`,{error:p,state:this.#state,readyState:L.readyState,url:this.#url})})}async#handleMessage(p){if(this.#log(`info`,`ws message received`),this.#debugHexDump){let L=this.#getHexDump(p.data)}try{let L=await this.#convertToString(p.data);if(L.length===0){logger.warn(`[${this.#connectionId}] Data string is empty, skipping`);return}this.#onMessage({rawData:L})}catch(p){logger.error(`[${this.#connectionId}] Error handling message`,{error:p})}}#getHexDump(p){return typeof p==`string`?Buffer.from(p,`utf8`).toString(`hex`):p instanceof ArrayBuffer?Buffer.from(p).toString(`hex`):typeof p==`object`&&p&&`arrayBuffer`in p?`[blob-data]`:``}async#convertToString(p){if(typeof p==`string`)return p;if(p==null)return logger.warn(`[${this.#connectionId}] Data is undefined or null, using empty string`),``;if(p&&typeof p==`object`&&`arrayBuffer`in p&&typeof p.arrayBuffer==`function`){let L=await p.arrayBuffer(),R=new Uint8Array(L);return new TextDecoder().decode(R)}else if(typeof Buffer<`u`&&Buffer.isBuffer(p)){let L=p,R=new Uint8Array(L.buffer,L.byteOffset,L.byteLength);return new TextDecoder().decode(R)}else if(p instanceof ArrayBuffer){let L=new Uint8Array(p);return new TextDecoder().decode(L)}else if(p instanceof Uint8Array)return new TextDecoder().decode(p);else return String(p)}#reconnect(){if(this.#state===`closed`||this.#state===`connecting`){logger.info(`[${this.#connectionId}] Reconnect called but connection is ${this.#state}, ignoring`);return}logger.info(`[${this.#connectionId}] Starting reconnect process`),this.#state=`idle`;let p=backoff(this.#retry,5e3);this.#log(`info`,`scheduling reconnect`,{retry:this.#retry,bckOff:p}),logger.info(`[${this.#connectionId}] Scheduling reconnect`,{retry:this.#retry,backoff:p}),this.#retry++,setTimeout(()=>{logger.info(`[${this.#connectionId}] Executing scheduled reconnect`),this.open({grant:this.#grant,timeout:5e3}).catch(p=>{logger.error(`[${this.#connectionId}] Reconnect failed`,{error:p}),this.#state=`closed`})},p)}},AckManager=class{#pendingPublishes=new Map;#clientMsgIdToRequestId=new Map;#pendingSubscriptions=new Map;#subscriptionMsgIdToRequestId=new Map;#connectionId;constructor(p){this.#connectionId=p,logger.info(`[${this.#connectionId}] AckManager created`)}trackPublish(p,L){logger.info(`[${this.#connectionId}] Tracking publish for ACK`,{requestId:p,clientMsgId:L.clientMsgId,topic:L.topic}),this.#pendingPublishes.set(p,L),this.#clientMsgIdToRequestId.set(L.clientMsgId,p),L.timeoutId&&logger.info(`[${this.#connectionId}] ACK timeout set`,{requestId:p,timeout:`already configured`})}trackSubscription(p,L){logger.info(`[${this.#connectionId}] Tracking subscription for ACK`,{requestId:p,clientMsgId:L.clientMsgId,topic:L.topic,path:L.path,clientMsgIdType:typeof L.clientMsgId,clientMsgIdLength:L.clientMsgId?.length}),this.#pendingSubscriptions.set(p,L),L.clientMsgId?(this.#subscriptionMsgIdToRequestId.set(L.clientMsgId,p),logger.info(`[${this.#connectionId}] Stored clientMsgId mapping`,{clientMsgId:L.clientMsgId,requestId:p,totalMappings:this.#subscriptionMsgIdToRequestId.size})):logger.info(`[${this.#connectionId}] No clientMsgId provided for subscription tracking`,{requestId:p,note:`ACK matching will rely on other mechanisms`}),L.timeoutId&&logger.info(`[${this.#connectionId}] Subscription ACK timeout set`,{requestId:p,timeout:`already configured`})}handleAck(p){logger.info(`[${this.#connectionId}] Handling ACK packet`,{clientMsgId:p.clientMsgId,path:p.result.path}),p.result.path===`publish`?this.#handlePublishAck(p):p.result.path===`subscribe`||p.result.path===`unsubscribe`?this.#handleSubscriptionAck(p):logger.warn(`[${this.#connectionId}] Unknown ACK path`,{path:p.result.path,clientMsgId:p.clientMsgId})}#handlePublishAck(p){let L=p.clientMsgId;if(!L){logger.warn(`[${this.#connectionId}] Publish ACK packet missing clientMsgId`);return}let R=this.#clientMsgIdToRequestId.get(L);if(!R){logger.warn(`[${this.#connectionId}] No requestId found for publish clientMsgId`,{clientMsgId:L});return}let V=this.#pendingPublishes.get(R);if(!V){logger.warn(`[${this.#connectionId}] No pending publish found for ACK`,{requestId:R,clientMsgId:L});return}V.timeoutId&&clearTimeout(V.timeoutId),this.#pendingPublishes.delete(R),this.#clientMsgIdToRequestId.delete(L);let H=this.#createAckResponse(p,V);logger.info(`[${this.#connectionId}] Calling publish ACK callback`,{requestId:R,clientMsgId:L,success:H.success});try{V.callback(H)}catch(p){logger.error(`[${this.#connectionId}] Error in publish ACK callback`,{error:p,requestId:R,clientMsgId:L})}}#handleSubscriptionAck(p){let L=p.clientMsgId,R,V;if(logger.info(`[${this.#connectionId}] Processing subscription ACK`,{path:p.result.path,topic:p.result.topic,clientMsgId:L,pendingSubscriptionCount:this.#pendingSubscriptions.size,subscriptionMsgIdMappings:Array.from(this.#subscriptionMsgIdToRequestId.entries()).map(([p,L])=>`${p} -> ${L}`)}),L&&(R=this.#subscriptionMsgIdToRequestId.get(L),logger.info(`[${this.#connectionId}] Looking up clientMsgId in mappings`,{clientMsgId:L,foundRequestId:R}),R?(V=this.#pendingSubscriptions.get(R),logger.info(`[${this.#connectionId}] Looking up requestId in pending subscriptions`,{requestId:R,foundPending:!!V})):(logger.info(`[${this.#connectionId}] No direct mapping found, checking if clientMsgId matches a requestId`,{clientMsgId:L}),V=this.#pendingSubscriptions.get(L),V&&(R=L,logger.info(`[${this.#connectionId}] Found pending subscription by matching clientMsgId to requestId`,{clientMsgId:L,requestId:R})))),!V){logger.info(`[${this.#connectionId}] Received untracked subscription ACK`,{path:p.result.path,topic:p.result.topic,clientMsgId:L,note:`This is normal for optimistic subscriptions`});return}logger.info(`[${this.#connectionId}] Handling tracked subscription ACK`,{requestId:R,clientMsgId:L,topic:V.topic,path:V.path}),V.timeoutId&&clearTimeout(V.timeoutId),this.#pendingSubscriptions.delete(R),L&&this.#subscriptionMsgIdToRequestId.delete(L);let H=this.#createSubscriptionResponse(p,V);logger.info(`[${this.#connectionId}] Calling subscription ACK callback`,{requestId:R,clientMsgId:L,topic:V.topic,success:H.success});try{V.callback(H)}catch(p){logger.error(`[${this.#connectionId}] Error in subscription ACK callback`,{error:p,requestId:R,clientMsgId:L,topic:V.topic})}}handlePublishTimeout(p){logger.warn(`[${this.#connectionId}] Publish ACK timeout`,{requestId:p});let L=this.#pendingPublishes.get(p);if(!L)return;this.#pendingPublishes.delete(p),this.#clientMsgIdToRequestId.delete(L.clientMsgId);let R={success:!1,ack:{},error:{code:`TIMEOUT`,message:`Publish ACK not received within timeout`},topic:L.topic};try{L.callback(R)}catch(L){logger.error(`[${this.#connectionId}] Error in publish timeout callback`,{error:L,requestId:p})}}handleSubscriptionTimeout(p){logger.warn(`[${this.#connectionId}] Subscription ACK timeout`,{requestId:p});let L=this.#pendingSubscriptions.get(p);if(!L)return;this.#pendingSubscriptions.delete(p),L.clientMsgId&&this.#subscriptionMsgIdToRequestId.delete(L.clientMsgId);let R={success:!1,error:{code:`TIMEOUT`,message:`Subscription ACK not received within timeout`},topic:L.topic,path:L.path};try{L.callback(R)}catch(R){logger.error(`[${this.#connectionId}] Error in subscription timeout callback`,{error:R,requestId:p,topic:L.topic})}}cleanup(p){logger.info(`[${this.#connectionId}] Cleaning up pending operations`,{publishCount:this.#pendingPublishes.size,subscriptionCount:this.#pendingSubscriptions.size,reason:p});for(let[L,R]of this.#pendingPublishes){R.timeoutId&&clearTimeout(R.timeoutId);let V={success:!1,ack:{},error:{code:`CONNECTION_ERROR`,message:p},topic:R.topic};try{R.callback(V)}catch(p){logger.error(`[${this.#connectionId}] Error in publish cleanup callback`,{error:p,requestId:L,clientMsgId:R.clientMsgId})}}for(let[L,R]of this.#pendingSubscriptions){R.timeoutId&&clearTimeout(R.timeoutId);let V={success:!1,error:{code:`CONNECTION_ERROR`,message:p},topic:R.topic,path:R.path};try{R.callback(V)}catch(p){logger.error(`[${this.#connectionId}] Error in subscription cleanup callback`,{error:p,requestId:L,clientMsgId:R.clientMsgId,topic:R.topic})}}this.#pendingPublishes.clear(),this.#clientMsgIdToRequestId.clear(),this.#pendingSubscriptions.clear(),this.#subscriptionMsgIdToRequestId.clear()}getPendingCount(){return this.#pendingPublishes.size}getPendingSubscriptionCount(){return this.#pendingSubscriptions.size}#createAckResponse(p,L){return p.result.path===`publish`?`result`in p.result&&p.result.result.ok?{success:!0,ack:p,seq:p.result.seq,serverMsgId:p.result.serverAssignedId,topic:p.result.topic}:`result`in p.result&&!p.result.result.ok?{success:!1,ack:p,error:{code:p.result.result.code,message:p.result.result.message},topic:p.result.topic}:{success:!1,ack:p,error:{code:`MALFORMED_ACK`,message:`ACK packet has invalid structure`},topic:L.topic}:(logger.info(`[${this.#connectionId}] Received non-publish ACK`,{path:p.result.path,clientMsgId:p.clientMsgId,note:`Subscription ACKs are not tracked in AckManager`}),{success:!1,ack:p,error:{code:`INVALID_ACK_TYPE`,message:`Received non-publish ACK for publish operation`},topic:L.topic})}#createSubscriptionResponse(p,L){if(p.result.path===`subscribe`||p.result.path===`unsubscribe`)if(`result`in p.result){let L=p.result.result;if(L.ok)return{success:!0,ack:p,topic:p.result.topic,status:L.status,path:p.result.path};{let R=L;return{success:!1,ack:p,error:{code:R.code||`SUBSCRIPTION_ERROR`,message:R.message||`Subscription operation failed`},topic:p.result.topic,path:p.result.path}}}else return{success:!1,ack:p,error:{code:`MALFORMED_ACK`,message:`Subscription ACK packet has invalid structure`},topic:L.topic,path:L.path};else return{success:!1,ack:p,error:{code:`INVALID_ACK_TYPE`,message:`Received non-subscription ACK for subscription operation`},topic:L.topic,path:L.path}}},SubscriptionManager=class{#subs=new Set;#subscribedTopics=new Set;#unsubscribedTopics=new Set;#connectionId;constructor(p){this.#connectionId=p,logger.info(`[${this.#connectionId}] SubscriptionManager created`)}get subscriptions(){return Array.from(this.#subs)}get subscribedTopics(){return Array.from(this.#subscribedTopics).filter(p=>!this.#unsubscribedTopics.has(p))}get unsubscribedTopics(){return Array.from(this.#unsubscribedTopics)}get subscriptionCount(){return this.#subs.size}subscribe(p){if(logger.info(`[${this.#connectionId}] Subscribe to topic`,{topic:p}),!p||typeof p!=`string`||p.trim().length===0){let L=`Invalid topic: must be a non-empty string`;throw logger.error(`[${this.#connectionId}] ${L}`,{topic:p}),Error(L)}return this.#subscribedTopics.add(p),this.#unsubscribedTopics.delete(p),this.#subs.has(p)?(logger.info(`[${this.#connectionId}] Topic already subscribed`,{topic:p}),!1):(this.#subs.add(p),logger.info(`[${this.#connectionId}] Topic added to subscriptions`,{topic:p,totalSubs:this.#subs.size}),!0)}unsubscribe(p){if(logger.info(`[${this.#connectionId}] Unsubscribe from topic`,{topic:p}),!p||typeof p!=`string`||p.trim().length===0){let L=`Invalid topic: must be a non-empty string`;throw logger.error(`[${this.#connectionId}] ${L}`,{topic:p}),Error(L)}return this.#subs.has(p)?(this.#unsubscribedTopics.add(p),this.#subscribedTopics.delete(p),this.#subs.delete(p),logger.info(`[${this.#connectionId}] Topic removed from subscriptions`,{topic:p,totalSubs:this.#subs.size}),!0):(logger.info(`[${this.#connectionId}] Topic already unsubscribed`,{topic:p}),!1)}isSubscribed(p){return this.#subscribedTopics.has(p)&&!this.#unsubscribedTopics.has(p)}getSubscriptionStatus(p){return this.#subscribedTopics.has(p)&&!this.#unsubscribedTopics.has(p)?`subscribed`:this.#unsubscribedTopics.has(p)?`unsubscribed`:`pending`}clear(){logger.info(`[${this.#connectionId}] Clearing all subscriptions`),this.#subs.clear(),this.#subscribedTopics.clear(),this.#unsubscribedTopics.clear()}getSubscriptionTracking(){let p=Array.from(this.#subs).filter(p=>!this.#subscribedTopics.has(p)&&!this.#unsubscribedTopics.has(p));return{subscribed:this.subscribedTopics,unsubscribed:this.unsubscribedTopics,pending:p}}getTopicsForResubscription(){return Array.from(this.#subs)}confirmSubscription(p){logger.info(`[${this.#connectionId}] Confirming subscription`,{topic:p}),this.#subscribedTopics.add(p),this.#unsubscribedTopics.delete(p)}confirmUnsubscription(p){logger.info(`[${this.#connectionId}] Confirming unsubscription`,{topic:p}),this.#unsubscribedTopics.add(p),this.#subscribedTopics.delete(p),this.#subs.delete(p)}},MessageProcessor=class{#connectionId;#onMessage;#ackManager;#presenceManager;constructor(p,L,R,V){this.#connectionId=p,this.#onMessage=L,this.#ackManager=R,this.#presenceManager=V,logger.info(`[${this.#connectionId}] MessageProcessor created`)}async processMessage(p){if(logger.info(`[${this.#connectionId}] Processing message`,{dataLength:p.length}),p.length===0)return logger.warn(`[${this.#connectionId}] Data string is empty, skipping`),null;if(p===`ping`)return logger.warn(`[${this.#connectionId}] Ping packet, skipping`,{rawDataPreview:p.slice(0,200)}),null;let L=parseServerFrame(p);return L?(logger.info(`[${this.#connectionId}] Parsed packet`,{packetType:L.packetType}),await this.handlePacket(L),L):(logger.warn(`[${this.#connectionId}] Failed to parse server frame`,{rawDataPreview:p.slice(0,200)}),null)}async handlePacket(p){try{p.packetType===`ack`?await this.#handleAckPacket(p):p.packetType===`publish`?(logger.info(`[${this.#connectionId}] Handling publish message`,{topic:p.payload?.topic,messageId:p.payload?.id||`unknown`}),this.#onMessage(p)):p.packetType===`presence`?(logger.info(`[${this.#connectionId}] Handling presence packet`,{topic:p.topic,clientId:p.clientId,status:p.status}),this.#presenceManager.handlePresencePacket(p)):logger.warn(`[${this.#connectionId}] Unknown packet type`,{packetType:p.packetType})}catch(L){logger.error(`[${this.#connectionId}] Error handling packet`,{error:L,packetType:p.packetType})}}async#handleAckPacket(p){logger.info(`[${this.#connectionId}] Processing ACK packet`,{clientMsgId:p.clientMsgId,path:p.result.path}),this.#ackManager.handleAck(p)}};const GRANT_CACHE_KEY=`erebus:grant`;function logSensitiveData(p,L=`data`){return!p||p.length<8?`${L}: [too short]`:`${L}: ${p.substring(0,4)}...${p.substring(p.length-4)}`}var GrantManager=class{#connectionId;#tokenProvider;constructor(p,L){this.#connectionId=p,this.#tokenProvider=L,logger.info(`[${this.#connectionId}] GrantManager created`)}getCachedGrant(){logger.info(`[${this.#connectionId}] Attempting to get cached grant`);try{if(typeof localStorage<`u`){let p=localStorage.getItem(GRANT_CACHE_KEY);return p?logger.info(`[${this.#connectionId}] Cached grant found`,{grantPreview:logSensitiveData(p,`cached_grant`)}):logger.info(`[${this.#connectionId}] No cached grant found`),p??void 0}else logger.info(`[${this.#connectionId}] localStorage not available (non-browser environment)`)}catch(p){logger.warn(`[${this.#connectionId}] Error accessing cached grant`,{error:p})}}setCachedGrant(p){logger.info(`[${this.#connectionId}] Setting cached grant`,{grantPreview:logSensitiveData(p,`grant_to_cache`)});try{typeof localStorage<`u`?(localStorage.setItem(GRANT_CACHE_KEY,p),logger.info(`[${this.#connectionId}] Grant cached successfully`)):logger.info(`[${this.#connectionId}] Cannot cache grant - localStorage not available`)}catch(p){logger.warn(`[${this.#connectionId}] Error caching grant`,{error:p})}}clearCachedGrant(){logger.info(`[${this.#connectionId}] Clearing cached grant`);try{typeof localStorage<`u`?(localStorage.removeItem(GRANT_CACHE_KEY),logger.info(`[${this.#connectionId}] Cached grant cleared successfully`)):logger.info(`[${this.#connectionId}] Cannot clear grant - localStorage not available`)}catch(p){logger.warn(`[${this.#connectionId}] Error clearing cached grant`,{error:p})}}async getToken(p){logger.info(`[${this.#connectionId}] Getting token from provider`,{channel:p});try{let L=await this.#tokenProvider(p);if(!L)throw logger.error(`[${this.#connectionId}] No token provided by token provider`),Error(`No token provided`);return logger.info(`[${this.#connectionId}] Token received`,{tokenPreview:logSensitiveData(L,`token`),channel:p}),L}catch(L){throw logger.error(`[${this.#connectionId}] Error getting token from provider`,{error:L,channel:p}),L}}async getTokenWithCache(p){logger.info(`[${this.#connectionId}] Requesting token from provider`,{channel:p});let L=await this.getToken(p);return L&&(logger.info(`[${this.#connectionId}] Token received from provider`),this.setCachedGrant(L)),L}},HeartbeatManager=class{#connectionId;#intervalMs;#intervalId;#sendHeartbeat;#log;constructor(p,L,R,V){this.#connectionId=p,this.#intervalMs=L,this.#sendHeartbeat=R,this.#log=V,logger.info(`[${this.#connectionId}] HeartbeatManager created`,{intervalMs:L})}get isRunning(){return this.#intervalId!==void 0}start(){logger.info(`[${this.#connectionId}] Starting heartbeat`,{intervalMs:this.#intervalMs}),this.stop(),this.#intervalId=setInterval(()=>{try{this.#log(`info`,`sending heartbeat ping`),logger.info(`[${this.#connectionId}] Sending heartbeat ping`),this.#sendHeartbeat()}catch(p){throw logger.error(`[${this.#connectionId}] Error sending heartbeat`,{error:p}),p}},this.#intervalMs),logger.info(`[${this.#connectionId}] Heartbeat started successfully`)}stop(){this.#intervalId?(logger.info(`[${this.#connectionId}] Stopping heartbeat`),clearInterval(this.#intervalId),this.#intervalId=void 0):logger.debug(`[${this.#connectionId}] No heartbeat to stop`)}setInterval(p){logger.info(`[${this.#connectionId}] Updating heartbeat interval`,{oldInterval:this.#intervalMs,newInterval:p});let L=this.isRunning;this.stop(),this.#intervalMs=p,L&&this.start()}getInterval(){return this.#intervalMs}},PresenceManager=class{#connectionId;#presenceHandlers=new Map;constructor(p){this.#connectionId=p,logger.info(`[${this.#connectionId}] PresenceManager created`)}onPresence(p,L){if(logger.info(`[${this.#connectionId}] Adding presence handler for topic`,{topic:p}),!p||typeof p!=`string`||p.trim().length===0){let L=`Invalid topic: must be a non-empty string`;throw logger.error(`[${this.#connectionId}] ${L}`,{topic:p}),Error(L)}if(typeof L!=`function`){let p=`Invalid handler: must be a function`;throw logger.error(`[${this.#connectionId}] ${p}`),Error(p)}this.#presenceHandlers.has(p)||this.#presenceHandlers.set(p,new Set),this.#presenceHandlers.get(p).add(L),logger.info(`[${this.#connectionId}] Presence handler added`,{topic:p,totalHandlers:this.#presenceHandlers.get(p).size})}offPresence(p,L){logger.info(`[${this.#connectionId}] Removing presence handler for topic`,{topic:p});let R=this.#presenceHandlers.get(p);if(!R){logger.warn(`[${this.#connectionId}] No handlers found for topic`,{topic:p});return}R.delete(L),R.size===0&&this.#presenceHandlers.delete(p),logger.info(`[${this.#connectionId}] Presence handler removed`,{topic:p,remainingHandlers:R.size})}clearPresenceHandlers(p){logger.info(`[${this.#connectionId}] Clearing all presence handlers for topic`,{topic:p}),this.#presenceHandlers.delete(p)}clearAllPresenceHandlers(){logger.info(`[${this.#connectionId}] Clearing all presence handlers`),this.#presenceHandlers.clear()}handlePresencePacket(p){logger.info(`[${this.#connectionId}] Handling presence packet`,{clientId:p.clientId,topic:p.topic,status:p.status});let L=this.#presenceHandlers.get(p.topic);if(!L||L.size===0){logger.debug(`[${this.#connectionId}] No presence handlers found for topic`,{topic:p.topic});return}let R={clientId:p.clientId,topic:p.topic,status:p.status,timestamp:Date.now(),...p.subscribers&&{subscribers:p.subscribers}};for(let V of L)try{V(R)}catch(L){logger.error(`[${this.#connectionId}] Error in presence handler`,{error:L,topic:p.topic,clientId:p.clientId})}logger.info(`[${this.#connectionId}] Presence packet handled successfully`,{topic:p.topic,handlersCount:L.size})}getTopicsWithPresenceHandlers(){return Array.from(this.#presenceHandlers.keys())}getPresenceHandlerCount(p){return this.#presenceHandlers.get(p)?.size||0}getTotalPresenceHandlerCount(){let p=0;for(let L of this.#presenceHandlers.values())p+=L.size;return p}get __debugPresenceHandlers(){return new Map(this.#presenceHandlers)}},PubSubConnection=class{#connectionManager;#ackManager;#subscriptionManager;#messageProcessor;#grantManager;#heartbeatManager;#presenceManager;#ackTimeoutMs=3e4;#connectionId;constructor(p){this.#connectionId=`conn_${Math.random().toString(36).slice(2,8)}`,logger.info(`[${this.#connectionId}] PubSubConnection constructor called`,{url:p.url,heartbeatMs:p.heartbeatMs??25e3}),this.#ackManager=new AckManager(this.#connectionId),this.#subscriptionManager=new SubscriptionManager(this.#connectionId),this.#presenceManager=new PresenceManager(this.#connectionId),this.#grantManager=new GrantManager(this.#connectionId,p.tokenProvider),this.#messageProcessor=new MessageProcessor(this.#connectionId,p.onMessage,this.#ackManager,this.#presenceManager);let L={...p,onMessage:async p=>{p.rawData?await this.#messageProcessor.processMessage(p.rawData):await this.#messageProcessor.handlePacket(p)}};this.#connectionManager=new ConnectionManager(L),this.#heartbeatManager=new HeartbeatManager(this.#connectionId,p.heartbeatMs??25e3,()=>this.#sendHeartbeat(),p.log??(()=>{})),logger.info(`[${this.#connectionId}] PubSubConnection initialized`)}get state(){return this.#connectionManager.state}get isConnected(){return this.#connectionManager.isConnected}get isConnecting(){return this.#connectionManager.isConnecting}get isClosed(){return this.#connectionManager.isClosed}get isIdle(){return this.#connectionManager.isIdle}get isReadable(){return this.#connectionManager.isConnected}get isWritable(){return this.#connectionManager.isConnected}get channel(){return this.#connectionManager.channel}get subscriptionCount(){return this.#subscriptionManager.subscriptionCount}get subscriptions(){return this.#subscriptionManager.subscriptions}get readyState(){return this.#connectionManager.readyState}get bufferedAmount(){return this.#connectionManager.bufferedAmount}get connectionId(){return this.#connectionManager.connectionId}get url(){return this.#connectionManager.url}get connectionHealth(){return{state:this.state,isConnected:this.isConnected,isReadable:this.isReadable,isWritable:this.isWritable,channel:this.channel,subscriptionCount:this.subscriptionCount,readyState:this.readyState,bufferedAmount:this.bufferedAmount,connectionId:this.connectionId,url:this.url}}get subscribedTopics(){return this.#subscriptionManager.subscribedTopics}get unsubscribedTopics(){return this.#subscriptionManager.unsubscribedTopics}get subscriptionTracking(){return this.#subscriptionManager.getSubscriptionTracking()}async open(p){let L=await this.#grantManager.getTokenWithCache(this.channel);await this.#connectionManager.open({grant:L,timeout:p}),this.#connectionManager.send({packetType:`connect`,version:`1`,grantJWT:L}),this.#heartbeatManager.start();let R=this.#subscriptionManager.getTopicsForResubscription();for(let p of R)logger.info(`[${this.#connectionId}] Resubscribing to topic`,{topic:p}),this.#connectionManager.send({packetType:`subscribe`,topic:p})}close(){this.#heartbeatManager.stop(),this.#ackManager.cleanup(`Connection closed`),this.#connectionManager.close(),this.#subscriptionManager.clear(),this.#grantManager.clearCachedGrant()}subscribe(p){this.subscribeWithCallback(p)}subscribeWithCallback(p,L,R){if(logger.info(`[${this.#connectionId}] Subscribe called`,{topic:p,hasCallback:!!L,timeout:R}),!this.#subscriptionManager.subscribe(p)){logger.info(`[${this.#connectionId}] Topic already subscribed`,{topic:p});return}if(this.isConnected){logger.info(`[${this.#connectionId}] Connection open, sending subscribe packet`,{topic:p});try{let B,V;if(L){B=`sub_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,typeof crypto<`u`&&typeof crypto.randomUUID==`function`&&(V=crypto.randomUUID());let H={requestId:B,clientMsgId:V,topic:p,path:`subscribe`,callback:L,timestamp:Date.now()};R&&R>0&&(H.timeoutId=setTimeout(()=>{this.#ackManager.handleSubscriptionTimeout(B)},R)),this.#ackManager.trackSubscription(B,H)}this.#connectionManager.send({packetType:`subscribe`,topic:p,...B&&{requestId:B},...V&&{clientMsgId:V}})}catch(L){throw logger.error(`[${this.#connectionId}] Error sending subscribe packet`,{error:L,topic:p}),this.#subscriptionManager.unsubscribe(p),L}}else logger.info(`[${this.#connectionId}] Connection not open, subscription will be sent when connected`,{topic:p})}unsubscribe(p){this.unsubscribeWithCallback(p)}unsubscribeWithCallback(p,L,R){if(logger.info(`[${this.#connectionId}] Unsubscribe called`,{topic:p,hasCallback:!!L,timeout:R}),!this.#subscriptionManager.unsubscribe(p)){logger.info(`[${this.#connectionId}] Topic already unsubscribed`,{topic:p});return}if(this.isConnected){logger.info(`[${this.#connectionId}] Connection open, sending unsubscribe packet`,{topic:p});try{let B,V;if(L){B=`unsub_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,typeof crypto<`u`&&typeof crypto.randomUUID==`function`&&(V=crypto.randomUUID());let H={requestId:B,clientMsgId:V,topic:p,path:`unsubscribe`,callback:L,timestamp:Date.now()};R&&R>0&&(H.timeoutId=setTimeout(()=>{this.#ackManager.handleSubscriptionTimeout(B)},R)),this.#ackManager.trackSubscription(B,H)}this.#connectionManager.send({packetType:`unsubscribe`,topic:p,...B&&{requestId:B},...V&&{clientMsgId:V}})}catch(L){logger.error(`[${this.#connectionId}] Error sending unsubscribe packet`,{error:L,topic:p})}}else logger.info(`[${this.#connectionId}] Connection not open, unsubscription will be sent when connected`,{topic:p})}publish(p){this.#publishInternal(p,!1)}publishWithAck(p,L,R=this.#ackTimeoutMs){return this.#publishInternal(p,!0,L,R)}isSubscribed(p){return this.#subscriptionManager.isSubscribed(p)}getSubscriptionStatus(p){return this.#subscriptionManager.getSubscriptionStatus(p)}setChannel(p){this.#connectionManager.setChannel(p),this.#grantManager.clearCachedGrant()}onPresence(p,L){this.#presenceManager.onPresence(p,L)}offPresence(p,L){this.#presenceManager.offPresence(p,L)}clearPresenceHandlers(p){this.#presenceManager.clearPresenceHandlers(p)}#publishInternal(p,L=!1,R,U){if(logger.info(`[${this.#connectionId}] Publish called`,{topic:p.topic,withAck:L}),!p||typeof p!=`object`){let L=`Invalid payload: must be an object`;throw logger.error(`[${this.#connectionId}] ${L}`,{payload:p}),Error(L)}if(L&&!R){let p=`ACK callback is required when withAck is true`;throw logger.error(`[${this.#connectionId}] ${p}`),Error(p)}if(!this.isConnected)throw logger.error(`[${this.#connectionId}] Cannot publish - not connected`,{state:this.state}),new NotConnectedError(`Not connected`);let W=`fallback_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,G;try{if(p.clientPublishTs||Object.assign(p,{clientPublishTs:Date.now()}),p.clientMsgId?W=p.clientMsgId:(W=typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`msg_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,Object.assign(p,{clientMsgId:W})),L&&R){G=`req_${Date.now()}_${nanoid()}`;let L={requestId:G,clientMsgId:W,topic:p.topic,callback:R,timestamp:Date.now()};U&&U>0&&(L.timeoutId=setTimeout(()=>{this.#ackManager.handlePublishTimeout(G)},U)),this.#ackManager.trackPublish(G,L)}}catch(L){Object.assign(p,{clientMsgId:W}),logger.warn(`[${this.#connectionId}] Error setting client fields, using fallback`,{err:L,clientMsgId:W})}logger.info(`[${this.#connectionId}] Publishing message`,{topic:p.topic,withAck:L,requestId:G,clientMsgId:W}),consola.info(`[PubSubConnection] [${this.#connectionId}] Publishing message`,{topic:p.topic,withAck:L});try{this.#connectionManager.send({packetType:`publish`,topic:p.topic,ack:L,payload:p,clientMsgId:W,...L&&G&&{requestId:G}})}catch(R){throw L&&G&&this.#ackManager.getPendingCount(),logger.error(`[${this.#connectionId}] Error publishing message`,{error:R,topic:p.topic}),R}return Promise.resolve(W)}#sendHeartbeat(){if(!this.isConnected){logger.debug(`[${this.#connectionId}] Skipping heartbeat - not connected`);return}try{this.#connectionManager.sendRaw(`ping`)}catch(p){throw logger.error(`[${this.#connectionId}] Error sending heartbeat`,{error:p}),this.#connectionManager.close(),p}}get __debugObject(){return{connectionManager:this.#connectionManager,ackManager:this.#ackManager,subscriptionManager:this.#subscriptionManager,messageProcessor:this.#messageProcessor,grantManager:this.#grantManager,heartbeatManager:this.#heartbeatManager}}get __debugState(){return this.#connectionManager.state}},StateManager=class{#connectionId;#connectionState=`idle`;#channel=null;#subscriptions=new Map;#processedMessages=new Set;#pendingSubscriptions=new Set;#handlers=new Map;#lastActivity=Date.now();#error=null;#isReconnecting=!1;#reconnectAttempts=0;constructor(p){this.#connectionId=p,logger.info(`[${this.#connectionId}] StateManager created`)}get connectionState(){return this.#connectionState}setConnectionState(p){logger.info(`[${this.#connectionId}] Connection state changed`,{from:this.#connectionState,to:p}),this.#connectionState=p,this.#updateActivity()}get isConnected(){return this.#connectionState===`open`}get isConnecting(){return this.#connectionState===`connecting`}get isClosed(){return this.#connectionState===`closed`}get isIdle(){return this.#connectionState===`idle`}get channel(){return this.#channel}setChannel(p){logger.info(`[${this.#connectionId}] Channel set`,{channel:p}),this.#channel=p,this.#updateActivity()}getChannel(){return this.#channel}get subscriptionCount(){return this.#subscriptions.size}get activeTopics(){return Array.from(this.#subscriptions.entries()).filter(([p,L])=>L===`subscribed`).map(([p,L])=>p)}get pendingTopics(){return Array.from(this.#subscriptions.entries()).filter(([p,L])=>L===`pending`).map(([p,L])=>p)}get unsubscribedTopics(){return Array.from(this.#subscriptions.entries()).filter(([p,L])=>L===`unsubscribed`).map(([p,L])=>p)}setSubscriptionStatus(p,L){logger.info(`[${this.#connectionId}] Subscription status updated`,{topic:p,status:L}),this.#subscriptions.set(p,L),this.#updateActivity(),L===`subscribed`?this.#pendingSubscriptions.delete(p):L===`pending`&&this.#pendingSubscriptions.add(p)}getSubscriptionStatus(p){return this.#subscriptions.get(p)||`unsubscribed`}isSubscribed(p){return this.getSubscriptionStatus(p)===`subscribed`}get processedMessagesCount(){return this.#processedMessages.size}addProcessedMessage(p){if(this.#processedMessages.add(p),this.#updateActivity(),this.#processedMessages.size>1e3){let p=Array.from(this.#processedMessages);this.#processedMessages.clear(),p.slice(-500).forEach(p=>this.#processedMessages.add(p))}}isMessageProcessed(p){return this.#processedMessages.has(p)}clearProcessedMessages(){logger.info(`[${this.#connectionId}] Clearing processed messages`),this.#processedMessages.clear()}get handlerCount(){let p=0;for(let L of this.#handlers.values())p+=L.size;return p}getTopicsWithHandlers(){return Array.from(this.#handlers.keys())}getHandlerCountForTopic(p){return this.#handlers.get(p)?.size||0}addHandler(p,L){this.#handlers.has(p)||this.#handlers.set(p,new Set),this.#handlers.get(p).add(L),this.#updateActivity()}removeHandler(p,L){let R=this.#handlers.get(p);R&&(R.delete(L),R.size===0&&this.#handlers.delete(p)),this.#updateActivity()}clearHandlers(p){this.#handlers.delete(p),this.#updateActivity()}getHandlers(p){return this.#handlers.get(p)}get pendingSubscriptionsCount(){return this.#pendingSubscriptions.size}addPendingSubscription(p){this.#pendingSubscriptions.add(p),this.#updateActivity()}removePendingSubscription(p){this.#pendingSubscriptions.delete(p),this.#updateActivity()}clearPendingSubscriptions(){logger.info(`[${this.#connectionId}] Clearing pending subscriptions`),this.#pendingSubscriptions.clear()}get hasError(){return this.#error!==null}get error(){return this.#error}setError(p){logger.error(`[${this.#connectionId}] Error set`,{error:p}),this.#error=p,this.#updateActivity()}clearError(){logger.info(`[${this.#connectionId}] Error cleared`),this.#error=null,this.#updateActivity()}get isReconnecting(){return this.#isReconnecting}setReconnecting(p){this.#isReconnecting=p,this.#updateActivity()}get reconnectAttempts(){return this.#reconnectAttempts}incrementReconnectAttempts(){this.#reconnectAttempts++,this.#updateActivity()}resetReconnectAttempts(){this.#reconnectAttempts=0,this.#updateActivity()}get lastActivity(){return this.#lastActivity}#updateActivity(){this.#lastActivity=Date.now()}get stateSummary(){return{connectionState:this.#connectionState,channel:this.#channel,subscriptionCount:this.subscriptionCount,handlerCount:this.handlerCount,processedMessagesCount:this.processedMessagesCount,pendingSubscriptionsCount:this.pendingSubscriptionsCount,hasError:this.hasError,isReconnecting:this.#isReconnecting,reconnectAttempts:this.#reconnectAttempts,lastActivity:this.#lastActivity}}reset(){logger.info(`[${this.#connectionId}] Resetting state manager`),this.#connectionState=`idle`,this.#channel=null,this.#subscriptions.clear(),this.#processedMessages.clear(),this.#pendingSubscriptions.clear(),this.#handlers.clear(),this.#error=null,this.#isReconnecting=!1,this.#reconnectAttempts=0,this.#updateActivity()}clear(){logger.info(`[${this.#connectionId}] Clearing state manager`),this.#subscriptions.clear(),this.#processedMessages.clear(),this.#pendingSubscriptions.clear(),this.#handlers.clear(),this.#error=null,this.#isReconnecting=!1,this.#reconnectAttempts=0,this.#updateActivity()}get __debugState(){return{connectionState:this.#connectionState,channel:this.#channel,subscriptions:new Map(this.#subscriptions),handlers:new Map(this.#handlers),processedMessages:new Set(this.#processedMessages),pendingSubscriptions:new Set(this.#pendingSubscriptions),error:this.#error,isReconnecting:this.#isReconnecting,reconnectAttempts:this.#reconnectAttempts,lastActivity:this.#lastActivity}}},ErebusPubSubClientNew=class{#conn;#stateManager;#debug;constructor(p){this.#debug=p.debug??!1;let L=Math.random().toString(36).substring(2,8);consola.info(`[Erebus:${L}] Constructor called`,{wsUrl:p.wsUrl,hasTokenProvider:!!p.tokenProvider,hasCustomLog:!!p.log}),logger.info(`Erebus constructor called`,{opts:p}),this.#conn=new PubSubConnection({url:p.wsUrl,tokenProvider:p.tokenProvider,channel:``,heartbeatMs:p.heartbeatMs,log:p.log,onMessage:p=>this.#handleMessage(p)}),this.#stateManager=new StateManager(this.#conn.connectionId),consola.info(`[Erebus:${L}] Instance created successfully`,{wsUrl:p.wsUrl}),logger.info(`Erebus instance created`,{wsUrl:p.wsUrl})}connect(p){let L=this.#conn.connectionId;if(consola.info(`[Erebus:${L}] Connect called`,{timeout:p}),logger.info(`Erebus.connect() called`),!this.#stateManager.channel){let p=`Channel must be set before connecting. Call joinChannel(channel) first.`;throw consola.error(`[Erebus:${L}] ${p}`),logger.error(`Connect failed - no channel set`),Error(p)}return this.#stateManager.clearProcessedMessages(),this.#conn.open(p)}joinChannel(p){let L=this.#conn.connectionId;if(consola.info(`[Erebus:${L}] Joining channel`,{channel:p}),logger.info(`Erebus.joinChannel() called`,{channel:p}),!p||typeof p!=`string`||p.trim().length===0){let R=`Invalid channel: must be a non-empty string`;throw consola.error(`[Erebus:${L}] ${R}`,{channel:p}),logger.error(`Invalid channel`,{channel:p}),Error(R)}if(this.#stateManager.getChannel()===p){consola.info(`[Erebus:${L}] Channel already joined`,{channel:p}),logger.info(`Channel already joined`,{channel:p});return}this.#stateManager.setChannel(p),this.#conn.setChannel(p),consola.info(`[Erebus:${L}] Channel joined successfully`,{channel:p}),logger.info(`Channel joined successfully`,{channel:p})}subscribe(p,L,R,B=3e3){this.subscribeWithCallback(p,L,R,B)}subscribeWithCallback(p,L,R,H){let U=this.#conn.connectionId;if(consola.info(`[Erebus:${U}] Subscribe called`,{topic:p,hasAckCallback:!!R,timeout:H}),logger.info(`Erebus.subscribe() called`,{topic:p}),!this.#stateManager.channel){let L=`Channel must be set before subscribing. Call joinChannel(channel) first.`;throw consola.error(`[Erebus:${U}] ${L}`,{topic:p}),logger.error(`Subscribe failed - no channel set`,{topic:p}),Error(L)}if(!p||typeof p!=`string`||p.trim().length===0){let L=`Invalid topic: must be a non-empty string`;throw consola.error(`[Erebus:${U}] ${L}`,{topic:p}),logger.error(`Invalid topic`,{topic:p}),Error(L)}if(typeof L!=`function`){let p=`Invalid handler: must be a function`;throw consola.error(`[Erebus:${U}] ${p}`),logger.error(`Invalid handler`,{handlerType:typeof L}),Error(p)}this.#stateManager.addHandler(p,L),this.#stateManager.addPendingSubscription(p),this.#conn.subscribeWithCallback(p,R,H)}unsubscribe(p){this.unsubscribeWithCallback(p)}unsubscribeWithCallback(p,L,R){let H=this.#conn.connectionId;if(consola.info(`[Erebus:${H}] Unsubscribe called`,{topic:p,hasAckCallback:!!L,timeout:R}),logger.info(`Unsubscribe function called`,{topic:p}),!p||typeof p!=`string`||p.trim().length===0){let L=`Invalid topic: must be a non-empty string`;throw consola.error(`[Erebus:${H}] ${L}`,{topic:p}),logger.error(`Invalid topic`,{topic:p}),Error(L)}let U=this.#stateManager.getHandlers(p);if(!U||U.size===0){consola.warn(`[Erebus:${H}] No handler set found during unsubscribe`,{topic:p}),logger.warn(`No handler set found during unsubscribe`,{topic:p});return}this.#stateManager.clearHandlers(p),this.#stateManager.removePendingSubscription(p),this.#conn.unsubscribeWithCallback(p,L,R)}publish({topic:p,messageBody:L}){return this.#publishInternal(p,L,!1)}publishWithAck({topic:p,messageBody:L,onAck:R,timeoutMs:B=3e3}