@wxn0brp/gloves-link-client 0.0.14 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 wxn0brP
3
+ Copyright (c) 2026 wxn0brP
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,2 +1,2 @@
1
- var u=class{_events={};on(e,t){let s=e;this._events[s]||(this._events[s]=[]),this._events[s].push(t)}once(e,t){let s=(...o)=>{this.off(e,s),t(...o)};this.on(e,s)}off(e,t){let s=e;this._events[s]&&(this._events[s]=this._events[s].filter(o=>o!==t))}emit(e,...t){let s=this._events[e];s&&s.length>0&&s.forEach(o=>{o(...t)})}listenerCount(e){return this._events[e]?.length||0}},h=u;var d=class{ws;ackIdCounter;ackCallbacks;handlers=new h;opts;url;connected=!1;_manuallyDisconnected=!1;messageQueue=[];constructor(e,t={}){this.ackIdCounter=1,this.ackCallbacks=new Map,this.opts={logs:!1,reConnect:!0,reConnectInterval:1e3,token:null,autoConnect:!0,...t},this.url=new URL(e,typeof window<"u"?window.location.href.replace("http","ws"):"ws://localhost"),this.opts.token&&this.url.searchParams.set("token",this.opts.token),this.opts.autoConnect&&this.connect()}connect(){this._manuallyDisconnected=!1;let e=Date.now().toString(36)+Math.random().toString(36).substring(2,10);this.url.searchParams.set("id",e),this.opts.connectionData&&this.url.searchParams.set("data",JSON.stringify(this.opts.connectionData)),this.ws=new WebSocket(this.url.href),this.ws.onopen=()=>{this.connected=!0,this.opts.logs&&console.log("[ws] Connected");let t;for(;t=this.messageQueue.shift();)this.ws.send(t);this._handlersEmit("connect")},this.ws.onerror=(...t)=>{this.opts.logs&&console.warn("[ws] Error:",t),this._handlersEmit("error",...t)},this.ws.onmessage=t=>{let s=t?.data?.toString()||t?.toString()||"",o;try{o=JSON.parse(s)}catch{this.opts.logs&&console.warn("[ws] Invalid JSON:",s);return}if("ack"in o){let c=o.ack,l=this.ackCallbacks.get(c);l&&(this.ackCallbacks.delete(c),l(...o.data));return}let{evt:a,data:n,ackI:i}=o;if(!(!a||n&&!Array.isArray(n))){if(Array.isArray(i))for(let c=0;c<i.length;c++){let l=i[c];if(!n[l])break;let f=n[l];n[l]=(...p)=>{this.ws.send(JSON.stringify({ack:f,data:p}))}}this._handlersEmit(a,...n)}},this.ws.onclose=async t=>{if(this.connected=!1,this.opts.logs&&console.log("[ws] Disconnected",t),this._handlersEmit("disconnect",t),this._manuallyDisconnected){this._manuallyDisconnected=!1;return}if(t.code===1006){this.opts.logs&&console.log("[ws] Connection closed by server");try{if(!await v(this,e))return}catch(s){this.opts.logs&&console.error("[ws] Status error",s)}}this.opts.reConnect&&setTimeout(()=>{this.connect()},this.opts.reConnectInterval)}}on(e,t){this.handlers.on(e,t)}once(e,t){this.handlers.once(e,t)}emit(e,...t){let s=t.map((a,n)=>{if(typeof a=="function")return n}).filter(a=>a!==void 0);for(let a=0;a<s.length;a++){let n=s[a],i=this.ackIdCounter++;this.ackCallbacks.set(i,t[n]),t[n]=i}let o=JSON.stringify({evt:e,data:t||void 0,ackI:s.length?s:void 0});this.connected&&this.ws?.readyState===WebSocket.OPEN?this.ws.send(o):this.messageQueue.push(o)}send(e,...t){return this.emit(e,...t)}disconnect(){this._manuallyDisconnected=!0,this.ws.close()}close(){this.ws.close()}_handlersEmit(e,...t){this.handlers.emit(e,...t),this.handlers.emit("*",e,...t)}};async function v(r,e){let t=new URLSearchParams;t.set("id",e),t.set("path",r.url.pathname);let s=r.url.origin+"/gloves-link/status?"+t.toString(),o=await fetch(s.replace("ws","http"));if(!o.ok)return console.error("[ws] Status error",o.status),!0;let a=await o.json();if(a.err)return r.opts.logs&&console.log("[ws] Status error",a.msg),!0;let n=a.status;return r.opts.logs&&console.log("[ws] Status",n),n.status===401?(r._handlersEmit("connect_unauthorized",n.msg),!1):n.status===403?(r._handlersEmit("connect_forbidden",n.msg),!1):n.status===500?(r._handlersEmit("connect_serverError",n.msg),!1):!0}export{d as GLC,d as GlovesLinkClient,d as client,d as default};
1
+ var h=class{_events={};on(e,t){let s=e;this._events[s]||(this._events[s]=[]),this._events[s].push(t)}once(e,t){let s=(...o)=>{this.off(e,s),t(...o)};this.on(e,s)}off(e,t){let s=e;this._events[s]&&(this._events[s]=this._events[s].filter(o=>o!==t))}_emit(e,...t){let s=this._events[e];s?.length&&s.forEach(o=>o(...t))}emit(e,...t){this._emit(e,...t),this._emit("*",e,...t)}listenerCount(e){return this._events[e]?.length||0}},u=h;var d=class{_ws;_ackIdCounter;_ackCallbacks;_handlers=new u;_manuallyDisconnected=!1;_messageQueue=[];_reconnectAttempts=0;opts;url;connected=!1;constructor(e,t={}){this._ackIdCounter=1,this._ackCallbacks=new Map,this.opts={logs:!1,token:null,autoConnect:!0,statusPath:"/gloves-link/status",reConnect:!0,reConnectInterval:1e3,maxReConnectAttempts:5,reConnectBackoffFactor:2,maxReConnectDelay:15e3,...t},this.url=new URL(e,typeof window<"u"?window.location.href.replace("http","ws"):"ws://localhost"),this.opts.token&&this.url.searchParams.set("token",this.opts.token),this.opts.autoConnect&&this.connect()}connect(){this._manuallyDisconnected=!1;let e=Date.now().toString(36)+Math.random().toString(36).substring(2,10);this.url.searchParams.set("id",e),this.opts.connectionData&&this.url.searchParams.set("data",JSON.stringify(this.opts.connectionData)),this._ws=new WebSocket(this.url.href),this._ws.onopen=()=>{this.connected=!0,this._reconnectAttempts=0,this.opts.logs&&console.log("[ws] Connected");let t;for(;t=this._messageQueue.shift();)this._ws.send(t);this._handlersEmit("connect")},this._ws.onerror=(...t)=>{this.opts.logs&&console.warn("[ws] Error:",t),this._handlersEmit("error",...t)},this._ws.onmessage=t=>{let s=t?.data?.toString()||t?.toString()||"",o;try{o=JSON.parse(s)}catch{this.opts.logs&&console.warn("[ws] Invalid JSON:",s);return}if("ack"in o){let c=o.ack,l=this._ackCallbacks.get(c);l&&(this._ackCallbacks.delete(c),l(...o.data));return}let{evt:a,data:n,ackI:i}=o;if(!(!a||n&&!Array.isArray(n))){if(Array.isArray(i))for(let c=0;c<i.length;c++){let l=i[c];if(!n[l])break;let m=n[l];n[l]=(...p)=>{this._ws.send(JSON.stringify({ack:m,data:p}))}}this._handlersEmit(a,...n)}},this._ws.onclose=async t=>{if(this.connected=!1,this.opts.logs&&console.log("[ws] Disconnected",t),this._handlersEmit("disconnect",t),this._manuallyDisconnected||!this.opts.reConnect)return;if(t.code===1006){this.opts.logs&&console.log("[ws] Connection closed by server");try{if(!await f(this,e))return}catch(n){this.opts.logs&&console.error("[ws] Status error",n)}}if(this._reconnectAttempts++,this._reconnectAttempts>this.opts.maxReConnectAttempts){this.opts.logs&&console.error(`[ws] Max reconnect attempts reached (${this.opts.maxReConnectAttempts})`),this._handlersEmit("reconnect_failed");return}let s=Math.min(this.opts.reConnectInterval*this.opts.reConnectBackoffFactor**(this._reconnectAttempts-1),this.opts.maxReConnectDelay),o=1+Math.random()*.5,a=Math.max(s*o,this.opts.reConnectInterval);this.opts.logs&&console.log(`[ws] Reconnecting in ${a.toFixed(0)}ms (attempt ${this._reconnectAttempts})`),setTimeout(()=>{this.connect()},a)}}on(e,t){this._handlers.on(e,t)}once(e,t){this._handlers.once(e,t)}emit(e,...t){let s=t.map((a,n)=>{if(typeof a=="function")return n}).filter(a=>a!==void 0);for(let a=0;a<s.length;a++){let n=s[a],i=this._ackIdCounter++;this._ackCallbacks.set(i,t[n]),t[n]=i}let o=JSON.stringify({evt:e,data:t||void 0,ackI:s.length?s:void 0});this.connected&&this._ws?.readyState===WebSocket.OPEN?this._ws.send(o):this._messageQueue.push(o)}send(e,...t){return this.emit(e,...t)}disconnect(){this._manuallyDisconnected=!0,this._ws.close()}close(){this._ws.close()}_handlersEmit(e,...t){this._handlers.emit(e,...t)}};async function f(r,e){let t=new URL(r.opts.statusPath,r.url.origin);t.searchParams.set("id",e),t.searchParams.set("path",r.url.pathname);let s=t.toString().replace("ws","http"),o=await fetch(s);if(!o.ok)return console.error("[ws] Status error",o.status),!0;let a=await o.json();if(a.err)return r.opts.logs&&console.log("[ws] Status error",a.msg),!0;let n=a.status;return r.opts.logs&&console.log("[ws] Status",n),n.status===401?(r._handlersEmit("connect_unauthorized",n.msg),!1):n.status===403?(r._handlersEmit("connect_forbidden",n.msg),!1):n.status===500?(r._handlersEmit("connect_serverError",n.msg),!1):!0}export{d as GLC,d as GlovesLinkClient,d as client,d as default};
2
2
  //# sourceMappingURL=GlovesLinkClient.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../node_modules/@wxn0brp/event-emitter/dist/index.js", "../src/index.ts"],
4
- "sourcesContent": ["export class VEE {\n _events = {};\n /**\n * Registers an event listener\n * @param {K} event - event name\n * @param {Function} listener - function to be called when event occurs\n */\n on(event, listener) {\n const _event = event;\n if (!this._events[_event])\n this._events[_event] = [];\n this._events[_event].push(listener);\n }\n /**\n * Registers a one-time event listener\n * @param {K} event - event name\n * @param {Function} listener - function to be called once\n */\n once(event, listener) {\n const onceListener = (...args) => {\n this.off(event, onceListener);\n listener(...args);\n };\n this.on(event, onceListener);\n }\n /**\n * Removes an event listener.\n * @param {K} event - event name\n * @param {Function} listener - listener to remove\n */\n off(event, listener) {\n const _event = event;\n if (!this._events[_event])\n return;\n this._events[_event] = this._events[_event].filter(l => l !== listener);\n }\n /**\n * Emits an event\n * @param {K} event - event name\n * @param {...EventArgs<T, K>} args - arguments to be passed to listeners\n */\n emit(event, ...args) {\n const listeners = this._events[event];\n if (listeners && listeners.length > 0) {\n listeners.forEach(listener => {\n listener(...args);\n });\n }\n }\n /**\n * Returns the number of listeners for the given event\n * @param {K} event - event name\n */\n listenerCount(event) {\n return this._events[event]?.length || 0;\n }\n}\nexport default VEE;\nexport { VEE as EventEmitter };\n", "import VEE, { EventArgs, EventMap, EventName } from \"@wxn0brp/event-emitter\";\n\nexport interface GLC_Opts {\n reConnect: boolean,\n reConnectInterval: number,\n logs: boolean;\n token: string;\n autoConnect: boolean;\n connectionData?: Record<string, any>;\n}\n\nexport interface GLC_DataEvent {\n evt: string;\n data: any[];\n ackI?: number[];\n}\n\nexport interface GLC_AckEvent {\n ack: number;\n data: any[];\n}\n\nexport type InternalEvents = {\n connect: (ws: WebSocket) => void;\n error: (...err: any[]) => void;\n disconnect: (event: CloseEvent) => void;\n connect_unauthorized: (msg: string) => void;\n connect_forbidden: (msg: string) => void;\n connect_serverError: (msg: string) => void;\n}\n\nexport class GlovesLinkClient<InputEvents extends EventMap = {}, OutputEvents extends EventMap = {}> {\n public ws: WebSocket;\n public ackIdCounter: number;\n public ackCallbacks: Map<number, Function>;\n public handlers = new VEE<InputEvents>();\n public opts: GLC_Opts;\n public url: URL;\n public connected: boolean = false;\n private _manuallyDisconnected: boolean = false;\n private messageQueue: string[] = [];\n\n constructor(url: string, opts: Partial<GLC_Opts> = {}) {\n this.ackIdCounter = 1;\n this.ackCallbacks = new Map();\n this.opts = {\n logs: false,\n reConnect: true,\n reConnectInterval: 1000,\n token: null,\n autoConnect: true,\n ...opts\n }\n\n this.url = new URL(url, typeof window !== \"undefined\" ? window.location.href.replace(\"http\", \"ws\") : \"ws://localhost\");\n if (this.opts.token) this.url.searchParams.set(\"token\", this.opts.token);\n\n if (this.opts.autoConnect) this.connect();\n }\n\n connect() {\n this._manuallyDisconnected = false;\n const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 10);\n this.url.searchParams.set(\"id\", id);\n\n if (this.opts.connectionData)\n this.url.searchParams.set(\"data\", JSON.stringify(this.opts.connectionData));\n\n this.ws = new WebSocket(this.url.href);\n\n this.ws.onopen = () => {\n this.connected = true;\n if (this.opts.logs) console.log(\"[ws] Connected\");\n\n let msg: string;\n while (msg = this.messageQueue.shift()) {\n this.ws.send(msg);\n }\n\n this._handlersEmit(\"connect\");\n }\n\n this.ws.onerror = (...err: any) => {\n if (this.opts.logs) console.warn(\"[ws] Error:\", err);\n this._handlersEmit(\"error\", ...err);\n }\n\n this.ws.onmessage = (_data) => {\n const raw = _data?.data?.toString() || _data?.toString() || \"\";\n let msg: GLC_DataEvent | GLC_AckEvent;\n\n try {\n msg = JSON.parse(raw);\n } catch {\n if (this.opts.logs) console.warn(\"[ws] Invalid JSON:\", raw);\n return;\n }\n\n if (\"ack\" in msg) {\n const ackId = msg.ack;\n const ackCallback = this.ackCallbacks.get(ackId);\n if (ackCallback) {\n this.ackCallbacks.delete(ackId);\n ackCallback(...msg.data);\n }\n return;\n }\n\n const { evt, data, ackI } = msg;\n if (!evt || (data && !Array.isArray(data))) return;\n\n if (Array.isArray(ackI)) {\n for (let i = 0; i < ackI.length; i++) {\n const ackIndex = ackI[i];\n if (!data[ackIndex]) break;\n\n const ackId = data[ackIndex];\n data[ackIndex] = (...res: any) => {\n this.ws.send(JSON.stringify({\n ack: ackId,\n data: res\n }));\n };\n }\n }\n\n this._handlersEmit(evt, ...data);\n }\n\n this.ws.onclose = async (event: CloseEvent) => {\n this.connected = false;\n if (this.opts.logs) console.log(\"[ws] Disconnected\", event);\n this._handlersEmit(\"disconnect\", event);\n\n if (this._manuallyDisconnected) {\n this._manuallyDisconnected = false;\n return;\n }\n\n if (event.code === 1006) {\n if (this.opts.logs) console.log(\"[ws] Connection closed by server\");\n\n try {\n const canReconnect = await checkStatus(this, id);\n if (!canReconnect) return;\n } catch (e) {\n if (this.opts.logs)\n console.error(\"[ws] Status error\", e);\n }\n }\n\n if (!this.opts.reConnect) return;\n\n setTimeout(() => {\n this.connect();\n }, this.opts.reConnectInterval);\n }\n }\n\n on<K extends EventName<InputEvents & InternalEvents>>(event: K, listener: (InputEvents & InternalEvents)[K]) {\n this.handlers.on(event, listener as any);\n }\n\n once<K extends EventName<InputEvents & InternalEvents>>(event: K, listener: (InputEvents & InternalEvents)[K]) {\n this.handlers.once(event, listener as any);\n }\n\n emit<K extends EventName<OutputEvents>>(evt: K, ...args: EventArgs<OutputEvents, K>) {\n const ackI = args.map((data, i) => {\n if (typeof data === \"function\") return i;\n }).filter(i => i !== undefined);\n\n for (let i = 0; i < ackI.length; i++) {\n const ackIndex = ackI[i];\n const ackId = this.ackIdCounter++;\n this.ackCallbacks.set(ackId, args[ackIndex]);\n args[ackIndex] = ackId;\n }\n\n const payload = JSON.stringify({\n evt,\n data: args || undefined,\n ackI: ackI.length ? ackI : undefined\n });\n\n if (this.connected && this.ws?.readyState === WebSocket.OPEN)\n this.ws.send(payload);\n else\n this.messageQueue.push(payload);\n }\n\n send<K extends EventName<OutputEvents>>(evt: K, ...args: EventArgs<OutputEvents, K>) {\n return this.emit(evt, ...args);\n }\n\n disconnect() {\n this._manuallyDisconnected = true;\n this.ws.close();\n }\n\n close() {\n this.ws.close();\n }\n\n _handlersEmit(evtName: string, ...args: any[]) {\n // @ts-ignore\n this.handlers.emit(evtName, ...args);\n // @ts-ignore\n this.handlers.emit(\"*\", evtName, ...args);\n }\n}\n\nasync function checkStatus(client: GlovesLinkClient, id: string) {\n const params = new URLSearchParams();\n params.set(\"id\", id);\n params.set(\"path\", client.url.pathname);\n\n const statusUrl = client.url.origin + \"/gloves-link/status?\" + params.toString();\n const res = await fetch(statusUrl.replace(\"ws\", \"http\"));\n if (!res.ok) {\n console.error(\"[ws] Status error\", res.status);\n return true;\n }\n\n const data = await res.json();\n if (data.err) {\n if (client.opts.logs) console.log(\"[ws] Status error\", data.msg);\n return true;\n }\n\n const status = data.status as { status: number, msg?: string };\n if (client.opts.logs) console.log(\"[ws] Status\", status);\n\n if (status.status === 401) {\n client._handlersEmit(\"connect_unauthorized\", status.msg);\n return false;\n }\n else if (status.status === 403) {\n client._handlersEmit(\"connect_forbidden\", status.msg);\n return false;\n }\n else if (status.status === 500) {\n client._handlersEmit(\"connect_serverError\", status.msg);\n return false;\n }\n\n return true;\n}\n\nexport {\n GlovesLinkClient as default,\n GlovesLinkClient as GLC,\n GlovesLinkClient as client,\n}"],
5
- "mappings": "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",
6
- "names": ["VEE", "event", "listener", "_event", "onceListener", "args", "l", "listeners", "dist_default", "GlovesLinkClient", "dist_default", "url", "opts", "id", "msg", "err", "_data", "raw", "ackId", "ackCallback", "evt", "data", "ackI", "i", "ackIndex", "res", "event", "checkStatus", "e", "listener", "args", "payload", "evtName", "client", "params", "statusUrl", "status"]
4
+ "sourcesContent": ["export class VEE {\n _events = {};\n /**\n * Registers an event listener\n * @param {K} event - event name\n * @param {Function} listener - function to be called when event occurs\n */\n on(event, listener) {\n const _event = event;\n if (!this._events[_event])\n this._events[_event] = [];\n this._events[_event].push(listener);\n }\n /**\n * Registers a one-time event listener\n * @param {K} event - event name\n * @param {Function} listener - function to be called once\n */\n once(event, listener) {\n const onceListener = (...args) => {\n this.off(event, onceListener);\n listener(...args);\n };\n this.on(event, onceListener);\n }\n /**\n * Removes an event listener.\n * @param {K} event - event name\n * @param {Function} listener - listener to remove\n */\n off(event, listener) {\n const _event = event;\n if (!this._events[_event])\n return;\n this._events[_event] = this._events[_event].filter(l => l !== listener);\n }\n _emit(event, ...args) {\n const listeners = this._events[event];\n if (!listeners?.length)\n return;\n listeners.forEach(listener => listener(...args));\n }\n /**\n * Emits an event\n * @param {K} event - event name\n * @param {...EventArgs<T, K>} args - arguments to be passed to listeners\n */\n emit(event, ...args) {\n this._emit(event, ...args);\n this._emit(\"*\", event, ...args);\n }\n /**\n * Returns the number of listeners for the given event\n * @param {K} event - event name\n */\n listenerCount(event) {\n return this._events[event]?.length || 0;\n }\n}\nexport default VEE;\nexport { VEE as EventEmitter };\n", "import VEE, { EventArgs, EventMap, EventName } from \"@wxn0brp/event-emitter\";\n\nexport interface GLC_Opts {\n logs: boolean;\n token: string;\n autoConnect: boolean;\n connectionData?: Record<string, any>;\n statusPath: string;\n reConnect: boolean,\n reConnectInterval: number,\n reConnectBackoffFactor: number;\n maxReConnectAttempts: number;\n /** Note: without jitter */\n maxReConnectDelay: number;\n}\n\nexport interface GLC_DataEvent {\n evt: string;\n data: any[];\n ackI?: number[];\n}\n\nexport interface GLC_AckEvent {\n ack: number;\n data: any[];\n}\n\nexport type InternalEvents = {\n connect: (ws: WebSocket) => void;\n error: (...err: any[]) => void;\n disconnect: (event: CloseEvent) => void;\n connect_unauthorized: (msg: string) => void;\n connect_forbidden: (msg: string) => void;\n connect_serverError: (msg: string) => void;\n reconnect_failed: () => void;\n}\n\nexport class GlovesLinkClient<InputEvents extends EventMap = {}, OutputEvents extends EventMap = {}> {\n _ws: WebSocket;\n _ackIdCounter: number;\n _ackCallbacks: Map<number, Function>;\n _handlers = new VEE<InputEvents>();\n _manuallyDisconnected: boolean = false;\n _messageQueue: string[] = [];\n _reconnectAttempts: number = 0;\n\n opts: GLC_Opts;\n url: URL;\n connected: boolean = false;\n\n constructor(url: string, opts: Partial<GLC_Opts> = {}) {\n this._ackIdCounter = 1;\n this._ackCallbacks = new Map();\n this.opts = {\n logs: false,\n token: null,\n autoConnect: true,\n statusPath: \"/gloves-link/status\",\n reConnect: true,\n reConnectInterval: 1000,\n maxReConnectAttempts: 5,\n reConnectBackoffFactor: 2,\n maxReConnectDelay: 15_000,\n ...opts\n }\n\n this.url = new URL(url, typeof window !== \"undefined\" ? window.location.href.replace(\"http\", \"ws\") : \"ws://localhost\");\n if (this.opts.token) this.url.searchParams.set(\"token\", this.opts.token);\n\n if (this.opts.autoConnect) this.connect();\n }\n\n connect() {\n this._manuallyDisconnected = false;\n const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 10);\n this.url.searchParams.set(\"id\", id);\n\n if (this.opts.connectionData)\n this.url.searchParams.set(\"data\", JSON.stringify(this.opts.connectionData));\n\n this._ws = new WebSocket(this.url.href);\n\n this._ws.onopen = () => {\n this.connected = true;\n this._reconnectAttempts = 0;\n if (this.opts.logs) console.log(\"[ws] Connected\");\n\n let msg: string;\n while (msg = this._messageQueue.shift()) {\n this._ws.send(msg);\n }\n\n this._handlersEmit(\"connect\");\n }\n\n this._ws.onerror = (...err: any) => {\n if (this.opts.logs) console.warn(\"[ws] Error:\", err);\n this._handlersEmit(\"error\", ...err);\n }\n\n this._ws.onmessage = (_data) => {\n const raw = _data?.data?.toString() || _data?.toString() || \"\";\n let msg: GLC_DataEvent | GLC_AckEvent;\n\n try {\n msg = JSON.parse(raw);\n } catch {\n if (this.opts.logs) console.warn(\"[ws] Invalid JSON:\", raw);\n return;\n }\n\n if (\"ack\" in msg) {\n const ackId = msg.ack;\n const ackCallback = this._ackCallbacks.get(ackId);\n if (ackCallback) {\n this._ackCallbacks.delete(ackId);\n ackCallback(...msg.data);\n }\n return;\n }\n\n const { evt, data, ackI } = msg;\n if (!evt || (data && !Array.isArray(data))) return;\n\n if (Array.isArray(ackI)) {\n for (let i = 0; i < ackI.length; i++) {\n const ackIndex = ackI[i];\n if (!data[ackIndex]) break;\n\n const ackId = data[ackIndex];\n data[ackIndex] = (...res: any) => {\n this._ws.send(JSON.stringify({\n ack: ackId,\n data: res\n }));\n };\n }\n }\n\n this._handlersEmit(evt, ...data);\n }\n\n this._ws.onclose = async (event: CloseEvent) => {\n this.connected = false;\n if (this.opts.logs) console.log(\"[ws] Disconnected\", event);\n this._handlersEmit(\"disconnect\", event);\n\n if (this._manuallyDisconnected || !this.opts.reConnect) return;\n\n if (event.code === 1006) {\n if (this.opts.logs) console.log(\"[ws] Connection closed by server\");\n\n try {\n const canReconnect = await checkStatus(this, id);\n if (!canReconnect) return;\n } catch (e) {\n if (this.opts.logs)\n console.error(\"[ws] Status error\", e);\n }\n }\n\n this._reconnectAttempts++;\n\n if (this._reconnectAttempts > this.opts.maxReConnectAttempts) {\n if (this.opts.logs)\n console.error(`[ws] Max reconnect attempts reached (${this.opts.maxReConnectAttempts})`);\n\n this._handlersEmit(\"reconnect_failed\");\n return;\n }\n\n const expDelay = Math.min(\n this.opts.reConnectInterval * this.opts.reConnectBackoffFactor ** (this._reconnectAttempts - 1),\n this.opts.maxReConnectDelay\n );\n\n const jitter = 1 + Math.random() * 0.5;\n const delay = Math.max(\n expDelay * jitter,\n this.opts.reConnectInterval\n );\n\n if (this.opts.logs)\n console.log(\n `[ws] Reconnecting in ${delay.toFixed(0)}ms (attempt ${this._reconnectAttempts})`\n );\n\n setTimeout(() => {\n this.connect();\n }, delay);\n }\n }\n\n on<K extends EventName<InputEvents & InternalEvents>>(event: K, listener: (InputEvents & InternalEvents)[K]) {\n this._handlers.on(event, listener as any);\n }\n\n once<K extends EventName<InputEvents & InternalEvents>>(event: K, listener: (InputEvents & InternalEvents)[K]) {\n this._handlers.once(event, listener as any);\n }\n\n emit<K extends EventName<OutputEvents>>(evt: K, ...args: EventArgs<OutputEvents, K>) {\n const ackI = args.map((data, i) => {\n if (typeof data === \"function\") return i;\n }).filter(i => i !== undefined);\n\n for (let i = 0; i < ackI.length; i++) {\n const ackIndex = ackI[i];\n const ackId = this._ackIdCounter++;\n this._ackCallbacks.set(ackId, args[ackIndex]);\n args[ackIndex] = ackId;\n }\n\n const payload = JSON.stringify({\n evt,\n data: args || undefined,\n ackI: ackI.length ? ackI : undefined\n });\n\n if (this.connected && this._ws?.readyState === WebSocket.OPEN)\n this._ws.send(payload);\n else\n this._messageQueue.push(payload);\n }\n\n send<K extends EventName<OutputEvents>>(evt: K, ...args: EventArgs<OutputEvents, K>) {\n return this.emit(evt, ...args);\n }\n\n disconnect() {\n this._manuallyDisconnected = true;\n this._ws.close();\n }\n\n close() {\n this._ws.close();\n }\n\n _handlersEmit(evtName: string, ...args: any[]) {\n // @ts-ignore\n this._handlers.emit(evtName, ...args);\n }\n}\n\nasync function checkStatus(client: GlovesLinkClient, id: string) {\n const statusURL = new URL(client.opts.statusPath, client.url.origin);\n statusURL.searchParams.set(\"id\", id);\n statusURL.searchParams.set(\"path\", client.url.pathname);\n\n const statusUrl = statusURL.toString().replace(\"ws\", \"http\");\n\n const res = await fetch(statusUrl);\n if (!res.ok) {\n console.error(\"[ws] Status error\", res.status);\n return true;\n }\n\n const data = await res.json();\n if (data.err) {\n if (client.opts.logs) console.log(\"[ws] Status error\", data.msg);\n return true;\n }\n\n const status = data.status as { status: number, msg?: string };\n if (client.opts.logs) console.log(\"[ws] Status\", status);\n\n if (status.status === 401) {\n client._handlersEmit(\"connect_unauthorized\", status.msg);\n return false;\n }\n else if (status.status === 403) {\n client._handlersEmit(\"connect_forbidden\", status.msg);\n return false;\n }\n else if (status.status === 500) {\n client._handlersEmit(\"connect_serverError\", status.msg);\n return false;\n }\n\n return true;\n}\n\nexport {\n GlovesLinkClient as default,\n GlovesLinkClient as GLC,\n GlovesLinkClient as client,\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["VEE", "event", "listener", "_event", "onceListener", "args", "l", "listeners", "dist_default", "GlovesLinkClient", "dist_default", "url", "opts", "id", "msg", "err", "_data", "raw", "ackId", "ackCallback", "evt", "data", "ackI", "i", "ackIndex", "res", "event", "checkStatus", "e", "expDelay", "jitter", "delay", "listener", "args", "payload", "evtName", "client", "statusURL", "statusUrl", "status"]
7
7
  }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  import VEE, { EventArgs, EventMap, EventName } from "@wxn0brp/event-emitter";
2
2
  export interface GLC_Opts {
3
- reConnect: boolean;
4
- reConnectInterval: number;
5
3
  logs: boolean;
6
4
  token: string;
7
5
  autoConnect: boolean;
8
6
  connectionData?: Record<string, any>;
7
+ statusPath: string;
8
+ reConnect: boolean;
9
+ reConnectInterval: number;
10
+ reConnectBackoffFactor: number;
11
+ maxReConnectAttempts: number;
12
+ /** Note: without jitter */
13
+ maxReConnectDelay: number;
9
14
  }
10
15
  export interface GLC_DataEvent {
11
16
  evt: string;
@@ -23,17 +28,19 @@ export type InternalEvents = {
23
28
  connect_unauthorized: (msg: string) => void;
24
29
  connect_forbidden: (msg: string) => void;
25
30
  connect_serverError: (msg: string) => void;
31
+ reconnect_failed: () => void;
26
32
  };
27
33
  export declare class GlovesLinkClient<InputEvents extends EventMap = {}, OutputEvents extends EventMap = {}> {
28
- ws: WebSocket;
29
- ackIdCounter: number;
30
- ackCallbacks: Map<number, Function>;
31
- handlers: VEE<InputEvents>;
34
+ _ws: WebSocket;
35
+ _ackIdCounter: number;
36
+ _ackCallbacks: Map<number, Function>;
37
+ _handlers: VEE<InputEvents>;
38
+ _manuallyDisconnected: boolean;
39
+ _messageQueue: string[];
40
+ _reconnectAttempts: number;
32
41
  opts: GLC_Opts;
33
42
  url: URL;
34
43
  connected: boolean;
35
- private _manuallyDisconnected;
36
- private messageQueue;
37
44
  constructor(url: string, opts?: Partial<GLC_Opts>);
38
45
  connect(): void;
39
46
  on<K extends EventName<InputEvents & InternalEvents>>(event: K, listener: (InputEvents & InternalEvents)[K]): void;
package/dist/index.js CHANGED
@@ -1,23 +1,28 @@
1
1
  import VEE from "@wxn0brp/event-emitter";
2
2
  export class GlovesLinkClient {
3
- ws;
4
- ackIdCounter;
5
- ackCallbacks;
6
- handlers = new VEE();
3
+ _ws;
4
+ _ackIdCounter;
5
+ _ackCallbacks;
6
+ _handlers = new VEE();
7
+ _manuallyDisconnected = false;
8
+ _messageQueue = [];
9
+ _reconnectAttempts = 0;
7
10
  opts;
8
11
  url;
9
12
  connected = false;
10
- _manuallyDisconnected = false;
11
- messageQueue = [];
12
13
  constructor(url, opts = {}) {
13
- this.ackIdCounter = 1;
14
- this.ackCallbacks = new Map();
14
+ this._ackIdCounter = 1;
15
+ this._ackCallbacks = new Map();
15
16
  this.opts = {
16
17
  logs: false,
17
- reConnect: true,
18
- reConnectInterval: 1000,
19
18
  token: null,
20
19
  autoConnect: true,
20
+ statusPath: "/gloves-link/status",
21
+ reConnect: true,
22
+ reConnectInterval: 1000,
23
+ maxReConnectAttempts: 5,
24
+ reConnectBackoffFactor: 2,
25
+ maxReConnectDelay: 15_000,
21
26
  ...opts
22
27
  };
23
28
  this.url = new URL(url, typeof window !== "undefined" ? window.location.href.replace("http", "ws") : "ws://localhost");
@@ -32,23 +37,24 @@ export class GlovesLinkClient {
32
37
  this.url.searchParams.set("id", id);
33
38
  if (this.opts.connectionData)
34
39
  this.url.searchParams.set("data", JSON.stringify(this.opts.connectionData));
35
- this.ws = new WebSocket(this.url.href);
36
- this.ws.onopen = () => {
40
+ this._ws = new WebSocket(this.url.href);
41
+ this._ws.onopen = () => {
37
42
  this.connected = true;
43
+ this._reconnectAttempts = 0;
38
44
  if (this.opts.logs)
39
45
  console.log("[ws] Connected");
40
46
  let msg;
41
- while (msg = this.messageQueue.shift()) {
42
- this.ws.send(msg);
47
+ while (msg = this._messageQueue.shift()) {
48
+ this._ws.send(msg);
43
49
  }
44
50
  this._handlersEmit("connect");
45
51
  };
46
- this.ws.onerror = (...err) => {
52
+ this._ws.onerror = (...err) => {
47
53
  if (this.opts.logs)
48
54
  console.warn("[ws] Error:", err);
49
55
  this._handlersEmit("error", ...err);
50
56
  };
51
- this.ws.onmessage = (_data) => {
57
+ this._ws.onmessage = (_data) => {
52
58
  const raw = _data?.data?.toString() || _data?.toString() || "";
53
59
  let msg;
54
60
  try {
@@ -61,9 +67,9 @@ export class GlovesLinkClient {
61
67
  }
62
68
  if ("ack" in msg) {
63
69
  const ackId = msg.ack;
64
- const ackCallback = this.ackCallbacks.get(ackId);
70
+ const ackCallback = this._ackCallbacks.get(ackId);
65
71
  if (ackCallback) {
66
- this.ackCallbacks.delete(ackId);
72
+ this._ackCallbacks.delete(ackId);
67
73
  ackCallback(...msg.data);
68
74
  }
69
75
  return;
@@ -78,7 +84,7 @@ export class GlovesLinkClient {
78
84
  break;
79
85
  const ackId = data[ackIndex];
80
86
  data[ackIndex] = (...res) => {
81
- this.ws.send(JSON.stringify({
87
+ this._ws.send(JSON.stringify({
82
88
  ack: ackId,
83
89
  data: res
84
90
  }));
@@ -87,15 +93,13 @@ export class GlovesLinkClient {
87
93
  }
88
94
  this._handlersEmit(evt, ...data);
89
95
  };
90
- this.ws.onclose = async (event) => {
96
+ this._ws.onclose = async (event) => {
91
97
  this.connected = false;
92
98
  if (this.opts.logs)
93
99
  console.log("[ws] Disconnected", event);
94
100
  this._handlersEmit("disconnect", event);
95
- if (this._manuallyDisconnected) {
96
- this._manuallyDisconnected = false;
101
+ if (this._manuallyDisconnected || !this.opts.reConnect)
97
102
  return;
98
- }
99
103
  if (event.code === 1006) {
100
104
  if (this.opts.logs)
101
105
  console.log("[ws] Connection closed by server");
@@ -109,18 +113,28 @@ export class GlovesLinkClient {
109
113
  console.error("[ws] Status error", e);
110
114
  }
111
115
  }
112
- if (!this.opts.reConnect)
116
+ this._reconnectAttempts++;
117
+ if (this._reconnectAttempts > this.opts.maxReConnectAttempts) {
118
+ if (this.opts.logs)
119
+ console.error(`[ws] Max reconnect attempts reached (${this.opts.maxReConnectAttempts})`);
120
+ this._handlersEmit("reconnect_failed");
113
121
  return;
122
+ }
123
+ const expDelay = Math.min(this.opts.reConnectInterval * this.opts.reConnectBackoffFactor ** (this._reconnectAttempts - 1), this.opts.maxReConnectDelay);
124
+ const jitter = 1 + Math.random() * 0.5;
125
+ const delay = Math.max(expDelay * jitter, this.opts.reConnectInterval);
126
+ if (this.opts.logs)
127
+ console.log(`[ws] Reconnecting in ${delay.toFixed(0)}ms (attempt ${this._reconnectAttempts})`);
114
128
  setTimeout(() => {
115
129
  this.connect();
116
- }, this.opts.reConnectInterval);
130
+ }, delay);
117
131
  };
118
132
  }
119
133
  on(event, listener) {
120
- this.handlers.on(event, listener);
134
+ this._handlers.on(event, listener);
121
135
  }
122
136
  once(event, listener) {
123
- this.handlers.once(event, listener);
137
+ this._handlers.once(event, listener);
124
138
  }
125
139
  emit(evt, ...args) {
126
140
  const ackI = args.map((data, i) => {
@@ -129,8 +143,8 @@ export class GlovesLinkClient {
129
143
  }).filter(i => i !== undefined);
130
144
  for (let i = 0; i < ackI.length; i++) {
131
145
  const ackIndex = ackI[i];
132
- const ackId = this.ackIdCounter++;
133
- this.ackCallbacks.set(ackId, args[ackIndex]);
146
+ const ackId = this._ackIdCounter++;
147
+ this._ackCallbacks.set(ackId, args[ackIndex]);
134
148
  args[ackIndex] = ackId;
135
149
  }
136
150
  const payload = JSON.stringify({
@@ -138,34 +152,32 @@ export class GlovesLinkClient {
138
152
  data: args || undefined,
139
153
  ackI: ackI.length ? ackI : undefined
140
154
  });
141
- if (this.connected && this.ws?.readyState === WebSocket.OPEN)
142
- this.ws.send(payload);
155
+ if (this.connected && this._ws?.readyState === WebSocket.OPEN)
156
+ this._ws.send(payload);
143
157
  else
144
- this.messageQueue.push(payload);
158
+ this._messageQueue.push(payload);
145
159
  }
146
160
  send(evt, ...args) {
147
161
  return this.emit(evt, ...args);
148
162
  }
149
163
  disconnect() {
150
164
  this._manuallyDisconnected = true;
151
- this.ws.close();
165
+ this._ws.close();
152
166
  }
153
167
  close() {
154
- this.ws.close();
168
+ this._ws.close();
155
169
  }
156
170
  _handlersEmit(evtName, ...args) {
157
171
  // @ts-ignore
158
- this.handlers.emit(evtName, ...args);
159
- // @ts-ignore
160
- this.handlers.emit("*", evtName, ...args);
172
+ this._handlers.emit(evtName, ...args);
161
173
  }
162
174
  }
163
175
  async function checkStatus(client, id) {
164
- const params = new URLSearchParams();
165
- params.set("id", id);
166
- params.set("path", client.url.pathname);
167
- const statusUrl = client.url.origin + "/gloves-link/status?" + params.toString();
168
- const res = await fetch(statusUrl.replace("ws", "http"));
176
+ const statusURL = new URL(client.opts.statusPath, client.url.origin);
177
+ statusURL.searchParams.set("id", id);
178
+ statusURL.searchParams.set("path", client.url.pathname);
179
+ const statusUrl = statusURL.toString().replace("ws", "http");
180
+ const res = await fetch(statusUrl);
169
181
  if (!res.ok) {
170
182
  console.error("[ws] Status error", res.status);
171
183
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/gloves-link-client",
3
- "version": "0.0.14",
3
+ "version": "0.1.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "wxn0brP",
@@ -18,7 +18,7 @@
18
18
  "typescript": "*"
19
19
  },
20
20
  "dependencies": {
21
- "@wxn0brp/event-emitter": "^0.0.4"
21
+ "@wxn0brp/event-emitter": "0.0.5"
22
22
  },
23
23
  "files": [
24
24
  "dist"