aes70 2.0.15 → 2.0.17

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/AES70.es5.js CHANGED
@@ -939,6 +939,14 @@
939
939
  };
940
940
  }
941
941
 
942
+ get bufferedAmount() {
943
+ return this._currentSize;
944
+ }
945
+
946
+ get batchSize() {
947
+ return this._batchSize;
948
+ }
949
+
942
950
  add(pdu) {
943
951
  const currentSize = this._currentSize;
944
952
  const encodedLength = pdu.encoded_length();
@@ -971,7 +979,7 @@
971
979
  /* Keepalive packets are never combined into one message. */
972
980
  this._lastMessageType = messageType;
973
981
 
974
- if (this._currentSize > this._batchSize) {
982
+ if (this._currentSize + additionalSize > this._batchSize) {
975
983
  this.flush();
976
984
  } else if (this._pdus.length === 1) {
977
985
  this.scheduleFlush();
@@ -1036,6 +1044,130 @@
1036
1044
  }
1037
1045
  }
1038
1046
 
1047
+ function isItTime(target, now) {
1048
+ // We are ok with 1ms accuracy.
1049
+ return target - now < 1;
1050
+ }
1051
+
1052
+ class Timer {
1053
+ constructor(callback, getNow) {
1054
+ this._callback = callback;
1055
+ this._getNow = getNow;
1056
+ this._targetTime = undefined;
1057
+ this._timerId = undefined;
1058
+ this._timerAt = undefined;
1059
+ }
1060
+
1061
+ poll() {
1062
+ const now = this._getNow();
1063
+
1064
+ if (this._targetTime === undefined) return;
1065
+
1066
+ if (isItTime(this._targetTime, now)) {
1067
+ this._targetTime = undefined;
1068
+ try {
1069
+ this._callback();
1070
+ } catch (err) {
1071
+ console.error('Timer callback threw an exception', err);
1072
+ }
1073
+ } else {
1074
+ this._reschedule();
1075
+ }
1076
+ }
1077
+
1078
+ _reschedule() {
1079
+ const target = this._targetTime;
1080
+ const interval = target - this._getNow();
1081
+
1082
+ if (this._timerId !== undefined) {
1083
+ if (target >= this._timerAt) {
1084
+ // The timer will fire before target. We will then reschedule it.
1085
+ return;
1086
+ }
1087
+
1088
+ clearTimeout(this._timerId);
1089
+ this._timerId = undefined;
1090
+ }
1091
+
1092
+ this._timerAt = target;
1093
+ this._timerId = setTimeout(() => {
1094
+ this._timerId = undefined;
1095
+ this._timerAt = undefined;
1096
+ this.poll();
1097
+ }, Math.max(0, interval));
1098
+ }
1099
+
1100
+ /**
1101
+ *
1102
+ * @param {number} interval
1103
+ * Interval in milliseconds.
1104
+ */
1105
+ scheduleIn(interval) {
1106
+ if (!(interval >= 0)) {
1107
+ throw new TypeError(`Expected positive interval.`);
1108
+ }
1109
+
1110
+ this._targetTime = this._getNow() + interval;
1111
+ this._reschedule();
1112
+ }
1113
+
1114
+ /**
1115
+ * Schedule the timer in a given number of milliseconds. If the timer
1116
+ * is already running and scheduled to run before, do not modify it.
1117
+ *
1118
+ * @param {number} interval
1119
+ */
1120
+ scheduleDeadlineIn(interval) {
1121
+ if (!(interval >= 0)) {
1122
+ throw new TypeError(`Expected positive interval.`);
1123
+ }
1124
+
1125
+ const target = this._getNow() + interval;
1126
+
1127
+ if (this._targetTime !== undefined && this._targetTime <= target) {
1128
+ this.poll();
1129
+ return;
1130
+ }
1131
+
1132
+ this.scheduleAt(target);
1133
+ }
1134
+
1135
+ /**
1136
+ *
1137
+ * @param {number} target
1138
+ * Target time in milliseconds.
1139
+ */
1140
+ scheduleAt(target) {
1141
+ if (!(target >= 0)) {
1142
+ throw new TypeError();
1143
+ }
1144
+
1145
+ this._targetTime = target;
1146
+ this._reschedule();
1147
+ }
1148
+
1149
+ stop() {
1150
+ this._targetTime = undefined;
1151
+ }
1152
+
1153
+ cancel() {
1154
+ this.stop();
1155
+ this._clearTimeout();
1156
+ }
1157
+
1158
+ _clearTimeout() {
1159
+ if (this._timerId) {
1160
+ clearTimeout(this._timerId);
1161
+ this._timerId = undefined;
1162
+ this._timerAt = undefined;
1163
+ }
1164
+ }
1165
+
1166
+ dispose() {
1167
+ this.cancel();
1168
+ }
1169
+ }
1170
+
1039
1171
  /**
1040
1172
  * Connection base class. It extends :class:`Events` and defines two events:
1041
1173
  *
@@ -1072,8 +1204,11 @@
1072
1204
  this.last_tx_time = now;
1073
1205
  this.rx_bytes = 0;
1074
1206
  this.tx_bytes = 0;
1207
+ this._keepalive_timer = new Timer(
1208
+ () => this._check_keepalive(),
1209
+ () => this._now()
1210
+ );
1075
1211
  this.keepalive_interval = -1;
1076
- this._keepalive_interval_id = null;
1077
1212
  this._closed = false;
1078
1213
  this.on('close', () => {
1079
1214
  if (this._closed) return;
@@ -1092,6 +1227,18 @@
1092
1227
  return true;
1093
1228
  }
1094
1229
 
1230
+ get bufferedAmount() {
1231
+ return this._message_generator.bufferedAmount;
1232
+ }
1233
+
1234
+ get batchSize() {
1235
+ return this._message_generator.batchSize;
1236
+ }
1237
+
1238
+ get pendingWrites() {
1239
+ return this._message_generator.bufferedAmount > 0 ? 1 : 0;
1240
+ }
1241
+
1095
1242
  send(pdu) {
1096
1243
  if (this.is_closed()) throw new Error('Connection is closed.');
1097
1244
  this.emit('send', pdu);
@@ -1149,7 +1296,7 @@
1149
1296
  }
1150
1297
  }
1151
1298
 
1152
- this._check_keepalive();
1299
+ this.poll();
1153
1300
  }
1154
1301
 
1155
1302
  incoming(a) {}
@@ -1180,16 +1327,18 @@
1180
1327
  if (this.is_closed()) throw new Error('cleanup() called twice.');
1181
1328
 
1182
1329
  // disable keepalive
1183
- this.set_keepalive_interval(0);
1330
+ this._keepalive_timer.dispose();
1184
1331
  this._message_generator.dispose();
1185
1332
  this._message_generator = null;
1186
1333
  this.removeAllEventListeners();
1187
1334
  }
1188
1335
 
1189
1336
  _check_keepalive() {
1190
- if (!(this.keepalive_interval > 0)) return;
1191
-
1337
+ if (this.is_closed()) return;
1192
1338
  const t = this.keepalive_interval;
1339
+ if (!(t > 0)) return;
1340
+
1341
+ this._keepalive_timer.scheduleIn(t / 2 + 10);
1193
1342
 
1194
1343
  if (this.rx_idle_time() > t * 3) {
1195
1344
  this.emit('timeout');
@@ -1201,6 +1350,13 @@
1201
1350
  }
1202
1351
  }
1203
1352
 
1353
+ /**
1354
+ * Check if some regular internal timers must run.
1355
+ */
1356
+ poll() {
1357
+ this._keepalive_timer.poll();
1358
+ }
1359
+
1204
1360
  /**
1205
1361
  * Flush write buffers. This are usually PDUs or may also be unwritten
1206
1362
  * buffers.
@@ -1226,11 +1382,6 @@
1226
1382
 
1227
1383
  const t = seconds * 1000;
1228
1384
 
1229
- if (this._keepalive_interval_id !== null) {
1230
- clearInterval(this._keepalive_interval_id);
1231
- this._keepalive_interval_id = null;
1232
- }
1233
-
1234
1385
  this.keepalive_interval = t;
1235
1386
 
1236
1387
  // Notify the other side about our new keepalive
@@ -1238,12 +1389,12 @@
1238
1389
 
1239
1390
  this.send(new KeepAlive(t));
1240
1391
 
1241
- if (!(t > 0)) return;
1242
-
1243
- // we check twice as often to make sure we stay within the timers
1244
- this._keepalive_interval_id = setInterval(() => {
1245
- this._check_keepalive();
1246
- }, t / 2);
1392
+ if (t > 0) {
1393
+ // we check twice as often to make sure we stay within the timers
1394
+ this._keepalive_timer.scheduleIn(t / 2 + 10);
1395
+ } else {
1396
+ this._keepalive_timer.stop();
1397
+ }
1247
1398
  }
1248
1399
  }
1249
1400
 
@@ -1729,17 +1880,57 @@
1729
1880
  class ClientConnection extends Connection {
1730
1881
  constructor(options) {
1731
1882
  super(options);
1883
+ // All pending commands by id/handle
1732
1884
  this._pendingCommands = new Map();
1885
+ // All pending commands scheduled to be sent.
1886
+ this._scheduledPendingCommands = new Set();
1887
+ // All pending commands wich have been sent.
1888
+ this._sentPendingCommands = new Set();
1733
1889
  this._nextCommandHandle = 0;
1734
1890
  this._subscribers = new Map();
1891
+ this._sendCommandsTimer = new Timer(
1892
+ () => {
1893
+ this.sendCommands();
1894
+ },
1895
+ () => this._now()
1896
+ );
1897
+ }
1898
+
1899
+ shouldSendMoreCommands() {
1900
+ return this.is_reliable;
1901
+ }
1902
+
1903
+ sendCommands() {
1904
+ const { _scheduledPendingCommands, _sentPendingCommands } = this;
1905
+
1906
+ for (const pendingCommand of _scheduledPendingCommands) {
1907
+ if (!this.shouldSendMoreCommands()) break;
1908
+ _scheduledPendingCommands.delete(pendingCommand);
1909
+ _sentPendingCommands.add(pendingCommand);
1910
+ this.send(pendingCommand.command);
1911
+ pendingCommand.lastSent = this._now();
1912
+ pendingCommand.retries++;
1913
+ }
1914
+ }
1915
+
1916
+ scheduleSendCommands() {
1917
+ this._sendCommandsTimer.scheduleDeadlineIn(5);
1918
+ }
1919
+
1920
+ poll() {
1921
+ super.poll();
1922
+ this._sendCommandsTimer.poll();
1735
1923
  }
1736
1924
 
1737
1925
  cleanup(error) {
1738
1926
  super.cleanup(error);
1927
+ this._sendCommandsTimer.dispose();
1739
1928
  const subscribers = this._subscribers;
1740
1929
  this._subscribers = null;
1741
1930
  const pendingCommands = this._pendingCommands;
1742
1931
  this._pendingCommands = null;
1932
+ this._scheduledPendingCommands.clear();
1933
+ this._sentPendingCommands.clear();
1743
1934
 
1744
1935
  const e = new CloseError(error);
1745
1936
  pendingCommands.forEach((pendingCommand, id) => {
@@ -1818,9 +2009,8 @@
1818
2009
  );
1819
2010
 
1820
2011
  this._pendingCommands.set(handle, pendingCommand);
1821
-
1822
- pendingCommand.lastSent = this._estimate_next_tx_time();
1823
- this.send(command);
2012
+ this._scheduledPendingCommands.add(pendingCommand);
2013
+ this.scheduleSendCommands();
1824
2014
  };
1825
2015
 
1826
2016
  if (callback) {
@@ -1841,6 +2031,9 @@
1841
2031
 
1842
2032
  pendingCommands.delete(handle);
1843
2033
 
2034
+ if (!this._sentPendingCommands.delete(pendingCommand))
2035
+ this._scheduledPendingCommands.delete(pendingCommand);
2036
+
1844
2037
  return pendingCommand;
1845
2038
  }
1846
2039
 
@@ -28806,15 +28999,20 @@
28806
28999
  this.retry_interval =
28807
29000
  options.retry_interval >= 0 ? options.retry_interval : 250;
28808
29001
  this.retry_count = options.retry_count >= 0 ? options.retry_count : 3;
28809
- this._write_out_id = -1;
28810
- this._write_out_callback = () => {
28811
- this._write_out_id = -1;
28812
- this._write_out();
28813
- };
28814
- this._retry_id =
28815
- this.retry_interval > 0
28816
- ? setInterval(() => this._retryCommands(), this.retry_interval)
28817
- : -1;
29002
+ this._write_out_timer = new Timer(
29003
+ () => {
29004
+ this._write_out();
29005
+ },
29006
+ () => this._now()
29007
+ );
29008
+ this._retry_timer = new Timer(
29009
+ () => {
29010
+ this._retryCommands();
29011
+ this._retry_timer.scheduleIn(this.retry_interval);
29012
+ },
29013
+ () => this._now()
29014
+ );
29015
+ this._retry_timer.scheduleIn(this.retry_interval);
28818
29016
  this.q = [];
28819
29017
  socket.onmessage = (buffer) => {
28820
29018
  try {
@@ -28832,6 +29030,24 @@
28832
29030
  return false;
28833
29031
  }
28834
29032
 
29033
+ get bufferedAmount() {
29034
+ let amount = super.bufferedAmount;
29035
+
29036
+ for (const buf of this.q) {
29037
+ amount += buf.byteLength;
29038
+ }
29039
+
29040
+ return amount;
29041
+ }
29042
+
29043
+ get pendingWrites() {
29044
+ return super.pendingWrites + this.q.length;
29045
+ }
29046
+
29047
+ shouldSendMoreCommands() {
29048
+ return this.q.length < 3;
29049
+ }
29050
+
28835
29051
  /**
28836
29052
  * Connect to the given endpoint.
28837
29053
  *
@@ -28901,14 +29117,8 @@
28901
29117
  this.socket.close();
28902
29118
  this.socket = null;
28903
29119
  }
28904
- if (this._write_out_id !== -1) {
28905
- clearTimeout(this._write_out_id);
28906
- this._write_out_id = -1;
28907
- }
28908
- if (this._retry_id !== -1) {
28909
- clearInterval(this._retry_id);
28910
- this._retry_id = -1;
28911
- }
29120
+ this._write_out_timer.dispose();
29121
+ this._retry_timer.dispose();
28912
29122
  }
28913
29123
 
28914
29124
  _estimate_next_tx_time() {
@@ -28927,6 +29137,13 @@
28927
29137
  super.write(buf);
28928
29138
 
28929
29139
  if (q.length) this._schedule_write_out();
29140
+ this.scheduleSendCommands();
29141
+ }
29142
+
29143
+ poll() {
29144
+ super.poll();
29145
+ this._write_out_timer.poll();
29146
+ this._retry_timer.poll();
28930
29147
  }
28931
29148
 
28932
29149
  _schedule_write_out() {
@@ -28938,54 +29155,45 @@
28938
29155
  return;
28939
29156
  }
28940
29157
 
28941
- // Already scheduled.
28942
- if (this._write_out_id !== -1) return;
28943
-
28944
- this._write_out_id = setTimeout(
28945
- this._write_out_callback,
28946
- delay - tx_idle_time
28947
- );
29158
+ this._write_out_timer.scheduleIn(delay - tx_idle_time);
28948
29159
  }
28949
29160
 
28950
29161
  _retryCommands() {
28951
29162
  const now = this._now();
28952
29163
  const retryTime = now - this.retry_interval;
28953
- // This is an estimate for how many commands we would manage to send
28954
- // off.
28955
- const max = 5 * (this.retry_interval / this.delay) - this.q.length;
28956
29164
  const pendingCommands = this._pendingCommands;
29165
+ const _sentPendingCommands = this._sentPendingCommands;
29166
+ const _scheduledPendingCommands = this._scheduledPendingCommands;
28957
29167
 
28958
- const retries = [];
29168
+ let scheduledCount = 0;
28959
29169
  const failed = [];
28960
29170
 
28961
- for (const entry of pendingCommands) {
28962
- const [, pendingCommand] = entry;
28963
-
28964
- // All later commands are newer than the cutoff.
29171
+ for (const pendingCommand of _sentPendingCommands) {
28965
29172
  if (pendingCommand.lastSent > retryTime) break;
29173
+
29174
+ _sentPendingCommands.delete(pendingCommand);
29175
+
28966
29176
  if (pendingCommand.retries >= this.retry_count) {
28967
- failed.push(entry);
28968
- } else if (retries.length < max) {
28969
- retries.push(entry);
29177
+ failed.push(pendingCommand);
29178
+ } else {
29179
+ _scheduledPendingCommands.add(pendingCommand);
29180
+ scheduledCount++;
28970
29181
  }
28971
29182
  }
28972
29183
 
29184
+ if (scheduledCount) {
29185
+ this.scheduleSendCommands();
29186
+ }
29187
+
28973
29188
  if (failed.length) {
28974
29189
  const timeoutError = new Error('Timeout.');
28975
29190
 
28976
- failed.forEach(([handle, pendingCommand]) => {
29191
+ failed.forEach((pendingCommand) => {
29192
+ const handle = pendingCommand.handle;
28977
29193
  pendingCommands.delete(handle);
28978
29194
  pendingCommand.reject(timeoutError);
28979
29195
  });
28980
29196
  }
28981
-
28982
- retries.forEach(([handle, pendingCommand]) => {
28983
- pendingCommands.delete(handle);
28984
- pendingCommands.set(handle, pendingCommand);
28985
- this.send(pendingCommand.command);
28986
- pendingCommand.lastSent = now;
28987
- pendingCommand.retries++;
28988
- });
28989
29197
  }
28990
29198
  }
28991
29199
 
@@ -29114,6 +29322,10 @@
29114
29322
  }
29115
29323
  return value;
29116
29324
  }
29325
+ } else if (typeof value === 'bigint') {
29326
+ return value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER
29327
+ ? Number(value)
29328
+ : value.toString();
29117
29329
  } else {
29118
29330
  return value;
29119
29331
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aes70",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
4
4
  "description": "A controller library for the AES70 protocol.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -19,6 +19,14 @@ export class MessageGenerator {
19
19
  };
20
20
  }
21
21
 
22
+ get bufferedAmount() {
23
+ return this._currentSize;
24
+ }
25
+
26
+ get batchSize() {
27
+ return this._batchSize;
28
+ }
29
+
22
30
  add(pdu) {
23
31
  const currentSize = this._currentSize;
24
32
  const encodedLength = pdu.encoded_length();
@@ -51,7 +59,7 @@ export class MessageGenerator {
51
59
  /* Keepalive packets are never combined into one message. */
52
60
  this._lastMessageType = messageType;
53
61
 
54
- if (this._currentSize > this._batchSize) {
62
+ if (this._currentSize + additionalSize > this._batchSize) {
55
63
  this.flush();
56
64
  } else if (this._pdus.length === 1) {
57
65
  this.scheduleFlush();
@@ -56,6 +56,23 @@ export declare class Connection extends Events {
56
56
  */
57
57
  get rx_idle_time(): number;
58
58
 
59
+ /**
60
+ * Amount of bytes currently buffered to be sent out.
61
+ */
62
+ get bufferedAmount(): number;
63
+
64
+ /**
65
+ * Amount of bytes which will be batched into single
66
+ * aes70 messages. This can be configured using the
67
+ * `batch` option when creating a connection.
68
+ */
69
+ get batchSize(): number;
70
+
71
+ /**
72
+ * The number of currently pending write operations.
73
+ */
74
+ get pendingWrites(): number;
75
+
59
76
  /**
60
77
  * Closes this connection.
61
78
  */
package/src/connection.js CHANGED
@@ -3,6 +3,7 @@ import { decodeMessage } from './OCP1/decode_message.js';
3
3
  import { KeepAlive } from './OCP1/keepalive.js';
4
4
  import { MessageGenerator } from './OCP1/message_generator.js';
5
5
  import { TimeoutError } from './timeout_error.js';
6
+ import { Timer } from './utils/timer.js';
6
7
 
7
8
  /**
8
9
  * Connection base class. It extends :class:`Events` and defines two events:
@@ -40,8 +41,11 @@ export class Connection extends Events {
40
41
  this.last_tx_time = now;
41
42
  this.rx_bytes = 0;
42
43
  this.tx_bytes = 0;
44
+ this._keepalive_timer = new Timer(
45
+ () => this._check_keepalive(),
46
+ () => this._now()
47
+ );
43
48
  this.keepalive_interval = -1;
44
- this._keepalive_interval_id = null;
45
49
  this._closed = false;
46
50
  this.on('close', () => {
47
51
  if (this._closed) return;
@@ -60,6 +64,18 @@ export class Connection extends Events {
60
64
  return true;
61
65
  }
62
66
 
67
+ get bufferedAmount() {
68
+ return this._message_generator.bufferedAmount;
69
+ }
70
+
71
+ get batchSize() {
72
+ return this._message_generator.batchSize;
73
+ }
74
+
75
+ get pendingWrites() {
76
+ return this._message_generator.bufferedAmount > 0 ? 1 : 0;
77
+ }
78
+
63
79
  send(pdu) {
64
80
  if (this.is_closed()) throw new Error('Connection is closed.');
65
81
  this.emit('send', pdu);
@@ -117,7 +133,7 @@ export class Connection extends Events {
117
133
  }
118
134
  }
119
135
 
120
- this._check_keepalive();
136
+ this.poll();
121
137
  }
122
138
 
123
139
  incoming(a) {}
@@ -148,16 +164,18 @@ export class Connection extends Events {
148
164
  if (this.is_closed()) throw new Error('cleanup() called twice.');
149
165
 
150
166
  // disable keepalive
151
- this.set_keepalive_interval(0);
167
+ this._keepalive_timer.dispose();
152
168
  this._message_generator.dispose();
153
169
  this._message_generator = null;
154
170
  this.removeAllEventListeners();
155
171
  }
156
172
 
157
173
  _check_keepalive() {
158
- if (!(this.keepalive_interval > 0)) return;
159
-
174
+ if (this.is_closed()) return;
160
175
  const t = this.keepalive_interval;
176
+ if (!(t > 0)) return;
177
+
178
+ this._keepalive_timer.scheduleIn(t / 2 + 10);
161
179
 
162
180
  if (this.rx_idle_time() > t * 3) {
163
181
  this.emit('timeout');
@@ -169,6 +187,13 @@ export class Connection extends Events {
169
187
  }
170
188
  }
171
189
 
190
+ /**
191
+ * Check if some regular internal timers must run.
192
+ */
193
+ poll() {
194
+ this._keepalive_timer.poll();
195
+ }
196
+
172
197
  /**
173
198
  * Flush write buffers. This are usually PDUs or may also be unwritten
174
199
  * buffers.
@@ -194,11 +219,6 @@ export class Connection extends Events {
194
219
 
195
220
  const t = seconds * 1000;
196
221
 
197
- if (this._keepalive_interval_id !== null) {
198
- clearInterval(this._keepalive_interval_id);
199
- this._keepalive_interval_id = null;
200
- }
201
-
202
222
  this.keepalive_interval = t;
203
223
 
204
224
  // Notify the other side about our new keepalive
@@ -206,11 +226,11 @@ export class Connection extends Events {
206
226
 
207
227
  this.send(new KeepAlive(t));
208
228
 
209
- if (!(t > 0)) return;
210
-
211
- // we check twice as often to make sure we stay within the timers
212
- this._keepalive_interval_id = setInterval(() => {
213
- this._check_keepalive();
214
- }, t / 2);
229
+ if (t > 0) {
230
+ // we check twice as often to make sure we stay within the timers
231
+ this._keepalive_timer.scheduleIn(t / 2 + 10);
232
+ } else {
233
+ this._keepalive_timer.stop();
234
+ }
215
235
  }
216
236
  }
@@ -1,4 +1,5 @@
1
1
  /* eslint-env node */
2
+ import { Timer } from '../utils/timer.js';
2
3
  import { ClientConnection } from './client_connection.js';
3
4
 
4
5
  /**
@@ -17,15 +18,20 @@ export class AbstractUDPConnection extends ClientConnection {
17
18
  this.retry_interval =
18
19
  options.retry_interval >= 0 ? options.retry_interval : 250;
19
20
  this.retry_count = options.retry_count >= 0 ? options.retry_count : 3;
20
- this._write_out_id = -1;
21
- this._write_out_callback = () => {
22
- this._write_out_id = -1;
23
- this._write_out();
24
- };
25
- this._retry_id =
26
- this.retry_interval > 0
27
- ? setInterval(() => this._retryCommands(), this.retry_interval)
28
- : -1;
21
+ this._write_out_timer = new Timer(
22
+ () => {
23
+ this._write_out();
24
+ },
25
+ () => this._now()
26
+ );
27
+ this._retry_timer = new Timer(
28
+ () => {
29
+ this._retryCommands();
30
+ this._retry_timer.scheduleIn(this.retry_interval);
31
+ },
32
+ () => this._now()
33
+ );
34
+ this._retry_timer.scheduleIn(this.retry_interval);
29
35
  this.q = [];
30
36
  socket.onmessage = (buffer) => {
31
37
  try {
@@ -43,6 +49,24 @@ export class AbstractUDPConnection extends ClientConnection {
43
49
  return false;
44
50
  }
45
51
 
52
+ get bufferedAmount() {
53
+ let amount = super.bufferedAmount;
54
+
55
+ for (const buf of this.q) {
56
+ amount += buf.byteLength;
57
+ }
58
+
59
+ return amount;
60
+ }
61
+
62
+ get pendingWrites() {
63
+ return super.pendingWrites + this.q.length;
64
+ }
65
+
66
+ shouldSendMoreCommands() {
67
+ return this.q.length < 3;
68
+ }
69
+
46
70
  /**
47
71
  * Connect to the given endpoint.
48
72
  *
@@ -112,14 +136,8 @@ export class AbstractUDPConnection extends ClientConnection {
112
136
  this.socket.close();
113
137
  this.socket = null;
114
138
  }
115
- if (this._write_out_id !== -1) {
116
- clearTimeout(this._write_out_id);
117
- this._write_out_id = -1;
118
- }
119
- if (this._retry_id !== -1) {
120
- clearInterval(this._retry_id);
121
- this._retry_id = -1;
122
- }
139
+ this._write_out_timer.dispose();
140
+ this._retry_timer.dispose();
123
141
  }
124
142
 
125
143
  _estimate_next_tx_time() {
@@ -138,6 +156,13 @@ export class AbstractUDPConnection extends ClientConnection {
138
156
  super.write(buf);
139
157
 
140
158
  if (q.length) this._schedule_write_out();
159
+ this.scheduleSendCommands();
160
+ }
161
+
162
+ poll() {
163
+ super.poll();
164
+ this._write_out_timer.poll();
165
+ this._retry_timer.poll();
141
166
  }
142
167
 
143
168
  _schedule_write_out() {
@@ -149,53 +174,44 @@ export class AbstractUDPConnection extends ClientConnection {
149
174
  return;
150
175
  }
151
176
 
152
- // Already scheduled.
153
- if (this._write_out_id !== -1) return;
154
-
155
- this._write_out_id = setTimeout(
156
- this._write_out_callback,
157
- delay - tx_idle_time
158
- );
177
+ this._write_out_timer.scheduleIn(delay - tx_idle_time);
159
178
  }
160
179
 
161
180
  _retryCommands() {
162
181
  const now = this._now();
163
182
  const retryTime = now - this.retry_interval;
164
- // This is an estimate for how many commands we would manage to send
165
- // off.
166
- const max = 5 * (this.retry_interval / this.delay) - this.q.length;
167
183
  const pendingCommands = this._pendingCommands;
184
+ const _sentPendingCommands = this._sentPendingCommands;
185
+ const _scheduledPendingCommands = this._scheduledPendingCommands;
168
186
 
169
- const retries = [];
187
+ let scheduledCount = 0;
170
188
  const failed = [];
171
189
 
172
- for (const entry of pendingCommands) {
173
- const [, pendingCommand] = entry;
174
-
175
- // All later commands are newer than the cutoff.
190
+ for (const pendingCommand of _sentPendingCommands) {
176
191
  if (pendingCommand.lastSent > retryTime) break;
192
+
193
+ _sentPendingCommands.delete(pendingCommand);
194
+
177
195
  if (pendingCommand.retries >= this.retry_count) {
178
- failed.push(entry);
179
- } else if (retries.length < max) {
180
- retries.push(entry);
196
+ failed.push(pendingCommand);
197
+ } else {
198
+ _scheduledPendingCommands.add(pendingCommand);
199
+ scheduledCount++;
181
200
  }
182
201
  }
183
202
 
203
+ if (scheduledCount) {
204
+ this.scheduleSendCommands();
205
+ }
206
+
184
207
  if (failed.length) {
185
208
  const timeoutError = new Error('Timeout.');
186
209
 
187
- failed.forEach(([handle, pendingCommand]) => {
210
+ failed.forEach((pendingCommand) => {
211
+ const handle = pendingCommand.handle;
188
212
  pendingCommands.delete(handle);
189
213
  pendingCommand.reject(timeoutError);
190
214
  });
191
215
  }
192
-
193
- retries.forEach(([handle, pendingCommand]) => {
194
- pendingCommands.delete(handle);
195
- pendingCommands.set(handle, pendingCommand);
196
- this.send(pendingCommand.command);
197
- pendingCommand.lastSent = now;
198
- pendingCommand.retries++;
199
- });
200
216
  }
201
217
  }
@@ -11,6 +11,7 @@ import { EncodedArguments } from '../OCP1/encoded_arguments.js';
11
11
  import { CloseError } from '../close_error.js';
12
12
  import { Subscriptions } from '../utils/subscriptions.js';
13
13
  import { subscribeEvent } from '../utils/subscribeEvent.js';
14
+ import { Timer } from '../utils/timer.js';
14
15
 
15
16
  class PendingCommand {
16
17
  get handle() {
@@ -90,17 +91,57 @@ function eventToKey(event) {
90
91
  export class ClientConnection extends Connection {
91
92
  constructor(options) {
92
93
  super(options);
94
+ // All pending commands by id/handle
93
95
  this._pendingCommands = new Map();
96
+ // All pending commands scheduled to be sent.
97
+ this._scheduledPendingCommands = new Set();
98
+ // All pending commands wich have been sent.
99
+ this._sentPendingCommands = new Set();
94
100
  this._nextCommandHandle = 0;
95
101
  this._subscribers = new Map();
102
+ this._sendCommandsTimer = new Timer(
103
+ () => {
104
+ this.sendCommands();
105
+ },
106
+ () => this._now()
107
+ );
108
+ }
109
+
110
+ shouldSendMoreCommands() {
111
+ return this.is_reliable;
112
+ }
113
+
114
+ sendCommands() {
115
+ const { _scheduledPendingCommands, _sentPendingCommands } = this;
116
+
117
+ for (const pendingCommand of _scheduledPendingCommands) {
118
+ if (!this.shouldSendMoreCommands()) break;
119
+ _scheduledPendingCommands.delete(pendingCommand);
120
+ _sentPendingCommands.add(pendingCommand);
121
+ this.send(pendingCommand.command);
122
+ pendingCommand.lastSent = this._now();
123
+ pendingCommand.retries++;
124
+ }
125
+ }
126
+
127
+ scheduleSendCommands() {
128
+ this._sendCommandsTimer.scheduleDeadlineIn(5);
129
+ }
130
+
131
+ poll() {
132
+ super.poll();
133
+ this._sendCommandsTimer.poll();
96
134
  }
97
135
 
98
136
  cleanup(error) {
99
137
  super.cleanup(error);
138
+ this._sendCommandsTimer.dispose();
100
139
  const subscribers = this._subscribers;
101
140
  this._subscribers = null;
102
141
  const pendingCommands = this._pendingCommands;
103
142
  this._pendingCommands = null;
143
+ this._scheduledPendingCommands.clear();
144
+ this._sentPendingCommands.clear();
104
145
 
105
146
  const e = new CloseError(error);
106
147
  pendingCommands.forEach((pendingCommand, id) => {
@@ -179,9 +220,8 @@ export class ClientConnection extends Connection {
179
220
  );
180
221
 
181
222
  this._pendingCommands.set(handle, pendingCommand);
182
-
183
- pendingCommand.lastSent = this._estimate_next_tx_time();
184
- this.send(command);
223
+ this._scheduledPendingCommands.add(pendingCommand);
224
+ this.scheduleSendCommands();
185
225
  };
186
226
 
187
227
  if (callback) {
@@ -202,6 +242,9 @@ export class ClientConnection extends Connection {
202
242
 
203
243
  pendingCommands.delete(handle);
204
244
 
245
+ if (!this._sentPendingCommands.delete(pendingCommand))
246
+ this._scheduledPendingCommands.delete(pendingCommand);
247
+
205
248
  return pendingCommand;
206
249
  }
207
250
 
@@ -13,6 +13,10 @@ function formatValue(value) {
13
13
  }
14
14
  return value;
15
15
  }
16
+ } else if (typeof value === 'bigint') {
17
+ return value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER
18
+ ? Number(value)
19
+ : value.toString();
16
20
  } else {
17
21
  return value;
18
22
  }
@@ -0,0 +1,123 @@
1
+ function isItTime(target, now) {
2
+ // We are ok with 1ms accuracy.
3
+ return target - now < 1;
4
+ }
5
+
6
+ export class Timer {
7
+ constructor(callback, getNow) {
8
+ this._callback = callback;
9
+ this._getNow = getNow;
10
+ this._targetTime = undefined;
11
+ this._timerId = undefined;
12
+ this._timerAt = undefined;
13
+ }
14
+
15
+ poll() {
16
+ const now = this._getNow();
17
+
18
+ if (this._targetTime === undefined) return;
19
+
20
+ if (isItTime(this._targetTime, now)) {
21
+ this._targetTime = undefined;
22
+ try {
23
+ this._callback();
24
+ } catch (err) {
25
+ console.error('Timer callback threw an exception', err);
26
+ }
27
+ } else {
28
+ this._reschedule();
29
+ }
30
+ }
31
+
32
+ _reschedule() {
33
+ const target = this._targetTime;
34
+ const interval = target - this._getNow();
35
+
36
+ if (this._timerId !== undefined) {
37
+ if (target >= this._timerAt) {
38
+ // The timer will fire before target. We will then reschedule it.
39
+ return;
40
+ }
41
+
42
+ clearTimeout(this._timerId);
43
+ this._timerId = undefined;
44
+ }
45
+
46
+ this._timerAt = target;
47
+ this._timerId = setTimeout(() => {
48
+ this._timerId = undefined;
49
+ this._timerAt = undefined;
50
+ this.poll();
51
+ }, Math.max(0, interval));
52
+ }
53
+
54
+ /**
55
+ *
56
+ * @param {number} interval
57
+ * Interval in milliseconds.
58
+ */
59
+ scheduleIn(interval) {
60
+ if (!(interval >= 0)) {
61
+ throw new TypeError(`Expected positive interval.`);
62
+ }
63
+
64
+ this._targetTime = this._getNow() + interval;
65
+ this._reschedule();
66
+ }
67
+
68
+ /**
69
+ * Schedule the timer in a given number of milliseconds. If the timer
70
+ * is already running and scheduled to run before, do not modify it.
71
+ *
72
+ * @param {number} interval
73
+ */
74
+ scheduleDeadlineIn(interval) {
75
+ if (!(interval >= 0)) {
76
+ throw new TypeError(`Expected positive interval.`);
77
+ }
78
+
79
+ const target = this._getNow() + interval;
80
+
81
+ if (this._targetTime !== undefined && this._targetTime <= target) {
82
+ this.poll();
83
+ return;
84
+ }
85
+
86
+ this.scheduleAt(target);
87
+ }
88
+
89
+ /**
90
+ *
91
+ * @param {number} target
92
+ * Target time in milliseconds.
93
+ */
94
+ scheduleAt(target) {
95
+ if (!(target >= 0)) {
96
+ throw new TypeError();
97
+ }
98
+
99
+ this._targetTime = target;
100
+ this._reschedule();
101
+ }
102
+
103
+ stop() {
104
+ this._targetTime = undefined;
105
+ }
106
+
107
+ cancel() {
108
+ this.stop();
109
+ this._clearTimeout();
110
+ }
111
+
112
+ _clearTimeout() {
113
+ if (this._timerId) {
114
+ clearTimeout(this._timerId);
115
+ this._timerId = undefined;
116
+ this._timerAt = undefined;
117
+ }
118
+ }
119
+
120
+ dispose() {
121
+ this.cancel();
122
+ }
123
+ }