jsgar 4.5.2 → 4.6.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 +188 -13
  2. package/gar.js +181 -8
  3. package/package.json +2 -2
package/dist/gar.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
- typeof define === 'function' && define.amd ? define(factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.GARClient = factory());
5
- })(this, (function () { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GARClient = {}));
5
+ })(this, (function (exports) { 'use strict';
6
6
 
7
7
  /**
8
8
  * A client implementation for the Generic Active Records (GAR) protocol using WebSockets.
@@ -30,6 +30,155 @@
30
30
  // - In Node.js, dynamically imports 'ws' and assigns globalThis.WebSocket
31
31
  // All client code references the constructor via helper methods, not a top-level binding.
32
32
 
33
+ // AF_UNIX local-transport optimization (mirrors ipc/gar.cpp). When the endpoint
34
+ // resolves to an IP bound to a local interface, the GAR server is also listening
35
+ // on an abstract-namespace AF_UNIX socket at "\0gar.endpoint.<port>". Routing the
36
+ // WebSocket through that path bypasses the kernel TCP loopback stack — avoiding
37
+ // EDR/Falcon-style instrumentation overhead — while preserving the wire protocol.
38
+ //
39
+ // Browser environments have no raw socket access, so this is Node.js-only. The
40
+ // 'ws' library lets us inject a pre-connected net.Socket (or TLS-wrapped) via
41
+ // the createConnection option in the constructor.
42
+ //
43
+ // Set TRS_DISABLE_AF_UNIX=1 (also accepts true/on/yes, case-insensitive) to opt
44
+ // out and force the regular TCP path. Useful when capturing GAR traffic with
45
+ // tcpdump/wireshark on the loopback interface, which only sees TCP.
46
+
47
+ function _garAfUnixDisabled() {
48
+ const v = (typeof process !== 'undefined' && process.env)
49
+ ? process.env.TRS_DISABLE_AF_UNIX : undefined;
50
+ if (!v) return false;
51
+ return ['1', 'true', 'on', 'yes'].includes(v.toLowerCase());
52
+ }
53
+
54
+ let _garNetMod = null;
55
+ let _garTlsMod = null;
56
+ let _garOsMod = null;
57
+ let _garDnsMod = null;
58
+
59
+ async function _garLoadNodeModules() {
60
+ if (_garNetMod) return true;
61
+ const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
62
+ if (!isNode) return false;
63
+ try {
64
+ const { createRequire } = await import('node:module');
65
+ const req = createRequire(process.cwd() + '/');
66
+ _garNetMod = req('net');
67
+ _garTlsMod = req('tls');
68
+ _garOsMod = req('os');
69
+ _garDnsMod = req('dns');
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ function _garLocalIPv4Set() {
77
+ const set = new Set(['127.0.0.1']);
78
+ const ifaces = _garOsMod.networkInterfaces();
79
+ for (const name of Object.keys(ifaces)) {
80
+ for (const addr of ifaces[name] || []) {
81
+ if (addr.family === 'IPv4' || addr.family === 4) set.add(addr.address);
82
+ }
83
+ }
84
+ return set;
85
+ }
86
+
87
+ // Returns the optimal GAR endpoint string for |inetEndpoint|: if the URL's
88
+ // host resolves locally on this machine, returns the AF_UNIX form
89
+ // "unix:gar.endpoint.<port>"; otherwise echoes the input. Mirrors
90
+ // `trsutil --optimal-endpoint`. Async because DNS lookup in Node is async-only.
91
+ // Respects TRS_DISABLE_AF_UNIX. Does not probe the AF_UNIX listener — caller
92
+ // (or jsgar's connect path) handles fallback if it's unreachable.
93
+ async function optimalEndpoint(inetEndpoint) {
94
+ if (_garAfUnixDisabled()) return inetEndpoint;
95
+ if (!(await _garLoadNodeModules())) return inetEndpoint;
96
+ let parsed;
97
+ try { parsed = new URL(inetEndpoint); } catch { return inetEndpoint; }
98
+ const host = parsed.hostname;
99
+ const port = parsed.port;
100
+ if (!host || !port) return inetEndpoint;
101
+ let ip;
102
+ try {
103
+ if (_garNetMod.isIP(host)) {
104
+ ip = host;
105
+ } else {
106
+ const r = await _garDnsMod.promises.lookup(host, { family: 4 });
107
+ ip = r.address;
108
+ }
109
+ } catch { return inetEndpoint; }
110
+ if (ip.startsWith('127.') || _garLocalIPv4Set().has(ip)) {
111
+ return `unix:gar.endpoint.${port}`;
112
+ }
113
+ return inetEndpoint;
114
+ }
115
+
116
+ // Returns the abstract-namespace AF_UNIX path (a string starting with \u0000)
117
+ // for the given GAR WebSocket endpoint if (a) it resolves to a local IPv4 and
118
+ // (b) the abstract listener at the matching name is reachable. Otherwise null.
119
+ async function _garUnixAbstractPathForEndpoint(wsEndpoint) {
120
+ if (_garAfUnixDisabled()) return null;
121
+ if (!(await _garLoadNodeModules())) return null;
122
+ let parsed;
123
+ try {
124
+ parsed = new URL(wsEndpoint);
125
+ } catch {
126
+ return null;
127
+ }
128
+ const host = parsed.hostname;
129
+ const port = parsed.port;
130
+ if (!host || !port) return null;
131
+
132
+ let ip;
133
+ try {
134
+ if (_garNetMod.isIP(host)) {
135
+ ip = host;
136
+ } else {
137
+ const r = await _garDnsMod.promises.lookup(host, { family: 4 });
138
+ ip = r.address;
139
+ }
140
+ } catch {
141
+ return null;
142
+ }
143
+
144
+ // Loopback short-circuit; otherwise check against the host's interface IPs.
145
+ // (Less precise than RTM_GETROUTE used by the C++ side, but covers the
146
+ // practical "is this destination on this machine?" question for our use.)
147
+ if (!ip.startsWith('127.')) {
148
+ if (!_garLocalIPv4Set().has(ip)) return null;
149
+ }
150
+
151
+ const abstractPath = '\u0000gar.endpoint.' + port;
152
+ // Reachability probe: AF_UNIX local connect either succeeds immediately or
153
+ // fails immediately with ECONNREFUSED/ENOENT.
154
+ const reachable = await new Promise((resolve) => {
155
+ let done = false;
156
+ const finish = (ok) => {
157
+ if (done) return;
158
+ done = true;
159
+ try { sock.destroy(); } catch { /* ignore */ }
160
+ resolve(ok);
161
+ };
162
+ const sock = _garNetMod.createConnection({ path: abstractPath });
163
+ sock.once('connect', () => finish(true));
164
+ sock.once('error', () => finish(false));
165
+ setTimeout(() => finish(false), 500);
166
+ });
167
+ return reachable ? abstractPath : null;
168
+ }
169
+
170
+ function _garMakeUnixCreateConnectionFn(abstractPath, useSSL, sniHost, allowSelfSigned) {
171
+ return () => {
172
+ const unixSocket = _garNetMod.createConnection({ path: abstractPath });
173
+ if (!useSSL) return unixSocket;
174
+ return _garTlsMod.connect({
175
+ socket: unixSocket,
176
+ servername: sniHost,
177
+ rejectUnauthorized: !allowSelfSigned,
178
+ });
179
+ };
180
+ }
181
+
33
182
  class GARClient {
34
183
  /**
35
184
  * Initialize the GAR client.
@@ -74,7 +223,7 @@
74
223
  this.localKeyMap = new Map();
75
224
  this.invalidatedKeyIds = new Set();
76
225
  this.recordMap = new Map();
77
- this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, client_key_id: 0 };
226
+ this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, require_existing: false, client_key_id: 0 };
78
227
 
79
228
  this.running = false;
80
229
  this.heartbeatIntervalId = null;
@@ -142,7 +291,7 @@
142
291
  this.invalidatedKeyIds.clear();
143
292
 
144
293
  // Active ownership state
145
- this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, client_key_id: 0 };
294
+ this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, require_existing: false, client_key_id: 0 };
146
295
 
147
296
  // Heartbeat grace period flags
148
297
  this._initialGracePeriod = false;
@@ -169,13 +318,35 @@
169
318
  const WS = await this._ensureWebSocketCtor();
170
319
  const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
171
320
 
172
- this.log('INFO', `Connecting to WebSocket server at ${this.wsEndpoint}`);
321
+ // Prefer AF_UNIX abstract-namespace transport for local destinations.
322
+ // Falls back to TCP automatically (next reconnect iteration) if the
323
+ // listener disappears between probe and real connect.
324
+ let unixAbstractPath = null;
325
+ let sniHost = null;
326
+ if (isNode) {
327
+ unixAbstractPath = await _garUnixAbstractPathForEndpoint(this.wsEndpoint);
328
+ if (unixAbstractPath) {
329
+ try { sniHost = new URL(this.wsEndpoint).hostname; } catch { /* ignore */ }
330
+ }
331
+ }
332
+
333
+ this.log('INFO', `Connecting to WebSocket server at ${this.wsEndpoint}${unixAbstractPath ? ' (via AF_UNIX)' : ''}`);
173
334
 
174
335
  const connectionPromise = new Promise((resolve, reject) => {
175
336
  let websocket;
176
- if (this.allowSelfSignedCertificate && isNode) {
337
+ const useSSL = this.wsEndpoint.toLowerCase().startsWith('wss://');
338
+ const wsOpts = {};
339
+ if (this.allowSelfSignedCertificate && isNode && useSSL) {
177
340
  // Node.js 'ws' supports options for TLS; browsers do not.
178
- websocket = new WS(this.wsEndpoint, ['gar-protocol'], { rejectUnauthorized: false });
341
+ wsOpts.rejectUnauthorized = false;
342
+ }
343
+ if (unixAbstractPath) {
344
+ wsOpts.createConnection = _garMakeUnixCreateConnectionFn(
345
+ unixAbstractPath, useSSL, sniHost, this.allowSelfSignedCertificate
346
+ );
347
+ }
348
+ if (Object.keys(wsOpts).length > 0) {
349
+ websocket = new WS(this.wsEndpoint, ['gar-protocol'], wsOpts);
179
350
  } else {
180
351
  websocket = new WS(this.wsEndpoint, ['gar-protocol']);
181
352
  }
@@ -1145,12 +1316,13 @@
1145
1316
  * @param {number} [options.msgRef=0] - Message reference for correlating recoverable error responses.
1146
1317
  * @param {string} [options.ownershipAction='None'] - 'None' (check only), 'Acquire', or 'Release'.
1147
1318
  * @param {boolean} [options.skipOwnershipChecks=false] - Bypass ownership validation.
1319
+ * @param {boolean} [options.requireExisting=false] - Only write to existing keys; do not create new keys.
1148
1320
  * @param {number} [options.clientKeyId=0] - The g::Client key ID for ownership. 0 uses the connection key.
1149
1321
  */
1150
- updateActiveOwnership({ msgRef = 0, ownershipAction = 'None', skipOwnershipChecks = false, clientKeyId = 0 } = {}) {
1151
- const newOwnership = { msg_ref: msgRef, ownership_action: ownershipAction, skip_ownership_checks: skipOwnershipChecks, client_key_id: clientKeyId };
1322
+ updateActiveOwnership({ msgRef = 0, ownershipAction = 'None', skipOwnershipChecks = false, requireExisting = false, clientKeyId = 0 } = {}) {
1323
+ const newOwnership = { msg_ref: msgRef, ownership_action: ownershipAction, skip_ownership_checks: skipOwnershipChecks, require_existing: requireExisting, client_key_id: clientKeyId };
1152
1324
  const cur = this._activeOwnership;
1153
- if (newOwnership.msg_ref !== cur.msg_ref || newOwnership.ownership_action !== cur.ownership_action || newOwnership.skip_ownership_checks !== cur.skip_ownership_checks || newOwnership.client_key_id !== cur.client_key_id) {
1325
+ 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) {
1154
1326
  this._activeOwnership = newOwnership;
1155
1327
  this.sendMessage({ message_type: 'ActiveOwnership', value: newOwnership });
1156
1328
  }
@@ -1516,6 +1688,9 @@
1516
1688
  }
1517
1689
  }
1518
1690
 
1519
- return GARClient;
1691
+ exports.default = GARClient;
1692
+ exports.optimalEndpoint = optimalEndpoint;
1693
+
1694
+ Object.defineProperty(exports, '__esModule', { value: true });
1520
1695
 
1521
1696
  }));
package/gar.js CHANGED
@@ -24,6 +24,155 @@
24
24
  // - In Node.js, dynamically imports 'ws' and assigns globalThis.WebSocket
25
25
  // All client code references the constructor via helper methods, not a top-level binding.
26
26
 
27
+ // AF_UNIX local-transport optimization (mirrors ipc/gar.cpp). When the endpoint
28
+ // resolves to an IP bound to a local interface, the GAR server is also listening
29
+ // on an abstract-namespace AF_UNIX socket at "\0gar.endpoint.<port>". Routing the
30
+ // WebSocket through that path bypasses the kernel TCP loopback stack — avoiding
31
+ // EDR/Falcon-style instrumentation overhead — while preserving the wire protocol.
32
+ //
33
+ // Browser environments have no raw socket access, so this is Node.js-only. The
34
+ // 'ws' library lets us inject a pre-connected net.Socket (or TLS-wrapped) via
35
+ // the createConnection option in the constructor.
36
+ //
37
+ // Set TRS_DISABLE_AF_UNIX=1 (also accepts true/on/yes, case-insensitive) to opt
38
+ // out and force the regular TCP path. Useful when capturing GAR traffic with
39
+ // tcpdump/wireshark on the loopback interface, which only sees TCP.
40
+
41
+ function _garAfUnixDisabled() {
42
+ const v = (typeof process !== 'undefined' && process.env)
43
+ ? process.env.TRS_DISABLE_AF_UNIX : undefined;
44
+ if (!v) return false;
45
+ return ['1', 'true', 'on', 'yes'].includes(v.toLowerCase());
46
+ }
47
+
48
+ let _garNetMod = null;
49
+ let _garTlsMod = null;
50
+ let _garOsMod = null;
51
+ let _garDnsMod = null;
52
+
53
+ async function _garLoadNodeModules() {
54
+ if (_garNetMod) return true;
55
+ const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
56
+ if (!isNode) return false;
57
+ try {
58
+ const { createRequire } = await import('node:module');
59
+ const req = createRequire(process.cwd() + '/');
60
+ _garNetMod = req('net');
61
+ _garTlsMod = req('tls');
62
+ _garOsMod = req('os');
63
+ _garDnsMod = req('dns');
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ function _garLocalIPv4Set() {
71
+ const set = new Set(['127.0.0.1']);
72
+ const ifaces = _garOsMod.networkInterfaces();
73
+ for (const name of Object.keys(ifaces)) {
74
+ for (const addr of ifaces[name] || []) {
75
+ if (addr.family === 'IPv4' || addr.family === 4) set.add(addr.address);
76
+ }
77
+ }
78
+ return set;
79
+ }
80
+
81
+ // Returns the optimal GAR endpoint string for |inetEndpoint|: if the URL's
82
+ // host resolves locally on this machine, returns the AF_UNIX form
83
+ // "unix:gar.endpoint.<port>"; otherwise echoes the input. Mirrors
84
+ // `trsutil --optimal-endpoint`. Async because DNS lookup in Node is async-only.
85
+ // Respects TRS_DISABLE_AF_UNIX. Does not probe the AF_UNIX listener — caller
86
+ // (or jsgar's connect path) handles fallback if it's unreachable.
87
+ async function optimalEndpoint(inetEndpoint) {
88
+ if (_garAfUnixDisabled()) return inetEndpoint;
89
+ if (!(await _garLoadNodeModules())) return inetEndpoint;
90
+ let parsed;
91
+ try { parsed = new URL(inetEndpoint); } catch { return inetEndpoint; }
92
+ const host = parsed.hostname;
93
+ const port = parsed.port;
94
+ if (!host || !port) return inetEndpoint;
95
+ let ip;
96
+ try {
97
+ if (_garNetMod.isIP(host)) {
98
+ ip = host;
99
+ } else {
100
+ const r = await _garDnsMod.promises.lookup(host, { family: 4 });
101
+ ip = r.address;
102
+ }
103
+ } catch { return inetEndpoint; }
104
+ if (ip.startsWith('127.') || _garLocalIPv4Set().has(ip)) {
105
+ return `unix:gar.endpoint.${port}`;
106
+ }
107
+ return inetEndpoint;
108
+ }
109
+
110
+ // Returns the abstract-namespace AF_UNIX path (a string starting with \u0000)
111
+ // for the given GAR WebSocket endpoint if (a) it resolves to a local IPv4 and
112
+ // (b) the abstract listener at the matching name is reachable. Otherwise null.
113
+ async function _garUnixAbstractPathForEndpoint(wsEndpoint) {
114
+ if (_garAfUnixDisabled()) return null;
115
+ if (!(await _garLoadNodeModules())) return null;
116
+ let parsed;
117
+ try {
118
+ parsed = new URL(wsEndpoint);
119
+ } catch {
120
+ return null;
121
+ }
122
+ const host = parsed.hostname;
123
+ const port = parsed.port;
124
+ if (!host || !port) return null;
125
+
126
+ let ip;
127
+ try {
128
+ if (_garNetMod.isIP(host)) {
129
+ ip = host;
130
+ } else {
131
+ const r = await _garDnsMod.promises.lookup(host, { family: 4 });
132
+ ip = r.address;
133
+ }
134
+ } catch {
135
+ return null;
136
+ }
137
+
138
+ // Loopback short-circuit; otherwise check against the host's interface IPs.
139
+ // (Less precise than RTM_GETROUTE used by the C++ side, but covers the
140
+ // practical "is this destination on this machine?" question for our use.)
141
+ if (!ip.startsWith('127.')) {
142
+ if (!_garLocalIPv4Set().has(ip)) return null;
143
+ }
144
+
145
+ const abstractPath = '\u0000gar.endpoint.' + port;
146
+ // Reachability probe: AF_UNIX local connect either succeeds immediately or
147
+ // fails immediately with ECONNREFUSED/ENOENT.
148
+ const reachable = await new Promise((resolve) => {
149
+ let done = false;
150
+ const finish = (ok) => {
151
+ if (done) return;
152
+ done = true;
153
+ try { sock.destroy(); } catch { /* ignore */ }
154
+ resolve(ok);
155
+ };
156
+ const sock = _garNetMod.createConnection({ path: abstractPath });
157
+ sock.once('connect', () => finish(true));
158
+ sock.once('error', () => finish(false));
159
+ setTimeout(() => finish(false), 500);
160
+ });
161
+ return reachable ? abstractPath : null;
162
+ }
163
+
164
+ function _garMakeUnixCreateConnectionFn(abstractPath, useSSL, sniHost, allowSelfSigned) {
165
+ return () => {
166
+ const unixSocket = _garNetMod.createConnection({ path: abstractPath });
167
+ if (!useSSL) return unixSocket;
168
+ return _garTlsMod.connect({
169
+ socket: unixSocket,
170
+ servername: sniHost,
171
+ rejectUnauthorized: !allowSelfSigned,
172
+ });
173
+ };
174
+ }
175
+
27
176
  class GARClient {
28
177
  /**
29
178
  * Initialize the GAR client.
@@ -68,7 +217,7 @@ class GARClient {
68
217
  this.localKeyMap = new Map();
69
218
  this.invalidatedKeyIds = new Set();
70
219
  this.recordMap = new Map();
71
- this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, client_key_id: 0 };
220
+ this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, require_existing: false, client_key_id: 0 };
72
221
 
73
222
  this.running = false;
74
223
  this.heartbeatIntervalId = null;
@@ -136,7 +285,7 @@ class GARClient {
136
285
  this.invalidatedKeyIds.clear();
137
286
 
138
287
  // Active ownership state
139
- this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, client_key_id: 0 };
288
+ this._activeOwnership = { msg_ref: 0, ownership_action: 'None', skip_ownership_checks: false, require_existing: false, client_key_id: 0 };
140
289
 
141
290
  // Heartbeat grace period flags
142
291
  this._initialGracePeriod = false;
@@ -163,13 +312,35 @@ class GARClient {
163
312
  const WS = await this._ensureWebSocketCtor();
164
313
  const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
165
314
 
166
- this.log('INFO', `Connecting to WebSocket server at ${this.wsEndpoint}`);
315
+ // Prefer AF_UNIX abstract-namespace transport for local destinations.
316
+ // Falls back to TCP automatically (next reconnect iteration) if the
317
+ // listener disappears between probe and real connect.
318
+ let unixAbstractPath = null;
319
+ let sniHost = null;
320
+ if (isNode) {
321
+ unixAbstractPath = await _garUnixAbstractPathForEndpoint(this.wsEndpoint);
322
+ if (unixAbstractPath) {
323
+ try { sniHost = new URL(this.wsEndpoint).hostname; } catch { /* ignore */ }
324
+ }
325
+ }
326
+
327
+ this.log('INFO', `Connecting to WebSocket server at ${this.wsEndpoint}${unixAbstractPath ? ' (via AF_UNIX)' : ''}`);
167
328
 
168
329
  const connectionPromise = new Promise((resolve, reject) => {
169
330
  let websocket;
170
- if (this.allowSelfSignedCertificate && isNode) {
331
+ const useSSL = this.wsEndpoint.toLowerCase().startsWith('wss://');
332
+ const wsOpts = {};
333
+ if (this.allowSelfSignedCertificate && isNode && useSSL) {
171
334
  // Node.js 'ws' supports options for TLS; browsers do not.
172
- websocket = new WS(this.wsEndpoint, ['gar-protocol'], { rejectUnauthorized: false });
335
+ wsOpts.rejectUnauthorized = false;
336
+ }
337
+ if (unixAbstractPath) {
338
+ wsOpts.createConnection = _garMakeUnixCreateConnectionFn(
339
+ unixAbstractPath, useSSL, sniHost, this.allowSelfSignedCertificate
340
+ );
341
+ }
342
+ if (Object.keys(wsOpts).length > 0) {
343
+ websocket = new WS(this.wsEndpoint, ['gar-protocol'], wsOpts);
173
344
  } else {
174
345
  websocket = new WS(this.wsEndpoint, ['gar-protocol']);
175
346
  }
@@ -1139,12 +1310,13 @@ class GARClient {
1139
1310
  * @param {number} [options.msgRef=0] - Message reference for correlating recoverable error responses.
1140
1311
  * @param {string} [options.ownershipAction='None'] - 'None' (check only), 'Acquire', or 'Release'.
1141
1312
  * @param {boolean} [options.skipOwnershipChecks=false] - Bypass ownership validation.
1313
+ * @param {boolean} [options.requireExisting=false] - Only write to existing keys; do not create new keys.
1142
1314
  * @param {number} [options.clientKeyId=0] - The g::Client key ID for ownership. 0 uses the connection key.
1143
1315
  */
1144
- updateActiveOwnership({ msgRef = 0, ownershipAction = 'None', skipOwnershipChecks = false, clientKeyId = 0 } = {}) {
1145
- const newOwnership = { msg_ref: msgRef, ownership_action: ownershipAction, skip_ownership_checks: skipOwnershipChecks, client_key_id: clientKeyId };
1316
+ updateActiveOwnership({ msgRef = 0, ownershipAction = 'None', skipOwnershipChecks = false, requireExisting = false, clientKeyId = 0 } = {}) {
1317
+ const newOwnership = { msg_ref: msgRef, ownership_action: ownershipAction, skip_ownership_checks: skipOwnershipChecks, require_existing: requireExisting, client_key_id: clientKeyId };
1146
1318
  const cur = this._activeOwnership;
1147
- if (newOwnership.msg_ref !== cur.msg_ref || newOwnership.ownership_action !== cur.ownership_action || newOwnership.skip_ownership_checks !== cur.skip_ownership_checks || newOwnership.client_key_id !== cur.client_key_id) {
1319
+ 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) {
1148
1320
  this._activeOwnership = newOwnership;
1149
1321
  this.sendMessage({ message_type: 'ActiveOwnership', value: newOwnership });
1150
1322
  }
@@ -1511,3 +1683,4 @@ class GARClient {
1511
1683
  }
1512
1684
 
1513
1685
  export default GARClient;
1686
+ export { optimalEndpoint };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgar",
3
- "version": "4.5.2",
3
+ "version": "4.6.0",
4
4
  "description": "A Javascript client for the GAR protocol",
5
5
  "type": "module",
6
6
  "main": "dist/gar.umd.js",
@@ -36,7 +36,7 @@
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
- "eslint": "^9.38.0",
39
+ "eslint": "^9.39.4",
40
40
  "globals": "^16.0.0",
41
41
  "rollup": "^3.0.0"
42
42
  }