dignity.js 0.5.3 → 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 +61 -3
- package/dist/dignity.cjs.js +603 -65
- package/dist/dignity.cjs.js.map +4 -4
- package/dist/dignity.esm.js +603 -65
- 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 +40 -5
- package/examples/decentralized-chess-lite.js +9 -0
- package/package.json +2 -1
- package/src/core/dignity-p2p.js +506 -20
- 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/persistence/indexeddb-persistence.js +2 -0
- package/src/security/message-security-service.js +10 -2
- package/src/security/sloth-vdf.js +11 -5
- package/src/signaling/websocket-signaling-provider.js +11 -2
package/dist/dignity.esm.js
CHANGED
|
@@ -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
|
}
|
|
@@ -2763,8 +2769,14 @@ var require_message_security_service = __commonJS({
|
|
|
2763
2769
|
const nonce = naclUtil.decodeBase64(encryption.nonce);
|
|
2764
2770
|
let key;
|
|
2765
2771
|
if (encryption.kdf === "pbkdf2") {
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2772
|
+
const configuredIterations = this.options.kdfIterations || DEFAULT_SECURITY_OPTIONS.kdfIterations;
|
|
2773
|
+
const requestedIterations = encryption.kdfIterations || configuredIterations;
|
|
2774
|
+
const minIterations = Math.max(1e3, Math.floor(configuredIterations * 0.1));
|
|
2775
|
+
const maxIterations = configuredIterations * 2;
|
|
2776
|
+
if (requestedIterations < minIterations || requestedIterations > maxIterations) {
|
|
2777
|
+
throw new Error(`Invalid kdfIterations: ${requestedIterations}`);
|
|
2778
|
+
}
|
|
2779
|
+
key = await deriveBroadcastKey(password, salt, requestedIterations);
|
|
2768
2780
|
} else {
|
|
2769
2781
|
key = legacyBroadcastKey(password, salt);
|
|
2770
2782
|
}
|
|
@@ -2868,11 +2880,84 @@ var require_message_security_service = __commonJS({
|
|
|
2868
2880
|
}
|
|
2869
2881
|
});
|
|
2870
2882
|
|
|
2883
|
+
// src/gossip/peer-group.js
|
|
2884
|
+
var require_peer_group = __commonJS({
|
|
2885
|
+
"src/gossip/peer-group.js"(exports, module) {
|
|
2886
|
+
var PEER_GROUP_SCOPE_PREFIX = "gossip:";
|
|
2887
|
+
var DEFAULT_PEER_GROUP_OPTIONS = {
|
|
2888
|
+
fanout: 3,
|
|
2889
|
+
maxActivePeers: 8,
|
|
2890
|
+
maxHops: 6,
|
|
2891
|
+
relayEnabled: true
|
|
2892
|
+
};
|
|
2893
|
+
function peerGroupScope(groupId) {
|
|
2894
|
+
if (!groupId) {
|
|
2895
|
+
throw new Error("peerGroupScope requires groupId");
|
|
2896
|
+
}
|
|
2897
|
+
return `${PEER_GROUP_SCOPE_PREFIX}${groupId}`;
|
|
2898
|
+
}
|
|
2899
|
+
function parsePeerGroupScope(scope) {
|
|
2900
|
+
if (!scope || !scope.startsWith(PEER_GROUP_SCOPE_PREFIX)) {
|
|
2901
|
+
return null;
|
|
2902
|
+
}
|
|
2903
|
+
return scope.slice(PEER_GROUP_SCOPE_PREFIX.length);
|
|
2904
|
+
}
|
|
2905
|
+
function shufflePeerIds(peerIds, randomFn = Math.random) {
|
|
2906
|
+
const list = [...peerIds];
|
|
2907
|
+
for (let i = list.length - 1; i > 0; i -= 1) {
|
|
2908
|
+
const j = Math.floor(randomFn() * (i + 1));
|
|
2909
|
+
[list[i], list[j]] = [list[j], list[i]];
|
|
2910
|
+
}
|
|
2911
|
+
return list;
|
|
2912
|
+
}
|
|
2913
|
+
function selectFanoutPeers({
|
|
2914
|
+
peers,
|
|
2915
|
+
count,
|
|
2916
|
+
excludePeerIds = [],
|
|
2917
|
+
connectedPeerIds = [],
|
|
2918
|
+
randomFn = Math.random
|
|
2919
|
+
}) {
|
|
2920
|
+
const excluded = new Set(excludePeerIds.filter(Boolean));
|
|
2921
|
+
const candidates = peers.map((entry) => entry.peerId || entry).filter((peerId) => peerId && !excluded.has(peerId));
|
|
2922
|
+
const connected = new Set(connectedPeerIds.filter(Boolean));
|
|
2923
|
+
const preferred = candidates.filter((peerId) => connected.has(peerId));
|
|
2924
|
+
const others = candidates.filter((peerId) => !connected.has(peerId));
|
|
2925
|
+
const ordered = [
|
|
2926
|
+
...shufflePeerIds(preferred, randomFn),
|
|
2927
|
+
...shufflePeerIds(others, randomFn)
|
|
2928
|
+
];
|
|
2929
|
+
return ordered.slice(0, Math.max(0, count));
|
|
2930
|
+
}
|
|
2931
|
+
module.exports = {
|
|
2932
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
2933
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
2934
|
+
peerGroupScope,
|
|
2935
|
+
parsePeerGroupScope,
|
|
2936
|
+
shufflePeerIds,
|
|
2937
|
+
selectFanoutPeers
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
});
|
|
2941
|
+
|
|
2871
2942
|
// src/core/dignity-p2p.js
|
|
2872
2943
|
var require_dignity_p2p = __commonJS({
|
|
2873
2944
|
"src/core/dignity-p2p.js"(exports, module) {
|
|
2945
|
+
var nacl = require_nacl_fast();
|
|
2946
|
+
var naclUtil = require_nacl_util();
|
|
2874
2947
|
var EventEmitter = require_event_emitter();
|
|
2875
|
-
var { MessageSecurityService } = require_message_security_service();
|
|
2948
|
+
var { MessageSecurityService, stableStringify } = require_message_security_service();
|
|
2949
|
+
var {
|
|
2950
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
2951
|
+
peerGroupScope,
|
|
2952
|
+
selectFanoutPeers
|
|
2953
|
+
} = require_peer_group();
|
|
2954
|
+
function computeContentHash(data) {
|
|
2955
|
+
const canonical = stableStringify(data || {});
|
|
2956
|
+
const bytes = naclUtil.decodeUTF8(canonical);
|
|
2957
|
+
const hash = nacl.hash(bytes);
|
|
2958
|
+
const hex = Array.from(hash, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2959
|
+
return `sha512:${hex}`;
|
|
2960
|
+
}
|
|
2876
2961
|
var DignityP2P = class extends EventEmitter {
|
|
2877
2962
|
constructor({ nodeId, networkAdapter, idGenerator, now, security } = {}) {
|
|
2878
2963
|
super();
|
|
@@ -2898,8 +2983,16 @@ var require_dignity_p2p = __commonJS({
|
|
|
2898
2983
|
this.defaultPresenceTtlMs = security && typeof security.presenceTtlMs === "number" ? security.presenceTtlMs : 45e3;
|
|
2899
2984
|
this.discoveryRooms = /* @__PURE__ */ new Map();
|
|
2900
2985
|
this.presenceByScope = /* @__PURE__ */ new Map();
|
|
2986
|
+
this.peerGroups = /* @__PURE__ */ new Map();
|
|
2987
|
+
this.seenGossipIds = /* @__PURE__ */ new Map();
|
|
2988
|
+
this.defaultPeerGroupFanout = security && typeof security.peerGroupFanout === "number" ? security.peerGroupFanout : DEFAULT_PEER_GROUP_OPTIONS.fanout;
|
|
2989
|
+
this.defaultPeerGroupMaxActivePeers = security && typeof security.peerGroupMaxActivePeers === "number" ? security.peerGroupMaxActivePeers : DEFAULT_PEER_GROUP_OPTIONS.maxActivePeers;
|
|
2990
|
+
this.defaultGossipMaxHops = security && typeof security.gossipMaxHops === "number" ? security.gossipMaxHops : DEFAULT_PEER_GROUP_OPTIONS.maxHops;
|
|
2991
|
+
this.globalMaxOpenConnections = security && typeof security.globalMaxOpenConnections === "number" ? security.globalMaxOpenConnections : 32;
|
|
2992
|
+
this.gossipIdTtlMs = security && typeof security.gossipIdTtlMs === "number" ? security.gossipIdTtlMs : 5 * 60 * 1e3;
|
|
2993
|
+
this.maxAppliedOperations = security && typeof security.maxAppliedOperations === "number" ? security.maxAppliedOperations : 5e4;
|
|
2901
2994
|
this.state = /* @__PURE__ */ new Map();
|
|
2902
|
-
this.appliedOperations = /* @__PURE__ */ new
|
|
2995
|
+
this.appliedOperations = /* @__PURE__ */ new Map();
|
|
2903
2996
|
this.boundMessageHandler = this.handleIncomingMessage.bind(this);
|
|
2904
2997
|
}
|
|
2905
2998
|
async start() {
|
|
@@ -2907,6 +3000,14 @@ var require_dignity_p2p = __commonJS({
|
|
|
2907
3000
|
await this.networkAdapter.start(this.nodeId);
|
|
2908
3001
|
}
|
|
2909
3002
|
async stop() {
|
|
3003
|
+
const joinedGroups = Array.from(this.peerGroups.keys());
|
|
3004
|
+
for (const groupId of joinedGroups) {
|
|
3005
|
+
try {
|
|
3006
|
+
await this.leavePeerGroup(groupId);
|
|
3007
|
+
} catch (error) {
|
|
3008
|
+
this.emit("warning", { type: "peer-group-leave-failed", groupId, error });
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
2910
3011
|
const joinedScopes = Array.from(this.discoveryRooms.keys());
|
|
2911
3012
|
for (const scope of joinedScopes) {
|
|
2912
3013
|
try {
|
|
@@ -2931,6 +3032,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
2931
3032
|
if (!record || record.deletedAt) {
|
|
2932
3033
|
return null;
|
|
2933
3034
|
}
|
|
3035
|
+
const normalizedData = { ...record.data || {} };
|
|
2934
3036
|
return {
|
|
2935
3037
|
id: record.id,
|
|
2936
3038
|
ownerId: record.ownerId,
|
|
@@ -2938,7 +3040,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
2938
3040
|
createdAt: record.createdAt,
|
|
2939
3041
|
updatedAt: record.updatedAt,
|
|
2940
3042
|
version: record.version,
|
|
2941
|
-
|
|
3043
|
+
hash: record.hash || computeContentHash(normalizedData),
|
|
3044
|
+
data: normalizedData
|
|
2942
3045
|
};
|
|
2943
3046
|
}
|
|
2944
3047
|
canUpdateRecord(record, actorId) {
|
|
@@ -3016,7 +3119,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3016
3119
|
ownerId: this.nodeId,
|
|
3017
3120
|
collaboratorIds,
|
|
3018
3121
|
timestamp,
|
|
3019
|
-
payload: { ...data }
|
|
3122
|
+
payload: { ...data || {} }
|
|
3020
3123
|
};
|
|
3021
3124
|
this.applyOperation(operation);
|
|
3022
3125
|
await this.broadcastMessage("operation", operation, {
|
|
@@ -3253,6 +3356,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3253
3356
|
const connectToPeers = securityContext.connectToPeers;
|
|
3254
3357
|
if (Array.isArray(connectToPeers) && connectToPeers.length > 0) {
|
|
3255
3358
|
await this.ensureConnectedToPeers(connectToPeers);
|
|
3359
|
+
await this.enforceConnectionBudget();
|
|
3256
3360
|
}
|
|
3257
3361
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3258
3362
|
messageType,
|
|
@@ -3260,6 +3364,11 @@ var require_dignity_p2p = __commonJS({
|
|
|
3260
3364
|
targetId: null,
|
|
3261
3365
|
securityContext
|
|
3262
3366
|
});
|
|
3367
|
+
const fanoutPeerIds = securityContext.fanoutPeerIds;
|
|
3368
|
+
if (Array.isArray(fanoutPeerIds) && fanoutPeerIds.length > 0 && typeof this.networkAdapter.sendToPeers === "function") {
|
|
3369
|
+
await this.networkAdapter.sendToPeers(envelope, fanoutPeerIds);
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3263
3372
|
await this.networkAdapter.broadcast(envelope);
|
|
3264
3373
|
}
|
|
3265
3374
|
async sendDirectMessage(targetId, messageType, payload) {
|
|
@@ -3275,8 +3384,280 @@ var require_dignity_p2p = __commonJS({
|
|
|
3275
3384
|
payload,
|
|
3276
3385
|
targetId
|
|
3277
3386
|
});
|
|
3387
|
+
if (targetId && typeof this.networkAdapter.sendToPeers === "function") {
|
|
3388
|
+
await this.networkAdapter.sendToPeers(envelope, [targetId]);
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3278
3391
|
await this.networkAdapter.broadcast(envelope);
|
|
3279
3392
|
}
|
|
3393
|
+
peerGroupScopeFor(groupId) {
|
|
3394
|
+
return peerGroupScope(groupId);
|
|
3395
|
+
}
|
|
3396
|
+
getPeerGroupConfig(groupId) {
|
|
3397
|
+
return this.peerGroups.get(groupId) || null;
|
|
3398
|
+
}
|
|
3399
|
+
listPeerGroupMembers(groupId, options = {}) {
|
|
3400
|
+
return this.listPeers(this.peerGroupScopeFor(groupId), options);
|
|
3401
|
+
}
|
|
3402
|
+
getPeerGroupStats() {
|
|
3403
|
+
const adapter = this.networkAdapter;
|
|
3404
|
+
const openPeerIds = typeof adapter.listOpenPeerIds === "function" ? adapter.listOpenPeerIds() : [];
|
|
3405
|
+
return {
|
|
3406
|
+
joinedGroups: Array.from(this.peerGroups.keys()),
|
|
3407
|
+
seenGossipCount: this.seenGossipIds.size,
|
|
3408
|
+
openConnectionCount: openPeerIds.length,
|
|
3409
|
+
globalMaxOpenConnections: this.globalMaxOpenConnections
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
pruneSeenGossip() {
|
|
3413
|
+
const now = this.now();
|
|
3414
|
+
for (const [gossipId, expiresAt] of this.seenGossipIds.entries()) {
|
|
3415
|
+
if (expiresAt <= now) {
|
|
3416
|
+
this.seenGossipIds.delete(gossipId);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
hasSeenGossip(gossipId) {
|
|
3421
|
+
if (!gossipId) {
|
|
3422
|
+
return false;
|
|
3423
|
+
}
|
|
3424
|
+
this.pruneSeenGossip();
|
|
3425
|
+
return this.seenGossipIds.has(gossipId);
|
|
3426
|
+
}
|
|
3427
|
+
markSeenGossip(gossipId) {
|
|
3428
|
+
if (!gossipId) {
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
this.seenGossipIds.set(gossipId, this.now() + this.gossipIdTtlMs);
|
|
3432
|
+
}
|
|
3433
|
+
listConnectedPeerIds() {
|
|
3434
|
+
if (typeof this.networkAdapter.listOpenPeerIds === "function") {
|
|
3435
|
+
return this.networkAdapter.listOpenPeerIds();
|
|
3436
|
+
}
|
|
3437
|
+
return [];
|
|
3438
|
+
}
|
|
3439
|
+
selectPeerGroupFanout(groupId, count, excludePeerIds = []) {
|
|
3440
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3441
|
+
const peers = this.listPeers(scope, { includeSelf: false });
|
|
3442
|
+
return selectFanoutPeers({
|
|
3443
|
+
peers,
|
|
3444
|
+
count,
|
|
3445
|
+
excludePeerIds: [...excludePeerIds, this.nodeId],
|
|
3446
|
+
connectedPeerIds: this.listConnectedPeerIds()
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
async enforceConnectionBudget() {
|
|
3450
|
+
const adapter = this.networkAdapter;
|
|
3451
|
+
if (typeof adapter.listOpenPeerIds !== "function" || typeof adapter.disconnectPeer !== "function") {
|
|
3452
|
+
return;
|
|
3453
|
+
}
|
|
3454
|
+
const openPeerIds = adapter.listOpenPeerIds();
|
|
3455
|
+
if (openPeerIds.length <= this.globalMaxOpenConnections) {
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
const excess = openPeerIds.length - this.globalMaxOpenConnections;
|
|
3459
|
+
const toClose = openPeerIds.slice(0, excess);
|
|
3460
|
+
for (const peerId of toClose) {
|
|
3461
|
+
try {
|
|
3462
|
+
await adapter.disconnectPeer(peerId);
|
|
3463
|
+
} catch (error) {
|
|
3464
|
+
this.emit("warning", { type: "peer-disconnect-failed", peerId, error });
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
async joinPeerGroup(groupId, options = {}) {
|
|
3469
|
+
if (!groupId) {
|
|
3470
|
+
throw new Error("joinPeerGroup requires groupId");
|
|
3471
|
+
}
|
|
3472
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3473
|
+
const config = {
|
|
3474
|
+
fanout: typeof options.fanout === "number" ? options.fanout : this.defaultPeerGroupFanout,
|
|
3475
|
+
maxActivePeers: typeof options.maxActivePeers === "number" ? options.maxActivePeers : this.defaultPeerGroupMaxActivePeers,
|
|
3476
|
+
maxHops: typeof options.maxHops === "number" ? options.maxHops : this.defaultGossipMaxHops,
|
|
3477
|
+
relayEnabled: options.relayEnabled !== false
|
|
3478
|
+
};
|
|
3479
|
+
await this.joinDiscovery(scope, {
|
|
3480
|
+
metadata: {
|
|
3481
|
+
peerGroup: groupId,
|
|
3482
|
+
...options.metadata || {}
|
|
3483
|
+
},
|
|
3484
|
+
bootstrapPeerIds: options.bootstrapPeerIds,
|
|
3485
|
+
heartbeatIntervalMs: options.heartbeatIntervalMs,
|
|
3486
|
+
ttlMs: options.ttlMs
|
|
3487
|
+
});
|
|
3488
|
+
this.peerGroups.set(groupId, config);
|
|
3489
|
+
this.emit("peergroupjoined", { groupId, config });
|
|
3490
|
+
return config;
|
|
3491
|
+
}
|
|
3492
|
+
async leavePeerGroup(groupId) {
|
|
3493
|
+
if (!groupId) {
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3497
|
+
await this.leaveDiscovery(scope);
|
|
3498
|
+
this.peerGroups.delete(groupId);
|
|
3499
|
+
this.emit("peergroupleft", { groupId });
|
|
3500
|
+
}
|
|
3501
|
+
async publishToPeerGroup(groupId, innerMessageType, innerPayload, options = {}) {
|
|
3502
|
+
if (!groupId) {
|
|
3503
|
+
throw new Error("publishToPeerGroup requires groupId");
|
|
3504
|
+
}
|
|
3505
|
+
const group = this.peerGroups.get(groupId);
|
|
3506
|
+
if (!group && options.allowUnjoined !== true) {
|
|
3507
|
+
throw new Error(`PeerGroup ${groupId} has not been joined`);
|
|
3508
|
+
}
|
|
3509
|
+
const fanout = typeof options.fanout === "number" ? options.fanout : group ? group.fanout : this.defaultPeerGroupFanout;
|
|
3510
|
+
const maxActivePeers = group ? group.maxActivePeers : this.defaultPeerGroupMaxActivePeers;
|
|
3511
|
+
const maxHop = typeof options.maxHops === "number" ? options.maxHops : group ? group.maxHops : this.defaultGossipMaxHops;
|
|
3512
|
+
const fanoutPeerIds = this.selectPeerGroupFanout(groupId, fanout, [this.nodeId]);
|
|
3513
|
+
if (fanoutPeerIds.length > 0) {
|
|
3514
|
+
await this.ensureConnectedToPeers(fanoutPeerIds.slice(0, maxActivePeers));
|
|
3515
|
+
await this.enforceConnectionBudget();
|
|
3516
|
+
}
|
|
3517
|
+
const gossipId = options.gossipId || this.idGenerator();
|
|
3518
|
+
this.markSeenGossip(gossipId);
|
|
3519
|
+
await this.broadcastMessage("peer-group:gossip", {
|
|
3520
|
+
groupId,
|
|
3521
|
+
gossipId,
|
|
3522
|
+
publisherId: this.nodeId,
|
|
3523
|
+
hop: 0,
|
|
3524
|
+
maxHop,
|
|
3525
|
+
innerMessageType,
|
|
3526
|
+
innerPayload
|
|
3527
|
+
}, {
|
|
3528
|
+
broadcastScope: this.peerGroupScopeFor(groupId),
|
|
3529
|
+
fanoutPeerIds
|
|
3530
|
+
});
|
|
3531
|
+
return { gossipId, fanoutPeerIds };
|
|
3532
|
+
}
|
|
3533
|
+
async publishRecordToPeerGroup(groupId, collectionName, id, options = {}) {
|
|
3534
|
+
const collection = this.getCollection(collectionName);
|
|
3535
|
+
const raw = collection.get(id);
|
|
3536
|
+
if (!raw || raw.deletedAt) {
|
|
3537
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3538
|
+
}
|
|
3539
|
+
const record = this.normalizeRecord(raw);
|
|
3540
|
+
return this.publishToPeerGroup(groupId, "record:snapshot", {
|
|
3541
|
+
collectionName,
|
|
3542
|
+
record
|
|
3543
|
+
}, options);
|
|
3544
|
+
}
|
|
3545
|
+
async handlePeerGroupGossip(decrypted) {
|
|
3546
|
+
const payload = decrypted.payload || {};
|
|
3547
|
+
const {
|
|
3548
|
+
groupId,
|
|
3549
|
+
gossipId,
|
|
3550
|
+
publisherId = decrypted.senderId,
|
|
3551
|
+
hop = 0,
|
|
3552
|
+
maxHop: payloadMaxHop,
|
|
3553
|
+
innerMessageType,
|
|
3554
|
+
innerPayload
|
|
3555
|
+
} = payload;
|
|
3556
|
+
if (!groupId || !innerMessageType || !gossipId) {
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
if (!this.peerGroups.has(groupId)) {
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
if (this.hasSeenGossip(gossipId)) {
|
|
3563
|
+
return;
|
|
3564
|
+
}
|
|
3565
|
+
this.markSeenGossip(gossipId);
|
|
3566
|
+
await this.dispatchPeerGroupInnerMessage(innerMessageType, innerPayload, {
|
|
3567
|
+
groupId,
|
|
3568
|
+
senderId: decrypted.senderId,
|
|
3569
|
+
publisherId
|
|
3570
|
+
});
|
|
3571
|
+
const group = this.peerGroups.get(groupId);
|
|
3572
|
+
const configuredMaxHop = group ? group.maxHops : this.defaultGossipMaxHops;
|
|
3573
|
+
const maxHop = typeof payloadMaxHop === "number" ? Math.min(payloadMaxHop, configuredMaxHop) : configuredMaxHop;
|
|
3574
|
+
if (!group || group.relayEnabled === false || hop >= maxHop) {
|
|
3575
|
+
return;
|
|
3576
|
+
}
|
|
3577
|
+
const relayPeers = this.selectPeerGroupFanout(groupId, group.fanout, [
|
|
3578
|
+
decrypted.senderId,
|
|
3579
|
+
this.nodeId
|
|
3580
|
+
]);
|
|
3581
|
+
if (relayPeers.length === 0) {
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
await this.ensureConnectedToPeers(relayPeers.slice(0, group.maxActivePeers));
|
|
3585
|
+
await this.enforceConnectionBudget();
|
|
3586
|
+
await this.broadcastMessage("peer-group:gossip", {
|
|
3587
|
+
groupId,
|
|
3588
|
+
gossipId,
|
|
3589
|
+
publisherId,
|
|
3590
|
+
hop: hop + 1,
|
|
3591
|
+
maxHop,
|
|
3592
|
+
innerMessageType,
|
|
3593
|
+
innerPayload
|
|
3594
|
+
}, {
|
|
3595
|
+
broadcastScope: this.peerGroupScopeFor(groupId),
|
|
3596
|
+
fanoutPeerIds: relayPeers
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
normalizeGossipOperation(operation, publisherId) {
|
|
3600
|
+
if (!operation || !publisherId) {
|
|
3601
|
+
return null;
|
|
3602
|
+
}
|
|
3603
|
+
if (operation.actorId && operation.actorId !== publisherId) {
|
|
3604
|
+
this.emit("warning", {
|
|
3605
|
+
type: "gossip-operation-actor-mismatch",
|
|
3606
|
+
publisherId,
|
|
3607
|
+
actorId: operation.actorId,
|
|
3608
|
+
kind: operation.kind,
|
|
3609
|
+
collection: operation.collectionName,
|
|
3610
|
+
id: operation.id
|
|
3611
|
+
});
|
|
3612
|
+
return null;
|
|
3613
|
+
}
|
|
3614
|
+
const normalized = {
|
|
3615
|
+
...operation,
|
|
3616
|
+
actorId: publisherId
|
|
3617
|
+
};
|
|
3618
|
+
if (normalized.kind === "create") {
|
|
3619
|
+
normalized.ownerId = publisherId;
|
|
3620
|
+
}
|
|
3621
|
+
return normalized;
|
|
3622
|
+
}
|
|
3623
|
+
async dispatchPeerGroupInnerMessage(innerMessageType, innerPayload, context = {}) {
|
|
3624
|
+
if (innerMessageType === "operation") {
|
|
3625
|
+
const operation = this.normalizeGossipOperation(
|
|
3626
|
+
innerPayload,
|
|
3627
|
+
context.publisherId || context.senderId
|
|
3628
|
+
);
|
|
3629
|
+
if (operation) {
|
|
3630
|
+
this.applyOperation(operation);
|
|
3631
|
+
}
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3634
|
+
if (innerMessageType === "record:snapshot") {
|
|
3635
|
+
const { collectionName, record } = innerPayload || {};
|
|
3636
|
+
if (collectionName && record) {
|
|
3637
|
+
const applied = this.restoreRecord(collectionName, record, {
|
|
3638
|
+
rejectOnHashMismatch: true,
|
|
3639
|
+
rejectOnOwnershipMismatch: true,
|
|
3640
|
+
via: "peer-group"
|
|
3641
|
+
});
|
|
3642
|
+
if (applied) {
|
|
3643
|
+
this.emit("change", {
|
|
3644
|
+
kind: "snapshot",
|
|
3645
|
+
collection: collectionName,
|
|
3646
|
+
id: record.id,
|
|
3647
|
+
via: "peer-group",
|
|
3648
|
+
groupId: context.groupId
|
|
3649
|
+
});
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
this.emit("peergroupmessage", {
|
|
3655
|
+
groupId: context.groupId,
|
|
3656
|
+
senderId: context.senderId,
|
|
3657
|
+
type: innerMessageType,
|
|
3658
|
+
payload: innerPayload
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3280
3661
|
getPresenceMap(scope) {
|
|
3281
3662
|
if (!this.presenceByScope.has(scope)) {
|
|
3282
3663
|
this.presenceByScope.set(scope, /* @__PURE__ */ new Map());
|
|
@@ -3407,6 +3788,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
3407
3788
|
}
|
|
3408
3789
|
async handleIncomingMessage(message) {
|
|
3409
3790
|
if (message && message.opId && message.kind) {
|
|
3791
|
+
if (this.securityService.options.enabled) {
|
|
3792
|
+
this.emit("messageignored", {
|
|
3793
|
+
reason: "raw-operation-rejected",
|
|
3794
|
+
hint: "Unsigned raw operations are disabled when security is enabled"
|
|
3795
|
+
});
|
|
3796
|
+
return;
|
|
3797
|
+
}
|
|
3410
3798
|
this.applyOperation(message);
|
|
3411
3799
|
return;
|
|
3412
3800
|
}
|
|
@@ -3417,9 +3805,6 @@ var require_dignity_p2p = __commonJS({
|
|
|
3417
3805
|
});
|
|
3418
3806
|
return;
|
|
3419
3807
|
}
|
|
3420
|
-
if (message && message.senderId && message.senderPublicKey) {
|
|
3421
|
-
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3422
|
-
}
|
|
3423
3808
|
let decrypted;
|
|
3424
3809
|
try {
|
|
3425
3810
|
decrypted = await this.securityService.decryptIncomingMessage(message);
|
|
@@ -3437,6 +3822,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3437
3822
|
if (!decrypted || decrypted.ignored) {
|
|
3438
3823
|
return;
|
|
3439
3824
|
}
|
|
3825
|
+
if (message && message.senderId && message.senderPublicKey) {
|
|
3826
|
+
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3827
|
+
}
|
|
3440
3828
|
if (decrypted.messageType === "operation") {
|
|
3441
3829
|
this.applyOperation(decrypted.payload);
|
|
3442
3830
|
return;
|
|
@@ -3460,18 +3848,27 @@ var require_dignity_p2p = __commonJS({
|
|
|
3460
3848
|
const payload = decrypted.payload || {};
|
|
3461
3849
|
const scope = payload.scope || "main";
|
|
3462
3850
|
const peerId = payload.peerId || decrypted.senderId;
|
|
3463
|
-
if (!peerId) {
|
|
3851
|
+
if (!peerId || peerId !== decrypted.senderId) {
|
|
3464
3852
|
return;
|
|
3465
3853
|
}
|
|
3854
|
+
if (!this.discoveryRooms.has(scope)) {
|
|
3855
|
+
return;
|
|
3856
|
+
}
|
|
3857
|
+
const room = this.discoveryRooms.get(scope);
|
|
3466
3858
|
const presenceMap = this.getPresenceMap(scope);
|
|
3467
3859
|
const isNewPeerInScope = !presenceMap.has(peerId);
|
|
3860
|
+
const requestedTtl = typeof payload.ttlMs === "number" ? payload.ttlMs : room.ttlMs;
|
|
3861
|
+
const ttlMs = Math.min(requestedTtl, room.ttlMs);
|
|
3468
3862
|
this.upsertPresence(
|
|
3469
3863
|
scope,
|
|
3470
3864
|
peerId,
|
|
3471
3865
|
payload.metadata || {},
|
|
3472
|
-
|
|
3473
|
-
|
|
3866
|
+
ttlMs,
|
|
3867
|
+
this.now()
|
|
3474
3868
|
);
|
|
3869
|
+
if (payload.metadata && payload.metadata.publicKey) {
|
|
3870
|
+
this.trustPeerPublicKey(peerId, payload.metadata.publicKey);
|
|
3871
|
+
}
|
|
3475
3872
|
if (isNewPeerInScope && peerId !== this.nodeId && this.discoveryRooms.has(scope)) {
|
|
3476
3873
|
if (typeof this.networkAdapter.connectToPeer === "function") {
|
|
3477
3874
|
Promise.resolve(this.connectToPeer(peerId)).catch((error) => {
|
|
@@ -3488,6 +3885,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3488
3885
|
const payload = decrypted.payload || {};
|
|
3489
3886
|
const scope = payload.scope || "main";
|
|
3490
3887
|
const peerId = payload.peerId || decrypted.senderId;
|
|
3888
|
+
if (!peerId || peerId !== decrypted.senderId) {
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3491
3891
|
const map = this.presenceByScope.get(scope);
|
|
3492
3892
|
if (map && peerId && map.has(peerId)) {
|
|
3493
3893
|
map.delete(peerId);
|
|
@@ -3495,6 +3895,10 @@ var require_dignity_p2p = __commonJS({
|
|
|
3495
3895
|
}
|
|
3496
3896
|
return;
|
|
3497
3897
|
}
|
|
3898
|
+
if (decrypted.messageType === "peer-group:gossip") {
|
|
3899
|
+
await this.handlePeerGroupGossip(decrypted);
|
|
3900
|
+
return;
|
|
3901
|
+
}
|
|
3498
3902
|
this.emit("message", {
|
|
3499
3903
|
senderId: decrypted.senderId,
|
|
3500
3904
|
targetId: decrypted.targetId,
|
|
@@ -3540,7 +3944,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3540
3944
|
emitConflict(details) {
|
|
3541
3945
|
this.emit("conflict", details);
|
|
3542
3946
|
}
|
|
3543
|
-
restoreRecord(collectionName, record) {
|
|
3947
|
+
restoreRecord(collectionName, record, options = {}) {
|
|
3544
3948
|
if (!record || !record.id) {
|
|
3545
3949
|
return false;
|
|
3546
3950
|
}
|
|
@@ -3549,11 +3953,51 @@ var require_dignity_p2p = __commonJS({
|
|
|
3549
3953
|
if (current && current.version >= record.version) {
|
|
3550
3954
|
return false;
|
|
3551
3955
|
}
|
|
3956
|
+
const restoredData = { ...record.data || {} };
|
|
3957
|
+
const computedHash = computeContentHash(restoredData);
|
|
3958
|
+
const rejectOnHashMismatch = options.rejectOnHashMismatch === true;
|
|
3959
|
+
const rejectOnOwnershipMismatch = options.rejectOnOwnershipMismatch === true;
|
|
3960
|
+
if (rejectOnOwnershipMismatch && current && record.ownerId && current.ownerId !== record.ownerId) {
|
|
3961
|
+
this.emit("warning", {
|
|
3962
|
+
type: "ownership-mismatch",
|
|
3963
|
+
collection: collectionName,
|
|
3964
|
+
id: record.id,
|
|
3965
|
+
currentOwnerId: current.ownerId,
|
|
3966
|
+
advertisedOwnerId: record.ownerId,
|
|
3967
|
+
via: options.via || null
|
|
3968
|
+
});
|
|
3969
|
+
return false;
|
|
3970
|
+
}
|
|
3971
|
+
if (!record.hash) {
|
|
3972
|
+
const warning = {
|
|
3973
|
+
type: "content-hash-missing",
|
|
3974
|
+
collection: collectionName,
|
|
3975
|
+
id: record.id,
|
|
3976
|
+
via: options.via || null
|
|
3977
|
+
};
|
|
3978
|
+
this.emit("warning", warning);
|
|
3979
|
+
if (rejectOnHashMismatch) {
|
|
3980
|
+
return false;
|
|
3981
|
+
}
|
|
3982
|
+
} else if (record.hash !== computedHash) {
|
|
3983
|
+
this.emit("warning", {
|
|
3984
|
+
type: "content-hash-mismatch",
|
|
3985
|
+
collection: collectionName,
|
|
3986
|
+
id: record.id,
|
|
3987
|
+
advertisedHash: record.hash,
|
|
3988
|
+
computedHash,
|
|
3989
|
+
via: options.via || null
|
|
3990
|
+
});
|
|
3991
|
+
if (rejectOnHashMismatch) {
|
|
3992
|
+
return false;
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3552
3995
|
collection.set(record.id, {
|
|
3553
3996
|
id: record.id,
|
|
3554
3997
|
ownerId: record.ownerId,
|
|
3555
3998
|
collaboratorIds: this.normalizeCollaboratorIds(record.collaboratorIds),
|
|
3556
|
-
data:
|
|
3999
|
+
data: restoredData,
|
|
4000
|
+
hash: computedHash,
|
|
3557
4001
|
createdAt: record.createdAt,
|
|
3558
4002
|
updatedAt: record.updatedAt,
|
|
3559
4003
|
deletedAt: record.deletedAt || null,
|
|
@@ -3571,7 +4015,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3571
4015
|
id: raw.id,
|
|
3572
4016
|
ownerId: raw.ownerId,
|
|
3573
4017
|
collaboratorIds: Array.isArray(raw.collaboratorIds) ? [...raw.collaboratorIds] : [],
|
|
3574
|
-
data: { ...raw.data },
|
|
4018
|
+
data: { ...raw.data || {} },
|
|
4019
|
+
hash: raw.hash || computeContentHash(raw.data || {}),
|
|
3575
4020
|
createdAt: raw.createdAt,
|
|
3576
4021
|
updatedAt: raw.updatedAt,
|
|
3577
4022
|
deletedAt: raw.deletedAt || null,
|
|
@@ -3587,6 +4032,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3587
4032
|
});
|
|
3588
4033
|
return record;
|
|
3589
4034
|
}
|
|
4035
|
+
pruneAppliedOperations() {
|
|
4036
|
+
while (this.appliedOperations.size > this.maxAppliedOperations) {
|
|
4037
|
+
const oldestOpId = this.appliedOperations.keys().next().value;
|
|
4038
|
+
if (!oldestOpId) {
|
|
4039
|
+
break;
|
|
4040
|
+
}
|
|
4041
|
+
this.appliedOperations.delete(oldestOpId);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
3590
4044
|
applyOperation(operation) {
|
|
3591
4045
|
if (!operation || !operation.opId || this.appliedOperations.has(operation.opId)) {
|
|
3592
4046
|
return false;
|
|
@@ -3601,13 +4055,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3601
4055
|
id: operation.id,
|
|
3602
4056
|
ownerId: operation.ownerId,
|
|
3603
4057
|
collaboratorIds: this.normalizeCollaboratorIds(operation.collaboratorIds),
|
|
3604
|
-
data: { ...operation.payload },
|
|
4058
|
+
data: { ...operation.payload || {} },
|
|
4059
|
+
hash: computeContentHash(operation.payload || {}),
|
|
3605
4060
|
createdAt: operation.timestamp,
|
|
3606
4061
|
updatedAt: operation.timestamp,
|
|
3607
4062
|
deletedAt: null,
|
|
3608
4063
|
version: 1
|
|
3609
4064
|
});
|
|
3610
|
-
this.appliedOperations.
|
|
4065
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4066
|
+
this.pruneAppliedOperations();
|
|
3611
4067
|
this.emit("change", { kind: "create", collection: operation.collectionName, id: operation.id });
|
|
3612
4068
|
return true;
|
|
3613
4069
|
}
|
|
@@ -3651,7 +4107,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3651
4107
|
}
|
|
3652
4108
|
current.updatedAt = operation.timestamp;
|
|
3653
4109
|
current.version += 1;
|
|
3654
|
-
this.appliedOperations.
|
|
4110
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4111
|
+
this.pruneAppliedOperations();
|
|
3655
4112
|
this.emit("change", {
|
|
3656
4113
|
kind: "transfer-ownership",
|
|
3657
4114
|
collection: operation.collectionName,
|
|
@@ -3680,7 +4137,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3680
4137
|
current.deletedAt = operation.timestamp;
|
|
3681
4138
|
current.updatedAt = operation.timestamp;
|
|
3682
4139
|
current.version += 1;
|
|
3683
|
-
this.appliedOperations.
|
|
4140
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4141
|
+
this.pruneAppliedOperations();
|
|
3684
4142
|
this.emit("change", { kind: "delete", collection: operation.collectionName, id: operation.id });
|
|
3685
4143
|
return true;
|
|
3686
4144
|
}
|
|
@@ -3704,12 +4162,14 @@ var require_dignity_p2p = __commonJS({
|
|
|
3704
4162
|
...current.data,
|
|
3705
4163
|
...operation.payload
|
|
3706
4164
|
};
|
|
4165
|
+
current.hash = computeContentHash(current.data);
|
|
3707
4166
|
if (Array.isArray(operation.collaboratorIds) && operation.actorId === current.ownerId) {
|
|
3708
4167
|
current.collaboratorIds = this.normalizeCollaboratorIds(operation.collaboratorIds);
|
|
3709
4168
|
}
|
|
3710
4169
|
current.updatedAt = operation.timestamp;
|
|
3711
4170
|
current.version += 1;
|
|
3712
|
-
this.appliedOperations.
|
|
4171
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4172
|
+
this.pruneAppliedOperations();
|
|
3713
4173
|
this.emit("change", { kind: "update", collection: operation.collectionName, id: operation.id });
|
|
3714
4174
|
return true;
|
|
3715
4175
|
}
|
|
@@ -3789,6 +4249,14 @@ var require_signaling_pool = __commonJS({
|
|
|
3789
4249
|
// src/signaling/websocket-signaling-provider.js
|
|
3790
4250
|
var require_websocket_signaling_provider = __commonJS({
|
|
3791
4251
|
"src/signaling/websocket-signaling-provider.js"(exports, module) {
|
|
4252
|
+
function randomBase36(length) {
|
|
4253
|
+
let value = "";
|
|
4254
|
+
while (value.length < length) {
|
|
4255
|
+
const chunk = Math.random().toString(36).slice(2);
|
|
4256
|
+
value += chunk.length > 0 ? chunk : "0";
|
|
4257
|
+
}
|
|
4258
|
+
return value.slice(0, length);
|
|
4259
|
+
}
|
|
3792
4260
|
var WebSocketSignalingProvider = class {
|
|
3793
4261
|
constructor({ id, url, WebSocketImpl, priority = 0 }) {
|
|
3794
4262
|
if (!url) {
|
|
@@ -3832,8 +4300,8 @@ var require_websocket_signaling_provider = __commonJS({
|
|
|
3832
4300
|
if (!peerJsHostPattern.test(this.url)) {
|
|
3833
4301
|
return this.url;
|
|
3834
4302
|
}
|
|
3835
|
-
const connectionId = `dignityjs_${
|
|
3836
|
-
const token =
|
|
4303
|
+
const connectionId = `dignityjs_${randomBase36(10)}`;
|
|
4304
|
+
const token = randomBase36(10);
|
|
3837
4305
|
const hasQuery = this.url.includes("?");
|
|
3838
4306
|
const hasId = /[?&]id=/.test(this.url);
|
|
3839
4307
|
const hasToken = /[?&]token=/.test(this.url);
|
|
@@ -10749,6 +11217,16 @@ var require_in_memory_network = __commonJS({
|
|
|
10749
11217
|
}
|
|
10750
11218
|
await Promise.all(deliveries);
|
|
10751
11219
|
}
|
|
11220
|
+
async sendToPeers(senderId, message, peerIds = []) {
|
|
11221
|
+
const targets = new Set((peerIds || []).filter((peerId) => peerId && peerId !== senderId));
|
|
11222
|
+
const deliveries = [];
|
|
11223
|
+
for (const [nodeId, adapter] of this.adapters.entries()) {
|
|
11224
|
+
if (nodeId !== senderId && targets.has(nodeId)) {
|
|
11225
|
+
deliveries.push(adapter.receive(message));
|
|
11226
|
+
}
|
|
11227
|
+
}
|
|
11228
|
+
await Promise.all(deliveries);
|
|
11229
|
+
}
|
|
10752
11230
|
};
|
|
10753
11231
|
var InMemoryNetworkAdapter = class {
|
|
10754
11232
|
constructor(hub) {
|
|
@@ -10758,6 +11236,7 @@ var require_in_memory_network = __commonJS({
|
|
|
10758
11236
|
this.hub = hub;
|
|
10759
11237
|
this.nodeId = null;
|
|
10760
11238
|
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
11239
|
+
this.connectedPeers = /* @__PURE__ */ new Set();
|
|
10761
11240
|
}
|
|
10762
11241
|
async start(nodeId) {
|
|
10763
11242
|
this.nodeId = nodeId;
|
|
@@ -10768,6 +11247,13 @@ var require_in_memory_network = __commonJS({
|
|
|
10768
11247
|
this.hub.unregister(this.nodeId);
|
|
10769
11248
|
}
|
|
10770
11249
|
this.nodeId = null;
|
|
11250
|
+
this.connectedPeers.clear();
|
|
11251
|
+
}
|
|
11252
|
+
async connectToPeer(remotePeerId) {
|
|
11253
|
+
if (!remotePeerId || remotePeerId === this.nodeId) {
|
|
11254
|
+
return;
|
|
11255
|
+
}
|
|
11256
|
+
this.connectedPeers.add(remotePeerId);
|
|
10771
11257
|
}
|
|
10772
11258
|
async broadcast(message) {
|
|
10773
11259
|
if (!this.nodeId) {
|
|
@@ -10775,6 +11261,21 @@ var require_in_memory_network = __commonJS({
|
|
|
10775
11261
|
}
|
|
10776
11262
|
await this.hub.broadcast(this.nodeId, message);
|
|
10777
11263
|
}
|
|
11264
|
+
async sendToPeers(message, peerIds = []) {
|
|
11265
|
+
if (!this.nodeId) {
|
|
11266
|
+
throw new Error("Network adapter has not been started");
|
|
11267
|
+
}
|
|
11268
|
+
await this.hub.sendToPeers(this.nodeId, message, peerIds);
|
|
11269
|
+
}
|
|
11270
|
+
listOpenPeerIds() {
|
|
11271
|
+
return [...this.connectedPeers];
|
|
11272
|
+
}
|
|
11273
|
+
getOpenConnectionCount() {
|
|
11274
|
+
return this.connectedPeers.size;
|
|
11275
|
+
}
|
|
11276
|
+
isConnectedTo(remotePeerId) {
|
|
11277
|
+
return this.connectedPeers.has(remotePeerId);
|
|
11278
|
+
}
|
|
10778
11279
|
onMessage(handler) {
|
|
10779
11280
|
this.messageHandlers.add(handler);
|
|
10780
11281
|
}
|
|
@@ -10953,6 +11454,29 @@ var require_peerjs_network = __commonJS({
|
|
|
10953
11454
|
}
|
|
10954
11455
|
await Promise.all(deliveries);
|
|
10955
11456
|
}
|
|
11457
|
+
async sendToPeers(message, peerIds = []) {
|
|
11458
|
+
if (!this.peer) {
|
|
11459
|
+
throw new Error("PeerJS network adapter has not been started");
|
|
11460
|
+
}
|
|
11461
|
+
const targets = new Set((peerIds || []).filter(Boolean));
|
|
11462
|
+
if (targets.size === 0) {
|
|
11463
|
+
return;
|
|
11464
|
+
}
|
|
11465
|
+
const deliveries = [];
|
|
11466
|
+
for (const [peerId, connection] of this.connections.entries()) {
|
|
11467
|
+
if (targets.has(peerId) && connection.open) {
|
|
11468
|
+
deliveries.push(connection.send(message));
|
|
11469
|
+
}
|
|
11470
|
+
}
|
|
11471
|
+
await Promise.all(deliveries);
|
|
11472
|
+
}
|
|
11473
|
+
async disconnectPeer(remotePeerId) {
|
|
11474
|
+
const connection = this.connections.get(remotePeerId);
|
|
11475
|
+
if (connection && typeof connection.close === "function") {
|
|
11476
|
+
connection.close();
|
|
11477
|
+
}
|
|
11478
|
+
this.connections.delete(remotePeerId);
|
|
11479
|
+
}
|
|
10956
11480
|
getOpenConnectionCount() {
|
|
10957
11481
|
return this.listOpenPeerIds().length;
|
|
10958
11482
|
}
|
|
@@ -11059,6 +11583,7 @@ var require_indexeddb_persistence = __commonJS({
|
|
|
11059
11583
|
ownerId: record.ownerId,
|
|
11060
11584
|
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
11061
11585
|
data: { ...record.data },
|
|
11586
|
+
hash: record.hash || null,
|
|
11062
11587
|
createdAt: record.createdAt,
|
|
11063
11588
|
updatedAt: record.updatedAt,
|
|
11064
11589
|
deletedAt: record.deletedAt,
|
|
@@ -11119,6 +11644,7 @@ var require_indexeddb_persistence = __commonJS({
|
|
|
11119
11644
|
ownerId: stored.ownerId,
|
|
11120
11645
|
collaboratorIds: stored.collaboratorIds,
|
|
11121
11646
|
data: stored.data,
|
|
11647
|
+
hash: stored.hash || null,
|
|
11122
11648
|
createdAt: stored.createdAt,
|
|
11123
11649
|
updatedAt: stored.updatedAt,
|
|
11124
11650
|
deletedAt: stored.deletedAt,
|
|
@@ -11181,6 +11707,13 @@ var require_index = __commonJS({
|
|
|
11181
11707
|
MessageSecurityService,
|
|
11182
11708
|
DEFAULT_SECURITY_OPTIONS
|
|
11183
11709
|
} = require_message_security_service();
|
|
11710
|
+
var {
|
|
11711
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
11712
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11713
|
+
peerGroupScope,
|
|
11714
|
+
parsePeerGroupScope,
|
|
11715
|
+
selectFanoutPeers
|
|
11716
|
+
} = require_peer_group();
|
|
11184
11717
|
module.exports = {
|
|
11185
11718
|
DignityP2P,
|
|
11186
11719
|
createDefaultSignalingPool,
|
|
@@ -11197,7 +11730,12 @@ var require_index = __commonJS({
|
|
|
11197
11730
|
VDF,
|
|
11198
11731
|
SlothPermutation,
|
|
11199
11732
|
MessageSecurityService,
|
|
11200
|
-
DEFAULT_SECURITY_OPTIONS
|
|
11733
|
+
DEFAULT_SECURITY_OPTIONS,
|
|
11734
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
11735
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11736
|
+
peerGroupScope,
|
|
11737
|
+
parsePeerGroupScope,
|
|
11738
|
+
selectFanoutPeers
|
|
11201
11739
|
};
|
|
11202
11740
|
}
|
|
11203
11741
|
});
|