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

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.
Files changed (3) hide show
  1. package/dist/index.js +1 -1
  2. package/index.ts +116 -15
  3. package/package.json +1 -4
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var B;((w)=>{w.DISCONNECTED="disconnected";w.CONNECTING="connecting";w.CONNECTED="connected";w.RECONNECTING="reconnecting"})(B||={});class D{ws=null;eventHandlers=new Map;anyEventHandlers=[];connectionStateHandlers=[];reconnectTimer=null;reconnectAttempts=0;clientId=null;connectionState="disconnected";options={url:"ws://localhost:9696",token:"",reconnect:!0,reconnectInterval:3000,maxReconnectAttempts:10};constructor(k={}){this.options={...this.options,...k}}connect(){return new Promise((k,m)=>{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;let w=(()=>{let z=new URL(q);if(z.searchParams.has("token"))z.searchParams.set("token","***");return z.toString()})();console.log(`Connected to LAPLACE Event Bridge: ${w}`),k()},this.ws.onmessage=(w)=>{try{let z=JSON.parse(w.data);if(z.type==="ping"){this.ws?.send(JSON.stringify({type:"pong",timestamp:Date.now(),respondingTo:z.timestamp}));return}if(z.type==="established"&&z.clientId)this.clientId=z.clientId,console.log(`Connection established with client ID: ${this.clientId}`);this.processEvent(z)}catch(z){console.error("Failed to parse event data:",z)}},this.ws.onerror=(w)=>{console.error("WebSocket error:",w),m(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"),m(q)}})}disconnect(){if(this.reconnectTimer)clearTimeout(this.reconnectTimer),this.reconnectTimer=null;if(this.ws)this.ws.close(),this.ws=null;this.setConnectionState("disconnected"),this.clientId=null}on(k,m){if(!this.eventHandlers.has(k))this.eventHandlers.set(k,[]);this.eventHandlers.get(k).push(m)}onAny(k){this.anyEventHandlers.push(k)}onConnectionStateChange(k){this.connectionStateHandlers.push(k),k(this.connectionState)}off(k,m){if(!this.eventHandlers.has(k))return;let q=this.eventHandlers.get(k),A=q.indexOf(m);if(A!==-1)q.splice(A,1);if(q.length===0)this.eventHandlers.delete(k)}offAny(k){let m=this.anyEventHandlers.indexOf(k);if(m!==-1)this.anyEventHandlers.splice(m,1)}offConnectionStateChange(k){let m=this.connectionStateHandlers.indexOf(k);if(m!==-1)this.connectionStateHandlers.splice(m,1)}isConnectedToBridge(){return this.connectionState==="connected"}getConnectionState(){return this.connectionState}getClientId(){return this.clientId}send(k){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected to LAPLACE Event Bridge");this.ws.send(JSON.stringify(k))}setConnectionState(k){if(this.connectionState!==k){this.connectionState=k;for(let m of this.connectionStateHandlers)try{m(k)}catch(q){console.error("Error in connection state change handler:",q)}}}processEvent(k){if(this.eventHandlers.has(k.type))for(let m of this.eventHandlers.get(k.type))try{m(k)}catch(q){console.error(`Error in event handler for type ${k.type}:`,q)}for(let m of this.anyEventHandlers)try{m(k)}catch(q){console.error("Error in any event handler:",q)}}}export{D as LaplaceEventBridgeClient,B as ConnectionState};
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};
package/index.ts CHANGED
@@ -45,6 +45,14 @@ export interface ConnectionOptions {
45
45
  * @default 10
46
46
  */
47
47
  maxReconnectAttempts?: number
48
+ /**
49
+ * The timeout for ping heartbeat in milliseconds
50
+ * If no ping is received within this time, the connection is considered dead
51
+ * Only applies to server versions >= 4.0.2
52
+ *
53
+ * @default 90000 (90 seconds)
54
+ */
55
+ pingTimeout?: number
48
56
  }
49
57
 
50
58
  export class LaplaceEventBridgeClient {
@@ -52,10 +60,13 @@ export class LaplaceEventBridgeClient {
52
60
  private eventHandlers = new Map<string, EventHandler<any>[]>()
53
61
  private anyEventHandlers: AnyEventHandler[] = []
54
62
  private connectionStateHandlers: ConnectionStateChangeHandler[] = []
55
- private reconnectTimer: number | null = null
63
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null
56
64
  private reconnectAttempts = 0
57
65
  private clientId: string | null = null
66
+ private serverVersion: string | null = null
58
67
  private connectionState: ConnectionState = ConnectionState.DISCONNECTED
68
+ private lastPingTime: number | null = null
69
+ private pingMonitorTimer: ReturnType<typeof setInterval> | null = null
59
70
 
60
71
  private options: Required<ConnectionOptions> = {
61
72
  url: 'ws://localhost:9696',
@@ -63,6 +74,7 @@ export class LaplaceEventBridgeClient {
63
74
  reconnect: true,
64
75
  reconnectInterval: 3000,
65
76
  maxReconnectAttempts: 10,
77
+ pingTimeout: 90000, // 90 seconds
66
78
  }
67
79
 
68
80
  constructor(options: ConnectionOptions = {}) {
@@ -99,17 +111,6 @@ export class LaplaceEventBridgeClient {
99
111
  this.ws.onopen = () => {
100
112
  this.setConnectionState(ConnectionState.CONNECTED)
101
113
  this.reconnectAttempts = 0
102
-
103
- // Create a display URL that masks the token if present
104
- const displayUrl = (() => {
105
- const urlObj = new URL(url)
106
- if (urlObj.searchParams.has('token')) {
107
- urlObj.searchParams.set('token', '***')
108
- }
109
- return urlObj.toString()
110
- })()
111
-
112
- console.log(`Connected to LAPLACE Event Bridge: ${displayUrl}`)
113
114
  resolve()
114
115
  }
115
116
 
@@ -119,6 +120,9 @@ export class LaplaceEventBridgeClient {
119
120
 
120
121
  // Handle ping from server
121
122
  if (data.type === 'ping') {
123
+ // Track last ping time
124
+ this.lastPingTime = Date.now()
125
+
122
126
  // Respond with pong
123
127
  this.ws?.send(
124
128
  JSON.stringify({
@@ -131,9 +135,27 @@ export class LaplaceEventBridgeClient {
131
135
  }
132
136
 
133
137
  // Store client ID from the established message
134
- if (data.type === 'established' && data.clientId) {
138
+ if (data.type === 'established') {
135
139
  this.clientId = data.clientId
136
- console.log(`Connection established with client ID: ${this.clientId}`)
140
+ this.serverVersion = data.version
141
+
142
+ // Create a display URL that masks the token if present
143
+ const displayUrl = (() => {
144
+ const urlObj = new URL(url)
145
+ if (urlObj.searchParams.has('token')) {
146
+ urlObj.searchParams.set('token', '***')
147
+ }
148
+ return urlObj.toString()
149
+ })()
150
+
151
+ console.log(
152
+ `Welcome to LAPLACE Event Bridge ${`v${data.version}` || '(unknown version)'}: ${displayUrl} with client ID ${this.clientId || 'unknown'}`
153
+ )
154
+
155
+ // Start ping monitoring if server version supports it (>= 4.0.2)
156
+ if (this.shouldMonitorPing()) {
157
+ this.startPingMonitoring()
158
+ }
137
159
  }
138
160
 
139
161
  // Process the event
@@ -159,7 +181,7 @@ export class LaplaceEventBridgeClient {
159
181
  this.connect().catch(err => {
160
182
  console.error('Reconnection failed:', err)
161
183
  })
162
- }, this.options.reconnectInterval) as unknown as number
184
+ }, this.options.reconnectInterval)
163
185
  } else {
164
186
  this.setConnectionState(ConnectionState.DISCONNECTED)
165
187
  }
@@ -180,6 +202,8 @@ export class LaplaceEventBridgeClient {
180
202
  this.reconnectTimer = null
181
203
  }
182
204
 
205
+ this.stopPingMonitoring()
206
+
183
207
  if (this.ws) {
184
208
  this.ws.close()
185
209
  this.ws = null
@@ -187,6 +211,8 @@ export class LaplaceEventBridgeClient {
187
211
 
188
212
  this.setConnectionState(ConnectionState.DISCONNECTED)
189
213
  this.clientId = null
214
+ this.serverVersion = null
215
+ this.lastPingTime = null
190
216
  }
191
217
 
192
218
  /**
@@ -332,4 +358,79 @@ export class LaplaceEventBridgeClient {
332
358
  }
333
359
  }
334
360
  }
361
+
362
+ /**
363
+ * Check if ping monitoring should be enabled based on server version
364
+ */
365
+ private shouldMonitorPing(): boolean {
366
+ if (!this.serverVersion) {
367
+ return false
368
+ }
369
+
370
+ // Parse version (e.g., "4.0.2" -> [4, 0, 2])
371
+ const versionParts = this.serverVersion.split('.').map(part => parseInt(part, 10))
372
+
373
+ // Ensure we have at least 3 version parts
374
+ if (versionParts.length < 3 || versionParts.some(isNaN)) {
375
+ console.warn(`Invalid server version format: ${this.serverVersion}`)
376
+ return false
377
+ }
378
+
379
+ const major = versionParts[0]!
380
+ const minor = versionParts[1]!
381
+ const patch = versionParts[2]!
382
+
383
+ // Check if version is >= 4.0.3
384
+ if (major > 4) return true
385
+ if (major === 4) {
386
+ if (minor > 0) return true
387
+ if (minor === 0 && patch >= 3) return true
388
+ }
389
+
390
+ return false
391
+ }
392
+
393
+ /**
394
+ * Start monitoring ping messages from the server
395
+ */
396
+ private startPingMonitoring(): void {
397
+ // Stop any existing monitoring
398
+ this.stopPingMonitoring()
399
+
400
+ console.log(`Ping monitoring enabled (timeout: ${this.options.pingTimeout}ms)`)
401
+
402
+ // Set initial ping time
403
+ this.lastPingTime = Date.now()
404
+
405
+ // Start monitoring
406
+ this.pingMonitorTimer = setInterval(() => {
407
+ if (!this.lastPingTime) {
408
+ return
409
+ }
410
+
411
+ const timeSinceLastPing = Date.now() - this.lastPingTime
412
+
413
+ if (timeSinceLastPing > this.options.pingTimeout) {
414
+ console.warn(`Ping timeout detected (${timeSinceLastPing}ms since last ping). Reconnecting...`)
415
+
416
+ // Stop monitoring
417
+ this.stopPingMonitoring()
418
+
419
+ // Force reconnection
420
+ if (this.ws) {
421
+ this.ws.close()
422
+ }
423
+ }
424
+ }, this.options.pingTimeout / 3) // Check 3 times within the timeout period
425
+ }
426
+
427
+ /**
428
+ * Stop monitoring ping messages
429
+ */
430
+ private stopPingMonitoring(): void {
431
+ if (this.pingMonitorTimer) {
432
+ clearInterval(this.pingMonitorTimer)
433
+ this.pingMonitorTimer = null
434
+ }
435
+ }
335
436
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@laplace.live/event-bridge-sdk",
3
3
  "description": "LAPLACE Event Bridge SDK",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "module": "index.ts",
6
6
  "types": "index.ts",
7
7
  "license": "MIT",
@@ -24,9 +24,6 @@
24
24
  "dependencies": {
25
25
  "@laplace.live/event-types": "^2.0.12"
26
26
  },
27
- "peerDependencies": {
28
- "@laplace.live/event-types": "^2.0.4"
29
- },
30
27
  "devDependencies": {
31
28
  "bun-types": "latest"
32
29
  }