@laplace.live/event-bridge-sdk 1.0.0 → 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 -5
  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,console.log("Connected to LAPLACE Event Bridge"),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,7 +111,6 @@ export class LaplaceEventBridgeClient {
99
111
  this.ws.onopen = () => {
100
112
  this.setConnectionState(ConnectionState.CONNECTED)
101
113
  this.reconnectAttempts = 0
102
- console.log('Connected to LAPLACE Event Bridge')
103
114
  resolve()
104
115
  }
105
116
 
@@ -109,6 +120,9 @@ export class LaplaceEventBridgeClient {
109
120
 
110
121
  // Handle ping from server
111
122
  if (data.type === 'ping') {
123
+ // Track last ping time
124
+ this.lastPingTime = Date.now()
125
+
112
126
  // Respond with pong
113
127
  this.ws?.send(
114
128
  JSON.stringify({
@@ -121,9 +135,27 @@ export class LaplaceEventBridgeClient {
121
135
  }
122
136
 
123
137
  // Store client ID from the established message
124
- if (data.type === 'established' && data.clientId) {
138
+ if (data.type === 'established') {
125
139
  this.clientId = data.clientId
126
- 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
+ }
127
159
  }
128
160
 
129
161
  // Process the event
@@ -149,7 +181,7 @@ export class LaplaceEventBridgeClient {
149
181
  this.connect().catch(err => {
150
182
  console.error('Reconnection failed:', err)
151
183
  })
152
- }, this.options.reconnectInterval) as unknown as number
184
+ }, this.options.reconnectInterval)
153
185
  } else {
154
186
  this.setConnectionState(ConnectionState.DISCONNECTED)
155
187
  }
@@ -170,6 +202,8 @@ export class LaplaceEventBridgeClient {
170
202
  this.reconnectTimer = null
171
203
  }
172
204
 
205
+ this.stopPingMonitoring()
206
+
173
207
  if (this.ws) {
174
208
  this.ws.close()
175
209
  this.ws = null
@@ -177,6 +211,8 @@ export class LaplaceEventBridgeClient {
177
211
 
178
212
  this.setConnectionState(ConnectionState.DISCONNECTED)
179
213
  this.clientId = null
214
+ this.serverVersion = null
215
+ this.lastPingTime = null
180
216
  }
181
217
 
182
218
  /**
@@ -322,4 +358,79 @@ export class LaplaceEventBridgeClient {
322
358
  }
323
359
  }
324
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
+ }
325
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.0",
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
  }