@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.
- package/dist/index.js +1 -1
- package/index.ts +116 -15
- 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,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'
|
|
138
|
+
if (data.type === 'established') {
|
|
135
139
|
this.clientId = data.clientId
|
|
136
|
-
|
|
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)
|
|
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.
|
|
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
|
}
|