dignity.js 0.3.0 → 0.5.1
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 +142 -4
- package/dist/dignity.cjs.js +768 -20
- package/dist/dignity.cjs.js.map +4 -4
- package/dist/dignity.esm.js +768 -20
- package/dist/dignity.esm.js.map +3 -3
- package/dist/dignity.min.js +18 -18
- package/docs/assets/dignity.esm.js +11205 -0
- package/docs/assets/docs.js +47 -0
- package/docs/assets/favicon.svg +8 -0
- package/docs/assets/highlight/github-dark.min.css +10 -0
- package/docs/assets/highlight/github.min.css +10 -0
- package/docs/assets/highlight/highlight.min.js +1244 -0
- package/docs/assets/styles.css +449 -38
- package/docs/chess/assets/chess-app.js +58022 -0
- package/docs/chess/assets/chess-app.js.map +7 -0
- package/docs/chess/assets/chess.css +584 -0
- package/docs/chess/favicon.ico +0 -0
- package/docs/chess/index.html +16 -0
- package/docs/chess/src/App.jsx +128 -0
- package/docs/chess/src/components/Board3D.jsx +364 -0
- package/docs/chess/src/components/GameView.jsx +847 -0
- package/docs/chess/src/components/JoinGate.jsx +68 -0
- package/docs/chess/src/components/LinkPanel.jsx +132 -0
- package/docs/chess/src/components/Lobby.jsx +154 -0
- package/docs/chess/src/components/MovePanel.jsx +123 -0
- package/docs/chess/src/lib/audio.js +50 -0
- package/docs/chess/src/lib/dignitySetup.js +42 -0
- package/docs/chess/src/lib/links.js +124 -0
- package/docs/chess/src/lib/localGames.js +160 -0
- package/docs/chess/src/lib/p2pDebug.js +192 -0
- package/docs/chess/src/main.jsx +5 -0
- package/docs/favicon.ico +0 -0
- package/docs/index.html +605 -81
- package/docs/openapi-like.json +74 -7
- package/examples/decentralized-chess-lite.js +52 -30
- package/package.json +30 -4
- package/src/core/dignity-p2p.js +466 -15
- package/src/index.js +8 -0
- package/src/network/peerjs-network.js +234 -0
- package/src/persistence/indexeddb-persistence.js +184 -0
- package/src/react/index.js +256 -0
- package/src/signaling/parse-peerjs-url.js +24 -0
- package/src/signaling/peerjs-signaling-provider.js +2 -8
package/dist/dignity.esm.js
CHANGED
|
@@ -2934,12 +2934,71 @@ var require_dignity_p2p = __commonJS({
|
|
|
2934
2934
|
return {
|
|
2935
2935
|
id: record.id,
|
|
2936
2936
|
ownerId: record.ownerId,
|
|
2937
|
+
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
2937
2938
|
createdAt: record.createdAt,
|
|
2938
2939
|
updatedAt: record.updatedAt,
|
|
2939
2940
|
version: record.version,
|
|
2940
2941
|
data: { ...record.data }
|
|
2941
2942
|
};
|
|
2942
2943
|
}
|
|
2944
|
+
canUpdateRecord(record, actorId) {
|
|
2945
|
+
if (!record || !actorId) {
|
|
2946
|
+
return false;
|
|
2947
|
+
}
|
|
2948
|
+
if (record.ownerId === actorId) {
|
|
2949
|
+
return true;
|
|
2950
|
+
}
|
|
2951
|
+
return Array.isArray(record.collaboratorIds) && record.collaboratorIds.includes(actorId);
|
|
2952
|
+
}
|
|
2953
|
+
normalizeCollaboratorIds(collaborators) {
|
|
2954
|
+
if (!Array.isArray(collaborators)) {
|
|
2955
|
+
return [];
|
|
2956
|
+
}
|
|
2957
|
+
return [...new Set(collaborators.filter(Boolean))];
|
|
2958
|
+
}
|
|
2959
|
+
getRecordPeerIds(collectionName, id, options = {}) {
|
|
2960
|
+
const record = options.fromRecord || this.getCollection(collectionName).get(id);
|
|
2961
|
+
if (!record) {
|
|
2962
|
+
return [];
|
|
2963
|
+
}
|
|
2964
|
+
const includeSelf = options.includeSelf === true;
|
|
2965
|
+
const peerIds = [record.ownerId, ...record.collaboratorIds || []];
|
|
2966
|
+
return [...new Set(peerIds.filter(Boolean).filter((peerId) => includeSelf || peerId !== this.nodeId))];
|
|
2967
|
+
}
|
|
2968
|
+
resolveReplicationPeers(collectionName, id, options = {}, hints = {}) {
|
|
2969
|
+
if (options.connectToPeers === false) {
|
|
2970
|
+
return void 0;
|
|
2971
|
+
}
|
|
2972
|
+
if (Array.isArray(options.connectToPeers)) {
|
|
2973
|
+
return options.connectToPeers;
|
|
2974
|
+
}
|
|
2975
|
+
const peerIds = /* @__PURE__ */ new Set();
|
|
2976
|
+
if (hints.fromRecord) {
|
|
2977
|
+
for (const peerId of this.getRecordPeerIds(collectionName, id, {
|
|
2978
|
+
fromRecord: hints.fromRecord,
|
|
2979
|
+
includeSelf: true
|
|
2980
|
+
})) {
|
|
2981
|
+
peerIds.add(peerId);
|
|
2982
|
+
}
|
|
2983
|
+
} else if (id) {
|
|
2984
|
+
for (const peerId of this.getRecordPeerIds(collectionName, id, { includeSelf: true })) {
|
|
2985
|
+
peerIds.add(peerId);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
if (Array.isArray(options.collaborators)) {
|
|
2989
|
+
for (const peerId of this.normalizeCollaboratorIds(options.collaborators)) {
|
|
2990
|
+
peerIds.add(peerId);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
if (Array.isArray(hints.extraPeerIds)) {
|
|
2994
|
+
for (const peerId of hints.extraPeerIds) {
|
|
2995
|
+
if (peerId) {
|
|
2996
|
+
peerIds.add(peerId);
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
return [...peerIds].filter((peerId) => peerId && peerId !== this.nodeId);
|
|
3001
|
+
}
|
|
2943
3002
|
async create(collectionName, data, options = {}) {
|
|
2944
3003
|
const collection = this.getCollection(collectionName);
|
|
2945
3004
|
const id = options.id || this.idGenerator();
|
|
@@ -2947,6 +3006,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
2947
3006
|
throw new Error(`Object ${id} already exists in ${collectionName}`);
|
|
2948
3007
|
}
|
|
2949
3008
|
const timestamp = this.now();
|
|
3009
|
+
const collaboratorIds = this.normalizeCollaboratorIds(options.collaborators);
|
|
2950
3010
|
const operation = {
|
|
2951
3011
|
opId: this.idGenerator(),
|
|
2952
3012
|
kind: "create",
|
|
@@ -2954,6 +3014,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
2954
3014
|
id,
|
|
2955
3015
|
actorId: this.nodeId,
|
|
2956
3016
|
ownerId: this.nodeId,
|
|
3017
|
+
collaboratorIds,
|
|
2957
3018
|
timestamp,
|
|
2958
3019
|
payload: { ...data }
|
|
2959
3020
|
};
|
|
@@ -2963,6 +3024,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
2963
3024
|
messageType: "operation",
|
|
2964
3025
|
operation,
|
|
2965
3026
|
collectionName
|
|
3027
|
+
}),
|
|
3028
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, null, options, {
|
|
3029
|
+
extraPeerIds: options.collaborators
|
|
2966
3030
|
})
|
|
2967
3031
|
});
|
|
2968
3032
|
return this.read(collectionName, id);
|
|
@@ -2997,8 +3061,26 @@ var require_dignity_p2p = __commonJS({
|
|
|
2997
3061
|
if (!existing || existing.deletedAt) {
|
|
2998
3062
|
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
2999
3063
|
}
|
|
3000
|
-
if (existing
|
|
3001
|
-
throw new Error(`Only owner ${existing.ownerId} can update object ${id}`);
|
|
3064
|
+
if (!this.canUpdateRecord(existing, this.nodeId)) {
|
|
3065
|
+
throw new Error(`Only owner ${existing.ownerId} or collaborators can update object ${id}`);
|
|
3066
|
+
}
|
|
3067
|
+
if (options.collaborators !== void 0 && existing.ownerId !== this.nodeId) {
|
|
3068
|
+
throw new Error(`Only owner ${existing.ownerId} can change collaborators on object ${id}`);
|
|
3069
|
+
}
|
|
3070
|
+
if (typeof options.expectedVersion === "number" && existing.version !== options.expectedVersion) {
|
|
3071
|
+
this.emitConflict({
|
|
3072
|
+
kind: "update",
|
|
3073
|
+
collection: collectionName,
|
|
3074
|
+
id,
|
|
3075
|
+
expectedVersion: options.expectedVersion,
|
|
3076
|
+
currentVersion: existing.version,
|
|
3077
|
+
phase: "local"
|
|
3078
|
+
});
|
|
3079
|
+
const error = new Error(
|
|
3080
|
+
`Version conflict on ${collectionName}/${id}: expected ${options.expectedVersion}, current ${existing.version}`
|
|
3081
|
+
);
|
|
3082
|
+
error.code = "VERSION_CONFLICT";
|
|
3083
|
+
throw error;
|
|
3002
3084
|
}
|
|
3003
3085
|
const operation = {
|
|
3004
3086
|
opId: this.idGenerator(),
|
|
@@ -3010,12 +3092,73 @@ var require_dignity_p2p = __commonJS({
|
|
|
3010
3092
|
baseVersion: existing.version,
|
|
3011
3093
|
payload: { ...partialData }
|
|
3012
3094
|
};
|
|
3095
|
+
if (options.collaborators !== void 0) {
|
|
3096
|
+
operation.collaboratorIds = this.normalizeCollaboratorIds(options.collaborators);
|
|
3097
|
+
}
|
|
3098
|
+
this.applyOperation(operation);
|
|
3099
|
+
await this.broadcastMessage("operation", operation, {
|
|
3100
|
+
broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
|
|
3101
|
+
messageType: "operation",
|
|
3102
|
+
operation,
|
|
3103
|
+
collectionName
|
|
3104
|
+
}),
|
|
3105
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: existing })
|
|
3106
|
+
});
|
|
3107
|
+
return this.read(collectionName, id);
|
|
3108
|
+
}
|
|
3109
|
+
async updateWithRetry(collectionName, id, patchFn, options = {}) {
|
|
3110
|
+
const maxAttempts = typeof options.maxAttempts === "number" ? options.maxAttempts : 5;
|
|
3111
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
3112
|
+
const current = this.read(collectionName, id);
|
|
3113
|
+
if (!current) {
|
|
3114
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3115
|
+
}
|
|
3116
|
+
const patch = await patchFn(current);
|
|
3117
|
+
try {
|
|
3118
|
+
return await this.update(collectionName, id, patch, {
|
|
3119
|
+
...options,
|
|
3120
|
+
expectedVersion: current.version
|
|
3121
|
+
});
|
|
3122
|
+
} catch (error) {
|
|
3123
|
+
if (error.code !== "VERSION_CONFLICT" || attempt === maxAttempts - 1) {
|
|
3124
|
+
throw error;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
throw new Error(`Unable to update ${collectionName}/${id} after ${maxAttempts} attempts`);
|
|
3129
|
+
}
|
|
3130
|
+
async transferOwnership(collectionName, id, newOwnerId, options = {}) {
|
|
3131
|
+
if (!newOwnerId) {
|
|
3132
|
+
throw new Error("newOwnerId is required");
|
|
3133
|
+
}
|
|
3134
|
+
const existing = this.getCollection(collectionName).get(id);
|
|
3135
|
+
if (!existing || existing.deletedAt) {
|
|
3136
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3137
|
+
}
|
|
3138
|
+
if (existing.ownerId !== this.nodeId) {
|
|
3139
|
+
throw new Error(`Only owner ${existing.ownerId} can transfer object ${id}`);
|
|
3140
|
+
}
|
|
3141
|
+
const operation = {
|
|
3142
|
+
opId: this.idGenerator(),
|
|
3143
|
+
kind: "transfer-ownership",
|
|
3144
|
+
collectionName,
|
|
3145
|
+
id,
|
|
3146
|
+
actorId: this.nodeId,
|
|
3147
|
+
timestamp: this.now(),
|
|
3148
|
+
baseVersion: existing.version,
|
|
3149
|
+
newOwnerId,
|
|
3150
|
+
keepPreviousOwnerAsCollaborator: options.keepAsCollaborator !== false
|
|
3151
|
+
};
|
|
3013
3152
|
this.applyOperation(operation);
|
|
3014
3153
|
await this.broadcastMessage("operation", operation, {
|
|
3015
3154
|
broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
|
|
3016
3155
|
messageType: "operation",
|
|
3017
3156
|
operation,
|
|
3018
3157
|
collectionName
|
|
3158
|
+
}),
|
|
3159
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, {
|
|
3160
|
+
fromRecord: existing,
|
|
3161
|
+
extraPeerIds: [newOwnerId]
|
|
3019
3162
|
})
|
|
3020
3163
|
});
|
|
3021
3164
|
return this.read(collectionName, id);
|
|
@@ -3043,16 +3186,74 @@ var require_dignity_p2p = __commonJS({
|
|
|
3043
3186
|
messageType: "operation",
|
|
3044
3187
|
operation,
|
|
3045
3188
|
collectionName
|
|
3046
|
-
})
|
|
3189
|
+
}),
|
|
3190
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: existing })
|
|
3047
3191
|
});
|
|
3048
3192
|
}
|
|
3049
3193
|
registerPeerPublicKey(peerId, publicKey) {
|
|
3050
3194
|
this.securityService.registerPeerPublicKey(peerId, publicKey);
|
|
3051
3195
|
}
|
|
3196
|
+
trustPeerPublicKey(peerId, publicKey) {
|
|
3197
|
+
if (!peerId || !publicKey) {
|
|
3198
|
+
return false;
|
|
3199
|
+
}
|
|
3200
|
+
try {
|
|
3201
|
+
this.registerPeerPublicKey(peerId, publicKey);
|
|
3202
|
+
return true;
|
|
3203
|
+
} catch (error) {
|
|
3204
|
+
this.emit("warning", { type: "peer-key-trust-failed", peerId, error });
|
|
3205
|
+
return false;
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
trustPeerFromMetadata(peerId, metadata) {
|
|
3209
|
+
if (!metadata || !metadata.publicKey) {
|
|
3210
|
+
return false;
|
|
3211
|
+
}
|
|
3212
|
+
return this.trustPeerPublicKey(peerId, metadata.publicKey);
|
|
3213
|
+
}
|
|
3052
3214
|
getPublicKey() {
|
|
3053
3215
|
return this.securityService.getPublicKey();
|
|
3054
3216
|
}
|
|
3217
|
+
async connectToPeer(peerId) {
|
|
3218
|
+
if (!peerId || peerId === this.nodeId) {
|
|
3219
|
+
return null;
|
|
3220
|
+
}
|
|
3221
|
+
if (typeof this.networkAdapter.connectToPeer !== "function") {
|
|
3222
|
+
throw new Error("Network adapter does not support connectToPeer");
|
|
3223
|
+
}
|
|
3224
|
+
return this.networkAdapter.connectToPeer(peerId);
|
|
3225
|
+
}
|
|
3226
|
+
getConnectionStats() {
|
|
3227
|
+
const adapter = this.networkAdapter;
|
|
3228
|
+
if (!adapter) {
|
|
3229
|
+
return { openCount: 0, peerIds: [] };
|
|
3230
|
+
}
|
|
3231
|
+
const peerIds = typeof adapter.listOpenPeerIds === "function" ? adapter.listOpenPeerIds() : [];
|
|
3232
|
+
const openCount = typeof adapter.getOpenConnectionCount === "function" ? adapter.getOpenConnectionCount() : peerIds.length;
|
|
3233
|
+
return { openCount, peerIds };
|
|
3234
|
+
}
|
|
3235
|
+
async ensureConnectedToPeers(peerIds = []) {
|
|
3236
|
+
const normalized = [...new Set((peerIds || []).filter(Boolean))];
|
|
3237
|
+
const results = [];
|
|
3238
|
+
for (const peerId of normalized) {
|
|
3239
|
+
if (peerId === this.nodeId) {
|
|
3240
|
+
continue;
|
|
3241
|
+
}
|
|
3242
|
+
try {
|
|
3243
|
+
await this.connectToPeer(peerId);
|
|
3244
|
+
results.push({ peerId, ok: true });
|
|
3245
|
+
} catch (error) {
|
|
3246
|
+
this.emit("warning", { type: "peer-connect-failed", peerId, error });
|
|
3247
|
+
results.push({ peerId, ok: false, error });
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
return results;
|
|
3251
|
+
}
|
|
3055
3252
|
async broadcastMessage(messageType, payload, securityContext = {}) {
|
|
3253
|
+
const connectToPeers = securityContext.connectToPeers;
|
|
3254
|
+
if (Array.isArray(connectToPeers) && connectToPeers.length > 0) {
|
|
3255
|
+
await this.ensureConnectedToPeers(connectToPeers);
|
|
3256
|
+
}
|
|
3056
3257
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3057
3258
|
messageType,
|
|
3058
3259
|
payload,
|
|
@@ -3062,6 +3263,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
3062
3263
|
await this.networkAdapter.broadcast(envelope);
|
|
3063
3264
|
}
|
|
3064
3265
|
async sendDirectMessage(targetId, messageType, payload) {
|
|
3266
|
+
if (targetId) {
|
|
3267
|
+
try {
|
|
3268
|
+
await this.connectToPeer(targetId);
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
this.emit("warning", { type: "direct-message-connect-failed", targetId, error });
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3065
3273
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3066
3274
|
messageType,
|
|
3067
3275
|
payload,
|
|
@@ -3086,6 +3294,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3086
3294
|
expiresAt: announcedAt + ttlMs
|
|
3087
3295
|
};
|
|
3088
3296
|
map.set(peerId, next);
|
|
3297
|
+
this.trustPeerFromMetadata(peerId, next.metadata);
|
|
3089
3298
|
if (!existing) {
|
|
3090
3299
|
this.emit("peerdiscovered", { scope, peerId, metadata: next.metadata });
|
|
3091
3300
|
}
|
|
@@ -3108,11 +3317,18 @@ var require_dignity_p2p = __commonJS({
|
|
|
3108
3317
|
const normalizedScope = scope || "main";
|
|
3109
3318
|
const heartbeatIntervalMs = options.heartbeatIntervalMs || this.defaultDiscoveryHeartbeatMs;
|
|
3110
3319
|
const ttlMs = options.ttlMs || this.defaultPresenceTtlMs;
|
|
3111
|
-
const metadata =
|
|
3320
|
+
const metadata = {
|
|
3321
|
+
publicKey: this.getPublicKey(),
|
|
3322
|
+
...options.metadata || {}
|
|
3323
|
+
};
|
|
3324
|
+
const bootstrapPeerIds = Array.isArray(options.bootstrapPeerIds) ? [...new Set(options.bootstrapPeerIds.filter(Boolean))] : [];
|
|
3112
3325
|
const existing = this.discoveryRooms.get(normalizedScope);
|
|
3113
3326
|
if (existing && existing.timer) {
|
|
3114
3327
|
clearInterval(existing.timer);
|
|
3115
3328
|
}
|
|
3329
|
+
if (bootstrapPeerIds.length > 0) {
|
|
3330
|
+
await this.ensureConnectedToPeers(bootstrapPeerIds);
|
|
3331
|
+
}
|
|
3116
3332
|
const timer = setInterval(() => {
|
|
3117
3333
|
this.announcePresence(normalizedScope).catch((error) => {
|
|
3118
3334
|
this.emit("warning", { type: "presence-heartbeat-failed", scope: normalizedScope, error });
|
|
@@ -3120,6 +3336,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3120
3336
|
}, heartbeatIntervalMs);
|
|
3121
3337
|
this.discoveryRooms.set(normalizedScope, {
|
|
3122
3338
|
metadata,
|
|
3339
|
+
bootstrapPeerIds,
|
|
3123
3340
|
heartbeatIntervalMs,
|
|
3124
3341
|
ttlMs,
|
|
3125
3342
|
timer
|
|
@@ -3200,6 +3417,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3200
3417
|
});
|
|
3201
3418
|
return;
|
|
3202
3419
|
}
|
|
3420
|
+
if (message && message.senderId && message.senderPublicKey) {
|
|
3421
|
+
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3422
|
+
}
|
|
3203
3423
|
let decrypted;
|
|
3204
3424
|
try {
|
|
3205
3425
|
decrypted = await this.securityService.decryptIncomingMessage(message);
|
|
@@ -3221,6 +3441,21 @@ var require_dignity_p2p = __commonJS({
|
|
|
3221
3441
|
this.applyOperation(decrypted.payload);
|
|
3222
3442
|
return;
|
|
3223
3443
|
}
|
|
3444
|
+
if (decrypted.messageType === "record:snapshot") {
|
|
3445
|
+
const payload = decrypted.payload || {};
|
|
3446
|
+
const { collectionName, record } = payload;
|
|
3447
|
+
if (collectionName && record) {
|
|
3448
|
+
const applied = this.restoreRecord(collectionName, record);
|
|
3449
|
+
if (applied) {
|
|
3450
|
+
this.emit("change", {
|
|
3451
|
+
kind: "snapshot",
|
|
3452
|
+
collection: collectionName,
|
|
3453
|
+
id: record.id
|
|
3454
|
+
});
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3224
3459
|
if (decrypted.messageType === "presence:announce") {
|
|
3225
3460
|
const payload = decrypted.payload || {};
|
|
3226
3461
|
const scope = payload.scope || "main";
|
|
@@ -3238,6 +3473,11 @@ var require_dignity_p2p = __commonJS({
|
|
|
3238
3473
|
payload.announcedAt || this.now()
|
|
3239
3474
|
);
|
|
3240
3475
|
if (isNewPeerInScope && peerId !== this.nodeId && this.discoveryRooms.has(scope)) {
|
|
3476
|
+
if (typeof this.networkAdapter.connectToPeer === "function") {
|
|
3477
|
+
Promise.resolve(this.connectToPeer(peerId)).catch((error) => {
|
|
3478
|
+
this.emit("warning", { type: "peer-connect-failed", scope, peerId, error });
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3241
3481
|
this.announcePresence(scope).catch((error) => {
|
|
3242
3482
|
this.emit("warning", { type: "presence-handshake-failed", scope, error });
|
|
3243
3483
|
});
|
|
@@ -3297,6 +3537,56 @@ var require_dignity_p2p = __commonJS({
|
|
|
3297
3537
|
isPeerBanned(peerId) {
|
|
3298
3538
|
return this.getBanInfo(peerId) !== null;
|
|
3299
3539
|
}
|
|
3540
|
+
emitConflict(details) {
|
|
3541
|
+
this.emit("conflict", details);
|
|
3542
|
+
}
|
|
3543
|
+
restoreRecord(collectionName, record) {
|
|
3544
|
+
if (!record || !record.id) {
|
|
3545
|
+
return false;
|
|
3546
|
+
}
|
|
3547
|
+
const collection = this.getCollection(collectionName);
|
|
3548
|
+
const current = collection.get(record.id);
|
|
3549
|
+
if (current && current.version >= record.version) {
|
|
3550
|
+
return false;
|
|
3551
|
+
}
|
|
3552
|
+
collection.set(record.id, {
|
|
3553
|
+
id: record.id,
|
|
3554
|
+
ownerId: record.ownerId,
|
|
3555
|
+
collaboratorIds: this.normalizeCollaboratorIds(record.collaboratorIds),
|
|
3556
|
+
data: { ...record.data || {} },
|
|
3557
|
+
createdAt: record.createdAt,
|
|
3558
|
+
updatedAt: record.updatedAt,
|
|
3559
|
+
deletedAt: record.deletedAt || null,
|
|
3560
|
+
version: record.version
|
|
3561
|
+
});
|
|
3562
|
+
return true;
|
|
3563
|
+
}
|
|
3564
|
+
async pushRecordSnapshot(collectionName, id, options = {}) {
|
|
3565
|
+
const collection = this.getCollection(collectionName);
|
|
3566
|
+
const raw = collection.get(id);
|
|
3567
|
+
if (!raw || raw.deletedAt) {
|
|
3568
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3569
|
+
}
|
|
3570
|
+
const record = {
|
|
3571
|
+
id: raw.id,
|
|
3572
|
+
ownerId: raw.ownerId,
|
|
3573
|
+
collaboratorIds: Array.isArray(raw.collaboratorIds) ? [...raw.collaboratorIds] : [],
|
|
3574
|
+
data: { ...raw.data },
|
|
3575
|
+
createdAt: raw.createdAt,
|
|
3576
|
+
updatedAt: raw.updatedAt,
|
|
3577
|
+
deletedAt: raw.deletedAt || null,
|
|
3578
|
+
version: raw.version
|
|
3579
|
+
};
|
|
3580
|
+
await this.broadcastMessage("record:snapshot", { collectionName, record }, {
|
|
3581
|
+
broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
|
|
3582
|
+
messageType: "record:snapshot",
|
|
3583
|
+
collectionName,
|
|
3584
|
+
id
|
|
3585
|
+
}),
|
|
3586
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: raw })
|
|
3587
|
+
});
|
|
3588
|
+
return record;
|
|
3589
|
+
}
|
|
3300
3590
|
applyOperation(operation) {
|
|
3301
3591
|
if (!operation || !operation.opId || this.appliedOperations.has(operation.opId)) {
|
|
3302
3592
|
return false;
|
|
@@ -3310,6 +3600,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3310
3600
|
collection.set(operation.id, {
|
|
3311
3601
|
id: operation.id,
|
|
3312
3602
|
ownerId: operation.ownerId,
|
|
3603
|
+
collaboratorIds: this.normalizeCollaboratorIds(operation.collaboratorIds),
|
|
3313
3604
|
data: { ...operation.payload },
|
|
3314
3605
|
createdAt: operation.timestamp,
|
|
3315
3606
|
updatedAt: operation.timestamp,
|
|
@@ -3321,12 +3612,91 @@ var require_dignity_p2p = __commonJS({
|
|
|
3321
3612
|
return true;
|
|
3322
3613
|
}
|
|
3323
3614
|
if (!current || current.deletedAt) {
|
|
3615
|
+
if (operation.kind !== "create") {
|
|
3616
|
+
this.emit("warning", {
|
|
3617
|
+
type: "orphan-operation",
|
|
3618
|
+
kind: operation.kind,
|
|
3619
|
+
collection: operation.collectionName,
|
|
3620
|
+
id: operation.id,
|
|
3621
|
+
actorId: operation.actorId,
|
|
3622
|
+
hint: "Peer is missing the record; pushRecordSnapshot from the owner to catch up."
|
|
3623
|
+
});
|
|
3624
|
+
}
|
|
3324
3625
|
return false;
|
|
3325
3626
|
}
|
|
3326
|
-
if (operation.
|
|
3627
|
+
if (operation.kind === "transfer-ownership") {
|
|
3628
|
+
if (operation.actorId !== current.ownerId) {
|
|
3629
|
+
return false;
|
|
3630
|
+
}
|
|
3631
|
+
if (typeof operation.baseVersion === "number" && operation.baseVersion !== current.version) {
|
|
3632
|
+
this.emitConflict({
|
|
3633
|
+
kind: operation.kind,
|
|
3634
|
+
collection: operation.collectionName,
|
|
3635
|
+
id: operation.id,
|
|
3636
|
+
expectedVersion: operation.baseVersion,
|
|
3637
|
+
currentVersion: current.version,
|
|
3638
|
+
phase: "remote",
|
|
3639
|
+
operation
|
|
3640
|
+
});
|
|
3641
|
+
return false;
|
|
3642
|
+
}
|
|
3643
|
+
const previousOwnerId = current.ownerId;
|
|
3644
|
+
current.ownerId = operation.newOwnerId;
|
|
3645
|
+
if (operation.keepPreviousOwnerAsCollaborator !== false) {
|
|
3646
|
+
const collaborators = this.normalizeCollaboratorIds(current.collaboratorIds);
|
|
3647
|
+
if (!collaborators.includes(previousOwnerId)) {
|
|
3648
|
+
collaborators.push(previousOwnerId);
|
|
3649
|
+
}
|
|
3650
|
+
current.collaboratorIds = collaborators.filter((peerId) => peerId !== operation.newOwnerId);
|
|
3651
|
+
}
|
|
3652
|
+
current.updatedAt = operation.timestamp;
|
|
3653
|
+
current.version += 1;
|
|
3654
|
+
this.appliedOperations.add(operation.opId);
|
|
3655
|
+
this.emit("change", {
|
|
3656
|
+
kind: "transfer-ownership",
|
|
3657
|
+
collection: operation.collectionName,
|
|
3658
|
+
id: operation.id,
|
|
3659
|
+
previousOwnerId,
|
|
3660
|
+
newOwnerId: operation.newOwnerId
|
|
3661
|
+
});
|
|
3662
|
+
return true;
|
|
3663
|
+
}
|
|
3664
|
+
if (operation.kind === "delete") {
|
|
3665
|
+
if (operation.actorId !== current.ownerId) {
|
|
3666
|
+
return false;
|
|
3667
|
+
}
|
|
3668
|
+
if (typeof operation.baseVersion === "number" && operation.baseVersion !== current.version) {
|
|
3669
|
+
this.emitConflict({
|
|
3670
|
+
kind: operation.kind,
|
|
3671
|
+
collection: operation.collectionName,
|
|
3672
|
+
id: operation.id,
|
|
3673
|
+
expectedVersion: operation.baseVersion,
|
|
3674
|
+
currentVersion: current.version,
|
|
3675
|
+
phase: "remote",
|
|
3676
|
+
operation
|
|
3677
|
+
});
|
|
3678
|
+
return false;
|
|
3679
|
+
}
|
|
3680
|
+
current.deletedAt = operation.timestamp;
|
|
3681
|
+
current.updatedAt = operation.timestamp;
|
|
3682
|
+
current.version += 1;
|
|
3683
|
+
this.appliedOperations.add(operation.opId);
|
|
3684
|
+
this.emit("change", { kind: "delete", collection: operation.collectionName, id: operation.id });
|
|
3685
|
+
return true;
|
|
3686
|
+
}
|
|
3687
|
+
if (!this.canUpdateRecord(current, operation.actorId)) {
|
|
3327
3688
|
return false;
|
|
3328
3689
|
}
|
|
3329
3690
|
if (typeof operation.baseVersion === "number" && operation.baseVersion !== current.version) {
|
|
3691
|
+
this.emitConflict({
|
|
3692
|
+
kind: operation.kind,
|
|
3693
|
+
collection: operation.collectionName,
|
|
3694
|
+
id: operation.id,
|
|
3695
|
+
expectedVersion: operation.baseVersion,
|
|
3696
|
+
currentVersion: current.version,
|
|
3697
|
+
phase: "remote",
|
|
3698
|
+
operation
|
|
3699
|
+
});
|
|
3330
3700
|
return false;
|
|
3331
3701
|
}
|
|
3332
3702
|
if (operation.kind === "update") {
|
|
@@ -3334,20 +3704,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3334
3704
|
...current.data,
|
|
3335
3705
|
...operation.payload
|
|
3336
3706
|
};
|
|
3707
|
+
if (Array.isArray(operation.collaboratorIds) && operation.actorId === current.ownerId) {
|
|
3708
|
+
current.collaboratorIds = this.normalizeCollaboratorIds(operation.collaboratorIds);
|
|
3709
|
+
}
|
|
3337
3710
|
current.updatedAt = operation.timestamp;
|
|
3338
3711
|
current.version += 1;
|
|
3339
3712
|
this.appliedOperations.add(operation.opId);
|
|
3340
3713
|
this.emit("change", { kind: "update", collection: operation.collectionName, id: operation.id });
|
|
3341
3714
|
return true;
|
|
3342
3715
|
}
|
|
3343
|
-
if (operation.kind === "delete") {
|
|
3344
|
-
current.deletedAt = operation.timestamp;
|
|
3345
|
-
current.updatedAt = operation.timestamp;
|
|
3346
|
-
current.version += 1;
|
|
3347
|
-
this.appliedOperations.add(operation.opId);
|
|
3348
|
-
this.emit("change", { kind: "delete", collection: operation.collectionName, id: operation.id });
|
|
3349
|
-
return true;
|
|
3350
|
-
}
|
|
3351
3716
|
return false;
|
|
3352
3717
|
}
|
|
3353
3718
|
};
|
|
@@ -3502,6 +3867,28 @@ var require_websocket_signaling_provider = __commonJS({
|
|
|
3502
3867
|
}
|
|
3503
3868
|
});
|
|
3504
3869
|
|
|
3870
|
+
// src/signaling/parse-peerjs-url.js
|
|
3871
|
+
var require_parse_peerjs_url = __commonJS({
|
|
3872
|
+
"src/signaling/parse-peerjs-url.js"(exports, module) {
|
|
3873
|
+
function parsePeerJsServerUrl(url) {
|
|
3874
|
+
const parsed = new URL(url);
|
|
3875
|
+
const secure = parsed.protocol === "wss:";
|
|
3876
|
+
const host = parsed.hostname;
|
|
3877
|
+
const port = parsed.port ? Number(parsed.port) : secure ? 443 : 80;
|
|
3878
|
+
const key = parsed.searchParams.get("key") || "peerjs";
|
|
3879
|
+
let path = parsed.pathname || "/";
|
|
3880
|
+
if (path.endsWith("/peerjs")) {
|
|
3881
|
+
path = path.slice(0, -"/peerjs".length) || "/";
|
|
3882
|
+
}
|
|
3883
|
+
if (path !== "/" && !path.endsWith("/")) {
|
|
3884
|
+
path += "/";
|
|
3885
|
+
}
|
|
3886
|
+
return { secure, host, port, path, key };
|
|
3887
|
+
}
|
|
3888
|
+
module.exports = parsePeerJsServerUrl;
|
|
3889
|
+
}
|
|
3890
|
+
});
|
|
3891
|
+
|
|
3505
3892
|
// node_modules/peerjs-js-binarypack/dist/binarypack.cjs
|
|
3506
3893
|
var require_binarypack = __commonJS({
|
|
3507
3894
|
"node_modules/peerjs-js-binarypack/dist/binarypack.cjs"(exports, module) {
|
|
@@ -10101,6 +10488,7 @@ var require_bundler = __commonJS({
|
|
|
10101
10488
|
var require_peerjs_signaling_provider = __commonJS({
|
|
10102
10489
|
"src/signaling/peerjs-signaling-provider.js"(exports, module) {
|
|
10103
10490
|
var WebSocketSignalingProvider = require_websocket_signaling_provider();
|
|
10491
|
+
var parsePeerJsServerUrl = require_parse_peerjs_url();
|
|
10104
10492
|
var PeerJSSignalingProvider = class {
|
|
10105
10493
|
constructor({ id, url, PeerImpl, WebSocketImpl, priority = 0, connectTimeoutMs = 1e4 }) {
|
|
10106
10494
|
if (!url) {
|
|
@@ -10128,13 +10516,7 @@ var require_peerjs_signaling_provider = __commonJS({
|
|
|
10128
10516
|
}
|
|
10129
10517
|
}
|
|
10130
10518
|
parsePeerJsServerUrl() {
|
|
10131
|
-
|
|
10132
|
-
const secure = parsed.protocol === "wss:";
|
|
10133
|
-
const host = parsed.hostname;
|
|
10134
|
-
const port = parsed.port ? Number(parsed.port) : secure ? 443 : 80;
|
|
10135
|
-
const path = parsed.pathname || "/";
|
|
10136
|
-
const key = parsed.searchParams.get("key") || "peerjs";
|
|
10137
|
-
return { secure, host, port, path, key };
|
|
10519
|
+
return parsePeerJsServerUrl(this.url);
|
|
10138
10520
|
}
|
|
10139
10521
|
shouldUseWebSocketFallback() {
|
|
10140
10522
|
return !this.isCustomPeerImpl && typeof globalThis.RTCPeerConnection !== "function";
|
|
@@ -10414,6 +10796,364 @@ var require_in_memory_network = __commonJS({
|
|
|
10414
10796
|
}
|
|
10415
10797
|
});
|
|
10416
10798
|
|
|
10799
|
+
// src/network/peerjs-network.js
|
|
10800
|
+
var require_peerjs_network = __commonJS({
|
|
10801
|
+
"src/network/peerjs-network.js"(exports, module) {
|
|
10802
|
+
var { DEFAULT_CLOUDFLARE_SIGNALING_URLS } = require_default_signaling_config();
|
|
10803
|
+
var parsePeerJsServerUrl = require_parse_peerjs_url();
|
|
10804
|
+
function resolvePeerImplementation(PeerImpl) {
|
|
10805
|
+
if (PeerImpl) {
|
|
10806
|
+
return PeerImpl;
|
|
10807
|
+
}
|
|
10808
|
+
try {
|
|
10809
|
+
const peerjs = require_bundler();
|
|
10810
|
+
return peerjs.Peer || peerjs;
|
|
10811
|
+
} catch (error) {
|
|
10812
|
+
return null;
|
|
10813
|
+
}
|
|
10814
|
+
}
|
|
10815
|
+
var PeerJSNetworkAdapter = class {
|
|
10816
|
+
constructor({
|
|
10817
|
+
url,
|
|
10818
|
+
urls,
|
|
10819
|
+
PeerImpl,
|
|
10820
|
+
connectTimeoutMs = 12e3
|
|
10821
|
+
} = {}) {
|
|
10822
|
+
this.urls = urls || (url ? [url] : [...DEFAULT_CLOUDFLARE_SIGNALING_URLS]);
|
|
10823
|
+
this.url = this.urls[0];
|
|
10824
|
+
this.PeerImpl = resolvePeerImplementation(PeerImpl);
|
|
10825
|
+
this.connectTimeoutMs = connectTimeoutMs;
|
|
10826
|
+
this.nodeId = null;
|
|
10827
|
+
this.peer = null;
|
|
10828
|
+
this.connections = /* @__PURE__ */ new Map();
|
|
10829
|
+
this.pendingConnections = /* @__PURE__ */ new Map();
|
|
10830
|
+
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
10831
|
+
}
|
|
10832
|
+
async start(nodeId) {
|
|
10833
|
+
if (!nodeId) {
|
|
10834
|
+
throw new Error("PeerJSNetworkAdapter requires nodeId on start");
|
|
10835
|
+
}
|
|
10836
|
+
if (!this.PeerImpl) {
|
|
10837
|
+
throw new Error("PeerJS implementation is not available");
|
|
10838
|
+
}
|
|
10839
|
+
if (this.peer) {
|
|
10840
|
+
await this.stop();
|
|
10841
|
+
}
|
|
10842
|
+
let lastError;
|
|
10843
|
+
for (const candidateUrl of this.urls) {
|
|
10844
|
+
try {
|
|
10845
|
+
await this.startWithUrl(nodeId, candidateUrl);
|
|
10846
|
+
this.url = candidateUrl;
|
|
10847
|
+
return;
|
|
10848
|
+
} catch (error) {
|
|
10849
|
+
lastError = error;
|
|
10850
|
+
}
|
|
10851
|
+
}
|
|
10852
|
+
throw lastError || new Error("Unable to connect PeerJS network adapter");
|
|
10853
|
+
}
|
|
10854
|
+
async startWithUrl(nodeId, url) {
|
|
10855
|
+
this.nodeId = nodeId;
|
|
10856
|
+
const server = parsePeerJsServerUrl(url);
|
|
10857
|
+
await new Promise((resolve, reject) => {
|
|
10858
|
+
const peer = new this.PeerImpl(nodeId, {
|
|
10859
|
+
host: server.host,
|
|
10860
|
+
port: server.port,
|
|
10861
|
+
path: server.path,
|
|
10862
|
+
secure: server.secure,
|
|
10863
|
+
key: server.key
|
|
10864
|
+
});
|
|
10865
|
+
const timeout = setTimeout(() => {
|
|
10866
|
+
peer.destroy?.();
|
|
10867
|
+
reject(new Error(`Unable to connect PeerJS network adapter to ${url}`));
|
|
10868
|
+
}, this.connectTimeoutMs);
|
|
10869
|
+
peer.on("open", () => {
|
|
10870
|
+
clearTimeout(timeout);
|
|
10871
|
+
this.peer = peer;
|
|
10872
|
+
resolve();
|
|
10873
|
+
});
|
|
10874
|
+
peer.on("connection", (connection) => {
|
|
10875
|
+
this.attachConnectionHandlers(connection);
|
|
10876
|
+
});
|
|
10877
|
+
peer.on("error", (error) => {
|
|
10878
|
+
clearTimeout(timeout);
|
|
10879
|
+
peer.destroy?.();
|
|
10880
|
+
reject(error || new Error(`Unable to connect PeerJS network adapter to ${url}`));
|
|
10881
|
+
});
|
|
10882
|
+
});
|
|
10883
|
+
}
|
|
10884
|
+
attachConnectionHandlers(connection) {
|
|
10885
|
+
const remoteId = connection.peer;
|
|
10886
|
+
if (!remoteId) {
|
|
10887
|
+
return;
|
|
10888
|
+
}
|
|
10889
|
+
this.connections.set(remoteId, connection);
|
|
10890
|
+
connection.on("data", (payload) => {
|
|
10891
|
+
const deliveries = [];
|
|
10892
|
+
for (const handler of this.messageHandlers) {
|
|
10893
|
+
deliveries.push(handler(payload));
|
|
10894
|
+
}
|
|
10895
|
+
return Promise.all(deliveries);
|
|
10896
|
+
});
|
|
10897
|
+
connection.on("close", () => {
|
|
10898
|
+
this.connections.delete(remoteId);
|
|
10899
|
+
});
|
|
10900
|
+
}
|
|
10901
|
+
async connectToPeer(remotePeerId) {
|
|
10902
|
+
if (!remotePeerId || remotePeerId === this.nodeId) {
|
|
10903
|
+
return null;
|
|
10904
|
+
}
|
|
10905
|
+
const existing = this.connections.get(remotePeerId);
|
|
10906
|
+
if (existing && existing.open) {
|
|
10907
|
+
return existing;
|
|
10908
|
+
}
|
|
10909
|
+
if (this.pendingConnections.has(remotePeerId)) {
|
|
10910
|
+
return this.pendingConnections.get(remotePeerId);
|
|
10911
|
+
}
|
|
10912
|
+
if (!this.peer) {
|
|
10913
|
+
throw new Error("PeerJS network adapter has not been started");
|
|
10914
|
+
}
|
|
10915
|
+
const pending = new Promise((resolve, reject) => {
|
|
10916
|
+
const connection = this.peer.connect(remotePeerId, {
|
|
10917
|
+
reliable: true,
|
|
10918
|
+
serialization: "json"
|
|
10919
|
+
});
|
|
10920
|
+
const timeout = setTimeout(() => {
|
|
10921
|
+
reject(new Error(`Unable to connect to peer ${remotePeerId}`));
|
|
10922
|
+
}, this.connectTimeoutMs);
|
|
10923
|
+
connection.on("open", () => {
|
|
10924
|
+
clearTimeout(timeout);
|
|
10925
|
+
this.attachConnectionHandlers(connection);
|
|
10926
|
+
resolve(connection);
|
|
10927
|
+
});
|
|
10928
|
+
connection.on("error", () => {
|
|
10929
|
+
clearTimeout(timeout);
|
|
10930
|
+
reject(new Error(`Unable to connect to peer ${remotePeerId}`));
|
|
10931
|
+
});
|
|
10932
|
+
}).finally(() => {
|
|
10933
|
+
this.pendingConnections.delete(remotePeerId);
|
|
10934
|
+
});
|
|
10935
|
+
this.pendingConnections.set(remotePeerId, pending);
|
|
10936
|
+
return pending;
|
|
10937
|
+
}
|
|
10938
|
+
onMessage(handler) {
|
|
10939
|
+
this.messageHandlers.add(handler);
|
|
10940
|
+
}
|
|
10941
|
+
offMessage(handler) {
|
|
10942
|
+
this.messageHandlers.delete(handler);
|
|
10943
|
+
}
|
|
10944
|
+
async broadcast(message) {
|
|
10945
|
+
if (!this.peer) {
|
|
10946
|
+
throw new Error("PeerJS network adapter has not been started");
|
|
10947
|
+
}
|
|
10948
|
+
const deliveries = [];
|
|
10949
|
+
for (const connection of this.connections.values()) {
|
|
10950
|
+
if (connection.open) {
|
|
10951
|
+
deliveries.push(connection.send(message));
|
|
10952
|
+
}
|
|
10953
|
+
}
|
|
10954
|
+
await Promise.all(deliveries);
|
|
10955
|
+
}
|
|
10956
|
+
getOpenConnectionCount() {
|
|
10957
|
+
return this.listOpenPeerIds().length;
|
|
10958
|
+
}
|
|
10959
|
+
listOpenPeerIds() {
|
|
10960
|
+
const ids = [];
|
|
10961
|
+
for (const [peerId, connection] of this.connections.entries()) {
|
|
10962
|
+
if (connection.open) {
|
|
10963
|
+
ids.push(peerId);
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
return ids;
|
|
10967
|
+
}
|
|
10968
|
+
isConnectedTo(remotePeerId) {
|
|
10969
|
+
const connection = this.connections.get(remotePeerId);
|
|
10970
|
+
return Boolean(connection && connection.open);
|
|
10971
|
+
}
|
|
10972
|
+
async stop() {
|
|
10973
|
+
for (const connection of this.connections.values()) {
|
|
10974
|
+
if (typeof connection.close === "function") {
|
|
10975
|
+
connection.close();
|
|
10976
|
+
}
|
|
10977
|
+
}
|
|
10978
|
+
this.connections.clear();
|
|
10979
|
+
this.pendingConnections.clear();
|
|
10980
|
+
if (this.peer && typeof this.peer.destroy === "function") {
|
|
10981
|
+
this.peer.destroy();
|
|
10982
|
+
}
|
|
10983
|
+
this.peer = null;
|
|
10984
|
+
this.nodeId = null;
|
|
10985
|
+
}
|
|
10986
|
+
};
|
|
10987
|
+
function createPeerJSNetworkAdapter(options = {}) {
|
|
10988
|
+
return new PeerJSNetworkAdapter(options);
|
|
10989
|
+
}
|
|
10990
|
+
module.exports = {
|
|
10991
|
+
PeerJSNetworkAdapter,
|
|
10992
|
+
createPeerJSNetworkAdapter,
|
|
10993
|
+
parsePeerJsServerUrl
|
|
10994
|
+
};
|
|
10995
|
+
}
|
|
10996
|
+
});
|
|
10997
|
+
|
|
10998
|
+
// src/persistence/indexeddb-persistence.js
|
|
10999
|
+
var require_indexeddb_persistence = __commonJS({
|
|
11000
|
+
"src/persistence/indexeddb-persistence.js"(exports, module) {
|
|
11001
|
+
var IndexedDBPersistence = class {
|
|
11002
|
+
constructor({
|
|
11003
|
+
dbName = "dignity",
|
|
11004
|
+
storeName = "records",
|
|
11005
|
+
collections = null,
|
|
11006
|
+
indexedDB = typeof globalThis !== "undefined" ? globalThis.indexedDB : null
|
|
11007
|
+
} = {}) {
|
|
11008
|
+
this.dbName = dbName;
|
|
11009
|
+
this.storeName = storeName;
|
|
11010
|
+
this.collections = collections;
|
|
11011
|
+
this.indexedDB = indexedDB;
|
|
11012
|
+
this.node = null;
|
|
11013
|
+
this.changeHandler = null;
|
|
11014
|
+
}
|
|
11015
|
+
recordKey(collection, id) {
|
|
11016
|
+
return `${collection}:${id}`;
|
|
11017
|
+
}
|
|
11018
|
+
shouldPersist(collection) {
|
|
11019
|
+
if (!this.collections) {
|
|
11020
|
+
return true;
|
|
11021
|
+
}
|
|
11022
|
+
return this.collections.includes(collection);
|
|
11023
|
+
}
|
|
11024
|
+
openDb() {
|
|
11025
|
+
if (!this.indexedDB) {
|
|
11026
|
+
return Promise.reject(new Error("IndexedDB is not available"));
|
|
11027
|
+
}
|
|
11028
|
+
return new Promise((resolve, reject) => {
|
|
11029
|
+
const request = this.indexedDB.open(this.dbName, 1);
|
|
11030
|
+
request.onupgradeneeded = () => {
|
|
11031
|
+
const db = request.result;
|
|
11032
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
11033
|
+
db.createObjectStore(this.storeName, { keyPath: "key" });
|
|
11034
|
+
}
|
|
11035
|
+
};
|
|
11036
|
+
request.onsuccess = () => resolve(request.result);
|
|
11037
|
+
request.onerror = () => reject(request.error || new Error("Unable to open IndexedDB"));
|
|
11038
|
+
});
|
|
11039
|
+
}
|
|
11040
|
+
runTransaction(mode, handler) {
|
|
11041
|
+
return this.openDb().then((db) => new Promise((resolve, reject) => {
|
|
11042
|
+
const transaction = db.transaction(this.storeName, mode);
|
|
11043
|
+
const store = transaction.objectStore(this.storeName);
|
|
11044
|
+
Promise.resolve(handler(store)).then(resolve).catch(reject);
|
|
11045
|
+
transaction.oncomplete = () => db.close();
|
|
11046
|
+
transaction.onerror = () => reject(transaction.error || new Error("IndexedDB transaction failed"));
|
|
11047
|
+
transaction.onabort = () => reject(transaction.error || new Error("IndexedDB transaction aborted"));
|
|
11048
|
+
}));
|
|
11049
|
+
}
|
|
11050
|
+
serializeRecord(collection, id) {
|
|
11051
|
+
const record = this.node.getCollection(collection).get(id);
|
|
11052
|
+
if (!record) {
|
|
11053
|
+
return null;
|
|
11054
|
+
}
|
|
11055
|
+
return {
|
|
11056
|
+
key: this.recordKey(collection, id),
|
|
11057
|
+
collection,
|
|
11058
|
+
id,
|
|
11059
|
+
ownerId: record.ownerId,
|
|
11060
|
+
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
11061
|
+
data: { ...record.data },
|
|
11062
|
+
createdAt: record.createdAt,
|
|
11063
|
+
updatedAt: record.updatedAt,
|
|
11064
|
+
deletedAt: record.deletedAt,
|
|
11065
|
+
version: record.version
|
|
11066
|
+
};
|
|
11067
|
+
}
|
|
11068
|
+
async persistRecord(collection, id) {
|
|
11069
|
+
if (!this.node || !this.shouldPersist(collection)) {
|
|
11070
|
+
return;
|
|
11071
|
+
}
|
|
11072
|
+
const serialized = this.serializeRecord(collection, id);
|
|
11073
|
+
const key = this.recordKey(collection, id);
|
|
11074
|
+
if (!serialized) {
|
|
11075
|
+
await this.runTransaction("readwrite", (store) => new Promise((resolve, reject) => {
|
|
11076
|
+
const request = store.delete(key);
|
|
11077
|
+
request.onsuccess = () => resolve();
|
|
11078
|
+
request.onerror = () => reject(request.error);
|
|
11079
|
+
}));
|
|
11080
|
+
return;
|
|
11081
|
+
}
|
|
11082
|
+
await this.runTransaction("readwrite", (store) => new Promise((resolve, reject) => {
|
|
11083
|
+
const request = store.put(serialized);
|
|
11084
|
+
request.onsuccess = () => resolve();
|
|
11085
|
+
request.onerror = () => reject(request.error);
|
|
11086
|
+
}));
|
|
11087
|
+
}
|
|
11088
|
+
persistChange(event) {
|
|
11089
|
+
if (!event || !event.collection || !event.id) {
|
|
11090
|
+
return;
|
|
11091
|
+
}
|
|
11092
|
+
this.persistRecord(event.collection, event.id).catch((error) => {
|
|
11093
|
+
this.node.emit("warning", {
|
|
11094
|
+
type: "persistence-failed",
|
|
11095
|
+
collection: event.collection,
|
|
11096
|
+
id: event.id,
|
|
11097
|
+
error
|
|
11098
|
+
});
|
|
11099
|
+
});
|
|
11100
|
+
}
|
|
11101
|
+
async loadAllRecords() {
|
|
11102
|
+
return this.runTransaction("readonly", (store) => new Promise((resolve, reject) => {
|
|
11103
|
+
const request = store.getAll();
|
|
11104
|
+
request.onsuccess = () => resolve(request.result || []);
|
|
11105
|
+
request.onerror = () => reject(request.error);
|
|
11106
|
+
}));
|
|
11107
|
+
}
|
|
11108
|
+
async hydrate() {
|
|
11109
|
+
if (!this.node) {
|
|
11110
|
+
throw new Error("IndexedDBPersistence requires an attached node before hydrate");
|
|
11111
|
+
}
|
|
11112
|
+
const storedRecords = await this.loadAllRecords();
|
|
11113
|
+
for (const stored of storedRecords) {
|
|
11114
|
+
if (!this.shouldPersist(stored.collection)) {
|
|
11115
|
+
continue;
|
|
11116
|
+
}
|
|
11117
|
+
this.node.restoreRecord(stored.collection, {
|
|
11118
|
+
id: stored.id,
|
|
11119
|
+
ownerId: stored.ownerId,
|
|
11120
|
+
collaboratorIds: stored.collaboratorIds,
|
|
11121
|
+
data: stored.data,
|
|
11122
|
+
createdAt: stored.createdAt,
|
|
11123
|
+
updatedAt: stored.updatedAt,
|
|
11124
|
+
deletedAt: stored.deletedAt,
|
|
11125
|
+
version: stored.version
|
|
11126
|
+
});
|
|
11127
|
+
}
|
|
11128
|
+
}
|
|
11129
|
+
async attach(node) {
|
|
11130
|
+
if (!node) {
|
|
11131
|
+
throw new Error("IndexedDBPersistence.attach requires a DignityP2P node");
|
|
11132
|
+
}
|
|
11133
|
+
this.node = node;
|
|
11134
|
+
await this.hydrate();
|
|
11135
|
+
this.changeHandler = (event) => this.persistChange(event);
|
|
11136
|
+
node.on("change", this.changeHandler);
|
|
11137
|
+
}
|
|
11138
|
+
async detach() {
|
|
11139
|
+
if (this.node && this.changeHandler) {
|
|
11140
|
+
this.node.off("change", this.changeHandler);
|
|
11141
|
+
}
|
|
11142
|
+
this.changeHandler = null;
|
|
11143
|
+
this.node = null;
|
|
11144
|
+
}
|
|
11145
|
+
async clear() {
|
|
11146
|
+
await this.runTransaction("readwrite", (store) => new Promise((resolve, reject) => {
|
|
11147
|
+
const request = store.clear();
|
|
11148
|
+
request.onsuccess = () => resolve();
|
|
11149
|
+
request.onerror = () => reject(request.error);
|
|
11150
|
+
}));
|
|
11151
|
+
}
|
|
11152
|
+
};
|
|
11153
|
+
module.exports = IndexedDBPersistence;
|
|
11154
|
+
}
|
|
11155
|
+
});
|
|
11156
|
+
|
|
10417
11157
|
// src/index.js
|
|
10418
11158
|
var require_index = __commonJS({
|
|
10419
11159
|
"src/index.js"(exports, module) {
|
|
@@ -10426,6 +11166,11 @@ var require_index = __commonJS({
|
|
|
10426
11166
|
InMemoryNetworkHub,
|
|
10427
11167
|
InMemoryNetworkAdapter
|
|
10428
11168
|
} = require_in_memory_network();
|
|
11169
|
+
var {
|
|
11170
|
+
PeerJSNetworkAdapter,
|
|
11171
|
+
createPeerJSNetworkAdapter
|
|
11172
|
+
} = require_peerjs_network();
|
|
11173
|
+
var IndexedDBPersistence = require_indexeddb_persistence();
|
|
10429
11174
|
var {
|
|
10430
11175
|
DEFAULT_CLOUDFLARE_SIGNALING_URLS,
|
|
10431
11176
|
DEFAULT_SIGNALING_FALLBACK_URLS
|
|
@@ -10444,6 +11189,9 @@ var require_index = __commonJS({
|
|
|
10444
11189
|
PeerJSSignalingProvider,
|
|
10445
11190
|
InMemoryNetworkHub,
|
|
10446
11191
|
InMemoryNetworkAdapter,
|
|
11192
|
+
PeerJSNetworkAdapter,
|
|
11193
|
+
createPeerJSNetworkAdapter,
|
|
11194
|
+
IndexedDBPersistence,
|
|
10447
11195
|
DEFAULT_CLOUDFLARE_SIGNALING_URLS,
|
|
10448
11196
|
DEFAULT_SIGNALING_FALLBACK_URLS,
|
|
10449
11197
|
VDF,
|