@laplace.live/event-bridge-sdk 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -78,7 +78,7 @@ interface ConnectionOptions {
78
78
  token?: string // Authentication token, default: ''
79
79
  reconnect?: boolean // Auto reconnect on disconnect, default: true
80
80
  reconnectInterval?: number // Milliseconds between reconnect attempts, default: 3000
81
- maxReconnectAttempts?: number // Maximum reconnect attempts, default: 10
81
+ maxReconnectAttempts?: number // Maximum reconnect attempts, default: 1000
82
82
  }
83
83
  ```
84
84
 
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var E;((w)=>{w.DISCONNECTED="disconnected";w.CONNECTING="connecting";w.CONNECTED="connected";w.RECONNECTING="reconnecting"})(E||={});class F{ws=null;eventHandlers=new Map;anyEventHandlers=[];connectionStateHandlers=[];reconnectTimer=null;reconnectAttempts=0;clientId=null;serverVersion=null;connectionState="disconnected";lastPingTime=null;pingMonitorTimer=null;options={url:"ws://localhost:9696",token:"",reconnect:!0,reconnectInterval:3000,maxReconnectAttempts:10,pingTimeout:90000};constructor(f={}){this.options={...this.options,...f}}connect(){return new Promise((f,k)=>{try{if(this.ws)this.ws.close();this.setConnectionState("connecting");let q=this.options.url,A=[];if(this.options.token){A.push("laplace-event-bridge-role-client",this.options.token);let w=new URL(q);w.searchParams.set("token",this.options.token),q=w.toString()}this.ws=new WebSocket(q,A),this.ws.onopen=()=>{this.setConnectionState("connected"),this.reconnectAttempts=0,f()},this.ws.onmessage=(w)=>{try{let z=JSON.parse(w.data);if(z.type==="ping"){this.lastPingTime=Date.now(),this.ws?.send(JSON.stringify({type:"pong",timestamp:Date.now(),respondingTo:z.timestamp}));return}if(z.type==="established"){this.clientId=z.clientId,this.serverVersion=z.version;let D=(()=>{let B=new URL(q);if(B.searchParams.has("token"))B.searchParams.set("token","***");return B.toString()})();if(console.log(`Welcome to LAPLACE Event Bridge ${`v${z.version}`||"(unknown version)"}: ${D} with client ID ${this.clientId||"unknown"}`),this.shouldMonitorPing())this.startPingMonitoring()}this.processEvent(z)}catch(z){console.error("Failed to parse event data:",z)}},this.ws.onerror=(w)=>{console.error("WebSocket error:",w),k(w)},this.ws.onclose=()=>{if(console.log("Disconnected from LAPLACE Event Bridge"),this.options.reconnect&&this.reconnectAttempts<this.options.maxReconnectAttempts)this.reconnectAttempts++,this.setConnectionState("reconnecting"),console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})...`),this.reconnectTimer=setTimeout(()=>{this.connect().catch((w)=>{console.error("Reconnection failed:",w)})},this.options.reconnectInterval);else this.setConnectionState("disconnected")}}catch(q){this.setConnectionState("disconnected"),k(q)}})}disconnect(){if(this.reconnectTimer)clearTimeout(this.reconnectTimer),this.reconnectTimer=null;if(this.stopPingMonitoring(),this.ws)this.ws.close(),this.ws=null;this.setConnectionState("disconnected"),this.clientId=null,this.serverVersion=null,this.lastPingTime=null}on(f,k){if(!this.eventHandlers.has(f))this.eventHandlers.set(f,[]);this.eventHandlers.get(f).push(k)}onAny(f){this.anyEventHandlers.push(f)}onConnectionStateChange(f){this.connectionStateHandlers.push(f),f(this.connectionState)}off(f,k){if(!this.eventHandlers.has(f))return;let q=this.eventHandlers.get(f),A=q.indexOf(k);if(A!==-1)q.splice(A,1);if(q.length===0)this.eventHandlers.delete(f)}offAny(f){let k=this.anyEventHandlers.indexOf(f);if(k!==-1)this.anyEventHandlers.splice(k,1)}offConnectionStateChange(f){let k=this.connectionStateHandlers.indexOf(f);if(k!==-1)this.connectionStateHandlers.splice(k,1)}isConnectedToBridge(){return this.connectionState==="connected"}getConnectionState(){return this.connectionState}getClientId(){return this.clientId}send(f){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected to LAPLACE Event Bridge");this.ws.send(JSON.stringify(f))}setConnectionState(f){if(this.connectionState!==f){this.connectionState=f;for(let k of this.connectionStateHandlers)try{k(f)}catch(q){console.error("Error in connection state change handler:",q)}}}processEvent(f){if(this.eventHandlers.has(f.type))for(let k of this.eventHandlers.get(f.type))try{k(f)}catch(q){console.error(`Error in event handler for type ${f.type}:`,q)}for(let k of this.anyEventHandlers)try{k(f)}catch(q){console.error("Error in any event handler:",q)}}shouldMonitorPing(){if(!this.serverVersion)return!1;let f=this.serverVersion.split(".").map((w)=>parseInt(w,10));if(f.length<3||f.some(isNaN))return console.warn(`Invalid server version format: ${this.serverVersion}`),!1;let k=f[0],q=f[1],A=f[2];if(k>4)return!0;if(k===4){if(q>0)return!0;if(q===0&&A>=3)return!0}return!1}startPingMonitoring(){this.stopPingMonitoring(),console.log(`Ping monitoring enabled (timeout: ${this.options.pingTimeout}ms)`),this.lastPingTime=Date.now(),this.pingMonitorTimer=setInterval(()=>{if(!this.lastPingTime)return;let f=Date.now()-this.lastPingTime;if(f>this.options.pingTimeout){if(console.warn(`Ping timeout detected (${f}ms since last ping). Reconnecting...`),this.stopPingMonitoring(),this.ws)this.ws.close()}},this.options.pingTimeout/3)}stopPingMonitoring(){if(this.pingMonitorTimer)clearInterval(this.pingMonitorTimer),this.pingMonitorTimer=null}}export{F as LaplaceEventBridgeClient,E as ConnectionState};
1
+ var K;((A)=>{A.DISCONNECTED="disconnected";A.CONNECTING="connecting";A.CONNECTED="connected";A.RECONNECTING="reconnecting"})(K||={});class N{ws=null;eventHandlers=new Map;anyEventHandlers=[];connectionStateHandlers=[];reconnectTimer=null;reconnectAttempts=0;clientId=null;serverVersion=null;connectionState="disconnected";lastPingTime=null;pingMonitorTimer=null;options={url:"ws://localhost:9696",token:"",reconnect:!0,reconnectInterval:3000,maxReconnectAttempts:1000,pingTimeout:90000};constructor(q={}){this.options={...this.options,...q}}connect(){return new Promise((q,z)=>{try{if(this.ws)this.ws.close();this.setConnectionState("connecting");let w=this.options.url,B=[];if(this.options.token){B.push("laplace-event-bridge-role-client",this.options.token);let A=new URL(w);A.searchParams.set("token",this.options.token),w=A.toString()}this.ws=new WebSocket(w,B),this.ws.onopen=()=>{this.setConnectionState("connected"),this.reconnectAttempts=0,q()},this.ws.onmessage=(A)=>{try{let E=JSON.parse(A.data);if(E.type==="ping"){this.lastPingTime=Date.now(),this.ws?.send(JSON.stringify({type:"pong",timestamp:Date.now(),respondingTo:E.timestamp}));return}if(E.type==="established"){this.clientId=E.clientId,this.serverVersion=E.version;let G=(()=>{let F=new URL(w);if(F.searchParams.has("token"))F.searchParams.set("token","***");return F.toString()})();if(console.log(`Welcome to LAPLACE Event Bridge ${`v${E.version}`||"(unknown version)"}: ${G} with client ID ${this.clientId||"unknown"}`),this.shouldMonitorPing())this.startPingMonitoring()}this.processEvent(E)}catch(E){console.error("Failed to parse event data:",E)}},this.ws.onerror=(A)=>{console.error("WebSocket error:",A),z(A)},this.ws.onclose=()=>{if(console.log("Disconnected from LAPLACE Event Bridge"),this.stopPingMonitoring(),this.lastPingTime=null,this.options.reconnect&&this.reconnectAttempts<this.options.maxReconnectAttempts){this.reconnectAttempts++,this.setConnectionState("reconnecting");let A=this.options.reconnectInterval,E=1.5,G=60000,F=Math.min(A*E**(this.reconnectAttempts-1),G),H=Math.round(F);console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.options.maxReconnectAttempts}) in ${H}ms...`),this.reconnectTimer=setTimeout(()=>{this.connect().catch((J)=>{console.error("Reconnection failed:",J)})},H)}else this.setConnectionState("disconnected")}}catch(w){this.setConnectionState("disconnected"),z(w)}})}disconnect(){if(this.reconnectTimer)clearTimeout(this.reconnectTimer),this.reconnectTimer=null;if(this.stopPingMonitoring(),this.ws)this.ws.close(),this.ws=null;this.setConnectionState("disconnected"),this.clientId=null,this.serverVersion=null,this.lastPingTime=null}on(q,z){let w=this.eventHandlers.get(q)||[];w.push(z),this.eventHandlers.set(q,w)}onAny(q){this.anyEventHandlers.push(q)}onConnectionStateChange(q){this.connectionStateHandlers.push(q),q(this.connectionState)}off(q,z){let w=this.eventHandlers.get(q);if(!w)return;let B=w.indexOf(z);if(B!==-1)w.splice(B,1);if(w.length===0)this.eventHandlers.delete(q)}offAny(q){let z=this.anyEventHandlers.indexOf(q);if(z!==-1)this.anyEventHandlers.splice(z,1)}offConnectionStateChange(q){let z=this.connectionStateHandlers.indexOf(q);if(z!==-1)this.connectionStateHandlers.splice(z,1)}isConnectedToBridge(){return this.connectionState==="connected"}getConnectionState(){return this.connectionState}getClientId(){return this.clientId}send(q){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected to LAPLACE Event Bridge");this.ws.send(JSON.stringify(q))}setConnectionState(q){if(this.connectionState!==q){this.connectionState=q;for(let z of this.connectionStateHandlers)try{z(q)}catch(w){console.error("Error in connection state change handler:",w)}}}processEvent(q){let z=this.eventHandlers.get(q.type);if(z)for(let w of z)try{w(q)}catch(B){console.error(`Error in event handler for type ${q.type}:`,B)}for(let w of this.anyEventHandlers)try{w(q)}catch(B){console.error("Error in any event handler:",B)}}shouldMonitorPing(){if(!this.serverVersion)return!1;let q=this.serverVersion.split(".").map((A)=>parseInt(A,10));if(q.length<3||q.some(Number.isNaN))return console.warn(`Invalid server version format: ${this.serverVersion}`),!1;let[z=0,w=0,B=0]=q;if(z>4)return!0;if(z===4){if(w>0)return!0;if(w===0&&B>=3)return!0}return!1}startPingMonitoring(){this.stopPingMonitoring(),console.log(`Ping monitoring enabled (timeout: ${this.options.pingTimeout}ms)`),this.lastPingTime=Date.now(),this.pingMonitorTimer=setInterval(()=>{if(!this.lastPingTime)return;let q=Date.now()-this.lastPingTime;if(q>this.options.pingTimeout){if(console.warn(`Ping timeout detected (${q}ms since last ping). Reconnecting...`),this.stopPingMonitoring(),this.ws)this.ws.close()}},this.options.pingTimeout/3)}stopPingMonitoring(){if(this.pingMonitorTimer)clearInterval(this.pingMonitorTimer),this.pingMonitorTimer=null}}export{N as LaplaceEventBridgeClient,K as ConnectionState};
package/index.ts CHANGED
@@ -34,15 +34,24 @@ export interface ConnectionOptions {
34
34
  */
35
35
  reconnect?: boolean
36
36
  /**
37
- * The interval between reconnect attempts in milliseconds
37
+ * The base interval between reconnect attempts in milliseconds.
38
+ * With exponential backoff, each attempt multiplies this by 1.5^(attempt-1).
39
+ * The maximum interval is capped at 60 seconds.
38
40
  *
39
41
  * @default 3000
42
+ * @example
43
+ * // With base interval of 3000ms:
44
+ * // Attempt 1: 3000ms
45
+ * // Attempt 2: 4500ms
46
+ * // Attempt 3: 6750ms
47
+ * // ...
48
+ * // Capped at: 60000ms
40
49
  */
41
50
  reconnectInterval?: number
42
51
  /**
43
52
  * The maximum number of reconnect attempts
44
53
  *
45
- * @default 10
54
+ * @default 1000
46
55
  */
47
56
  maxReconnectAttempts?: number
48
57
  /**
@@ -73,7 +82,7 @@ export class LaplaceEventBridgeClient {
73
82
  token: '',
74
83
  reconnect: true,
75
84
  reconnectInterval: 3000,
76
- maxReconnectAttempts: 10,
85
+ maxReconnectAttempts: 1000,
77
86
  pingTimeout: 90000, // 90 seconds
78
87
  }
79
88
 
@@ -173,15 +182,36 @@ export class LaplaceEventBridgeClient {
173
182
  this.ws.onclose = () => {
174
183
  console.log('Disconnected from LAPLACE Event Bridge')
175
184
 
185
+ // Stop ping monitoring before attempting reconnection
186
+ this.stopPingMonitoring()
187
+
188
+ // Clear ping state
189
+ this.lastPingTime = null
190
+
176
191
  if (this.options.reconnect && this.reconnectAttempts < this.options.maxReconnectAttempts) {
177
192
  this.reconnectAttempts++
178
193
  this.setConnectionState(ConnectionState.RECONNECTING)
179
- console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})...`)
194
+
195
+ // Calculate exponential backoff with cap at 60 seconds
196
+ const baseInterval = this.options.reconnectInterval
197
+ const backoffMultiplier = 1.5 // Increase by 50% each time
198
+ const maxInterval = 60000 // 60 seconds cap
199
+
200
+ // Calculate delay: base * (multiplier ^ (attempt - 1))
201
+ const calculatedDelay = Math.min(
202
+ baseInterval * backoffMultiplier ** (this.reconnectAttempts - 1),
203
+ maxInterval
204
+ )
205
+ const delay = Math.round(calculatedDelay)
206
+
207
+ console.log(
208
+ `Attempting to reconnect (${this.reconnectAttempts}/${this.options.maxReconnectAttempts}) in ${delay}ms...`
209
+ )
180
210
  this.reconnectTimer = setTimeout(() => {
181
211
  this.connect().catch(err => {
182
212
  console.error('Reconnection failed:', err)
183
213
  })
184
- }, this.options.reconnectInterval)
214
+ }, delay)
185
215
  } else {
186
216
  this.setConnectionState(ConnectionState.DISCONNECTED)
187
217
  }
@@ -221,10 +251,9 @@ export class LaplaceEventBridgeClient {
221
251
  * @param handler The handler function to call when the event is received
222
252
  */
223
253
  public on<T extends LaplaceEventTypes>(eventType: T, handler: (event: EventTypeMap[T]) => void): void {
224
- if (!this.eventHandlers.has(eventType)) {
225
- this.eventHandlers.set(eventType, [])
226
- }
227
- this.eventHandlers.get(eventType)!.push(handler)
254
+ const handlers = this.eventHandlers.get(eventType) || []
255
+ handlers.push(handler)
256
+ this.eventHandlers.set(eventType, handlers)
228
257
  }
229
258
 
230
259
  /**
@@ -251,11 +280,11 @@ export class LaplaceEventBridgeClient {
251
280
  * @param handler The handler function to remove
252
281
  */
253
282
  public off<T extends LaplaceEventTypes>(eventType: T, handler: (event: EventTypeMap[T]) => void): void {
254
- if (!this.eventHandlers.has(eventType)) {
283
+ const handlers = this.eventHandlers.get(eventType)
284
+ if (!handlers) {
255
285
  return
256
286
  }
257
287
 
258
- const handlers = this.eventHandlers.get(eventType)!
259
288
  const index = handlers.indexOf(handler)
260
289
 
261
290
  if (index !== -1) {
@@ -339,8 +368,9 @@ export class LaplaceEventBridgeClient {
339
368
 
340
369
  private processEvent(event: LaplaceEvent): void {
341
370
  // Call specific event handlers
342
- if (this.eventHandlers.has(event.type)) {
343
- for (const handler of this.eventHandlers.get(event.type)!) {
371
+ const handlers = this.eventHandlers.get(event.type)
372
+ if (handlers) {
373
+ for (const handler of handlers) {
344
374
  try {
345
375
  handler(event)
346
376
  } catch (err) {
@@ -371,14 +401,12 @@ export class LaplaceEventBridgeClient {
371
401
  const versionParts = this.serverVersion.split('.').map(part => parseInt(part, 10))
372
402
 
373
403
  // Ensure we have at least 3 version parts
374
- if (versionParts.length < 3 || versionParts.some(isNaN)) {
404
+ if (versionParts.length < 3 || versionParts.some(Number.isNaN)) {
375
405
  console.warn(`Invalid server version format: ${this.serverVersion}`)
376
406
  return false
377
407
  }
378
408
 
379
- const major = versionParts[0]!
380
- const minor = versionParts[1]!
381
- const patch = versionParts[2]!
409
+ const [major = 0, minor = 0, patch = 0] = versionParts
382
410
 
383
411
  // Check if version is >= 4.0.3
384
412
  if (major > 4) return true
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@laplace.live/event-bridge-sdk",
3
3
  "description": "LAPLACE Event Bridge SDK",
4
- "version": "1.0.2",
4
+ "version": "1.0.4",
5
5
  "module": "index.ts",
6
6
  "types": "index.ts",
7
7
  "license": "MIT",
8
8
  "type": "module",
9
9
  "scripts": {
10
- "build": "bun build index.ts --outdir dist --target browser --minify"
10
+ "build": "bun build index.ts --outdir dist --target browser --minify",
11
+ "lint": "biome check",
12
+ "format": "biome format --write"
11
13
  },
12
14
  "exports": {
13
15
  ".": {