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.cjs.js
CHANGED
|
@@ -2914,12 +2914,71 @@ var require_dignity_p2p = __commonJS({
|
|
|
2914
2914
|
return {
|
|
2915
2915
|
id: record.id,
|
|
2916
2916
|
ownerId: record.ownerId,
|
|
2917
|
+
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
2917
2918
|
createdAt: record.createdAt,
|
|
2918
2919
|
updatedAt: record.updatedAt,
|
|
2919
2920
|
version: record.version,
|
|
2920
2921
|
data: { ...record.data }
|
|
2921
2922
|
};
|
|
2922
2923
|
}
|
|
2924
|
+
canUpdateRecord(record, actorId) {
|
|
2925
|
+
if (!record || !actorId) {
|
|
2926
|
+
return false;
|
|
2927
|
+
}
|
|
2928
|
+
if (record.ownerId === actorId) {
|
|
2929
|
+
return true;
|
|
2930
|
+
}
|
|
2931
|
+
return Array.isArray(record.collaboratorIds) && record.collaboratorIds.includes(actorId);
|
|
2932
|
+
}
|
|
2933
|
+
normalizeCollaboratorIds(collaborators) {
|
|
2934
|
+
if (!Array.isArray(collaborators)) {
|
|
2935
|
+
return [];
|
|
2936
|
+
}
|
|
2937
|
+
return [...new Set(collaborators.filter(Boolean))];
|
|
2938
|
+
}
|
|
2939
|
+
getRecordPeerIds(collectionName, id, options = {}) {
|
|
2940
|
+
const record = options.fromRecord || this.getCollection(collectionName).get(id);
|
|
2941
|
+
if (!record) {
|
|
2942
|
+
return [];
|
|
2943
|
+
}
|
|
2944
|
+
const includeSelf = options.includeSelf === true;
|
|
2945
|
+
const peerIds = [record.ownerId, ...record.collaboratorIds || []];
|
|
2946
|
+
return [...new Set(peerIds.filter(Boolean).filter((peerId) => includeSelf || peerId !== this.nodeId))];
|
|
2947
|
+
}
|
|
2948
|
+
resolveReplicationPeers(collectionName, id, options = {}, hints = {}) {
|
|
2949
|
+
if (options.connectToPeers === false) {
|
|
2950
|
+
return void 0;
|
|
2951
|
+
}
|
|
2952
|
+
if (Array.isArray(options.connectToPeers)) {
|
|
2953
|
+
return options.connectToPeers;
|
|
2954
|
+
}
|
|
2955
|
+
const peerIds = /* @__PURE__ */ new Set();
|
|
2956
|
+
if (hints.fromRecord) {
|
|
2957
|
+
for (const peerId of this.getRecordPeerIds(collectionName, id, {
|
|
2958
|
+
fromRecord: hints.fromRecord,
|
|
2959
|
+
includeSelf: true
|
|
2960
|
+
})) {
|
|
2961
|
+
peerIds.add(peerId);
|
|
2962
|
+
}
|
|
2963
|
+
} else if (id) {
|
|
2964
|
+
for (const peerId of this.getRecordPeerIds(collectionName, id, { includeSelf: true })) {
|
|
2965
|
+
peerIds.add(peerId);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
if (Array.isArray(options.collaborators)) {
|
|
2969
|
+
for (const peerId of this.normalizeCollaboratorIds(options.collaborators)) {
|
|
2970
|
+
peerIds.add(peerId);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
if (Array.isArray(hints.extraPeerIds)) {
|
|
2974
|
+
for (const peerId of hints.extraPeerIds) {
|
|
2975
|
+
if (peerId) {
|
|
2976
|
+
peerIds.add(peerId);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return [...peerIds].filter((peerId) => peerId && peerId !== this.nodeId);
|
|
2981
|
+
}
|
|
2923
2982
|
async create(collectionName, data, options = {}) {
|
|
2924
2983
|
const collection = this.getCollection(collectionName);
|
|
2925
2984
|
const id = options.id || this.idGenerator();
|
|
@@ -2927,6 +2986,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
2927
2986
|
throw new Error(`Object ${id} already exists in ${collectionName}`);
|
|
2928
2987
|
}
|
|
2929
2988
|
const timestamp = this.now();
|
|
2989
|
+
const collaboratorIds = this.normalizeCollaboratorIds(options.collaborators);
|
|
2930
2990
|
const operation = {
|
|
2931
2991
|
opId: this.idGenerator(),
|
|
2932
2992
|
kind: "create",
|
|
@@ -2934,6 +2994,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
2934
2994
|
id,
|
|
2935
2995
|
actorId: this.nodeId,
|
|
2936
2996
|
ownerId: this.nodeId,
|
|
2997
|
+
collaboratorIds,
|
|
2937
2998
|
timestamp,
|
|
2938
2999
|
payload: { ...data }
|
|
2939
3000
|
};
|
|
@@ -2943,6 +3004,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
2943
3004
|
messageType: "operation",
|
|
2944
3005
|
operation,
|
|
2945
3006
|
collectionName
|
|
3007
|
+
}),
|
|
3008
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, null, options, {
|
|
3009
|
+
extraPeerIds: options.collaborators
|
|
2946
3010
|
})
|
|
2947
3011
|
});
|
|
2948
3012
|
return this.read(collectionName, id);
|
|
@@ -2977,8 +3041,26 @@ var require_dignity_p2p = __commonJS({
|
|
|
2977
3041
|
if (!existing || existing.deletedAt) {
|
|
2978
3042
|
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
2979
3043
|
}
|
|
2980
|
-
if (existing
|
|
2981
|
-
throw new Error(`Only owner ${existing.ownerId} can update object ${id}`);
|
|
3044
|
+
if (!this.canUpdateRecord(existing, this.nodeId)) {
|
|
3045
|
+
throw new Error(`Only owner ${existing.ownerId} or collaborators can update object ${id}`);
|
|
3046
|
+
}
|
|
3047
|
+
if (options.collaborators !== void 0 && existing.ownerId !== this.nodeId) {
|
|
3048
|
+
throw new Error(`Only owner ${existing.ownerId} can change collaborators on object ${id}`);
|
|
3049
|
+
}
|
|
3050
|
+
if (typeof options.expectedVersion === "number" && existing.version !== options.expectedVersion) {
|
|
3051
|
+
this.emitConflict({
|
|
3052
|
+
kind: "update",
|
|
3053
|
+
collection: collectionName,
|
|
3054
|
+
id,
|
|
3055
|
+
expectedVersion: options.expectedVersion,
|
|
3056
|
+
currentVersion: existing.version,
|
|
3057
|
+
phase: "local"
|
|
3058
|
+
});
|
|
3059
|
+
const error = new Error(
|
|
3060
|
+
`Version conflict on ${collectionName}/${id}: expected ${options.expectedVersion}, current ${existing.version}`
|
|
3061
|
+
);
|
|
3062
|
+
error.code = "VERSION_CONFLICT";
|
|
3063
|
+
throw error;
|
|
2982
3064
|
}
|
|
2983
3065
|
const operation = {
|
|
2984
3066
|
opId: this.idGenerator(),
|
|
@@ -2990,12 +3072,73 @@ var require_dignity_p2p = __commonJS({
|
|
|
2990
3072
|
baseVersion: existing.version,
|
|
2991
3073
|
payload: { ...partialData }
|
|
2992
3074
|
};
|
|
3075
|
+
if (options.collaborators !== void 0) {
|
|
3076
|
+
operation.collaboratorIds = this.normalizeCollaboratorIds(options.collaborators);
|
|
3077
|
+
}
|
|
3078
|
+
this.applyOperation(operation);
|
|
3079
|
+
await this.broadcastMessage("operation", operation, {
|
|
3080
|
+
broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
|
|
3081
|
+
messageType: "operation",
|
|
3082
|
+
operation,
|
|
3083
|
+
collectionName
|
|
3084
|
+
}),
|
|
3085
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: existing })
|
|
3086
|
+
});
|
|
3087
|
+
return this.read(collectionName, id);
|
|
3088
|
+
}
|
|
3089
|
+
async updateWithRetry(collectionName, id, patchFn, options = {}) {
|
|
3090
|
+
const maxAttempts = typeof options.maxAttempts === "number" ? options.maxAttempts : 5;
|
|
3091
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
3092
|
+
const current = this.read(collectionName, id);
|
|
3093
|
+
if (!current) {
|
|
3094
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3095
|
+
}
|
|
3096
|
+
const patch = await patchFn(current);
|
|
3097
|
+
try {
|
|
3098
|
+
return await this.update(collectionName, id, patch, {
|
|
3099
|
+
...options,
|
|
3100
|
+
expectedVersion: current.version
|
|
3101
|
+
});
|
|
3102
|
+
} catch (error) {
|
|
3103
|
+
if (error.code !== "VERSION_CONFLICT" || attempt === maxAttempts - 1) {
|
|
3104
|
+
throw error;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
throw new Error(`Unable to update ${collectionName}/${id} after ${maxAttempts} attempts`);
|
|
3109
|
+
}
|
|
3110
|
+
async transferOwnership(collectionName, id, newOwnerId, options = {}) {
|
|
3111
|
+
if (!newOwnerId) {
|
|
3112
|
+
throw new Error("newOwnerId is required");
|
|
3113
|
+
}
|
|
3114
|
+
const existing = this.getCollection(collectionName).get(id);
|
|
3115
|
+
if (!existing || existing.deletedAt) {
|
|
3116
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3117
|
+
}
|
|
3118
|
+
if (existing.ownerId !== this.nodeId) {
|
|
3119
|
+
throw new Error(`Only owner ${existing.ownerId} can transfer object ${id}`);
|
|
3120
|
+
}
|
|
3121
|
+
const operation = {
|
|
3122
|
+
opId: this.idGenerator(),
|
|
3123
|
+
kind: "transfer-ownership",
|
|
3124
|
+
collectionName,
|
|
3125
|
+
id,
|
|
3126
|
+
actorId: this.nodeId,
|
|
3127
|
+
timestamp: this.now(),
|
|
3128
|
+
baseVersion: existing.version,
|
|
3129
|
+
newOwnerId,
|
|
3130
|
+
keepPreviousOwnerAsCollaborator: options.keepAsCollaborator !== false
|
|
3131
|
+
};
|
|
2993
3132
|
this.applyOperation(operation);
|
|
2994
3133
|
await this.broadcastMessage("operation", operation, {
|
|
2995
3134
|
broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
|
|
2996
3135
|
messageType: "operation",
|
|
2997
3136
|
operation,
|
|
2998
3137
|
collectionName
|
|
3138
|
+
}),
|
|
3139
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, {
|
|
3140
|
+
fromRecord: existing,
|
|
3141
|
+
extraPeerIds: [newOwnerId]
|
|
2999
3142
|
})
|
|
3000
3143
|
});
|
|
3001
3144
|
return this.read(collectionName, id);
|
|
@@ -3023,16 +3166,74 @@ var require_dignity_p2p = __commonJS({
|
|
|
3023
3166
|
messageType: "operation",
|
|
3024
3167
|
operation,
|
|
3025
3168
|
collectionName
|
|
3026
|
-
})
|
|
3169
|
+
}),
|
|
3170
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: existing })
|
|
3027
3171
|
});
|
|
3028
3172
|
}
|
|
3029
3173
|
registerPeerPublicKey(peerId, publicKey) {
|
|
3030
3174
|
this.securityService.registerPeerPublicKey(peerId, publicKey);
|
|
3031
3175
|
}
|
|
3176
|
+
trustPeerPublicKey(peerId, publicKey) {
|
|
3177
|
+
if (!peerId || !publicKey) {
|
|
3178
|
+
return false;
|
|
3179
|
+
}
|
|
3180
|
+
try {
|
|
3181
|
+
this.registerPeerPublicKey(peerId, publicKey);
|
|
3182
|
+
return true;
|
|
3183
|
+
} catch (error) {
|
|
3184
|
+
this.emit("warning", { type: "peer-key-trust-failed", peerId, error });
|
|
3185
|
+
return false;
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
trustPeerFromMetadata(peerId, metadata) {
|
|
3189
|
+
if (!metadata || !metadata.publicKey) {
|
|
3190
|
+
return false;
|
|
3191
|
+
}
|
|
3192
|
+
return this.trustPeerPublicKey(peerId, metadata.publicKey);
|
|
3193
|
+
}
|
|
3032
3194
|
getPublicKey() {
|
|
3033
3195
|
return this.securityService.getPublicKey();
|
|
3034
3196
|
}
|
|
3197
|
+
async connectToPeer(peerId) {
|
|
3198
|
+
if (!peerId || peerId === this.nodeId) {
|
|
3199
|
+
return null;
|
|
3200
|
+
}
|
|
3201
|
+
if (typeof this.networkAdapter.connectToPeer !== "function") {
|
|
3202
|
+
throw new Error("Network adapter does not support connectToPeer");
|
|
3203
|
+
}
|
|
3204
|
+
return this.networkAdapter.connectToPeer(peerId);
|
|
3205
|
+
}
|
|
3206
|
+
getConnectionStats() {
|
|
3207
|
+
const adapter = this.networkAdapter;
|
|
3208
|
+
if (!adapter) {
|
|
3209
|
+
return { openCount: 0, peerIds: [] };
|
|
3210
|
+
}
|
|
3211
|
+
const peerIds = typeof adapter.listOpenPeerIds === "function" ? adapter.listOpenPeerIds() : [];
|
|
3212
|
+
const openCount = typeof adapter.getOpenConnectionCount === "function" ? adapter.getOpenConnectionCount() : peerIds.length;
|
|
3213
|
+
return { openCount, peerIds };
|
|
3214
|
+
}
|
|
3215
|
+
async ensureConnectedToPeers(peerIds = []) {
|
|
3216
|
+
const normalized = [...new Set((peerIds || []).filter(Boolean))];
|
|
3217
|
+
const results = [];
|
|
3218
|
+
for (const peerId of normalized) {
|
|
3219
|
+
if (peerId === this.nodeId) {
|
|
3220
|
+
continue;
|
|
3221
|
+
}
|
|
3222
|
+
try {
|
|
3223
|
+
await this.connectToPeer(peerId);
|
|
3224
|
+
results.push({ peerId, ok: true });
|
|
3225
|
+
} catch (error) {
|
|
3226
|
+
this.emit("warning", { type: "peer-connect-failed", peerId, error });
|
|
3227
|
+
results.push({ peerId, ok: false, error });
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
return results;
|
|
3231
|
+
}
|
|
3035
3232
|
async broadcastMessage(messageType, payload, securityContext = {}) {
|
|
3233
|
+
const connectToPeers = securityContext.connectToPeers;
|
|
3234
|
+
if (Array.isArray(connectToPeers) && connectToPeers.length > 0) {
|
|
3235
|
+
await this.ensureConnectedToPeers(connectToPeers);
|
|
3236
|
+
}
|
|
3036
3237
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3037
3238
|
messageType,
|
|
3038
3239
|
payload,
|
|
@@ -3042,6 +3243,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
3042
3243
|
await this.networkAdapter.broadcast(envelope);
|
|
3043
3244
|
}
|
|
3044
3245
|
async sendDirectMessage(targetId, messageType, payload) {
|
|
3246
|
+
if (targetId) {
|
|
3247
|
+
try {
|
|
3248
|
+
await this.connectToPeer(targetId);
|
|
3249
|
+
} catch (error) {
|
|
3250
|
+
this.emit("warning", { type: "direct-message-connect-failed", targetId, error });
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3045
3253
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3046
3254
|
messageType,
|
|
3047
3255
|
payload,
|
|
@@ -3066,6 +3274,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3066
3274
|
expiresAt: announcedAt + ttlMs
|
|
3067
3275
|
};
|
|
3068
3276
|
map.set(peerId, next);
|
|
3277
|
+
this.trustPeerFromMetadata(peerId, next.metadata);
|
|
3069
3278
|
if (!existing) {
|
|
3070
3279
|
this.emit("peerdiscovered", { scope, peerId, metadata: next.metadata });
|
|
3071
3280
|
}
|
|
@@ -3088,11 +3297,18 @@ var require_dignity_p2p = __commonJS({
|
|
|
3088
3297
|
const normalizedScope = scope || "main";
|
|
3089
3298
|
const heartbeatIntervalMs = options.heartbeatIntervalMs || this.defaultDiscoveryHeartbeatMs;
|
|
3090
3299
|
const ttlMs = options.ttlMs || this.defaultPresenceTtlMs;
|
|
3091
|
-
const metadata =
|
|
3300
|
+
const metadata = {
|
|
3301
|
+
publicKey: this.getPublicKey(),
|
|
3302
|
+
...options.metadata || {}
|
|
3303
|
+
};
|
|
3304
|
+
const bootstrapPeerIds = Array.isArray(options.bootstrapPeerIds) ? [...new Set(options.bootstrapPeerIds.filter(Boolean))] : [];
|
|
3092
3305
|
const existing = this.discoveryRooms.get(normalizedScope);
|
|
3093
3306
|
if (existing && existing.timer) {
|
|
3094
3307
|
clearInterval(existing.timer);
|
|
3095
3308
|
}
|
|
3309
|
+
if (bootstrapPeerIds.length > 0) {
|
|
3310
|
+
await this.ensureConnectedToPeers(bootstrapPeerIds);
|
|
3311
|
+
}
|
|
3096
3312
|
const timer = setInterval(() => {
|
|
3097
3313
|
this.announcePresence(normalizedScope).catch((error) => {
|
|
3098
3314
|
this.emit("warning", { type: "presence-heartbeat-failed", scope: normalizedScope, error });
|
|
@@ -3100,6 +3316,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3100
3316
|
}, heartbeatIntervalMs);
|
|
3101
3317
|
this.discoveryRooms.set(normalizedScope, {
|
|
3102
3318
|
metadata,
|
|
3319
|
+
bootstrapPeerIds,
|
|
3103
3320
|
heartbeatIntervalMs,
|
|
3104
3321
|
ttlMs,
|
|
3105
3322
|
timer
|
|
@@ -3180,6 +3397,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3180
3397
|
});
|
|
3181
3398
|
return;
|
|
3182
3399
|
}
|
|
3400
|
+
if (message && message.senderId && message.senderPublicKey) {
|
|
3401
|
+
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3402
|
+
}
|
|
3183
3403
|
let decrypted;
|
|
3184
3404
|
try {
|
|
3185
3405
|
decrypted = await this.securityService.decryptIncomingMessage(message);
|
|
@@ -3201,6 +3421,21 @@ var require_dignity_p2p = __commonJS({
|
|
|
3201
3421
|
this.applyOperation(decrypted.payload);
|
|
3202
3422
|
return;
|
|
3203
3423
|
}
|
|
3424
|
+
if (decrypted.messageType === "record:snapshot") {
|
|
3425
|
+
const payload = decrypted.payload || {};
|
|
3426
|
+
const { collectionName, record } = payload;
|
|
3427
|
+
if (collectionName && record) {
|
|
3428
|
+
const applied = this.restoreRecord(collectionName, record);
|
|
3429
|
+
if (applied) {
|
|
3430
|
+
this.emit("change", {
|
|
3431
|
+
kind: "snapshot",
|
|
3432
|
+
collection: collectionName,
|
|
3433
|
+
id: record.id
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3204
3439
|
if (decrypted.messageType === "presence:announce") {
|
|
3205
3440
|
const payload = decrypted.payload || {};
|
|
3206
3441
|
const scope = payload.scope || "main";
|
|
@@ -3218,6 +3453,11 @@ var require_dignity_p2p = __commonJS({
|
|
|
3218
3453
|
payload.announcedAt || this.now()
|
|
3219
3454
|
);
|
|
3220
3455
|
if (isNewPeerInScope && peerId !== this.nodeId && this.discoveryRooms.has(scope)) {
|
|
3456
|
+
if (typeof this.networkAdapter.connectToPeer === "function") {
|
|
3457
|
+
Promise.resolve(this.connectToPeer(peerId)).catch((error) => {
|
|
3458
|
+
this.emit("warning", { type: "peer-connect-failed", scope, peerId, error });
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3221
3461
|
this.announcePresence(scope).catch((error) => {
|
|
3222
3462
|
this.emit("warning", { type: "presence-handshake-failed", scope, error });
|
|
3223
3463
|
});
|
|
@@ -3277,6 +3517,56 @@ var require_dignity_p2p = __commonJS({
|
|
|
3277
3517
|
isPeerBanned(peerId) {
|
|
3278
3518
|
return this.getBanInfo(peerId) !== null;
|
|
3279
3519
|
}
|
|
3520
|
+
emitConflict(details) {
|
|
3521
|
+
this.emit("conflict", details);
|
|
3522
|
+
}
|
|
3523
|
+
restoreRecord(collectionName, record) {
|
|
3524
|
+
if (!record || !record.id) {
|
|
3525
|
+
return false;
|
|
3526
|
+
}
|
|
3527
|
+
const collection = this.getCollection(collectionName);
|
|
3528
|
+
const current = collection.get(record.id);
|
|
3529
|
+
if (current && current.version >= record.version) {
|
|
3530
|
+
return false;
|
|
3531
|
+
}
|
|
3532
|
+
collection.set(record.id, {
|
|
3533
|
+
id: record.id,
|
|
3534
|
+
ownerId: record.ownerId,
|
|
3535
|
+
collaboratorIds: this.normalizeCollaboratorIds(record.collaboratorIds),
|
|
3536
|
+
data: { ...record.data || {} },
|
|
3537
|
+
createdAt: record.createdAt,
|
|
3538
|
+
updatedAt: record.updatedAt,
|
|
3539
|
+
deletedAt: record.deletedAt || null,
|
|
3540
|
+
version: record.version
|
|
3541
|
+
});
|
|
3542
|
+
return true;
|
|
3543
|
+
}
|
|
3544
|
+
async pushRecordSnapshot(collectionName, id, options = {}) {
|
|
3545
|
+
const collection = this.getCollection(collectionName);
|
|
3546
|
+
const raw = collection.get(id);
|
|
3547
|
+
if (!raw || raw.deletedAt) {
|
|
3548
|
+
throw new Error(`Object ${id} does not exist in ${collectionName}`);
|
|
3549
|
+
}
|
|
3550
|
+
const record = {
|
|
3551
|
+
id: raw.id,
|
|
3552
|
+
ownerId: raw.ownerId,
|
|
3553
|
+
collaboratorIds: Array.isArray(raw.collaboratorIds) ? [...raw.collaboratorIds] : [],
|
|
3554
|
+
data: { ...raw.data },
|
|
3555
|
+
createdAt: raw.createdAt,
|
|
3556
|
+
updatedAt: raw.updatedAt,
|
|
3557
|
+
deletedAt: raw.deletedAt || null,
|
|
3558
|
+
version: raw.version
|
|
3559
|
+
};
|
|
3560
|
+
await this.broadcastMessage("record:snapshot", { collectionName, record }, {
|
|
3561
|
+
broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
|
|
3562
|
+
messageType: "record:snapshot",
|
|
3563
|
+
collectionName,
|
|
3564
|
+
id
|
|
3565
|
+
}),
|
|
3566
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: raw })
|
|
3567
|
+
});
|
|
3568
|
+
return record;
|
|
3569
|
+
}
|
|
3280
3570
|
applyOperation(operation) {
|
|
3281
3571
|
if (!operation || !operation.opId || this.appliedOperations.has(operation.opId)) {
|
|
3282
3572
|
return false;
|
|
@@ -3290,6 +3580,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3290
3580
|
collection.set(operation.id, {
|
|
3291
3581
|
id: operation.id,
|
|
3292
3582
|
ownerId: operation.ownerId,
|
|
3583
|
+
collaboratorIds: this.normalizeCollaboratorIds(operation.collaboratorIds),
|
|
3293
3584
|
data: { ...operation.payload },
|
|
3294
3585
|
createdAt: operation.timestamp,
|
|
3295
3586
|
updatedAt: operation.timestamp,
|
|
@@ -3301,12 +3592,91 @@ var require_dignity_p2p = __commonJS({
|
|
|
3301
3592
|
return true;
|
|
3302
3593
|
}
|
|
3303
3594
|
if (!current || current.deletedAt) {
|
|
3595
|
+
if (operation.kind !== "create") {
|
|
3596
|
+
this.emit("warning", {
|
|
3597
|
+
type: "orphan-operation",
|
|
3598
|
+
kind: operation.kind,
|
|
3599
|
+
collection: operation.collectionName,
|
|
3600
|
+
id: operation.id,
|
|
3601
|
+
actorId: operation.actorId,
|
|
3602
|
+
hint: "Peer is missing the record; pushRecordSnapshot from the owner to catch up."
|
|
3603
|
+
});
|
|
3604
|
+
}
|
|
3304
3605
|
return false;
|
|
3305
3606
|
}
|
|
3306
|
-
if (operation.
|
|
3607
|
+
if (operation.kind === "transfer-ownership") {
|
|
3608
|
+
if (operation.actorId !== current.ownerId) {
|
|
3609
|
+
return false;
|
|
3610
|
+
}
|
|
3611
|
+
if (typeof operation.baseVersion === "number" && operation.baseVersion !== current.version) {
|
|
3612
|
+
this.emitConflict({
|
|
3613
|
+
kind: operation.kind,
|
|
3614
|
+
collection: operation.collectionName,
|
|
3615
|
+
id: operation.id,
|
|
3616
|
+
expectedVersion: operation.baseVersion,
|
|
3617
|
+
currentVersion: current.version,
|
|
3618
|
+
phase: "remote",
|
|
3619
|
+
operation
|
|
3620
|
+
});
|
|
3621
|
+
return false;
|
|
3622
|
+
}
|
|
3623
|
+
const previousOwnerId = current.ownerId;
|
|
3624
|
+
current.ownerId = operation.newOwnerId;
|
|
3625
|
+
if (operation.keepPreviousOwnerAsCollaborator !== false) {
|
|
3626
|
+
const collaborators = this.normalizeCollaboratorIds(current.collaboratorIds);
|
|
3627
|
+
if (!collaborators.includes(previousOwnerId)) {
|
|
3628
|
+
collaborators.push(previousOwnerId);
|
|
3629
|
+
}
|
|
3630
|
+
current.collaboratorIds = collaborators.filter((peerId) => peerId !== operation.newOwnerId);
|
|
3631
|
+
}
|
|
3632
|
+
current.updatedAt = operation.timestamp;
|
|
3633
|
+
current.version += 1;
|
|
3634
|
+
this.appliedOperations.add(operation.opId);
|
|
3635
|
+
this.emit("change", {
|
|
3636
|
+
kind: "transfer-ownership",
|
|
3637
|
+
collection: operation.collectionName,
|
|
3638
|
+
id: operation.id,
|
|
3639
|
+
previousOwnerId,
|
|
3640
|
+
newOwnerId: operation.newOwnerId
|
|
3641
|
+
});
|
|
3642
|
+
return true;
|
|
3643
|
+
}
|
|
3644
|
+
if (operation.kind === "delete") {
|
|
3645
|
+
if (operation.actorId !== current.ownerId) {
|
|
3646
|
+
return false;
|
|
3647
|
+
}
|
|
3648
|
+
if (typeof operation.baseVersion === "number" && operation.baseVersion !== current.version) {
|
|
3649
|
+
this.emitConflict({
|
|
3650
|
+
kind: operation.kind,
|
|
3651
|
+
collection: operation.collectionName,
|
|
3652
|
+
id: operation.id,
|
|
3653
|
+
expectedVersion: operation.baseVersion,
|
|
3654
|
+
currentVersion: current.version,
|
|
3655
|
+
phase: "remote",
|
|
3656
|
+
operation
|
|
3657
|
+
});
|
|
3658
|
+
return false;
|
|
3659
|
+
}
|
|
3660
|
+
current.deletedAt = operation.timestamp;
|
|
3661
|
+
current.updatedAt = operation.timestamp;
|
|
3662
|
+
current.version += 1;
|
|
3663
|
+
this.appliedOperations.add(operation.opId);
|
|
3664
|
+
this.emit("change", { kind: "delete", collection: operation.collectionName, id: operation.id });
|
|
3665
|
+
return true;
|
|
3666
|
+
}
|
|
3667
|
+
if (!this.canUpdateRecord(current, operation.actorId)) {
|
|
3307
3668
|
return false;
|
|
3308
3669
|
}
|
|
3309
3670
|
if (typeof operation.baseVersion === "number" && operation.baseVersion !== current.version) {
|
|
3671
|
+
this.emitConflict({
|
|
3672
|
+
kind: operation.kind,
|
|
3673
|
+
collection: operation.collectionName,
|
|
3674
|
+
id: operation.id,
|
|
3675
|
+
expectedVersion: operation.baseVersion,
|
|
3676
|
+
currentVersion: current.version,
|
|
3677
|
+
phase: "remote",
|
|
3678
|
+
operation
|
|
3679
|
+
});
|
|
3310
3680
|
return false;
|
|
3311
3681
|
}
|
|
3312
3682
|
if (operation.kind === "update") {
|
|
@@ -3314,20 +3684,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3314
3684
|
...current.data,
|
|
3315
3685
|
...operation.payload
|
|
3316
3686
|
};
|
|
3687
|
+
if (Array.isArray(operation.collaboratorIds) && operation.actorId === current.ownerId) {
|
|
3688
|
+
current.collaboratorIds = this.normalizeCollaboratorIds(operation.collaboratorIds);
|
|
3689
|
+
}
|
|
3317
3690
|
current.updatedAt = operation.timestamp;
|
|
3318
3691
|
current.version += 1;
|
|
3319
3692
|
this.appliedOperations.add(operation.opId);
|
|
3320
3693
|
this.emit("change", { kind: "update", collection: operation.collectionName, id: operation.id });
|
|
3321
3694
|
return true;
|
|
3322
3695
|
}
|
|
3323
|
-
if (operation.kind === "delete") {
|
|
3324
|
-
current.deletedAt = operation.timestamp;
|
|
3325
|
-
current.updatedAt = operation.timestamp;
|
|
3326
|
-
current.version += 1;
|
|
3327
|
-
this.appliedOperations.add(operation.opId);
|
|
3328
|
-
this.emit("change", { kind: "delete", collection: operation.collectionName, id: operation.id });
|
|
3329
|
-
return true;
|
|
3330
|
-
}
|
|
3331
3696
|
return false;
|
|
3332
3697
|
}
|
|
3333
3698
|
};
|
|
@@ -3482,6 +3847,28 @@ var require_websocket_signaling_provider = __commonJS({
|
|
|
3482
3847
|
}
|
|
3483
3848
|
});
|
|
3484
3849
|
|
|
3850
|
+
// src/signaling/parse-peerjs-url.js
|
|
3851
|
+
var require_parse_peerjs_url = __commonJS({
|
|
3852
|
+
"src/signaling/parse-peerjs-url.js"(exports2, module2) {
|
|
3853
|
+
function parsePeerJsServerUrl(url) {
|
|
3854
|
+
const parsed = new URL(url);
|
|
3855
|
+
const secure = parsed.protocol === "wss:";
|
|
3856
|
+
const host = parsed.hostname;
|
|
3857
|
+
const port = parsed.port ? Number(parsed.port) : secure ? 443 : 80;
|
|
3858
|
+
const key = parsed.searchParams.get("key") || "peerjs";
|
|
3859
|
+
let path = parsed.pathname || "/";
|
|
3860
|
+
if (path.endsWith("/peerjs")) {
|
|
3861
|
+
path = path.slice(0, -"/peerjs".length) || "/";
|
|
3862
|
+
}
|
|
3863
|
+
if (path !== "/" && !path.endsWith("/")) {
|
|
3864
|
+
path += "/";
|
|
3865
|
+
}
|
|
3866
|
+
return { secure, host, port, path, key };
|
|
3867
|
+
}
|
|
3868
|
+
module2.exports = parsePeerJsServerUrl;
|
|
3869
|
+
}
|
|
3870
|
+
});
|
|
3871
|
+
|
|
3485
3872
|
// node_modules/peerjs-js-binarypack/dist/binarypack.cjs
|
|
3486
3873
|
var require_binarypack = __commonJS({
|
|
3487
3874
|
"node_modules/peerjs-js-binarypack/dist/binarypack.cjs"(exports2, module2) {
|
|
@@ -10071,6 +10458,7 @@ var require_bundler = __commonJS({
|
|
|
10071
10458
|
var require_peerjs_signaling_provider = __commonJS({
|
|
10072
10459
|
"src/signaling/peerjs-signaling-provider.js"(exports2, module2) {
|
|
10073
10460
|
var WebSocketSignalingProvider2 = require_websocket_signaling_provider();
|
|
10461
|
+
var parsePeerJsServerUrl = require_parse_peerjs_url();
|
|
10074
10462
|
var PeerJSSignalingProvider2 = class {
|
|
10075
10463
|
constructor({ id, url, PeerImpl, WebSocketImpl, priority = 0, connectTimeoutMs = 1e4 }) {
|
|
10076
10464
|
if (!url) {
|
|
@@ -10098,13 +10486,7 @@ var require_peerjs_signaling_provider = __commonJS({
|
|
|
10098
10486
|
}
|
|
10099
10487
|
}
|
|
10100
10488
|
parsePeerJsServerUrl() {
|
|
10101
|
-
|
|
10102
|
-
const secure = parsed.protocol === "wss:";
|
|
10103
|
-
const host = parsed.hostname;
|
|
10104
|
-
const port = parsed.port ? Number(parsed.port) : secure ? 443 : 80;
|
|
10105
|
-
const path = parsed.pathname || "/";
|
|
10106
|
-
const key = parsed.searchParams.get("key") || "peerjs";
|
|
10107
|
-
return { secure, host, port, path, key };
|
|
10489
|
+
return parsePeerJsServerUrl(this.url);
|
|
10108
10490
|
}
|
|
10109
10491
|
shouldUseWebSocketFallback() {
|
|
10110
10492
|
return !this.isCustomPeerImpl && typeof globalThis.RTCPeerConnection !== "function";
|
|
@@ -10384,6 +10766,364 @@ var require_in_memory_network = __commonJS({
|
|
|
10384
10766
|
}
|
|
10385
10767
|
});
|
|
10386
10768
|
|
|
10769
|
+
// src/network/peerjs-network.js
|
|
10770
|
+
var require_peerjs_network = __commonJS({
|
|
10771
|
+
"src/network/peerjs-network.js"(exports2, module2) {
|
|
10772
|
+
var { DEFAULT_CLOUDFLARE_SIGNALING_URLS: DEFAULT_CLOUDFLARE_SIGNALING_URLS2 } = require_default_signaling_config();
|
|
10773
|
+
var parsePeerJsServerUrl = require_parse_peerjs_url();
|
|
10774
|
+
function resolvePeerImplementation(PeerImpl) {
|
|
10775
|
+
if (PeerImpl) {
|
|
10776
|
+
return PeerImpl;
|
|
10777
|
+
}
|
|
10778
|
+
try {
|
|
10779
|
+
const peerjs = require_bundler();
|
|
10780
|
+
return peerjs.Peer || peerjs;
|
|
10781
|
+
} catch (error) {
|
|
10782
|
+
return null;
|
|
10783
|
+
}
|
|
10784
|
+
}
|
|
10785
|
+
var PeerJSNetworkAdapter2 = class {
|
|
10786
|
+
constructor({
|
|
10787
|
+
url,
|
|
10788
|
+
urls,
|
|
10789
|
+
PeerImpl,
|
|
10790
|
+
connectTimeoutMs = 12e3
|
|
10791
|
+
} = {}) {
|
|
10792
|
+
this.urls = urls || (url ? [url] : [...DEFAULT_CLOUDFLARE_SIGNALING_URLS2]);
|
|
10793
|
+
this.url = this.urls[0];
|
|
10794
|
+
this.PeerImpl = resolvePeerImplementation(PeerImpl);
|
|
10795
|
+
this.connectTimeoutMs = connectTimeoutMs;
|
|
10796
|
+
this.nodeId = null;
|
|
10797
|
+
this.peer = null;
|
|
10798
|
+
this.connections = /* @__PURE__ */ new Map();
|
|
10799
|
+
this.pendingConnections = /* @__PURE__ */ new Map();
|
|
10800
|
+
this.messageHandlers = /* @__PURE__ */ new Set();
|
|
10801
|
+
}
|
|
10802
|
+
async start(nodeId) {
|
|
10803
|
+
if (!nodeId) {
|
|
10804
|
+
throw new Error("PeerJSNetworkAdapter requires nodeId on start");
|
|
10805
|
+
}
|
|
10806
|
+
if (!this.PeerImpl) {
|
|
10807
|
+
throw new Error("PeerJS implementation is not available");
|
|
10808
|
+
}
|
|
10809
|
+
if (this.peer) {
|
|
10810
|
+
await this.stop();
|
|
10811
|
+
}
|
|
10812
|
+
let lastError;
|
|
10813
|
+
for (const candidateUrl of this.urls) {
|
|
10814
|
+
try {
|
|
10815
|
+
await this.startWithUrl(nodeId, candidateUrl);
|
|
10816
|
+
this.url = candidateUrl;
|
|
10817
|
+
return;
|
|
10818
|
+
} catch (error) {
|
|
10819
|
+
lastError = error;
|
|
10820
|
+
}
|
|
10821
|
+
}
|
|
10822
|
+
throw lastError || new Error("Unable to connect PeerJS network adapter");
|
|
10823
|
+
}
|
|
10824
|
+
async startWithUrl(nodeId, url) {
|
|
10825
|
+
this.nodeId = nodeId;
|
|
10826
|
+
const server = parsePeerJsServerUrl(url);
|
|
10827
|
+
await new Promise((resolve, reject) => {
|
|
10828
|
+
const peer = new this.PeerImpl(nodeId, {
|
|
10829
|
+
host: server.host,
|
|
10830
|
+
port: server.port,
|
|
10831
|
+
path: server.path,
|
|
10832
|
+
secure: server.secure,
|
|
10833
|
+
key: server.key
|
|
10834
|
+
});
|
|
10835
|
+
const timeout = setTimeout(() => {
|
|
10836
|
+
peer.destroy?.();
|
|
10837
|
+
reject(new Error(`Unable to connect PeerJS network adapter to ${url}`));
|
|
10838
|
+
}, this.connectTimeoutMs);
|
|
10839
|
+
peer.on("open", () => {
|
|
10840
|
+
clearTimeout(timeout);
|
|
10841
|
+
this.peer = peer;
|
|
10842
|
+
resolve();
|
|
10843
|
+
});
|
|
10844
|
+
peer.on("connection", (connection) => {
|
|
10845
|
+
this.attachConnectionHandlers(connection);
|
|
10846
|
+
});
|
|
10847
|
+
peer.on("error", (error) => {
|
|
10848
|
+
clearTimeout(timeout);
|
|
10849
|
+
peer.destroy?.();
|
|
10850
|
+
reject(error || new Error(`Unable to connect PeerJS network adapter to ${url}`));
|
|
10851
|
+
});
|
|
10852
|
+
});
|
|
10853
|
+
}
|
|
10854
|
+
attachConnectionHandlers(connection) {
|
|
10855
|
+
const remoteId = connection.peer;
|
|
10856
|
+
if (!remoteId) {
|
|
10857
|
+
return;
|
|
10858
|
+
}
|
|
10859
|
+
this.connections.set(remoteId, connection);
|
|
10860
|
+
connection.on("data", (payload) => {
|
|
10861
|
+
const deliveries = [];
|
|
10862
|
+
for (const handler of this.messageHandlers) {
|
|
10863
|
+
deliveries.push(handler(payload));
|
|
10864
|
+
}
|
|
10865
|
+
return Promise.all(deliveries);
|
|
10866
|
+
});
|
|
10867
|
+
connection.on("close", () => {
|
|
10868
|
+
this.connections.delete(remoteId);
|
|
10869
|
+
});
|
|
10870
|
+
}
|
|
10871
|
+
async connectToPeer(remotePeerId) {
|
|
10872
|
+
if (!remotePeerId || remotePeerId === this.nodeId) {
|
|
10873
|
+
return null;
|
|
10874
|
+
}
|
|
10875
|
+
const existing = this.connections.get(remotePeerId);
|
|
10876
|
+
if (existing && existing.open) {
|
|
10877
|
+
return existing;
|
|
10878
|
+
}
|
|
10879
|
+
if (this.pendingConnections.has(remotePeerId)) {
|
|
10880
|
+
return this.pendingConnections.get(remotePeerId);
|
|
10881
|
+
}
|
|
10882
|
+
if (!this.peer) {
|
|
10883
|
+
throw new Error("PeerJS network adapter has not been started");
|
|
10884
|
+
}
|
|
10885
|
+
const pending = new Promise((resolve, reject) => {
|
|
10886
|
+
const connection = this.peer.connect(remotePeerId, {
|
|
10887
|
+
reliable: true,
|
|
10888
|
+
serialization: "json"
|
|
10889
|
+
});
|
|
10890
|
+
const timeout = setTimeout(() => {
|
|
10891
|
+
reject(new Error(`Unable to connect to peer ${remotePeerId}`));
|
|
10892
|
+
}, this.connectTimeoutMs);
|
|
10893
|
+
connection.on("open", () => {
|
|
10894
|
+
clearTimeout(timeout);
|
|
10895
|
+
this.attachConnectionHandlers(connection);
|
|
10896
|
+
resolve(connection);
|
|
10897
|
+
});
|
|
10898
|
+
connection.on("error", () => {
|
|
10899
|
+
clearTimeout(timeout);
|
|
10900
|
+
reject(new Error(`Unable to connect to peer ${remotePeerId}`));
|
|
10901
|
+
});
|
|
10902
|
+
}).finally(() => {
|
|
10903
|
+
this.pendingConnections.delete(remotePeerId);
|
|
10904
|
+
});
|
|
10905
|
+
this.pendingConnections.set(remotePeerId, pending);
|
|
10906
|
+
return pending;
|
|
10907
|
+
}
|
|
10908
|
+
onMessage(handler) {
|
|
10909
|
+
this.messageHandlers.add(handler);
|
|
10910
|
+
}
|
|
10911
|
+
offMessage(handler) {
|
|
10912
|
+
this.messageHandlers.delete(handler);
|
|
10913
|
+
}
|
|
10914
|
+
async broadcast(message) {
|
|
10915
|
+
if (!this.peer) {
|
|
10916
|
+
throw new Error("PeerJS network adapter has not been started");
|
|
10917
|
+
}
|
|
10918
|
+
const deliveries = [];
|
|
10919
|
+
for (const connection of this.connections.values()) {
|
|
10920
|
+
if (connection.open) {
|
|
10921
|
+
deliveries.push(connection.send(message));
|
|
10922
|
+
}
|
|
10923
|
+
}
|
|
10924
|
+
await Promise.all(deliveries);
|
|
10925
|
+
}
|
|
10926
|
+
getOpenConnectionCount() {
|
|
10927
|
+
return this.listOpenPeerIds().length;
|
|
10928
|
+
}
|
|
10929
|
+
listOpenPeerIds() {
|
|
10930
|
+
const ids = [];
|
|
10931
|
+
for (const [peerId, connection] of this.connections.entries()) {
|
|
10932
|
+
if (connection.open) {
|
|
10933
|
+
ids.push(peerId);
|
|
10934
|
+
}
|
|
10935
|
+
}
|
|
10936
|
+
return ids;
|
|
10937
|
+
}
|
|
10938
|
+
isConnectedTo(remotePeerId) {
|
|
10939
|
+
const connection = this.connections.get(remotePeerId);
|
|
10940
|
+
return Boolean(connection && connection.open);
|
|
10941
|
+
}
|
|
10942
|
+
async stop() {
|
|
10943
|
+
for (const connection of this.connections.values()) {
|
|
10944
|
+
if (typeof connection.close === "function") {
|
|
10945
|
+
connection.close();
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
this.connections.clear();
|
|
10949
|
+
this.pendingConnections.clear();
|
|
10950
|
+
if (this.peer && typeof this.peer.destroy === "function") {
|
|
10951
|
+
this.peer.destroy();
|
|
10952
|
+
}
|
|
10953
|
+
this.peer = null;
|
|
10954
|
+
this.nodeId = null;
|
|
10955
|
+
}
|
|
10956
|
+
};
|
|
10957
|
+
function createPeerJSNetworkAdapter2(options = {}) {
|
|
10958
|
+
return new PeerJSNetworkAdapter2(options);
|
|
10959
|
+
}
|
|
10960
|
+
module2.exports = {
|
|
10961
|
+
PeerJSNetworkAdapter: PeerJSNetworkAdapter2,
|
|
10962
|
+
createPeerJSNetworkAdapter: createPeerJSNetworkAdapter2,
|
|
10963
|
+
parsePeerJsServerUrl
|
|
10964
|
+
};
|
|
10965
|
+
}
|
|
10966
|
+
});
|
|
10967
|
+
|
|
10968
|
+
// src/persistence/indexeddb-persistence.js
|
|
10969
|
+
var require_indexeddb_persistence = __commonJS({
|
|
10970
|
+
"src/persistence/indexeddb-persistence.js"(exports2, module2) {
|
|
10971
|
+
var IndexedDBPersistence2 = class {
|
|
10972
|
+
constructor({
|
|
10973
|
+
dbName = "dignity",
|
|
10974
|
+
storeName = "records",
|
|
10975
|
+
collections = null,
|
|
10976
|
+
indexedDB = typeof globalThis !== "undefined" ? globalThis.indexedDB : null
|
|
10977
|
+
} = {}) {
|
|
10978
|
+
this.dbName = dbName;
|
|
10979
|
+
this.storeName = storeName;
|
|
10980
|
+
this.collections = collections;
|
|
10981
|
+
this.indexedDB = indexedDB;
|
|
10982
|
+
this.node = null;
|
|
10983
|
+
this.changeHandler = null;
|
|
10984
|
+
}
|
|
10985
|
+
recordKey(collection, id) {
|
|
10986
|
+
return `${collection}:${id}`;
|
|
10987
|
+
}
|
|
10988
|
+
shouldPersist(collection) {
|
|
10989
|
+
if (!this.collections) {
|
|
10990
|
+
return true;
|
|
10991
|
+
}
|
|
10992
|
+
return this.collections.includes(collection);
|
|
10993
|
+
}
|
|
10994
|
+
openDb() {
|
|
10995
|
+
if (!this.indexedDB) {
|
|
10996
|
+
return Promise.reject(new Error("IndexedDB is not available"));
|
|
10997
|
+
}
|
|
10998
|
+
return new Promise((resolve, reject) => {
|
|
10999
|
+
const request = this.indexedDB.open(this.dbName, 1);
|
|
11000
|
+
request.onupgradeneeded = () => {
|
|
11001
|
+
const db = request.result;
|
|
11002
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
11003
|
+
db.createObjectStore(this.storeName, { keyPath: "key" });
|
|
11004
|
+
}
|
|
11005
|
+
};
|
|
11006
|
+
request.onsuccess = () => resolve(request.result);
|
|
11007
|
+
request.onerror = () => reject(request.error || new Error("Unable to open IndexedDB"));
|
|
11008
|
+
});
|
|
11009
|
+
}
|
|
11010
|
+
runTransaction(mode, handler) {
|
|
11011
|
+
return this.openDb().then((db) => new Promise((resolve, reject) => {
|
|
11012
|
+
const transaction = db.transaction(this.storeName, mode);
|
|
11013
|
+
const store = transaction.objectStore(this.storeName);
|
|
11014
|
+
Promise.resolve(handler(store)).then(resolve).catch(reject);
|
|
11015
|
+
transaction.oncomplete = () => db.close();
|
|
11016
|
+
transaction.onerror = () => reject(transaction.error || new Error("IndexedDB transaction failed"));
|
|
11017
|
+
transaction.onabort = () => reject(transaction.error || new Error("IndexedDB transaction aborted"));
|
|
11018
|
+
}));
|
|
11019
|
+
}
|
|
11020
|
+
serializeRecord(collection, id) {
|
|
11021
|
+
const record = this.node.getCollection(collection).get(id);
|
|
11022
|
+
if (!record) {
|
|
11023
|
+
return null;
|
|
11024
|
+
}
|
|
11025
|
+
return {
|
|
11026
|
+
key: this.recordKey(collection, id),
|
|
11027
|
+
collection,
|
|
11028
|
+
id,
|
|
11029
|
+
ownerId: record.ownerId,
|
|
11030
|
+
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
11031
|
+
data: { ...record.data },
|
|
11032
|
+
createdAt: record.createdAt,
|
|
11033
|
+
updatedAt: record.updatedAt,
|
|
11034
|
+
deletedAt: record.deletedAt,
|
|
11035
|
+
version: record.version
|
|
11036
|
+
};
|
|
11037
|
+
}
|
|
11038
|
+
async persistRecord(collection, id) {
|
|
11039
|
+
if (!this.node || !this.shouldPersist(collection)) {
|
|
11040
|
+
return;
|
|
11041
|
+
}
|
|
11042
|
+
const serialized = this.serializeRecord(collection, id);
|
|
11043
|
+
const key = this.recordKey(collection, id);
|
|
11044
|
+
if (!serialized) {
|
|
11045
|
+
await this.runTransaction("readwrite", (store) => new Promise((resolve, reject) => {
|
|
11046
|
+
const request = store.delete(key);
|
|
11047
|
+
request.onsuccess = () => resolve();
|
|
11048
|
+
request.onerror = () => reject(request.error);
|
|
11049
|
+
}));
|
|
11050
|
+
return;
|
|
11051
|
+
}
|
|
11052
|
+
await this.runTransaction("readwrite", (store) => new Promise((resolve, reject) => {
|
|
11053
|
+
const request = store.put(serialized);
|
|
11054
|
+
request.onsuccess = () => resolve();
|
|
11055
|
+
request.onerror = () => reject(request.error);
|
|
11056
|
+
}));
|
|
11057
|
+
}
|
|
11058
|
+
persistChange(event) {
|
|
11059
|
+
if (!event || !event.collection || !event.id) {
|
|
11060
|
+
return;
|
|
11061
|
+
}
|
|
11062
|
+
this.persistRecord(event.collection, event.id).catch((error) => {
|
|
11063
|
+
this.node.emit("warning", {
|
|
11064
|
+
type: "persistence-failed",
|
|
11065
|
+
collection: event.collection,
|
|
11066
|
+
id: event.id,
|
|
11067
|
+
error
|
|
11068
|
+
});
|
|
11069
|
+
});
|
|
11070
|
+
}
|
|
11071
|
+
async loadAllRecords() {
|
|
11072
|
+
return this.runTransaction("readonly", (store) => new Promise((resolve, reject) => {
|
|
11073
|
+
const request = store.getAll();
|
|
11074
|
+
request.onsuccess = () => resolve(request.result || []);
|
|
11075
|
+
request.onerror = () => reject(request.error);
|
|
11076
|
+
}));
|
|
11077
|
+
}
|
|
11078
|
+
async hydrate() {
|
|
11079
|
+
if (!this.node) {
|
|
11080
|
+
throw new Error("IndexedDBPersistence requires an attached node before hydrate");
|
|
11081
|
+
}
|
|
11082
|
+
const storedRecords = await this.loadAllRecords();
|
|
11083
|
+
for (const stored of storedRecords) {
|
|
11084
|
+
if (!this.shouldPersist(stored.collection)) {
|
|
11085
|
+
continue;
|
|
11086
|
+
}
|
|
11087
|
+
this.node.restoreRecord(stored.collection, {
|
|
11088
|
+
id: stored.id,
|
|
11089
|
+
ownerId: stored.ownerId,
|
|
11090
|
+
collaboratorIds: stored.collaboratorIds,
|
|
11091
|
+
data: stored.data,
|
|
11092
|
+
createdAt: stored.createdAt,
|
|
11093
|
+
updatedAt: stored.updatedAt,
|
|
11094
|
+
deletedAt: stored.deletedAt,
|
|
11095
|
+
version: stored.version
|
|
11096
|
+
});
|
|
11097
|
+
}
|
|
11098
|
+
}
|
|
11099
|
+
async attach(node) {
|
|
11100
|
+
if (!node) {
|
|
11101
|
+
throw new Error("IndexedDBPersistence.attach requires a DignityP2P node");
|
|
11102
|
+
}
|
|
11103
|
+
this.node = node;
|
|
11104
|
+
await this.hydrate();
|
|
11105
|
+
this.changeHandler = (event) => this.persistChange(event);
|
|
11106
|
+
node.on("change", this.changeHandler);
|
|
11107
|
+
}
|
|
11108
|
+
async detach() {
|
|
11109
|
+
if (this.node && this.changeHandler) {
|
|
11110
|
+
this.node.off("change", this.changeHandler);
|
|
11111
|
+
}
|
|
11112
|
+
this.changeHandler = null;
|
|
11113
|
+
this.node = null;
|
|
11114
|
+
}
|
|
11115
|
+
async clear() {
|
|
11116
|
+
await this.runTransaction("readwrite", (store) => new Promise((resolve, reject) => {
|
|
11117
|
+
const request = store.clear();
|
|
11118
|
+
request.onsuccess = () => resolve();
|
|
11119
|
+
request.onerror = () => reject(request.error);
|
|
11120
|
+
}));
|
|
11121
|
+
}
|
|
11122
|
+
};
|
|
11123
|
+
module2.exports = IndexedDBPersistence2;
|
|
11124
|
+
}
|
|
11125
|
+
});
|
|
11126
|
+
|
|
10387
11127
|
// src/index.js
|
|
10388
11128
|
var DignityP2P = require_dignity_p2p();
|
|
10389
11129
|
var createDefaultSignalingPool = require_create_default_signaling_pool();
|
|
@@ -10394,6 +11134,11 @@ var {
|
|
|
10394
11134
|
InMemoryNetworkHub,
|
|
10395
11135
|
InMemoryNetworkAdapter
|
|
10396
11136
|
} = require_in_memory_network();
|
|
11137
|
+
var {
|
|
11138
|
+
PeerJSNetworkAdapter,
|
|
11139
|
+
createPeerJSNetworkAdapter
|
|
11140
|
+
} = require_peerjs_network();
|
|
11141
|
+
var IndexedDBPersistence = require_indexeddb_persistence();
|
|
10397
11142
|
var {
|
|
10398
11143
|
DEFAULT_CLOUDFLARE_SIGNALING_URLS,
|
|
10399
11144
|
DEFAULT_SIGNALING_FALLBACK_URLS
|
|
@@ -10412,6 +11157,9 @@ module.exports = {
|
|
|
10412
11157
|
PeerJSSignalingProvider,
|
|
10413
11158
|
InMemoryNetworkHub,
|
|
10414
11159
|
InMemoryNetworkAdapter,
|
|
11160
|
+
PeerJSNetworkAdapter,
|
|
11161
|
+
createPeerJSNetworkAdapter,
|
|
11162
|
+
IndexedDBPersistence,
|
|
10415
11163
|
DEFAULT_CLOUDFLARE_SIGNALING_URLS,
|
|
10416
11164
|
DEFAULT_SIGNALING_FALLBACK_URLS,
|
|
10417
11165
|
VDF,
|