jsgar 3.7.2 → 3.8.0

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 (2) hide show
  1. package/dist/gar.umd.js +76 -31
  2. package/package.json +1 -1
package/dist/gar.umd.js CHANGED
@@ -47,7 +47,11 @@
47
47
  this.reconnectDelay = 5000; // Milliseconds
48
48
  this.user = user;
49
49
  this.working_namespace = working_namespace;
50
- this.heartbeatTimeoutInterval = heartbeatTimeoutInterval;
50
+ this.timeoutScale = (typeof process !== 'undefined' && process.env && process.env.TRS_TIMEOUT_SCALE) ? parseFloat(process.env.TRS_TIMEOUT_SCALE) : 1.0;
51
+ this.scaledHeartbeatTimeoutInterval = heartbeatTimeoutInterval;
52
+ if (this.timeoutScale !== 1.0) {
53
+ this.scaledHeartbeatTimeoutInterval *= this.timeoutScale;
54
+ }
51
55
  this.version = 650707;
52
56
 
53
57
  if (typeof window !== 'undefined' && window.location) {
@@ -70,7 +74,6 @@
70
74
  this.heartbeatIntervalId = null;
71
75
  this.messageHandlers = new Map();
72
76
  this.lastHeartbeatTime = Date.now() / 1000;
73
- this.heartbeatTimeout = 3; // Seconds
74
77
 
75
78
  this.heartbeatTimeoutCallback = null;
76
79
  this.stoppedCallback = null;
@@ -145,42 +148,72 @@
145
148
 
146
149
  /**
147
150
  * Establish WebSocket connection with reconnection logic.
151
+ * @param {number} [openTimeout=20] - Connection timeout in seconds
148
152
  */
149
- /**
150
- * Establish WebSocket connection with reconnection logic.
151
- */
152
- async connect() {
153
+ async connect(openTimeout = 20) {
153
154
  // Before attempting a new connection, clear any previous state
154
155
  this.clearConnectionState();
155
156
 
157
+ let scaledOpenTimeout = openTimeout * this.timeoutScale;
158
+
156
159
  while (this.running && !this.connected) {
157
160
  try {
158
161
  const WS = await this._ensureWebSocketCtor();
159
162
  const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
160
- if (this.allowSelfSignedCertificate && isNode) {
161
- // Node.js 'ws' supports options for TLS; browsers do not.
162
- this.websocket = new WS(this.wsEndpoint, ['gar-protocol'], { rejectUnauthorized: false });
163
- } else {
164
- this.websocket = new WS(this.wsEndpoint, ['gar-protocol']);
165
- }
166
- this.websocket.onopen = () => {
167
- this.connected = true;
168
- this.log('INFO', `Connected to WebSocket server at ${this.wsEndpoint} using gar-protocol`);
169
- this._sendMessages();
170
- this._receiveMessages();
171
- };
163
+
164
+ this.log('INFO', `Connecting to WebSocket server at ${this.wsEndpoint}`);
165
+
166
+ const connectionPromise = new Promise((resolve, reject) => {
167
+ let websocket;
168
+ if (this.allowSelfSignedCertificate && isNode) {
169
+ // Node.js 'ws' supports options for TLS; browsers do not.
170
+ websocket = new WS(this.wsEndpoint, ['gar-protocol'], { rejectUnauthorized: false });
171
+ } else {
172
+ websocket = new WS(this.wsEndpoint, ['gar-protocol']);
173
+ }
174
+
175
+ const timeoutId = setTimeout(() => {
176
+ websocket.onopen = null;
177
+ websocket.onclose = null;
178
+ websocket.onerror = null;
179
+ if (websocket.terminate) websocket.terminate();
180
+ else if (websocket.close) websocket.close();
181
+ reject(new Error(`Connection timed out after ${scaledOpenTimeout}s`));
182
+ }, scaledOpenTimeout * 1000);
183
+
184
+ websocket.onopen = () => {
185
+ clearTimeout(timeoutId);
186
+ resolve(websocket);
187
+ };
188
+ websocket.onerror = (error) => {
189
+ clearTimeout(timeoutId);
190
+ reject(new Error(error.message || 'Unknown WebSocket error'));
191
+ };
192
+ websocket.onclose = () => {
193
+ clearTimeout(timeoutId);
194
+ reject(new Error('WebSocket closed during connection attempt'));
195
+ };
196
+ });
197
+
198
+ this.websocket = await connectionPromise;
199
+ this.connected = true;
200
+ this.log('INFO', `Connected to WebSocket server at ${this.wsEndpoint} using gar-protocol`);
201
+
172
202
  this.websocket.onclose = () => {
173
203
  this.log('WARNING', 'WebSocket connection closed.');
174
204
  this.connected = false;
175
205
  this.websocket = null;
206
+ this._reconnect();
176
207
  };
177
208
  this.websocket.onerror = (error) => {
178
209
  this.log('ERROR', `WebSocket error: ${error.message || 'Unknown error'}`);
179
210
  this.connected = false;
180
211
  this.websocket = null;
181
212
  };
182
- // Wait indefinitely until connection succeeds or fails
183
- await new Promise(resolve => setTimeout(resolve, 5000));
213
+
214
+ this._sendMessages();
215
+ this._receiveMessages();
216
+
184
217
  } catch (e) {
185
218
  this.log('ERROR', `WebSocket connection failed: ${e.message}. Reconnecting in ${this.reconnectDelay / 1000} seconds...`);
186
219
  this.connected = false;
@@ -329,11 +362,11 @@
329
362
 
330
363
  /**
331
364
  * Register handler for DeleteKey message.
332
- * @param {Function} handler - Callback with (keyId)
365
+ * @param {Function} handler - Callback with (keyId, keyName)
333
366
  * @param subscriptionGroup - The subscription group for callback (default 0)
334
367
  */
335
368
  registerDeleteKeyHandler(handler, subscriptionGroup = 0) {
336
- this.registerHandler('DeleteKey', (msg) => handler(msg.value.key_id), subscriptionGroup);
369
+ this.registerHandler('DeleteKey', (msg) => handler(msg.value.key_id, (msg.value && ('name' in msg.value)) ? msg.value.name : null), subscriptionGroup);
337
370
  }
338
371
 
339
372
  /**
@@ -426,8 +459,8 @@
426
459
  this.log('DEBUG', `New server topic: ${name} (Server ID: ${topicId})`));
427
460
  this.registerKeyIntroductionHandler((keyId, name, classList, deletedClass) =>
428
461
  this.log('DEBUG', `Key: ${name} : ${keyId} (Classes: ${JSON.stringify(classList)}/-${deletedClass})`));
429
- this.registerDeleteKeyHandler((keyId) =>
430
- this.log('DEBUG', `Delete key: ${this.serverKeyIdToName.get(keyId) || 'unknown'} (Server ID: ${keyId})`));
462
+ this.registerDeleteKeyHandler((keyId, keyName) =>
463
+ this.log('DEBUG', `Delete key: ${keyName || 'unknown'} (Server ID: ${keyId})`));
431
464
  this.registerSubscriptionStatusHandler(this._defaultSubscriptionStatusHandler.bind(this));
432
465
  this.registerDeleteRecordHandler((keyId, topicId) =>
433
466
  this.log('DEBUG', `Delete record: ${this.serverKeyIdToName.get(keyId) || 'unknown'} - ${this.serverTopicIdToName.get(topicId) || 'unknown'}`));
@@ -457,13 +490,13 @@
457
490
  message_type: 'Introduction',
458
491
  value: {
459
492
  version: this.version,
460
- heartbeat_timeout_interval: this.heartbeatTimeoutInterval,
493
+ heartbeat_timeout_interval: Math.floor(this.scaledHeartbeatTimeoutInterval),
461
494
  user: this.user,
462
495
  working_namespace: this.working_namespace
463
496
  }
464
497
  };
465
498
  this.sendMessage(introMsg);
466
- this.heartbeatIntervalId = setInterval(() => this._heartbeatLoop(), this.heartbeatTimeoutInterval / 2);
499
+ this.heartbeatIntervalId = setInterval(() => this._heartbeatLoop(), this.scaledHeartbeatTimeoutInterval / 2);
467
500
  this.connect();
468
501
  }
469
502
 
@@ -590,7 +623,7 @@
590
623
  */
591
624
  checkHeartbeat() {
592
625
  const now = Date.now() / 1000;
593
- const cutoff = this._initialGracePeriod ? this._initialGraceDeadline : this.lastHeartbeatTime + this.heartbeatTimeout;
626
+ const cutoff = this._initialGracePeriod ? this._initialGraceDeadline : this.lastHeartbeatTime + (this.scaledHeartbeatTimeoutInterval / 1000.0);
594
627
  if (now > cutoff) {
595
628
  this.log('WARNING', `Heartbeat failed; previous heartbeat: ${this.lastHeartbeatTime.toFixed(3)}s`);
596
629
  this.exitCode = 1;
@@ -660,8 +693,16 @@
660
693
  this.serverKeyNameToId.set(message.value.name, message.value.key_id);
661
694
  } else if (msgType === 'DeleteKey') {
662
695
  subscriptionGroup = this.activeSubscriptionGroup;
663
- this.serverKeyNameToId.delete(this.serverKeyIdToName.get(message.value.key_id) || "");
664
- this.serverKeyIdToName.delete(message.value.key_id);
696
+ const keyId = message.value.key_id;
697
+ const keyName = this.serverKeyIdToName.get(keyId) || null;
698
+ message.value.name = keyName;
699
+ if (keyName) {
700
+ this.serverKeyNameToId.delete(keyName);
701
+ } else {
702
+ // Ensure 'name' field exists even if unknown
703
+ if (!('name' in message.value)) message.value.name = null;
704
+ }
705
+ this.serverKeyIdToName.delete(keyId);
665
706
  } else if (msgType === 'Heartbeat') {
666
707
  this.lastHeartbeatTime = Date.now() / 1000;
667
708
  if (this._initialGracePeriod) {
@@ -678,10 +719,14 @@
678
719
  this.serverKeyIdToName.clear();
679
720
  this.serverKeyNameToId.clear();
680
721
  this.recordMap.clear();
681
- this.heartbeatTimeout = Math.max(this.heartbeatTimeout, value.heartbeat_timeout_interval / 1000);
722
+ let newInterval = value.heartbeat_timeout_interval;
723
+ if (this.timeoutScale !== 1.0) {
724
+ newInterval *= this.timeoutScale;
725
+ }
726
+ this.scaledHeartbeatTimeoutInterval = Math.max(this.scaledHeartbeatTimeoutInterval, newInterval);
682
727
  this.lastHeartbeatTime = Date.now() / 1000;
683
728
  this._initialGracePeriod = true;
684
- this._initialGraceDeadline = this.lastHeartbeatTime + this.heartbeatTimeout * 10;
729
+ this._initialGraceDeadline = this.lastHeartbeatTime + (this.scaledHeartbeatTimeoutInterval / 1000.0) * 10;
685
730
  // New Introduction: do not reset the first-heartbeat promise to avoid racing
686
731
  // with consumers already awaiting it. The existing promise will resolve on
687
732
  // the first Heartbeat observed during the grace period above.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgar",
3
- "version": "3.7.2",
3
+ "version": "3.8.0",
4
4
  "description": "A Javascript client for the GAR protocol",
5
5
  "type": "module",
6
6
  "main": "dist/gar.umd.js",