@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.
- package/dist/index.js +1 -1
- package/index.ts +116 -5
- package/package.json +1 -4
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
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:
|
|
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'
|
|
138
|
+
if (data.type === 'established') {
|
|
125
139
|
this.clientId = data.clientId
|
|
126
|
-
|
|
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)
|
|
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.
|
|
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
|
}
|