sen-ether-client 0.1.3 → 0.1.4

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/lib/client.js +41 -17
  2. package/lib/sen.js +247 -48
  3. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -103,6 +103,25 @@ function resolveInterfaceAddress(value) {
103
103
  return ipv4.address;
104
104
  }
105
105
 
106
+ function multicastInterfaceCandidates(interfaceAddress) {
107
+ if (interfaceAddress) {
108
+ return [interfaceAddress];
109
+ }
110
+ try {
111
+ const addresses = [];
112
+ for (const candidates of Object.values(os.networkInterfaces())) {
113
+ for (const item of candidates ?? []) {
114
+ if ((item.family === 'IPv4' || item.family === 4) && !item.internal && item.address) {
115
+ addresses.push(item.address);
116
+ }
117
+ }
118
+ }
119
+ return [...new Set(addresses)];
120
+ } catch {
121
+ return [];
122
+ }
123
+ }
124
+
106
125
  function normalizeMulticastRange(value) {
107
126
  const ranges = Array.isArray(value) && value.length === 4 ? value : DEFAULT_MULTICAST_RANGE;
108
127
  return ranges.map((range, index) => {
@@ -435,6 +454,15 @@ export class EtherClient extends EventEmitter {
435
454
  return { busName: busState.busName, busId: busState.busId, id, query };
436
455
  }
437
456
 
457
+ #restartInterestForRemote(busState) {
458
+ for (const interest of busState.interests.values()) {
459
+ this.#sendBusControl(busState, busState.participantId, {
460
+ type: 'InterestStarted',
461
+ value: { query: interest.query, id: interest.id }
462
+ });
463
+ }
464
+ }
465
+
438
466
  /**
439
467
  * Stop a previously started interest.
440
468
  *
@@ -711,25 +739,13 @@ export class EtherClient extends EventEmitter {
711
739
 
712
740
  #onMulticastBusDatagram(busState, message, remote) {
713
741
  try {
714
- if (message.length < 8) {
742
+ if (message.length < 9) {
715
743
  throw new RangeError(`SEN multicast bus datagram too small: ${message.length}`);
716
744
  }
717
- const processId = message.readUInt32LE(0);
718
- const payloadSize = message.readUInt32LE(4);
719
- if (processId === this.processInfo.processId) {
745
+ const frame = decodeConfirmedBusFrame(message);
746
+ if (frame.to !== busState.participantId) {
720
747
  return;
721
748
  }
722
- if (payloadSize !== message.length - 8) {
723
- throw new RangeError(
724
- `SEN multicast bus payload size mismatch: expected ${payloadSize}, got ${message.length - 8}`
725
- );
726
- }
727
-
728
- const frame = {
729
- to: busState.participantId,
730
- busId: busState.busId,
731
- message: message.subarray(8)
732
- };
733
749
  const busMessage = decodeBusMessage(frame.message);
734
750
  this.emit('busFrame', { ...frame, busMessage, remote, multicast: true });
735
751
  if (busMessage.categoryName === 'controlMessage') {
@@ -766,7 +782,14 @@ export class EtherClient extends EventEmitter {
766
782
  const onListening = () => {
767
783
  socket.off('error', onError);
768
784
  try {
769
- socket.addMembership(group, this.interfaceAddress);
785
+ const interfaces = multicastInterfaceCandidates(this.interfaceAddress);
786
+ if (interfaces.length) {
787
+ for (const interfaceAddress of interfaces) {
788
+ socket.addMembership(group, interfaceAddress);
789
+ }
790
+ } else {
791
+ socket.addMembership(group);
792
+ }
770
793
  socket.setMulticastLoopback(true);
771
794
  if (this.interfaceAddress) {
772
795
  socket.setMulticastInterface(this.interfaceAddress);
@@ -789,13 +812,14 @@ export class EtherClient extends EventEmitter {
789
812
  return;
790
813
  }
791
814
 
792
- const remoteParticipantId = frame.to;
815
+ const remoteParticipantId = frame.to >>> 0;
793
816
  if (!busState.readyRemoteParticipants.has(remoteParticipantId)) {
794
817
  busState.readyRemoteParticipants.add(remoteParticipantId);
795
818
  this.#sendBusControl(busState, busState.participantId, {
796
819
  type: 'RemoteParticipantReady',
797
820
  value: { id: remoteParticipantId }
798
821
  });
822
+ this.#restartInterestForRemote(busState);
799
823
  this.emit('busParticipantReady', {
800
824
  busName: busState.busName,
801
825
  busId: busState.busId,
package/lib/sen.js CHANGED
@@ -9,6 +9,9 @@ function wait(ms) {
9
9
  return new Promise(resolve => setTimeout(resolve, ms));
10
10
  }
11
11
 
12
+ const STATE_RESYNC_DELAYS_MS = [250, 1000, 3000, 8000];
13
+ const STATE_RESYNC_INTERVAL_MS = 1000;
14
+
12
15
  async function waitForEvent(emitter, event, timeoutMs) {
13
16
  let timeoutId;
14
17
  const timeout = new Promise((_, reject) => {
@@ -782,6 +785,13 @@ export class Sen extends EventEmitter {
782
785
  return target;
783
786
  }
784
787
 
788
+ async #reconnectTarget(options) {
789
+ if (options.tcpHub || !options.target) {
790
+ return await this.#discoverTarget(options);
791
+ }
792
+ return options.target;
793
+ }
794
+
785
795
  #sessionNameForBus(busName, options = {}) {
786
796
  const explicit = String(options.session || '').trim();
787
797
  if (explicit) {
@@ -899,7 +909,7 @@ export class Sen extends EventEmitter {
899
909
  }
900
910
 
901
911
  const config = this.connectOptions;
902
- const target = config.target ?? await this.#discoverTarget(config);
912
+ const target = await this.#reconnectTarget(config);
903
913
  if (!target) {
904
914
  throw new Error('no SEN ether process matches the requested filters');
905
915
  }
@@ -1062,6 +1072,8 @@ export class SenBus extends EventEmitter {
1062
1072
  this.typeRegistry = new Map();
1063
1073
  this.requestedTypeHashes = new Set();
1064
1074
  this.stateRequestedObjectIds = new Set();
1075
+ this.stateResyncTimers = new Set();
1076
+ this.stateResyncInterval = undefined;
1065
1077
  this.interests = new Map();
1066
1078
  this.pendingCalls = new Map();
1067
1079
  this.nextTicketId = 1;
@@ -1076,10 +1088,13 @@ export class SenBus extends EventEmitter {
1076
1088
 
1077
1089
  stopInterest(id) {
1078
1090
  const interestId = typeof id === 'object' ? id.id : id;
1079
- this.sen.client.stopInterest(this.name, interestId);
1091
+ this.sen.client?.stopInterest(this.name, interestId);
1080
1092
  const interest = this.interests.get(interestId);
1081
1093
  this.#detachInterestObjects(interestId, interest);
1082
1094
  this.interests.delete(interestId);
1095
+ if (!this.interests.size) {
1096
+ this.#clearStateResyncTimers();
1097
+ }
1083
1098
  interest?.closeLocal();
1084
1099
  interest?.emit('close');
1085
1100
  }
@@ -1097,7 +1112,7 @@ export class SenBus extends EventEmitter {
1097
1112
  }
1098
1113
  }
1099
1114
  try {
1100
- this.sen.client.leaveBus(this.name);
1115
+ this.sen.client?.leaveBus(this.name);
1101
1116
  } catch (error) {
1102
1117
  this.sen.emit('warning', error);
1103
1118
  }
@@ -1117,6 +1132,7 @@ export class SenBus extends EventEmitter {
1117
1132
  this.typeRegistry.clear();
1118
1133
  this.requestedTypeHashes.clear();
1119
1134
  this.stateRequestedObjectIds.clear();
1135
+ this.#clearStateResyncTimers();
1120
1136
  for (const interest of this.interests.values()) {
1121
1137
  interest.closeLocal();
1122
1138
  interest.objectsById.clear();
@@ -1158,8 +1174,25 @@ export class SenBus extends EventEmitter {
1158
1174
  handleObjectsPublished(event) {
1159
1175
  const newTypeHashes = new Set();
1160
1176
  for (const discovery of event.discoveries ?? []) {
1177
+ const interest = this.interests.get(discovery.interestId);
1178
+ if (interest && event.ownerId !== undefined) {
1179
+ if (interest.ownerId !== undefined && interest.ownerId !== event.ownerId) {
1180
+ this.#resetInterestForOwner(interest, event.ownerId);
1181
+ } else {
1182
+ interest.ownerId = event.ownerId;
1183
+ }
1184
+ }
1185
+
1161
1186
  for (const info of discovery.objects ?? []) {
1162
1187
  let object = this.objectsById.get(info.id);
1188
+ if (object && event.ownerId !== undefined && object.ownerId !== event.ownerId) {
1189
+ this.#removeObjectFromAllInterests(object, {
1190
+ reason: 'ownerChanged',
1191
+ ownerId: event.ownerId,
1192
+ previousOwnerId: object.ownerId
1193
+ });
1194
+ object = undefined;
1195
+ }
1163
1196
  const isNewObject = !object;
1164
1197
  if (!object) {
1165
1198
  object = new SenRemoteObject(this, {
@@ -1175,26 +1208,22 @@ export class SenBus extends EventEmitter {
1175
1208
  ownerId: event.ownerId
1176
1209
  });
1177
1210
  }
1178
- const interest = this.interests.get(discovery.interestId);
1179
1211
  interest?.objectsById.set(object.id, object);
1180
1212
  if (info.state?.length) {
1181
- object.applyState(info.state, 'state', info.time, { interestId: discovery.interestId });
1213
+ object.applyState(info.state, 'published', info.time, { interestId: discovery.interestId });
1182
1214
  }
1183
1215
  if (!this.requestedTypeHashes.has(info.typeHash)) {
1184
1216
  this.requestedTypeHashes.add(info.typeHash);
1185
1217
  newTypeHashes.add(info.typeHash);
1186
1218
  }
1187
- interest?.emit('object', object);
1188
- if (isNewObject) {
1189
- this.emit('object', object);
1190
- this.sen.emit('object', object);
1191
- }
1219
+ this.#emitObjectWhenReady(interest, object, isNewObject);
1192
1220
  }
1193
1221
  }
1194
1222
  if (newTypeHashes.size) {
1195
1223
  this.sen.client.requestTypes(this.name, newTypeHashes);
1196
1224
  }
1197
1225
  this.#requestReadyObjectStates();
1226
+ this.#scheduleStateResyncs();
1198
1227
  }
1199
1228
 
1200
1229
  handleObjectsRemoved(event) {
@@ -1202,18 +1231,8 @@ export class SenBus extends EventEmitter {
1202
1231
  for (const id of removal.ids ?? []) {
1203
1232
  const object = this.objectsById.get(id);
1204
1233
  const interest = this.interests.get(removal.interestId);
1205
- interest?.objectsById.delete(id);
1206
- this.stateRequestedObjectIds.delete(stateRequestKey(removal.interestId, id));
1207
1234
  if (object) {
1208
- object.detachInterest(removal.interestId);
1209
- object.emit('remove', { interestId: removal.interestId });
1210
- interest?.emit('remove', object);
1211
- if (object.interestIds.size === 0) {
1212
- this.objectsById.delete(id);
1213
- this.requestedTypeHashes.delete(object.typeHash);
1214
- this.emit('remove', object);
1215
- this.sen.emit('remove', object);
1216
- }
1235
+ this.#removeObjectFromInterest(object, removal.interestId, interest);
1217
1236
  }
1218
1237
  }
1219
1238
  }
@@ -1233,13 +1252,74 @@ export class SenBus extends EventEmitter {
1233
1252
  }
1234
1253
 
1235
1254
  for (const object of interest.objectsById.values()) {
1236
- object.detachInterest(normalizedInterestId);
1237
- if (object.interestIds.size === 0) {
1238
- this.objectsById.delete(object.id);
1239
- this.requestedTypeHashes.delete(object.typeHash);
1240
- }
1255
+ this.#removeObjectFromInterest(object, normalizedInterestId, interest);
1256
+ }
1257
+ interest.objectsById.clear();
1258
+ }
1259
+
1260
+ #resetInterestForOwner(interest, ownerId) {
1261
+ const previousOwnerId = interest.ownerId;
1262
+ const detail = { reason: 'ownerChanged', ownerId, previousOwnerId };
1263
+ for (const object of [...interest.objectsById.values()]) {
1264
+ this.#removeObjectFromAllInterests(object, detail);
1241
1265
  }
1242
1266
  interest.objectsById.clear();
1267
+ interest.ownerId = ownerId;
1268
+ interest.resetLocal();
1269
+ interest.emit('stale', detail);
1270
+ this.emit('stale', { interest, ...detail });
1271
+ this.sen.emit('stale', { bus: this, interest, ...detail });
1272
+ }
1273
+
1274
+ #removeObjectFromAllInterests(object, detail = {}) {
1275
+ for (const interestId of [...object.interestIds]) {
1276
+ this.#removeObjectFromInterest(object, interestId, this.interests.get(interestId), detail);
1277
+ }
1278
+ }
1279
+
1280
+ #removeObjectFromInterest(object, interestId, interest, detail = {}) {
1281
+ const normalizedInterestId = interestId >>> 0;
1282
+ this.stateRequestedObjectIds.delete(stateRequestKey(normalizedInterestId, object.id));
1283
+ interest?.objectsById.delete(object.id);
1284
+ object.detachInterest(normalizedInterestId);
1285
+ object.emit('remove', { interestId: normalizedInterestId, ...detail });
1286
+ interest?.emit('remove', object);
1287
+ if (object.interestIds.size === 0) {
1288
+ this.objectsById.delete(object.id);
1289
+ this.requestedTypeHashes.delete(object.typeHash);
1290
+ this.emit('remove', object);
1291
+ this.sen.emit('remove', object);
1292
+ }
1293
+ }
1294
+
1295
+ #emitObjectWhenReady(interest, object, emitGlobal) {
1296
+ if (!interest) {
1297
+ return;
1298
+ }
1299
+ const publish = () => {
1300
+ if (!object.isReadyForInterest(interest.id)) {
1301
+ return false;
1302
+ }
1303
+ if (object.markInterestObjectEmitted(interest.id)) {
1304
+ interest.emit('object', object);
1305
+ }
1306
+ if (emitGlobal && object.markGlobalObjectEmitted()) {
1307
+ this.emit('object', object);
1308
+ this.sen.emit('object', object);
1309
+ }
1310
+ return true;
1311
+ };
1312
+
1313
+ if (publish()) {
1314
+ return;
1315
+ }
1316
+
1317
+ const onReady = () => {
1318
+ if (publish()) {
1319
+ object.off('ready', onReady);
1320
+ }
1321
+ };
1322
+ object.on('ready', onReady);
1243
1323
  }
1244
1324
 
1245
1325
  handleTypesInfoResponse(event) {
@@ -1267,13 +1347,14 @@ export class SenBus extends EventEmitter {
1267
1347
  }
1268
1348
  this.#retryPendingStates();
1269
1349
  this.#requestReadyObjectStates();
1350
+ this.#scheduleStateResyncs();
1270
1351
  }
1271
1352
 
1272
1353
  handleObjectsStateResponse(event) {
1273
1354
  for (const response of event.responses ?? []) {
1274
1355
  for (const state of response.objectStates ?? []) {
1275
1356
  const object = this.objectsById.get(state.id);
1276
- if (!object) {
1357
+ if (!object || (event.ownerId !== undefined && object.ownerId !== event.ownerId)) {
1277
1358
  continue;
1278
1359
  }
1279
1360
  object.applyState(state.state, 'state', state.timestamp, { interestId: response.interestId });
@@ -1354,7 +1435,8 @@ export class SenBus extends EventEmitter {
1354
1435
  });
1355
1436
  }
1356
1437
 
1357
- #requestReadyObjectStates() {
1438
+ #requestReadyObjectStates(options = {}) {
1439
+ const force = options.force === true;
1358
1440
  const requestsByInterest = new Map();
1359
1441
  for (const interest of this.interests.values()) {
1360
1442
  for (const object of interest.objectsById.values()) {
@@ -1362,7 +1444,7 @@ export class SenBus extends EventEmitter {
1362
1444
  continue;
1363
1445
  }
1364
1446
  const key = stateRequestKey(interest.id, object.id);
1365
- if (this.stateRequestedObjectIds.has(key)) {
1447
+ if (!force && this.stateRequestedObjectIds.has(key)) {
1366
1448
  continue;
1367
1449
  }
1368
1450
  this.stateRequestedObjectIds.add(key);
@@ -1372,22 +1454,64 @@ export class SenBus extends EventEmitter {
1372
1454
  }
1373
1455
  }
1374
1456
 
1375
- if (requestsByInterest.size) {
1376
- this.sen.client.requestObjectStates(this.name, [...requestsByInterest].map(([interestId, objectIds]) => ({
1377
- interestId,
1378
- objectIds
1379
- })));
1457
+ if (requestsByInterest.size && this.sen.client) {
1458
+ try {
1459
+ this.sen.client.requestObjectStates(this.name, [...requestsByInterest].map(([interestId, objectIds]) => ({
1460
+ interestId,
1461
+ objectIds
1462
+ })));
1463
+ } catch (error) {
1464
+ this.sen.emit('warning', error);
1465
+ }
1380
1466
  }
1381
1467
  }
1382
1468
 
1469
+ #scheduleStateResyncs() {
1470
+ if (!this.interests.size || this.stateResyncTimers.size) {
1471
+ return;
1472
+ }
1473
+
1474
+ for (const delayMs of STATE_RESYNC_DELAYS_MS) {
1475
+ const timer = setTimeout(() => {
1476
+ this.stateResyncTimers.delete(timer);
1477
+ try {
1478
+ this.#requestReadyObjectStates({ force: true });
1479
+ } catch (error) {
1480
+ this.sen.emit('warning', error);
1481
+ }
1482
+ }, delayMs);
1483
+ timer.unref?.();
1484
+ this.stateResyncTimers.add(timer);
1485
+ }
1486
+ const interval = setInterval(() => {
1487
+ try {
1488
+ this.#requestReadyObjectStates({ force: true });
1489
+ } catch (error) {
1490
+ this.sen.emit('warning', error);
1491
+ }
1492
+ }, STATE_RESYNC_INTERVAL_MS);
1493
+ interval.unref?.();
1494
+ this.stateResyncInterval = interval;
1495
+ this.stateResyncTimers.add(interval);
1496
+ }
1497
+
1498
+ #clearStateResyncTimers() {
1499
+ for (const timer of this.stateResyncTimers) {
1500
+ clearTimeout(timer);
1501
+ }
1502
+ this.stateResyncTimers.clear();
1503
+ this.stateResyncInterval = undefined;
1504
+ }
1505
+
1383
1506
  #retryPendingStates() {
1384
1507
  for (const object of this.objectsById.values()) {
1385
- if (object.pendingState) {
1508
+ const pendingStates = object.pendingStates.splice(0);
1509
+ for (const pendingState of pendingStates) {
1386
1510
  object.applyState(
1387
- object.pendingState.buffer,
1388
- object.pendingState.source,
1389
- object.pendingState.timestampNs,
1390
- { interestId: object.pendingState.interestId }
1511
+ pendingState.buffer,
1512
+ pendingState.source,
1513
+ pendingState.timestampNs,
1514
+ { interestId: pendingState.interestId }
1391
1515
  );
1392
1516
  }
1393
1517
  }
@@ -1400,6 +1524,7 @@ export class SenInterest extends EventEmitter {
1400
1524
  this.bus = bus;
1401
1525
  this.id = id;
1402
1526
  this.query = query;
1527
+ this.ownerId = undefined;
1403
1528
  this.options = { ...options };
1404
1529
  this.propertyNames = normalizePropertyNames(options.properties ?? options.propertyNames);
1405
1530
  this.changeMode = options.changeMode ?? (options.batch ? 'batch' : 'individual');
@@ -1491,7 +1616,12 @@ export class SenRemoteObject extends EventEmitter {
1491
1616
  }
1492
1617
  this.snapshot = {};
1493
1618
  this.spec = undefined;
1619
+ this.typePromise = undefined;
1494
1620
  this.pendingState = undefined;
1621
+ this.pendingStates = [];
1622
+ this.readyInterestIds = new Set();
1623
+ this.emittedInterestObjectIds = new Set();
1624
+ this.emittedGlobalObject = false;
1495
1625
  this.timestamp = undefined;
1496
1626
  this.timestampNs = undefined;
1497
1627
  this.lastStateTimestamp = undefined;
@@ -1520,7 +1650,10 @@ export class SenRemoteObject extends EventEmitter {
1520
1650
 
1521
1651
  detachInterest(interestId) {
1522
1652
  if (interestId !== undefined) {
1523
- this.interestIds.delete(interestId);
1653
+ const normalizedInterestId = interestId >>> 0;
1654
+ this.interestIds.delete(normalizedInterestId);
1655
+ this.readyInterestIds.delete(normalizedInterestId);
1656
+ this.emittedInterestObjectIds.delete(normalizedInterestId);
1524
1657
  if (this.interestId === interestId) {
1525
1658
  this.interestId = this.interestIds.values().next().value;
1526
1659
  }
@@ -1552,18 +1685,38 @@ export class SenRemoteObject extends EventEmitter {
1552
1685
  }
1553
1686
 
1554
1687
  const timeoutMs = options.timeout ?? 3000;
1555
- return await new Promise((resolve, reject) => {
1556
- const timeout = setTimeout(() => {
1557
- this.off('type', onType);
1558
- reject(new Error(`timeout waiting for SEN type ${this.className}`));
1559
- }, timeoutMs);
1688
+ let timeout;
1689
+ try {
1690
+ return await Promise.race([
1691
+ this.#waitForTypeReady(),
1692
+ new Promise((_, reject) => {
1693
+ timeout = setTimeout(() => {
1694
+ reject(new Error(`timeout waiting for SEN type ${this.className}`));
1695
+ }, timeoutMs);
1696
+ timeout.unref?.();
1697
+ })
1698
+ ]);
1699
+ } finally {
1700
+ clearTimeout(timeout);
1701
+ }
1702
+ }
1703
+
1704
+ #waitForTypeReady() {
1705
+ if (this.spec) {
1706
+ return Promise.resolve(this.spec);
1707
+ }
1708
+ if (this.typePromise) {
1709
+ return this.typePromise;
1710
+ }
1711
+ this.typePromise = new Promise(resolve => {
1560
1712
  const onType = spec => {
1561
- clearTimeout(timeout);
1562
1713
  this.off('type', onType);
1714
+ this.typePromise = undefined;
1563
1715
  resolve(spec);
1564
1716
  };
1565
1717
  this.on('type', onType);
1566
1718
  });
1719
+ return this.typePromise;
1567
1720
  }
1568
1721
 
1569
1722
  async get(name) {
@@ -1574,6 +1727,27 @@ export class SenRemoteObject extends EventEmitter {
1574
1727
  return this.propertyTimestamps.get(name);
1575
1728
  }
1576
1729
 
1730
+ isReadyForInterest(interestId) {
1731
+ return this.readyInterestIds.has(interestId >>> 0);
1732
+ }
1733
+
1734
+ markInterestObjectEmitted(interestId) {
1735
+ const normalizedInterestId = interestId >>> 0;
1736
+ if (this.emittedInterestObjectIds.has(normalizedInterestId)) {
1737
+ return false;
1738
+ }
1739
+ this.emittedInterestObjectIds.add(normalizedInterestId);
1740
+ return true;
1741
+ }
1742
+
1743
+ markGlobalObjectEmitted() {
1744
+ if (this.emittedGlobalObject) {
1745
+ return false;
1746
+ }
1747
+ this.emittedGlobalObject = true;
1748
+ return true;
1749
+ }
1750
+
1577
1751
  async set(name, value, options = {}) {
1578
1752
  await this.waitForType(options);
1579
1753
  const property = this.property(name);
@@ -1612,7 +1786,7 @@ export class SenRemoteObject extends EventEmitter {
1612
1786
  this.#rememberObjectTimestamp(source, timestampNs);
1613
1787
 
1614
1788
  if (!this.spec) {
1615
- this.pendingState = { buffer, source, timestampNs, interestId: options.interestId };
1789
+ this.#queuePendingState({ buffer, source, timestampNs, interestId: options.interestId });
1616
1790
  return;
1617
1791
  }
1618
1792
 
@@ -1652,7 +1826,32 @@ export class SenRemoteObject extends EventEmitter {
1652
1826
  }
1653
1827
  }
1654
1828
 
1655
- this.pendingState = complete ? undefined : { buffer, source, timestampNs, interestId: options.interestId };
1829
+ if (complete) {
1830
+ if (source === 'state') {
1831
+ this.#markReady(options.interestId);
1832
+ }
1833
+ if (!this.pendingStates.length) this.pendingState = undefined;
1834
+ } else {
1835
+ this.#queuePendingState({ buffer, source, timestampNs, interestId: options.interestId });
1836
+ }
1837
+ }
1838
+
1839
+ #markReady(interestId) {
1840
+ if (interestId !== undefined) {
1841
+ const normalizedInterestId = interestId >>> 0;
1842
+ this.readyInterestIds.add(normalizedInterestId);
1843
+ this.emit('ready', { interestId: normalizedInterestId });
1844
+ return;
1845
+ }
1846
+ for (const id of this.interestIds) {
1847
+ this.readyInterestIds.add(id >>> 0);
1848
+ this.emit('ready', { interestId: id >>> 0 });
1849
+ }
1850
+ }
1851
+
1852
+ #queuePendingState(state) {
1853
+ this.pendingStates.push(state);
1854
+ this.pendingState = state;
1656
1855
  }
1657
1856
 
1658
1857
  #targetInterests(interestId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sen-ether-client",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Pure JavaScript SEN client for existing kernels over ether",
5
5
  "senCompatibility": {
6
6
  "kernelProtocolVersion": 9,