jsgar 4.6.3 → 4.9.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 (3) hide show
  1. package/dist/gar.umd.js +69 -11
  2. package/gar.js +69 -11
  3. package/package.json +4 -1
package/dist/gar.umd.js CHANGED
@@ -200,6 +200,7 @@
200
200
  this.websocket = null;
201
201
  this.messageQueue = [];
202
202
  this.connected = false;
203
+ this.connectedCallback = null;
203
204
  this.reconnectDelay = 5000; // Milliseconds
204
205
  this.user = user;
205
206
  this.working_namespace = working_namespace;
@@ -383,18 +384,18 @@
383
384
  });
384
385
 
385
386
  this.websocket = await connectionPromise;
386
- this.connected = true;
387
+ this._setConnected(true);
387
388
  this.log('INFO', `Connected to WebSocket server at ${this.wsEndpoint} using gar-protocol`);
388
389
 
389
390
  this.websocket.onclose = () => {
390
391
  this.log('WARNING', 'WebSocket connection closed.');
391
- this.connected = false;
392
+ this._setConnected(false);
392
393
  this.websocket = null;
393
394
  this._reconnect();
394
395
  };
395
396
  this.websocket.onerror = (error) => {
396
397
  this.log('ERROR', `WebSocket error: ${error.message || 'Unknown error'}`);
397
- this.connected = false;
398
+ this._setConnected(false);
398
399
  this.websocket = null;
399
400
  };
400
401
 
@@ -402,7 +403,7 @@
402
403
  this._receiveMessages();
403
404
 
404
405
  } catch (e) {
405
- this.connected = false;
406
+ this._setConnected(false);
406
407
  this.websocket = null;
407
408
  // 401 is a permanent auth rejection — retrying won't help
408
409
  if (e.message && e.message.includes('401')) {
@@ -454,7 +455,7 @@
454
455
  }
455
456
  this.log('INFO', 'Done sending messages.');
456
457
  this.stop();
457
- this.connected = false;
458
+ this._setConnected(false);
458
459
  }
459
460
 
460
461
  /**
@@ -467,6 +468,34 @@
467
468
  };
468
469
  }
469
470
 
471
+ /**
472
+ * Register a callback fired when the WS `connected` flag transitions.
473
+ * @param {Function} handler - Callback receiving the new boolean value
474
+ */
475
+ registerConnectedHandler(handler) {
476
+ this.connectedCallback = handler;
477
+ }
478
+
479
+ /**
480
+ * Clear the connected handler.
481
+ */
482
+ clearConnectedHandler() {
483
+ this.connectedCallback = null;
484
+ }
485
+
486
+ /**
487
+ * Internal setter that fires connectedCallback on state transitions.
488
+ * @param {boolean} value
489
+ */
490
+ _setConnected(value) {
491
+ if (this.connected === value) return;
492
+ this.connected = value;
493
+ if (this.connectedCallback) {
494
+ try { this.connectedCallback(value); }
495
+ catch (e) { this.log('ERROR', `connectedCallback threw: ${e.message}`); }
496
+ }
497
+ }
498
+
470
499
  /**
471
500
  * Register a callback handler for a specific message type.
472
501
  * @param {string} messageType - Type of message
@@ -802,7 +831,7 @@
802
831
  * Stop the client without sending pending messages.
803
832
  */
804
833
  halt() {
805
- this.connected = false;
834
+ this._setConnected(false);
806
835
  this.stop();
807
836
  }
808
837
 
@@ -1331,13 +1360,29 @@
1331
1360
  * @param {boolean} [options.skipOwnershipChecks=false] - Bypass ownership validation.
1332
1361
  * @param {boolean} [options.requireExisting=false] - Only write to existing keys; do not create new keys.
1333
1362
  * @param {number} [options.clientKeyId=0] - The g::Client key ID for ownership. 0 uses the connection key.
1363
+ * @param {string} [options.clientKeyName=''] - If non-empty, atomically introduce this g::Client key with
1364
+ * the message. The server creates the key and maps the remote id in one round-trip; no separate
1365
+ * KeyIntroduction is sent. Ignored if the name is already mapped locally (existing id is reused).
1334
1366
  */
1335
- updateActiveOwnership({ msgRef = 0, ownershipAction = 'None', skipOwnershipChecks = false, requireExisting = false, clientKeyId = 0 } = {}) {
1367
+ updateActiveOwnership({ msgRef = 0, ownershipAction = 'None', skipOwnershipChecks = false, requireExisting = false, clientKeyId = 0, clientKeyName = '' } = {}) {
1368
+ if (clientKeyName) {
1369
+ const existingId = this.localKeyMap.get(clientKeyName);
1370
+ if (existingId !== undefined) {
1371
+ clientKeyId = existingId;
1372
+ clientKeyName = '';
1373
+ } else {
1374
+ clientKeyId = this.localKeyCounter++;
1375
+ this.localKeyMap.set(clientKeyName, clientKeyId);
1376
+ }
1377
+ }
1336
1378
  const newOwnership = { msg_ref: msgRef, ownership_action: ownershipAction, skip_ownership_checks: skipOwnershipChecks, require_existing: requireExisting, client_key_id: clientKeyId };
1337
1379
  const cur = this._activeOwnership;
1338
- if (newOwnership.msg_ref !== cur.msg_ref || newOwnership.ownership_action !== cur.ownership_action || newOwnership.skip_ownership_checks !== cur.skip_ownership_checks || newOwnership.require_existing !== cur.require_existing || newOwnership.client_key_id !== cur.client_key_id) {
1380
+ const changed = newOwnership.msg_ref !== cur.msg_ref || newOwnership.ownership_action !== cur.ownership_action || newOwnership.skip_ownership_checks !== cur.skip_ownership_checks || newOwnership.require_existing !== cur.require_existing || newOwnership.client_key_id !== cur.client_key_id;
1381
+ if (changed || clientKeyName) {
1339
1382
  this._activeOwnership = newOwnership;
1340
- this.sendMessage({ message_type: 'ActiveOwnership', value: newOwnership });
1383
+ const value = { ...newOwnership };
1384
+ if (clientKeyName) value.client_key_name = clientKeyName;
1385
+ this.sendMessage({ message_type: 'ActiveOwnership', value });
1341
1386
  }
1342
1387
  }
1343
1388
 
@@ -1405,11 +1450,24 @@
1405
1450
  /**
1406
1451
  * Delete a key by name if it exists in the localKeyMap. Safe to call multiple times.
1407
1452
  * @param {string} keyName - Key name
1453
+ * @param {string|null} [className=null] - If set, removes the key from this class only via
1454
+ * a KeyIntroduction with deleted_class set; the key may still exist on the server in
1455
+ * other classes and the localKeyMap entry is preserved. If unset, sends DeleteKey to
1456
+ * remove the key entirely and forgets the local id so a future
1457
+ * getAndPossiblyIntroduceKeyId(keyName) re-introduces with a fresh id.
1408
1458
  */
1409
- deleteKey(keyName) {
1459
+ deleteKey(keyName, className = null) {
1410
1460
  const keyId = this.localKeyMap.get(keyName);
1411
- if (keyId) {
1461
+ if (keyId === undefined) return;
1462
+ if (className) {
1463
+ this.sendMessage({
1464
+ message_type: 'KeyIntroduction',
1465
+ value: { key_id: keyId, name: keyName, deleted_class: className },
1466
+ });
1467
+ } else {
1412
1468
  this.publishDeleteKey(keyId);
1469
+ // Forget the local id so future re-introduction allocates a new one.
1470
+ this.localKeyMap.delete(keyName);
1413
1471
  }
1414
1472
  }
1415
1473
 
package/gar.js CHANGED
@@ -194,6 +194,7 @@ class GARClient {
194
194
  this.websocket = null;
195
195
  this.messageQueue = [];
196
196
  this.connected = false;
197
+ this.connectedCallback = null;
197
198
  this.reconnectDelay = 5000; // Milliseconds
198
199
  this.user = user;
199
200
  this.working_namespace = working_namespace;
@@ -377,18 +378,18 @@ class GARClient {
377
378
  });
378
379
 
379
380
  this.websocket = await connectionPromise;
380
- this.connected = true;
381
+ this._setConnected(true);
381
382
  this.log('INFO', `Connected to WebSocket server at ${this.wsEndpoint} using gar-protocol`);
382
383
 
383
384
  this.websocket.onclose = () => {
384
385
  this.log('WARNING', 'WebSocket connection closed.');
385
- this.connected = false;
386
+ this._setConnected(false);
386
387
  this.websocket = null;
387
388
  this._reconnect();
388
389
  };
389
390
  this.websocket.onerror = (error) => {
390
391
  this.log('ERROR', `WebSocket error: ${error.message || 'Unknown error'}`);
391
- this.connected = false;
392
+ this._setConnected(false);
392
393
  this.websocket = null;
393
394
  };
394
395
 
@@ -396,7 +397,7 @@ class GARClient {
396
397
  this._receiveMessages();
397
398
 
398
399
  } catch (e) {
399
- this.connected = false;
400
+ this._setConnected(false);
400
401
  this.websocket = null;
401
402
  // 401 is a permanent auth rejection — retrying won't help
402
403
  if (e.message && e.message.includes('401')) {
@@ -448,7 +449,7 @@ class GARClient {
448
449
  }
449
450
  this.log('INFO', 'Done sending messages.');
450
451
  this.stop()
451
- this.connected = false;
452
+ this._setConnected(false);
452
453
  }
453
454
 
454
455
  /**
@@ -461,6 +462,34 @@ class GARClient {
461
462
  };
462
463
  }
463
464
 
465
+ /**
466
+ * Register a callback fired when the WS `connected` flag transitions.
467
+ * @param {Function} handler - Callback receiving the new boolean value
468
+ */
469
+ registerConnectedHandler(handler) {
470
+ this.connectedCallback = handler;
471
+ }
472
+
473
+ /**
474
+ * Clear the connected handler.
475
+ */
476
+ clearConnectedHandler() {
477
+ this.connectedCallback = null;
478
+ }
479
+
480
+ /**
481
+ * Internal setter that fires connectedCallback on state transitions.
482
+ * @param {boolean} value
483
+ */
484
+ _setConnected(value) {
485
+ if (this.connected === value) return;
486
+ this.connected = value;
487
+ if (this.connectedCallback) {
488
+ try { this.connectedCallback(value); }
489
+ catch (e) { this.log('ERROR', `connectedCallback threw: ${e.message}`); }
490
+ }
491
+ }
492
+
464
493
  /**
465
494
  * Register a callback handler for a specific message type.
466
495
  * @param {string} messageType - Type of message
@@ -796,7 +825,7 @@ class GARClient {
796
825
  * Stop the client without sending pending messages.
797
826
  */
798
827
  halt() {
799
- this.connected = false;
828
+ this._setConnected(false);
800
829
  this.stop();
801
830
  }
802
831
 
@@ -1325,13 +1354,29 @@ class GARClient {
1325
1354
  * @param {boolean} [options.skipOwnershipChecks=false] - Bypass ownership validation.
1326
1355
  * @param {boolean} [options.requireExisting=false] - Only write to existing keys; do not create new keys.
1327
1356
  * @param {number} [options.clientKeyId=0] - The g::Client key ID for ownership. 0 uses the connection key.
1357
+ * @param {string} [options.clientKeyName=''] - If non-empty, atomically introduce this g::Client key with
1358
+ * the message. The server creates the key and maps the remote id in one round-trip; no separate
1359
+ * KeyIntroduction is sent. Ignored if the name is already mapped locally (existing id is reused).
1328
1360
  */
1329
- updateActiveOwnership({ msgRef = 0, ownershipAction = 'None', skipOwnershipChecks = false, requireExisting = false, clientKeyId = 0 } = {}) {
1361
+ updateActiveOwnership({ msgRef = 0, ownershipAction = 'None', skipOwnershipChecks = false, requireExisting = false, clientKeyId = 0, clientKeyName = '' } = {}) {
1362
+ if (clientKeyName) {
1363
+ const existingId = this.localKeyMap.get(clientKeyName);
1364
+ if (existingId !== undefined) {
1365
+ clientKeyId = existingId;
1366
+ clientKeyName = '';
1367
+ } else {
1368
+ clientKeyId = this.localKeyCounter++;
1369
+ this.localKeyMap.set(clientKeyName, clientKeyId);
1370
+ }
1371
+ }
1330
1372
  const newOwnership = { msg_ref: msgRef, ownership_action: ownershipAction, skip_ownership_checks: skipOwnershipChecks, require_existing: requireExisting, client_key_id: clientKeyId };
1331
1373
  const cur = this._activeOwnership;
1332
- if (newOwnership.msg_ref !== cur.msg_ref || newOwnership.ownership_action !== cur.ownership_action || newOwnership.skip_ownership_checks !== cur.skip_ownership_checks || newOwnership.require_existing !== cur.require_existing || newOwnership.client_key_id !== cur.client_key_id) {
1374
+ const changed = newOwnership.msg_ref !== cur.msg_ref || newOwnership.ownership_action !== cur.ownership_action || newOwnership.skip_ownership_checks !== cur.skip_ownership_checks || newOwnership.require_existing !== cur.require_existing || newOwnership.client_key_id !== cur.client_key_id;
1375
+ if (changed || clientKeyName) {
1333
1376
  this._activeOwnership = newOwnership;
1334
- this.sendMessage({ message_type: 'ActiveOwnership', value: newOwnership });
1377
+ const value = { ...newOwnership };
1378
+ if (clientKeyName) value.client_key_name = clientKeyName;
1379
+ this.sendMessage({ message_type: 'ActiveOwnership', value });
1335
1380
  }
1336
1381
  }
1337
1382
 
@@ -1399,11 +1444,24 @@ class GARClient {
1399
1444
  /**
1400
1445
  * Delete a key by name if it exists in the localKeyMap. Safe to call multiple times.
1401
1446
  * @param {string} keyName - Key name
1447
+ * @param {string|null} [className=null] - If set, removes the key from this class only via
1448
+ * a KeyIntroduction with deleted_class set; the key may still exist on the server in
1449
+ * other classes and the localKeyMap entry is preserved. If unset, sends DeleteKey to
1450
+ * remove the key entirely and forgets the local id so a future
1451
+ * getAndPossiblyIntroduceKeyId(keyName) re-introduces with a fresh id.
1402
1452
  */
1403
- deleteKey(keyName) {
1453
+ deleteKey(keyName, className = null) {
1404
1454
  const keyId = this.localKeyMap.get(keyName);
1405
- if (keyId) {
1455
+ if (keyId === undefined) return;
1456
+ if (className) {
1457
+ this.sendMessage({
1458
+ message_type: 'KeyIntroduction',
1459
+ value: { key_id: keyId, name: keyName, deleted_class: className },
1460
+ });
1461
+ } else {
1406
1462
  this.publishDeleteKey(keyId);
1463
+ // Forget the local id so future re-introduction allocates a new one.
1464
+ this.localKeyMap.delete(keyName);
1407
1465
  }
1408
1466
  }
1409
1467
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgar",
3
- "version": "4.6.3",
3
+ "version": "4.9.0",
4
4
  "description": "A Javascript client for the GAR protocol",
5
5
  "type": "module",
6
6
  "main": "dist/gar.umd.js",
@@ -36,8 +36,11 @@
36
36
  "@eslint/json": "^0.13.2",
37
37
  "@rollup/plugin-commonjs": "^24.0.0",
38
38
  "@rollup/plugin-node-resolve": "^15.0.0",
39
+ "ag-grid-community": "^34.1.1",
40
+ "ag-grid-enterprise": "^34.1.1",
39
41
  "eslint": "^9.39.4",
40
42
  "globals": "^16.0.0",
43
+ "playwright": "^1.50.0",
41
44
  "rollup": "^3.0.0"
42
45
  }
43
46
  }