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
package/dist/dignity.cjs.js
CHANGED
|
@@ -2749,8 +2749,14 @@ var require_message_security_service = __commonJS({
|
|
|
2749
2749
|
const nonce = naclUtil.decodeBase64(encryption.nonce);
|
|
2750
2750
|
let key;
|
|
2751
2751
|
if (encryption.kdf === "pbkdf2") {
|
|
2752
|
-
const
|
|
2753
|
-
|
|
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);
|
|
2754
2760
|
} else {
|
|
2755
2761
|
key = legacyBroadcastKey(password, salt);
|
|
2756
2762
|
}
|
|
@@ -2854,6 +2860,65 @@ var require_message_security_service = __commonJS({
|
|
|
2854
2860
|
}
|
|
2855
2861
|
});
|
|
2856
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
|
+
|
|
2857
2922
|
// src/core/dignity-p2p.js
|
|
2858
2923
|
var require_dignity_p2p = __commonJS({
|
|
2859
2924
|
"src/core/dignity-p2p.js"(exports2, module2) {
|
|
@@ -2861,6 +2926,11 @@ var require_dignity_p2p = __commonJS({
|
|
|
2861
2926
|
var naclUtil = require_nacl_util();
|
|
2862
2927
|
var EventEmitter = require_event_emitter();
|
|
2863
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();
|
|
2864
2934
|
function computeContentHash(data) {
|
|
2865
2935
|
const canonical = stableStringify(data || {});
|
|
2866
2936
|
const bytes = naclUtil.decodeUTF8(canonical);
|
|
@@ -2893,8 +2963,16 @@ var require_dignity_p2p = __commonJS({
|
|
|
2893
2963
|
this.defaultPresenceTtlMs = security && typeof security.presenceTtlMs === "number" ? security.presenceTtlMs : 45e3;
|
|
2894
2964
|
this.discoveryRooms = /* @__PURE__ */ new Map();
|
|
2895
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;
|
|
2896
2974
|
this.state = /* @__PURE__ */ new Map();
|
|
2897
|
-
this.appliedOperations = /* @__PURE__ */ new
|
|
2975
|
+
this.appliedOperations = /* @__PURE__ */ new Map();
|
|
2898
2976
|
this.boundMessageHandler = this.handleIncomingMessage.bind(this);
|
|
2899
2977
|
}
|
|
2900
2978
|
async start() {
|
|
@@ -2902,6 +2980,14 @@ var require_dignity_p2p = __commonJS({
|
|
|
2902
2980
|
await this.networkAdapter.start(this.nodeId);
|
|
2903
2981
|
}
|
|
2904
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
|
+
}
|
|
2905
2991
|
const joinedScopes = Array.from(this.discoveryRooms.keys());
|
|
2906
2992
|
for (const scope of joinedScopes) {
|
|
2907
2993
|
try {
|
|
@@ -3250,6 +3336,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3250
3336
|
const connectToPeers = securityContext.connectToPeers;
|
|
3251
3337
|
if (Array.isArray(connectToPeers) && connectToPeers.length > 0) {
|
|
3252
3338
|
await this.ensureConnectedToPeers(connectToPeers);
|
|
3339
|
+
await this.enforceConnectionBudget();
|
|
3253
3340
|
}
|
|
3254
3341
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3255
3342
|
messageType,
|
|
@@ -3257,6 +3344,11 @@ var require_dignity_p2p = __commonJS({
|
|
|
3257
3344
|
targetId: null,
|
|
3258
3345
|
securityContext
|
|
3259
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
|
+
}
|
|
3260
3352
|
await this.networkAdapter.broadcast(envelope);
|
|
3261
3353
|
}
|
|
3262
3354
|
async sendDirectMessage(targetId, messageType, payload) {
|
|
@@ -3272,8 +3364,280 @@ var require_dignity_p2p = __commonJS({
|
|
|
3272
3364
|
payload,
|
|
3273
3365
|
targetId
|
|
3274
3366
|
});
|
|
3367
|
+
if (targetId && typeof this.networkAdapter.sendToPeers === "function") {
|
|
3368
|
+
await this.networkAdapter.sendToPeers(envelope, [targetId]);
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3275
3371
|
await this.networkAdapter.broadcast(envelope);
|
|
3276
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
|
+
}
|
|
3277
3641
|
getPresenceMap(scope) {
|
|
3278
3642
|
if (!this.presenceByScope.has(scope)) {
|
|
3279
3643
|
this.presenceByScope.set(scope, /* @__PURE__ */ new Map());
|
|
@@ -3404,6 +3768,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
3404
3768
|
}
|
|
3405
3769
|
async handleIncomingMessage(message) {
|
|
3406
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
|
+
}
|
|
3407
3778
|
this.applyOperation(message);
|
|
3408
3779
|
return;
|
|
3409
3780
|
}
|
|
@@ -3414,9 +3785,6 @@ var require_dignity_p2p = __commonJS({
|
|
|
3414
3785
|
});
|
|
3415
3786
|
return;
|
|
3416
3787
|
}
|
|
3417
|
-
if (message && message.senderId && message.senderPublicKey) {
|
|
3418
|
-
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3419
|
-
}
|
|
3420
3788
|
let decrypted;
|
|
3421
3789
|
try {
|
|
3422
3790
|
decrypted = await this.securityService.decryptIncomingMessage(message);
|
|
@@ -3434,6 +3802,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3434
3802
|
if (!decrypted || decrypted.ignored) {
|
|
3435
3803
|
return;
|
|
3436
3804
|
}
|
|
3805
|
+
if (message && message.senderId && message.senderPublicKey) {
|
|
3806
|
+
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3807
|
+
}
|
|
3437
3808
|
if (decrypted.messageType === "operation") {
|
|
3438
3809
|
this.applyOperation(decrypted.payload);
|
|
3439
3810
|
return;
|
|
@@ -3457,18 +3828,27 @@ var require_dignity_p2p = __commonJS({
|
|
|
3457
3828
|
const payload = decrypted.payload || {};
|
|
3458
3829
|
const scope = payload.scope || "main";
|
|
3459
3830
|
const peerId = payload.peerId || decrypted.senderId;
|
|
3460
|
-
if (!peerId) {
|
|
3831
|
+
if (!peerId || peerId !== decrypted.senderId) {
|
|
3832
|
+
return;
|
|
3833
|
+
}
|
|
3834
|
+
if (!this.discoveryRooms.has(scope)) {
|
|
3461
3835
|
return;
|
|
3462
3836
|
}
|
|
3837
|
+
const room = this.discoveryRooms.get(scope);
|
|
3463
3838
|
const presenceMap = this.getPresenceMap(scope);
|
|
3464
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);
|
|
3465
3842
|
this.upsertPresence(
|
|
3466
3843
|
scope,
|
|
3467
3844
|
peerId,
|
|
3468
3845
|
payload.metadata || {},
|
|
3469
|
-
|
|
3470
|
-
|
|
3846
|
+
ttlMs,
|
|
3847
|
+
this.now()
|
|
3471
3848
|
);
|
|
3849
|
+
if (payload.metadata && payload.metadata.publicKey) {
|
|
3850
|
+
this.trustPeerPublicKey(peerId, payload.metadata.publicKey);
|
|
3851
|
+
}
|
|
3472
3852
|
if (isNewPeerInScope && peerId !== this.nodeId && this.discoveryRooms.has(scope)) {
|
|
3473
3853
|
if (typeof this.networkAdapter.connectToPeer === "function") {
|
|
3474
3854
|
Promise.resolve(this.connectToPeer(peerId)).catch((error) => {
|
|
@@ -3485,6 +3865,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3485
3865
|
const payload = decrypted.payload || {};
|
|
3486
3866
|
const scope = payload.scope || "main";
|
|
3487
3867
|
const peerId = payload.peerId || decrypted.senderId;
|
|
3868
|
+
if (!peerId || peerId !== decrypted.senderId) {
|
|
3869
|
+
return;
|
|
3870
|
+
}
|
|
3488
3871
|
const map = this.presenceByScope.get(scope);
|
|
3489
3872
|
if (map && peerId && map.has(peerId)) {
|
|
3490
3873
|
map.delete(peerId);
|
|
@@ -3492,6 +3875,10 @@ var require_dignity_p2p = __commonJS({
|
|
|
3492
3875
|
}
|
|
3493
3876
|
return;
|
|
3494
3877
|
}
|
|
3878
|
+
if (decrypted.messageType === "peer-group:gossip") {
|
|
3879
|
+
await this.handlePeerGroupGossip(decrypted);
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3495
3882
|
this.emit("message", {
|
|
3496
3883
|
senderId: decrypted.senderId,
|
|
3497
3884
|
targetId: decrypted.targetId,
|
|
@@ -3537,7 +3924,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3537
3924
|
emitConflict(details) {
|
|
3538
3925
|
this.emit("conflict", details);
|
|
3539
3926
|
}
|
|
3540
|
-
restoreRecord(collectionName, record) {
|
|
3927
|
+
restoreRecord(collectionName, record, options = {}) {
|
|
3541
3928
|
if (!record || !record.id) {
|
|
3542
3929
|
return false;
|
|
3543
3930
|
}
|
|
@@ -3548,14 +3935,42 @@ var require_dignity_p2p = __commonJS({
|
|
|
3548
3935
|
}
|
|
3549
3936
|
const restoredData = { ...record.data || {} };
|
|
3550
3937
|
const computedHash = computeContentHash(restoredData);
|
|
3551
|
-
|
|
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) {
|
|
3552
3963
|
this.emit("warning", {
|
|
3553
3964
|
type: "content-hash-mismatch",
|
|
3554
3965
|
collection: collectionName,
|
|
3555
3966
|
id: record.id,
|
|
3556
3967
|
advertisedHash: record.hash,
|
|
3557
|
-
computedHash
|
|
3968
|
+
computedHash,
|
|
3969
|
+
via: options.via || null
|
|
3558
3970
|
});
|
|
3971
|
+
if (rejectOnHashMismatch) {
|
|
3972
|
+
return false;
|
|
3973
|
+
}
|
|
3559
3974
|
}
|
|
3560
3975
|
collection.set(record.id, {
|
|
3561
3976
|
id: record.id,
|
|
@@ -3597,6 +4012,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3597
4012
|
});
|
|
3598
4013
|
return record;
|
|
3599
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
|
+
}
|
|
3600
4024
|
applyOperation(operation) {
|
|
3601
4025
|
if (!operation || !operation.opId || this.appliedOperations.has(operation.opId)) {
|
|
3602
4026
|
return false;
|
|
@@ -3618,7 +4042,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3618
4042
|
deletedAt: null,
|
|
3619
4043
|
version: 1
|
|
3620
4044
|
});
|
|
3621
|
-
this.appliedOperations.
|
|
4045
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4046
|
+
this.pruneAppliedOperations();
|
|
3622
4047
|
this.emit("change", { kind: "create", collection: operation.collectionName, id: operation.id });
|
|
3623
4048
|
return true;
|
|
3624
4049
|
}
|
|
@@ -3662,7 +4087,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3662
4087
|
}
|
|
3663
4088
|
current.updatedAt = operation.timestamp;
|
|
3664
4089
|
current.version += 1;
|
|
3665
|
-
this.appliedOperations.
|
|
4090
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4091
|
+
this.pruneAppliedOperations();
|
|
3666
4092
|
this.emit("change", {
|
|
3667
4093
|
kind: "transfer-ownership",
|
|
3668
4094
|
collection: operation.collectionName,
|
|
@@ -3691,7 +4117,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3691
4117
|
current.deletedAt = operation.timestamp;
|
|
3692
4118
|
current.updatedAt = operation.timestamp;
|
|
3693
4119
|
current.version += 1;
|
|
3694
|
-
this.appliedOperations.
|
|
4120
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4121
|
+
this.pruneAppliedOperations();
|
|
3695
4122
|
this.emit("change", { kind: "delete", collection: operation.collectionName, id: operation.id });
|
|
3696
4123
|
return true;
|
|
3697
4124
|
}
|
|
@@ -3721,7 +4148,8 @@ var require_dignity_p2p = __commonJS({
|
|
|
3721
4148
|
}
|
|
3722
4149
|
current.updatedAt = operation.timestamp;
|
|
3723
4150
|
current.version += 1;
|
|
3724
|
-
this.appliedOperations.
|
|
4151
|
+
this.appliedOperations.set(operation.opId, this.now());
|
|
4152
|
+
this.pruneAppliedOperations();
|
|
3725
4153
|
this.emit("change", { kind: "update", collection: operation.collectionName, id: operation.id });
|
|
3726
4154
|
return true;
|
|
3727
4155
|
}
|
|
@@ -10759,6 +11187,16 @@ var require_in_memory_network = __commonJS({
|
|
|
10759
11187
|
}
|
|
10760
11188
|
await Promise.all(deliveries);
|
|
10761
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
|
+
}
|
|
10762
11200
|
};
|
|
10763
11201
|
var InMemoryNetworkAdapter2 = class {
|
|
10764
11202
|
constructor(hub) {
|
|
@@ -10768,6 +11206,7 @@ var require_in_memory_network = __commonJS({
|
|
|
10768
11206
|
this.hub = hub;
|
|
10769
11207
|
this.nodeId = null;
|
|
10770
11208
|
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
11209
|
+
this.connectedPeers = /* @__PURE__ */ new Set();
|
|
10771
11210
|
}
|
|
10772
11211
|
async start(nodeId) {
|
|
10773
11212
|
this.nodeId = nodeId;
|
|
@@ -10778,6 +11217,13 @@ var require_in_memory_network = __commonJS({
|
|
|
10778
11217
|
this.hub.unregister(this.nodeId);
|
|
10779
11218
|
}
|
|
10780
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);
|
|
10781
11227
|
}
|
|
10782
11228
|
async broadcast(message) {
|
|
10783
11229
|
if (!this.nodeId) {
|
|
@@ -10785,6 +11231,21 @@ var require_in_memory_network = __commonJS({
|
|
|
10785
11231
|
}
|
|
10786
11232
|
await this.hub.broadcast(this.nodeId, message);
|
|
10787
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
|
+
}
|
|
10788
11249
|
onMessage(handler) {
|
|
10789
11250
|
this.messageHandlers.add(handler);
|
|
10790
11251
|
}
|
|
@@ -10963,6 +11424,29 @@ var require_peerjs_network = __commonJS({
|
|
|
10963
11424
|
}
|
|
10964
11425
|
await Promise.all(deliveries);
|
|
10965
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
|
+
}
|
|
10966
11450
|
getOpenConnectionCount() {
|
|
10967
11451
|
return this.listOpenPeerIds().length;
|
|
10968
11452
|
}
|
|
@@ -11191,6 +11675,13 @@ var {
|
|
|
11191
11675
|
MessageSecurityService,
|
|
11192
11676
|
DEFAULT_SECURITY_OPTIONS
|
|
11193
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();
|
|
11194
11685
|
module.exports = {
|
|
11195
11686
|
DignityP2P,
|
|
11196
11687
|
createDefaultSignalingPool,
|
|
@@ -11207,6 +11698,11 @@ module.exports = {
|
|
|
11207
11698
|
VDF,
|
|
11208
11699
|
SlothPermutation,
|
|
11209
11700
|
MessageSecurityService,
|
|
11210
|
-
DEFAULT_SECURITY_OPTIONS
|
|
11701
|
+
DEFAULT_SECURITY_OPTIONS,
|
|
11702
|
+
PEER_GROUP_SCOPE_PREFIX,
|
|
11703
|
+
DEFAULT_PEER_GROUP_OPTIONS,
|
|
11704
|
+
peerGroupScope,
|
|
11705
|
+
parsePeerGroupScope,
|
|
11706
|
+
selectFanoutPeers
|
|
11211
11707
|
};
|
|
11212
11708
|
//# sourceMappingURL=dignity.cjs.js.map
|