@meshwhisper/sdk 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.
Files changed (163) hide show
  1. package/README.md +138 -0
  2. package/dist/browser/index.d.ts +4 -0
  3. package/dist/browser/index.d.ts.map +1 -0
  4. package/dist/browser/index.js +19 -0
  5. package/dist/browser/index.js.map +1 -0
  6. package/dist/chaff/index.d.ts +91 -0
  7. package/dist/chaff/index.d.ts.map +1 -0
  8. package/dist/chaff/index.js +268 -0
  9. package/dist/chaff/index.js.map +1 -0
  10. package/dist/cluster/index.d.ts +159 -0
  11. package/dist/cluster/index.d.ts.map +1 -0
  12. package/dist/cluster/index.js +393 -0
  13. package/dist/cluster/index.js.map +1 -0
  14. package/dist/compliance/index.d.ts +129 -0
  15. package/dist/compliance/index.d.ts.map +1 -0
  16. package/dist/compliance/index.js +315 -0
  17. package/dist/compliance/index.js.map +1 -0
  18. package/dist/crypto/index.d.ts +65 -0
  19. package/dist/crypto/index.d.ts.map +1 -0
  20. package/dist/crypto/index.js +146 -0
  21. package/dist/crypto/index.js.map +1 -0
  22. package/dist/group/index.d.ts +155 -0
  23. package/dist/group/index.d.ts.map +1 -0
  24. package/dist/group/index.js +560 -0
  25. package/dist/group/index.js.map +1 -0
  26. package/dist/index.d.ts +7 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +11 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/namespace/index.d.ts +155 -0
  31. package/dist/namespace/index.d.ts.map +1 -0
  32. package/dist/namespace/index.js +278 -0
  33. package/dist/namespace/index.js.map +1 -0
  34. package/dist/node/index.d.ts +4 -0
  35. package/dist/node/index.d.ts.map +1 -0
  36. package/dist/node/index.js +19 -0
  37. package/dist/node/index.js.map +1 -0
  38. package/dist/packet/index.d.ts +63 -0
  39. package/dist/packet/index.d.ts.map +1 -0
  40. package/dist/packet/index.js +244 -0
  41. package/dist/packet/index.js.map +1 -0
  42. package/dist/permissions/index.d.ts +107 -0
  43. package/dist/permissions/index.d.ts.map +1 -0
  44. package/dist/permissions/index.js +282 -0
  45. package/dist/permissions/index.js.map +1 -0
  46. package/dist/persistence/idb-storage.d.ts +27 -0
  47. package/dist/persistence/idb-storage.d.ts.map +1 -0
  48. package/dist/persistence/idb-storage.js +75 -0
  49. package/dist/persistence/idb-storage.js.map +1 -0
  50. package/dist/persistence/index.d.ts +4 -0
  51. package/dist/persistence/index.d.ts.map +1 -0
  52. package/dist/persistence/index.js +3 -0
  53. package/dist/persistence/index.js.map +1 -0
  54. package/dist/persistence/node-storage.d.ts +33 -0
  55. package/dist/persistence/node-storage.d.ts.map +1 -0
  56. package/dist/persistence/node-storage.js +90 -0
  57. package/dist/persistence/node-storage.js.map +1 -0
  58. package/dist/persistence/serialization.d.ts +4 -0
  59. package/dist/persistence/serialization.d.ts.map +1 -0
  60. package/dist/persistence/serialization.js +49 -0
  61. package/dist/persistence/serialization.js.map +1 -0
  62. package/dist/persistence/types.d.ts +29 -0
  63. package/dist/persistence/types.d.ts.map +1 -0
  64. package/dist/persistence/types.js +5 -0
  65. package/dist/persistence/types.js.map +1 -0
  66. package/dist/ratchet/index.d.ts +80 -0
  67. package/dist/ratchet/index.d.ts.map +1 -0
  68. package/dist/ratchet/index.js +259 -0
  69. package/dist/ratchet/index.js.map +1 -0
  70. package/dist/reciprocity/index.d.ts +109 -0
  71. package/dist/reciprocity/index.d.ts.map +1 -0
  72. package/dist/reciprocity/index.js +311 -0
  73. package/dist/reciprocity/index.js.map +1 -0
  74. package/dist/relay/index.d.ts +87 -0
  75. package/dist/relay/index.d.ts.map +1 -0
  76. package/dist/relay/index.js +286 -0
  77. package/dist/relay/index.js.map +1 -0
  78. package/dist/routing/index.d.ts +136 -0
  79. package/dist/routing/index.d.ts.map +1 -0
  80. package/dist/routing/index.js +478 -0
  81. package/dist/routing/index.js.map +1 -0
  82. package/dist/sdk/index.d.ts +322 -0
  83. package/dist/sdk/index.d.ts.map +1 -0
  84. package/dist/sdk/index.js +1530 -0
  85. package/dist/sdk/index.js.map +1 -0
  86. package/dist/sybil/index.d.ts +123 -0
  87. package/dist/sybil/index.d.ts.map +1 -0
  88. package/dist/sybil/index.js +491 -0
  89. package/dist/sybil/index.js.map +1 -0
  90. package/dist/transport/browser/index.d.ts +34 -0
  91. package/dist/transport/browser/index.d.ts.map +1 -0
  92. package/dist/transport/browser/index.js +176 -0
  93. package/dist/transport/browser/index.js.map +1 -0
  94. package/dist/transport/local/index.d.ts +57 -0
  95. package/dist/transport/local/index.d.ts.map +1 -0
  96. package/dist/transport/local/index.js +442 -0
  97. package/dist/transport/local/index.js.map +1 -0
  98. package/dist/transport/negotiator/index.d.ts +79 -0
  99. package/dist/transport/negotiator/index.d.ts.map +1 -0
  100. package/dist/transport/negotiator/index.js +289 -0
  101. package/dist/transport/negotiator/index.js.map +1 -0
  102. package/dist/transport/node/index.d.ts +56 -0
  103. package/dist/transport/node/index.d.ts.map +1 -0
  104. package/dist/transport/node/index.js +209 -0
  105. package/dist/transport/node/index.js.map +1 -0
  106. package/dist/transport/noop/index.d.ts +11 -0
  107. package/dist/transport/noop/index.d.ts.map +1 -0
  108. package/dist/transport/noop/index.js +20 -0
  109. package/dist/transport/noop/index.js.map +1 -0
  110. package/dist/transport/p2p/index.d.ts +109 -0
  111. package/dist/transport/p2p/index.d.ts.map +1 -0
  112. package/dist/transport/p2p/index.js +237 -0
  113. package/dist/transport/p2p/index.js.map +1 -0
  114. package/dist/transport/websocket/index.d.ts +89 -0
  115. package/dist/transport/websocket/index.d.ts.map +1 -0
  116. package/dist/transport/websocket/index.js +498 -0
  117. package/dist/transport/websocket/index.js.map +1 -0
  118. package/dist/transport/websocket/serialize.d.ts +5 -0
  119. package/dist/transport/websocket/serialize.d.ts.map +1 -0
  120. package/dist/transport/websocket/serialize.js +55 -0
  121. package/dist/transport/websocket/serialize.js.map +1 -0
  122. package/dist/types.d.ts +215 -0
  123. package/dist/types.d.ts.map +1 -0
  124. package/dist/types.js +15 -0
  125. package/dist/types.js.map +1 -0
  126. package/dist/x3dh/index.d.ts +120 -0
  127. package/dist/x3dh/index.d.ts.map +1 -0
  128. package/dist/x3dh/index.js +290 -0
  129. package/dist/x3dh/index.js.map +1 -0
  130. package/package.json +59 -0
  131. package/src/browser/index.ts +19 -0
  132. package/src/chaff/index.ts +340 -0
  133. package/src/cluster/index.ts +482 -0
  134. package/src/compliance/index.ts +407 -0
  135. package/src/crypto/index.ts +193 -0
  136. package/src/group/index.ts +719 -0
  137. package/src/index.ts +87 -0
  138. package/src/lz4js.d.ts +58 -0
  139. package/src/namespace/index.ts +336 -0
  140. package/src/node/index.ts +19 -0
  141. package/src/packet/index.ts +326 -0
  142. package/src/permissions/index.ts +405 -0
  143. package/src/persistence/idb-storage.ts +83 -0
  144. package/src/persistence/index.ts +3 -0
  145. package/src/persistence/node-storage.ts +96 -0
  146. package/src/persistence/serialization.ts +75 -0
  147. package/src/persistence/types.ts +33 -0
  148. package/src/ratchet/index.ts +363 -0
  149. package/src/reciprocity/index.ts +371 -0
  150. package/src/relay/index.ts +382 -0
  151. package/src/routing/index.ts +577 -0
  152. package/src/sdk/index.ts +1994 -0
  153. package/src/sybil/index.ts +661 -0
  154. package/src/transport/browser/index.ts +201 -0
  155. package/src/transport/local/index.ts +540 -0
  156. package/src/transport/negotiator/index.ts +397 -0
  157. package/src/transport/node/index.ts +234 -0
  158. package/src/transport/noop/index.ts +22 -0
  159. package/src/transport/p2p/index.ts +345 -0
  160. package/src/transport/websocket/index.ts +660 -0
  161. package/src/transport/websocket/serialize.ts +68 -0
  162. package/src/types.ts +275 -0
  163. package/src/x3dh/index.ts +388 -0
@@ -0,0 +1,159 @@
1
+ import type { ClusterDevice, DeviceCapability } from '../types.js';
2
+ /**
3
+ * A message queued for synchronization across the device cluster.
4
+ */
5
+ export interface SyncMessage {
6
+ messageId: string;
7
+ encryptedPayload: Uint8Array;
8
+ receivedAt: number;
9
+ receivedBy: string;
10
+ syncedTo: Set<string>;
11
+ }
12
+ export interface ClusterStatus {
13
+ deviceCount: number;
14
+ primaryId: string;
15
+ syncPending: number;
16
+ }
17
+ /**
18
+ * Manages the queue of messages waiting to be synchronized to other
19
+ * devices in the cluster.
20
+ */
21
+ export declare class MessageSyncManager {
22
+ private readonly messages;
23
+ /**
24
+ * Enqueue a received message for sync to other cluster devices.
25
+ */
26
+ queueForSync(message: SyncMessage): void;
27
+ /**
28
+ * Retrieve all messages that have **not** yet been synced to `deviceId`.
29
+ */
30
+ getUnsyncedMessages(deviceId: string): SyncMessage[];
31
+ /**
32
+ * Mark a single message as synced to a specific device.
33
+ */
34
+ markSynced(messageId: string, deviceId: string): void;
35
+ /**
36
+ * Convenience method: returns all unsynced messages for `targetDeviceId`
37
+ * and marks each one as synced to that device in a single pass.
38
+ */
39
+ sync(targetDeviceId: string): SyncMessage[];
40
+ /**
41
+ * Total count of messages that still need to be synced to at least one
42
+ * device. Used by `DeviceCluster.getClusterStatus()`.
43
+ */
44
+ getPendingCount(allDeviceIds: string[]): number;
45
+ /**
46
+ * Remove all tracked messages.
47
+ */
48
+ clear(): void;
49
+ private cloneMessage;
50
+ }
51
+ /**
52
+ * Manages a personal device cluster.
53
+ *
54
+ * A cluster groups all of a single user's devices so they can:
55
+ * - elect a primary receiver (best battery/connectivity/willingness)
56
+ * - accept messages on behalf of the user from any member
57
+ * - synchronize messages to all other members when connectivity allows
58
+ */
59
+ export declare class DeviceCluster {
60
+ private readonly localDeviceId;
61
+ private readonly clusterKey;
62
+ private readonly devices;
63
+ private primaryDeviceId;
64
+ private running;
65
+ /** Public message sync manager. */
66
+ readonly syncManager: MessageSyncManager;
67
+ constructor(identityKey: Uint8Array, localDeviceId: string);
68
+ /**
69
+ * Derive a cluster key from the user's identity private key.
70
+ *
71
+ * clusterKey = BLAKE3(identity_key || "meshwhisper-cluster-v1")
72
+ */
73
+ static deriveClusterKey(identityPrivateKey: Uint8Array): Uint8Array;
74
+ /**
75
+ * Register a device in the cluster.
76
+ */
77
+ addDevice(device: ClusterDevice): void;
78
+ /**
79
+ * Remove a device from the cluster.
80
+ */
81
+ removeDevice(deviceId: string): void;
82
+ /**
83
+ * Return a snapshot of all devices currently in the cluster.
84
+ */
85
+ getDevices(): ClusterDevice[];
86
+ /**
87
+ * Return the local device entry.
88
+ *
89
+ * @throws if the local device has not been added to the cluster.
90
+ */
91
+ getLocalDevice(): ClusterDevice;
92
+ /**
93
+ * Check whether a device ID belongs to this cluster.
94
+ */
95
+ isClusterMember(deviceId: string): boolean;
96
+ /**
97
+ * Elect the most available device as primary receiver.
98
+ *
99
+ * Scoring criteria (in priority order):
100
+ * 1. Battery state (charging > high > medium > low > critical)
101
+ * 2. Connectivity (internet > local_net > platform_p2p)
102
+ * 3. Relay willingness (eager > willing > reluctant > unavailable)
103
+ * 4. Inbound connectable (true preferred)
104
+ *
105
+ * Returns the elected primary device, or throws if the cluster is empty.
106
+ */
107
+ electPrimary(): ClusterDevice;
108
+ /**
109
+ * Return the current primary device, or `null` if no election has run.
110
+ */
111
+ getPrimary(): ClusterDevice | null;
112
+ /**
113
+ * Whether the local device is currently the primary receiver.
114
+ */
115
+ isPrimary(): boolean;
116
+ /**
117
+ * Update the local device's capabilities and re-elect primary if the
118
+ * cluster is running.
119
+ */
120
+ updateLocalCapabilities(capabilities: DeviceCapability): void;
121
+ /**
122
+ * Handle a capability update broadcast from a remote device.
123
+ */
124
+ handleCapabilityUpdate(deviceId: string, capabilities: DeviceCapability): void;
125
+ /**
126
+ * Start cluster operations. Triggers an initial primary election.
127
+ */
128
+ start(): void;
129
+ /**
130
+ * Stop cluster operations gracefully.
131
+ */
132
+ stop(): void;
133
+ /**
134
+ * Return a summary of the current cluster state.
135
+ */
136
+ getClusterStatus(): ClusterStatus;
137
+ /** The derived cluster key shared by all devices. */
138
+ getClusterKey(): Uint8Array;
139
+ /** Whether the cluster has been started. */
140
+ isRunning(): boolean;
141
+ /**
142
+ * Compute an availability score for a device.
143
+ *
144
+ * Higher score = more suitable as primary receiver.
145
+ * The score is a composite of battery, connectivity, relay willingness,
146
+ * and inbound-connectable flag, weighted so battery dominates.
147
+ */
148
+ static scoreDevice(device: ClusterDevice): number;
149
+ /**
150
+ * Compute a connectivity score from capability flags.
151
+ * A device can have multiple bearers; we use the best available.
152
+ */
153
+ private static connectivityScore;
154
+ /**
155
+ * Run a primary election and update internal state.
156
+ */
157
+ private reelectPrimary;
158
+ }
159
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cluster/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAkC,MAAM,aAAa,CAAC;AAuCnG;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB;AAMD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAMD;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAEhE;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAWxC;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE;IAUpD;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOrD;;;OAGG;IACH,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW,EAAE;IAQ3C;;;OAGG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM;IAa/C;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb,OAAO,CAAC,YAAY;CAMrB;AAMD;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IACjE,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,OAAO,CAAS;IAExB,mCAAmC;IACnC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAA4B;gBAExD,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM;IAgB1D;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,UAAU,GAAG,UAAU;IAYnE;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAWtC;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYpC;;OAEG;IACH,UAAU,IAAI,aAAa,EAAE;IAI7B;;;;OAIG;IACH,cAAc,IAAI,aAAa;IAU/B;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQ1C;;;;;;;;;;OAUG;IACH,YAAY,IAAI,aAAa;IA4B7B;;OAEG;IACH,UAAU,IAAI,aAAa,GAAG,IAAI;IAQlC;;OAEG;IACH,SAAS,IAAI,OAAO;IAQpB;;;OAGG;IACH,uBAAuB,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI;IAc7D;;OAEG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAG,IAAI;IAgB9E;;OAEG;IACH,KAAK,IAAI,IAAI;IAWb;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;OAEG;IACH,gBAAgB,IAAI,aAAa;IAajC,qDAAqD;IACrD,aAAa,IAAI,UAAU;IAI3B,4CAA4C;IAC5C,SAAS,IAAI,OAAO;IAQpB;;;;;;OAMG;IACH,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM;IAiBjD;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAOhC;;OAEG;IACH,OAAO,CAAC,cAAc;CAGvB"}
@@ -0,0 +1,393 @@
1
+ // ============================================================
2
+ // MeshWhisper SDK — Device Clustering Module
3
+ // Manages device self-clustering so a user's devices form a
4
+ // personal availability cluster (PRD §5.6). The most capable
5
+ // device acts as primary receiver; messages sync across the
6
+ // cluster over local connections when connectivity allows.
7
+ // ============================================================
8
+ import { blake3 } from '@noble/hashes/blake3';
9
+ // ---------------------------------------------------------------------------
10
+ // Constants
11
+ // ---------------------------------------------------------------------------
12
+ /** Domain separator for cluster key derivation. */
13
+ const CLUSTER_KEY_DOMAIN = 'meshwhisper-cluster-v1';
14
+ // ---------------------------------------------------------------------------
15
+ // Scoring tables for primary election
16
+ // ---------------------------------------------------------------------------
17
+ const BATTERY_SCORE = {
18
+ charging: 5,
19
+ high: 4,
20
+ medium: 3,
21
+ low: 2,
22
+ critical: 1,
23
+ };
24
+ const CONNECTIVITY_SCORE = {
25
+ internet: 3,
26
+ local_net: 2,
27
+ platform_p2p: 1,
28
+ none: 0,
29
+ };
30
+ const RELAY_SCORE = {
31
+ eager: 4,
32
+ willing: 3,
33
+ reluctant: 2,
34
+ unavailable: 1,
35
+ };
36
+ // ---------------------------------------------------------------------------
37
+ // MessageSyncManager
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Manages the queue of messages waiting to be synchronized to other
41
+ * devices in the cluster.
42
+ */
43
+ export class MessageSyncManager {
44
+ messages = new Map();
45
+ /**
46
+ * Enqueue a received message for sync to other cluster devices.
47
+ */
48
+ queueForSync(message) {
49
+ if (!message.messageId) {
50
+ throw new TypeError('SyncMessage must have a non-empty messageId');
51
+ }
52
+ // Clone syncedTo so the caller cannot mutate our internal state.
53
+ this.messages.set(message.messageId, {
54
+ ...message,
55
+ syncedTo: new Set(message.syncedTo),
56
+ });
57
+ }
58
+ /**
59
+ * Retrieve all messages that have **not** yet been synced to `deviceId`.
60
+ */
61
+ getUnsyncedMessages(deviceId) {
62
+ const result = [];
63
+ for (const msg of this.messages.values()) {
64
+ if (!msg.syncedTo.has(deviceId)) {
65
+ result.push(this.cloneMessage(msg));
66
+ }
67
+ }
68
+ return result;
69
+ }
70
+ /**
71
+ * Mark a single message as synced to a specific device.
72
+ */
73
+ markSynced(messageId, deviceId) {
74
+ const msg = this.messages.get(messageId);
75
+ if (msg) {
76
+ msg.syncedTo.add(deviceId);
77
+ }
78
+ }
79
+ /**
80
+ * Convenience method: returns all unsynced messages for `targetDeviceId`
81
+ * and marks each one as synced to that device in a single pass.
82
+ */
83
+ sync(targetDeviceId) {
84
+ const unsynced = this.getUnsyncedMessages(targetDeviceId);
85
+ for (const msg of unsynced) {
86
+ this.markSynced(msg.messageId, targetDeviceId);
87
+ }
88
+ return unsynced;
89
+ }
90
+ /**
91
+ * Total count of messages that still need to be synced to at least one
92
+ * device. Used by `DeviceCluster.getClusterStatus()`.
93
+ */
94
+ getPendingCount(allDeviceIds) {
95
+ let count = 0;
96
+ for (const msg of this.messages.values()) {
97
+ for (const id of allDeviceIds) {
98
+ if (!msg.syncedTo.has(id)) {
99
+ count++;
100
+ break; // only count each message once
101
+ }
102
+ }
103
+ }
104
+ return count;
105
+ }
106
+ /**
107
+ * Remove all tracked messages.
108
+ */
109
+ clear() {
110
+ this.messages.clear();
111
+ }
112
+ // -----------------------------------------------------------------------
113
+ // Private helpers
114
+ // -----------------------------------------------------------------------
115
+ cloneMessage(msg) {
116
+ return {
117
+ ...msg,
118
+ syncedTo: new Set(msg.syncedTo),
119
+ };
120
+ }
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // DeviceCluster
124
+ // ---------------------------------------------------------------------------
125
+ /**
126
+ * Manages a personal device cluster.
127
+ *
128
+ * A cluster groups all of a single user's devices so they can:
129
+ * - elect a primary receiver (best battery/connectivity/willingness)
130
+ * - accept messages on behalf of the user from any member
131
+ * - synchronize messages to all other members when connectivity allows
132
+ */
133
+ export class DeviceCluster {
134
+ localDeviceId;
135
+ clusterKey;
136
+ devices = new Map();
137
+ primaryDeviceId = null;
138
+ running = false;
139
+ /** Public message sync manager. */
140
+ syncManager = new MessageSyncManager();
141
+ constructor(identityKey, localDeviceId) {
142
+ if (!localDeviceId) {
143
+ throw new TypeError('localDeviceId must be a non-empty string');
144
+ }
145
+ if (identityKey.length === 0) {
146
+ throw new TypeError('identityKey must not be empty');
147
+ }
148
+ this.localDeviceId = localDeviceId;
149
+ this.clusterKey = DeviceCluster.deriveClusterKey(identityKey);
150
+ }
151
+ // -----------------------------------------------------------------------
152
+ // Cluster key derivation
153
+ // -----------------------------------------------------------------------
154
+ /**
155
+ * Derive a cluster key from the user's identity private key.
156
+ *
157
+ * clusterKey = BLAKE3(identity_key || "meshwhisper-cluster-v1")
158
+ */
159
+ static deriveClusterKey(identityPrivateKey) {
160
+ const domain = new TextEncoder().encode(CLUSTER_KEY_DOMAIN);
161
+ const input = new Uint8Array(identityPrivateKey.length + domain.length);
162
+ input.set(identityPrivateKey, 0);
163
+ input.set(domain, identityPrivateKey.length);
164
+ return blake3(input);
165
+ }
166
+ // -----------------------------------------------------------------------
167
+ // Device registration
168
+ // -----------------------------------------------------------------------
169
+ /**
170
+ * Register a device in the cluster.
171
+ */
172
+ addDevice(device) {
173
+ if (!device.deviceId) {
174
+ throw new TypeError('ClusterDevice must have a non-empty deviceId');
175
+ }
176
+ this.devices.set(device.deviceId, { ...device });
177
+ if (this.running) {
178
+ this.reelectPrimary();
179
+ }
180
+ }
181
+ /**
182
+ * Remove a device from the cluster.
183
+ */
184
+ removeDevice(deviceId) {
185
+ this.devices.delete(deviceId);
186
+ if (this.primaryDeviceId === deviceId) {
187
+ this.primaryDeviceId = null;
188
+ }
189
+ if (this.running && this.devices.size > 0) {
190
+ this.reelectPrimary();
191
+ }
192
+ }
193
+ /**
194
+ * Return a snapshot of all devices currently in the cluster.
195
+ */
196
+ getDevices() {
197
+ return Array.from(this.devices.values()).map((d) => ({ ...d }));
198
+ }
199
+ /**
200
+ * Return the local device entry.
201
+ *
202
+ * @throws if the local device has not been added to the cluster.
203
+ */
204
+ getLocalDevice() {
205
+ const local = this.devices.get(this.localDeviceId);
206
+ if (!local) {
207
+ throw new Error(`Local device "${this.localDeviceId}" has not been added to the cluster`);
208
+ }
209
+ return { ...local };
210
+ }
211
+ /**
212
+ * Check whether a device ID belongs to this cluster.
213
+ */
214
+ isClusterMember(deviceId) {
215
+ return this.devices.has(deviceId);
216
+ }
217
+ // -----------------------------------------------------------------------
218
+ // Primary receiver election
219
+ // -----------------------------------------------------------------------
220
+ /**
221
+ * Elect the most available device as primary receiver.
222
+ *
223
+ * Scoring criteria (in priority order):
224
+ * 1. Battery state (charging > high > medium > low > critical)
225
+ * 2. Connectivity (internet > local_net > platform_p2p)
226
+ * 3. Relay willingness (eager > willing > reluctant > unavailable)
227
+ * 4. Inbound connectable (true preferred)
228
+ *
229
+ * Returns the elected primary device, or throws if the cluster is empty.
230
+ */
231
+ electPrimary() {
232
+ if (this.devices.size === 0) {
233
+ throw new Error('Cannot elect primary: cluster has no devices');
234
+ }
235
+ let best = null;
236
+ let bestScore = -1;
237
+ for (const device of this.devices.values()) {
238
+ const score = DeviceCluster.scoreDevice(device);
239
+ if (score > bestScore) {
240
+ bestScore = score;
241
+ best = device;
242
+ }
243
+ }
244
+ // best is guaranteed non-null because devices.size > 0
245
+ const elected = best;
246
+ // Update isPrimary flags
247
+ for (const device of this.devices.values()) {
248
+ device.isPrimary = device.deviceId === elected.deviceId;
249
+ }
250
+ this.primaryDeviceId = elected.deviceId;
251
+ return { ...elected, isPrimary: true };
252
+ }
253
+ /**
254
+ * Return the current primary device, or `null` if no election has run.
255
+ */
256
+ getPrimary() {
257
+ if (this.primaryDeviceId === null) {
258
+ return null;
259
+ }
260
+ const device = this.devices.get(this.primaryDeviceId);
261
+ return device ? { ...device } : null;
262
+ }
263
+ /**
264
+ * Whether the local device is currently the primary receiver.
265
+ */
266
+ isPrimary() {
267
+ return this.primaryDeviceId === this.localDeviceId;
268
+ }
269
+ // -----------------------------------------------------------------------
270
+ // Capability updates
271
+ // -----------------------------------------------------------------------
272
+ /**
273
+ * Update the local device's capabilities and re-elect primary if the
274
+ * cluster is running.
275
+ */
276
+ updateLocalCapabilities(capabilities) {
277
+ const local = this.devices.get(this.localDeviceId);
278
+ if (!local) {
279
+ throw new Error(`Local device "${this.localDeviceId}" has not been added to the cluster`);
280
+ }
281
+ local.capabilities = { ...capabilities };
282
+ if (this.running) {
283
+ this.reelectPrimary();
284
+ }
285
+ }
286
+ /**
287
+ * Handle a capability update broadcast from a remote device.
288
+ */
289
+ handleCapabilityUpdate(deviceId, capabilities) {
290
+ const device = this.devices.get(deviceId);
291
+ if (!device) {
292
+ throw new Error(`Device "${deviceId}" is not a member of this cluster`);
293
+ }
294
+ device.capabilities = { ...capabilities };
295
+ if (this.running) {
296
+ this.reelectPrimary();
297
+ }
298
+ }
299
+ // -----------------------------------------------------------------------
300
+ // Cluster lifecycle
301
+ // -----------------------------------------------------------------------
302
+ /**
303
+ * Start cluster operations. Triggers an initial primary election.
304
+ */
305
+ start() {
306
+ if (this.running) {
307
+ return;
308
+ }
309
+ this.running = true;
310
+ if (this.devices.size > 0) {
311
+ this.reelectPrimary();
312
+ }
313
+ }
314
+ /**
315
+ * Stop cluster operations gracefully.
316
+ */
317
+ stop() {
318
+ if (!this.running) {
319
+ return;
320
+ }
321
+ this.running = false;
322
+ this.primaryDeviceId = null;
323
+ // Reset isPrimary on all devices
324
+ for (const device of this.devices.values()) {
325
+ device.isPrimary = false;
326
+ }
327
+ }
328
+ /**
329
+ * Return a summary of the current cluster state.
330
+ */
331
+ getClusterStatus() {
332
+ const deviceIds = Array.from(this.devices.keys());
333
+ return {
334
+ deviceCount: this.devices.size,
335
+ primaryId: this.primaryDeviceId ?? '',
336
+ syncPending: this.syncManager.getPendingCount(deviceIds),
337
+ };
338
+ }
339
+ // -----------------------------------------------------------------------
340
+ // Accessors
341
+ // -----------------------------------------------------------------------
342
+ /** The derived cluster key shared by all devices. */
343
+ getClusterKey() {
344
+ return new Uint8Array(this.clusterKey);
345
+ }
346
+ /** Whether the cluster has been started. */
347
+ isRunning() {
348
+ return this.running;
349
+ }
350
+ // -----------------------------------------------------------------------
351
+ // Static scoring helpers
352
+ // -----------------------------------------------------------------------
353
+ /**
354
+ * Compute an availability score for a device.
355
+ *
356
+ * Higher score = more suitable as primary receiver.
357
+ * The score is a composite of battery, connectivity, relay willingness,
358
+ * and inbound-connectable flag, weighted so battery dominates.
359
+ */
360
+ static scoreDevice(device) {
361
+ const cap = device.capabilities;
362
+ const battery = BATTERY_SCORE[cap.batteryState];
363
+ const connectivity = DeviceCluster.connectivityScore(cap);
364
+ const relay = RELAY_SCORE[cap.relayWillingness];
365
+ const inbound = cap.inboundConnectable ? 1 : 0;
366
+ // Weight battery most heavily so charging/plugged-in devices are
367
+ // strongly preferred, then connectivity, relay, inbound.
368
+ return battery * 1000 + connectivity * 100 + relay * 10 + inbound;
369
+ }
370
+ // -----------------------------------------------------------------------
371
+ // Private helpers
372
+ // -----------------------------------------------------------------------
373
+ /**
374
+ * Compute a connectivity score from capability flags.
375
+ * A device can have multiple bearers; we use the best available.
376
+ */
377
+ static connectivityScore(cap) {
378
+ if (cap.bearerInternet)
379
+ return CONNECTIVITY_SCORE.internet;
380
+ if (cap.bearerLocalNet)
381
+ return CONNECTIVITY_SCORE.local_net;
382
+ if (cap.bearerPlatformP2P)
383
+ return CONNECTIVITY_SCORE.platform_p2p;
384
+ return CONNECTIVITY_SCORE.none;
385
+ }
386
+ /**
387
+ * Run a primary election and update internal state.
388
+ */
389
+ reelectPrimary() {
390
+ this.electPrimary();
391
+ }
392
+ }
393
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cluster/index.ts"],"names":[],"mappings":"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"}