@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.
- package/README.md +138 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +19 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/chaff/index.d.ts +91 -0
- package/dist/chaff/index.d.ts.map +1 -0
- package/dist/chaff/index.js +268 -0
- package/dist/chaff/index.js.map +1 -0
- package/dist/cluster/index.d.ts +159 -0
- package/dist/cluster/index.d.ts.map +1 -0
- package/dist/cluster/index.js +393 -0
- package/dist/cluster/index.js.map +1 -0
- package/dist/compliance/index.d.ts +129 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +315 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/crypto/index.d.ts +65 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +146 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/group/index.d.ts +155 -0
- package/dist/group/index.d.ts.map +1 -0
- package/dist/group/index.js +560 -0
- package/dist/group/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/namespace/index.d.ts +155 -0
- package/dist/namespace/index.d.ts.map +1 -0
- package/dist/namespace/index.js +278 -0
- package/dist/namespace/index.js.map +1 -0
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +19 -0
- package/dist/node/index.js.map +1 -0
- package/dist/packet/index.d.ts +63 -0
- package/dist/packet/index.d.ts.map +1 -0
- package/dist/packet/index.js +244 -0
- package/dist/packet/index.js.map +1 -0
- package/dist/permissions/index.d.ts +107 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +282 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/persistence/idb-storage.d.ts +27 -0
- package/dist/persistence/idb-storage.d.ts.map +1 -0
- package/dist/persistence/idb-storage.js +75 -0
- package/dist/persistence/idb-storage.js.map +1 -0
- package/dist/persistence/index.d.ts +4 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +3 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/node-storage.d.ts +33 -0
- package/dist/persistence/node-storage.d.ts.map +1 -0
- package/dist/persistence/node-storage.js +90 -0
- package/dist/persistence/node-storage.js.map +1 -0
- package/dist/persistence/serialization.d.ts +4 -0
- package/dist/persistence/serialization.d.ts.map +1 -0
- package/dist/persistence/serialization.js +49 -0
- package/dist/persistence/serialization.js.map +1 -0
- package/dist/persistence/types.d.ts +29 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +5 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/ratchet/index.d.ts +80 -0
- package/dist/ratchet/index.d.ts.map +1 -0
- package/dist/ratchet/index.js +259 -0
- package/dist/ratchet/index.js.map +1 -0
- package/dist/reciprocity/index.d.ts +109 -0
- package/dist/reciprocity/index.d.ts.map +1 -0
- package/dist/reciprocity/index.js +311 -0
- package/dist/reciprocity/index.js.map +1 -0
- package/dist/relay/index.d.ts +87 -0
- package/dist/relay/index.d.ts.map +1 -0
- package/dist/relay/index.js +286 -0
- package/dist/relay/index.js.map +1 -0
- package/dist/routing/index.d.ts +136 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +478 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sdk/index.d.ts +322 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +1530 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sybil/index.d.ts +123 -0
- package/dist/sybil/index.d.ts.map +1 -0
- package/dist/sybil/index.js +491 -0
- package/dist/sybil/index.js.map +1 -0
- package/dist/transport/browser/index.d.ts +34 -0
- package/dist/transport/browser/index.d.ts.map +1 -0
- package/dist/transport/browser/index.js +176 -0
- package/dist/transport/browser/index.js.map +1 -0
- package/dist/transport/local/index.d.ts +57 -0
- package/dist/transport/local/index.d.ts.map +1 -0
- package/dist/transport/local/index.js +442 -0
- package/dist/transport/local/index.js.map +1 -0
- package/dist/transport/negotiator/index.d.ts +79 -0
- package/dist/transport/negotiator/index.d.ts.map +1 -0
- package/dist/transport/negotiator/index.js +289 -0
- package/dist/transport/negotiator/index.js.map +1 -0
- package/dist/transport/node/index.d.ts +56 -0
- package/dist/transport/node/index.d.ts.map +1 -0
- package/dist/transport/node/index.js +209 -0
- package/dist/transport/node/index.js.map +1 -0
- package/dist/transport/noop/index.d.ts +11 -0
- package/dist/transport/noop/index.d.ts.map +1 -0
- package/dist/transport/noop/index.js +20 -0
- package/dist/transport/noop/index.js.map +1 -0
- package/dist/transport/p2p/index.d.ts +109 -0
- package/dist/transport/p2p/index.d.ts.map +1 -0
- package/dist/transport/p2p/index.js +237 -0
- package/dist/transport/p2p/index.js.map +1 -0
- package/dist/transport/websocket/index.d.ts +89 -0
- package/dist/transport/websocket/index.d.ts.map +1 -0
- package/dist/transport/websocket/index.js +498 -0
- package/dist/transport/websocket/index.js.map +1 -0
- package/dist/transport/websocket/serialize.d.ts +5 -0
- package/dist/transport/websocket/serialize.d.ts.map +1 -0
- package/dist/transport/websocket/serialize.js +55 -0
- package/dist/transport/websocket/serialize.js.map +1 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/x3dh/index.d.ts +120 -0
- package/dist/x3dh/index.d.ts.map +1 -0
- package/dist/x3dh/index.js +290 -0
- package/dist/x3dh/index.js.map +1 -0
- package/package.json +59 -0
- package/src/browser/index.ts +19 -0
- package/src/chaff/index.ts +340 -0
- package/src/cluster/index.ts +482 -0
- package/src/compliance/index.ts +407 -0
- package/src/crypto/index.ts +193 -0
- package/src/group/index.ts +719 -0
- package/src/index.ts +87 -0
- package/src/lz4js.d.ts +58 -0
- package/src/namespace/index.ts +336 -0
- package/src/node/index.ts +19 -0
- package/src/packet/index.ts +326 -0
- package/src/permissions/index.ts +405 -0
- package/src/persistence/idb-storage.ts +83 -0
- package/src/persistence/index.ts +3 -0
- package/src/persistence/node-storage.ts +96 -0
- package/src/persistence/serialization.ts +75 -0
- package/src/persistence/types.ts +33 -0
- package/src/ratchet/index.ts +363 -0
- package/src/reciprocity/index.ts +371 -0
- package/src/relay/index.ts +382 -0
- package/src/routing/index.ts +577 -0
- package/src/sdk/index.ts +1994 -0
- package/src/sybil/index.ts +661 -0
- package/src/transport/browser/index.ts +201 -0
- package/src/transport/local/index.ts +540 -0
- package/src/transport/negotiator/index.ts +397 -0
- package/src/transport/node/index.ts +234 -0
- package/src/transport/noop/index.ts +22 -0
- package/src/transport/p2p/index.ts +345 -0
- package/src/transport/websocket/index.ts +660 -0
- package/src/transport/websocket/serialize.ts +68 -0
- package/src/types.ts +275 -0
- 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
|
+
}
|