@matter-server/ws-controller 0.6.8 → 0.7.0-alpha.0-20260516-8ee9631
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/ControllerCommandHandler.d.ts +12 -1
- package/dist/esm/controller/ControllerCommandHandler.d.ts.map +1 -1
- package/dist/esm/controller/ControllerCommandHandler.js +115 -34
- package/dist/esm/controller/ControllerCommandHandler.js.map +1 -1
- package/dist/esm/controller/MatterController.d.ts +8 -3
- package/dist/esm/controller/MatterController.d.ts.map +1 -1
- package/dist/esm/controller/MatterController.js +46 -2
- package/dist/esm/controller/MatterController.js.map +1 -1
- package/dist/esm/controller/Nodes.d.ts +7 -61
- package/dist/esm/controller/Nodes.d.ts.map +1 -1
- package/dist/esm/controller/Nodes.js +13 -77
- package/dist/esm/controller/Nodes.js.map +1 -1
- package/dist/esm/controller/WebRtcCallbackBridge.d.ts +9 -0
- package/dist/esm/controller/WebRtcCallbackBridge.d.ts.map +1 -0
- package/dist/esm/controller/WebRtcCallbackBridge.js +75 -0
- package/dist/esm/controller/WebRtcCallbackBridge.js.map +6 -0
- package/dist/esm/controller/behaviors/WebRtcTransportRequestorServer.d.ts +35 -0
- package/dist/esm/controller/behaviors/WebRtcTransportRequestorServer.d.ts.map +1 -0
- package/dist/esm/controller/behaviors/WebRtcTransportRequestorServer.js +123 -0
- package/dist/esm/controller/behaviors/WebRtcTransportRequestorServer.js.map +6 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/server/ConfigStorage.d.ts +2 -0
- package/dist/esm/server/ConfigStorage.d.ts.map +1 -1
- package/dist/esm/server/ConfigStorage.js +18 -0
- package/dist/esm/server/ConfigStorage.js.map +1 -1
- package/dist/esm/server/WebSocketControllerHandler.d.ts.map +1 -1
- package/dist/esm/server/WebSocketControllerHandler.js +44 -0
- package/dist/esm/server/WebSocketControllerHandler.js.map +1 -1
- package/package.json +22 -24
- package/src/controller/ControllerCommandHandler.ts +141 -54
- package/src/controller/MatterController.ts +56 -3
- package/src/controller/Nodes.ts +13 -78
- package/src/controller/WebRtcCallbackBridge.ts +79 -0
- package/src/controller/behaviors/WebRtcTransportRequestorServer.ts +149 -0
- package/src/index.ts +1 -0
- package/src/server/ConfigStorage.ts +20 -0
- package/src/server/WebSocketControllerHandler.ts +57 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { WebRtcCallbackData } from "@matter-server/ws-client";
|
|
8
|
+
import { Logger } from "@matter/main";
|
|
9
|
+
import type { WebRtcTransportRequestorServer } from "./behaviors/WebRtcTransportRequestorServer.js";
|
|
10
|
+
|
|
11
|
+
const logger = Logger.get("WebRtcCallbackBridge");
|
|
12
|
+
|
|
13
|
+
export function attachWebRtcCallbackBridge(
|
|
14
|
+
events: WebRtcTransportRequestorServer.Events,
|
|
15
|
+
emit: (data: WebRtcCallbackData) => void,
|
|
16
|
+
): void {
|
|
17
|
+
events.offer.on((session, request) => {
|
|
18
|
+
logger.info(
|
|
19
|
+
`offer from peer node=${session.peerNodeId} ep=${session.peerEndpointId} session=${session.id} sdpLen=${request.sdp.length}`,
|
|
20
|
+
);
|
|
21
|
+
emit({
|
|
22
|
+
event_type: "offer",
|
|
23
|
+
webrtc_session_id: session.id,
|
|
24
|
+
node_id: session.peerNodeId,
|
|
25
|
+
endpoint_id: session.peerEndpointId,
|
|
26
|
+
fabric_index: session.fabricIndex,
|
|
27
|
+
data: {
|
|
28
|
+
sdp: request.sdp,
|
|
29
|
+
ice_servers: request.iceServers,
|
|
30
|
+
ice_transport_policy: request.iceTransportPolicy,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
events.answer.on((session, sdp) => {
|
|
35
|
+
logger.info(
|
|
36
|
+
`answer from peer node=${session.peerNodeId} ep=${session.peerEndpointId} session=${session.id} sdpLen=${sdp.length}`,
|
|
37
|
+
);
|
|
38
|
+
emit({
|
|
39
|
+
event_type: "answer",
|
|
40
|
+
webrtc_session_id: session.id,
|
|
41
|
+
node_id: session.peerNodeId,
|
|
42
|
+
endpoint_id: session.peerEndpointId,
|
|
43
|
+
fabric_index: session.fabricIndex,
|
|
44
|
+
data: { sdp },
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
events.iceCandidates.on((session, candidates) => {
|
|
48
|
+
logger.info(
|
|
49
|
+
`ice_candidates from peer node=${session.peerNodeId} ep=${session.peerEndpointId} session=${session.id} count=${candidates.length}`,
|
|
50
|
+
);
|
|
51
|
+
emit({
|
|
52
|
+
event_type: "ice_candidates",
|
|
53
|
+
webrtc_session_id: session.id,
|
|
54
|
+
node_id: session.peerNodeId,
|
|
55
|
+
endpoint_id: session.peerEndpointId,
|
|
56
|
+
fabric_index: session.fabricIndex,
|
|
57
|
+
data: {
|
|
58
|
+
ice_candidates: candidates.map(c => ({
|
|
59
|
+
candidate: c.candidate,
|
|
60
|
+
sdpMid: c.sdpMid ?? null,
|
|
61
|
+
sdpMLineIndex: c.sdpmLineIndex ?? null,
|
|
62
|
+
})),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
events.end.on((session, reason) => {
|
|
67
|
+
logger.info(
|
|
68
|
+
`end from peer node=${session.peerNodeId} ep=${session.peerEndpointId} session=${session.id} reason=${reason}`,
|
|
69
|
+
);
|
|
70
|
+
emit({
|
|
71
|
+
event_type: "end",
|
|
72
|
+
webrtc_session_id: session.id,
|
|
73
|
+
node_id: session.peerNodeId,
|
|
74
|
+
endpoint_id: session.peerEndpointId,
|
|
75
|
+
fabric_index: session.fabricIndex,
|
|
76
|
+
data: { reason },
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Observable } from "@matter/general";
|
|
8
|
+
import { Logger, Node } from "@matter/main";
|
|
9
|
+
import type { ServerNode } from "@matter/main";
|
|
10
|
+
import { AccessControlServer } from "@matter/node/behaviors/access-control";
|
|
11
|
+
import { WebRtcTransportRequestorBehavior } from "@matter/node/behaviors/web-rtc-transport-requestor";
|
|
12
|
+
import { assertRemoteActor, FabricAuthority, NodeSession } from "@matter/protocol";
|
|
13
|
+
import { Status, StatusResponseError } from "@matter/types";
|
|
14
|
+
import { AccessControl } from "@matter/types/clusters/access-control";
|
|
15
|
+
import { WebRtcTransportDefinitions } from "@matter/types/clusters/web-rtc-transport-definitions";
|
|
16
|
+
import { WebRtcTransportRequestor } from "@matter/types/clusters/web-rtc-transport-requestor";
|
|
17
|
+
|
|
18
|
+
type WebRtcSession = WebRtcTransportDefinitions.WebRtcSession;
|
|
19
|
+
|
|
20
|
+
const logger = Logger.get("WebRtcTransportRequestorServer");
|
|
21
|
+
|
|
22
|
+
export class WebRtcTransportRequestorServer extends WebRtcTransportRequestorBehavior {
|
|
23
|
+
declare state: WebRtcTransportRequestorServer.State;
|
|
24
|
+
declare events: WebRtcTransportRequestorServer.Events;
|
|
25
|
+
|
|
26
|
+
upsertSession(session: WebRtcSession): void {
|
|
27
|
+
const enriched: WebRtcSession = {
|
|
28
|
+
...session,
|
|
29
|
+
videoStreamId: session.videoStreams?.[0] ?? null,
|
|
30
|
+
audioStreamId: session.audioStreams?.[0] ?? null,
|
|
31
|
+
};
|
|
32
|
+
const sessions = this.state.currentSessions;
|
|
33
|
+
const idx = sessions.findIndex(s => s.id === session.id);
|
|
34
|
+
if (idx === -1) {
|
|
35
|
+
this.state.currentSessions = [...sessions, enriched];
|
|
36
|
+
} else {
|
|
37
|
+
this.state.currentSessions = sessions.map((s, i) => (i === idx ? enriched : s));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
removeSession(id: number): void {
|
|
42
|
+
this.state.currentSessions = this.state.currentSessions.filter(s => s.id !== id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override async offer(request: WebRtcTransportRequestor.OfferRequest): Promise<void> {
|
|
46
|
+
logger.info(`incoming Offer webRtcSessionId=${request.webRtcSessionId} sdpLen=${request.sdp.length}`);
|
|
47
|
+
const session = this.#findSessionStrict(request.webRtcSessionId);
|
|
48
|
+
this.events.offer.emit(session, request);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override async answer(request: WebRtcTransportRequestor.AnswerRequest): Promise<void> {
|
|
52
|
+
logger.info(`incoming Answer webRtcSessionId=${request.webRtcSessionId} sdpLen=${request.sdp.length}`);
|
|
53
|
+
const session = this.#findSessionStrict(request.webRtcSessionId);
|
|
54
|
+
this.events.answer.emit(session, request.sdp);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override async iceCandidates(request: WebRtcTransportRequestor.IceCandidatesRequest): Promise<void> {
|
|
58
|
+
logger.info(
|
|
59
|
+
`incoming ICECandidates webRtcSessionId=${request.webRtcSessionId} count=${request.iceCandidates.length}`,
|
|
60
|
+
);
|
|
61
|
+
if (request.iceCandidates.length === 0) {
|
|
62
|
+
throw new StatusResponseError("ICE candidates list must not be empty", Status.InvalidCommand);
|
|
63
|
+
}
|
|
64
|
+
const session = this.#findSessionStrict(request.webRtcSessionId);
|
|
65
|
+
this.events.iceCandidates.emit(session, request.iceCandidates);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override async end(request: WebRtcTransportRequestor.EndRequest): Promise<void> {
|
|
69
|
+
logger.info(`incoming End webRtcSessionId=${request.webRtcSessionId} reason=${request.reason}`);
|
|
70
|
+
const session = this.#findSessionStrict(request.webRtcSessionId);
|
|
71
|
+
this.removeSession(request.webRtcSessionId);
|
|
72
|
+
this.events.end.emit(session, request.reason);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override async initialize() {
|
|
76
|
+
const node = Node.forEndpoint(this.endpoint) as ServerNode;
|
|
77
|
+
logger.info(
|
|
78
|
+
`initialized on endpoint=${this.endpoint.number} (id="${this.endpoint.id}") cluster=0x${WebRtcTransportRequestor.id.toString(16)}`,
|
|
79
|
+
);
|
|
80
|
+
this.reactTo(node.lifecycle.online, this.#nodeOnline);
|
|
81
|
+
if (node.lifecycle.isOnline) {
|
|
82
|
+
await this.#nodeOnline();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async #nodeOnline() {
|
|
87
|
+
const fabricAuthority = this.env.get(FabricAuthority);
|
|
88
|
+
const ownFabric = fabricAuthority.fabrics[0];
|
|
89
|
+
if (!ownFabric) {
|
|
90
|
+
// void: fabricAdded is a synchronous observable; awaiting #nodeOnline here deadlocks setStateOf.
|
|
91
|
+
fabricAuthority.fabricAdded.once(() => void this.#nodeOnline());
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const node = Node.forEndpoint(this.endpoint) as ServerNode;
|
|
96
|
+
await node.act(agent => agent.load(AccessControlServer));
|
|
97
|
+
if (node.behaviors.has(AccessControlServer)) {
|
|
98
|
+
if (
|
|
99
|
+
!node
|
|
100
|
+
.stateOf(AccessControlServer)
|
|
101
|
+
.acl.some(
|
|
102
|
+
({ fabricIndex, privilege, authMode, subjects, targets }) =>
|
|
103
|
+
fabricIndex === ownFabric.fabricIndex &&
|
|
104
|
+
privilege === AccessControl.AccessControlEntryPrivilege.Operate &&
|
|
105
|
+
authMode === AccessControl.AccessControlEntryAuthMode.Case &&
|
|
106
|
+
subjects?.length === 0 &&
|
|
107
|
+
targets?.length === 1 &&
|
|
108
|
+
targets[0].endpoint === this.endpoint.number &&
|
|
109
|
+
targets[0].cluster === WebRtcTransportRequestor.id,
|
|
110
|
+
)
|
|
111
|
+
) {
|
|
112
|
+
const acl = [
|
|
113
|
+
...node.stateOf(AccessControlServer).acl,
|
|
114
|
+
{
|
|
115
|
+
fabricIndex: ownFabric.fabricIndex,
|
|
116
|
+
privilege: AccessControl.AccessControlEntryPrivilege.Operate,
|
|
117
|
+
authMode: AccessControl.AccessControlEntryAuthMode.Case,
|
|
118
|
+
subjects: [],
|
|
119
|
+
targets: [{ endpoint: this.endpoint.number, cluster: WebRtcTransportRequestor.id }],
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
await node.setStateOf(AccessControlServer, { acl });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#findSessionStrict(id: number): WebRtcSession {
|
|
128
|
+
assertRemoteActor(this.context);
|
|
129
|
+
NodeSession.assert(this.context.session);
|
|
130
|
+
const peer = this.context.session.peerAddress;
|
|
131
|
+
const session = this.state.currentSessions.find(s => s.id === id);
|
|
132
|
+
if (session === undefined || session.fabricIndex !== peer.fabricIndex || session.peerNodeId !== peer.nodeId) {
|
|
133
|
+
throw new StatusResponseError(`WebRTC session ${id} not found`, Status.NotFound);
|
|
134
|
+
}
|
|
135
|
+
return session;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export namespace WebRtcTransportRequestorServer {
|
|
140
|
+
export class State extends WebRtcTransportRequestorBehavior.State {
|
|
141
|
+
override currentSessions: WebRtcSession[] = new Array<WebRtcSession>();
|
|
142
|
+
}
|
|
143
|
+
export class Events extends WebRtcTransportRequestorBehavior.Events {
|
|
144
|
+
offer = Observable<[session: WebRtcSession, args: WebRtcTransportRequestor.OfferRequest]>();
|
|
145
|
+
answer = Observable<[session: WebRtcSession, sdp: string]>();
|
|
146
|
+
iceCandidates = Observable<[session: WebRtcSession, candidates: WebRtcTransportDefinitions.IceCandidate[]]>();
|
|
147
|
+
end = Observable<[session: WebRtcSession, reason: WebRtcTransportDefinitions.WebRtcEndReason]>();
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
// Export controller components
|
|
12
|
+
export * from "./controller/behaviors/WebRtcTransportRequestorServer.js";
|
|
12
13
|
export * from "./controller/ControllerCommandHandler.js";
|
|
13
14
|
export * from "./controller/LegacyDataInjector.js";
|
|
14
15
|
export * from "./controller/MatterController.js";
|
|
@@ -114,6 +114,26 @@ export class ConfigStorage {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
async removeWifiCredentials() {
|
|
118
|
+
if (!this.#configStore) {
|
|
119
|
+
throw new Error("Storage not open");
|
|
120
|
+
}
|
|
121
|
+
this.#data.wifiSsid = undefined;
|
|
122
|
+
this.#data.wifiCredentials = undefined;
|
|
123
|
+
await this.#configStore.delete("wifiSsid");
|
|
124
|
+
await this.#configStore.delete("wifiCredentials");
|
|
125
|
+
logger.info("Removed WiFi credentials");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async removeThreadDataset() {
|
|
129
|
+
if (!this.#configStore) {
|
|
130
|
+
throw new Error("Storage not open");
|
|
131
|
+
}
|
|
132
|
+
this.#data.threadDataset = undefined;
|
|
133
|
+
await this.#configStore.delete("threadDataset");
|
|
134
|
+
logger.info("Removed Thread dataset");
|
|
135
|
+
}
|
|
136
|
+
|
|
117
137
|
async close() {
|
|
118
138
|
if (this.#storage) {
|
|
119
139
|
await this.#storage.close();
|
|
@@ -84,6 +84,7 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
84
84
|
#wss?: WebSocketServer;
|
|
85
85
|
#closed = false;
|
|
86
86
|
#shuttingDown = false;
|
|
87
|
+
#serverObservers = new ObserverGroup();
|
|
87
88
|
/** Circular buffer for recent node events (max 25) */
|
|
88
89
|
#eventHistory: MatterNodeEvent[] = [];
|
|
89
90
|
/** Track when each node was last interviewed (connected) - keyed by nodeId */
|
|
@@ -142,6 +143,13 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
142
143
|
async register(server: HttpServer) {
|
|
143
144
|
logger.info(`Starting server: matter-server/${this.#serverVersion} (matter.js/${MATTER_VERSION})`);
|
|
144
145
|
const wss = (this.#wss = new WebSocketServer({ server: server, path: "/ws" }));
|
|
146
|
+
|
|
147
|
+
// WebRTC callbacks fan out to every connected client, so subscribe once at the server
|
|
148
|
+
// level rather than per-connection.
|
|
149
|
+
this.#serverObservers.on(this.#commandHandler.events.webRtcCallback, data => {
|
|
150
|
+
this.#broadcastEvent("webrtc_callback", data);
|
|
151
|
+
});
|
|
152
|
+
|
|
145
153
|
wss.on("connection", ws => {
|
|
146
154
|
if (this.#closed || this.#shuttingDown) {
|
|
147
155
|
try {
|
|
@@ -372,6 +380,7 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
372
380
|
}
|
|
373
381
|
|
|
374
382
|
this.#closed = true;
|
|
383
|
+
this.#serverObservers.close();
|
|
375
384
|
// Send server_shutdown event to all connected clients before closing
|
|
376
385
|
const shutdownMessage = toBigIntAwareJson({ event: "server_shutdown", data: {} });
|
|
377
386
|
this.#wss.clients.forEach(client => {
|
|
@@ -445,6 +454,9 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
445
454
|
case "device_command":
|
|
446
455
|
result = await this.#handleDeviceCommand(args);
|
|
447
456
|
break;
|
|
457
|
+
case "send_webrtc_provider_command":
|
|
458
|
+
result = await this.#handleSendWebRtcProviderCommand(args);
|
|
459
|
+
break;
|
|
448
460
|
case "write_attribute":
|
|
449
461
|
result = await this.#handleWriteAttribute(args);
|
|
450
462
|
break;
|
|
@@ -470,6 +482,12 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
470
482
|
case "set_thread_dataset":
|
|
471
483
|
result = await this.#handleSetThreadDataset(args);
|
|
472
484
|
break;
|
|
485
|
+
case "remove_wifi_credentials":
|
|
486
|
+
result = await this.#handleRemoveWifiCredentials();
|
|
487
|
+
break;
|
|
488
|
+
case "remove_thread_dataset":
|
|
489
|
+
result = await this.#handleRemoveThreadDataset();
|
|
490
|
+
break;
|
|
473
491
|
case "get_thread_border_routers":
|
|
474
492
|
result = this.#controller.borderRouters.list();
|
|
475
493
|
break;
|
|
@@ -558,6 +576,7 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
558
576
|
min_supported_schema_version: SCHEMA_VERSION,
|
|
559
577
|
sdk_version: `matter-server/${this.#serverVersion} (matter.js/${MATTER_VERSION})`,
|
|
560
578
|
wifi_credentials_set: !!(this.#config.wifiSsid && this.#config.wifiCredentials),
|
|
579
|
+
wifi_ssid: this.#config.wifiSsid && this.#config.wifiCredentials ? this.#config.wifiSsid : undefined,
|
|
561
580
|
thread_credentials_set: !!this.#config.threadDataset,
|
|
562
581
|
bluetooth_enabled: this.#commandHandler.bleEnabled,
|
|
563
582
|
};
|
|
@@ -631,6 +650,9 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
631
650
|
}
|
|
632
651
|
}
|
|
633
652
|
|
|
653
|
+
// Ensure certificates are loaded and initialized
|
|
654
|
+
await this.#controller.certificateService();
|
|
655
|
+
|
|
634
656
|
await this.#config.set({
|
|
635
657
|
nextNodeId: typeof nextNodeId === "bigint" ? nextNodeId + 1n : nextNodeId + 1,
|
|
636
658
|
});
|
|
@@ -688,6 +710,9 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
688
710
|
break;
|
|
689
711
|
}
|
|
690
712
|
|
|
713
|
+
// Ensure certificates are loaded and initialized
|
|
714
|
+
await this.#controller.certificateService();
|
|
715
|
+
|
|
691
716
|
await this.#config.set({
|
|
692
717
|
nextNodeId: typeof nextNodeId === "bigint" ? nextNodeId + 1n : nextNodeId + 1,
|
|
693
718
|
});
|
|
@@ -853,6 +878,18 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
853
878
|
return cmdResult;
|
|
854
879
|
}
|
|
855
880
|
|
|
881
|
+
async #handleSendWebRtcProviderCommand(
|
|
882
|
+
args: ArgsOf<"send_webrtc_provider_command">,
|
|
883
|
+
): Promise<ResponseOf<"send_webrtc_provider_command">> {
|
|
884
|
+
const { node_id, endpoint_id, command_name, payload } = args;
|
|
885
|
+
return this.#commandHandler.sendWebRtcProviderCommand({
|
|
886
|
+
nodeId: NodeId(node_id),
|
|
887
|
+
endpointId: EndpointNumber(endpoint_id),
|
|
888
|
+
commandName: command_name,
|
|
889
|
+
payload,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
856
893
|
async #handleInterviewNode(args: ArgsOf<"interview_node">): Promise<ResponseOf<"interview_node">> {
|
|
857
894
|
const { node_id } = args;
|
|
858
895
|
const nodeId = NodeId(node_id);
|
|
@@ -927,6 +964,26 @@ export class WebSocketControllerHandler implements WebServerHandler {
|
|
|
927
964
|
return {};
|
|
928
965
|
}
|
|
929
966
|
|
|
967
|
+
async #handleRemoveWifiCredentials(): Promise<ResponseOf<"remove_wifi_credentials">> {
|
|
968
|
+
await this.#config.removeWifiCredentials();
|
|
969
|
+
try {
|
|
970
|
+
await this.#broadcastServerInfoUpdated();
|
|
971
|
+
} catch (error) {
|
|
972
|
+
logger.warn("Failed to broadcast server info update", error);
|
|
973
|
+
}
|
|
974
|
+
return {};
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
async #handleRemoveThreadDataset(): Promise<ResponseOf<"remove_thread_dataset">> {
|
|
978
|
+
await this.#config.removeThreadDataset();
|
|
979
|
+
try {
|
|
980
|
+
await this.#broadcastServerInfoUpdated();
|
|
981
|
+
} catch (error) {
|
|
982
|
+
logger.warn("Failed to broadcast server info update", error);
|
|
983
|
+
}
|
|
984
|
+
return {};
|
|
985
|
+
}
|
|
986
|
+
|
|
930
987
|
async #handleOpenCommissioningWindow(
|
|
931
988
|
args: ArgsOf<"open_commissioning_window">,
|
|
932
989
|
): Promise<ResponseOf<"open_commissioning_window">> {
|