@matter/node 0.16.0-alpha.0-20251210-206ca2db7 → 0.16.0-alpha.0-20251212-4dde71be3

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 (70) hide show
  1. package/dist/cjs/behavior/system/network/NetworkClient.d.ts +5 -1
  2. package/dist/cjs/behavior/system/network/NetworkClient.d.ts.map +1 -1
  3. package/dist/cjs/behavior/system/network/NetworkClient.js +19 -0
  4. package/dist/cjs/behavior/system/network/NetworkClient.js.map +1 -1
  5. package/dist/cjs/endpoint/properties/Behaviors.d.ts.map +1 -1
  6. package/dist/cjs/endpoint/properties/Behaviors.js +4 -3
  7. package/dist/cjs/endpoint/properties/Behaviors.js.map +1 -1
  8. package/dist/cjs/node/client/ClientEventEmitter.d.ts +2 -3
  9. package/dist/cjs/node/client/ClientEventEmitter.d.ts.map +1 -1
  10. package/dist/cjs/node/client/ClientEventEmitter.js +10 -2
  11. package/dist/cjs/node/client/ClientEventEmitter.js.map +1 -1
  12. package/dist/cjs/node/client/ClientGroupInteraction.d.ts +2 -2
  13. package/dist/cjs/node/client/ClientGroupInteraction.d.ts.map +1 -1
  14. package/dist/cjs/node/client/ClientGroupInteraction.js +1 -0
  15. package/dist/cjs/node/client/ClientGroupInteraction.js.map +1 -1
  16. package/dist/cjs/node/client/ClientNodeInteraction.js +1 -1
  17. package/dist/cjs/node/client/ClientNodeInteraction.js.map +1 -1
  18. package/dist/cjs/node/client/ClientStructure.d.ts +3 -1
  19. package/dist/cjs/node/client/ClientStructure.d.ts.map +1 -1
  20. package/dist/cjs/node/client/ClientStructure.js +31 -12
  21. package/dist/cjs/node/client/ClientStructure.js.map +1 -1
  22. package/dist/cjs/node/client/NodePeerAddressStore.d.ts.map +1 -1
  23. package/dist/cjs/node/client/NodePeerAddressStore.js +6 -3
  24. package/dist/cjs/node/client/NodePeerAddressStore.js.map +1 -1
  25. package/dist/cjs/node/client/PeerBehavior.d.ts +4 -5
  26. package/dist/cjs/node/client/PeerBehavior.d.ts.map +1 -1
  27. package/dist/cjs/node/client/PeerBehavior.js +20 -18
  28. package/dist/cjs/node/client/PeerBehavior.js.map +1 -1
  29. package/dist/cjs/node/server/InteractionServer.d.ts.map +1 -1
  30. package/dist/cjs/node/server/InteractionServer.js.map +1 -1
  31. package/dist/esm/behavior/system/network/NetworkClient.d.ts +5 -1
  32. package/dist/esm/behavior/system/network/NetworkClient.d.ts.map +1 -1
  33. package/dist/esm/behavior/system/network/NetworkClient.js +19 -0
  34. package/dist/esm/behavior/system/network/NetworkClient.js.map +1 -1
  35. package/dist/esm/endpoint/properties/Behaviors.d.ts.map +1 -1
  36. package/dist/esm/endpoint/properties/Behaviors.js +4 -3
  37. package/dist/esm/endpoint/properties/Behaviors.js.map +1 -1
  38. package/dist/esm/node/client/ClientEventEmitter.d.ts +2 -3
  39. package/dist/esm/node/client/ClientEventEmitter.d.ts.map +1 -1
  40. package/dist/esm/node/client/ClientEventEmitter.js +10 -2
  41. package/dist/esm/node/client/ClientEventEmitter.js.map +1 -1
  42. package/dist/esm/node/client/ClientGroupInteraction.d.ts +2 -2
  43. package/dist/esm/node/client/ClientGroupInteraction.d.ts.map +1 -1
  44. package/dist/esm/node/client/ClientGroupInteraction.js +1 -0
  45. package/dist/esm/node/client/ClientGroupInteraction.js.map +1 -1
  46. package/dist/esm/node/client/ClientNodeInteraction.js +1 -1
  47. package/dist/esm/node/client/ClientNodeInteraction.js.map +1 -1
  48. package/dist/esm/node/client/ClientStructure.d.ts +3 -1
  49. package/dist/esm/node/client/ClientStructure.d.ts.map +1 -1
  50. package/dist/esm/node/client/ClientStructure.js +32 -12
  51. package/dist/esm/node/client/ClientStructure.js.map +1 -1
  52. package/dist/esm/node/client/NodePeerAddressStore.d.ts.map +1 -1
  53. package/dist/esm/node/client/NodePeerAddressStore.js +6 -3
  54. package/dist/esm/node/client/NodePeerAddressStore.js.map +1 -1
  55. package/dist/esm/node/client/PeerBehavior.d.ts +4 -5
  56. package/dist/esm/node/client/PeerBehavior.d.ts.map +1 -1
  57. package/dist/esm/node/client/PeerBehavior.js +20 -18
  58. package/dist/esm/node/client/PeerBehavior.js.map +1 -1
  59. package/dist/esm/node/server/InteractionServer.d.ts.map +1 -1
  60. package/dist/esm/node/server/InteractionServer.js.map +1 -1
  61. package/package.json +7 -7
  62. package/src/behavior/system/network/NetworkClient.ts +21 -1
  63. package/src/endpoint/properties/Behaviors.ts +3 -2
  64. package/src/node/client/ClientEventEmitter.ts +12 -4
  65. package/src/node/client/ClientGroupInteraction.ts +4 -2
  66. package/src/node/client/ClientNodeInteraction.ts +1 -1
  67. package/src/node/client/ClientStructure.ts +37 -15
  68. package/src/node/client/NodePeerAddressStore.ts +6 -3
  69. package/src/node/client/PeerBehavior.ts +34 -28
  70. package/src/node/server/InteractionServer.ts +1 -0
@@ -761,9 +761,9 @@ export class Behaviors {
761
761
  const { id, Events } = type;
762
762
 
763
763
  const get = () => this.#backingFor(type).stateView;
764
- Object.defineProperty(this.#endpoint.state, id, { get, enumerable: true });
764
+ Object.defineProperty(this.#endpoint.state, id, { get, enumerable: true, configurable: true });
765
765
  if (type.schema.id !== undefined) {
766
- Object.defineProperty(this.#endpoint.state, type.schema.id, { get });
766
+ Object.defineProperty(this.#endpoint.state, type.schema.id, { get, configurable: true });
767
767
  }
768
768
 
769
769
  Object.defineProperty(this.#endpoint.events, id, {
@@ -780,6 +780,7 @@ export class Behaviors {
780
780
  },
781
781
 
782
782
  enumerable: true,
783
+ configurable: true,
783
784
  });
784
785
  }
785
786
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ElementEvent, Events } from "#behavior/Events.js";
8
+ import { NetworkClient } from "#behavior/system/network/NetworkClient.js";
8
9
  import { camelize, Diagnostic, isObject, Logger } from "#general";
9
10
  import { ClusterModel, EventModel, MatterModel } from "#model";
10
11
  import type { ClientNode } from "#node/ClientNode.js";
@@ -18,10 +19,9 @@ const logger = Logger.get("ClientEventEmitter");
18
19
  * Event handler for Matter events transmitted by a peer.
19
20
  *
20
21
  * TODO - set priority on context when split for server vs. client
21
- * TODO - record latest event number for each subscription shape (or just wildcard?)
22
22
  */
23
23
  export interface ClientEventEmitter {
24
- (event: ReadResult.EventValue): void;
24
+ (event: ReadResult.EventValue): Promise<void>;
25
25
  }
26
26
 
27
27
  /**
@@ -40,7 +40,7 @@ const warnedForUnknown = new Set<ClusterId | `${ClusterId}-${EventId}`>();
40
40
  export function ClientEventEmitter(node: ClientNode, structure: ClientStructure) {
41
41
  return emitClientEvent;
42
42
 
43
- function emitClientEvent(occurrence: ReadResult.EventValue) {
43
+ async function emitClientEvent(occurrence: ReadResult.EventValue) {
44
44
  const names = getNames(node.matter, occurrence);
45
45
  if (!names) {
46
46
  return;
@@ -48,10 +48,18 @@ export function ClientEventEmitter(node: ClientNode, structure: ClientStructure)
48
48
 
49
49
  const event = getEvent(node, occurrence, names.cluster, names.event);
50
50
  if (event) {
51
- node.act(agent => {
51
+ await node.act(async agent => {
52
52
  // Current ActionContext is not writable, could skip act() but meh, see TODO above
53
53
  //agent.context.priority = occurrence.priority;
54
54
  event.emit(occurrence.value, agent.context);
55
+
56
+ const network = agent.get(NetworkClient);
57
+ if (occurrence.number > network.state.maxEventNumber) {
58
+ await agent.context.transaction.addResources(network);
59
+ await agent.context.transaction.begin();
60
+ network.state.maxEventNumber = occurrence.number;
61
+ await agent.context.transaction.commit();
62
+ }
55
63
  });
56
64
  }
57
65
  }
@@ -8,11 +8,11 @@ import type { ActionContext } from "#behavior/index.js";
8
8
  import { ImplementationError } from "#general";
9
9
  import {
10
10
  ClientInvoke,
11
+ ClientSubscription,
11
12
  DecodedInvokeResult,
12
13
  Read,
13
14
  ReadResult,
14
15
  Subscribe,
15
- SubscribeResult,
16
16
  Write,
17
17
  WriteResult,
18
18
  } from "#protocol";
@@ -27,7 +27,7 @@ export class ClientGroupInteraction extends ClientNodeInteraction {
27
27
  }
28
28
 
29
29
  /** Groups do not support reading or subscribing to attributes */
30
- override async subscribe(_request: Subscribe, _context?: ActionContext): SubscribeResult {
30
+ override async subscribe(_request: Subscribe, _context?: ActionContext): Promise<ClientSubscription> {
31
31
  throw new InvalidGroupOperationError("Groups do not support subscribing to attributes");
32
32
  }
33
33
 
@@ -65,6 +65,8 @@ export class ClientGroupInteraction extends ClientNodeInteraction {
65
65
  throw new InvalidGroupOperationError("Timed requests are not supported for group address invokes.");
66
66
  }
67
67
 
68
+ request.suppressResponse = true; // Invoking on a group does not yield a response by definition
69
+
68
70
  return super.invoke(request, context);
69
71
  }
70
72
  }
@@ -74,7 +74,7 @@ export class ClientNodeInteraction implements Interactable<ActionContext> {
74
74
  request,
75
75
  }),
76
76
 
77
- sustain: request.sustain === undefined ? true : request.sustain,
77
+ sustain: request.sustain !== false,
78
78
 
79
79
  updated: async data => {
80
80
  const result = this.structure.mutate(request, data);
@@ -11,7 +11,7 @@ import { Descriptor } from "#clusters/descriptor";
11
11
  import { Endpoint } from "#endpoint/Endpoint.js";
12
12
  import { EndpointType } from "#endpoint/type/EndpointType.js";
13
13
  import { RootEndpoint } from "#endpoints/root";
14
- import { Diagnostic, InternalError, isDeepEqual, Logger } from "#general";
14
+ import { Diagnostic, InternalError, isDeepEqual, Logger, Observable } from "#general";
15
15
  import {
16
16
  AcceptedCommandList,
17
17
  AttributeList,
@@ -19,14 +19,15 @@ import {
19
19
  DeviceClassification,
20
20
  DeviceTypeModel,
21
21
  FeatureMap,
22
+ GeneratedCommandList,
22
23
  Matter,
23
24
  type FeatureBitmap,
24
25
  } from "#model";
25
26
  import type { ClientNode } from "#node/ClientNode.js";
26
27
  import type { Node } from "#node/Node.js";
27
28
  import { ReadScope, type Read, type ReadResult } from "#protocol";
29
+ import { ClientNodeStore } from "#storage/client/ClientNodeStore.js";
28
30
  import { DatasourceCache } from "#storage/client/DatasourceCache.js";
29
- import { ClientNodeStore } from "#storage/index.js";
30
31
  import type { AttributeId, ClusterId, ClusterType, CommandId, EndpointNumber } from "#types";
31
32
  import { Status } from "#types";
32
33
  import { ClientEventEmitter } from "./ClientEventEmitter.js";
@@ -52,6 +53,7 @@ export class ClientStructure {
52
53
  #pendingStructureEvents = Array<PendingEvent>();
53
54
  #delayedClusterEvents = new Array<ReadResult.EventValue>();
54
55
  #events: ClientStructureEvents;
56
+ #changed = Observable<[void]>();
55
57
 
56
58
  constructor(node: ClientNode) {
57
59
  this.#node = node;
@@ -64,6 +66,10 @@ export class ClientStructure {
64
66
  this.#events = this.#node.env.get(ClientStructureEvents);
65
67
  }
66
68
 
69
+ get changed() {
70
+ return this.#changed;
71
+ }
72
+
67
73
  /**
68
74
  * Load initial structure from cache.
69
75
  */
@@ -175,14 +181,16 @@ export class ClientStructure {
175
181
  // Apply changes
176
182
  const scope = ReadScope(request);
177
183
  for await (const chunk of changes) {
184
+ const chunkData = new Array<ReadResult.Report>();
178
185
  for (const change of chunk) {
186
+ chunkData.push(change);
179
187
  switch (change.kind) {
180
188
  case "attr-value":
181
189
  currentUpdates = await this.#mutateAttribute(change, scope, currentUpdates);
182
190
  break;
183
191
 
184
192
  case "event-value":
185
- this.#emitEvent(change, currentUpdates);
193
+ await this.#emitEvent(change, currentUpdates);
186
194
  break;
187
195
 
188
196
  case "attr-status":
@@ -197,7 +205,7 @@ export class ClientStructure {
197
205
  }
198
206
  }
199
207
 
200
- yield chunk;
208
+ yield chunkData;
201
209
  }
202
210
 
203
211
  // The last cluster still needs its changes applied
@@ -226,6 +234,7 @@ export class ClientStructure {
226
234
 
227
235
  // Likewise, we don't emit events until we've applied all structural changes
228
236
  this.#emitPendingStructureEvents();
237
+ await this.#emitPendingEvents();
229
238
  }
230
239
 
231
240
  /** Reference to the default subscription used when the node was started. */
@@ -280,7 +289,7 @@ export class ClientStructure {
280
289
  return currentUpdates;
281
290
  }
282
291
 
283
- #emitEvent(occurrence: ReadResult.EventValue, currentUpdates?: AttributeUpdates) {
292
+ async #emitEvent(occurrence: ReadResult.EventValue, currentUpdates?: AttributeUpdates) {
284
293
  const { endpointId, clusterId } = occurrence.path;
285
294
 
286
295
  const endpoint = this.#endpoints.get(endpointId);
@@ -291,7 +300,7 @@ export class ClientStructure {
291
300
  ) {
292
301
  this.#delayedClusterEvents.push(occurrence);
293
302
  } else {
294
- this.#eventEmitter(occurrence);
303
+ await this.#eventEmitter(occurrence);
295
304
  }
296
305
  }
297
306
 
@@ -370,7 +379,7 @@ export class ClientStructure {
370
379
  #synchronizeCluster(structure: EndpointStructure, cluster: ClusterStructure) {
371
380
  const { endpoint } = structure;
372
381
 
373
- // Generate a behavior if enough information is available
382
+ // Generate a behavior if enough information is available
374
383
  if (cluster.behavior === undefined) {
375
384
  if (cluster.store.initialValues) {
376
385
  const {
@@ -378,6 +387,7 @@ export class ClientStructure {
378
387
  [FeatureMap.id]: features,
379
388
  [AttributeList.id]: attributeList,
380
389
  [AcceptedCommandList.id]: commandList,
390
+ [GeneratedCommandList.id]: generatedCommandList,
381
391
  } = cluster.store.initialValues;
382
392
 
383
393
  if (typeof clusterRevision === "number") {
@@ -399,13 +409,21 @@ export class ClientStructure {
399
409
  (a, b) => a - b,
400
410
  );
401
411
  }
412
+
413
+ if (Array.isArray(generatedCommandList)) {
414
+ cluster.generatedCommands = (
415
+ generatedCommandList.filter(cmd => typeof cmd === "number") as CommandId[]
416
+ ).sort((a, b) => a - b);
417
+ }
402
418
  }
403
419
 
404
420
  if (
405
- cluster.revision !== undefined &&
406
- cluster.features !== undefined &&
407
- cluster.attributes !== undefined &&
408
- cluster.commands !== undefined
421
+ // All global attributes have fallbacks so we can't wait until we're sure we have them all. Instead
422
+ // wait until we are sure there is something useful. We therefore rely on unspecified behavior that all
423
+ // attributes travel consecutively to ensure we initialize fully as we have no other choice
424
+ cluster.attributes?.length ||
425
+ cluster.commands?.length ||
426
+ cluster.generatedCommands?.length
409
427
  ) {
410
428
  const behaviorType = PeerBehavior(cluster as PeerBehavior.ClusterShape);
411
429
 
@@ -628,7 +646,7 @@ export class ClientStructure {
628
646
  /**
629
647
  * Replace clusters after activation because fixed global attributes have changed.
630
648
  *
631
- * Currently we apply granular updates to clusters. This will possibly result in subtle errors if peers change in
649
+ * Currently, we apply granular updates to clusters. This will possibly result in subtle errors if peers change in
632
650
  * incompatible ways, but the backings are designed to be fairly resilient to this. This is simpler for API users
633
651
  * to deal with in the common case where they can just ignore. If it becomes problematic we can revert to replacing
634
652
  * entire endpoints or behaviors when there are structural changes.
@@ -742,8 +760,6 @@ export class ClientStructure {
742
760
  */
743
761
  #emitPendingStructureEvents() {
744
762
  const structureEvents = this.#pendingStructureEvents;
745
- const clusterEvents = this.#delayedClusterEvents;
746
- this.#delayedClusterEvents = [];
747
763
  this.#pendingStructureEvents = [];
748
764
  for (const event of structureEvents) {
749
765
  switch (event.kind) {
@@ -789,8 +805,14 @@ export class ClientStructure {
789
805
  }
790
806
  }
791
807
  }
808
+ this.#changed.emit();
809
+ }
810
+
811
+ async #emitPendingEvents() {
812
+ const clusterEvents = this.#delayedClusterEvents;
813
+ this.#delayedClusterEvents = [];
792
814
  for (const occurrence of clusterEvents) {
793
- this.#eventEmitter(occurrence);
815
+ await this.#eventEmitter(occurrence);
794
816
  }
795
817
  }
796
818
  }
@@ -5,12 +5,12 @@
5
5
  */
6
6
 
7
7
  import { RemoteDescriptor } from "#behavior/system/commissioning/RemoteDescriptor.js";
8
+ import { Crypto, ServerAddress, ServerAddressUdp } from "#general";
8
9
  import type { ClientNode } from "#node/ClientNode.js";
9
10
  import { IdentityService } from "#node/server/IdentityService.js";
10
11
  import type { ServerNode } from "#node/ServerNode.js";
11
12
  import { PeerAddress, PeerAddressMap, PeerAddressStore, PeerDescriptor } from "#protocol";
12
13
  import { FabricIndex, NodeId } from "#types";
13
- import { Crypto, ServerAddress, ServerAddressUdp } from "@matter/general";
14
14
 
15
15
  /**
16
16
  * This is an adapter for lower-level components in the protocol package.
@@ -74,17 +74,20 @@ export class NodePeerAddressStore extends PeerAddressStore {
74
74
 
75
75
  async updatePeer(peer: PeerDescriptor) {
76
76
  const node = this.#owner.peers.get(peer.address);
77
- if (!node) {
77
+ if (!node || !node.lifecycle.isInstalled) {
78
78
  return;
79
79
  }
80
80
 
81
- await node.act(agent => {
81
+ await node.act(async agent => {
82
+ await agent.context.transaction.addResources(agent.commissioning);
83
+ await agent.context.transaction.begin();
82
84
  const state = agent.commissioning.state;
83
85
  RemoteDescriptor.toLongForm(peer.discoveryData, state);
84
86
  if (peer.operationalAddress) {
85
87
  // TODO - modify lower tiers to pass along full set of operational addresses
86
88
  state.addresses = [peer.operationalAddress];
87
89
  }
90
+ await agent.context.transaction.commit();
88
91
  });
89
92
  }
90
93
 
@@ -25,6 +25,7 @@ import {
25
25
  ClusterComposer,
26
26
  ClusterId,
27
27
  ClusterRegistry,
28
+ ClusterType,
28
29
  Command,
29
30
  CommandId,
30
31
  MutableCluster,
@@ -78,11 +79,10 @@ export namespace PeerBehavior {
78
79
  kind: "discovered";
79
80
  id: ClusterId;
80
81
  revision: number;
81
- features: FeatureBitmap | number;
82
- attributes: AttributeId[];
83
- commands: CommandId[];
84
- attributeNames: Record<AttributeId, string>;
85
- commandNames: Record<CommandId, string>;
82
+ features?: FeatureBitmap | number;
83
+ attributes?: AttributeId[];
84
+ commands?: CommandId[];
85
+ generatedCommands?: CommandId[];
86
86
  }
87
87
 
88
88
  /**
@@ -103,12 +103,10 @@ function instrumentDiscoveredShape(shape: PeerBehavior.DiscoveredClusterShape) {
103
103
  return type;
104
104
  }
105
105
 
106
- let baseType: Behavior.Type;
106
+ let baseType: Behavior.Type | undefined;
107
107
  const standardCluster = ClusterRegistry.get(shape.id);
108
- if (standardCluster) {
108
+ if (standardCluster && !standardCluster.name.startsWith("Unknown cluster 0x")) {
109
109
  baseType = ClusterBehavior.for(standardCluster);
110
- } else {
111
- baseType = ClusterBehavior;
112
110
  }
113
111
 
114
112
  type = discoveredCache[fingerprint] = generateDiscoveredType(analysis, baseType);
@@ -138,30 +136,38 @@ function instrumentKnownShape(shape: PeerBehavior.KnownClusterShape) {
138
136
  return type;
139
137
  }
140
138
 
141
- function generateDiscoveredType(analysis: DiscoveredShapeAnalysis, baseType: Behavior.Type): ClusterBehavior.Type {
142
- // Ensure the input type is a ClusterBehavior
143
- if (!ClusterBehavior.is(baseType)) {
144
- throw new InternalError(`Base for cluster ${analysis.schema.name} is not a ClusterBehavior`);
145
- }
146
-
139
+ function generateDiscoveredType(analysis: DiscoveredShapeAnalysis, baseType?: Behavior.Type): ClusterBehavior.Type {
147
140
  let { schema } = analysis;
148
- let isExtended = false;
149
- const { attrSupportOverrides, extraAttrs, commandSupportOverrides, extraCommands } = analysis;
150
141
 
151
- // Obtain a ClusterType. This provides TLV for known elements
152
- let { cluster } = baseType;
153
- if (!cluster) {
142
+ let isExtended: boolean;
143
+ let cluster: ClusterType;
144
+
145
+ if (baseType) {
146
+ isExtended = false;
147
+
148
+ // Ensure the input type is a ClusterBehavior
149
+ if (!ClusterBehavior.is(baseType)) {
150
+ throw new InternalError(`Base for cluster ${analysis.schema.name} is not a ClusterBehavior`);
151
+ }
152
+
153
+ cluster = baseType.cluster;
154
+ } else {
155
+ isExtended = true;
156
+
154
157
  cluster = MutableCluster({ id: schema.id, name: schema.name, revision: schema.revision });
158
+
159
+ baseType = ClusterBehavior;
155
160
  }
156
161
 
162
+ const { attrSupportOverrides, extraAttrs, commandSupportOverrides, extraCommands } = analysis;
163
+
157
164
  // Identify known features the device supports
158
165
  let supportedFeatures = analysis.shape.features;
159
166
  if (typeof supportedFeatures === "number") {
160
- if (supportedFeatures) {
161
- supportedFeatures = cluster.attributes.featureMap.schema.decode(supportedFeatures as any) as FeatureBitmap;
162
- } else {
163
- supportedFeatures = {};
164
- }
167
+ supportedFeatures = cluster.attributes.featureMap.schema.decode(supportedFeatures as any) as FeatureBitmap;
168
+ }
169
+ if (supportedFeatures === undefined) {
170
+ supportedFeatures = {};
165
171
  }
166
172
 
167
173
  // If there are features supported, customize the ClusterModel and ClusterType accordingly
@@ -354,18 +360,18 @@ function DiscoveredShapeAnalysis(shape: PeerBehavior.DiscoveredClusterShape): Di
354
360
  if (typeof shape.features === "number") {
355
361
  featureBitmap = shape.features;
356
362
  } else {
357
- featureBitmap = EncodedBitmap(schema.featureMap, shape.features);
363
+ featureBitmap = EncodedBitmap(schema.featureMap, shape.features ?? {});
358
364
  }
359
365
 
360
366
  const attrSupportOverrides = new Map<AttributeModel, boolean>();
361
- const extraAttrs = new Set<number>(shape.attributes);
367
+ const extraAttrs = new Set<number>(shape.attributes ?? []);
362
368
  for (const attr of schema.attributes) {
363
369
  maybeOverrideSupport(standardCluster, attr, extraAttrs, attrSupportOverrides);
364
370
  extraAttrs.delete(attr.id as AttributeId);
365
371
  }
366
372
 
367
373
  const commandSupportOverrides = new Map<CommandModel, boolean>();
368
- const extraCommands = new Set(shape.commands);
374
+ const extraCommands = new Set(shape.commands ?? []);
369
375
  for (const command of schema.commands) {
370
376
  maybeOverrideSupport(standardCluster, command, extraCommands, commandSupportOverrides);
371
377
  extraCommands.delete(command.id as CommandId);
@@ -449,6 +449,7 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
449
449
  if (fabric !== undefined && !keepSubscriptions) {
450
450
  let clearedCount = 0;
451
451
  for (const sess of this.#context.sessions.sessions) {
452
+ // TODO Adjust this filtering when subscriptions move to Peer
452
453
  if (!PeerAddress.is(sess.peerAddress, session.peerAddress)) {
453
454
  // Ignore subscriptions from other peers
454
455
  continue;