sen-ether-client 0.2.1 → 0.2.2

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/API.md CHANGED
@@ -95,15 +95,15 @@ Preferred multi-session usage:
95
95
  ```js
96
96
  const sen = await Sen.connect();
97
97
 
98
- const hmi = await sen.interest('SELECT * FROM hmi.diagnostics');
99
- const world = await sen.interest('SELECT * FROM world1.environment');
98
+ const first = await sen.interest('SELECT * FROM session.bus');
99
+ const second = await sen.interest('SELECT * FROM otherSession.otherBus');
100
100
  ```
101
101
 
102
102
  TCP discovery hub usage:
103
103
 
104
104
  ```js
105
105
  const sen = await Sen.connect({
106
- session: 'hmi',
106
+ session: 'session',
107
107
  tcpHub: '127.0.0.1:65222'
108
108
  });
109
109
  ```
@@ -116,7 +116,7 @@ Multicast discovery usage:
116
116
 
117
117
  ```js
118
118
  const sen = await Sen.connect({
119
- session: 'hmi',
119
+ session: 'session',
120
120
  interfaceAddress: '127.0.0.1',
121
121
  listenHost: '127.0.0.1',
122
122
  advertisedHost: '127.0.0.1'
@@ -129,21 +129,21 @@ and received on the intended network device.
129
129
  Explicit single-session usage is still supported:
130
130
 
131
131
  ```js
132
- const hmi = await Sen.connect({ session: 'hmi' });
133
- await hmi.interest('SELECT * FROM hmi.diagnostics');
132
+ const session = await Sen.connect({ session: 'session' });
133
+ await session.interest('SELECT * FROM session.bus');
134
134
  ```
135
135
 
136
136
  If a SEN bus name itself contains dots and is not a session-qualified bus, pass
137
137
  it explicitly. This is useful for standalone scenarios that run in one Ether
138
- session but publish on a bus such as `scenario.environment`:
138
+ session but publish on a bus such as `domain.bus`:
139
139
 
140
140
  ```js
141
- const scenario = await Sen.connect({
142
- session: 'scenario'
141
+ const session = await Sen.connect({
142
+ session: 'session'
143
143
  });
144
144
 
145
- const objects = await scenario.interest('SELECT * FROM scenario.environment', {
146
- bus: 'scenario.environment',
145
+ const objects = await session.interest('SELECT * FROM domain.bus', {
146
+ bus: 'domain.bus',
147
147
  forceBus: true
148
148
  });
149
149
  ```
@@ -164,20 +164,24 @@ Main methods:
164
164
  - `await sen.waitForObject(selector, options)`
165
165
  - `await sen.close()`
166
166
 
167
+ By default, interest creation uses the SEN-native `CRC32(query)` value as the
168
+ interest id. Pass `options.id` only when a caller must force a specific native
169
+ interest id.
170
+
167
171
  Session and bus navigation:
168
172
 
169
173
  ```js
170
174
  const sen = await Sen.connect();
171
175
 
172
176
  console.log(await sen.discoverBuses());
173
- // [{ session: 'hmi', bus: 'diagnostics', qualified: 'hmi.diagnostics' }]
177
+ // [{ session: 'session', bus: 'bus', qualified: 'session.bus' }]
174
178
 
175
179
  for (const sessionName of sen.listSessions()) {
176
180
  const session = await sen.session(sessionName);
177
181
  console.log(sessionName, session.listBuses());
178
182
  }
179
183
 
180
- const diagnostics = await sen.session('hmi').then(hmi => hmi.bus('diagnostics'));
184
+ const bus = await sen.session('session').then(session => session.bus('bus'));
181
185
  ```
182
186
 
183
187
  `discoverBuses()` does not create interests and does not join any SEN bus. It
@@ -226,8 +230,8 @@ Main events:
226
230
  Returned by `await sen.interest(query)`.
227
231
 
228
232
  ```js
229
- const interest = await sen.interest('SELECT * FROM hmi.diagnostics');
230
- const object = await interest.waitFor('EtherProbe');
233
+ const interest = await sen.interest('SELECT * FROM session.bus');
234
+ const object = await interest.waitFor('object-1');
231
235
  ```
232
236
 
233
237
  Main methods:
@@ -251,7 +255,7 @@ For browser gateways or high-frequency telemetry, request only the properties
251
255
  you need and emit batches instead of one JS event per property update:
252
256
 
253
257
  ```js
254
- const tracks = await sen.interest('SELECT bus.Track FROM hmi.loadtest', {
258
+ const objects = await sen.interest('SELECT demo.Object FROM session.bus', {
255
259
  properties: ['latitude', 'longitude', 'altitude', 'heading'],
256
260
  changeMode: 'batch',
257
261
  batchIntervalMs: 16,
@@ -261,7 +265,7 @@ const tracks = await sen.interest('SELECT bus.Track FROM hmi.loadtest', {
261
265
  coalesce: true
262
266
  });
263
267
 
264
- tracks.on('changes', ({ changes, dropped }) => {
268
+ objects.on('changes', ({ changes, dropped }) => {
265
269
  // Send one compact WebSocket frame to the browser.
266
270
  });
267
271
  ```
@@ -309,7 +313,7 @@ Main events:
309
313
  64-bit timestamp precision. Convert it explicitly at JSON boundaries:
310
314
 
311
315
  ```js
312
- tracks.on('change', ({ object, name, value, timestampNs }) => {
316
+ objects.on('change', ({ object, name, value, timestampNs }) => {
313
317
  websocket.send(JSON.stringify({
314
318
  object: object.name,
315
319
  name,
package/README.md CHANGED
@@ -11,16 +11,16 @@ import { Sen } from 'sen-ether-client';
11
11
 
12
12
  const sen = await Sen.connect();
13
13
 
14
- const diagnostics = await sen.interest('SELECT * FROM hmi.diagnostics');
15
- const probe = await diagnostics.waitFor('EtherProbe');
14
+ const objects = await sen.interest('SELECT * FROM session.bus');
15
+ const object = await objects.waitFor('object-1');
16
16
 
17
- probe.on('change:label', ({ value }) => {
17
+ object.on('change:label', ({ value }) => {
18
18
  console.log('label changed:', value);
19
19
  });
20
20
 
21
- console.log(await probe.get('label'));
22
- await probe.set('label', 'from-js');
23
- console.log(await probe.call('ping', ['hello']));
21
+ console.log(await object.get('label'));
22
+ await object.set('label', 'from-js');
23
+ console.log(await object.call('ping', ['hello']));
24
24
 
25
25
  await sen.close();
26
26
  ```
@@ -85,7 +85,7 @@ explicitly:
85
85
 
86
86
  ```js
87
87
  const sen = await Sen.connect({
88
- session: 'hmi',
88
+ session: 'session',
89
89
  tcpHub: '127.0.0.1:65222'
90
90
  });
91
91
  ```
@@ -100,7 +100,7 @@ For local multicast tests, select loopback explicitly:
100
100
 
101
101
  ```js
102
102
  const sen = await Sen.connect({
103
- session: 'hmi',
103
+ session: 'session',
104
104
  interfaceAddress: '127.0.0.1',
105
105
  listenHost: '127.0.0.1',
106
106
  advertisedHost: '127.0.0.1'
@@ -116,8 +116,8 @@ configured interests.
116
116
  is inferred from the query:
117
117
 
118
118
  ```js
119
- const hmi = await sen.interest('SELECT * FROM hmi.diagnostics');
120
- const world = await sen.interest('SELECT * FROM world1.environment');
119
+ const first = await sen.interest('SELECT * FROM session.bus');
120
+ const second = await sen.interest('SELECT * FROM otherSession.otherBus');
121
121
  ```
122
122
 
123
123
  You can also navigate explicitly through sessions and buses:
@@ -127,13 +127,13 @@ const sen = await Sen.connect();
127
127
 
128
128
  console.log(sen.listSessions());
129
129
  console.log(await sen.discoverBuses());
130
- // [{ session: 'hmi', bus: 'diagnostics', qualified: 'hmi.diagnostics' }]
130
+ // [{ session: 'session', bus: 'bus', qualified: 'session.bus' }]
131
131
 
132
- const hmi = await sen.session('hmi');
133
- console.log(hmi.listBuses());
132
+ const session = await sen.session('session');
133
+ console.log(session.listBuses());
134
134
 
135
- const diagnostics = await hmi.bus('diagnostics');
136
- const probe = await diagnostics.waitFor('EtherProbe');
135
+ const bus = await session.bus('bus');
136
+ const object = await bus.waitFor('object-1');
137
137
  ```
138
138
 
139
139
  `discoverBuses()` does not create interests and does not join any SEN bus. It
@@ -144,11 +144,11 @@ process connection, it waits up to `busDiscoverySettleMs` milliseconds.
144
144
  You can also connect to one explicit session:
145
145
 
146
146
  ```js
147
- const hmi = await Sen.connect({
148
- session: 'hmi'
147
+ const session = await Sen.connect({
148
+ session: 'session'
149
149
  });
150
150
 
151
- const diagnostics = await hmi.interest('SELECT * FROM hmi.diagnostics');
151
+ const objects = await session.interest('SELECT * FROM session.bus');
152
152
  ```
153
153
 
154
154
  ## Interests
@@ -156,17 +156,17 @@ const diagnostics = await hmi.interest('SELECT * FROM hmi.diagnostics');
156
156
  Create an interest with a normal SEN query:
157
157
 
158
158
  ```js
159
- const tracks = await sen.interest('SELECT * FROM world1.environment');
159
+ const objects = await sen.interest('SELECT * FROM session.bus');
160
160
  ```
161
161
 
162
162
  Listen for objects and changes:
163
163
 
164
164
  ```js
165
- tracks.on('object', object => {
165
+ objects.on('object', object => {
166
166
  console.log(object.name, object.className);
167
167
  });
168
168
 
169
- tracks.on('change', ({ object, name, value }) => {
169
+ objects.on('change', ({ object, name, value }) => {
170
170
  console.log(object.name, name, value);
171
171
  });
172
172
  ```
@@ -175,13 +175,13 @@ For browser gateways or high-frequency telemetry, batch changes and decode only
175
175
  the properties needed by the UI:
176
176
 
177
177
  ```js
178
- const tracks = await sen.interest('SELECT bus.Track FROM hmi.loadtest', {
178
+ const objects = await sen.interest('SELECT demo.Object FROM session.bus', {
179
179
  properties: ['latitude', 'longitude', 'altitude', 'heading'],
180
180
  changeMode: 'batch',
181
181
  coalesce: true
182
182
  });
183
183
 
184
- tracks.on('changes', ({ changes }) => {
184
+ objects.on('changes', ({ changes }) => {
185
185
  websocket.send(JSON.stringify(changes.map(({ object, name, value, timestampNs }) => ({
186
186
  object: object.name,
187
187
  name,
@@ -194,10 +194,10 @@ tracks.on('changes', ({ changes }) => {
194
194
  Get an object by name, id, class name, or predicate:
195
195
 
196
196
  ```js
197
- const aircraft = await tracks.waitFor('blue-air-1');
197
+ const object = await objects.waitFor('object-1');
198
198
 
199
- const firstAircraft = await tracks.waitFor(
200
- object => object.className === 'rpr.Aircraft'
199
+ const firstDemoObject = await objects.waitFor(
200
+ object => object.className === 'demo.Object'
201
201
  );
202
202
  ```
203
203
 
@@ -277,7 +277,7 @@ Probe a bus:
277
277
  ```bash
278
278
  npx sen-ether-probe \
279
279
  --tcp-hub 127.0.0.1:65222 \
280
- --bus hmi.diagnostics
280
+ --bus session.bus
281
281
  ```
282
282
 
283
283
  ## API
@@ -66,7 +66,7 @@ Options:
66
66
  Examples:
67
67
  sen-ether-probe --bus scenario.control
68
68
  sen-ether-probe --tcp-hub 127.0.0.1:64222 --bus scenario.control
69
- sen-ether-probe --bus world1.environment --query "SELECT * FROM world1.environment"
69
+ sen-ether-probe --bus session.bus --query "SELECT * FROM session.bus"
70
70
 
71
71
  Environment:
72
72
  SEN_ETHER_DISCOVERY_PORT Default multicast discovery port
package/index.js CHANGED
@@ -11,17 +11,16 @@
11
11
  * const sen = await Sen.connect();
12
12
  *
13
13
  * console.log(sen.listSessions());
14
- * const hmi = await sen.session('hmi');
15
- * console.log(hmi.listBuses());
14
+ * const session = await sen.session('session');
15
+ * console.log(session.listBuses());
16
16
  *
17
- * const diagnostics = await sen.interest('SELECT * FROM hmi.diagnostics');
18
- * const world = await sen.interest('SELECT * FROM world1.environment');
19
- * const probe = await diagnostics.waitFor('EtherProbe');
17
+ * const objects = await sen.interest('SELECT * FROM session.bus');
18
+ * const object = await objects.waitFor('object-1');
20
19
  *
21
- * probe.on('change:label', ({ value }) => console.log(value));
22
- * console.log(await probe.get('label'));
23
- * await probe.set('label', 'from-js');
24
- * console.log(await probe.call('ping', ['hello']));
20
+ * object.on('change:label', ({ value }) => console.log(value));
21
+ * console.log(await object.get('label'));
22
+ * await object.set('label', 'from-js');
23
+ * console.log(await object.call('ping', ['hello']));
25
24
  *
26
25
  * await sen.close();
27
26
  */
@@ -61,6 +60,7 @@
61
60
  * @property {string} [bus] Explicit bus name when it cannot be inferred from the query.
62
61
  * @property {boolean} [forceBus=false] Join without waiting for the remote process to announce the bus.
63
62
  * @property {number} [timeout] Operation timeout in ms.
63
+ * @property {number} [id] Optional native interest id. Defaults to CRC32(query).
64
64
  * @property {string[]|string} [properties] Optional property names to decode and emit.
65
65
  * @property {'individual'|'batch'|'both'} [changeMode='individual'] Change emission mode.
66
66
  * @property {number} [batchIntervalMs=16] Batched change flush interval in ms.
package/lib/client.js CHANGED
@@ -788,6 +788,12 @@ export class EtherClient extends EventEmitter {
788
788
  this.socket = socket;
789
789
  }
790
790
  socket.on('data', chunk => this.#onTcpData(connection, chunk));
791
+ socket.on('error', error => {
792
+ this.#removeConnection(connection);
793
+ if (!['EPIPE', 'ECONNRESET'].includes(error?.code)) {
794
+ this.emit('error', error);
795
+ }
796
+ });
791
797
  socket.on('close', hadError => {
792
798
  this.#removeConnection(connection);
793
799
  this.emit('connectionClose', { connection, hadError });
@@ -804,10 +810,18 @@ export class EtherClient extends EventEmitter {
804
810
  if (connection.processKey) {
805
811
  this.connectionsByProcessKey.delete(connection.processKey);
806
812
  }
813
+ const leftParticipants = [];
807
814
  for (const [busId, participants] of this.remoteParticipantsByBusId) {
808
815
  for (const [participantId, participant] of participants) {
809
816
  if (participant.connection === connection) {
810
817
  participants.delete(participantId);
818
+ leftParticipants.push({
819
+ participantId,
820
+ busId,
821
+ busName: participant.busName,
822
+ connection,
823
+ reason: 'connectionClose'
824
+ });
811
825
  }
812
826
  }
813
827
  if (!participants.size) {
@@ -824,6 +838,9 @@ export class EtherClient extends EventEmitter {
824
838
  if (this.socket === connection.socket) {
825
839
  this.socket = [...this.connections.values()][0]?.socket;
826
840
  }
841
+ for (const participant of leftParticipants) {
842
+ this.emit('busLeft', participant);
843
+ }
827
844
  }
828
845
 
829
846
  #configureTcpSocket(socket) {
@@ -998,7 +1015,7 @@ export class EtherClient extends EventEmitter {
998
1015
  */
999
1016
  startInterest(bus, query, options = {}) {
1000
1017
  const busState = this.#getBus(bus);
1001
- const id = options.id ?? this.#nextInterestId(busState);
1018
+ const id = options.id ?? this.#nextInterestId(busState, crc32(query));
1002
1019
  this.#sendBusControlToRemoteParticipants(busState, {
1003
1020
  type: 'InterestStarted',
1004
1021
  value: { query, id }
@@ -1008,7 +1025,13 @@ export class EtherClient extends EventEmitter {
1008
1025
  return { busName: busState.busName, busId: busState.busId, id, query };
1009
1026
  }
1010
1027
 
1011
- #nextInterestId(busState) {
1028
+ #nextInterestId(busState, preferredId) {
1029
+ const preferred = preferredId >>> 0;
1030
+ if (preferred && !busState.interests.has(preferred)) {
1031
+ this.nextInterestId = preferred;
1032
+ return preferred;
1033
+ }
1034
+
1012
1035
  for (let attempts = 0; attempts < 0xffff_ffff; attempts += 1) {
1013
1036
  this.nextInterestId = (this.nextInterestId + 1) >>> 0;
1014
1037
  const id = this.nextInterestId || 1;
@@ -1746,25 +1769,26 @@ export class EtherClient extends EventEmitter {
1746
1769
  return;
1747
1770
  }
1748
1771
  const socket = this.#writableConnectionSocket(connection);
1772
+ if (!socket) {
1773
+ return;
1774
+ }
1749
1775
  const processBusPayload = encodeConfirmedBusFrame({
1750
1776
  to: busState.participantId,
1751
1777
  busId: busState.busId,
1752
1778
  message: busPayload
1753
1779
  });
1754
- socket.write(encodeProcessTcpFrame(PROCESS_MESSAGE_CATEGORY.busMessage, processBusPayload), error => {
1755
- if (error) {
1756
- this.emit('error', error);
1757
- }
1758
- });
1780
+ try {
1781
+ socket.write(encodeProcessTcpFrame(PROCESS_MESSAGE_CATEGORY.busMessage, processBusPayload));
1782
+ } catch {
1783
+ this.#removeConnection(connection);
1784
+ }
1759
1785
  }
1760
1786
 
1761
1787
  #writableConnectionSocket(connection) {
1762
1788
  const socket = connection?.socket;
1763
1789
  if (!socket || socket.destroyed || !socket.writable) {
1764
- const error = new Error('SEN ether TCP socket is not writable');
1765
- error.code = 'SEN_TCP_NOT_WRITABLE';
1766
- this.emit('error', error);
1767
- throw error;
1790
+ this.#removeConnection(connection);
1791
+ return undefined;
1768
1792
  }
1769
1793
  return socket;
1770
1794
  }
package/lib/sen.js CHANGED
@@ -4,6 +4,7 @@ import { decodePropertyValues, decodeValue, encodeArguments, decodeArguments } f
4
4
  import { EtherClient } from './client.js';
5
5
  import { EtherDiscoveryScanner, TcpDiscoveryHubScanner, scan, scanTcpDiscoveryHub } from './discovery.js';
6
6
  import { methodHash } from './hash32.js';
7
+ import { crc32 } from './crc32.js';
7
8
 
8
9
  function wait(ms) {
9
10
  return new Promise(resolve => setTimeout(resolve, ms));
@@ -467,6 +468,7 @@ export class Sen extends EventEmitter {
467
468
  * @param {string} [options.query]
468
469
  * @param {boolean} [options.forceBus]
469
470
  * @param {number} [options.timeout]
471
+ * @param {number} [options.id] Optional native interest id. Defaults to CRC32(query).
470
472
  */
471
473
  async subscribe(busName, options = {}) {
472
474
  if (!this.client) {
@@ -507,7 +509,7 @@ export class Sen extends EventEmitter {
507
509
  /**
508
510
  * Start a native SEN interest and return a live object collection.
509
511
  *
510
- * @param {string} query Native SEN interest query, for example `SELECT * FROM hmi.hud`.
512
+ * @param {string} query Native SEN interest query, for example `SELECT * FROM session.bus`.
511
513
  * @param {object} [options]
512
514
  * @param {string} [options.bus] Explicit bus when it cannot be inferred from the query.
513
515
  * @param {boolean} [options.forceBus]
@@ -944,7 +946,7 @@ export class Sen extends EventEmitter {
944
946
  return this.targetsBySession.keys().next().value;
945
947
  }
946
948
 
947
- throw new Error(`cannot infer SEN session from bus "${busName}"; use a session-qualified query such as SELECT * FROM hmi.${busName}`);
949
+ throw new Error(`cannot infer SEN session from bus "${busName}"; use a session-qualified query such as SELECT * FROM session.${busName}`);
948
950
  }
949
951
 
950
952
  #assertBusBelongsToSession(busName, sessionName) {
@@ -987,6 +989,7 @@ export class Sen extends EventEmitter {
987
989
  });
988
990
  client.on('busLeft', value => {
989
991
  this.remoteBuses.delete(value.busName);
992
+ this.#busForEvent(value)?.handleParticipantLeft(value);
990
993
  this.emit('busUnavailable', value);
991
994
  });
992
995
  client.on('objectsPublished', event => this.#busForEvent(event)?.handleObjectsPublished(event));
@@ -998,6 +1001,10 @@ export class Sen extends EventEmitter {
998
1001
  client.on('runtimeEvents', event => this.#busForEvent(event)?.handleRuntimeEvents(event));
999
1002
  client.on('runtimeMethodResponse', event => this.#busForEvent(event)?.handleRuntimeMethodResponse(event));
1000
1003
  client.on('error', error => {
1004
+ if (['EPIPE', 'ECONNRESET', 'SEN_TCP_NOT_WRITABLE'].includes(error?.code)) {
1005
+ this.emit('warning', error);
1006
+ return;
1007
+ }
1001
1008
  if (this.manualClose || this.reconnecting || this.options.reconnect !== false) {
1002
1009
  this.emit('warning', error);
1003
1010
  return;
@@ -1232,7 +1239,7 @@ export class SenBus extends EventEmitter {
1232
1239
  }
1233
1240
 
1234
1241
  startInterest(query, options = {}) {
1235
- const started = this.sen.client.startInterest(this.name, query);
1242
+ const started = this.sen.client.startInterest(this.name, query, { id: options.id });
1236
1243
  const interest = new SenInterest(this, started.id, query, options);
1237
1244
  this.interests.set(interest.id, interest);
1238
1245
  return interest;
@@ -1240,7 +1247,7 @@ export class SenBus extends EventEmitter {
1240
1247
 
1241
1248
  stopInterest(id) {
1242
1249
  const interestId = typeof id === 'object' ? id.id : id;
1243
- this.sen.client?.stopInterest(this.name, interestId);
1250
+ this.sen.client?.stopInterest(this.id ?? this.name, interestId);
1244
1251
  const interest = this.interests.get(interestId);
1245
1252
  this.#detachInterestObjects(interestId, interest);
1246
1253
  this.interests.delete(interestId);
@@ -1264,7 +1271,7 @@ export class SenBus extends EventEmitter {
1264
1271
  }
1265
1272
  }
1266
1273
  try {
1267
- this.sen.client?.leaveBus(this.name);
1274
+ this.sen.client?.leaveBus(this.id ?? this.name);
1268
1275
  } catch (error) {
1269
1276
  this.sen.emit('warning', error);
1270
1277
  }
@@ -1307,7 +1314,7 @@ export class SenBus extends EventEmitter {
1307
1314
  const interests = [...this.interests.values()];
1308
1315
  this.interests.clear();
1309
1316
  for (const interest of interests) {
1310
- const started = this.sen.client.startInterest(this.name, interest.query);
1317
+ const started = this.sen.client.startInterest(this.name, interest.query, { id: interest.options.id ?? interest.id });
1311
1318
  interest.id = started.id;
1312
1319
  interest.resetLocal();
1313
1320
  this.interests.set(interest.id, interest);
@@ -1353,6 +1360,7 @@ export class SenBus extends EventEmitter {
1353
1360
  ownerId
1354
1361
  });
1355
1362
  }
1363
+ this.#attachKnownType(object);
1356
1364
  interest?.objectsById.set(object.key, object);
1357
1365
  if (info.state?.length) {
1358
1366
  object.applyState(info.state, 'published', info.time, { interestId: discovery.interestId });
@@ -1387,6 +1395,19 @@ export class SenBus extends EventEmitter {
1387
1395
  }
1388
1396
  }
1389
1397
 
1398
+ handleParticipantLeft(event) {
1399
+ const ownerId = event?.participantId;
1400
+ if (ownerId === undefined || ownerId === null) {
1401
+ return;
1402
+ }
1403
+ const detail = { reason: event?.reason ?? 'busLeft', ownerId: ownerId >>> 0 };
1404
+ for (const object of [...this.objectsById.values()]) {
1405
+ if (object.ownerId === (ownerId >>> 0)) {
1406
+ this.#removeObjectFromAllInterests(object, detail);
1407
+ }
1408
+ }
1409
+ }
1410
+
1390
1411
  #detachInterestObjects(interestId, interest) {
1391
1412
  const normalizedInterestId = interestId >>> 0;
1392
1413
  const keyPrefix = `${normalizedInterestId}:`;
@@ -1674,6 +1695,20 @@ export class SenBus extends EventEmitter {
1674
1695
  }
1675
1696
  }
1676
1697
 
1698
+ #attachKnownType(object) {
1699
+ if (object.spec) {
1700
+ return;
1701
+ }
1702
+ for (const spec of this.typeRegistry.values()) {
1703
+ const hash = crc32(spec?.qualifiedName ?? spec?.name ?? '');
1704
+ if (hash === object.typeHash) {
1705
+ object.spec = spec;
1706
+ object.emit('type', spec);
1707
+ return;
1708
+ }
1709
+ }
1710
+ }
1711
+
1677
1712
  #objectByOwnerAndId(ownerId, objectId) {
1678
1713
  if (ownerId !== undefined && ownerId !== null) {
1679
1714
  return this.objectsById.get(remoteObjectKey(ownerId, objectId));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sen-ether-client",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Pure JavaScript SEN client for existing kernels over ether",
5
5
  "senCompatibility": {
6
6
  "kernelProtocolVersion": 9,