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.cjs.js
CHANGED
|
@@ -3,43 +3,6 @@ var __commonJS = (cb, mod) => function __require() {
|
|
|
3
3
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
4
4
|
};
|
|
5
5
|
|
|
6
|
-
// src/utils/event-emitter.js
|
|
7
|
-
var require_event_emitter = __commonJS({
|
|
8
|
-
"src/utils/event-emitter.js"(exports2, module2) {
|
|
9
|
-
var EventEmitter = class {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.handlers = /* @__PURE__ */ new Map();
|
|
12
|
-
}
|
|
13
|
-
on(eventName, handler) {
|
|
14
|
-
if (!this.handlers.has(eventName)) {
|
|
15
|
-
this.handlers.set(eventName, /* @__PURE__ */ new Set());
|
|
16
|
-
}
|
|
17
|
-
this.handlers.get(eventName).add(handler);
|
|
18
|
-
}
|
|
19
|
-
off(eventName, handler) {
|
|
20
|
-
const eventHandlers = this.handlers.get(eventName);
|
|
21
|
-
if (!eventHandlers) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
eventHandlers.delete(handler);
|
|
25
|
-
if (eventHandlers.size === 0) {
|
|
26
|
-
this.handlers.delete(eventName);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
emit(eventName, payload) {
|
|
30
|
-
const eventHandlers = this.handlers.get(eventName);
|
|
31
|
-
if (!eventHandlers) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
for (const handler of eventHandlers) {
|
|
35
|
-
handler(payload);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
module2.exports = EventEmitter;
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
6
|
// node_modules/tweetnacl/nacl-fast.js
|
|
44
7
|
var require_nacl_fast = __commonJS({
|
|
45
8
|
"node_modules/tweetnacl/nacl-fast.js"(exports2, module2) {
|
|
@@ -2330,6 +2293,43 @@ var require_nacl_util = __commonJS({
|
|
|
2330
2293
|
}
|
|
2331
2294
|
});
|
|
2332
2295
|
|
|
2296
|
+
// src/utils/event-emitter.js
|
|
2297
|
+
var require_event_emitter = __commonJS({
|
|
2298
|
+
"src/utils/event-emitter.js"(exports2, module2) {
|
|
2299
|
+
var EventEmitter = class {
|
|
2300
|
+
constructor() {
|
|
2301
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
2302
|
+
}
|
|
2303
|
+
on(eventName, handler) {
|
|
2304
|
+
if (!this.handlers.has(eventName)) {
|
|
2305
|
+
this.handlers.set(eventName, /* @__PURE__ */ new Set());
|
|
2306
|
+
}
|
|
2307
|
+
this.handlers.get(eventName).add(handler);
|
|
2308
|
+
}
|
|
2309
|
+
off(eventName, handler) {
|
|
2310
|
+
const eventHandlers = this.handlers.get(eventName);
|
|
2311
|
+
if (!eventHandlers) {
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
eventHandlers.delete(handler);
|
|
2315
|
+
if (eventHandlers.size === 0) {
|
|
2316
|
+
this.handlers.delete(eventName);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
emit(eventName, payload) {
|
|
2320
|
+
const eventHandlers = this.handlers.get(eventName);
|
|
2321
|
+
if (!eventHandlers) {
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
for (const handler of eventHandlers) {
|
|
2325
|
+
handler(payload);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
};
|
|
2329
|
+
module2.exports = EventEmitter;
|
|
2330
|
+
}
|
|
2331
|
+
});
|
|
2332
|
+
|
|
2333
2333
|
// src/security/sloth-vdf.js
|
|
2334
2334
|
var require_sloth_vdf = __commonJS({
|
|
2335
2335
|
"src/security/sloth-vdf.js"(exports2, module2) {
|
|
@@ -2337,6 +2337,12 @@ var require_sloth_vdf = __commonJS({
|
|
|
2337
2337
|
static p = BigInt(
|
|
2338
2338
|
"170082004324204494273811327264862981553264701145937538369570764779791492622392118654022654452947093285873855529044371650895045691292912712699015605832276411308653107069798639938826015099738961427172366594187783204437869906954750443653318078358839409699824714551430573905637228307966826784684174483831608534979"
|
|
2339
2339
|
);
|
|
2340
|
+
// precompute values for optimization:
|
|
2341
|
+
// (p - 1) / 2
|
|
2342
|
+
static pHalf = _SlothPermutation.p - BigInt(1) >> BigInt(1);
|
|
2343
|
+
// (p + 1) / 4
|
|
2344
|
+
// p ≡ 3 (mod 4) ⇒ (p+1) divisible by 4
|
|
2345
|
+
static pQuarter = _SlothPermutation.p + BigInt(1) >> BigInt(2);
|
|
2340
2346
|
fastPow(base, exponent, modulus) {
|
|
2341
2347
|
if (modulus === BigInt(1)) {
|
|
2342
2348
|
return BigInt(0);
|
|
@@ -2345,25 +2351,25 @@ var require_sloth_vdf = __commonJS({
|
|
|
2345
2351
|
let powBase = base % modulus;
|
|
2346
2352
|
let powExponent = exponent;
|
|
2347
2353
|
while (powExponent > 0) {
|
|
2348
|
-
if (powExponent
|
|
2354
|
+
if ((powExponent & BigInt(1)) === BigInt(1)) {
|
|
2349
2355
|
result = result * powBase % modulus;
|
|
2350
2356
|
}
|
|
2351
|
-
powExponent = powExponent
|
|
2357
|
+
powExponent = powExponent >> BigInt(1);
|
|
2352
2358
|
powBase = powBase * powBase % modulus;
|
|
2353
2359
|
}
|
|
2354
2360
|
return result;
|
|
2355
2361
|
}
|
|
2356
2362
|
quadRes(x) {
|
|
2357
|
-
return this.fastPow(x,
|
|
2363
|
+
return this.fastPow(x, _SlothPermutation.pHalf, _SlothPermutation.p) === BigInt(1);
|
|
2358
2364
|
}
|
|
2359
2365
|
modSqrtOp(x) {
|
|
2360
2366
|
let y;
|
|
2361
2367
|
let value = x;
|
|
2362
2368
|
if (this.quadRes(value)) {
|
|
2363
|
-
y = this.fastPow(value,
|
|
2369
|
+
y = this.fastPow(value, _SlothPermutation.pQuarter, _SlothPermutation.p);
|
|
2364
2370
|
} else {
|
|
2365
2371
|
value = (-value + _SlothPermutation.p) % _SlothPermutation.p;
|
|
2366
|
-
y = this.fastPow(value,
|
|
2372
|
+
y = this.fastPow(value, _SlothPermutation.pQuarter, _SlothPermutation.p);
|
|
2367
2373
|
}
|
|
2368
2374
|
return y;
|
|
2369
2375
|
}
|
|
@@ -2743,8 +2749,14 @@ var require_message_security_service = __commonJS({
|
|
|
2743
2749
|
const nonce = naclUtil.decodeBase64(encryption.nonce);
|
|
2744
2750
|
let key;
|
|
2745
2751
|
if (encryption.kdf === "pbkdf2") {
|
|
2746
|
-
const
|
|
2747
|
-
|
|
2752
|
+
const configuredIterations = this.options.kdfIterations || DEFAULT_SECURITY_OPTIONS2.kdfIterations;
|
|
2753
|
+
const requestedIterations = encryption.kdfIterations || configuredIterations;
|
|
2754
|
+
const minIterations = Math.max(1e3, Math.floor(configuredIterations * 0.1));
|
|
2755
|
+
const maxIterations = configuredIterations * 2;
|
|
2756
|
+
if (requestedIterations < minIterations || requestedIterations > maxIterations) {
|
|
2757
|
+
throw new Error(`Invalid kdfIterations: ${requestedIterations}`);
|
|
2758
|
+
}
|
|
2759
|
+
key = await deriveBroadcastKey(password, salt, requestedIterations);
|
|
2748
2760
|
} else {
|
|
2749
2761
|
key = legacyBroadcastKey(password, salt);
|
|
2750
2762
|
}
|
|
@@ -2848,11 +2860,84 @@ var require_message_security_service = __commonJS({
|
|
|
2848
2860
|
}
|
|
2849
2861
|
});
|
|
2850
2862
|
|
|
2863
|
+
// src/gossip/peer-group.js
|
|
2864
|
+
var require_peer_group = __commonJS({
|
|
2865
|
+
"src/gossip/peer-group.js"(exports2, module2) {
|
|
2866
|
+
var PEER_GROUP_SCOPE_PREFIX2 = "gossip:";
|
|
2867
|
+
var DEFAULT_PEER_GROUP_OPTIONS2 = {
|
|
2868
|
+
fanout: 3,
|
|
2869
|
+
maxActivePeers: 8,
|
|
2870
|
+
maxHops: 6,
|
|
2871
|
+
relayEnabled: true
|
|
2872
|
+
};
|
|
2873
|
+
function peerGroupScope2(groupId) {
|
|
2874
|
+
if (!groupId) {
|
|
2875
|
+
throw new Error("peerGroupScope requires groupId");
|
|
2876
|
+
}
|
|
2877
|
+
return `${PEER_GROUP_SCOPE_PREFIX2}${groupId}`;
|
|
2878
|
+
}
|
|
2879
|
+
function parsePeerGroupScope2(scope) {
|
|
2880
|
+
if (!scope || !scope.startsWith(PEER_GROUP_SCOPE_PREFIX2)) {
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
return scope.slice(PEER_GROUP_SCOPE_PREFIX2.length);
|
|
2884
|
+
}
|
|
2885
|
+
function shufflePeerIds(peerIds, randomFn = Math.random) {
|
|
2886
|
+
const list = [...peerIds];
|
|
2887
|
+
for (let i = list.length - 1; i > 0; i -= 1) {
|
|
2888
|
+
const j = Math.floor(randomFn() * (i + 1));
|
|
2889
|
+
[list[i], list[j]] = [list[j], list[i]];
|
|
2890
|
+
}
|
|
2891
|
+
return list;
|
|
2892
|
+
}
|
|
2893
|
+
function selectFanoutPeers2({
|
|
2894
|
+
peers,
|
|
2895
|
+
count,
|
|
2896
|
+
excludePeerIds = [],
|
|
2897
|
+
connectedPeerIds = [],
|
|
2898
|
+
randomFn = Math.random
|
|
2899
|
+
}) {
|
|
2900
|
+
const excluded = new Set(excludePeerIds.filter(Boolean));
|
|
2901
|
+
const candidates = peers.map((entry) => entry.peerId || entry).filter((peerId) => peerId && !excluded.has(peerId));
|
|
2902
|
+
const connected = new Set(connectedPeerIds.filter(Boolean));
|
|
2903
|
+
const preferred = candidates.filter((peerId) => connected.has(peerId));
|
|
2904
|
+
const others = candidates.filter((peerId) => !connected.has(peerId));
|
|
2905
|
+
const ordered = [
|
|
2906
|
+
...shufflePeerIds(preferred, randomFn),
|
|
2907
|
+
...shufflePeerIds(others, randomFn)
|
|
2908
|
+
];
|
|
2909
|
+
return ordered.slice(0, Math.max(0, count));
|
|
2910
|
+
}
|
|
2911
|
+
module2.exports = {
|
|
2912
|
+
PEER_GROUP_SCOPE_PREFIX: PEER_GROUP_SCOPE_PREFIX2,
|
|
2913
|
+
DEFAULT_PEER_GROUP_OPTIONS: DEFAULT_PEER_GROUP_OPTIONS2,
|
|
2914
|
+
peerGroupScope: peerGroupScope2,
|
|
2915
|
+
parsePeerGroupScope: parsePeerGroupScope2,
|
|
2916
|
+
shufflePeerIds,
|
|
2917
|
+
selectFanoutPeers: selectFanoutPeers2
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
});
|
|
2921
|
+
|
|
2851
2922
|
// src/core/dignity-p2p.js
|
|
2852
2923
|
var require_dignity_p2p = __commonJS({
|
|
2853
2924
|
"src/core/dignity-p2p.js"(exports2, module2) {
|
|
2925
|
+
var nacl = require_nacl_fast();
|
|
2926
|
+
var naclUtil = require_nacl_util();
|
|
2854
2927
|
var EventEmitter = require_event_emitter();
|
|
2855
|
-
var { MessageSecurityService: MessageSecurityService2 } = require_message_security_service();
|
|
2928
|
+
var { MessageSecurityService: MessageSecurityService2, stableStringify } = require_message_security_service();
|
|
2929
|
+
var {
|
|
2930
|
+
DEFAULT_PEER_GROUP_OPTIONS: DEFAULT_PEER_GROUP_OPTIONS2,
|
|
2931
|
+
peerGroupScope: peerGroupScope2,
|
|
2932
|
+
selectFanoutPeers: selectFanoutPeers2
|
|
2933
|
+
} = require_peer_group();
|
|
2934
|
+
function computeContentHash(data) {
|
|
2935
|
+
const canonical = stableStringify(data || {});
|
|
2936
|
+
const bytes = naclUtil.decodeUTF8(canonical);
|
|
2937
|
+
const hash = nacl.hash(bytes);
|
|
2938
|
+
const hex = Array.from(hash, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2939
|
+
return `sha512:${hex}`;
|
|
2940
|
+
}
|
|
2856
2941
|
var DignityP2P2 = class extends EventEmitter {
|
|
2857
2942
|
constructor({ nodeId, networkAdapter, idGenerator, now, security } = {}) {
|
|
2858
2943
|
super();
|
|
@@ -2878,8 +2963,16 @@ var require_dignity_p2p = __commonJS({
|
|
|
2878
2963
|
this.defaultPresenceTtlMs = security && typeof security.presenceTtlMs === "number" ? security.presenceTtlMs : 45e3;
|
|
2879
2964
|
this.discoveryRooms = /* @__PURE__ */ new Map();
|
|
2880
2965
|
this.presenceByScope = /* @__PURE__ */ new Map();
|
|
2966
|
+
this.peerGroups = /* @__PURE__ */ new Map();
|
|
2967
|
+
this.seenGossipIds = /* @__PURE__ */ new Map();
|
|
2968
|
+
this.defaultPeerGroupFanout = security && typeof security.peerGroupFanout === "number" ? security.peerGroupFanout : DEFAULT_PEER_GROUP_OPTIONS2.fanout;
|
|
2969
|
+
this.defaultPeerGroupMaxActivePeers = security && typeof security.peerGroupMaxActivePeers === "number" ? security.peerGroupMaxActivePeers : DEFAULT_PEER_GROUP_OPTIONS2.maxActivePeers;
|
|
2970
|
+
this.defaultGossipMaxHops = security && typeof security.gossipMaxHops === "number" ? security.gossipMaxHops : DEFAULT_PEER_GROUP_OPTIONS2.maxHops;
|
|
2971
|
+
this.globalMaxOpenConnections = security && typeof security.globalMaxOpenConnections === "number" ? security.globalMaxOpenConnections : 32;
|
|
2972
|
+
this.gossipIdTtlMs = security && typeof security.gossipIdTtlMs === "number" ? security.gossipIdTtlMs : 5 * 60 * 1e3;
|
|
2973
|
+
this.maxAppliedOperations = security && typeof security.maxAppliedOperations === "number" ? security.maxAppliedOperations : 5e4;
|
|
2881
2974
|
this.state = /* @__PURE__ */ new Map();
|
|
2882
|
-
this.appliedOperations = /* @__PURE__ */ new
|
|
2975
|
+
this.appliedOperations = /* @__PURE__ */ new Map();
|
|
2883
2976
|
this.boundMessageHandler = this.handleIncomingMessage.bind(this);
|
|
2884
2977
|
}
|
|
2885
2978
|
async start() {
|
|
@@ -2887,6 +2980,14 @@ var require_dignity_p2p = __commonJS({
|
|
|
2887
2980
|
await this.networkAdapter.start(this.nodeId);
|
|
2888
2981
|
}
|
|
2889
2982
|
async stop() {
|
|
2983
|
+
const joinedGroups = Array.from(this.peerGroups.keys());
|
|
2984
|
+
for (const groupId of joinedGroups) {
|
|
2985
|
+
try {
|
|
2986
|
+
await this.leavePeerGroup(groupId);
|
|
2987
|
+
} catch (error) {
|
|
2988
|
+
this.emit("warning", { type: "peer-group-leave-failed", groupId, error });
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2890
2991
|
const joinedScopes = Array.from(this.discoveryRooms.keys());
|
|
2891
2992
|
for (const scope of joinedScopes) {
|
|
2892
2993
|
try {
|
|
@@ -2911,6 +3012,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
2911
3012
|
if (!record || record.deletedAt) {
|
|
2912
3013
|
return null;
|
|
2913
3014
|
}
|
|
3015
|
+
const normalizedData = { ...record.data || {} };
|
|
2914
3016
|
return {
|
|
2915
3017
|
id: record.id,
|
|
2916
3018
|
ownerId: record.ownerId,
|
|
@@ -2918,7 +3020,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
2918
3020
|
createdAt: record.createdAt,
|
|
2919
3021
|
updatedAt: record.updatedAt,
|
|
2920
3022
|
version: record.version,
|
|
2921
|
-
|
|
3023
|
+
hash: record.hash || computeContentHash(normalizedData),
|
|
3024
|
+
data: normalizedData
|
|
2922
3025
|
};
|
|
2923
3026
|
}
|
|
2924
3027
|
canUpdateRecord(record, actorId) {
|
|
@@ -2996,7 +3099,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
2996
3099
|
ownerId: this.nodeId,
|
|
2997
3100
|
collaboratorIds,
|
|
2998
3101
|
timestamp,
|
|
2999
|
-
payload: { ...data }
|
|
3102
|
+
payload: { ...data || {} }
|
|
3000
3103
|
};
|
|
3001
3104
|
this.applyOperation(operation);
|
|
3002
3105
|
await this.broadcastMessage("operation", operation, {
|
|
@@ -3233,6 +3336,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3233
3336
|
const connectToPeers = securityContext.connectToPeers;
|
|
3234
3337
|
if (Array.isArray(connectToPeers) && connectToPeers.length > 0) {
|
|
3235
3338
|
await this.ensureConnectedToPeers(connectToPeers);
|
|
3339
|
+
await this.enforceConnectionBudget();
|
|
3236
3340
|
}
|
|
3237
3341
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3238
3342
|
messageType,
|
|
@@ -3240,6 +3344,11 @@ var require_dignity_p2p = __commonJS({
|
|
|
3240
3344
|
targetId: null,
|
|
3241
3345
|
securityContext
|
|
3242
3346
|
});
|
|
3347
|
+
const fanoutPeerIds = securityContext.fanoutPeerIds;
|
|
3348
|
+
if (Array.isArray(fanoutPeerIds) && fanoutPeerIds.length > 0 && typeof this.networkAdapter.sendToPeers === "function") {
|
|
3349
|
+
await this.networkAdapter.sendToPeers(envelope, fanoutPeerIds);
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3243
3352
|
await this.networkAdapter.broadcast(envelope);
|
|
3244
3353
|
}
|
|
3245
3354
|
async sendDirectMessage(targetId, messageType, payload) {
|
|
@@ -3255,8 +3364,280 @@ var require_dignity_p2p = __commonJS({
|
|
|
3255
3364
|
payload,
|
|
3256
3365
|
targetId
|
|
3257
3366
|
});
|
|
3367
|
+
if (targetId && typeof this.networkAdapter.sendToPeers === "function") {
|
|
3368
|
+
await this.networkAdapter.sendToPeers(envelope, [targetId]);
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3258
3371
|
await this.networkAdapter.broadcast(envelope);
|
|
3259
3372
|
}
|
|
3373
|
+
peerGroupScopeFor(groupId) {
|
|
3374
|
+
return peerGroupScope2(groupId);
|
|
3375
|
+
}
|
|
3376
|
+
getPeerGroupConfig(groupId) {
|
|
3377
|
+
return this.peerGroups.get(groupId) || null;
|
|
3378
|
+
}
|
|
3379
|
+
listPeerGroupMembers(groupId, options = {}) {
|
|
3380
|
+
return this.listPeers(this.peerGroupScopeFor(groupId), options);
|
|
3381
|
+
}
|
|
3382
|
+
getPeerGroupStats() {
|
|
3383
|
+
const adapter = this.networkAdapter;
|
|
3384
|
+
const openPeerIds = typeof adapter.listOpenPeerIds === "function" ? adapter.listOpenPeerIds() : [];
|
|
3385
|
+
return {
|
|
3386
|
+
joinedGroups: Array.from(this.peerGroups.keys()),
|
|
3387
|
+
seenGossipCount: this.seenGossipIds.size,
|
|
3388
|
+
openConnectionCount: openPeerIds.length,
|
|
3389
|
+
globalMaxOpenConnections: this.globalMaxOpenConnections
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
pruneSeenGossip() {
|
|
3393
|
+
const now = this.now();
|
|
3394
|
+
for (const [gossipId, expiresAt] of this.seenGossipIds.entries()) {
|
|
3395
|
+
if (expiresAt <= now) {
|
|
3396
|
+
this.seenGossipIds.delete(gossipId);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
hasSeenGossip(gossipId) {
|
|
3401
|
+
if (!gossipId) {
|
|
3402
|
+
return false;
|
|
3403
|
+
}
|
|
3404
|
+
this.pruneSeenGossip();
|
|
3405
|
+
return this.seenGossipIds.has(gossipId);
|
|
3406
|
+
}
|
|
3407
|
+
markSeenGossip(gossipId) {
|
|
3408
|
+
if (!gossipId) {
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
this.seenGossipIds.set(gossipId, this.now() + this.gossipIdTtlMs);
|
|
3412
|
+
}
|
|
3413
|
+
listConnectedPeerIds() {
|
|
3414
|
+
if (typeof this.networkAdapter.listOpenPeerIds === "function") {
|
|
3415
|
+
return this.networkAdapter.listOpenPeerIds();
|
|
3416
|
+
}
|
|
3417
|
+
return [];
|
|
3418
|
+
}
|
|
3419
|
+
selectPeerGroupFanout(groupId, count, excludePeerIds = []) {
|
|
3420
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3421
|
+
const peers = this.listPeers(scope, { includeSelf: false });
|
|
3422
|
+
return selectFanoutPeers2({
|
|
3423
|
+
peers,
|
|
3424
|
+
count,
|
|
3425
|
+
excludePeerIds: [...excludePeerIds, this.nodeId],
|
|
3426
|
+
connectedPeerIds: this.listConnectedPeerIds()
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
async enforceConnectionBudget() {
|
|
3430
|
+
const adapter = this.networkAdapter;
|
|
3431
|
+
if (typeof adapter.listOpenPeerIds !== "function" || typeof adapter.disconnectPeer !== "function") {
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
const openPeerIds = adapter.listOpenPeerIds();
|
|
3435
|
+
if (openPeerIds.length <= this.globalMaxOpenConnections) {
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3438
|
+
const excess = openPeerIds.length - this.globalMaxOpenConnections;
|
|
3439
|
+
const toClose = openPeerIds.slice(0, excess);
|
|
3440
|
+
for (const peerId of toClose) {
|
|
3441
|
+
try {
|
|
3442
|
+
await adapter.disconnectPeer(peerId);
|
|
3443
|
+
} catch (error) {
|
|
3444
|
+
this.emit("warning", { type: "peer-disconnect-failed", peerId, error });
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
async joinPeerGroup(groupId, options = {}) {
|
|
3449
|
+
if (!groupId) {
|
|
3450
|
+
throw new Error("joinPeerGroup requires groupId");
|
|
3451
|
+
}
|
|
3452
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3453
|
+
const config = {
|
|
3454
|
+
fanout: typeof options.fanout === "number" ? options.fanout : this.defaultPeerGroupFanout,
|
|
3455
|
+
maxActivePeers: typeof options.maxActivePeers === "number" ? options.maxActivePeers : this.defaultPeerGroupMaxActivePeers,
|
|
3456
|
+
maxHops: typeof options.maxHops === "number" ? options.maxHops : this.defaultGossipMaxHops,
|
|
3457
|
+
relayEnabled: options.relayEnabled !== false
|
|
3458
|
+
};
|
|
3459
|
+
await this.joinDiscovery(scope, {
|
|
3460
|
+
metadata: {
|
|
3461
|
+
peerGroup: groupId,
|
|
3462
|
+
...options.metadata || {}
|
|
3463
|
+
},
|
|
3464
|
+
bootstrapPeerIds: options.bootstrapPeerIds,
|
|
3465
|
+
heartbeatIntervalMs: options.heartbeatIntervalMs,
|
|
3466
|
+
ttlMs: options.ttlMs
|
|
3467
|
+
});
|
|
3468
|
+
this.peerGroups.set(groupId, config);
|
|
3469
|
+
this.emit("peergroupjoined", { groupId, config });
|
|
3470
|
+
return config;
|
|
3471
|
+
}
|
|
3472
|
+
async leavePeerGroup(groupId) {
|
|
3473
|
+
if (!groupId) {
|
|
3474
|
+
return;
|
|
3475
|
+
}
|
|
3476
|
+
const scope = this.peerGroupScopeFor(groupId);
|
|
3477
|
+
await this.leaveDiscovery(scope);
|
|
3478
|
+
this.peerGroups.delete(groupId);
|
|
3479
|
+
this.emit("peergroupleft", { groupId });
|
|
3480
|
+
}
|
|
3481
|
+
async publishToPeerGroup(groupId, innerMessageType, innerPayload, options = {}) {
|
|
3482
|
+
if (!groupId) {
|
|
3483
|
+
throw new Error("publishToPeerGroup requires groupId");
|
|
3484
|
+
}
|
|
3485
|
+
const group = this.peerGroups.get(groupId);
|
|
3486
|
+
if (!group && options.allowUnjoined !== true) {
|
|
3487
|
+
throw new Error(`PeerGroup ${groupId} has not been joined`);
|
|
3488
|
+
}
|
|
3489
|
+
const fanout = typeof options.fanout === "number" ? options.fanout : group ? group.fanout : this.defaultPeerGroupFanout;
|
|
3490
|
+
const maxActivePeers = group ? group.maxActivePeers : this.defaultPeerGroupMaxActivePeers;
|
|
3491
|
+
const maxHop = typeof options.maxHops === "number" ? options.maxHops : group ? group.maxHops : this.defaultGossipMaxHops;
|
|
3492
|
+
const fanoutPeerIds = this.selectPeerGroupFanout(groupId, fanout, [this.nodeId]);
|
|
3493
|
+
if (fanoutPeerIds.length > 0) {
|
|
3494
|
+
await this.ensureConnectedToPeers(fanoutPeerIds.slice(0, maxActivePeers));
|
|
3495
|
+
await this.enforceConnectionBudget();
|
|
3496
|
+
}
|
|
3497
|
+
const gossipId = options.gossipId || this.idGenerator();
|
|
3498
|
+
this.markSeenGossip(gossipId);
|
|
3499
|
+
await this.broadcastMessage("peer-group:gossip", {
|
|
3500
|
+
groupId,
|
|
3501
|
+
gossipId,
|
|
3502
|
+
publisherId: this.nodeId,
|
|
3503
|
+
hop: 0,
|
|
3504
|
+
maxHop,
|
|
3505
|
+
innerMessageType,
|
|
3506
|
+
innerPayload
|
|
3507
|
+
}, {
|
|
3508
|
+
broadcastScope: this.peerGroupScopeFor(groupId),
|
|
3509
|
+
fanoutPeerIds
|
|
3510
|
+
});
|
|
3511
|
+
return { gossipId, fanoutPeerIds };
|
|
3512
|
+
}
|
|
3513
|
+
async publishRecordToPeerGroup(groupId, collectionName, id, options = {}) {
|
|
3514
|
+
const collection = this.getCollection(collectionName);
|
|
3515
|
+
const raw = collection.get(id);
|
|
3516
|
+
if (!raw || raw.deletedAt) {
|
|
3517
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3518
|
+
}
|
|
3519
|
+
const record = this.normalizeRecord(raw);
|
|
3520
|
+
return this.publishToPeerGroup(groupId, "record:snapshot", {
|
|
3521
|
+
collectionName,
|
|
3522
|
+
record
|
|
3523
|
+
}, options);
|
|
3524
|
+
}
|
|
3525
|
+
async handlePeerGroupGossip(decrypted) {
|
|
3526
|
+
const payload = decrypted.payload || {};
|
|
3527
|
+
const {
|
|
3528
|
+
groupId,
|
|
3529
|
+
gossipId,
|
|
3530
|
+
publisherId = decrypted.senderId,
|
|
3531
|
+
hop = 0,
|
|
3532
|
+
maxHop: payloadMaxHop,
|
|
3533
|
+
innerMessageType,
|
|
3534
|
+
innerPayload
|
|
3535
|
+
} = payload;
|
|
3536
|
+
if (!groupId || !innerMessageType || !gossipId) {
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3539
|
+
if (!this.peerGroups.has(groupId)) {
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
if (this.hasSeenGossip(gossipId)) {
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
this.markSeenGossip(gossipId);
|
|
3546
|
+
await this.dispatchPeerGroupInnerMessage(innerMessageType, innerPayload, {
|
|
3547
|
+
groupId,
|
|
3548
|
+
senderId: decrypted.senderId,
|
|
3549
|
+
publisherId
|
|
3550
|
+
});
|
|
3551
|
+
const group = this.peerGroups.get(groupId);
|
|
3552
|
+
const configuredMaxHop = group ? group.maxHops : this.defaultGossipMaxHops;
|
|
3553
|
+
const maxHop = typeof payloadMaxHop === "number" ? Math.min(payloadMaxHop, configuredMaxHop) : configuredMaxHop;
|
|
3554
|
+
if (!group || group.relayEnabled === false || hop >= maxHop) {
|
|
3555
|
+
return;
|
|
3556
|
+
}
|
|
3557
|
+
const relayPeers = this.selectPeerGroupFanout(groupId, group.fanout, [
|
|
3558
|
+
decrypted.senderId,
|
|
3559
|
+
this.nodeId
|
|
3560
|
+
]);
|
|
3561
|
+
if (relayPeers.length === 0) {
|
|
3562
|
+
return;
|
|
3563
|
+
}
|
|
3564
|
+
await this.ensureConnectedToPeers(relayPeers.slice(0, group.maxActivePeers));
|
|
3565
|
+
await this.enforceConnectionBudget();
|
|
3566
|
+
await this.broadcastMessage("peer-group:gossip", {
|
|
3567
|
+
groupId,
|
|
3568
|
+
gossipId,
|
|
3569
|
+
publisherId,
|
|
3570
|
+
hop: hop + 1,
|
|
3571
|
+
maxHop,
|
|
3572
|
+
innerMessageType,
|
|
3573
|
+
innerPayload
|
|
3574
|
+
}, {
|
|
3575
|
+
broadcastScope: this.peerGroupScopeFor(groupId),
|
|
3576
|
+
fanoutPeerIds: relayPeers
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3579
|
+
normalizeGossipOperation(operation, publisherId) {
|
|
3580
|
+
if (!operation || !publisherId) {
|
|
3581
|
+
return null;
|
|
3582
|
+
}
|
|
3583
|
+
if (operation.actorId && operation.actorId !== publisherId) {
|
|
3584
|
+
this.emit("warning", {
|
|
3585
|
+
type: "gossip-operation-actor-mismatch",
|
|
3586
|
+
publisherId,
|
|
3587
|
+
actorId: operation.actorId,
|
|
3588
|
+
kind: operation.kind,
|
|
3589
|
+
collection: operation.collectionName,
|
|
3590
|
+
id: operation.id
|
|
3591
|
+
});
|
|
3592
|
+
return null;
|
|
3593
|
+
}
|
|
3594
|
+
const normalized = {
|
|
3595
|
+
...operation,
|
|
3596
|
+
actorId: publisherId
|
|
3597
|
+
};
|
|
3598
|
+
if (normalized.kind === "create") {
|
|
3599
|
+
normalized.ownerId = publisherId;
|
|
3600
|
+
}
|
|
3601
|
+
return normalized;
|
|
3602
|
+
}
|
|
3603
|
+
async dispatchPeerGroupInnerMessage(innerMessageType, innerPayload, context = {}) {
|
|
3604
|
+
if (innerMessageType === "operation") {
|
|
3605
|
+
const operation = this.normalizeGossipOperation(
|
|
3606
|
+
innerPayload,
|
|
3607
|
+
context.publisherId || context.senderId
|
|
3608
|
+
);
|
|
3609
|
+
if (operation) {
|
|
3610
|
+
this.applyOperation(operation);
|
|
3611
|
+
}
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
if (innerMessageType === "record:snapshot") {
|
|
3615
|
+
const { collectionName, record } = innerPayload || {};
|
|
3616
|
+
if (collectionName && record) {
|
|
3617
|
+
const applied = this.restoreRecord(collectionName, record, {
|
|
3618
|
+
rejectOnHashMismatch: true,
|
|
3619
|
+
rejectOnOwnershipMismatch: true,
|
|
3620
|
+
via: "peer-group"
|
|
3621
|
+
});
|
|
3622
|
+
if (applied) {
|
|
3623
|
+
this.emit("change", {
|
|
3624
|
+
kind: "snapshot",
|
|
3625
|
+
collection: collectionName,
|
|
3626
|
+
id: record.id,
|
|
3627
|
+
via: "peer-group",
|
|
3628
|
+
groupId: context.groupId
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3634
|
+
this.emit("peergroupmessage", {
|
|
3635
|
+
groupId: context.groupId,
|
|
3636
|
+
senderId: context.senderId,
|
|
3637
|
+
type: innerMessageType,
|
|
3638
|
+
payload: innerPayload
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3260
3641
|
getPresenceMap(scope) {
|
|
3261
3642
|
if (!this.presenceByScope.has(scope)) {
|
|
3262
3643
|
this.presenceByScope.set(scope, /* @__PURE__ */ new Map());
|
|
@@ -3387,6 +3768,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
3387
3768
|
}
|
|
3388
3769
|
async handleIncomingMessage(message) {
|
|
3389
3770
|
if (message && message.opId && message.kind) {
|
|
3771
|
+
if (this.securityService.options.enabled) {
|
|
3772
|
+
this.emit("messageignored", {
|
|
3773
|
+
reason: "raw-operation-rejected",
|
|
3774
|
+
hint: "Unsigned raw operations are disabled when security is enabled"
|
|
3775
|
+
});
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3390
3778
|
this.applyOperation(message);
|
|
3391
3779
|
return;
|
|
3392
3780
|
}
|
|
@@ -3397,9 +3785,6 @@ var require_dignity_p2p = __commonJS({
|
|
|
3397
3785
|
});
|
|
3398
3786
|
return;
|
|
3399
3787
|
}
|
|
3400
|
-
if (message && message.senderId && message.senderPublicKey) {
|
|
3401
|
-
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3402
|
-
}
|
|
3403
3788
|
let decrypted;
|
|
3404
3789
|
try {
|
|
3405
3790
|
decrypted = await this.securityService.decryptIncomingMessage(message);
|
|
@@ -3417,6 +3802,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3417
3802
|
if (!decrypted || decrypted.ignored) {
|
|
3418
3803
|
return;
|
|
3419
3804
|
}
|
|
3805
|
+
if (message && message.senderId && message.senderPublicKey) {
|
|
3806
|
+
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3807
|
+
}
|
|
3420
3808
|
if (decrypted.messageType === "operation") {
|
|
3421
3809
|
this.applyOperation(decrypted.payload);
|
|
3422
3810
|
return;
|
|
@@ -3440,18 +3828,27 @@ var require_dignity_p2p = __commonJS({
|
|
|
3440
3828
|
const payload = decrypted.payload || {};
|
|
3441
3829
|
const scope = payload.scope || "main";
|
|
3442
3830
|
const peerId = payload.peerId || decrypted.senderId;
|
|
3443
|
-
if (!peerId) {
|
|
3831
|
+
if (!peerId || peerId !== decrypted.senderId) {
|
|
3444
3832
|
return;
|
|
3445
3833
|
}
|
|
3834
|
+
if (!this.discoveryRooms.has(scope)) {
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
const room = this.discoveryRooms.get(scope);
|
|
3446
3838
|
const presenceMap = this.getPresenceMap(scope);
|
|
3447
3839
|
const isNewPeerInScope = !presenceMap.has(peerId);
|
|
3840
|
+
const requestedTtl = typeof payload.ttlMs === "number" ? payload.ttlMs : room.ttlMs;
|
|
3841
|
+
const ttlMs = Math.min(requestedTtl, room.ttlMs);
|
|
3448
3842
|
this.upsertPresence(
|
|
3449
3843
|
scope,
|
|
3450
3844
|
peerId,
|
|
3451
3845
|
payload.metadata || {},
|
|
3452
|
-
|
|
3453
|
-
|
|
3846
|
+
ttlMs,
|
|
3847
|
+
this.now()
|
|
3454
3848
|
);
|
|
3849
|
+
if (payload.metadata && payload.metadata.publicKey) {
|
|
3850
|
+
this.trustPeerPublicKey(peerId, payload.metadata.publicKey);
|
|
3851
|
+
}
|
|
3455
3852
|
if (isNewPeerInScope && peerId !== this.nodeId && this.discoveryRooms.has(scope)) {
|
|
3456
3853
|
if (typeof this.networkAdapter.connectToPeer === "function") {
|
|
3457
3854
|
Promise.resolve(this.connectToPeer(peerId)).catch((error) => {
|
|
@@ -3468,6 +3865,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3468
3865
|
const payload = decrypted.payload || {};
|
|
3469
3866
|
const scope = payload.scope || "main";
|
|
3470
3867
|
const peerId = payload.peerId || decrypted.senderId;
|
|
3868
|
+
if (!peerId || peerId !== decrypted.senderId) {
|
|
3869
|
+
return;
|
|
3870
|
+
}
|
|
3471
3871
|
const map = this.presenceByScope.get(scope);
|
|
3472
3872
|
if (map && peerId && map.has(peerId)) {
|
|
3473
3873
|
map.delete(peerId);
|
|
@@ -3475,6 +3875,10 @@ var require_dignity_p2p = __commonJS({
|
|
|
3475
3875
|
}
|
|
3476
3876
|
return;
|
|
3477
3877
|
}
|
|
3878
|
+
if (decrypted.messageType === "peer-group:gossip") {
|
|
3879
|
+
await this.handlePeerGroupGossip(decrypted);
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3478
3882
|
this.emit("message", {
|
|
3479
3883
|
senderId: decrypted.senderId,
|
|
3480
3884
|
targetId: decrypted.targetId,
|
|
@@ -3520,7 +3924,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3520
3924
|
emitConflict(details) {
|
|
3521
3925
|
this.emit("conflict", details);
|
|
3522
3926
|
}
|
|
3523
|
-
restoreRecord(collectionName, record) {
|
|
3927
|
+
restoreRecord(collectionName, record, options = {}) {
|
|
3524
3928
|
if (!record || !record.id) {
|
|
3525
3929
|
return false;
|
|
3526
3930
|
}
|
|
@@ -3529,11 +3933,51 @@ var require_dignity_p2p = __commonJS({
|
|
|
3529
3933
|
if (current && current.version >= record.version) {
|
|
3530
3934
|
return false;
|
|
3531
3935
|
}
|
|
3936
|
+
const restoredData = { ...record.data || {} };
|
|
3937
|
+
const computedHash = computeContentHash(restoredData);
|
|
3938
|
+
const rejectOnHashMismatch = options.rejectOnHashMismatch === true;
|
|
3939
|
+
const rejectOnOwnershipMismatch = options.rejectOnOwnershipMismatch === true;
|
|
3940
|
+
if (rejectOnOwnershipMismatch && current && record.ownerId && current.ownerId !== record.ownerId) {
|
|
3941
|
+
this.emit("warning", {
|
|
3942
|
+
type: "ownership-mismatch",
|
|
3943
|
+
collection: collectionName,
|
|
3944
|
+
id: record.id,
|
|
3945
|
+
currentOwnerId: current.ownerId,
|
|
3946
|
+
advertisedOwnerId: record.ownerId,
|
|
3947
|
+
via: options.via || null
|
|
3948
|
+
});
|
|
3949
|
+
return false;
|
|
3950
|
+
}
|
|
3951
|
+
if (!record.hash) {
|
|
3952
|
+
const warning = {
|
|
3953
|
+
type: "content-hash-missing",
|
|
3954
|
+
collection: collectionName,
|
|
3955
|
+
id: record.id,
|
|
3956
|
+
via: options.via || null
|
|
3957
|
+
};
|
|
3958
|
+
this.emit("warning", warning);
|
|
3959
|
+
if (rejectOnHashMismatch) {
|
|
3960
|
+
return false;
|
|
3961
|
+
}
|
|
3962
|
+
} else if (record.hash !== computedHash) {
|
|
3963
|
+
this.emit("warning", {
|
|
3964
|
+
type: "content-hash-mismatch",
|
|
3965
|
+
collection: collectionName,
|
|
3966
|
+
id: record.id,
|
|
3967
|
+
advertisedHash: record.hash,
|
|
3968
|
+
computedHash,
|
|
3969
|
+
via: options.via || null
|
|
3970
|
+
});
|
|
3971
|
+
if (rejectOnHashMismatch) {
|
|
3972
|
+
return false;
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3532
3975
|
collection.set(record.id, {
|
|
3533
3976
|
id: record.id,
|
|
3534
3977
|
ownerId: record.ownerId,
|
|
3535
3978
|
collaboratorIds: this.normalizeCollaboratorIds(record.collaboratorIds),
|
|
3536
|
-
data:
|
|
3979
|
+
data: restoredData,
|
|
3980
|
+
hash: computedHash,
|
|
3537
3981
|
createdAt: record.createdAt,
|
|
3538
3982
|
updatedAt: record.updatedAt,
|
|
3539
3983
|
deletedAt: record.deletedAt || null,
|
|
@@ -3551,7 +3995,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3551
3995
|
id: raw.id,
|
|
3552
3996
|
ownerId: raw.ownerId,
|
|
3553
3997
|
collaboratorIds: Array.isArray(raw.collaboratorIds) ? [...raw.collaboratorIds] : [],
|
|
3554
|
-
data: { ...raw.data },
|
|
3998
|
+
data: { ...raw.data || {} },
|
|
3999
|
+
hash: raw.hash || computeContentHash(raw.data || {}),
|
|
3555
4000
|
createdAt: raw.createdAt,
|
|
3556
4001
|
updatedAt: raw.updatedAt,
|
|
3557
4002
|
deletedAt: raw.deletedAt || null,
|
|
@@ -3567,6 +4012,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3567
4012
|
});
|
|
3568
4013
|
return record;
|
|
3569
4014
|
}
|
|
4015
|
+
pruneAppliedOperations() {
|
|
4016
|
+
while (this.appliedOperations.size > this.maxAppliedOperations) {
|
|
4017
|
+
const oldestOpId = this.appliedOperations.keys().next().value;
|
|
4018
|
+
if (!oldestOpId) {
|
|
4019
|
+
break;
|
|
4020
|
+
}
|
|
4021
|
+
this.appliedOperations.delete(oldestOpId);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
3570
4024
|
applyOperation(operation) {
|
|
3571
4025
|
if (!operation || !operation.opId || this.appliedOperations.has(operation.opId)) {
|
|
3572
4026
|
return false;
|
|
@@ -3581,13 +4035,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3581
4035
|
id: operation.id,
|
|
3582
4036
|
ownerId: operation.ownerId,
|
|
3583
4037
|
collaboratorIds: this.normalizeCollaboratorIds(operation.collaboratorIds),
|
|
3584
|
-
data: { ...operation.payload },
|
|
4038
|
+
data: { ...operation.payload || {} },
|
|
4039
|
+
hash: computeContentHash(operation.payload || {}),
|
|
3585
4040
|
createdAt: operation.timestamp,
|
|
3586
4041
|
updatedAt: operation.timestamp,
|
|
3587
4042
|
deletedAt: null,
|
|
3588
4043
|
version: 1
|
|
3589
4044
|
});
|
|
3590
|
-
this.appliedOperations.
|
|
4045
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4046
|
+
this.pruneAppliedOperations();
|
|
3591
4047
|
this.emit("change", { kind: "create", collection: operation.collectionName, id: operation.id });
|
|
3592
4048
|
return true;
|
|
3593
4049
|
}
|
|
@@ -3631,7 +4087,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3631
4087
|
}
|
|
3632
4088
|
current.updatedAt = operation.timestamp;
|
|
3633
4089
|
current.version += 1;
|
|
3634
|
-
this.appliedOperations.
|
|
4090
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4091
|
+
this.pruneAppliedOperations();
|
|
3635
4092
|
this.emit("change", {
|
|
3636
4093
|
kind: "transfer-ownership",
|
|
3637
4094
|
collection: operation.collectionName,
|
|
@@ -3660,7 +4117,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3660
4117
|
current.deletedAt = operation.timestamp;
|
|
3661
4118
|
current.updatedAt = operation.timestamp;
|
|
3662
4119
|
current.version += 1;
|
|
3663
|
-
this.appliedOperations.
|
|
4120
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4121
|
+
this.pruneAppliedOperations();
|
|
3664
4122
|
this.emit("change", { kind: "delete", collection: operation.collectionName, id: operation.id });
|
|
3665
4123
|
return true;
|
|
3666
4124
|
}
|
|
@@ -3684,12 +4142,14 @@ var require_dignity_p2p = __commonJS({
|
|
|
3684
4142
|
...current.data,
|
|
3685
4143
|
...operation.payload
|
|
3686
4144
|
};
|
|
4145
|
+
current.hash = computeContentHash(current.data);
|
|
3687
4146
|
if (Array.isArray(operation.collaboratorIds) && operation.actorId === current.ownerId) {
|
|
3688
4147
|
current.collaboratorIds = this.normalizeCollaboratorIds(operation.collaboratorIds);
|
|
3689
4148
|
}
|
|
3690
4149
|
current.updatedAt = operation.timestamp;
|
|
3691
4150
|
current.version += 1;
|
|
3692
|
-
this.appliedOperations.
|
|
4151
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4152
|
+
this.pruneAppliedOperations();
|
|
3693
4153
|
this.emit("change", { kind: "update", collection: operation.collectionName, id: operation.id });
|
|
3694
4154
|
return true;
|
|
3695
4155
|
}
|
|
@@ -3769,6 +4229,14 @@ var require_signaling_pool = __commonJS({
|
|
|
3769
4229
|
// src/signaling/websocket-signaling-provider.js
|
|
3770
4230
|
var require_websocket_signaling_provider = __commonJS({
|
|
3771
4231
|
"src/signaling/websocket-signaling-provider.js"(exports2, module2) {
|
|
4232
|
+
function randomBase36(length) {
|
|
4233
|
+
let value = "";
|
|
4234
|
+
while (value.length < length) {
|
|
4235
|
+
const chunk = Math.random().toString(36).slice(2);
|
|
4236
|
+
value += chunk.length > 0 ? chunk : "0";
|
|
4237
|
+
}
|
|
4238
|
+
return value.slice(0, length);
|
|
4239
|
+
}
|
|
3772
4240
|
var WebSocketSignalingProvider2 = class {
|
|
3773
4241
|
constructor({ id, url, WebSocketImpl, priority = 0 }) {
|
|
3774
4242
|
if (!url) {
|
|
@@ -3812,8 +4280,8 @@ var require_websocket_signaling_provider = __commonJS({
|
|
|
3812
4280
|
if (!peerJsHostPattern.test(this.url)) {
|
|
3813
4281
|
return this.url;
|
|
3814
4282
|
}
|
|
3815
|
-
const connectionId = `dignityjs_${
|
|
3816
|
-
const token =
|
|
4283
|
+
const connectionId = `dignityjs_${randomBase36(10)}`;
|
|
4284
|
+
const token = randomBase36(10);
|
|
3817
4285
|
const hasQuery = this.url.includes("?");
|
|
3818
4286
|
const hasId = /[?&]id=/.test(this.url);
|
|
3819
4287
|
const hasToken = /[?&]token=/.test(this.url);
|
|
@@ -10719,6 +11187,16 @@ var require_in_memory_network = __commonJS({
|
|
|
10719
11187
|
}
|
|
10720
11188
|
await Promise.all(deliveries);
|
|
10721
11189
|
}
|
|
11190
|
+
async sendToPeers(senderId, message, peerIds = []) {
|
|
11191
|
+
const targets = new Set((peerIds || []).filter((peerId) => peerId && peerId !== senderId));
|
|
11192
|
+
const deliveries = [];
|
|
11193
|
+
for (const [nodeId, adapter] of this.adapters.entries()) {
|
|
11194
|
+
if (nodeId !== senderId && targets.has(nodeId)) {
|
|
11195
|
+
deliveries.push(adapter.receive(message));
|
|
11196
|
+
}
|
|
11197
|
+
}
|
|
11198
|
+
await Promise.all(deliveries);
|
|
11199
|
+
}
|
|
10722
11200
|
};
|
|
10723
11201
|
var InMemoryNetworkAdapter2 = class {
|
|
10724
11202
|
constructor(hub) {
|
|
@@ -10728,6 +11206,7 @@ var require_in_memory_network = __commonJS({
|
|
|
10728
11206
|
this.hub = hub;
|
|
10729
11207
|
this.nodeId = null;
|
|
10730
11208
|
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
11209
|
+
this.connectedPeers = /* @__PURE__ */ new Set();
|
|
10731
11210
|
}
|
|
10732
11211
|
async start(nodeId) {
|
|
10733
11212
|
this.nodeId = nodeId;
|
|
@@ -10738,6 +11217,13 @@ var require_in_memory_network = __commonJS({
|
|
|
10738
11217
|
this.hub.unregister(this.nodeId);
|
|
10739
11218
|
}
|
|
10740
11219
|
this.nodeId = null;
|
|
11220
|
+
this.connectedPeers.clear();
|
|
11221
|
+
}
|
|
11222
|
+
async connectToPeer(remotePeerId) {
|
|
11223
|
+
if (!remotePeerId || remotePeerId === this.nodeId) {
|
|
11224
|
+
return;
|
|
11225
|
+
}
|
|
11226
|
+
this.connectedPeers.add(remotePeerId);
|
|
10741
11227
|
}
|
|
10742
11228
|
async broadcast(message) {
|
|
10743
11229
|
if (!this.nodeId) {
|
|
@@ -10745,6 +11231,21 @@ var require_in_memory_network = __commonJS({
|
|
|
10745
11231
|
}
|
|
10746
11232
|
await this.hub.broadcast(this.nodeId, message);
|
|
10747
11233
|
}
|
|
11234
|
+
async sendToPeers(message, peerIds = []) {
|
|
11235
|
+
if (!this.nodeId) {
|
|
11236
|
+
throw new Error("Network adapter has not been started");
|
|
11237
|
+
}
|
|
11238
|
+
await this.hub.sendToPeers(this.nodeId, message, peerIds);
|
|
11239
|
+
}
|
|
11240
|
+
listOpenPeerIds() {
|
|
11241
|
+
return [...this.connectedPeers];
|
|
11242
|
+
}
|
|
11243
|
+
getOpenConnectionCount() {
|
|
11244
|
+
return this.connectedPeers.size;
|
|
11245
|
+
}
|
|
11246
|
+
isConnectedTo(remotePeerId) {
|
|
11247
|
+
return this.connectedPeers.has(remotePeerId);
|
|
11248
|
+
}
|
|
10748
11249
|
onMessage(handler) {
|
|
10749
11250
|
this.messageHandlers.add(handler);
|
|
10750
11251
|
}
|
|
@@ -10923,6 +11424,29 @@ var require_peerjs_network = __commonJS({
|
|
|
10923
11424
|
}
|
|
10924
11425
|
await Promise.all(deliveries);
|
|
10925
11426
|
}
|
|
11427
|
+
async sendToPeers(message, peerIds = []) {
|
|
11428
|
+
if (!this.peer) {
|
|
11429
|
+
throw new Error("PeerJS network adapter has not been started");
|
|
11430
|
+
}
|
|
11431
|
+
const targets = new Set((peerIds || []).filter(Boolean));
|
|
11432
|
+
if (targets.size === 0) {
|
|
11433
|
+
return;
|
|
11434
|
+
}
|
|
11435
|
+
const deliveries = [];
|
|
11436
|
+
for (const [peerId, connection] of this.connections.entries()) {
|
|
11437
|
+
if (targets.has(peerId) && connection.open) {
|
|
11438
|
+
deliveries.push(connection.send(message));
|
|
11439
|
+
}
|
|
11440
|
+
}
|
|
11441
|
+
await Promise.all(deliveries);
|
|
11442
|
+
}
|
|
11443
|
+
async disconnectPeer(remotePeerId) {
|
|
11444
|
+
const connection = this.connections.get(remotePeerId);
|
|
11445
|
+
if (connection && typeof connection.close === "function") {
|
|
11446
|
+
connection.close();
|
|
11447
|
+
}
|
|
11448
|
+
this.connections.delete(remotePeerId);
|
|
11449
|
+
}
|
|
10926
11450
|
getOpenConnectionCount() {
|
|
10927
11451
|
return this.listOpenPeerIds().length;
|
|
10928
11452
|
}
|
|
@@ -11029,6 +11553,7 @@ var require_indexeddb_persistence = __commonJS({
|
|
|
11029
11553
|
ownerId: record.ownerId,
|
|
11030
11554
|
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
11031
11555
|
data: { ...record.data },
|
|
11556
|
+
hash: record.hash || null,
|
|
11032
11557
|
createdAt: record.createdAt,
|
|
11033
11558
|
updatedAt: record.updatedAt,
|
|
11034
11559
|
deletedAt: record.deletedAt,
|
|
@@ -11089,6 +11614,7 @@ var require_indexeddb_persistence = __commonJS({
|
|
|
11089
11614
|
ownerId: stored.ownerId,
|
|
11090
11615
|
collaboratorIds: stored.collaboratorIds,
|
|
11091
11616
|
data: stored.data,
|
|
11617
|
+
hash: stored.hash || null,
|
|
11092
11618
|
createdAt: stored.createdAt,
|
|
11093
11619
|
updatedAt: stored.updatedAt,
|
|
11094
11620
|
deletedAt: stored.deletedAt,
|
|
@@ -11149,6 +11675,13 @@ var {
|
|
|
11149
11675
|
MessageSecurityService,
|
|
11150
11676
|
DEFAULT_SECURITY_OPTIONS
|
|
11151
11677
|
} = require_message_security_service();
|
|
11678
|
+
var {
|
|
11679
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
11680
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11681
|
+
peerGroupScope,
|
|
11682
|
+
parsePeerGroupScope,
|
|
11683
|
+
selectFanoutPeers
|
|
11684
|
+
} = require_peer_group();
|
|
11152
11685
|
module.exports = {
|
|
11153
11686
|
DignityP2P,
|
|
11154
11687
|
createDefaultSignalingPool,
|
|
@@ -11165,6 +11698,11 @@ module.exports = {
|
|
|
11165
11698
|
VDF,
|
|
11166
11699
|
SlothPermutation,
|
|
11167
11700
|
MessageSecurityService,
|
|
11168
|
-
DEFAULT_SECURITY_OPTIONS
|
|
11701
|
+
DEFAULT_SECURITY_OPTIONS,
|
|
11702
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
11703
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11704
|
+
peerGroupScope,
|
|
11705
|
+
parsePeerGroupScope,
|
|
11706
|
+
selectFanoutPeers
|
|
11169
11707
|
};
|
|
11170
11708
|
//# sourceMappingURL=dignity.cjs.js.map
|