@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,371 @@
1
+ // ============================================================
2
+ // MeshWhisper SDK — Reciprocity Engine
3
+ // Maintains a local relay ledger implementing tit-for-tat
4
+ // relay fairness as described in PRD section 7.3.
5
+ // ============================================================
6
+
7
+ import { RelayLedgerEntry, ReciprocityTier } from '../types.js';
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Constants
11
+ // ---------------------------------------------------------------------------
12
+
13
+ /** Default grace period for newly registered devices (48 hours). */
14
+ const DEFAULT_GRACE_PERIOD_HOURS = 48;
15
+
16
+ /** Tier thresholds for reciprocity scoring. */
17
+ const TIER_CONTRIBUTOR_THRESHOLD = 1.0;
18
+ const TIER_BALANCED_THRESHOLD = 0.5;
19
+ const TIER_CONSUMER_THRESHOLD = 0.1;
20
+
21
+ /** Relay priority values per tier. */
22
+ const PRIORITY_CONTRIBUTOR = 1.0;
23
+ const PRIORITY_BALANCED = 0.7;
24
+ const PRIORITY_CONSUMER = 0.3;
25
+ const PRIORITY_FREERIDER = 0.0;
26
+
27
+ /** Serialization format version. */
28
+ const SERIALIZATION_VERSION = 1;
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Options
32
+ // ---------------------------------------------------------------------------
33
+
34
+ export interface RelayLedgerOptions {
35
+ /** Duration of the new-device grace period, in hours. Defaults to 48. */
36
+ gracePeriodHours?: number;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Internal serialization shape
41
+ // ---------------------------------------------------------------------------
42
+
43
+ interface SerializedLedger {
44
+ version: number;
45
+ gracePeriodHours: number;
46
+ deviceId: string | null;
47
+ deviceRegisteredAt: number | null;
48
+ entries: RelayLedgerEntry[];
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // RelayLedger
53
+ // ---------------------------------------------------------------------------
54
+
55
+ /**
56
+ * Tracks bytes relayed for and by each peer, computes reciprocity scores,
57
+ * and makes relay-priority decisions. Purely local and approximate — there
58
+ * is no global accounting.
59
+ */
60
+ export class RelayLedger {
61
+ private readonly gracePeriodMs: number;
62
+ private entries: Map<string, RelayLedgerEntry> = new Map();
63
+ private deviceId: string | null = null;
64
+ private deviceRegisteredAt: number | null = null;
65
+
66
+ constructor(options?: RelayLedgerOptions) {
67
+ const hours = options?.gracePeriodHours ?? DEFAULT_GRACE_PERIOD_HOURS;
68
+ if (hours < 0) {
69
+ throw new RangeError('gracePeriodHours must be non-negative');
70
+ }
71
+ this.gracePeriodMs = hours * 60 * 60 * 1000;
72
+ }
73
+
74
+ // -----------------------------------------------------------------------
75
+ // Ledger operations
76
+ // -----------------------------------------------------------------------
77
+
78
+ /**
79
+ * Record that we relayed `bytes` of data on behalf of `peerId`.
80
+ */
81
+ recordRelayedForPeer(peerId: string, bytes: number): void {
82
+ if (bytes < 0) {
83
+ throw new RangeError('bytes must be non-negative');
84
+ }
85
+ const entry = this.getOrCreateEntry(peerId);
86
+ entry.bytesRelayedForThem += bytes;
87
+ entry.lastUpdated = Date.now();
88
+ }
89
+
90
+ /**
91
+ * Record that `peerId` relayed `bytes` of data for us.
92
+ */
93
+ recordPeerRelayedForUs(peerId: string, bytes: number): void {
94
+ if (bytes < 0) {
95
+ throw new RangeError('bytes must be non-negative');
96
+ }
97
+ const entry = this.getOrCreateEntry(peerId);
98
+ entry.bytesTheyRelayedForUs += bytes;
99
+ entry.lastUpdated = Date.now();
100
+ }
101
+
102
+ /**
103
+ * Return the ledger entry for a given peer, or `null` if none exists.
104
+ */
105
+ getEntry(peerId: string): RelayLedgerEntry | null {
106
+ const entry = this.entries.get(peerId);
107
+ return entry ? { ...entry } : null;
108
+ }
109
+
110
+ /**
111
+ * Return a snapshot of all ledger entries.
112
+ */
113
+ getAllEntries(): RelayLedgerEntry[] {
114
+ return Array.from(this.entries.values()).map((e) => ({ ...e }));
115
+ }
116
+
117
+ // -----------------------------------------------------------------------
118
+ // Score computation
119
+ // -----------------------------------------------------------------------
120
+
121
+ /**
122
+ * Compute the reciprocity score for a specific peer.
123
+ *
124
+ * Score = bytes_relayed_for_them / bytes_they_relayed_for_us
125
+ *
126
+ * - If neither party has relayed anything, returns 1.0 (neutral).
127
+ * - If we have relayed for them but they haven't for us, returns Infinity
128
+ * (we are a pure contributor to this peer).
129
+ * - If they have relayed for us but we haven't for them, returns 0.0
130
+ * (we are a pure consumer from this peer).
131
+ */
132
+ getScore(peerId: string): number {
133
+ const entry = this.entries.get(peerId);
134
+ if (!entry) {
135
+ return 1.0; // unknown peer — treat as neutral
136
+ }
137
+ return this.computeScore(entry.bytesRelayedForThem, entry.bytesTheyRelayedForUs);
138
+ }
139
+
140
+ /**
141
+ * Compute the aggregate reciprocity score across all peers.
142
+ *
143
+ * Uses total bytes relayed for all peers vs total bytes all peers
144
+ * relayed for us.
145
+ */
146
+ getGlobalScore(): number {
147
+ let totalRelayedForThem = 0;
148
+ let totalTheyRelayedForUs = 0;
149
+ for (const entry of this.entries.values()) {
150
+ totalRelayedForThem += entry.bytesRelayedForThem;
151
+ totalTheyRelayedForUs += entry.bytesTheyRelayedForUs;
152
+ }
153
+ return this.computeScore(totalRelayedForThem, totalTheyRelayedForUs);
154
+ }
155
+
156
+ /**
157
+ * Determine the reciprocity tier for a given peer.
158
+ */
159
+ getTier(peerId: string): ReciprocityTier {
160
+ if (this.isInGracePeriod()) {
161
+ return 'balanced';
162
+ }
163
+ const score = this.getScore(peerId);
164
+ return RelayLedger.tierFromScore(score);
165
+ }
166
+
167
+ // -----------------------------------------------------------------------
168
+ // Relay priority
169
+ // -----------------------------------------------------------------------
170
+
171
+ /**
172
+ * Determine whether we should relay traffic for this peer.
173
+ *
174
+ * Free-riders are refused unless we are still in our grace period.
175
+ */
176
+ shouldRelay(peerId: string): boolean {
177
+ if (this.isInGracePeriod()) {
178
+ return true;
179
+ }
180
+ return this.getTier(peerId) !== 'freerider';
181
+ }
182
+
183
+ /**
184
+ * Return a relay-queue priority between 0.0 and 1.0 for this peer.
185
+ *
186
+ * Higher values mean packets for/from this peer should be relayed sooner.
187
+ */
188
+ getRelayPriority(peerId: string): number {
189
+ const tier = this.getTier(peerId);
190
+ switch (tier) {
191
+ case 'contributor':
192
+ return PRIORITY_CONTRIBUTOR;
193
+ case 'balanced':
194
+ return PRIORITY_BALANCED;
195
+ case 'consumer':
196
+ return PRIORITY_CONSUMER;
197
+ case 'freerider':
198
+ return PRIORITY_FREERIDER;
199
+ }
200
+ }
201
+
202
+ // -----------------------------------------------------------------------
203
+ // Grace period
204
+ // -----------------------------------------------------------------------
205
+
206
+ /**
207
+ * Register this device, recording the current time as the creation
208
+ * timestamp for grace-period purposes.
209
+ */
210
+ registerDevice(deviceId: string): void {
211
+ if (!deviceId) {
212
+ throw new TypeError('deviceId must be a non-empty string');
213
+ }
214
+ this.deviceId = deviceId;
215
+ this.deviceRegisteredAt = Date.now();
216
+ }
217
+
218
+ /**
219
+ * Returns `true` if this device was registered within the configured
220
+ * grace period (default 48 hours).
221
+ */
222
+ isInGracePeriod(): boolean {
223
+ if (this.deviceRegisteredAt === null) {
224
+ return false;
225
+ }
226
+ return Date.now() - this.deviceRegisteredAt < this.gracePeriodMs;
227
+ }
228
+
229
+ // -----------------------------------------------------------------------
230
+ // Ledger maintenance
231
+ // -----------------------------------------------------------------------
232
+
233
+ /**
234
+ * Apply exponential decay to every entry in the ledger.
235
+ *
236
+ * Multiplies both byte counters by `factor` (should be between 0 and 1).
237
+ * This prevents ancient history from permanently dominating the score.
238
+ */
239
+ decayLedger(factor: number): void {
240
+ if (factor < 0 || factor > 1) {
241
+ throw new RangeError('decay factor must be between 0 and 1');
242
+ }
243
+ for (const entry of this.entries.values()) {
244
+ entry.bytesRelayedForThem = Math.floor(entry.bytesRelayedForThem * factor);
245
+ entry.bytesTheyRelayedForUs = Math.floor(entry.bytesTheyRelayedForUs * factor);
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Remove entries for peers whose `lastUpdated` timestamp is older
251
+ * than `maxAgeMs` milliseconds ago.
252
+ */
253
+ pruneInactive(maxAgeMs: number): void {
254
+ if (maxAgeMs < 0) {
255
+ throw new RangeError('maxAgeMs must be non-negative');
256
+ }
257
+ const cutoff = Date.now() - maxAgeMs;
258
+ for (const [peerId, entry] of this.entries) {
259
+ if (entry.lastUpdated < cutoff) {
260
+ this.entries.delete(peerId);
261
+ }
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Clear all ledger entries. Device registration is preserved.
267
+ */
268
+ reset(): void {
269
+ this.entries.clear();
270
+ }
271
+
272
+ // -----------------------------------------------------------------------
273
+ // Serialization
274
+ // -----------------------------------------------------------------------
275
+
276
+ /**
277
+ * Serialize the entire ledger state to a `Uint8Array` for persistence.
278
+ */
279
+ serialize(): Uint8Array {
280
+ const payload: SerializedLedger = {
281
+ version: SERIALIZATION_VERSION,
282
+ gracePeriodHours: this.gracePeriodMs / (60 * 60 * 1000),
283
+ deviceId: this.deviceId,
284
+ deviceRegisteredAt: this.deviceRegisteredAt,
285
+ entries: this.getAllEntries(),
286
+ };
287
+ const json = JSON.stringify(payload);
288
+ return new TextEncoder().encode(json);
289
+ }
290
+
291
+ /**
292
+ * Restore a `RelayLedger` from previously serialized data.
293
+ */
294
+ static deserialize(data: Uint8Array): RelayLedger {
295
+ let parsed: SerializedLedger;
296
+ try {
297
+ const json = new TextDecoder().decode(data);
298
+ parsed = JSON.parse(json) as SerializedLedger;
299
+ } catch {
300
+ throw new Error('Failed to deserialize RelayLedger: invalid data');
301
+ }
302
+
303
+ if (parsed.version !== SERIALIZATION_VERSION) {
304
+ throw new Error(
305
+ `Unsupported RelayLedger serialization version: ${parsed.version}`,
306
+ );
307
+ }
308
+
309
+ const ledger = new RelayLedger({
310
+ gracePeriodHours: parsed.gracePeriodHours,
311
+ });
312
+
313
+ if (parsed.deviceId !== null && parsed.deviceRegisteredAt !== null) {
314
+ ledger.deviceId = parsed.deviceId;
315
+ ledger.deviceRegisteredAt = parsed.deviceRegisteredAt;
316
+ }
317
+
318
+ for (const entry of parsed.entries) {
319
+ ledger.entries.set(entry.peerId, {
320
+ peerId: entry.peerId,
321
+ bytesRelayedForThem: entry.bytesRelayedForThem,
322
+ bytesTheyRelayedForUs: entry.bytesTheyRelayedForUs,
323
+ lastUpdated: entry.lastUpdated,
324
+ });
325
+ }
326
+
327
+ return ledger;
328
+ }
329
+
330
+ // -----------------------------------------------------------------------
331
+ // Static helpers
332
+ // -----------------------------------------------------------------------
333
+
334
+ /**
335
+ * Map a numeric reciprocity score to a tier label.
336
+ */
337
+ static tierFromScore(score: number): ReciprocityTier {
338
+ if (score > TIER_CONTRIBUTOR_THRESHOLD) return 'contributor';
339
+ if (score >= TIER_BALANCED_THRESHOLD) return 'balanced';
340
+ if (score >= TIER_CONSUMER_THRESHOLD) return 'consumer';
341
+ return 'freerider';
342
+ }
343
+
344
+ // -----------------------------------------------------------------------
345
+ // Private helpers
346
+ // -----------------------------------------------------------------------
347
+
348
+ private getOrCreateEntry(peerId: string): RelayLedgerEntry {
349
+ let entry = this.entries.get(peerId);
350
+ if (!entry) {
351
+ entry = {
352
+ peerId,
353
+ bytesRelayedForThem: 0,
354
+ bytesTheyRelayedForUs: 0,
355
+ lastUpdated: Date.now(),
356
+ };
357
+ this.entries.set(peerId, entry);
358
+ }
359
+ return entry;
360
+ }
361
+
362
+ private computeScore(relayedForThem: number, theyRelayedForUs: number): number {
363
+ if (relayedForThem === 0 && theyRelayedForUs === 0) {
364
+ return 1.0; // no data — neutral
365
+ }
366
+ if (theyRelayedForUs === 0) {
367
+ return Infinity; // pure contributor
368
+ }
369
+ return relayedForThem / theyRelayedForUs;
370
+ }
371
+ }