@cryptforge/key-exchange 0.1.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.
@@ -0,0 +1,902 @@
1
+ // src/server/useKeyExchangeServer.ts
2
+ import DHT from "hyperdht";
3
+ import b4a2 from "b4a";
4
+
5
+ // src/server/messages.ts
6
+ import b4a from "b4a";
7
+
8
+ // src/types/messages.ts
9
+ var MESSAGES = {
10
+ enableBroadcast: "admin:broadcast:enable",
11
+ connect: "client:topic:connect",
12
+ onClientConnection: "admin:topic:connected",
13
+ requestKeystore: "client:keystore:request",
14
+ onKeystoreRequestInvalidPIN: "admin:pin:invalid",
15
+ onKeystoreRequestValidPIN: "admin:pin:valid",
16
+ onKeystoreRequest: "admin:request:received",
17
+ approveRequest: "admin:request:approve",
18
+ onClientApproval: "client:request:approved",
19
+ denyRequest: "admin:request:deny",
20
+ onClientDenial: "client:request:denied",
21
+ onCompletion: "server:link:complete",
22
+ disableBroadcast: "admin:broadcast:disable",
23
+ onBroadcastDisable: "client:broadcast:disabled",
24
+ getDeviceInfo: "client:request:deviceInfo",
25
+ getHostname: "client:request:hostname",
26
+ // Presence/Network messages
27
+ connectPresence: "network:connect",
28
+ broadcastClientState: "network:broadcast",
29
+ onClientStateRequest: "network:request",
30
+ onClientStateUpdate: "network:update"
31
+ };
32
+
33
+ // src/server/messages.ts
34
+ var syncSetupEventRoutes = {
35
+ onClientConnection: MESSAGES.onClientConnection,
36
+ onKeystoreRequest: MESSAGES.onKeystoreRequest,
37
+ onClientApproval: MESSAGES.onClientApproval,
38
+ onClientDenial: MESSAGES.onClientDenial,
39
+ onCompletion: MESSAGES.onCompletion,
40
+ onBroadcastDisable: MESSAGES.onBroadcastDisable,
41
+ onKeystoreRequestInvalidPIN: MESSAGES.onKeystoreRequestInvalidPIN,
42
+ onKeystoreRequestValidPIN: MESSAGES.onKeystoreRequestValidPIN
43
+ };
44
+ var requestHeaders = [
45
+ {
46
+ code: 1e3,
47
+ name: "Request PIN",
48
+ description: "The Client is asking the Server to show UI of a PIN screen for the user to enter on the Client"
49
+ },
50
+ {
51
+ code: 2e3,
52
+ name: "Request Keystore",
53
+ description: "The Client is asking the Server to show UI for the user to approve or reject the request"
54
+ }
55
+ ];
56
+ var sendRequest = async (options) => {
57
+ const index = requestHeaders.findIndex(
58
+ (response) => response.name === options.header
59
+ );
60
+ if (index !== -1) {
61
+ const header = requestHeaders[index];
62
+ const body = options.body();
63
+ const request = {
64
+ header,
65
+ body
66
+ };
67
+ const payload = JSON.stringify(request);
68
+ for (const socket of options.to) {
69
+ console.log(
70
+ `hyperswarm: sending ${header.name} to connection ${b4a.toString(
71
+ socket.remotePublicKey,
72
+ "hex"
73
+ )}`
74
+ );
75
+ socket.write(payload);
76
+ }
77
+ } else {
78
+ console.log(`Request with name \`${options.header}\` not found`);
79
+ }
80
+ };
81
+ var responseHeaders = [
82
+ {
83
+ code: 1501,
84
+ name: "Connection Failure: Broadcast disabled",
85
+ type: "Failure"
86
+ },
87
+ { code: 2200, name: "Authentication Succeeded", type: "Success" },
88
+ { code: 2400, name: "Authentication Error: Invalid PIN", type: "Error" },
89
+ { code: 3200, name: "Transmition Succeeded: Approved", type: "Success" },
90
+ { code: 3201, name: "Transmition Succeeded: Rejected", type: "Success" },
91
+ { code: 4200, name: "Setup Succeeded", type: "Success" }
92
+ ];
93
+ var sendResponse = (options) => {
94
+ const index = responseHeaders.findIndex(
95
+ (response) => response.name === options.header
96
+ );
97
+ if (index !== -1) {
98
+ const header = responseHeaders[index];
99
+ const data = options.body();
100
+ const response = {
101
+ header,
102
+ body: data
103
+ };
104
+ const payload = JSON.stringify(response);
105
+ for (const socket of options.to) {
106
+ console.log(
107
+ `hyperswarm: sending ${header.name} to connection ${b4a.toString(
108
+ socket.remotePublicKey,
109
+ "hex"
110
+ )}`
111
+ );
112
+ socket.write(payload);
113
+ }
114
+ } else {
115
+ console.log(`Response with name \`${options.header}\` not found`);
116
+ }
117
+ };
118
+
119
+ // src/server/useKeyExchangeServer.ts
120
+ function useKeyExchangeServer(onEvent) {
121
+ let client;
122
+ let dht;
123
+ const keyPair = DHT.keyPair();
124
+ const getKey = () => {
125
+ return b4a2.toString(keyPair.publicKey, "hex");
126
+ };
127
+ const generateRandomPIN = () => {
128
+ return Math.floor(1e5 + Math.random() * 9e5);
129
+ };
130
+ const pin = generateRandomPIN();
131
+ const getPIN = () => {
132
+ return `${pin}`;
133
+ };
134
+ let server;
135
+ const createServer = async () => {
136
+ dht = new DHT();
137
+ server = await dht.createServer((socket) => {
138
+ const name = b4a2.toString(socket.remotePublicKey, "hex");
139
+ console.log(`server: got a connection on \`${name}\``);
140
+ socket.on("data", (data) => {
141
+ parseRequest(data);
142
+ });
143
+ socket.on("error", (error) => {
144
+ onConnectionError(error);
145
+ });
146
+ client = socket;
147
+ });
148
+ };
149
+ const parseRequest = (data) => {
150
+ console.log(`server: got data: ${data}`);
151
+ const parsedData = JSON.parse(data);
152
+ const payload = parsedData;
153
+ console.log("payload");
154
+ console.log(payload);
155
+ switch (payload.header.name) {
156
+ case "Request PIN":
157
+ onRequestPin(payload.body);
158
+ break;
159
+ case "Request Keystore":
160
+ onRequestKeystore(payload.body);
161
+ break;
162
+ }
163
+ };
164
+ const onConnectionError = (error) => {
165
+ console.log(`server: Connection error: ${error}`);
166
+ };
167
+ const onRequestPin = (body) => {
168
+ const route = syncSetupEventRoutes["onClientConnection"];
169
+ onEvent({
170
+ route,
171
+ data: {
172
+ pin: getPIN(),
173
+ name: body.deviceName,
174
+ app: body.appName
175
+ }
176
+ });
177
+ };
178
+ const onRequestKeystore = (body) => {
179
+ const pin2 = body.pin;
180
+ if (pin2 !== getPIN()) {
181
+ sendResponse({
182
+ to: [client],
183
+ header: "Authentication Error: Invalid PIN",
184
+ body: () => {
185
+ return {};
186
+ }
187
+ });
188
+ return;
189
+ } else {
190
+ sendResponse({
191
+ to: [client],
192
+ header: "Authentication Succeeded",
193
+ body: () => {
194
+ return {};
195
+ }
196
+ });
197
+ }
198
+ const route = syncSetupEventRoutes["onKeystoreRequest"];
199
+ onEvent({
200
+ route,
201
+ data: {}
202
+ });
203
+ };
204
+ const onRequestDenied = () => {
205
+ const route = syncSetupEventRoutes["onClientDenial"];
206
+ onEvent({ route, data: {} });
207
+ };
208
+ const onSetupComplete = () => {
209
+ const route = syncSetupEventRoutes["onCompletion"];
210
+ onEvent({ route, data: {} });
211
+ };
212
+ const beginBroadcast = async () => {
213
+ await createServer();
214
+ await server.listen(keyPair);
215
+ await server.refresh();
216
+ console.log(`server: opened connection to ${getKey()}`);
217
+ };
218
+ const endBroadcast = async () => {
219
+ if (server) {
220
+ await server.close();
221
+ console.log(`server: closed connection to ${getKey()}`);
222
+ }
223
+ if (dht) {
224
+ await dht.destroy();
225
+ console.log(`server: destroyed dht`);
226
+ }
227
+ };
228
+ const approveRequest = async (identity, appId) => {
229
+ sendResponse({
230
+ to: [client],
231
+ header: "Transmition Succeeded: Approved",
232
+ body: () => {
233
+ return {
234
+ identity,
235
+ appId
236
+ };
237
+ }
238
+ });
239
+ };
240
+ const completeSetupSuccess = async () => {
241
+ console.log("sending compltion message to client");
242
+ sendResponse({
243
+ to: [client],
244
+ header: "Setup Succeeded",
245
+ body: () => {
246
+ return {};
247
+ }
248
+ });
249
+ onSetupComplete();
250
+ };
251
+ const rejectRequest = async () => {
252
+ sendResponse({
253
+ to: [client],
254
+ header: "Transmition Succeeded: Rejected",
255
+ body: () => {
256
+ return {};
257
+ }
258
+ });
259
+ onRequestDenied();
260
+ };
261
+ const broadcastDisabled = async () => {
262
+ if (client) {
263
+ sendResponse({
264
+ to: [client],
265
+ header: "Connection Failure: Broadcast disabled",
266
+ body: () => {
267
+ return {};
268
+ }
269
+ });
270
+ }
271
+ };
272
+ return {
273
+ getKey,
274
+ getPIN,
275
+ beginBroadcast,
276
+ endBroadcast,
277
+ approveRequest,
278
+ completeSetupSuccess,
279
+ rejectRequest,
280
+ broadcastDisabled
281
+ };
282
+ }
283
+
284
+ // src/server/keyTransportServer.ts
285
+ import WebSocket, { WebSocketServer } from "ws";
286
+
287
+ // src/useNetworkPresenceServer.ts
288
+ import Hyperswarm from "hyperswarm";
289
+ import b4a3 from "b4a";
290
+ function useNetworkPresence(onUpdate, onRequest) {
291
+ const deviceIDs = /* @__PURE__ */ new Map();
292
+ const swarm = new Hyperswarm();
293
+ swarm.on("connection", (connection) => {
294
+ const name = b4a3.toString(connection.remotePublicKey, "hex");
295
+ console.log("Presence: * got a connection from:", name, "*");
296
+ connections.push(connection);
297
+ sendStateRequestMessage(connection);
298
+ connection.once("close", () => {
299
+ console.log(
300
+ `Presence: Connection closed. Removing connection ${b4a3.toString(
301
+ connection.remotePublicKey,
302
+ "hex"
303
+ )}`
304
+ );
305
+ connections.splice(connections.indexOf(connection), 1);
306
+ const id = deviceIDs.get(b4a3.toString(connection.remotePublicKey, "hex"));
307
+ if (id) {
308
+ broadcastStatusMessage(id, "unknown");
309
+ }
310
+ deviceIDs.delete(b4a3.toString(connection.remotePublicKey, "hex"));
311
+ });
312
+ connection.on("data", (data) => {
313
+ let parsedData;
314
+ try {
315
+ parsedData = JSON.parse(data);
316
+ } catch (error) {
317
+ console.error("Error parsing data:", error);
318
+ return;
319
+ }
320
+ const header = parsedData;
321
+ console.log(
322
+ "Presence: got data for ",
323
+ connection.publicKey.toString("hex")
324
+ );
325
+ if (header.type === "update") {
326
+ const payload = parsedData;
327
+ onUpdate({ id: payload.id, state: payload.state });
328
+ deviceIDs.set(
329
+ b4a3.toString(connection.remotePublicKey, "hex"),
330
+ payload.id
331
+ );
332
+ } else if (header.type === "request") {
333
+ onRequest();
334
+ }
335
+ });
336
+ connection.on("error", (error) => {
337
+ console.error(
338
+ `Presence: Connection error on connection ${b4a3.toString(
339
+ connection.remotePublicKey,
340
+ "hex"
341
+ )}: ${error}`
342
+ );
343
+ });
344
+ });
345
+ const connections = [];
346
+ const connect = async (topic, id) => {
347
+ console.log("Presence: connecting to topic: ", topic);
348
+ const key = b4a3.from(topic, "hex");
349
+ console.log("key: ", key);
350
+ const discovery = swarm.join(key, { client: true, server: true });
351
+ console.log("got discovery: ");
352
+ const publicKey = b4a3.toString(
353
+ discovery.swarm.keyPair?.publicKey || discovery.swarm.publicKey || Buffer.alloc(32),
354
+ "hex"
355
+ );
356
+ console.log("Presence: public key is ", publicKey);
357
+ deviceIDs.set(publicKey, id);
358
+ discovery.flushed().then(() => {
359
+ console.log("Presence: joined topic:", b4a3.toString(key, "hex"));
360
+ });
361
+ };
362
+ const sendStateRequestMessage = async (connection) => {
363
+ const message = {
364
+ type: "request"
365
+ };
366
+ const payload = JSON.stringify(message);
367
+ console.log(
368
+ "sending state request message to: ",
369
+ connection.publicKey.toString("hex")
370
+ );
371
+ connection.write(payload);
372
+ };
373
+ const receiveStatusMessage = async (id, state) => {
374
+ return broadcastStatusMessage(id, state);
375
+ };
376
+ const broadcastStatusMessage = (id, state) => {
377
+ console.log(`server got connection state message for ${id}: `, state);
378
+ const message = {
379
+ type: "update",
380
+ id,
381
+ state
382
+ };
383
+ console.log("active connections: ", connections.length);
384
+ const payload = JSON.stringify(message);
385
+ for (const connection of connections) {
386
+ console.log("sending message to: ", connection.publicKey.toString("hex"));
387
+ connection.write(payload);
388
+ }
389
+ onUpdate(message);
390
+ };
391
+ return {
392
+ connect,
393
+ receiveStatusMessage
394
+ };
395
+ }
396
+
397
+ // src/client/useKeyExchangeClient.ts
398
+ import DHT2 from "hyperdht";
399
+ import b4a4 from "b4a";
400
+ function useKeyExchangeClient(onEvent) {
401
+ let socket;
402
+ let deviceName;
403
+ let appName;
404
+ const connect = async (options) => {
405
+ deviceName = options.name;
406
+ appName = options.app;
407
+ return new Promise((resolve) => {
408
+ console.log(`client: connecting to server on ${options.key}`);
409
+ const publicKey = b4a4.from(options.key, "hex");
410
+ const dht = new DHT2();
411
+ socket = dht.connect(publicKey);
412
+ socket.once("open", () => {
413
+ const name = b4a4.toString(socket.remotePublicKey, "hex");
414
+ console.log(`client: got a connection on ${name}`);
415
+ resolve();
416
+ });
417
+ socket.on("data", (data) => {
418
+ parseResponse(data);
419
+ });
420
+ socket.on("error", (error) => {
421
+ onConnectionError(error);
422
+ });
423
+ });
424
+ };
425
+ const parseResponse = async (data) => {
426
+ console.log(`client: got data: ${data}`);
427
+ const parsedData = JSON.parse(data);
428
+ const payload = parsedData;
429
+ console.log("payload");
430
+ console.log(payload);
431
+ switch (payload.header.name) {
432
+ case "Connection Failure: Broadcast disabled":
433
+ onEvent({
434
+ route: syncSetupEventRoutes["onBroadcastDisable"],
435
+ data: {}
436
+ });
437
+ break;
438
+ case "Transmition Succeeded: Approved":
439
+ const identity = payload.body.identity;
440
+ const appId = payload.body.appId;
441
+ onEvent({
442
+ route: syncSetupEventRoutes["onClientApproval"],
443
+ data: { identity, appId }
444
+ });
445
+ break;
446
+ case "Transmition Succeeded: Rejected":
447
+ onEvent({ route: syncSetupEventRoutes["onClientDenial"], data: {} });
448
+ break;
449
+ case "Setup Succeeded":
450
+ onEvent({ route: syncSetupEventRoutes["onCompletion"], data: {} });
451
+ break;
452
+ case "Authentication Error: Invalid PIN":
453
+ onEvent({
454
+ route: syncSetupEventRoutes["onKeystoreRequestInvalidPIN"],
455
+ data: {
456
+ reason: "Invalid PIN Entered"
457
+ }
458
+ });
459
+ break;
460
+ case "Authentication Succeeded":
461
+ onEvent({
462
+ route: syncSetupEventRoutes["onKeystoreRequestValidPIN"],
463
+ data: {}
464
+ });
465
+ break;
466
+ default:
467
+ console.error(`error: response ${payload} not handled in client`);
468
+ break;
469
+ }
470
+ };
471
+ const onConnectionError = async (error) => {
472
+ console.log(`client: Connection error: ${error}`);
473
+ const route = syncSetupEventRoutes["onBroadcastDisable"];
474
+ switch (error.code) {
475
+ case "PEER_NOT_FOUND":
476
+ onEvent({
477
+ route,
478
+ data: {
479
+ title: "Peer Not Found",
480
+ description: "Another device with the specified broadcast key could not be located."
481
+ }
482
+ });
483
+ break;
484
+ default:
485
+ onEvent({
486
+ route,
487
+ data: {
488
+ title: "Client Disconnected",
489
+ description: "The connection to your other device was lost. Please ensure you have another device that is broadcasting"
490
+ }
491
+ });
492
+ break;
493
+ }
494
+ };
495
+ const requestPIN = async () => {
496
+ await sendRequest({
497
+ to: [socket],
498
+ header: "Request PIN",
499
+ body: () => {
500
+ return { deviceName, appName };
501
+ }
502
+ });
503
+ };
504
+ const requestKeystore = async (pin) => {
505
+ await sendRequest({
506
+ to: [socket],
507
+ header: "Request Keystore",
508
+ body: () => {
509
+ return {
510
+ pin
511
+ };
512
+ }
513
+ });
514
+ const route = syncSetupEventRoutes["onKeystoreRequest"];
515
+ onEvent({ route, data: {} });
516
+ };
517
+ return {
518
+ connect,
519
+ requestPIN,
520
+ requestKeystore
521
+ };
522
+ }
523
+
524
+ // src/CoreTransportLogic.ts
525
+ import crypto from "crypto";
526
+ import os from "os";
527
+ import si from "systeminformation";
528
+ var CoreTransportLogic = class {
529
+ syncServer;
530
+ syncClient;
531
+ presence;
532
+ deviceInfo = null;
533
+ handleEvent;
534
+ presenceConnected = false;
535
+ constructor(handleEvent = () => {
536
+ }) {
537
+ this.handleEvent = handleEvent;
538
+ this.syncServer = useKeyExchangeServer(this.handleEvent);
539
+ this.syncClient = useKeyExchangeClient(this.handleEvent);
540
+ this.presence = useNetworkPresence(
541
+ this.onStateUpdate.bind(this),
542
+ this.onStateRequest.bind(this)
543
+ );
544
+ this.initializeDeviceInfo();
545
+ }
546
+ async initializeDeviceInfo() {
547
+ this.deviceInfo = await this.getDeviceInfo();
548
+ }
549
+ onStateUpdate(response) {
550
+ console.log("received state message for:", response.id);
551
+ this.handleEvent({ route: MESSAGES.onClientStateUpdate, data: response });
552
+ }
553
+ onStateRequest() {
554
+ console.log("received state request");
555
+ this.handleEvent({ route: MESSAGES.onClientStateRequest, data: {} });
556
+ }
557
+ delay(ms) {
558
+ return new Promise((resolve) => setTimeout(resolve, ms));
559
+ }
560
+ // Sync methods
561
+ async enableBroadcast() {
562
+ console.log(`admin: enabled broadcast`);
563
+ try {
564
+ await this.syncServer.beginBroadcast();
565
+ return this.syncServer.getKey();
566
+ } catch (error) {
567
+ const code = error && typeof error === "object" && "code" in error ? error.code : "UNKNOWN";
568
+ console.error("Error on broadcast enable during topic sync setup:", code);
569
+ if (code === "ALREADY_LISTENING") {
570
+ return this.syncServer.getKey();
571
+ }
572
+ throw error;
573
+ }
574
+ }
575
+ async connect(topic, name, app) {
576
+ console.log(`client: requested connection to ${topic}`);
577
+ try {
578
+ await this.syncClient.connect({ key: topic, name, app });
579
+ await this.syncClient.requestPIN();
580
+ } catch (error) {
581
+ console.error("Error on topic connect during sync setup:", error);
582
+ throw error;
583
+ }
584
+ }
585
+ async requestKeystore(pin) {
586
+ console.log(`client: requested keystore using pin \`${pin}\``);
587
+ try {
588
+ await this.syncClient.requestKeystore(pin);
589
+ } catch (error) {
590
+ console.error("Error on keystore request during sync setup:", error);
591
+ throw error;
592
+ }
593
+ }
594
+ async approveRequest(data) {
595
+ console.log(`admin: request approved`);
596
+ try {
597
+ await this.syncServer.approveRequest(data.identity, data.appId);
598
+ await this.delay(3e3);
599
+ await this.syncServer.completeSetupSuccess();
600
+ } catch (error) {
601
+ console.error("Error on request approve during sync setup:", error);
602
+ throw error;
603
+ }
604
+ }
605
+ async denyRequest() {
606
+ console.log(`admin: request denied`);
607
+ try {
608
+ await this.syncServer.rejectRequest();
609
+ } catch (error) {
610
+ console.error("Error on request deny during sync setup:", error);
611
+ throw error;
612
+ }
613
+ }
614
+ async disableBroadcast() {
615
+ try {
616
+ await this.syncServer.endBroadcast();
617
+ await this.syncServer.broadcastDisabled();
618
+ } catch (error) {
619
+ console.error("Error on broadcast disable during sync setup:", error);
620
+ throw error;
621
+ }
622
+ }
623
+ // Device methods
624
+ async getDeviceInfo() {
625
+ if (this.deviceInfo) {
626
+ return this.deviceInfo;
627
+ }
628
+ const hostname = os.hostname();
629
+ const osInfo = await si.osInfo();
630
+ const uuid = await si.uuid();
631
+ const serial = osInfo.serial;
632
+ const distro = osInfo.distro;
633
+ const arch = osInfo.arch;
634
+ const hardwareUUID = uuid.hardware;
635
+ const key = serial + distro + arch + hardwareUUID;
636
+ const hash = crypto.createHash("sha256").update(key).digest("hex");
637
+ return { id: hash, name: hostname };
638
+ }
639
+ async getHostname() {
640
+ return os.hostname();
641
+ }
642
+ // Network methods
643
+ async connectPresence(topic) {
644
+ if (this.presenceConnected) {
645
+ console.log("Presence already connected, skipping");
646
+ return;
647
+ }
648
+ const deviceInfo = await this.getDeviceInfo();
649
+ const topicHash = crypto.createHash("sha256").update(topic).digest("hex");
650
+ console.log(`Connecting presence network to topic: ${topicHash}`);
651
+ await this.presence.connect(topicHash, deviceInfo.id);
652
+ this.presenceConnected = true;
653
+ }
654
+ broadcastClientState(id, state) {
655
+ this.presence.receiveStatusMessage(id, state);
656
+ }
657
+ };
658
+
659
+ // src/server/keyTransportServer.ts
660
+ var KeyTransportServer = class {
661
+ wss;
662
+ activeConnections = /* @__PURE__ */ new Set();
663
+ core;
664
+ port;
665
+ logger;
666
+ constructor(config) {
667
+ this.port = config.port || 3001;
668
+ this.logger = config.logger || console;
669
+ this.wss = new WebSocketServer({ port: this.port });
670
+ this.core = new CoreTransportLogic(this.handleEvent);
671
+ this.setupServerHandlers();
672
+ this.logger.log(
673
+ `\u{1F680} CryptForge WebSocket Server running on ws://localhost:${this.port}`
674
+ );
675
+ }
676
+ // Event handler for CoreTransportLogic
677
+ handleEvent = (options) => {
678
+ this.logger?.log(
679
+ `Core event with route: \`${options.route}\` data:`,
680
+ options.data
681
+ );
682
+ this.broadcastEvent(options.route, options.data);
683
+ };
684
+ // Helper function to send a response
685
+ sendResponse = (ws, type, requestId, data, error) => {
686
+ const response = {
687
+ type,
688
+ requestId,
689
+ ...data && { data },
690
+ ...error && { error }
691
+ };
692
+ ws.send(JSON.stringify(response));
693
+ this.logger?.log("\u{1F4E4} Sent response:", response);
694
+ };
695
+ // Helper function to broadcast events to all connected clients
696
+ broadcastEvent = (route, data) => {
697
+ const message = JSON.stringify({
698
+ type: route,
699
+ data
700
+ });
701
+ this.activeConnections.forEach((ws) => {
702
+ if (ws.readyState === WebSocket.OPEN) {
703
+ ws.send(message);
704
+ }
705
+ });
706
+ this.logger?.log(`\u{1F4E1} Broadcast event: ${route}`, data);
707
+ };
708
+ setupServerHandlers = () => {
709
+ this.wss.on("connection", (ws) => {
710
+ this.logger?.log("\u2705 Client connected");
711
+ this.activeConnections.add(ws);
712
+ ws.on("message", async (data) => {
713
+ await this.handleMessage(ws, data);
714
+ });
715
+ ws.on("close", () => {
716
+ this.logger?.log("\u{1F44B} Client disconnected");
717
+ this.activeConnections.delete(ws);
718
+ });
719
+ ws.on("error", (error) => {
720
+ this.logger?.error("\u274C WebSocket error:", error);
721
+ this.activeConnections.delete(ws);
722
+ });
723
+ });
724
+ this.wss.on("error", (error) => {
725
+ this.logger?.error("\u274C Server error:", error);
726
+ });
727
+ };
728
+ handleMessage = async (ws, data) => {
729
+ try {
730
+ const message = JSON.parse(data.toString());
731
+ this.logger?.log("\u{1F4E8} Received:", message);
732
+ const { type, requestId, data: requestData } = message;
733
+ switch (type) {
734
+ // Sync messages
735
+ case MESSAGES.enableBroadcast:
736
+ this.logger?.log("\u{1F4E1} Enabling broadcast...");
737
+ try {
738
+ const broadcastId = await this.core.enableBroadcast();
739
+ this.sendResponse(ws, type, requestId, { broadcastId });
740
+ } catch (error) {
741
+ this.sendResponse(ws, type, requestId, null, {
742
+ message: error instanceof Error ? error.message : "Unknown error"
743
+ });
744
+ }
745
+ break;
746
+ case MESSAGES.connect:
747
+ this.logger?.log("\u{1F50C} Connecting to topic...");
748
+ try {
749
+ await this.core.connect(
750
+ requestData.topic,
751
+ requestData.name,
752
+ requestData.app
753
+ );
754
+ this.sendResponse(ws, type, requestId, {
755
+ connected: true,
756
+ topic: requestData.topic,
757
+ name: requestData.name,
758
+ app: requestData.app
759
+ });
760
+ } catch (error) {
761
+ this.sendResponse(ws, type, requestId, null, {
762
+ message: error instanceof Error ? error.message : "Unknown error"
763
+ });
764
+ }
765
+ break;
766
+ case MESSAGES.requestKeystore:
767
+ this.logger?.log("\u{1F4E8} Requesting keystore...");
768
+ try {
769
+ await this.core.requestKeystore(requestData.pin);
770
+ this.sendResponse(ws, type, requestId, {
771
+ requested: true,
772
+ pin: requestData.pin
773
+ });
774
+ } catch (error) {
775
+ this.sendResponse(ws, type, requestId, null, {
776
+ message: error instanceof Error ? error.message : "Unknown error"
777
+ });
778
+ }
779
+ break;
780
+ case MESSAGES.approveRequest:
781
+ this.logger?.log("\u2705 Approving request...");
782
+ try {
783
+ await this.core.approveRequest({
784
+ identity: requestData.identity,
785
+ appId: requestData.appId
786
+ });
787
+ this.sendResponse(ws, type, requestId, {
788
+ approved: true
789
+ });
790
+ } catch (error) {
791
+ this.sendResponse(ws, type, requestId, null, {
792
+ message: error instanceof Error ? error.message : "Unknown error"
793
+ });
794
+ }
795
+ break;
796
+ case MESSAGES.denyRequest:
797
+ this.logger?.log("\u274C Denying request...");
798
+ try {
799
+ await this.core.denyRequest();
800
+ this.sendResponse(ws, type, requestId, {
801
+ denied: true
802
+ });
803
+ } catch (error) {
804
+ this.sendResponse(ws, type, requestId, null, {
805
+ message: error instanceof Error ? error.message : "Unknown error"
806
+ });
807
+ }
808
+ break;
809
+ case MESSAGES.disableBroadcast:
810
+ this.logger?.log("\u{1F4F4} Disabling broadcast...");
811
+ try {
812
+ await this.core.disableBroadcast();
813
+ this.sendResponse(ws, type, requestId, {
814
+ disabled: true
815
+ });
816
+ } catch (error) {
817
+ this.sendResponse(ws, type, requestId, null, {
818
+ message: error instanceof Error ? error.message : "Unknown error"
819
+ });
820
+ }
821
+ break;
822
+ case MESSAGES.getDeviceInfo:
823
+ this.logger?.log("\u{1F50D} Getting device info...");
824
+ try {
825
+ const deviceInfo = await this.core.getDeviceInfo();
826
+ this.sendResponse(ws, type, requestId, deviceInfo);
827
+ } catch (error) {
828
+ this.sendResponse(ws, type, requestId, null, {
829
+ message: error instanceof Error ? error.message : "Unknown error"
830
+ });
831
+ }
832
+ break;
833
+ case MESSAGES.getHostname:
834
+ this.logger?.log("\u{1F50D} Getting hostname...");
835
+ try {
836
+ const hostname = await this.core.getHostname();
837
+ this.sendResponse(ws, type, requestId, { hostname });
838
+ } catch (error) {
839
+ this.sendResponse(ws, type, requestId, null, {
840
+ message: error instanceof Error ? error.message : "Unknown error"
841
+ });
842
+ }
843
+ break;
844
+ case MESSAGES.connectPresence:
845
+ this.logger?.log("\u{1F50C} Connecting presence network...");
846
+ try {
847
+ await this.core.connectPresence(requestData.topic);
848
+ this.sendResponse(ws, type, requestId, { success: true });
849
+ } catch (error) {
850
+ this.sendResponse(ws, type, requestId, null, {
851
+ message: error instanceof Error ? error.message : "Unknown error"
852
+ });
853
+ }
854
+ break;
855
+ case MESSAGES.broadcastClientState:
856
+ this.logger?.log("\u{1F4E1} Broadcasting client state...");
857
+ try {
858
+ this.core.broadcastClientState(requestData.id, requestData.state);
859
+ this.sendResponse(ws, type, requestId, { success: true });
860
+ } catch (error) {
861
+ this.sendResponse(ws, type, requestId, null, {
862
+ message: error instanceof Error ? error.message : "Unknown error"
863
+ });
864
+ }
865
+ break;
866
+ default:
867
+ this.logger?.log(`\u26A0\uFE0F Unknown message type: ${type}`);
868
+ if (requestId) {
869
+ this.sendResponse(ws, type, requestId, null, {
870
+ message: `Unknown message type: ${type}`
871
+ });
872
+ }
873
+ }
874
+ } catch (error) {
875
+ this.logger?.error("\u274C Error processing message:", error);
876
+ try {
877
+ const parsed = JSON.parse(data.toString());
878
+ if (parsed.requestId) {
879
+ this.sendResponse(ws, parsed.type, parsed.requestId, null, {
880
+ message: error instanceof Error ? error.message : "Unknown error"
881
+ });
882
+ }
883
+ } catch (e) {
884
+ this.logger?.error("\u274C Could not send error response:", e);
885
+ }
886
+ }
887
+ };
888
+ // Public methods for managing the server
889
+ close = (callback) => {
890
+ this.wss.close(callback);
891
+ };
892
+ getActiveConnections = () => {
893
+ return new Set(this.activeConnections);
894
+ };
895
+ getPort = () => {
896
+ return this.port;
897
+ };
898
+ };
899
+ export {
900
+ KeyTransportServer,
901
+ useKeyExchangeServer
902
+ };