jsgar 4.13.0 → 4.13.3
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/gar.umd.js +57 -5
- package/gar.js +57 -5
- package/package.json +3 -1
package/dist/gar.umd.js
CHANGED
|
@@ -852,6 +852,31 @@
|
|
|
852
852
|
*/
|
|
853
853
|
sendMessage(message) {
|
|
854
854
|
this.messageQueue.push(message);
|
|
855
|
+
// Drain synchronously when possible: background tabs throttle timers — the polling
|
|
856
|
+
// drain loop (and any setInterval sender) can stretch from 100ms to minutes — but
|
|
857
|
+
// synchronous sends from an event or enqueue are never throttled.
|
|
858
|
+
this._flushQueue();
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Synchronously send everything sendable. Safe alongside the polling loop (same JS
|
|
863
|
+
* thread); leaves the null shutdown sentinel for the loop's exit handling.
|
|
864
|
+
*/
|
|
865
|
+
_flushQueue() {
|
|
866
|
+
while (this.connected && this.messageQueue.length > 0 && this._isSocketOpen()) {
|
|
867
|
+
if (this.messageQueue[0] === null) return;
|
|
868
|
+
const message = this.messageQueue.shift();
|
|
869
|
+
try {
|
|
870
|
+
this.websocket.send(JSON.stringify(message));
|
|
871
|
+
if (this.logEveryMessage) {
|
|
872
|
+
this.log('INFO', `Sent: ${JSON.stringify(message)}`);
|
|
873
|
+
}
|
|
874
|
+
} catch (e) {
|
|
875
|
+
this.log('WARNING', `Error sending message: ${e.message}`);
|
|
876
|
+
this.halt();
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
855
880
|
}
|
|
856
881
|
|
|
857
882
|
/**
|
|
@@ -979,6 +1004,13 @@
|
|
|
979
1004
|
this.invalidatedKeyIds.add(message.value.client_introduced_key_id);
|
|
980
1005
|
} else if (msgType === 'Heartbeat') {
|
|
981
1006
|
this.lastHeartbeatTime = Date.now() / 1000;
|
|
1007
|
+
// Echo our own heartbeat on the server's cadence. A hidden tab's throttled timers
|
|
1008
|
+
// can silence the periodic sender far past the server's timeout while this
|
|
1009
|
+
// (unthrottled) receive path keeps delivering — the server then rightly declares
|
|
1010
|
+
// us dead. Event-driven liveness: receive -> reply, flushed synchronously above.
|
|
1011
|
+
if (this.running) {
|
|
1012
|
+
this.sendMessage({ message_type: 'Heartbeat', value: { u_milliseconds: Date.now() } });
|
|
1013
|
+
}
|
|
982
1014
|
if (this._initialGracePeriod) {
|
|
983
1015
|
// Resolve first-heartbeat waiters and end the initial grace window
|
|
984
1016
|
if (this._firstHeartbeatResolve) {
|
|
@@ -1147,7 +1179,9 @@
|
|
|
1147
1179
|
* @param {Object} opts - Named subscription options (fields below).
|
|
1148
1180
|
* @param {string} opts.name - Subscription name
|
|
1149
1181
|
* @param {string} [opts.subscriptionMode='Streaming'] - Subscription mode
|
|
1150
|
-
* @param {string|Array<string>|null} [keyName=null] - Key name(s)
|
|
1182
|
+
* @param {string|Array<string>|Array<number>|null} [keyName=null] - Key name(s), matched
|
|
1183
|
+
* exactly via a synthesized key_filter alternation (keys are NOT created by
|
|
1184
|
+
* subscribing); an array of integers passes through as the wire key_id_list
|
|
1151
1185
|
* @param {string|Array<string>|null} [topicName=null] - Topic name(s)
|
|
1152
1186
|
* @param {string|Array<string>|null} [className=null] - Class name(s)
|
|
1153
1187
|
* @param {string|null} [keyFilter=null] - Key filter regex (cannot use with keyName)
|
|
@@ -1226,16 +1260,24 @@
|
|
|
1226
1260
|
referencingClasses = referencingClassList;
|
|
1227
1261
|
}
|
|
1228
1262
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
//
|
|
1263
|
+
// Convert keyName to array. Subscribing must NOT create keys: names are no longer
|
|
1264
|
+
// introduced to build a key_id_list (the old behavior materialized every named key on
|
|
1265
|
+
// the server). Names become an exact-match alternation key_filter instead — the server
|
|
1266
|
+
// recognizes the literal-alternation shape and builds a trie, skipping the full
|
|
1267
|
+
// regex->DFA compilation. An array of integers passes through as key_id_list verbatim
|
|
1268
|
+
// (the caller already holds real key ids).
|
|
1232
1269
|
let keyNames = [];
|
|
1233
1270
|
if (typeof keyName === 'string') {
|
|
1234
1271
|
keyNames = [keyName];
|
|
1235
1272
|
} else if (Array.isArray(keyName)) {
|
|
1236
1273
|
keyNames = keyName;
|
|
1237
1274
|
}
|
|
1238
|
-
|
|
1275
|
+
let keyIdList = [];
|
|
1276
|
+
if (keyNames.length > 0 && keyNames.every(Number.isInteger)) {
|
|
1277
|
+
keyIdList = keyNames;
|
|
1278
|
+
} else if (keyNames.length > 0) {
|
|
1279
|
+
keyFilter = keyNames.map(GARClient.escapeRegex).join('|');
|
|
1280
|
+
}
|
|
1239
1281
|
|
|
1240
1282
|
// Convert topicName to array
|
|
1241
1283
|
let topicNames = [];
|
|
@@ -1817,6 +1859,16 @@
|
|
|
1817
1859
|
});
|
|
1818
1860
|
}
|
|
1819
1861
|
|
|
1862
|
+
/**
|
|
1863
|
+
* Escape TRS-regex metacharacters so `name` matches literally (TRS regexes match the
|
|
1864
|
+
* whole string, so an escaped name is an exact-match alternative in a key_filter).
|
|
1865
|
+
* @param {string} name
|
|
1866
|
+
* @returns {string}
|
|
1867
|
+
*/
|
|
1868
|
+
static escapeRegex(name) {
|
|
1869
|
+
return String(name).replace(/[\\.^$|?*+()[\]{}]/g, '\\$&');
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1820
1872
|
static _generateUUID() {
|
|
1821
1873
|
const bytes = new Uint8Array(16);
|
|
1822
1874
|
if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues)
|
package/gar.js
CHANGED
|
@@ -845,6 +845,31 @@ class GARClient {
|
|
|
845
845
|
*/
|
|
846
846
|
sendMessage(message) {
|
|
847
847
|
this.messageQueue.push(message);
|
|
848
|
+
// Drain synchronously when possible: background tabs throttle timers — the polling
|
|
849
|
+
// drain loop (and any setInterval sender) can stretch from 100ms to minutes — but
|
|
850
|
+
// synchronous sends from an event or enqueue are never throttled.
|
|
851
|
+
this._flushQueue();
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Synchronously send everything sendable. Safe alongside the polling loop (same JS
|
|
856
|
+
* thread); leaves the null shutdown sentinel for the loop's exit handling.
|
|
857
|
+
*/
|
|
858
|
+
_flushQueue() {
|
|
859
|
+
while (this.connected && this.messageQueue.length > 0 && this._isSocketOpen()) {
|
|
860
|
+
if (this.messageQueue[0] === null) return;
|
|
861
|
+
const message = this.messageQueue.shift();
|
|
862
|
+
try {
|
|
863
|
+
this.websocket.send(JSON.stringify(message));
|
|
864
|
+
if (this.logEveryMessage) {
|
|
865
|
+
this.log('INFO', `Sent: ${JSON.stringify(message)}`);
|
|
866
|
+
}
|
|
867
|
+
} catch (e) {
|
|
868
|
+
this.log('WARNING', `Error sending message: ${e.message}`);
|
|
869
|
+
this.halt();
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
848
873
|
}
|
|
849
874
|
|
|
850
875
|
/**
|
|
@@ -972,6 +997,13 @@ class GARClient {
|
|
|
972
997
|
this.invalidatedKeyIds.add(message.value.client_introduced_key_id);
|
|
973
998
|
} else if (msgType === 'Heartbeat') {
|
|
974
999
|
this.lastHeartbeatTime = Date.now() / 1000;
|
|
1000
|
+
// Echo our own heartbeat on the server's cadence. A hidden tab's throttled timers
|
|
1001
|
+
// can silence the periodic sender far past the server's timeout while this
|
|
1002
|
+
// (unthrottled) receive path keeps delivering — the server then rightly declares
|
|
1003
|
+
// us dead. Event-driven liveness: receive -> reply, flushed synchronously above.
|
|
1004
|
+
if (this.running) {
|
|
1005
|
+
this.sendMessage({ message_type: 'Heartbeat', value: { u_milliseconds: Date.now() } });
|
|
1006
|
+
}
|
|
975
1007
|
if (this._initialGracePeriod) {
|
|
976
1008
|
// Resolve first-heartbeat waiters and end the initial grace window
|
|
977
1009
|
if (this._firstHeartbeatResolve) {
|
|
@@ -1140,7 +1172,9 @@ class GARClient {
|
|
|
1140
1172
|
* @param {Object} opts - Named subscription options (fields below).
|
|
1141
1173
|
* @param {string} opts.name - Subscription name
|
|
1142
1174
|
* @param {string} [opts.subscriptionMode='Streaming'] - Subscription mode
|
|
1143
|
-
* @param {string|Array<string>|null} [keyName=null] - Key name(s)
|
|
1175
|
+
* @param {string|Array<string>|Array<number>|null} [keyName=null] - Key name(s), matched
|
|
1176
|
+
* exactly via a synthesized key_filter alternation (keys are NOT created by
|
|
1177
|
+
* subscribing); an array of integers passes through as the wire key_id_list
|
|
1144
1178
|
* @param {string|Array<string>|null} [topicName=null] - Topic name(s)
|
|
1145
1179
|
* @param {string|Array<string>|null} [className=null] - Class name(s)
|
|
1146
1180
|
* @param {string|null} [keyFilter=null] - Key filter regex (cannot use with keyName)
|
|
@@ -1219,16 +1253,24 @@ class GARClient {
|
|
|
1219
1253
|
referencingClasses = referencingClassList;
|
|
1220
1254
|
}
|
|
1221
1255
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
//
|
|
1256
|
+
// Convert keyName to array. Subscribing must NOT create keys: names are no longer
|
|
1257
|
+
// introduced to build a key_id_list (the old behavior materialized every named key on
|
|
1258
|
+
// the server). Names become an exact-match alternation key_filter instead — the server
|
|
1259
|
+
// recognizes the literal-alternation shape and builds a trie, skipping the full
|
|
1260
|
+
// regex->DFA compilation. An array of integers passes through as key_id_list verbatim
|
|
1261
|
+
// (the caller already holds real key ids).
|
|
1225
1262
|
let keyNames = [];
|
|
1226
1263
|
if (typeof keyName === 'string') {
|
|
1227
1264
|
keyNames = [keyName];
|
|
1228
1265
|
} else if (Array.isArray(keyName)) {
|
|
1229
1266
|
keyNames = keyName;
|
|
1230
1267
|
}
|
|
1231
|
-
|
|
1268
|
+
let keyIdList = [];
|
|
1269
|
+
if (keyNames.length > 0 && keyNames.every(Number.isInteger)) {
|
|
1270
|
+
keyIdList = keyNames;
|
|
1271
|
+
} else if (keyNames.length > 0) {
|
|
1272
|
+
keyFilter = keyNames.map(GARClient.escapeRegex).join('|');
|
|
1273
|
+
}
|
|
1232
1274
|
|
|
1233
1275
|
// Convert topicName to array
|
|
1234
1276
|
let topicNames = [];
|
|
@@ -1810,6 +1852,16 @@ class GARClient {
|
|
|
1810
1852
|
});
|
|
1811
1853
|
}
|
|
1812
1854
|
|
|
1855
|
+
/**
|
|
1856
|
+
* Escape TRS-regex metacharacters so `name` matches literally (TRS regexes match the
|
|
1857
|
+
* whole string, so an escaped name is an exact-match alternative in a key_filter).
|
|
1858
|
+
* @param {string} name
|
|
1859
|
+
* @returns {string}
|
|
1860
|
+
*/
|
|
1861
|
+
static escapeRegex(name) {
|
|
1862
|
+
return String(name).replace(/[\\.^$|?*+()[\]{}]/g, '\\$&');
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1813
1865
|
static _generateUUID() {
|
|
1814
1866
|
const bytes = new Uint8Array(16);
|
|
1815
1867
|
if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsgar",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.3",
|
|
4
4
|
"description": "A Javascript client for the GAR protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/gar.umd.js",
|
|
@@ -36,6 +36,8 @@
|
|
|
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
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
40
|
+
"@xterm/xterm": "^5.5.0",
|
|
39
41
|
"ag-grid-community": "^34.1.1",
|
|
40
42
|
"ag-grid-enterprise": "^34.1.1",
|
|
41
43
|
"eslint": "^9.39.4",
|