@cryptforge/key-exchange 0.1.0 → 0.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.
package/README.md CHANGED
@@ -103,6 +103,61 @@ src/
103
103
  - WebSocket-based communication for web/browser
104
104
  - Hyperswarm support for server-to-server
105
105
 
106
+ ## Vite Plugin (Electron Main Process)
107
+
108
+ When building Electron apps with Vite, native Node.js modules need to be externalized to prevent bundling errors. The package includes a Vite plugin that automatically handles this for you.
109
+
110
+ ### Usage
111
+
112
+ ```typescript
113
+ // vite.main.config.ts (for Electron main process)
114
+ import { defineConfig } from "vite";
115
+ import { cryptforgeMainPlugin } from "@cryptforge/key-exchange/vite";
116
+
117
+ export default defineConfig({
118
+ plugins: [cryptforgeMainPlugin()],
119
+ // ... rest of your config
120
+ });
121
+ ```
122
+
123
+ ### What it does
124
+
125
+ The plugin automatically externalizes these native modules:
126
+ - `hyperswarm`, `hyperdht`, `udx-native`, `utp-native`
127
+ - `sodium-native`, `hypercore`, `hypercore-crypto`
128
+ - `random-access-file`, `compact-encoding`, `b4a`
129
+ - `@cryptforge/key-exchange`
130
+
131
+ ### Without the plugin
132
+
133
+ If you prefer manual configuration or need to customize, you can add externals manually:
134
+
135
+ ```typescript
136
+ export default defineConfig({
137
+ build: {
138
+ rollupOptions: {
139
+ external: [
140
+ "hyperswarm",
141
+ "hyperdht",
142
+ "udx-native",
143
+ // ... other native modules
144
+ ],
145
+ },
146
+ },
147
+ });
148
+ ```
149
+
150
+ ### Important Notes
151
+
152
+ - ⚠️ **Only use this plugin for Electron main process builds**
153
+ - ✅ The plugin merges with your existing `external` configuration
154
+ - ✅ Works with both array and function forms of `external`
155
+ - ✅ Supports custom build tools that use Rollup
156
+
157
+ ## Additional Documentation
158
+
159
+ - **[USAGE_EXAMPLES.md](./USAGE_EXAMPLES.md)** - Additional usage examples for different environments
160
+
106
161
  ## License
107
162
 
108
163
  ISC
@@ -35,7 +35,7 @@ declare class CoreTransportLogic {
35
35
  getDeviceInfo(): Promise<DeviceInfo>;
36
36
  getHostname(): Promise<string>;
37
37
  connectPresence(topic: string): Promise<void>;
38
- broadcastClientState(id: string, state: ClientConnectionState): void;
38
+ broadcastClientState(id: string, state: ClientConnectionState, name?: string, isHeartbeat?: boolean): void;
39
39
  }
40
40
 
41
41
  export { CoreTransportLogic, setupKeyExchangeHandlers };
@@ -35,7 +35,7 @@ declare class CoreTransportLogic {
35
35
  getDeviceInfo(): Promise<DeviceInfo>;
36
36
  getHostname(): Promise<string>;
37
37
  connectPresence(topic: string): Promise<void>;
38
- broadcastClientState(id: string, state: ClientConnectionState): void;
38
+ broadcastClientState(id: string, state: ClientConnectionState, name?: string, isHeartbeat?: boolean): void;
39
39
  }
40
40
 
41
41
  export { CoreTransportLogic, setupKeyExchangeHandlers };
@@ -71,16 +71,9 @@ function useNetworkPresence(onUpdate, onRequest) {
71
71
  const swarm = new import_hyperswarm.default();
72
72
  swarm.on("connection", (connection) => {
73
73
  const name = import_b4a.default.toString(connection.remotePublicKey, "hex");
74
- console.log("Presence: * got a connection from:", name, "*");
75
74
  connections.push(connection);
76
75
  sendStateRequestMessage(connection);
77
76
  connection.once("close", () => {
78
- console.log(
79
- `Presence: Connection closed. Removing connection ${import_b4a.default.toString(
80
- connection.remotePublicKey,
81
- "hex"
82
- )}`
83
- );
84
77
  connections.splice(connections.indexOf(connection), 1);
85
78
  const id = deviceIDs.get(import_b4a.default.toString(connection.remotePublicKey, "hex"));
86
79
  if (id) {
@@ -93,17 +86,18 @@ function useNetworkPresence(onUpdate, onRequest) {
93
86
  try {
94
87
  parsedData = JSON.parse(data);
95
88
  } catch (error) {
96
- console.error("Error parsing data:", error);
89
+ console.error("Error parsing presence data:", error);
97
90
  return;
98
91
  }
99
92
  const header = parsedData;
100
- console.log(
101
- "Presence: got data for ",
102
- connection.publicKey.toString("hex")
103
- );
104
93
  if (header.type === "update") {
105
94
  const payload = parsedData;
106
- onUpdate({ id: payload.id, state: payload.state });
95
+ onUpdate({
96
+ id: payload.id,
97
+ state: payload.state,
98
+ name: payload.name,
99
+ isHeartbeat: payload.isHeartbeat
100
+ });
107
101
  deviceIDs.set(
108
102
  import_b4a.default.toString(connection.remotePublicKey, "hex"),
109
103
  payload.id
@@ -123,46 +117,35 @@ function useNetworkPresence(onUpdate, onRequest) {
123
117
  });
124
118
  const connections = [];
125
119
  const connect = async (topic, id) => {
126
- console.log("Presence: connecting to topic: ", topic);
127
120
  const key = import_b4a.default.from(topic, "hex");
128
- console.log("key: ", key);
129
121
  const discovery = swarm.join(key, { client: true, server: true });
130
- console.log("got discovery: ");
131
122
  const publicKey = import_b4a.default.toString(
132
123
  discovery.swarm.keyPair?.publicKey || discovery.swarm.publicKey || Buffer.alloc(32),
133
124
  "hex"
134
125
  );
135
- console.log("Presence: public key is ", publicKey);
136
126
  deviceIDs.set(publicKey, id);
137
- discovery.flushed().then(() => {
138
- console.log("Presence: joined topic:", import_b4a.default.toString(key, "hex"));
139
- });
127
+ await discovery.flushed();
140
128
  };
141
129
  const sendStateRequestMessage = async (connection) => {
142
130
  const message = {
143
131
  type: "request"
144
132
  };
145
133
  const payload = JSON.stringify(message);
146
- console.log(
147
- "sending state request message to: ",
148
- connection.publicKey.toString("hex")
149
- );
150
134
  connection.write(payload);
151
135
  };
152
- const receiveStatusMessage = async (id, state) => {
153
- return broadcastStatusMessage(id, state);
136
+ const receiveStatusMessage = async (id, state, name, isHeartbeat) => {
137
+ return broadcastStatusMessage(id, state, name, isHeartbeat);
154
138
  };
155
- const broadcastStatusMessage = (id, state) => {
156
- console.log(`server got connection state message for ${id}: `, state);
139
+ const broadcastStatusMessage = (id, state, name, isHeartbeat) => {
157
140
  const message = {
158
141
  type: "update",
159
142
  id,
160
- state
143
+ state,
144
+ name,
145
+ isHeartbeat
161
146
  };
162
- console.log("active connections: ", connections.length);
163
147
  const payload = JSON.stringify(message);
164
148
  for (const connection of connections) {
165
- console.log("sending message to: ", connection.publicKey.toString("hex"));
166
149
  connection.write(payload);
167
150
  }
168
151
  onUpdate(message);
@@ -513,7 +496,7 @@ function useKeyExchangeServer(onEvent) {
513
496
  });
514
497
  };
515
498
  const completeSetupSuccess = async () => {
516
- console.log("sending compltion message to client");
499
+ console.log("sending completion message to client");
517
500
  sendResponse({
518
501
  to: [client],
519
502
  header: "Setup Succeeded",
@@ -686,8 +669,8 @@ var CoreTransportLogic = class {
686
669
  await this.presence.connect(topicHash, deviceInfo.id);
687
670
  this.presenceConnected = true;
688
671
  }
689
- broadcastClientState(id, state) {
690
- this.presence.receiveStatusMessage(id, state);
672
+ broadcastClientState(id, state, name, isHeartbeat) {
673
+ this.presence.receiveStatusMessage(id, state, name, isHeartbeat);
691
674
  }
692
675
  };
693
676
 
@@ -740,9 +723,9 @@ var KeyTransportMain = class {
740
723
  connectPresence = async (topic) => {
741
724
  return this.core.connectPresence(topic);
742
725
  };
743
- broadcastClientState = async (id, state) => {
726
+ broadcastClientState = async (id, state, name, isHeartbeat) => {
744
727
  this.peers.set(id, state);
745
- this.core.broadcastClientState(id, state);
728
+ this.core.broadcastClientState(id, state, name, isHeartbeat);
746
729
  };
747
730
  onClientStateRequest = async (_callback) => {
748
731
  };
@@ -794,8 +777,8 @@ var setupKeyExchangeHandlers = () => {
794
777
  });
795
778
  import_electron2.ipcMain.handle(
796
779
  MESSAGES.broadcastClientState,
797
- async (_event, id, state) => {
798
- return transport.broadcastClientState(id, state);
780
+ async (_event, id, state, name, isHeartbeat) => {
781
+ return transport.broadcastClientState(id, state, name, isHeartbeat);
799
782
  }
800
783
  );
801
784
  };
@@ -34,16 +34,9 @@ function useNetworkPresence(onUpdate, onRequest) {
34
34
  const swarm = new Hyperswarm();
35
35
  swarm.on("connection", (connection) => {
36
36
  const name = b4a.toString(connection.remotePublicKey, "hex");
37
- console.log("Presence: * got a connection from:", name, "*");
38
37
  connections.push(connection);
39
38
  sendStateRequestMessage(connection);
40
39
  connection.once("close", () => {
41
- console.log(
42
- `Presence: Connection closed. Removing connection ${b4a.toString(
43
- connection.remotePublicKey,
44
- "hex"
45
- )}`
46
- );
47
40
  connections.splice(connections.indexOf(connection), 1);
48
41
  const id = deviceIDs.get(b4a.toString(connection.remotePublicKey, "hex"));
49
42
  if (id) {
@@ -56,17 +49,18 @@ function useNetworkPresence(onUpdate, onRequest) {
56
49
  try {
57
50
  parsedData = JSON.parse(data);
58
51
  } catch (error) {
59
- console.error("Error parsing data:", error);
52
+ console.error("Error parsing presence data:", error);
60
53
  return;
61
54
  }
62
55
  const header = parsedData;
63
- console.log(
64
- "Presence: got data for ",
65
- connection.publicKey.toString("hex")
66
- );
67
56
  if (header.type === "update") {
68
57
  const payload = parsedData;
69
- onUpdate({ id: payload.id, state: payload.state });
58
+ onUpdate({
59
+ id: payload.id,
60
+ state: payload.state,
61
+ name: payload.name,
62
+ isHeartbeat: payload.isHeartbeat
63
+ });
70
64
  deviceIDs.set(
71
65
  b4a.toString(connection.remotePublicKey, "hex"),
72
66
  payload.id
@@ -86,46 +80,35 @@ function useNetworkPresence(onUpdate, onRequest) {
86
80
  });
87
81
  const connections = [];
88
82
  const connect = async (topic, id) => {
89
- console.log("Presence: connecting to topic: ", topic);
90
83
  const key = b4a.from(topic, "hex");
91
- console.log("key: ", key);
92
84
  const discovery = swarm.join(key, { client: true, server: true });
93
- console.log("got discovery: ");
94
85
  const publicKey = b4a.toString(
95
86
  discovery.swarm.keyPair?.publicKey || discovery.swarm.publicKey || Buffer.alloc(32),
96
87
  "hex"
97
88
  );
98
- console.log("Presence: public key is ", publicKey);
99
89
  deviceIDs.set(publicKey, id);
100
- discovery.flushed().then(() => {
101
- console.log("Presence: joined topic:", b4a.toString(key, "hex"));
102
- });
90
+ await discovery.flushed();
103
91
  };
104
92
  const sendStateRequestMessage = async (connection) => {
105
93
  const message = {
106
94
  type: "request"
107
95
  };
108
96
  const payload = JSON.stringify(message);
109
- console.log(
110
- "sending state request message to: ",
111
- connection.publicKey.toString("hex")
112
- );
113
97
  connection.write(payload);
114
98
  };
115
- const receiveStatusMessage = async (id, state) => {
116
- return broadcastStatusMessage(id, state);
99
+ const receiveStatusMessage = async (id, state, name, isHeartbeat) => {
100
+ return broadcastStatusMessage(id, state, name, isHeartbeat);
117
101
  };
118
- const broadcastStatusMessage = (id, state) => {
119
- console.log(`server got connection state message for ${id}: `, state);
102
+ const broadcastStatusMessage = (id, state, name, isHeartbeat) => {
120
103
  const message = {
121
104
  type: "update",
122
105
  id,
123
- state
106
+ state,
107
+ name,
108
+ isHeartbeat
124
109
  };
125
- console.log("active connections: ", connections.length);
126
110
  const payload = JSON.stringify(message);
127
111
  for (const connection of connections) {
128
- console.log("sending message to: ", connection.publicKey.toString("hex"));
129
112
  connection.write(payload);
130
113
  }
131
114
  onUpdate(message);
@@ -476,7 +459,7 @@ function useKeyExchangeServer(onEvent) {
476
459
  });
477
460
  };
478
461
  const completeSetupSuccess = async () => {
479
- console.log("sending compltion message to client");
462
+ console.log("sending completion message to client");
480
463
  sendResponse({
481
464
  to: [client],
482
465
  header: "Setup Succeeded",
@@ -649,8 +632,8 @@ var CoreTransportLogic = class {
649
632
  await this.presence.connect(topicHash, deviceInfo.id);
650
633
  this.presenceConnected = true;
651
634
  }
652
- broadcastClientState(id, state) {
653
- this.presence.receiveStatusMessage(id, state);
635
+ broadcastClientState(id, state, name, isHeartbeat) {
636
+ this.presence.receiveStatusMessage(id, state, name, isHeartbeat);
654
637
  }
655
638
  };
656
639
 
@@ -703,9 +686,9 @@ var KeyTransportMain = class {
703
686
  connectPresence = async (topic) => {
704
687
  return this.core.connectPresence(topic);
705
688
  };
706
- broadcastClientState = async (id, state) => {
689
+ broadcastClientState = async (id, state, name, isHeartbeat) => {
707
690
  this.peers.set(id, state);
708
- this.core.broadcastClientState(id, state);
691
+ this.core.broadcastClientState(id, state, name, isHeartbeat);
709
692
  };
710
693
  onClientStateRequest = async (_callback) => {
711
694
  };
@@ -757,8 +740,8 @@ var setupKeyExchangeHandlers = () => {
757
740
  });
758
741
  ipcMain.handle(
759
742
  MESSAGES.broadcastClientState,
760
- async (_event, id, state) => {
761
- return transport.broadcastClientState(id, state);
743
+ async (_event, id, state, name, isHeartbeat) => {
744
+ return transport.broadcastClientState(id, state, name, isHeartbeat);
762
745
  }
763
746
  );
764
747
  };
@@ -97,7 +97,7 @@ var setupKeyExchangePreloadAPI = () => {
97
97
  getDeviceInfo: () => import_electron.ipcRenderer.invoke(MESSAGES.getDeviceInfo),
98
98
  getHostname: () => import_electron.ipcRenderer.invoke(MESSAGES.getHostname),
99
99
  connectPresence: (topic) => import_electron.ipcRenderer.invoke(MESSAGES.connectPresence, topic),
100
- broadcastClientState: (id, state) => import_electron.ipcRenderer.invoke(MESSAGES.broadcastClientState, id, state),
100
+ broadcastClientState: (id, state, name, isHeartbeat) => import_electron.ipcRenderer.invoke(MESSAGES.broadcastClientState, id, state, name, isHeartbeat),
101
101
  onClientStateRequest: (callback) => import_electron.ipcRenderer.on(
102
102
  MESSAGES.onClientStateRequest,
103
103
  (_event) => callback()
@@ -71,7 +71,7 @@ var setupKeyExchangePreloadAPI = () => {
71
71
  getDeviceInfo: () => ipcRenderer.invoke(MESSAGES.getDeviceInfo),
72
72
  getHostname: () => ipcRenderer.invoke(MESSAGES.getHostname),
73
73
  connectPresence: (topic) => ipcRenderer.invoke(MESSAGES.connectPresence, topic),
74
- broadcastClientState: (id, state) => ipcRenderer.invoke(MESSAGES.broadcastClientState, id, state),
74
+ broadcastClientState: (id, state, name, isHeartbeat) => ipcRenderer.invoke(MESSAGES.broadcastClientState, id, state, name, isHeartbeat),
75
75
  onClientStateRequest: (callback) => ipcRenderer.on(
76
76
  MESSAGES.onClientStateRequest,
77
77
  (_event) => callback()
@@ -25,7 +25,7 @@ interface KeyExchangeElectronAPI {
25
25
  getDeviceInfo: () => Promise<Record<string, any>>;
26
26
  getHostname: () => Promise<string>;
27
27
  connectPresence: (topic: string) => Promise<void>;
28
- broadcastClientState: (id: string, state: ClientConnectionState) => Promise<void>;
28
+ broadcastClientState: (id: string, state: ClientConnectionState, name?: string, isHeartbeat?: boolean) => Promise<void>;
29
29
  onClientStateRequest: (callback: () => void) => Promise<() => void>;
30
30
  onClientStateUpdate: (callback: (response: {
31
31
  id: string;
@@ -61,7 +61,7 @@ declare class KeyTransportRenderer implements KeyTransportAdapter {
61
61
  getDeviceInfo: () => Promise<Record<string, any>>;
62
62
  getHostname: () => Promise<string>;
63
63
  connectPresence: (topic: string) => Promise<void>;
64
- broadcastClientState: (id: string, state: ClientConnectionState) => Promise<void>;
64
+ broadcastClientState: (id: string, state: ClientConnectionState, name?: string, isHeartbeat?: boolean) => Promise<void>;
65
65
  onClientStateRequest: (callback: () => void) => Promise<() => void>;
66
66
  onClientStateUpdate: (callback: (response: {
67
67
  id: string;
@@ -25,7 +25,7 @@ interface KeyExchangeElectronAPI {
25
25
  getDeviceInfo: () => Promise<Record<string, any>>;
26
26
  getHostname: () => Promise<string>;
27
27
  connectPresence: (topic: string) => Promise<void>;
28
- broadcastClientState: (id: string, state: ClientConnectionState) => Promise<void>;
28
+ broadcastClientState: (id: string, state: ClientConnectionState, name?: string, isHeartbeat?: boolean) => Promise<void>;
29
29
  onClientStateRequest: (callback: () => void) => Promise<() => void>;
30
30
  onClientStateUpdate: (callback: (response: {
31
31
  id: string;
@@ -61,7 +61,7 @@ declare class KeyTransportRenderer implements KeyTransportAdapter {
61
61
  getDeviceInfo: () => Promise<Record<string, any>>;
62
62
  getHostname: () => Promise<string>;
63
63
  connectPresence: (topic: string) => Promise<void>;
64
- broadcastClientState: (id: string, state: ClientConnectionState) => Promise<void>;
64
+ broadcastClientState: (id: string, state: ClientConnectionState, name?: string, isHeartbeat?: boolean) => Promise<void>;
65
65
  onClientStateRequest: (callback: () => void) => Promise<() => void>;
66
66
  onClientStateUpdate: (callback: (response: {
67
67
  id: string;
@@ -81,8 +81,8 @@ var KeyTransportRenderer = class {
81
81
  connectPresence = async (topic) => {
82
82
  return window.keyExchangeElectronAPI.connectPresence(topic);
83
83
  };
84
- broadcastClientState = async (id, state) => {
85
- return window.keyExchangeElectronAPI.broadcastClientState(id, state);
84
+ broadcastClientState = async (id, state, name, isHeartbeat) => {
85
+ return window.keyExchangeElectronAPI.broadcastClientState(id, state, name, isHeartbeat);
86
86
  };
87
87
  onClientStateRequest = async (callback) => {
88
88
  return window.keyExchangeElectronAPI.onClientStateRequest(callback);
@@ -54,8 +54,8 @@ var KeyTransportRenderer = class {
54
54
  connectPresence = async (topic) => {
55
55
  return window.keyExchangeElectronAPI.connectPresence(topic);
56
56
  };
57
- broadcastClientState = async (id, state) => {
58
- return window.keyExchangeElectronAPI.broadcastClientState(id, state);
57
+ broadcastClientState = async (id, state, name, isHeartbeat) => {
58
+ return window.keyExchangeElectronAPI.broadcastClientState(id, state, name, isHeartbeat);
59
59
  };
60
60
  onClientStateRequest = async (callback) => {
61
61
  return window.keyExchangeElectronAPI.onClientStateRequest(callback);
package/dist/index.d.mts CHANGED
@@ -40,7 +40,7 @@ declare class KeyTransportClient implements KeyTransportAdapter {
40
40
  getDeviceInfo: () => Promise<Record<string, any>>;
41
41
  getHostname: () => Promise<string>;
42
42
  connectPresence: (topic: string) => Promise<void>;
43
- broadcastClientState: (id: string, state: ClientConnectionState) => Promise<void>;
43
+ broadcastClientState: (id: string, state: ClientConnectionState, name?: string, isHeartbeat?: boolean) => Promise<void>;
44
44
  onClientStateRequest: (callback: () => void) => Promise<() => void>;
45
45
  onClientStateUpdate: (callback: (response: {
46
46
  id: string;
@@ -62,4 +62,48 @@ declare class KeyTransportClient implements KeyTransportAdapter {
62
62
  isConnected: () => boolean;
63
63
  }
64
64
 
65
- export { KeyTransportClient };
65
+ /**
66
+ * Manages linked devices (devices that have completed key exchange)
67
+ * Stores device info in localStorage scoped per app
68
+ */
69
+ interface LinkedDevice {
70
+ id: string;
71
+ name: string;
72
+ customName?: string;
73
+ linkedAt: number;
74
+ }
75
+ declare class DevicePairingManager {
76
+ private storageKey;
77
+ constructor(identityId: string);
78
+ /**
79
+ * Get all linked devices for this app
80
+ */
81
+ getLinkedDevices: () => LinkedDevice[];
82
+ /**
83
+ * Add a device to the linked devices list
84
+ * Called after successful key exchange
85
+ */
86
+ addLinkedDevice: (device: {
87
+ id: string;
88
+ name: string;
89
+ }) => void;
90
+ /**
91
+ * Remove a device from the linked devices list
92
+ * For future use when user wants to unlink a device
93
+ */
94
+ removeLinkedDevice: (id: string) => void;
95
+ /**
96
+ * Update the hostname of a device (system-level name)
97
+ */
98
+ updateDeviceName: (id: string, name: string) => void;
99
+ /**
100
+ * Set a custom user-defined name for a device
101
+ */
102
+ setCustomName: (id: string, customName: string) => void;
103
+ /**
104
+ * Check if a device is linked
105
+ */
106
+ isLinked: (id: string) => boolean;
107
+ }
108
+
109
+ export { DevicePairingManager, KeyTransportClient, type LinkedDevice };
package/dist/index.d.ts CHANGED
@@ -40,7 +40,7 @@ declare class KeyTransportClient implements KeyTransportAdapter {
40
40
  getDeviceInfo: () => Promise<Record<string, any>>;
41
41
  getHostname: () => Promise<string>;
42
42
  connectPresence: (topic: string) => Promise<void>;
43
- broadcastClientState: (id: string, state: ClientConnectionState) => Promise<void>;
43
+ broadcastClientState: (id: string, state: ClientConnectionState, name?: string, isHeartbeat?: boolean) => Promise<void>;
44
44
  onClientStateRequest: (callback: () => void) => Promise<() => void>;
45
45
  onClientStateUpdate: (callback: (response: {
46
46
  id: string;
@@ -62,4 +62,48 @@ declare class KeyTransportClient implements KeyTransportAdapter {
62
62
  isConnected: () => boolean;
63
63
  }
64
64
 
65
- export { KeyTransportClient };
65
+ /**
66
+ * Manages linked devices (devices that have completed key exchange)
67
+ * Stores device info in localStorage scoped per app
68
+ */
69
+ interface LinkedDevice {
70
+ id: string;
71
+ name: string;
72
+ customName?: string;
73
+ linkedAt: number;
74
+ }
75
+ declare class DevicePairingManager {
76
+ private storageKey;
77
+ constructor(identityId: string);
78
+ /**
79
+ * Get all linked devices for this app
80
+ */
81
+ getLinkedDevices: () => LinkedDevice[];
82
+ /**
83
+ * Add a device to the linked devices list
84
+ * Called after successful key exchange
85
+ */
86
+ addLinkedDevice: (device: {
87
+ id: string;
88
+ name: string;
89
+ }) => void;
90
+ /**
91
+ * Remove a device from the linked devices list
92
+ * For future use when user wants to unlink a device
93
+ */
94
+ removeLinkedDevice: (id: string) => void;
95
+ /**
96
+ * Update the hostname of a device (system-level name)
97
+ */
98
+ updateDeviceName: (id: string, name: string) => void;
99
+ /**
100
+ * Set a custom user-defined name for a device
101
+ */
102
+ setCustomName: (id: string, customName: string) => void;
103
+ /**
104
+ * Check if a device is linked
105
+ */
106
+ isLinked: (id: string) => boolean;
107
+ }
108
+
109
+ export { DevicePairingManager, KeyTransportClient, type LinkedDevice };
package/dist/index.js CHANGED
@@ -195,6 +195,7 @@ var require_eventemitter3 = __commonJS({
195
195
  // src/index.ts
196
196
  var index_exports = {};
197
197
  __export(index_exports, {
198
+ DevicePairingManager: () => DevicePairingManager,
198
199
  KeyTransportClient: () => KeyTransportClient
199
200
  });
200
201
  module.exports = __toCommonJS(index_exports);
@@ -508,11 +509,11 @@ var KeyTransportClient = class {
508
509
  throw error;
509
510
  }
510
511
  };
511
- broadcastClientState = async (id, state) => {
512
+ broadcastClientState = async (id, state, name, isHeartbeat) => {
512
513
  console.log("KeyTransportClient.broadcastClientState");
513
514
  this.peers.set(id, state);
514
515
  try {
515
- await this.sendRequest(MESSAGES.broadcastClientState, { id, state });
516
+ await this.sendRequest(MESSAGES.broadcastClientState, { id, state, name, isHeartbeat });
516
517
  } catch (error) {
517
518
  console.error("Failed to broadcast client state:", error);
518
519
  throw error;
@@ -580,7 +581,99 @@ var KeyTransportClient = class {
580
581
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
581
582
  };
582
583
  };
584
+
585
+ // src/devicePairing.ts
586
+ var DevicePairingManager = class {
587
+ storageKey;
588
+ constructor(identityId) {
589
+ this.storageKey = `cryptforge:identity:${identityId}:linked-devices`;
590
+ }
591
+ /**
592
+ * Get all linked devices for this app
593
+ */
594
+ getLinkedDevices = () => {
595
+ try {
596
+ const stored = localStorage.getItem(this.storageKey);
597
+ return stored ? JSON.parse(stored) : [];
598
+ } catch (error) {
599
+ console.error("Failed to load linked devices:", error);
600
+ return [];
601
+ }
602
+ };
603
+ /**
604
+ * Add a device to the linked devices list
605
+ * Called after successful key exchange
606
+ */
607
+ addLinkedDevice = (device) => {
608
+ try {
609
+ const devices = this.getLinkedDevices();
610
+ const existing = devices.find((d) => d.id === device.id);
611
+ if (existing) {
612
+ existing.name = device.name;
613
+ } else {
614
+ devices.push({
615
+ id: device.id,
616
+ name: device.name,
617
+ linkedAt: Date.now()
618
+ });
619
+ }
620
+ localStorage.setItem(this.storageKey, JSON.stringify(devices));
621
+ } catch (error) {
622
+ console.error("Failed to add linked device:", error);
623
+ }
624
+ };
625
+ /**
626
+ * Remove a device from the linked devices list
627
+ * For future use when user wants to unlink a device
628
+ */
629
+ removeLinkedDevice = (id) => {
630
+ try {
631
+ const devices = this.getLinkedDevices().filter((d) => d.id !== id);
632
+ localStorage.setItem(this.storageKey, JSON.stringify(devices));
633
+ console.log(`Removed linked device: ${id}`);
634
+ } catch (error) {
635
+ console.error("Failed to remove linked device:", error);
636
+ }
637
+ };
638
+ /**
639
+ * Update the hostname of a device (system-level name)
640
+ */
641
+ updateDeviceName = (id, name) => {
642
+ try {
643
+ const devices = this.getLinkedDevices();
644
+ const device = devices.find((d) => d.id === id);
645
+ if (device) {
646
+ device.name = name;
647
+ localStorage.setItem(this.storageKey, JSON.stringify(devices));
648
+ }
649
+ } catch (error) {
650
+ console.error("Failed to update device name:", error);
651
+ }
652
+ };
653
+ /**
654
+ * Set a custom user-defined name for a device
655
+ */
656
+ setCustomName = (id, customName) => {
657
+ try {
658
+ const devices = this.getLinkedDevices();
659
+ const device = devices.find((d) => d.id === id);
660
+ if (device) {
661
+ device.customName = customName.trim() || void 0;
662
+ localStorage.setItem(this.storageKey, JSON.stringify(devices));
663
+ }
664
+ } catch (error) {
665
+ console.error("Failed to set custom device name:", error);
666
+ }
667
+ };
668
+ /**
669
+ * Check if a device is linked
670
+ */
671
+ isLinked = (id) => {
672
+ return this.getLinkedDevices().some((d) => d.id === id);
673
+ };
674
+ };
583
675
  // Annotate the CommonJS export names for ESM import in node:
584
676
  0 && (module.exports = {
677
+ DevicePairingManager,
585
678
  KeyTransportClient
586
679
  });