@matter-server/ws-client 1.1.8-alpha.0-20260707-c552b9d → 1.2.0

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.
@@ -4,64 +4,205 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ import type { BorderRouterEntry } from "@matter/thread-br-client";
7
8
  import type { MatterNodeData } from "./node.js";
8
9
  // Re-export so consumers can import the MatterNodeData type from this module
9
10
  export type { MatterNodeData } from "./node.js";
11
+ export type { BorderRouterEntry } from "@matter/thread-br-client";
10
12
 
11
13
  /** Attribute data stored as path -> value mapping */
12
14
  export type AttributesData = { [key: string]: unknown };
13
15
 
16
+ /*
17
+ * The interfaces below decode the Thread Network Diagnostic TLVs a Border Router / router
18
+ * reports (per the Thread spec / OpenThread `netdiag`). They are all part of the Thread
19
+ * Network diagnostics feature and are @since schema 12.
20
+ */
21
+
22
+ /** CONNECTIVITY TLV — a node's view of its links to neighbors and the leader. @since schema 12 */
23
+ export interface ThreadConnectivity {
24
+ /** Suitability as a parent: -1 low, 0 medium, 1 high. */
25
+ parentPriority: -1 | 0 | 1;
26
+ /** Count of neighbors with link quality 3 (best) / 2 / 1 (worst). */
27
+ linkQuality3: number;
28
+ linkQuality2: number;
29
+ linkQuality1: number;
30
+ /** Routing cost from this node to the leader. */
31
+ leaderCost: number;
32
+ /** Router-ID assignment sequence number (bumped when the router set changes). */
33
+ idSequence: number;
34
+ /** Number of active routers in the Thread network. */
35
+ activeRouters: number;
36
+ /** Buffer size a parent reserves for a sleepy child (bytes). */
37
+ sedBufferSize: number;
38
+ /** Max IPv6 datagrams a parent queues for a sleepy child. */
39
+ sedDatagramCount: number;
40
+ }
41
+
42
+ /** One neighbor-router row within {@link ThreadRoute64}. @since schema 12 */
43
+ export interface ThreadRoute64Entry {
44
+ /** Router ID of the neighbor. */
45
+ routerId: number;
46
+ /** Link quality of packets received from / sent to that router (0–3). */
47
+ linkQualityIn: number;
48
+ linkQualityOut: number;
49
+ /** Routing cost to that router. */
50
+ routeCost: number;
51
+ }
52
+
53
+ /** ROUTE64 TLV — the node's routing table to other routers. @since schema 12 */
54
+ export interface ThreadRoute64 {
55
+ /** Router-ID assignment sequence number. */
56
+ idSequence: number;
57
+ entries: ThreadRoute64Entry[];
58
+ }
59
+
60
+ /** LEADER_DATA TLV — the current leader / partition identity. @since schema 12 */
61
+ export interface ThreadLeaderData {
62
+ /** Thread partition ID (changes on partition merge/split). */
63
+ partitionId: number;
64
+ /** Leader weighting used to arbitrate leadership. */
65
+ weighting: number;
66
+ /** Full and stable-only Network Data version counters. */
67
+ dataVersion: number;
68
+ stableDataVersion: number;
69
+ /** Router ID of the leader. */
70
+ leaderRouterId: number;
71
+ }
72
+
73
+ /** MAC_COUNTERS TLV — IEEE 802.15.4 MAC packet counters since last reset. @since schema 12 */
74
+ export interface ThreadMacCounters {
75
+ ifInUnknownProtos: number;
76
+ ifInErrors: number;
77
+ ifOutErrors: number;
78
+ ifInUcastPkts: number;
79
+ ifInBroadcastPkts: number;
80
+ ifInDiscards: number;
81
+ ifOutUcastPkts: number;
82
+ ifOutBroadcastPkts: number;
83
+ ifOutDiscards: number;
84
+ }
85
+
86
+ /** MODE TLV — device capability/role flags. @since schema 12 */
87
+ export interface ThreadMode {
88
+ /** Radio stays on when idle (a non-sleepy device). */
89
+ rxOnWhenIdle: boolean;
90
+ /** Full Thread Device (router-eligible) vs a Minimal Thread Device. */
91
+ ftd: boolean;
92
+ /** Requests the full Network Data vs the stable subset. */
93
+ fullNetworkData: boolean;
94
+ }
95
+
96
+ /** CHILD_TABLE TLV entry — one child attached to this (router) node. @since schema 12 */
97
+ export interface ThreadChildTableEntry {
98
+ /** Child timeout as a 2^exponent value and its resolved seconds. */
99
+ timeoutExponent: number;
100
+ timeoutSeconds: number;
101
+ /** Link quality of packets received from the child (0–3). */
102
+ incomingLinkQuality: number;
103
+ /** Child ID (low bits of the child's RLOC16). */
104
+ childId: number;
105
+ mode: ThreadMode;
106
+ }
107
+
108
+ /** MLE_COUNTERS TLV — Mesh Link Establishment role/state counters. @since schema 12 */
109
+ export interface ThreadMleCounters {
110
+ /** Number of times the node entered each role. */
111
+ disabledRole: number;
112
+ detachedRole: number;
113
+ childRole: number;
114
+ routerRole: number;
115
+ leaderRole: number;
116
+ /** Attach / partition-change bookkeeping. */
117
+ attachAttempts: number;
118
+ partitionIdChanges: number;
119
+ betterPartitionAttachAttempts: number;
120
+ parentChanges: number;
121
+ /** Cumulative time spent tracked / in each role, in milliseconds (64-bit). */
122
+ trackedTime: bigint;
123
+ disabledTime: bigint;
124
+ detachedTime: bigint;
125
+ childTime: bigint;
126
+ routerTime: bigint;
127
+ leaderTime: bigint;
128
+ }
129
+
14
130
  /**
15
- * Single Thread Border Router record discovered via mDNS.
16
- *
17
- * The extended address (xa) is the 64-bit Thread MAC of the border agent and serves as
18
- * the primary key. It is the same value as ThreadNetworkDiagnostics.NeighborTable.extAddress
19
- * for a BR that is itself a Thread router, which is what allows the dashboard to join
20
- * BRs onto external-device entries seen in commissioned-node neighbor tables.
131
+ * Diagnostics for a single Thread node, assembled from the diagnostic TLVs it returned.
132
+ * Every field is optional: a node only reports the TLVs it supports / were requested.
133
+ * @since schema 12
21
134
  */
22
- export interface BorderRouterEntry {
23
- /** 16-char uppercase hex of the 64-bit Thread MAC. Primary key. */
24
- extAddressHex: string;
25
- /** 16-char uppercase hex of the extended PAN ID, when known. */
26
- extendedPanIdHex?: string;
27
- /** Friendly Thread network name (`_meshcop` TXT "nn"). */
28
- networkName?: string;
29
- /** Vendor name (`_meshcop` TXT "vn"). */
135
+ export interface ThreadDiagnosticsNode {
136
+ /** 16-char uppercase hex of the 64-bit Thread MAC (extended address). */
137
+ extMacAddress?: string;
138
+ /** Short 16-bit routing locator (router+child) within the mesh. */
139
+ rloc16?: number;
140
+ mode?: ThreadMode;
141
+ /** Polling/child timeout in seconds. */
142
+ timeout?: number;
143
+ connectivity?: ThreadConnectivity;
144
+ route64?: ThreadRoute64;
145
+ leaderData?: ThreadLeaderData;
146
+ /** Hex-encoded raw Network Data blob (prefixes, routes, services). */
147
+ networkData?: string;
148
+ /** Per-node IPv6 addresses, each 16-byte address as uppercase hex. */
149
+ ipv6Addresses?: string[];
150
+ macCounters?: ThreadMacCounters;
151
+ childTable?: ThreadChildTableEntry[];
152
+ /** Supported channel pages (radio band identifiers). */
153
+ channelPages?: number[];
154
+ /** Max child timeout this node grants, in seconds. */
155
+ maxChildTimeout?: number;
156
+ /** 16-char uppercase hex EUI-64 (factory-assigned identifier). */
157
+ eui64?: string;
158
+ /** Thread protocol version the node implements. */
159
+ version?: number;
160
+ /** Vendor identity strings (VENDOR_NAME / MODEL / SW_VERSION TLVs). */
30
161
  vendorName?: string;
31
- /** Model name (`_meshcop` TXT "mn"). */
32
- modelName?: string;
33
- /** mDNS hostname from the SRV target, e.g. "Kuche.local.". */
34
- hostname?: string;
35
- /** Sorted IPv4 + IPv6 addresses resolved from the SRV target's A/AAAA records. */
36
- addresses: string[];
37
- /** Service port from the `_meshcop` SRV record. */
38
- meshcopPort?: number;
39
- /** Service port from the `_trel` SRV record. */
40
- trelPort?: number;
41
- /** Thread version, e.g. "1.3.0" (`_meshcop` TXT "tv"). */
42
- threadVersion?: string;
43
- /** Border agent ID hex (`_meshcop` TXT "dd"); not always present. */
44
- borderAgentIdHex?: string;
45
- /** Raw 4-byte state bitmap as hex (`_meshcop` TXT "sb"). Flag parsing left to dashboard. */
46
- stateBitmapHex?: string;
47
- /** Active timestamp as hex (`_meshcop` TXT "at"). */
48
- activeTimestampHex?: string;
49
- /** Partition ID as hex (`_meshcop` TXT "pt"). */
50
- partitionIdHex?: string;
51
- /** Vendor-specific data domain name (`_meshcop` TXT "dn"). */
52
- domainName?: string;
53
- /** Which mDNS source(s) contributed to this entry. */
54
- sources: ("meshcop" | "trel")[];
55
- /**
56
- * Epoch milliseconds of the most recent successful mDNS discovery for this entry.
57
- * Frozen when `sources` becomes empty (the entry is "stale"); updated on every
58
- * re-discovery. Stale entries are retained server-side for at least 24h after
59
- * `lastSeen`; the actual prune is lazy and happens on the next `get`, `list`,
60
- * or mDNS discovery event after the window elapses, so an entry may linger
61
- * longer than 24h if there is no activity. Consumers can derive stale state
62
- * via `sources.length === 0` and stale age via `Date.now() - lastSeen`.
63
- */
64
- lastSeen: number;
162
+ vendorModel?: string;
163
+ vendorSwVersion?: string;
164
+ /** OpenThread (or other) stack build string. */
165
+ threadStackVersion?: string;
166
+ vendorAppUrl?: string;
167
+ mleCounters?: ThreadMleCounters;
168
+ /** Battery level (0–100%) and supply voltage (mV) for battery-powered nodes. */
169
+ batteryLevel?: number;
170
+ supplyVoltage?: number;
171
+ /** TLVs this decoder does not model, preserved verbatim; `value` is uppercase hex. */
172
+ unknown?: Array<{ type: number; value: string }>;
173
+ }
174
+
175
+ /** Why a {@link ThreadDiagnosticsBatch} is incomplete (absent when the batch is complete). @since schema 12 */
176
+ export type ThreadDiagnosticsPartialReason =
177
+ | "petition_rejected"
178
+ | "dtls_failed"
179
+ | "border_router_unreachable"
180
+ | "no_credentials"
181
+ | "no_source"
182
+ | "rest_unreachable"
183
+ | "rest_protocol"
184
+ | "timeout"
185
+ /** Streaming multicast query is still active; this is a snapshot, more nodes may follow. */
186
+ | "in_progress"
187
+ /** Streaming multicast query is active but no responses have arrived yet. */
188
+ | "meshcop_no_responses_yet";
189
+
190
+ /**
191
+ * One Thread network's diagnostics snapshot, keyed by extended PAN ID, delivered by
192
+ * `get_thread_diagnostics` and streamed via the `thread_diagnostics_updated` event.
193
+ * @since schema 12
194
+ */
195
+ export interface ThreadDiagnosticsBatch {
196
+ /** 16-char uppercase hex extended PAN ID — the network this batch describes. */
197
+ extPanIdHex: string;
198
+ networkName: string;
199
+ /** Epoch ms when the batch was assembled. */
200
+ collectedAt: number;
201
+ /** Which transport produced it: MeshCoP (CoAP/DTLS) or the OTBR REST API. */
202
+ source: "meshcop" | "otbr-rest";
203
+ nodes: ThreadDiagnosticsNode[];
204
+ /** Set when the snapshot is partial or the query could not complete; see {@link ThreadDiagnosticsPartialReason}. */
205
+ partialReason?: ThreadDiagnosticsPartialReason;
65
206
  }
66
207
 
67
208
  export type WebRtcEventType = "offer" | "answer" | "ice_candidates" | "end";
@@ -129,7 +270,7 @@ export interface APICommands {
129
270
  response: MatterNodeData;
130
271
  };
131
272
  commission_with_code: {
132
- requestArgs: { code: string; network_only?: boolean };
273
+ requestArgs: { code: string; network_only?: boolean; wifi_credentials_id?: string; thread_dataset_id?: string };
133
274
  response: MatterNodeData;
134
275
  };
135
276
  commission_on_network: {
@@ -145,25 +286,41 @@ export interface APICommands {
145
286
  response: MatterNodeData;
146
287
  };
147
288
  set_wifi_credentials: {
148
- requestArgs: { ssid: string; credentials: string };
289
+ requestArgs: { ssid: string; credentials: string; id?: string };
149
290
  response: Record<string, never>;
150
291
  };
151
292
  set_thread_dataset: {
152
- requestArgs: { dataset: string };
293
+ requestArgs: { dataset: string; id?: string };
153
294
  response: Record<string, never>;
154
295
  };
155
296
  remove_wifi_credentials: {
156
- requestArgs: Record<string, never>;
297
+ requestArgs: { id?: string };
157
298
  response: Record<string, never>;
158
299
  };
159
300
  remove_thread_dataset: {
160
- requestArgs: Record<string, never>;
301
+ requestArgs: { id?: string };
161
302
  response: Record<string, never>;
162
303
  };
304
+ /** List stored WiFi/Thread credential summaries (no secrets). @since schema 12 */
305
+ get_all_credentials: {
306
+ requestArgs: Record<string, never>;
307
+ response: AllCredentialsSummary;
308
+ };
309
+ /** mDNS-discovered Thread Border Routers (passive). @since schema 12 */
163
310
  get_thread_border_routers: {
164
311
  requestArgs: Record<string, never>;
165
312
  response: BorderRouterEntry[];
166
313
  };
314
+ /**
315
+ * Per-Thread-network diagnostics. `ext_pan_id` selects one network (returns the batch, or `null`
316
+ * when nothing is cached / diagnostics are disabled); omitted returns an array of all known
317
+ * networks. `force` bypasses the cache. Also streamed via the `thread_diagnostics_updated` event.
318
+ * @since schema 12
319
+ */
320
+ get_thread_diagnostics: {
321
+ requestArgs: { ext_pan_id?: string; force?: boolean };
322
+ response: ThreadDiagnosticsBatch | ThreadDiagnosticsBatch[] | null;
323
+ };
167
324
  open_commissioning_window: {
168
325
  requestArgs: {
169
326
  node_id: number | bigint;
@@ -187,6 +344,26 @@ export interface APICommands {
187
344
  requestArgs: { node_id: number | bigint };
188
345
  response: null;
189
346
  };
347
+ get_icd_state: {
348
+ requestArgs: { node_id: number | bigint };
349
+ response: IcdStateData;
350
+ };
351
+ register_icd: {
352
+ requestArgs: {
353
+ node_id: number | bigint;
354
+ allow_multi_admin?: boolean;
355
+ ignored_vendors?: number[];
356
+ };
357
+ response: IcdStateData;
358
+ };
359
+ resync_icd: {
360
+ requestArgs: { node_id: number | bigint };
361
+ response: null;
362
+ };
363
+ unregister_icd: {
364
+ requestArgs: { node_id: number | bigint; force?: boolean };
365
+ response: IcdStateData;
366
+ };
190
367
  device_command: {
191
368
  requestArgs: {
192
369
  node_id: number | bigint;
@@ -287,6 +464,10 @@ export interface APICommands {
287
464
  requestArgs: { label: string | null };
288
465
  response: null;
289
466
  };
467
+ get_fabric_label: {
468
+ requestArgs: Record<string, never>;
469
+ response: { fabric_label: string | null };
470
+ };
290
471
  get_loglevel: {
291
472
  requestArgs: Record<string, never>;
292
473
  response: LogLevelResponse;
@@ -368,6 +549,11 @@ export interface CommandMessage {
368
549
  args?: APICommands[keyof APICommands]["requestArgs"];
369
550
  }
370
551
 
552
+ export interface AllCredentialsSummary {
553
+ wifi: Array<{ id: string; ssid: string }>;
554
+ thread: Array<{ id: string; networkName?: string; extPanId?: string }>;
555
+ }
556
+
371
557
  export interface ServerInfoMessage {
372
558
  fabric_id: number | bigint;
373
559
  compressed_fabric_id: number | bigint;
@@ -415,6 +601,9 @@ export interface APIEvents {
415
601
  server_info_updated: {
416
602
  data: ServerInfoMessage;
417
603
  };
604
+ thread_diagnostics_updated: {
605
+ data: ThreadDiagnosticsBatch;
606
+ };
418
607
  webrtc_callback: {
419
608
  data: WebRtcCallbackData;
420
609
  };
@@ -459,6 +648,10 @@ interface ServerEventInfoUpdated {
459
648
  event: "server_info_updated";
460
649
  data: ServerInfoMessage;
461
650
  }
651
+ interface ServerEventThreadDiagnosticsUpdated {
652
+ event: "thread_diagnostics_updated";
653
+ data: ThreadDiagnosticsBatch;
654
+ }
462
655
  interface ServerEventWebRtcCallback {
463
656
  event: "webrtc_callback";
464
657
  data: WebRtcCallbackData;
@@ -474,6 +667,7 @@ export type EventMessage =
474
667
  | ServerEventEndpointAdded
475
668
  | ServerEventEndpointRemoved
476
669
  | ServerEventInfoUpdated
670
+ | ServerEventThreadDiagnosticsUpdated
477
671
  | ServerEventWebRtcCallback;
478
672
 
479
673
  export interface ResultMessageBase {
@@ -547,6 +741,25 @@ export interface MatterFabricData {
547
741
  vendor_name?: string;
548
742
  }
549
743
 
744
+ /**
745
+ * Error code used when ICD registration is rejected because other administrator fabrics may not
746
+ * support LIT. OHF extension (python-matter-server codes stop at 11); the error `details` is a JSON
747
+ * string `{"message": string, "admin_vendor_ids": number[]}`.
748
+ */
749
+ export const ICD_MULTI_ADMIN_ERROR_CODE = 100;
750
+
751
+ /** ICD controller-side state for a node. Note: Only available with OHF Matter Server. */
752
+ export interface IcdStateData {
753
+ supported: boolean;
754
+ lit_supported: boolean;
755
+ registered: boolean;
756
+ operating_mode: "SIT" | "LIT" | null;
757
+ awake: boolean | null;
758
+ available: boolean | null;
759
+ /** Epoch milliseconds of the next expected check-in, if known. */
760
+ next_expected_checkin: number | null;
761
+ }
762
+
550
763
  export type NotificationType = "success" | "info" | "warning" | "error";
551
764
  export type NodePingResult = Record<string, boolean>;
552
765
 
@@ -0,0 +1,249 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import type { BorderRouterEntry, MatterNodeData } from "../models/model.js";
8
+
9
+ /**
10
+ * Minimal structural view of a node required to derive network topology.
11
+ *
12
+ * The derivation only ever reads `node_id` and the flat `attributes`
13
+ * map, so it accepts this narrow shape rather than the full {@link MatterNode}
14
+ * class. Both the client-side `MatterNode` and any server-side node record that
15
+ * exposes these fields satisfy it, which lets the topology pipeline run in the
16
+ * browser (dashboard) and in Node (server) without a shared node class.
17
+ */
18
+ export type TopologySourceNode = Pick<MatterNodeData, "node_id" | "attributes">;
19
+
20
+ /**
21
+ * Network type detected from NetworkCommissioning cluster feature map.
22
+ */
23
+ export type NetworkType = "thread" | "wifi" | "ethernet" | "unknown";
24
+
25
+ /**
26
+ * Classification of a Thread mesh link based on LQI.
27
+ * "none" means LQI=0 — neighbor entry exists but no recent valid frames (dead/stale link).
28
+ */
29
+ export type SignalLevel = "strong" | "medium" | "weak" | "none";
30
+
31
+ /**
32
+ * Thread routing role from ThreadNetworkDiagnostics cluster.
33
+ * Attribute 0/53/1 (RoutingRole)
34
+ */
35
+ export enum ThreadRoutingRole {
36
+ Unspecified = 0,
37
+ Unassigned = 1,
38
+ SleepyEndDevice = 2,
39
+ EndDevice = 3,
40
+ REED = 4, // Router-Eligible End Device
41
+ Router = 5,
42
+ Leader = 6,
43
+ }
44
+
45
+ /**
46
+ * Thread neighbor table entry from ThreadNetworkDiagnostics cluster.
47
+ * Attribute 0/53/7 (NeighborTable)
48
+ */
49
+ export interface ThreadNeighbor {
50
+ /** Extended address of the neighbor (64-bit) */
51
+ extAddress: bigint;
52
+ /** Age of the entry in seconds */
53
+ age: number;
54
+ /** RLOC16 (Router Locator) */
55
+ rloc16: number;
56
+ /** Link frame counter */
57
+ linkFrameCounter: number;
58
+ /** MLE frame counter */
59
+ mleFrameCounter: number;
60
+ /**
61
+ * Link Quality Indicator. Spec types as uint8 (0-255), but OpenThread reports
62
+ * 0-3 in practice. 0 = no recent valid frames (dead/stale link).
63
+ */
64
+ lqi: number;
65
+ /** Average RSSI in dBm (nullable) */
66
+ avgRssi: number | null;
67
+ /** Last RSSI in dBm (nullable) */
68
+ lastRssi: number | null;
69
+ /** Frame error rate (0-255) */
70
+ frameErrorRate: number;
71
+ /** Message error rate (0-255) */
72
+ messageErrorRate: number;
73
+ /** Whether RX is on when idle */
74
+ rxOnWhenIdle: boolean;
75
+ /** Whether this is a full Thread device */
76
+ fullThreadDevice: boolean;
77
+ /** Whether this is a full network data device */
78
+ fullNetworkData: boolean;
79
+ /** Whether the neighbor is a child of this node */
80
+ isChild: boolean;
81
+ }
82
+
83
+ /**
84
+ * Thread route table entry from ThreadNetworkDiagnostics cluster.
85
+ * Attribute 0/53/8 (RouteTable)
86
+ */
87
+ export interface ThreadRoute {
88
+ /** Extended address of the destination */
89
+ extAddress: bigint;
90
+ /** RLOC16 of the destination */
91
+ rloc16: number;
92
+ /** Router ID */
93
+ routerId: number;
94
+ /** Next hop RLOC16 */
95
+ nextHop: number;
96
+ /** Path cost */
97
+ pathCost: number;
98
+ /** LQI in (0-3 on OpenThread; 0 = no link). */
99
+ lqiIn: number;
100
+ /** LQI out (0-3 on OpenThread; 0 = no link). */
101
+ lqiOut: number;
102
+ /** Age of the route */
103
+ age: number;
104
+ /** Whether this is allocated */
105
+ allocated: boolean;
106
+ /** Whether link is established */
107
+ linkEstablished: boolean;
108
+ }
109
+
110
+ /**
111
+ * WiFi Diagnostics info from cluster 0x36/54.
112
+ */
113
+ export interface WiFiDiagnostics {
114
+ /** BSSID as hex string */
115
+ bssid: string | null;
116
+ /** RSSI in dBm (-120 to 0) */
117
+ rssi: number | null;
118
+ /** WiFi channel */
119
+ channel: number | null;
120
+ /** Security type */
121
+ securityType: number | null;
122
+ /** WiFi version */
123
+ wifiVersion: number | null;
124
+ }
125
+
126
+ /**
127
+ * Categorized devices by network type.
128
+ * Node IDs are stored as strings to avoid BigInt precision loss.
129
+ */
130
+ export interface CategorizedDevices {
131
+ thread: string[];
132
+ wifi: string[];
133
+ ethernet: string[];
134
+ unknown: string[];
135
+ }
136
+
137
+ /**
138
+ * Thread mesh connection between two nodes.
139
+ *
140
+ * Intentionally carries no color: color is a render concern derived from
141
+ * {@link signalLevel} by the consumer, keeping this model DOM/theme-free so the
142
+ * topology pipeline can run server-side.
143
+ */
144
+ export interface ThreadConnection {
145
+ fromNodeId: number | string;
146
+ toNodeId: number | string;
147
+ signalLevel: SignalLevel;
148
+ lqi: number;
149
+ rssi: number | null;
150
+ /** Path cost from route table (1 = direct, higher = multi-hop). Only available for routers. */
151
+ pathCost?: number;
152
+ /** Bidirectional LQI from route table (average of lqiIn and lqiOut) */
153
+ bidirectionalLqi?: number;
154
+ /** Whether this connection was supplemented by route table data (vs neighbor table only) */
155
+ fromRouteTable?: boolean;
156
+ }
157
+
158
+ /**
159
+ * A pair of Thread nodes with their directional edge data.
160
+ * Each connected pair has 0-2 edges (one per neighbor/route table direction).
161
+ */
162
+ export interface ThreadEdgePair {
163
+ /** Canonical pair key (sorted node IDs joined by "|") */
164
+ pairKey: string;
165
+ /** First node ID (lexicographically smaller) */
166
+ nodeA: string;
167
+ /** Second node ID (lexicographically larger) */
168
+ nodeB: string;
169
+ /** Edge where nodeA reports nodeB as neighbor */
170
+ edgeAB?: ThreadConnection;
171
+ /** Edge where nodeB reports nodeA as neighbor */
172
+ edgeBA?: ThreadConnection;
173
+ }
174
+
175
+ /**
176
+ * Unknown Thread device seen in neighbor tables but not commissioned.
177
+ */
178
+ export interface UnknownThreadDevice {
179
+ kind: "unknown";
180
+ /** Unique ID for the unknown device (prefixed with 'unknown_') */
181
+ id: string;
182
+ /** Extended address as hex string */
183
+ extAddressHex: string;
184
+ /** Extended address as BigInt */
185
+ extAddress: bigint;
186
+ /** Node IDs that see this device as a neighbor (as strings to avoid BigInt precision loss) */
187
+ seenBy: string[];
188
+ /** Whether this device appears to be a router */
189
+ isRouter: boolean;
190
+ /** Best signal strength seen */
191
+ bestRssi: number | null;
192
+ /**
193
+ * Extended PAN ID (16-char uppercase hex) inherited from the commissioned node that
194
+ * reports this neighbor. All Thread neighbors share the observing node's network.
195
+ */
196
+ extendedPanIdHex?: string;
197
+ /**
198
+ * Friendly Thread network name resolved by joining {@link extendedPanIdHex} against the
199
+ * Border Router registry. Some BR vendors (e.g. Apple, Aqara) use a stable border-agent
200
+ * ID as the MeshCoP `xa` while the actual Thread radio MAC differs, so the BR can show
201
+ * up as both a known BR and an "unknown" router on the same network.
202
+ */
203
+ networkName?: string;
204
+ }
205
+
206
+ /**
207
+ * Router/leader sourced purely from diagnostics (route64) that matches no
208
+ * commissioned Matter device, known BR, or neighbor-inferred unknown. Its
209
+ * childTable leaves are not materialized as nodes — they surface as {@link childCount}.
210
+ */
211
+ export interface DiagnosticMeshNode {
212
+ kind: "diagnostic";
213
+ /** Graph id `thread_<EXTMAC>`, else `meshrloc_<extPanId>_<rloc16>`. */
214
+ id: string;
215
+ rloc16: number;
216
+ /** Uppercase hex Thread MAC if known. */
217
+ extAddressHex?: string;
218
+ isRouter: boolean;
219
+ vendorName?: string;
220
+ /** Number of childTable entries this router reports (shown as a label badge). */
221
+ childCount: number;
222
+ networkName: string;
223
+ }
224
+
225
+ /**
226
+ * Thread Border Router enriched via mDNS.
227
+ *
228
+ * Same neighbor-table aggregate fields as UnknownThreadDevice (seenBy, isRouter, bestRssi),
229
+ * plus all BorderRouterEntry fields (network name, vendor, addresses, etc.).
230
+ */
231
+ export interface KnownBorderRouter extends BorderRouterEntry {
232
+ kind: "br";
233
+ /** DOM/graph ID, formatted "br_<XAHEX>". */
234
+ id: string;
235
+ /** Convenience copy of extAddressHex as bigint, mirroring UnknownThreadDevice. */
236
+ extAddress: bigint;
237
+ /** Commissioned node IDs whose neighbor table sees this xa. */
238
+ seenBy: string[];
239
+ /** Inferred from neighbor entry. */
240
+ isRouter: boolean;
241
+ /** Best signal strength seen across observers. */
242
+ bestRssi: number | null;
243
+ }
244
+
245
+ /**
246
+ * External Thread device discriminated union — either a recognized Border Router
247
+ * or an unidentified neighbor.
248
+ */
249
+ export type ThreadExternalDevice = KnownBorderRouter | UnknownThreadDevice;