dignity.js 0.5.4 → 0.6.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 +25 -3
- package/dist/dignity.cjs.js +513 -17
- package/dist/dignity.cjs.js.map +4 -4
- package/dist/dignity.esm.js +513 -17
- package/dist/dignity.esm.js.map +3 -3
- package/dist/dignity.min.js +27 -27
- package/docs/assets/dignity.esm.js +477 -51
- package/docs/index.html +346 -6
- package/docs/openapi-like.json +9 -1
- package/examples/decentralized-chess-lite.js +2 -2
- package/package.json +2 -1
- package/src/core/dignity-p2p.js +473 -16
- package/src/gossip/peer-group.js +64 -0
- package/src/index.js +13 -1
- package/src/network/in-memory-network.js +42 -0
- package/src/network/peerjs-network.js +28 -0
- package/src/security/message-security-service.js +10 -2
|
@@ -16,43 +16,6 @@ var __commonJS = (cb, mod) => function __require2() {
|
|
|
16
16
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
17
17
|
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
18
18
|
|
|
19
|
-
// src/utils/event-emitter.js
|
|
20
|
-
var require_event_emitter = __commonJS({
|
|
21
|
-
"src/utils/event-emitter.js"(exports, module) {
|
|
22
|
-
var EventEmitter = class {
|
|
23
|
-
constructor() {
|
|
24
|
-
this.handlers = /* @__PURE__ */ new Map();
|
|
25
|
-
}
|
|
26
|
-
on(eventName, handler) {
|
|
27
|
-
if (!this.handlers.has(eventName)) {
|
|
28
|
-
this.handlers.set(eventName, /* @__PURE__ */ new Set());
|
|
29
|
-
}
|
|
30
|
-
this.handlers.get(eventName).add(handler);
|
|
31
|
-
}
|
|
32
|
-
off(eventName, handler) {
|
|
33
|
-
const eventHandlers = this.handlers.get(eventName);
|
|
34
|
-
if (!eventHandlers) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
eventHandlers.delete(handler);
|
|
38
|
-
if (eventHandlers.size === 0) {
|
|
39
|
-
this.handlers.delete(eventName);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
emit(eventName, payload) {
|
|
43
|
-
const eventHandlers = this.handlers.get(eventName);
|
|
44
|
-
if (!eventHandlers) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
for (const handler of eventHandlers) {
|
|
48
|
-
handler(payload);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
module.exports = EventEmitter;
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
19
|
// (disabled):crypto
|
|
57
20
|
var require_crypto = __commonJS({
|
|
58
21
|
"(disabled):crypto"() {
|
|
@@ -2349,6 +2312,43 @@ var require_nacl_util = __commonJS({
|
|
|
2349
2312
|
}
|
|
2350
2313
|
});
|
|
2351
2314
|
|
|
2315
|
+
// src/utils/event-emitter.js
|
|
2316
|
+
var require_event_emitter = __commonJS({
|
|
2317
|
+
"src/utils/event-emitter.js"(exports, module) {
|
|
2318
|
+
var EventEmitter = class {
|
|
2319
|
+
constructor() {
|
|
2320
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
2321
|
+
}
|
|
2322
|
+
on(eventName, handler) {
|
|
2323
|
+
if (!this.handlers.has(eventName)) {
|
|
2324
|
+
this.handlers.set(eventName, /* @__PURE__ */ new Set());
|
|
2325
|
+
}
|
|
2326
|
+
this.handlers.get(eventName).add(handler);
|
|
2327
|
+
}
|
|
2328
|
+
off(eventName, handler) {
|
|
2329
|
+
const eventHandlers = this.handlers.get(eventName);
|
|
2330
|
+
if (!eventHandlers) {
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
eventHandlers.delete(handler);
|
|
2334
|
+
if (eventHandlers.size === 0) {
|
|
2335
|
+
this.handlers.delete(eventName);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
emit(eventName, payload) {
|
|
2339
|
+
const eventHandlers = this.handlers.get(eventName);
|
|
2340
|
+
if (!eventHandlers) {
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
for (const handler of eventHandlers) {
|
|
2344
|
+
handler(payload);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
module.exports = EventEmitter;
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
|
|
2352
2352
|
// src/security/sloth-vdf.js
|
|
2353
2353
|
var require_sloth_vdf = __commonJS({
|
|
2354
2354
|
"src/security/sloth-vdf.js"(exports, module) {
|
|
@@ -2361,25 +2361,25 @@ var require_sloth_vdf = __commonJS({
|
|
|
2361
2361
|
let powBase = base % modulus;
|
|
2362
2362
|
let powExponent = exponent;
|
|
2363
2363
|
while (powExponent > 0) {
|
|
2364
|
-
if (powExponent
|
|
2364
|
+
if ((powExponent & BigInt(1)) === BigInt(1)) {
|
|
2365
2365
|
result = result * powBase % modulus;
|
|
2366
2366
|
}
|
|
2367
|
-
powExponent = powExponent
|
|
2367
|
+
powExponent = powExponent >> BigInt(1);
|
|
2368
2368
|
powBase = powBase * powBase % modulus;
|
|
2369
2369
|
}
|
|
2370
2370
|
return result;
|
|
2371
2371
|
}
|
|
2372
2372
|
quadRes(x) {
|
|
2373
|
-
return this.fastPow(x,
|
|
2373
|
+
return this.fastPow(x, _SlothPermutation.pHalf, _SlothPermutation.p) === BigInt(1);
|
|
2374
2374
|
}
|
|
2375
2375
|
modSqrtOp(x) {
|
|
2376
2376
|
let y;
|
|
2377
2377
|
let value = x;
|
|
2378
2378
|
if (this.quadRes(value)) {
|
|
2379
|
-
y = this.fastPow(value,
|
|
2379
|
+
y = this.fastPow(value, _SlothPermutation.pQuarter, _SlothPermutation.p);
|
|
2380
2380
|
} else {
|
|
2381
2381
|
value = (-value + _SlothPermutation.p) % _SlothPermutation.p;
|
|
2382
|
-
y = this.fastPow(value,
|
|
2382
|
+
y = this.fastPow(value, _SlothPermutation.pQuarter, _SlothPermutation.p);
|
|
2383
2383
|
}
|
|
2384
2384
|
return y;
|
|
2385
2385
|
}
|
|
@@ -2411,6 +2411,12 @@ var require_sloth_vdf = __commonJS({
|
|
|
2411
2411
|
__publicField(_SlothPermutation, "p", BigInt(
|
|
2412
2412
|
"170082004324204494273811327264862981553264701145937538369570764779791492622392118654022654452947093285873855529044371650895045691292912712699015605832276411308653107069798639938826015099738961427172366594187783204437869906954750443653318078358839409699824714551430573905637228307966826784684174483831608534979"
|
|
2413
2413
|
));
|
|
2414
|
+
// precompute values for optimization:
|
|
2415
|
+
// (p - 1) / 2
|
|
2416
|
+
__publicField(_SlothPermutation, "pHalf", _SlothPermutation.p - BigInt(1) >> BigInt(1));
|
|
2417
|
+
// (p + 1) / 4
|
|
2418
|
+
// p ≡ 3 (mod 4) ⇒ (p+1) divisible by 4
|
|
2419
|
+
__publicField(_SlothPermutation, "pQuarter", _SlothPermutation.p + BigInt(1) >> BigInt(2));
|
|
2414
2420
|
var SlothPermutation = _SlothPermutation;
|
|
2415
2421
|
module.exports = SlothPermutation;
|
|
2416
2422
|
}
|
|
@@ -2868,11 +2874,84 @@ var require_message_security_service = __commonJS({
|
|
|
2868
2874
|
}
|
|
2869
2875
|
});
|
|
2870
2876
|
|
|
2877
|
+
// src/gossip/peer-group.js
|
|
2878
|
+
var require_peer_group = __commonJS({
|
|
2879
|
+
"src/gossip/peer-group.js"(exports, module) {
|
|
2880
|
+
var PEER_GROUP_SCOPE_PREFIX = "gossip:";
|
|
2881
|
+
var DEFAULT_PEER_GROUP_OPTIONS = {
|
|
2882
|
+
fanout: 3,
|
|
2883
|
+
maxActivePeers: 8,
|
|
2884
|
+
maxHops: 6,
|
|
2885
|
+
relayEnabled: true
|
|
2886
|
+
};
|
|
2887
|
+
function peerGroupScope(groupId) {
|
|
2888
|
+
if (!groupId) {
|
|
2889
|
+
throw new Error("peerGroupScope requires groupId");
|
|
2890
|
+
}
|
|
2891
|
+
return `${PEER_GROUP_SCOPE_PREFIX}${groupId}`;
|
|
2892
|
+
}
|
|
2893
|
+
function parsePeerGroupScope(scope) {
|
|
2894
|
+
if (!scope || !scope.startsWith(PEER_GROUP_SCOPE_PREFIX)) {
|
|
2895
|
+
return null;
|
|
2896
|
+
}
|
|
2897
|
+
return scope.slice(PEER_GROUP_SCOPE_PREFIX.length);
|
|
2898
|
+
}
|
|
2899
|
+
function shufflePeerIds(peerIds, randomFn = Math.random) {
|
|
2900
|
+
const list = [...peerIds];
|
|
2901
|
+
for (let i = list.length - 1; i > 0; i -= 1) {
|
|
2902
|
+
const j = Math.floor(randomFn() * (i + 1));
|
|
2903
|
+
[list[i], list[j]] = [list[j], list[i]];
|
|
2904
|
+
}
|
|
2905
|
+
return list;
|
|
2906
|
+
}
|
|
2907
|
+
function selectFanoutPeers({
|
|
2908
|
+
peers,
|
|
2909
|
+
count,
|
|
2910
|
+
excludePeerIds = [],
|
|
2911
|
+
connectedPeerIds = [],
|
|
2912
|
+
randomFn = Math.random
|
|
2913
|
+
}) {
|
|
2914
|
+
const excluded = new Set(excludePeerIds.filter(Boolean));
|
|
2915
|
+
const candidates = peers.map((entry) => entry.peerId || entry).filter((peerId) => peerId && !excluded.has(peerId));
|
|
2916
|
+
const connected = new Set(connectedPeerIds.filter(Boolean));
|
|
2917
|
+
const preferred = candidates.filter((peerId) => connected.has(peerId));
|
|
2918
|
+
const others = candidates.filter((peerId) => !connected.has(peerId));
|
|
2919
|
+
const ordered = [
|
|
2920
|
+
...shufflePeerIds(preferred, randomFn),
|
|
2921
|
+
...shufflePeerIds(others, randomFn)
|
|
2922
|
+
];
|
|
2923
|
+
return ordered.slice(0, Math.max(0, count));
|
|
2924
|
+
}
|
|
2925
|
+
module.exports = {
|
|
2926
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
2927
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
2928
|
+
peerGroupScope,
|
|
2929
|
+
parsePeerGroupScope,
|
|
2930
|
+
shufflePeerIds,
|
|
2931
|
+
selectFanoutPeers
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
|
|
2871
2936
|
// src/core/dignity-p2p.js
|
|
2872
2937
|
var require_dignity_p2p = __commonJS({
|
|
2873
2938
|
"src/core/dignity-p2p.js"(exports, module) {
|
|
2939
|
+
var nacl = require_nacl_fast();
|
|
2940
|
+
var naclUtil = require_nacl_util();
|
|
2874
2941
|
var EventEmitter = require_event_emitter();
|
|
2875
|
-
var { MessageSecurityService } = require_message_security_service();
|
|
2942
|
+
var { MessageSecurityService, stableStringify } = require_message_security_service();
|
|
2943
|
+
var {
|
|
2944
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
2945
|
+
peerGroupScope,
|
|
2946
|
+
selectFanoutPeers
|
|
2947
|
+
} = require_peer_group();
|
|
2948
|
+
function computeContentHash(data) {
|
|
2949
|
+
const canonical = stableStringify(data || {});
|
|
2950
|
+
const bytes = naclUtil.decodeUTF8(canonical);
|
|
2951
|
+
const hash = nacl.hash(bytes);
|
|
2952
|
+
const hex = Array.from(hash, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2953
|
+
return `sha512:${hex}`;
|
|
2954
|
+
}
|
|
2876
2955
|
var DignityP2P = class extends EventEmitter {
|
|
2877
2956
|
constructor({ nodeId, networkAdapter, idGenerator, now, security } = {}) {
|
|
2878
2957
|
super();
|
|
@@ -2898,6 +2977,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
2898
2977
|
this.defaultPresenceTtlMs = security && typeof security.presenceTtlMs === "number" ? security.presenceTtlMs : 45e3;
|
|
2899
2978
|
this.discoveryRooms = /* @__PURE__ */ new Map();
|
|
2900
2979
|
this.presenceByScope = /* @__PURE__ */ new Map();
|
|
2980
|
+
this.peerGroups = /* @__PURE__ */ new Map();
|
|
2981
|
+
this.seenGossipIds = /* @__PURE__ */ new Map();
|
|
2982
|
+
this.defaultPeerGroupFanout = security && typeof security.peerGroupFanout === "number" ? security.peerGroupFanout : DEFAULT_PEER_GROUP_OPTIONS.fanout;
|
|
2983
|
+
this.defaultPeerGroupMaxActivePeers = security && typeof security.peerGroupMaxActivePeers === "number" ? security.peerGroupMaxActivePeers : DEFAULT_PEER_GROUP_OPTIONS.maxActivePeers;
|
|
2984
|
+
this.defaultGossipMaxHops = security && typeof security.gossipMaxHops === "number" ? security.gossipMaxHops : DEFAULT_PEER_GROUP_OPTIONS.maxHops;
|
|
2985
|
+
this.globalMaxOpenConnections = security && typeof security.globalMaxOpenConnections === "number" ? security.globalMaxOpenConnections : 32;
|
|
2986
|
+
this.gossipIdTtlMs = security && typeof security.gossipIdTtlMs === "number" ? security.gossipIdTtlMs : 5 * 60 * 1e3;
|
|
2901
2987
|
this.state = /* @__PURE__ */ new Map();
|
|
2902
2988
|
this.appliedOperations = /* @__PURE__ */ new Set();
|
|
2903
2989
|
this.boundMessageHandler = this.handleIncomingMessage.bind(this);
|
|
@@ -2907,6 +2993,14 @@ var require_dignity_p2p = __commonJS({
|
|
|
2907
2993
|
await this.networkAdapter.start(this.nodeId);
|
|
2908
2994
|
}
|
|
2909
2995
|
async stop() {
|
|
2996
|
+
const joinedGroups = Array.from(this.peerGroups.keys());
|
|
2997
|
+
for (const groupId of joinedGroups) {
|
|
2998
|
+
try {
|
|
2999
|
+
await this.leavePeerGroup(groupId);
|
|
3000
|
+
} catch (error) {
|
|
3001
|
+
this.emit("warning", { type: "peer-group-leave-failed", groupId, error });
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
2910
3004
|
const joinedScopes = Array.from(this.discoveryRooms.keys());
|
|
2911
3005
|
for (const scope of joinedScopes) {
|
|
2912
3006
|
try {
|
|
@@ -2931,6 +3025,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
2931
3025
|
if (!record || record.deletedAt) {
|
|
2932
3026
|
return null;
|
|
2933
3027
|
}
|
|
3028
|
+
const normalizedData = { ...record.data || {} };
|
|
2934
3029
|
return {
|
|
2935
3030
|
id: record.id,
|
|
2936
3031
|
ownerId: record.ownerId,
|
|
@@ -2938,7 +3033,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
2938
3033
|
createdAt: record.createdAt,
|
|
2939
3034
|
updatedAt: record.updatedAt,
|
|
2940
3035
|
version: record.version,
|
|
2941
|
-
|
|
3036
|
+
hash: record.hash || computeContentHash(normalizedData),
|
|
3037
|
+
data: normalizedData
|
|
2942
3038
|
};
|
|
2943
3039
|
}
|
|
2944
3040
|
canUpdateRecord(record, actorId) {
|
|
@@ -3016,7 +3112,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3016
3112
|
ownerId: this.nodeId,
|
|
3017
3113
|
collaboratorIds,
|
|
3018
3114
|
timestamp,
|
|
3019
|
-
payload: { ...data }
|
|
3115
|
+
payload: { ...data || {} }
|
|
3020
3116
|
};
|
|
3021
3117
|
this.applyOperation(operation);
|
|
3022
3118
|
await this.broadcastMessage("operation", operation, {
|
|
@@ -3253,6 +3349,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3253
3349
|
const connectToPeers = securityContext.connectToPeers;
|
|
3254
3350
|
if (Array.isArray(connectToPeers) && connectToPeers.length > 0) {
|
|
3255
3351
|
await this.ensureConnectedToPeers(connectToPeers);
|
|
3352
|
+
await this.enforceConnectionBudget();
|
|
3256
3353
|
}
|
|
3257
3354
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3258
3355
|
messageType,
|
|
@@ -3260,6 +3357,11 @@ var require_dignity_p2p = __commonJS({
|
|
|
3260
3357
|
targetId: null,
|
|
3261
3358
|
securityContext
|
|
3262
3359
|
});
|
|
3360
|
+
const fanoutPeerIds = securityContext.fanoutPeerIds;
|
|
3361
|
+
if (Array.isArray(fanoutPeerIds) && fanoutPeerIds.length > 0 && typeof this.networkAdapter.sendToPeers === "function") {
|
|
3362
|
+
await this.networkAdapter.sendToPeers(envelope, fanoutPeerIds);
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3263
3365
|
await this.networkAdapter.broadcast(envelope);
|
|
3264
3366
|
}
|
|
3265
3367
|
async sendDirectMessage(targetId, messageType, payload) {
|
|
@@ -3275,8 +3377,237 @@ var require_dignity_p2p = __commonJS({
|
|
|
3275
3377
|
payload,
|
|
3276
3378
|
targetId
|
|
3277
3379
|
});
|
|
3380
|
+
if (targetId && typeof this.networkAdapter.sendToPeers === "function") {
|
|
3381
|
+
await this.networkAdapter.sendToPeers(envelope, [targetId]);
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3278
3384
|
await this.networkAdapter.broadcast(envelope);
|
|
3279
3385
|
}
|
|
3386
|
+
peerGroupScopeFor(groupId) {
|
|
3387
|
+
return peerGroupScope(groupId);
|
|
3388
|
+
}
|
|
3389
|
+
getPeerGroupConfig(groupId) {
|
|
3390
|
+
return this.peerGroups.get(groupId) || null;
|
|
3391
|
+
}
|
|
3392
|
+
listPeerGroupMembers(groupId, options = {}) {
|
|
3393
|
+
return this.listPeers(this.peerGroupScopeFor(groupId), options);
|
|
3394
|
+
}
|
|
3395
|
+
getPeerGroupStats() {
|
|
3396
|
+
const adapter = this.networkAdapter;
|
|
3397
|
+
const openPeerIds = typeof adapter.listOpenPeerIds === "function" ? adapter.listOpenPeerIds() : [];
|
|
3398
|
+
return {
|
|
3399
|
+
joinedGroups: Array.from(this.peerGroups.keys()),
|
|
3400
|
+
seenGossipCount: this.seenGossipIds.size,
|
|
3401
|
+
openConnectionCount: openPeerIds.length,
|
|
3402
|
+
globalMaxOpenConnections: this.globalMaxOpenConnections
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
pruneSeenGossip() {
|
|
3406
|
+
const now = this.now();
|
|
3407
|
+
for (const [gossipId, expiresAt] of this.seenGossipIds.entries()) {
|
|
3408
|
+
if (expiresAt <= now) {
|
|
3409
|
+
this.seenGossipIds.delete(gossipId);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
hasSeenGossip(gossipId) {
|
|
3414
|
+
if (!gossipId) {
|
|
3415
|
+
return false;
|
|
3416
|
+
}
|
|
3417
|
+
this.pruneSeenGossip();
|
|
3418
|
+
return this.seenGossipIds.has(gossipId);
|
|
3419
|
+
}
|
|
3420
|
+
markSeenGossip(gossipId) {
|
|
3421
|
+
if (!gossipId) {
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
this.seenGossipIds.set(gossipId, this.now() + this.gossipIdTtlMs);
|
|
3425
|
+
}
|
|
3426
|
+
listConnectedPeerIds() {
|
|
3427
|
+
if (typeof this.networkAdapter.listOpenPeerIds === "function") {
|
|
3428
|
+
return this.networkAdapter.listOpenPeerIds();
|
|
3429
|
+
}
|
|
3430
|
+
return [];
|
|
3431
|
+
}
|
|
3432
|
+
selectPeerGroupFanout(groupId, count, excludePeerIds = []) {
|
|
3433
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3434
|
+
const peers = this.listPeers(scope, { includeSelf: false });
|
|
3435
|
+
return selectFanoutPeers({
|
|
3436
|
+
peers,
|
|
3437
|
+
count,
|
|
3438
|
+
excludePeerIds: [...excludePeerIds, this.nodeId],
|
|
3439
|
+
connectedPeerIds: this.listConnectedPeerIds()
|
|
3440
|
+
});
|
|
3441
|
+
}
|
|
3442
|
+
async enforceConnectionBudget() {
|
|
3443
|
+
const adapter = this.networkAdapter;
|
|
3444
|
+
if (typeof adapter.listOpenPeerIds !== "function" || typeof adapter.disconnectPeer !== "function") {
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3447
|
+
const openPeerIds = adapter.listOpenPeerIds();
|
|
3448
|
+
if (openPeerIds.length <= this.globalMaxOpenConnections) {
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3451
|
+
const excess = openPeerIds.length - this.globalMaxOpenConnections;
|
|
3452
|
+
const toClose = openPeerIds.slice(0, excess);
|
|
3453
|
+
for (const peerId of toClose) {
|
|
3454
|
+
try {
|
|
3455
|
+
await adapter.disconnectPeer(peerId);
|
|
3456
|
+
} catch (error) {
|
|
3457
|
+
this.emit("warning", { type: "peer-disconnect-failed", peerId, error });
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
async joinPeerGroup(groupId, options = {}) {
|
|
3462
|
+
if (!groupId) {
|
|
3463
|
+
throw new Error("joinPeerGroup requires groupId");
|
|
3464
|
+
}
|
|
3465
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3466
|
+
const config = {
|
|
3467
|
+
fanout: typeof options.fanout === "number" ? options.fanout : this.defaultPeerGroupFanout,
|
|
3468
|
+
maxActivePeers: typeof options.maxActivePeers === "number" ? options.maxActivePeers : this.defaultPeerGroupMaxActivePeers,
|
|
3469
|
+
maxHops: typeof options.maxHops === "number" ? options.maxHops : this.defaultGossipMaxHops,
|
|
3470
|
+
relayEnabled: options.relayEnabled !== false
|
|
3471
|
+
};
|
|
3472
|
+
await this.joinDiscovery(scope, {
|
|
3473
|
+
metadata: {
|
|
3474
|
+
peerGroup: groupId,
|
|
3475
|
+
...options.metadata || {}
|
|
3476
|
+
},
|
|
3477
|
+
bootstrapPeerIds: options.bootstrapPeerIds,
|
|
3478
|
+
heartbeatIntervalMs: options.heartbeatIntervalMs,
|
|
3479
|
+
ttlMs: options.ttlMs
|
|
3480
|
+
});
|
|
3481
|
+
this.peerGroups.set(groupId, config);
|
|
3482
|
+
this.emit("peergroupjoined", { groupId, config });
|
|
3483
|
+
return config;
|
|
3484
|
+
}
|
|
3485
|
+
async leavePeerGroup(groupId) {
|
|
3486
|
+
if (!groupId) {
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3490
|
+
await this.leaveDiscovery(scope);
|
|
3491
|
+
this.peerGroups.delete(groupId);
|
|
3492
|
+
this.emit("peergroupleft", { groupId });
|
|
3493
|
+
}
|
|
3494
|
+
async publishToPeerGroup(groupId, innerMessageType, innerPayload, options = {}) {
|
|
3495
|
+
if (!groupId) {
|
|
3496
|
+
throw new Error("publishToPeerGroup requires groupId");
|
|
3497
|
+
}
|
|
3498
|
+
const group = this.peerGroups.get(groupId);
|
|
3499
|
+
if (!group && options.allowUnjoined !== true) {
|
|
3500
|
+
throw new Error(`PeerGroup ${groupId} has not been joined`);
|
|
3501
|
+
}
|
|
3502
|
+
const fanout = typeof options.fanout === "number" ? options.fanout : group ? group.fanout : this.defaultPeerGroupFanout;
|
|
3503
|
+
const maxActivePeers = group ? group.maxActivePeers : this.defaultPeerGroupMaxActivePeers;
|
|
3504
|
+
const maxHop = typeof options.maxHops === "number" ? options.maxHops : group ? group.maxHops : this.defaultGossipMaxHops;
|
|
3505
|
+
const fanoutPeerIds = this.selectPeerGroupFanout(groupId, fanout, [this.nodeId]);
|
|
3506
|
+
if (fanoutPeerIds.length > 0) {
|
|
3507
|
+
await this.ensureConnectedToPeers(fanoutPeerIds.slice(0, maxActivePeers));
|
|
3508
|
+
await this.enforceConnectionBudget();
|
|
3509
|
+
}
|
|
3510
|
+
const gossipId = options.gossipId || this.idGenerator();
|
|
3511
|
+
this.markSeenGossip(gossipId);
|
|
3512
|
+
await this.broadcastMessage("peer-group:gossip", {
|
|
3513
|
+
groupId,
|
|
3514
|
+
gossipId,
|
|
3515
|
+
hop: 0,
|
|
3516
|
+
maxHop,
|
|
3517
|
+
innerMessageType,
|
|
3518
|
+
innerPayload
|
|
3519
|
+
}, {
|
|
3520
|
+
broadcastScope: this.peerGroupScopeFor(groupId),
|
|
3521
|
+
fanoutPeerIds
|
|
3522
|
+
});
|
|
3523
|
+
return { gossipId, fanoutPeerIds };
|
|
3524
|
+
}
|
|
3525
|
+
async publishRecordToPeerGroup(groupId, collectionName, id, options = {}) {
|
|
3526
|
+
const collection = this.getCollection(collectionName);
|
|
3527
|
+
const raw = collection.get(id);
|
|
3528
|
+
if (!raw || raw.deletedAt) {
|
|
3529
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3530
|
+
}
|
|
3531
|
+
const record = this.normalizeRecord(raw);
|
|
3532
|
+
return this.publishToPeerGroup(groupId, "record:snapshot", {
|
|
3533
|
+
collectionName,
|
|
3534
|
+
record
|
|
3535
|
+
}, options);
|
|
3536
|
+
}
|
|
3537
|
+
async handlePeerGroupGossip(decrypted) {
|
|
3538
|
+
const payload = decrypted.payload || {};
|
|
3539
|
+
const {
|
|
3540
|
+
groupId,
|
|
3541
|
+
gossipId,
|
|
3542
|
+
hop = 0,
|
|
3543
|
+
maxHop = this.defaultGossipMaxHops,
|
|
3544
|
+
innerMessageType,
|
|
3545
|
+
innerPayload
|
|
3546
|
+
} = payload;
|
|
3547
|
+
if (!groupId || !innerMessageType) {
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
if (this.hasSeenGossip(gossipId)) {
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
this.markSeenGossip(gossipId);
|
|
3554
|
+
await this.dispatchPeerGroupInnerMessage(innerMessageType, innerPayload, {
|
|
3555
|
+
groupId,
|
|
3556
|
+
senderId: decrypted.senderId
|
|
3557
|
+
});
|
|
3558
|
+
const group = this.peerGroups.get(groupId);
|
|
3559
|
+
if (!group || group.relayEnabled === false || hop >= maxHop) {
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
const relayPeers = this.selectPeerGroupFanout(groupId, group.fanout, [
|
|
3563
|
+
decrypted.senderId,
|
|
3564
|
+
this.nodeId
|
|
3565
|
+
]);
|
|
3566
|
+
if (relayPeers.length === 0) {
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
await this.ensureConnectedToPeers(relayPeers.slice(0, group.maxActivePeers));
|
|
3570
|
+
await this.enforceConnectionBudget();
|
|
3571
|
+
await this.broadcastMessage("peer-group:gossip", {
|
|
3572
|
+
groupId,
|
|
3573
|
+
gossipId,
|
|
3574
|
+
hop: hop + 1,
|
|
3575
|
+
maxHop,
|
|
3576
|
+
innerMessageType,
|
|
3577
|
+
innerPayload
|
|
3578
|
+
}, {
|
|
3579
|
+
broadcastScope: this.peerGroupScopeFor(groupId),
|
|
3580
|
+
fanoutPeerIds: relayPeers
|
|
3581
|
+
});
|
|
3582
|
+
}
|
|
3583
|
+
async dispatchPeerGroupInnerMessage(innerMessageType, innerPayload, context = {}) {
|
|
3584
|
+
if (innerMessageType === "operation") {
|
|
3585
|
+
this.applyOperation(innerPayload);
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
if (innerMessageType === "record:snapshot") {
|
|
3589
|
+
const { collectionName, record } = innerPayload || {};
|
|
3590
|
+
if (collectionName && record) {
|
|
3591
|
+
const applied = this.restoreRecord(collectionName, record);
|
|
3592
|
+
if (applied) {
|
|
3593
|
+
this.emit("change", {
|
|
3594
|
+
kind: "snapshot",
|
|
3595
|
+
collection: collectionName,
|
|
3596
|
+
id: record.id,
|
|
3597
|
+
via: "peer-group",
|
|
3598
|
+
groupId: context.groupId
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
this.emit("peergroupmessage", {
|
|
3605
|
+
groupId: context.groupId,
|
|
3606
|
+
senderId: context.senderId,
|
|
3607
|
+
type: innerMessageType,
|
|
3608
|
+
payload: innerPayload
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3280
3611
|
getPresenceMap(scope) {
|
|
3281
3612
|
if (!this.presenceByScope.has(scope)) {
|
|
3282
3613
|
this.presenceByScope.set(scope, /* @__PURE__ */ new Map());
|
|
@@ -3495,6 +3826,10 @@ var require_dignity_p2p = __commonJS({
|
|
|
3495
3826
|
}
|
|
3496
3827
|
return;
|
|
3497
3828
|
}
|
|
3829
|
+
if (decrypted.messageType === "peer-group:gossip") {
|
|
3830
|
+
await this.handlePeerGroupGossip(decrypted);
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3498
3833
|
this.emit("message", {
|
|
3499
3834
|
senderId: decrypted.senderId,
|
|
3500
3835
|
targetId: decrypted.targetId,
|
|
@@ -3549,11 +3884,23 @@ var require_dignity_p2p = __commonJS({
|
|
|
3549
3884
|
if (current && current.version >= record.version) {
|
|
3550
3885
|
return false;
|
|
3551
3886
|
}
|
|
3887
|
+
const restoredData = { ...record.data || {} };
|
|
3888
|
+
const computedHash = computeContentHash(restoredData);
|
|
3889
|
+
if (record.hash && record.hash !== computedHash) {
|
|
3890
|
+
this.emit("warning", {
|
|
3891
|
+
type: "content-hash-mismatch",
|
|
3892
|
+
collection: collectionName,
|
|
3893
|
+
id: record.id,
|
|
3894
|
+
advertisedHash: record.hash,
|
|
3895
|
+
computedHash
|
|
3896
|
+
});
|
|
3897
|
+
}
|
|
3552
3898
|
collection.set(record.id, {
|
|
3553
3899
|
id: record.id,
|
|
3554
3900
|
ownerId: record.ownerId,
|
|
3555
3901
|
collaboratorIds: this.normalizeCollaboratorIds(record.collaboratorIds),
|
|
3556
|
-
data:
|
|
3902
|
+
data: restoredData,
|
|
3903
|
+
hash: computedHash,
|
|
3557
3904
|
createdAt: record.createdAt,
|
|
3558
3905
|
updatedAt: record.updatedAt,
|
|
3559
3906
|
deletedAt: record.deletedAt || null,
|
|
@@ -3571,7 +3918,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3571
3918
|
id: raw.id,
|
|
3572
3919
|
ownerId: raw.ownerId,
|
|
3573
3920
|
collaboratorIds: Array.isArray(raw.collaboratorIds) ? [...raw.collaboratorIds] : [],
|
|
3574
|
-
data: { ...raw.data },
|
|
3921
|
+
data: { ...raw.data || {} },
|
|
3922
|
+
hash: raw.hash || computeContentHash(raw.data || {}),
|
|
3575
3923
|
createdAt: raw.createdAt,
|
|
3576
3924
|
updatedAt: raw.updatedAt,
|
|
3577
3925
|
deletedAt: raw.deletedAt || null,
|
|
@@ -3601,7 +3949,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3601
3949
|
id: operation.id,
|
|
3602
3950
|
ownerId: operation.ownerId,
|
|
3603
3951
|
collaboratorIds: this.normalizeCollaboratorIds(operation.collaboratorIds),
|
|
3604
|
-
data: { ...operation.payload },
|
|
3952
|
+
data: { ...operation.payload || {} },
|
|
3953
|
+
hash: computeContentHash(operation.payload || {}),
|
|
3605
3954
|
createdAt: operation.timestamp,
|
|
3606
3955
|
updatedAt: operation.timestamp,
|
|
3607
3956
|
deletedAt: null,
|
|
@@ -3704,6 +4053,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3704
4053
|
...current.data,
|
|
3705
4054
|
...operation.payload
|
|
3706
4055
|
};
|
|
4056
|
+
current.hash = computeContentHash(current.data);
|
|
3707
4057
|
if (Array.isArray(operation.collaboratorIds) && operation.actorId === current.ownerId) {
|
|
3708
4058
|
current.collaboratorIds = this.normalizeCollaboratorIds(operation.collaboratorIds);
|
|
3709
4059
|
}
|
|
@@ -3789,6 +4139,14 @@ var require_signaling_pool = __commonJS({
|
|
|
3789
4139
|
// src/signaling/websocket-signaling-provider.js
|
|
3790
4140
|
var require_websocket_signaling_provider = __commonJS({
|
|
3791
4141
|
"src/signaling/websocket-signaling-provider.js"(exports, module) {
|
|
4142
|
+
function randomBase36(length) {
|
|
4143
|
+
let value = "";
|
|
4144
|
+
while (value.length < length) {
|
|
4145
|
+
const chunk = Math.random().toString(36).slice(2);
|
|
4146
|
+
value += chunk.length > 0 ? chunk : "0";
|
|
4147
|
+
}
|
|
4148
|
+
return value.slice(0, length);
|
|
4149
|
+
}
|
|
3792
4150
|
var WebSocketSignalingProvider = class {
|
|
3793
4151
|
constructor({ id, url, WebSocketImpl, priority = 0 }) {
|
|
3794
4152
|
if (!url) {
|
|
@@ -3832,8 +4190,8 @@ var require_websocket_signaling_provider = __commonJS({
|
|
|
3832
4190
|
if (!peerJsHostPattern.test(this.url)) {
|
|
3833
4191
|
return this.url;
|
|
3834
4192
|
}
|
|
3835
|
-
const connectionId = `dignityjs_${
|
|
3836
|
-
const token =
|
|
4193
|
+
const connectionId = `dignityjs_${randomBase36(10)}`;
|
|
4194
|
+
const token = randomBase36(10);
|
|
3837
4195
|
const hasQuery = this.url.includes("?");
|
|
3838
4196
|
const hasId = /[?&]id=/.test(this.url);
|
|
3839
4197
|
const hasToken = /[?&]token=/.test(this.url);
|
|
@@ -10749,6 +11107,16 @@ var require_in_memory_network = __commonJS({
|
|
|
10749
11107
|
}
|
|
10750
11108
|
await Promise.all(deliveries);
|
|
10751
11109
|
}
|
|
11110
|
+
async sendToPeers(senderId, message, peerIds = []) {
|
|
11111
|
+
const targets = new Set((peerIds || []).filter((peerId) => peerId && peerId !== senderId));
|
|
11112
|
+
const deliveries = [];
|
|
11113
|
+
for (const [nodeId, adapter] of this.adapters.entries()) {
|
|
11114
|
+
if (nodeId !== senderId && targets.has(nodeId)) {
|
|
11115
|
+
deliveries.push(adapter.receive(message));
|
|
11116
|
+
}
|
|
11117
|
+
}
|
|
11118
|
+
await Promise.all(deliveries);
|
|
11119
|
+
}
|
|
10752
11120
|
};
|
|
10753
11121
|
var InMemoryNetworkAdapter = class {
|
|
10754
11122
|
constructor(hub) {
|
|
@@ -10758,6 +11126,7 @@ var require_in_memory_network = __commonJS({
|
|
|
10758
11126
|
this.hub = hub;
|
|
10759
11127
|
this.nodeId = null;
|
|
10760
11128
|
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
11129
|
+
this.connectedPeers = /* @__PURE__ */ new Set();
|
|
10761
11130
|
}
|
|
10762
11131
|
async start(nodeId) {
|
|
10763
11132
|
this.nodeId = nodeId;
|
|
@@ -10768,6 +11137,13 @@ var require_in_memory_network = __commonJS({
|
|
|
10768
11137
|
this.hub.unregister(this.nodeId);
|
|
10769
11138
|
}
|
|
10770
11139
|
this.nodeId = null;
|
|
11140
|
+
this.connectedPeers.clear();
|
|
11141
|
+
}
|
|
11142
|
+
async connectToPeer(remotePeerId) {
|
|
11143
|
+
if (!remotePeerId || remotePeerId === this.nodeId) {
|
|
11144
|
+
return;
|
|
11145
|
+
}
|
|
11146
|
+
this.connectedPeers.add(remotePeerId);
|
|
10771
11147
|
}
|
|
10772
11148
|
async broadcast(message) {
|
|
10773
11149
|
if (!this.nodeId) {
|
|
@@ -10775,6 +11151,21 @@ var require_in_memory_network = __commonJS({
|
|
|
10775
11151
|
}
|
|
10776
11152
|
await this.hub.broadcast(this.nodeId, message);
|
|
10777
11153
|
}
|
|
11154
|
+
async sendToPeers(message, peerIds = []) {
|
|
11155
|
+
if (!this.nodeId) {
|
|
11156
|
+
throw new Error("Network adapter has not been started");
|
|
11157
|
+
}
|
|
11158
|
+
await this.hub.sendToPeers(this.nodeId, message, peerIds);
|
|
11159
|
+
}
|
|
11160
|
+
listOpenPeerIds() {
|
|
11161
|
+
return [...this.connectedPeers];
|
|
11162
|
+
}
|
|
11163
|
+
getOpenConnectionCount() {
|
|
11164
|
+
return this.connectedPeers.size;
|
|
11165
|
+
}
|
|
11166
|
+
isConnectedTo(remotePeerId) {
|
|
11167
|
+
return this.connectedPeers.has(remotePeerId);
|
|
11168
|
+
}
|
|
10778
11169
|
onMessage(handler) {
|
|
10779
11170
|
this.messageHandlers.add(handler);
|
|
10780
11171
|
}
|
|
@@ -10953,6 +11344,29 @@ var require_peerjs_network = __commonJS({
|
|
|
10953
11344
|
}
|
|
10954
11345
|
await Promise.all(deliveries);
|
|
10955
11346
|
}
|
|
11347
|
+
async sendToPeers(message, peerIds = []) {
|
|
11348
|
+
if (!this.peer) {
|
|
11349
|
+
throw new Error("PeerJS network adapter has not been started");
|
|
11350
|
+
}
|
|
11351
|
+
const targets = new Set((peerIds || []).filter(Boolean));
|
|
11352
|
+
if (targets.size === 0) {
|
|
11353
|
+
return;
|
|
11354
|
+
}
|
|
11355
|
+
const deliveries = [];
|
|
11356
|
+
for (const [peerId, connection] of this.connections.entries()) {
|
|
11357
|
+
if (targets.has(peerId) && connection.open) {
|
|
11358
|
+
deliveries.push(connection.send(message));
|
|
11359
|
+
}
|
|
11360
|
+
}
|
|
11361
|
+
await Promise.all(deliveries);
|
|
11362
|
+
}
|
|
11363
|
+
async disconnectPeer(remotePeerId) {
|
|
11364
|
+
const connection = this.connections.get(remotePeerId);
|
|
11365
|
+
if (connection && typeof connection.close === "function") {
|
|
11366
|
+
connection.close();
|
|
11367
|
+
}
|
|
11368
|
+
this.connections.delete(remotePeerId);
|
|
11369
|
+
}
|
|
10956
11370
|
getOpenConnectionCount() {
|
|
10957
11371
|
return this.listOpenPeerIds().length;
|
|
10958
11372
|
}
|
|
@@ -11059,6 +11473,7 @@ var require_indexeddb_persistence = __commonJS({
|
|
|
11059
11473
|
ownerId: record.ownerId,
|
|
11060
11474
|
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
11061
11475
|
data: { ...record.data },
|
|
11476
|
+
hash: record.hash || null,
|
|
11062
11477
|
createdAt: record.createdAt,
|
|
11063
11478
|
updatedAt: record.updatedAt,
|
|
11064
11479
|
deletedAt: record.deletedAt,
|
|
@@ -11119,6 +11534,7 @@ var require_indexeddb_persistence = __commonJS({
|
|
|
11119
11534
|
ownerId: stored.ownerId,
|
|
11120
11535
|
collaboratorIds: stored.collaboratorIds,
|
|
11121
11536
|
data: stored.data,
|
|
11537
|
+
hash: stored.hash || null,
|
|
11122
11538
|
createdAt: stored.createdAt,
|
|
11123
11539
|
updatedAt: stored.updatedAt,
|
|
11124
11540
|
deletedAt: stored.deletedAt,
|
|
@@ -11181,6 +11597,12 @@ var require_index = __commonJS({
|
|
|
11181
11597
|
MessageSecurityService,
|
|
11182
11598
|
DEFAULT_SECURITY_OPTIONS
|
|
11183
11599
|
} = require_message_security_service();
|
|
11600
|
+
var {
|
|
11601
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
11602
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11603
|
+
peerGroupScope,
|
|
11604
|
+
selectFanoutPeers
|
|
11605
|
+
} = require_peer_group();
|
|
11184
11606
|
module.exports = {
|
|
11185
11607
|
DignityP2P,
|
|
11186
11608
|
createDefaultSignalingPool,
|
|
@@ -11197,7 +11619,11 @@ var require_index = __commonJS({
|
|
|
11197
11619
|
VDF,
|
|
11198
11620
|
SlothPermutation,
|
|
11199
11621
|
MessageSecurityService,
|
|
11200
|
-
DEFAULT_SECURITY_OPTIONS
|
|
11622
|
+
DEFAULT_SECURITY_OPTIONS,
|
|
11623
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
11624
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11625
|
+
peerGroupScope,
|
|
11626
|
+
selectFanoutPeers
|
|
11201
11627
|
};
|
|
11202
11628
|
}
|
|
11203
11629
|
});
|