@matter-server/ws-controller 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/controller/BorderRouterDiscovery.d.ts +66 -0
- package/dist/esm/controller/BorderRouterDiscovery.d.ts.map +1 -0
- package/dist/esm/controller/BorderRouterDiscovery.js +450 -0
- package/dist/esm/controller/BorderRouterDiscovery.js.map +6 -0
- package/dist/esm/controller/ControllerCommandHandler.d.ts +0 -1
- package/dist/esm/controller/ControllerCommandHandler.d.ts.map +1 -1
- package/dist/esm/controller/ControllerCommandHandler.js +30 -35
- package/dist/esm/controller/ControllerCommandHandler.js.map +1 -1
- package/dist/esm/controller/CustomClusterPoller.d.ts +5 -10
- package/dist/esm/controller/CustomClusterPoller.d.ts.map +1 -1
- package/dist/esm/controller/CustomClusterPoller.js +19 -17
- package/dist/esm/controller/CustomClusterPoller.js.map +1 -1
- package/dist/esm/controller/MatterController.d.ts +2 -0
- package/dist/esm/controller/MatterController.d.ts.map +1 -1
- package/dist/esm/controller/MatterController.js +8 -0
- package/dist/esm/controller/MatterController.js.map +1 -1
- package/dist/esm/server/WebSocketControllerHandler.d.ts.map +1 -1
- package/dist/esm/server/WebSocketControllerHandler.js +3 -0
- package/dist/esm/server/WebSocketControllerHandler.js.map +1 -1
- package/dist/esm/util/formatNodeId.d.ts +3 -3
- package/dist/esm/util/formatNodeId.d.ts.map +1 -1
- package/dist/esm/util/formatNodeId.js +3 -2
- package/dist/esm/util/formatNodeId.js.map +1 -1
- package/package.json +5 -5
- package/src/controller/BorderRouterDiscovery.ts +613 -0
- package/src/controller/ControllerCommandHandler.ts +32 -37
- package/src/controller/CustomClusterPoller.ts +26 -20
- package/src/controller/MatterController.ts +10 -0
- package/src/server/WebSocketControllerHandler.ts +3 -0
- package/src/util/formatNodeId.ts +7 -5
|
@@ -110,10 +110,7 @@ const RECONNECT_TIMEOUT = Minutes(3);
|
|
|
110
110
|
* BasicInformation (0x28) covers firmware version, product name, etc.
|
|
111
111
|
* BridgedDeviceBasicInformation (0x39) covers the same for bridged child nodes.
|
|
112
112
|
*/
|
|
113
|
-
const FULL_UPDATE_CLUSTER_IDS = new Set<ClusterId>([
|
|
114
|
-
BasicInformation.Cluster.id,
|
|
115
|
-
BridgedDeviceBasicInformation.Cluster.id,
|
|
116
|
-
]);
|
|
113
|
+
const FULL_UPDATE_CLUSTER_IDS = new Set<ClusterId>([BasicInformation.id, BridgedDeviceBasicInformation.id]);
|
|
117
114
|
|
|
118
115
|
/**
|
|
119
116
|
* Determine the Matter specification version from cached attributes.
|
|
@@ -151,8 +148,8 @@ export class ControllerCommandHandler {
|
|
|
151
148
|
#controller: CommissioningController;
|
|
152
149
|
#started = false;
|
|
153
150
|
#connected = false;
|
|
154
|
-
#bleEnabled
|
|
155
|
-
#otaEnabled
|
|
151
|
+
readonly #bleEnabled: boolean;
|
|
152
|
+
readonly #otaEnabled: boolean;
|
|
156
153
|
/** Node management and attribute cache */
|
|
157
154
|
#nodes = new Nodes();
|
|
158
155
|
/** Cache of available updates keyed by nodeId */
|
|
@@ -188,12 +185,27 @@ export class ControllerCommandHandler {
|
|
|
188
185
|
// Initialize custom cluster poller for Eve energy attributes etc.
|
|
189
186
|
// Reads automatically trigger change events through the normal attribute flow
|
|
190
187
|
this.#customClusterPoller = new CustomClusterPoller({
|
|
191
|
-
nodeConnected:
|
|
192
|
-
handleReadAttributes: (
|
|
193
|
-
this.handleReadAttributes(nodeId, paths, fabricFiltered),
|
|
188
|
+
nodeConnected: peer => !!(this.#nodes.has(peer.nodeId) && this.#nodes.get(peer.nodeId).isConnected),
|
|
189
|
+
handleReadAttributes: (peer, paths, fabricFiltered) =>
|
|
190
|
+
this.handleReadAttributes(peer.nodeId, paths, fabricFiltered),
|
|
194
191
|
});
|
|
195
192
|
}
|
|
196
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Build the canonical PeerAddress for the given node on this controller's fabric.
|
|
196
|
+
*
|
|
197
|
+
* Throws if the controller's fabric is not yet resolved. Callers must run after
|
|
198
|
+
* controller start; a silent fallback would intern PeerAddressMap entries under the
|
|
199
|
+
* wrong fabric index and leak poller registrations.
|
|
200
|
+
*/
|
|
201
|
+
#peerOf(nodeId: NodeId): PeerAddress {
|
|
202
|
+
const fabric = this.#controller.fabric;
|
|
203
|
+
if (fabric === undefined) {
|
|
204
|
+
throw new Error(`Cannot resolve PeerAddress for node ${nodeId}: controller fabric is not initialized`);
|
|
205
|
+
}
|
|
206
|
+
return PeerAddress({ fabricIndex: fabric.fabricIndex, nodeId });
|
|
207
|
+
}
|
|
208
|
+
|
|
197
209
|
/**
|
|
198
210
|
* Format a NodeId as a PeerAddress string for logging.
|
|
199
211
|
* Uses the controller's fabric index when available, otherwise "?" is used.
|
|
@@ -254,14 +266,14 @@ export class ControllerCommandHandler {
|
|
|
254
266
|
// Handle updateAvailable events - cache the update info
|
|
255
267
|
softwareUpdateManagerEvents.updateAvailable.on(
|
|
256
268
|
(peerAddress: PeerAddress, updateDetails: SoftwareUpdateInfo) => {
|
|
257
|
-
logger.info(`Update available for node ${peerAddress.nodeId}:`, updateDetails);
|
|
269
|
+
logger.info(`Update available for node ${this.formatNode(peerAddress.nodeId)}:`, updateDetails);
|
|
258
270
|
this.#availableUpdates.set(peerAddress.nodeId, updateDetails);
|
|
259
271
|
},
|
|
260
272
|
);
|
|
261
273
|
|
|
262
274
|
// Handle updateDone events - clear the cached update info
|
|
263
275
|
softwareUpdateManagerEvents.updateDone.on((peerAddress: PeerAddress) => {
|
|
264
|
-
logger.info(`Update done for node ${peerAddress.nodeId}`);
|
|
276
|
+
logger.info(`Update done for node ${this.formatNode(peerAddress.nodeId)}`);
|
|
265
277
|
this.#availableUpdates.delete(peerAddress.nodeId);
|
|
266
278
|
});
|
|
267
279
|
|
|
@@ -315,7 +327,7 @@ export class ControllerCommandHandler {
|
|
|
315
327
|
attributeCache.update(node);
|
|
316
328
|
const attributes = attributeCache.get(nodeId);
|
|
317
329
|
if (attributes) {
|
|
318
|
-
this.#customClusterPoller.registerNode(nodeId, attributes);
|
|
330
|
+
this.#customClusterPoller.registerNode(this.#peerOf(nodeId), attributes);
|
|
319
331
|
}
|
|
320
332
|
}
|
|
321
333
|
|
|
@@ -381,7 +393,7 @@ export class ControllerCommandHandler {
|
|
|
381
393
|
// Register for custom cluster polling (e.g., Eve energy)
|
|
382
394
|
const attributes = attributeCache.get(nodeId);
|
|
383
395
|
if (attributes) {
|
|
384
|
-
this.#customClusterPoller.registerNode(nodeId, attributes);
|
|
396
|
+
this.#customClusterPoller.registerNode(this.#peerOf(nodeId), attributes);
|
|
385
397
|
}
|
|
386
398
|
}
|
|
387
399
|
|
|
@@ -615,10 +627,6 @@ export class ControllerCommandHandler {
|
|
|
615
627
|
await this.#controller.updateFabricLabel(label);
|
|
616
628
|
}
|
|
617
629
|
|
|
618
|
-
disconnectNode(nodeId: NodeId) {
|
|
619
|
-
return this.#controller.disconnectNode(nodeId, true);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
630
|
async handleWriteAttribute(data: WriteAttributeRequest): Promise<AttributeResponseStatus> {
|
|
623
631
|
const { nodeId, endpointId, clusterId, attributeId } = data;
|
|
624
632
|
let { value } = data;
|
|
@@ -792,7 +800,7 @@ export class ControllerCommandHandler {
|
|
|
792
800
|
}
|
|
793
801
|
|
|
794
802
|
const { onNetworkOnly, wifiCredentials: wifiNetwork, threadCredentials: threadNetwork } = data;
|
|
795
|
-
|
|
803
|
+
return {
|
|
796
804
|
commissioning: {
|
|
797
805
|
nodeId: data.nodeId,
|
|
798
806
|
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
@@ -817,7 +825,6 @@ export class ControllerCommandHandler {
|
|
|
817
825
|
},
|
|
818
826
|
passcode,
|
|
819
827
|
};
|
|
820
|
-
return options;
|
|
821
828
|
}
|
|
822
829
|
|
|
823
830
|
async commissionNode(data: CommissioningRequest): Promise<CommissioningResponse> {
|
|
@@ -1040,7 +1047,7 @@ export class ControllerCommandHandler {
|
|
|
1040
1047
|
this.#reconnectTimers.get(nodeId)?.stop();
|
|
1041
1048
|
this.#reconnectTimers.delete(nodeId);
|
|
1042
1049
|
this.#nodes.delete(nodeId);
|
|
1043
|
-
this.#customClusterPoller.unregisterNode(nodeId);
|
|
1050
|
+
this.#customClusterPoller.unregisterNode(this.#peerOf(nodeId));
|
|
1044
1051
|
this.#availableUpdates.delete(nodeId);
|
|
1045
1052
|
}
|
|
1046
1053
|
|
|
@@ -1061,7 +1068,7 @@ export class ControllerCommandHandler {
|
|
|
1061
1068
|
},
|
|
1062
1069
|
Read.Attribute({
|
|
1063
1070
|
endpoint: EndpointNumber(0),
|
|
1064
|
-
cluster: OperationalCredentials
|
|
1071
|
+
cluster: OperationalCredentials,
|
|
1065
1072
|
attributes: "fabrics",
|
|
1066
1073
|
}),
|
|
1067
1074
|
),
|
|
@@ -1110,16 +1117,10 @@ export class ControllerCommandHandler {
|
|
|
1110
1117
|
|
|
1111
1118
|
logger.info("Setting ACL entries", aclEntries);
|
|
1112
1119
|
|
|
1113
|
-
const { status } = await this.#writeAttribute(
|
|
1114
|
-
nodeId,
|
|
1115
|
-
EndpointNumber(0),
|
|
1116
|
-
AccessControl.Cluster.id,
|
|
1117
|
-
"acl",
|
|
1118
|
-
aclEntries,
|
|
1119
|
-
);
|
|
1120
|
+
const { status } = await this.#writeAttribute(nodeId, EndpointNumber(0), AccessControl.id, "acl", aclEntries);
|
|
1120
1121
|
return [
|
|
1121
1122
|
{
|
|
1122
|
-
path: { endpoint_id: 0, cluster_id: AccessControl.
|
|
1123
|
+
path: { endpoint_id: 0, cluster_id: AccessControl.id, attribute_id: 0 },
|
|
1123
1124
|
status,
|
|
1124
1125
|
},
|
|
1125
1126
|
];
|
|
@@ -1144,16 +1145,10 @@ export class ControllerCommandHandler {
|
|
|
1144
1145
|
|
|
1145
1146
|
logger.info("Setting bindings on endpoint", endpointId, bindingEntries);
|
|
1146
1147
|
|
|
1147
|
-
const { status } = await this.#writeAttribute(
|
|
1148
|
-
nodeId,
|
|
1149
|
-
endpointId,
|
|
1150
|
-
Binding.Cluster.id,
|
|
1151
|
-
"binding",
|
|
1152
|
-
bindingEntries,
|
|
1153
|
-
);
|
|
1148
|
+
const { status } = await this.#writeAttribute(nodeId, endpointId, Binding.id, "binding", bindingEntries);
|
|
1154
1149
|
return [
|
|
1155
1150
|
{
|
|
1156
|
-
path: { endpoint_id: endpointId, cluster_id: Binding.
|
|
1151
|
+
path: { endpoint_id: endpointId, cluster_id: Binding.id, attribute_id: 0 },
|
|
1157
1152
|
status,
|
|
1158
1153
|
},
|
|
1159
1154
|
];
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* a custom cluster without standard Matter subscription support.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { CancelablePromise, Duration, Logger, Millis,
|
|
13
|
+
import { CancelablePromise, Duration, Logger, Millis, Time, Timer } from "@matter/main";
|
|
14
|
+
import { PeerAddress, PeerAddressMap } from "@matter/main/protocol";
|
|
14
15
|
import { AttributesData } from "../types/CommandHandler.js";
|
|
16
|
+
import { formatNodeId } from "../util/formatNodeId.js";
|
|
15
17
|
|
|
16
18
|
const logger = Logger.get("CustomClusterPoller");
|
|
17
19
|
|
|
@@ -42,8 +44,12 @@ const MAX_INITIAL_DELAY_MS = 30_000;
|
|
|
42
44
|
type AttributePath = string;
|
|
43
45
|
|
|
44
46
|
export interface NodeAttributeReader {
|
|
45
|
-
handleReadAttributes(
|
|
46
|
-
|
|
47
|
+
handleReadAttributes(
|
|
48
|
+
peer: PeerAddress,
|
|
49
|
+
attributePaths: string[],
|
|
50
|
+
fabricFiltered?: boolean,
|
|
51
|
+
): Promise<AttributesData>;
|
|
52
|
+
nodeConnected(peer: PeerAddress): boolean;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/**
|
|
@@ -112,7 +118,7 @@ export function checkPolledAttributes(attributes: AttributesData): Set<Attribute
|
|
|
112
118
|
* Manages polling of custom cluster attributes for multiple nodes.
|
|
113
119
|
*/
|
|
114
120
|
export class CustomClusterPoller {
|
|
115
|
-
#polledAttributes = new
|
|
121
|
+
#polledAttributes = new PeerAddressMap<Set<AttributePath>>();
|
|
116
122
|
#pollerTimer: Timer;
|
|
117
123
|
#attributeReader: NodeAttributeReader;
|
|
118
124
|
#isPolling = false;
|
|
@@ -130,18 +136,18 @@ export class CustomClusterPoller {
|
|
|
130
136
|
* Register a node for polling if it has custom attributes that need polling.
|
|
131
137
|
* Call this after a node is connected and its attributes are available.
|
|
132
138
|
*/
|
|
133
|
-
registerNode(
|
|
139
|
+
registerNode(peer: PeerAddress, attributes: AttributesData): void {
|
|
134
140
|
const attributesToPoll = checkPolledAttributes(attributes);
|
|
135
141
|
|
|
136
142
|
if (attributesToPoll.size === 0) {
|
|
137
143
|
// Remove from polling if it was previously registered
|
|
138
|
-
this.unregisterNode(
|
|
144
|
+
this.unregisterNode(peer);
|
|
139
145
|
return;
|
|
140
146
|
}
|
|
141
147
|
|
|
142
|
-
this.#polledAttributes.set(
|
|
148
|
+
this.#polledAttributes.set(peer, attributesToPoll);
|
|
143
149
|
logger.info(
|
|
144
|
-
`Registered node ${
|
|
150
|
+
`Registered node ${formatNodeId(peer)} for custom attribute polling: ${Array.from(attributesToPoll).join(", ")}`,
|
|
145
151
|
);
|
|
146
152
|
|
|
147
153
|
// Start the poller if not already running
|
|
@@ -151,9 +157,9 @@ export class CustomClusterPoller {
|
|
|
151
157
|
/**
|
|
152
158
|
* Unregister a node from polling (e.g., when decommissioned or disconnected).
|
|
153
159
|
*/
|
|
154
|
-
unregisterNode(
|
|
155
|
-
if (this.#polledAttributes.delete(
|
|
156
|
-
logger.info(`Unregistered node ${
|
|
160
|
+
unregisterNode(peer: PeerAddress): void {
|
|
161
|
+
if (this.#polledAttributes.delete(peer)) {
|
|
162
|
+
logger.info(`Unregistered node ${formatNodeId(peer)} from custom attribute polling`);
|
|
157
163
|
}
|
|
158
164
|
if (this.#polledAttributes.size === 0) {
|
|
159
165
|
this.#pollerTimer.stop();
|
|
@@ -217,13 +223,13 @@ export class CustomClusterPoller {
|
|
|
217
223
|
if (this.#closed) {
|
|
218
224
|
break;
|
|
219
225
|
}
|
|
220
|
-
const [
|
|
221
|
-
if (!this.#polledAttributes.has(
|
|
226
|
+
const [peer, attributePaths] = entries[i];
|
|
227
|
+
if (!this.#polledAttributes.has(peer)) {
|
|
222
228
|
// Node was removed, so skip it
|
|
223
229
|
continue;
|
|
224
230
|
}
|
|
225
231
|
polledNodes++;
|
|
226
|
-
await this.#pollNode(
|
|
232
|
+
await this.#pollNode(peer, attributePaths);
|
|
227
233
|
// Small delay between nodes to avoid overwhelming the network
|
|
228
234
|
// Only add this delay if there are more nodes remaining to be polled
|
|
229
235
|
if (i < entries.length - 1) {
|
|
@@ -249,25 +255,25 @@ export class CustomClusterPoller {
|
|
|
249
255
|
* Poll a single node for its custom attributes.
|
|
250
256
|
* The read will automatically trigger change events through the normal attribute flow.
|
|
251
257
|
*/
|
|
252
|
-
async #pollNode(
|
|
253
|
-
if (!this.#attributeReader.nodeConnected(
|
|
254
|
-
logger.debug(`Node ${
|
|
258
|
+
async #pollNode(peer: PeerAddress, attributePaths: Set<AttributePath>): Promise<void> {
|
|
259
|
+
if (!this.#attributeReader.nodeConnected(peer)) {
|
|
260
|
+
logger.debug(`Node ${formatNodeId(peer)} not connected, skipping custom attribute polling`);
|
|
255
261
|
return;
|
|
256
262
|
}
|
|
257
263
|
const paths = Array.from(attributePaths);
|
|
258
|
-
logger.debug(`Polling ${paths.length} custom attributes for node ${
|
|
264
|
+
logger.debug(`Polling ${paths.length} custom attributes for node ${formatNodeId(peer)}`);
|
|
259
265
|
|
|
260
266
|
try {
|
|
261
267
|
// Read with fabricFiltered=true as per Eve's requirements
|
|
262
268
|
// This automatically updates the attribute cache and triggers change events
|
|
263
|
-
const readPromise = this.#attributeReader.handleReadAttributes(
|
|
269
|
+
const readPromise = this.#attributeReader.handleReadAttributes(peer, paths, true);
|
|
264
270
|
this.#currentReadPromise = readPromise.then(
|
|
265
271
|
() => {},
|
|
266
272
|
() => {},
|
|
267
273
|
);
|
|
268
274
|
await readPromise;
|
|
269
275
|
} catch (error) {
|
|
270
|
-
logger.warn(`Failed to poll custom attributes for node ${
|
|
276
|
+
logger.warn(`Failed to poll custom attributes for node ${formatNodeId(peer)}: `, error);
|
|
271
277
|
} finally {
|
|
272
278
|
this.#currentReadPromise = undefined;
|
|
273
279
|
}
|
|
@@ -23,6 +23,7 @@ import { VendorId } from "@matter/main/types";
|
|
|
23
23
|
import { CommissioningController } from "@project-chip/matter.js";
|
|
24
24
|
import { Readable } from "node:stream";
|
|
25
25
|
import { ConfigStorage } from "../server/ConfigStorage.js";
|
|
26
|
+
import { BorderRouterDiscovery } from "./BorderRouterDiscovery.js";
|
|
26
27
|
import { ControllerCommandHandler } from "./ControllerCommandHandler.js";
|
|
27
28
|
import { LegacyDataInjector, LegacyServerData } from "./LegacyDataInjector.js";
|
|
28
29
|
import { resolveServerId } from "./ServerIdResolver.js";
|
|
@@ -75,6 +76,7 @@ export class MatterController {
|
|
|
75
76
|
#legacyCommissionedDates?: Map<string, Timestamp>;
|
|
76
77
|
#enableTestNetDcl = false;
|
|
77
78
|
#disableOtaProvider = true;
|
|
79
|
+
readonly #borderRouterDiscovery: BorderRouterDiscovery;
|
|
78
80
|
|
|
79
81
|
static async create(
|
|
80
82
|
environment: Environment,
|
|
@@ -144,6 +146,7 @@ export class MatterController {
|
|
|
144
146
|
|
|
145
147
|
constructor(environment: Environment, config: ConfigStorage, options: MatterControllerOptions, serverId: string) {
|
|
146
148
|
this.#env = environment;
|
|
149
|
+
this.#borderRouterDiscovery = new BorderRouterDiscovery(this.#env);
|
|
147
150
|
this.#config = config;
|
|
148
151
|
this.#serverId = serverId;
|
|
149
152
|
this.#serverVersion = options.serverVersion ?? "0.0.0";
|
|
@@ -210,6 +213,8 @@ export class MatterController {
|
|
|
210
213
|
initPromises.push(this.#enableTestOtaImages());
|
|
211
214
|
}
|
|
212
215
|
|
|
216
|
+
initPromises.push(this.#borderRouterDiscovery.start());
|
|
217
|
+
|
|
213
218
|
try {
|
|
214
219
|
await MatterAggregateError.allSettled(initPromises);
|
|
215
220
|
} catch (error) {
|
|
@@ -221,6 +226,10 @@ export class MatterController {
|
|
|
221
226
|
return this.#commandHandler;
|
|
222
227
|
}
|
|
223
228
|
|
|
229
|
+
get borderRouters(): BorderRouterDiscovery {
|
|
230
|
+
return this.#borderRouterDiscovery;
|
|
231
|
+
}
|
|
232
|
+
|
|
224
233
|
/**
|
|
225
234
|
* Get the DCL vendor info service instance.
|
|
226
235
|
* Lazily initializes the service if not already present.
|
|
@@ -294,6 +303,7 @@ export class MatterController {
|
|
|
294
303
|
}
|
|
295
304
|
|
|
296
305
|
async stop() {
|
|
306
|
+
await this.#borderRouterDiscovery.stop();
|
|
297
307
|
await this.#commandHandler?.close(); // This closes also the controller instance if started
|
|
298
308
|
}
|
|
299
309
|
|
|
@@ -470,6 +470,9 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
470
470
|
case "set_thread_dataset":
|
|
471
471
|
result = await this.#handleSetThreadDataset(args);
|
|
472
472
|
break;
|
|
473
|
+
case "get_thread_border_routers":
|
|
474
|
+
result = this.#controller.borderRouters.list();
|
|
475
|
+
break;
|
|
473
476
|
case "open_commissioning_window":
|
|
474
477
|
result = await this.#handleOpenCommissioningWindow(args);
|
|
475
478
|
break;
|
package/src/util/formatNodeId.ts
CHANGED
|
@@ -5,16 +5,18 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { FabricIndex, NodeId } from "@matter/main";
|
|
8
|
+
import { PeerAddress } from "@matter/main/protocol";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* Format a NodeId as a
|
|
11
|
+
* Format a NodeId or PeerAddress as a string for logging.
|
|
11
12
|
* Uses the Matter address format: @fabricIndexDecimal:nodeIdHex (e.g., "@1:a", "@10:1f").
|
|
12
13
|
*
|
|
13
|
-
* @param nodeId The node ID to format (rendered in hexadecimal)
|
|
14
|
-
* @param fabricIndex The fabric index (rendered in decimal). If omitted or unknown, "?" is used.
|
|
15
14
|
* @returns Formatted PeerAddress string like "@1:a", "@10:1f", or "@?:1f" when fabric index is unknown.
|
|
16
15
|
*/
|
|
17
|
-
export function formatNodeId(
|
|
18
|
-
|
|
16
|
+
export function formatNodeId(peer: PeerAddress): string;
|
|
17
|
+
export function formatNodeId(nodeId: NodeId, fabricIndex?: FabricIndex): string;
|
|
18
|
+
export function formatNodeId(arg: NodeId | PeerAddress, fabricIndex?: FabricIndex): string {
|
|
19
|
+
const [nodeId, resolvedFabricIndex] = typeof arg === "object" ? [arg.nodeId, arg.fabricIndex] : [arg, fabricIndex];
|
|
20
|
+
const fabricIndexPart = resolvedFabricIndex === undefined ? "?" : resolvedFabricIndex.toString(10);
|
|
19
21
|
return `@${fabricIndexPart}:${nodeId.toString(16)}`;
|
|
20
22
|
}
|