dignity.js 0.4.0 → 0.5.2
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 +83 -2
- package/dist/dignity.cjs.js +542 -21
- package/dist/dignity.cjs.js.map +4 -4
- package/dist/dignity.esm.js +542 -21
- 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/favicon.svg +8 -0
- 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 +7 -3
- package/docs/openapi-like.json +35 -6
- package/examples/decentralized-chess-lite.js +52 -30
- package/package.json +13 -5
- package/src/core/dignity-p2p.js +388 -16
- package/src/index.js +6 -0
- package/src/network/peerjs-network.js +234 -0
- package/src/persistence/indexeddb-persistence.js +2 -0
- package/src/react/index.js +143 -1
- 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,11 @@ 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}`);
|
|
2982
3049
|
}
|
|
2983
3050
|
if (typeof options.expectedVersion === "number" && existing.version !== options.expectedVersion) {
|
|
2984
3051
|
this.emitConflict({
|
|
@@ -3005,13 +3072,17 @@ var require_dignity_p2p = __commonJS({
|
|
|
3005
3072
|
baseVersion: existing.version,
|
|
3006
3073
|
payload: { ...partialData }
|
|
3007
3074
|
};
|
|
3075
|
+
if (options.collaborators !== void 0) {
|
|
3076
|
+
operation.collaboratorIds = this.normalizeCollaboratorIds(options.collaborators);
|
|
3077
|
+
}
|
|
3008
3078
|
this.applyOperation(operation);
|
|
3009
3079
|
await this.broadcastMessage("operation", operation, {
|
|
3010
3080
|
broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
|
|
3011
3081
|
messageType: "operation",
|
|
3012
3082
|
operation,
|
|
3013
3083
|
collectionName
|
|
3014
|
-
})
|
|
3084
|
+
}),
|
|
3085
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: existing })
|
|
3015
3086
|
});
|
|
3016
3087
|
return this.read(collectionName, id);
|
|
3017
3088
|
}
|
|
@@ -3036,6 +3107,42 @@ var require_dignity_p2p = __commonJS({
|
|
|
3036
3107
|
}
|
|
3037
3108
|
throw new Error(`Unable to update ${collectionName}/${id} after ${maxAttempts} attempts`);
|
|
3038
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
|
+
};
|
|
3132
|
+
this.applyOperation(operation);
|
|
3133
|
+
await this.broadcastMessage("operation", operation, {
|
|
3134
|
+
broadcastScope: options.broadcastScope || this.resolveBroadcastScope({
|
|
3135
|
+
messageType: "operation",
|
|
3136
|
+
operation,
|
|
3137
|
+
collectionName
|
|
3138
|
+
}),
|
|
3139
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, {
|
|
3140
|
+
fromRecord: existing,
|
|
3141
|
+
extraPeerIds: [newOwnerId]
|
|
3142
|
+
})
|
|
3143
|
+
});
|
|
3144
|
+
return this.read(collectionName, id);
|
|
3145
|
+
}
|
|
3039
3146
|
async remove(collectionName, id, options = {}) {
|
|
3040
3147
|
const existing = this.getCollection(collectionName).get(id);
|
|
3041
3148
|
if (!existing || existing.deletedAt) {
|
|
@@ -3059,16 +3166,74 @@ var require_dignity_p2p = __commonJS({
|
|
|
3059
3166
|
messageType: "operation",
|
|
3060
3167
|
operation,
|
|
3061
3168
|
collectionName
|
|
3062
|
-
})
|
|
3169
|
+
}),
|
|
3170
|
+
connectToPeers: this.resolveReplicationPeers(collectionName, id, options, { fromRecord: existing })
|
|
3063
3171
|
});
|
|
3064
3172
|
}
|
|
3065
3173
|
registerPeerPublicKey(peerId, publicKey) {
|
|
3066
3174
|
this.securityService.registerPeerPublicKey(peerId, publicKey);
|
|
3067
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
|
+
}
|
|
3068
3194
|
getPublicKey() {
|
|
3069
3195
|
return this.securityService.getPublicKey();
|
|
3070
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
|
+
}
|
|
3071
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
|
+
}
|
|
3072
3237
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3073
3238
|
messageType,
|
|
3074
3239
|
payload,
|
|
@@ -3078,6 +3243,13 @@ var require_dignity_p2p = __commonJS({
|
|
|
3078
3243
|
await this.networkAdapter.broadcast(envelope);
|
|
3079
3244
|
}
|
|
3080
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
|
+
}
|
|
3081
3253
|
const envelope = await this.securityService.secureOutgoingMessage({
|
|
3082
3254
|
messageType,
|
|
3083
3255
|
payload,
|
|
@@ -3102,6 +3274,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3102
3274
|
expiresAt: announcedAt + ttlMs
|
|
3103
3275
|
};
|
|
3104
3276
|
map.set(peerId, next);
|
|
3277
|
+
this.trustPeerFromMetadata(peerId, next.metadata);
|
|
3105
3278
|
if (!existing) {
|
|
3106
3279
|
this.emit("peerdiscovered", { scope, peerId, metadata: next.metadata });
|
|
3107
3280
|
}
|
|
@@ -3124,11 +3297,18 @@ var require_dignity_p2p = __commonJS({
|
|
|
3124
3297
|
const normalizedScope = scope || "main";
|
|
3125
3298
|
const heartbeatIntervalMs = options.heartbeatIntervalMs || this.defaultDiscoveryHeartbeatMs;
|
|
3126
3299
|
const ttlMs = options.ttlMs || this.defaultPresenceTtlMs;
|
|
3127
|
-
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))] : [];
|
|
3128
3305
|
const existing = this.discoveryRooms.get(normalizedScope);
|
|
3129
3306
|
if (existing && existing.timer) {
|
|
3130
3307
|
clearInterval(existing.timer);
|
|
3131
3308
|
}
|
|
3309
|
+
if (bootstrapPeerIds.length > 0) {
|
|
3310
|
+
await this.ensureConnectedToPeers(bootstrapPeerIds);
|
|
3311
|
+
}
|
|
3132
3312
|
const timer = setInterval(() => {
|
|
3133
3313
|
this.announcePresence(normalizedScope).catch((error) => {
|
|
3134
3314
|
this.emit("warning", { type: "presence-heartbeat-failed", scope: normalizedScope, error });
|
|
@@ -3136,6 +3316,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3136
3316
|
}, heartbeatIntervalMs);
|
|
3137
3317
|
this.discoveryRooms.set(normalizedScope, {
|
|
3138
3318
|
metadata,
|
|
3319
|
+
bootstrapPeerIds,
|
|
3139
3320
|
heartbeatIntervalMs,
|
|
3140
3321
|
ttlMs,
|
|
3141
3322
|
timer
|
|
@@ -3216,6 +3397,9 @@ var require_dignity_p2p = __commonJS({
|
|
|
3216
3397
|
});
|
|
3217
3398
|
return;
|
|
3218
3399
|
}
|
|
3400
|
+
if (message && message.senderId && message.senderPublicKey) {
|
|
3401
|
+
this.trustPeerPublicKey(message.senderId, message.senderPublicKey);
|
|
3402
|
+
}
|
|
3219
3403
|
let decrypted;
|
|
3220
3404
|
try {
|
|
3221
3405
|
decrypted = await this.securityService.decryptIncomingMessage(message);
|
|
@@ -3237,6 +3421,21 @@ var require_dignity_p2p = __commonJS({
|
|
|
3237
3421
|
this.applyOperation(decrypted.payload);
|
|
3238
3422
|
return;
|
|
3239
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
|
+
}
|
|
3240
3439
|
if (decrypted.messageType === "presence:announce") {
|
|
3241
3440
|
const payload = decrypted.payload || {};
|
|
3242
3441
|
const scope = payload.scope || "main";
|
|
@@ -3254,6 +3453,11 @@ var require_dignity_p2p = __commonJS({
|
|
|
3254
3453
|
payload.announcedAt || this.now()
|
|
3255
3454
|
);
|
|
3256
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
|
+
}
|
|
3257
3461
|
this.announcePresence(scope).catch((error) => {
|
|
3258
3462
|
this.emit("warning", { type: "presence-handshake-failed", scope, error });
|
|
3259
3463
|
});
|
|
@@ -3328,6 +3532,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3328
3532
|
collection.set(record.id, {
|
|
3329
3533
|
id: record.id,
|
|
3330
3534
|
ownerId: record.ownerId,
|
|
3535
|
+
collaboratorIds: this.normalizeCollaboratorIds(record.collaboratorIds),
|
|
3331
3536
|
data: { ...record.data || {} },
|
|
3332
3537
|
createdAt: record.createdAt,
|
|
3333
3538
|
updatedAt: record.updatedAt,
|
|
@@ -3336,6 +3541,32 @@ var require_dignity_p2p = __commonJS({
|
|
|
3336
3541
|
});
|
|
3337
3542
|
return true;
|
|
3338
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
|
+
}
|
|
3339
3570
|
applyOperation(operation) {
|
|
3340
3571
|
if (!operation || !operation.opId || this.appliedOperations.has(operation.opId)) {
|
|
3341
3572
|
return false;
|
|
@@ -3349,6 +3580,7 @@ var require_dignity_p2p = __commonJS({
|
|
|
3349
3580
|
collection.set(operation.id, {
|
|
3350
3581
|
id: operation.id,
|
|
3351
3582
|
ownerId: operation.ownerId,
|
|
3583
|
+
collaboratorIds: this.normalizeCollaboratorIds(operation.collaboratorIds),
|
|
3352
3584
|
data: { ...operation.payload },
|
|
3353
3585
|
createdAt: operation.timestamp,
|
|
3354
3586
|
updatedAt: operation.timestamp,
|
|
@@ -3360,9 +3592,79 @@ var require_dignity_p2p = __commonJS({
|
|
|
3360
3592
|
return true;
|
|
3361
3593
|
}
|
|
3362
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
|
+
}
|
|
3363
3605
|
return false;
|
|
3364
3606
|
}
|
|
3365
|
-
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)) {
|
|
3366
3668
|
return false;
|
|
3367
3669
|
}
|
|
3368
3670
|
if (typeof operation.baseVersion === "number" && operation.baseVersion !== current.version) {
|
|
@@ -3382,20 +3684,15 @@ var require_dignity_p2p = __commonJS({
|
|
|
3382
3684
|
...current.data,
|
|
3383
3685
|
...operation.payload
|
|
3384
3686
|
};
|
|
3687
|
+
if (Array.isArray(operation.collaboratorIds) && operation.actorId === current.ownerId) {
|
|
3688
|
+
current.collaboratorIds = this.normalizeCollaboratorIds(operation.collaboratorIds);
|
|
3689
|
+
}
|
|
3385
3690
|
current.updatedAt = operation.timestamp;
|
|
3386
3691
|
current.version += 1;
|
|
3387
3692
|
this.appliedOperations.add(operation.opId);
|
|
3388
3693
|
this.emit("change", { kind: "update", collection: operation.collectionName, id: operation.id });
|
|
3389
3694
|
return true;
|
|
3390
3695
|
}
|
|
3391
|
-
if (operation.kind === "delete") {
|
|
3392
|
-
current.deletedAt = operation.timestamp;
|
|
3393
|
-
current.updatedAt = operation.timestamp;
|
|
3394
|
-
current.version += 1;
|
|
3395
|
-
this.appliedOperations.add(operation.opId);
|
|
3396
|
-
this.emit("change", { kind: "delete", collection: operation.collectionName, id: operation.id });
|
|
3397
|
-
return true;
|
|
3398
|
-
}
|
|
3399
3696
|
return false;
|
|
3400
3697
|
}
|
|
3401
3698
|
};
|
|
@@ -3550,6 +3847,28 @@ var require_websocket_signaling_provider = __commonJS({
|
|
|
3550
3847
|
}
|
|
3551
3848
|
});
|
|
3552
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
|
+
|
|
3553
3872
|
// node_modules/peerjs-js-binarypack/dist/binarypack.cjs
|
|
3554
3873
|
var require_binarypack = __commonJS({
|
|
3555
3874
|
"node_modules/peerjs-js-binarypack/dist/binarypack.cjs"(exports2, module2) {
|
|
@@ -10139,6 +10458,7 @@ var require_bundler = __commonJS({
|
|
|
10139
10458
|
var require_peerjs_signaling_provider = __commonJS({
|
|
10140
10459
|
"src/signaling/peerjs-signaling-provider.js"(exports2, module2) {
|
|
10141
10460
|
var WebSocketSignalingProvider2 = require_websocket_signaling_provider();
|
|
10461
|
+
var parsePeerJsServerUrl = require_parse_peerjs_url();
|
|
10142
10462
|
var PeerJSSignalingProvider2 = class {
|
|
10143
10463
|
constructor({ id, url, PeerImpl, WebSocketImpl, priority = 0, connectTimeoutMs = 1e4 }) {
|
|
10144
10464
|
if (!url) {
|
|
@@ -10166,13 +10486,7 @@ var require_peerjs_signaling_provider = __commonJS({
|
|
|
10166
10486
|
}
|
|
10167
10487
|
}
|
|
10168
10488
|
parsePeerJsServerUrl() {
|
|
10169
|
-
|
|
10170
|
-
const secure = parsed.protocol === "wss:";
|
|
10171
|
-
const host = parsed.hostname;
|
|
10172
|
-
const port = parsed.port ? Number(parsed.port) : secure ? 443 : 80;
|
|
10173
|
-
const path = parsed.pathname || "/";
|
|
10174
|
-
const key = parsed.searchParams.get("key") || "peerjs";
|
|
10175
|
-
return { secure, host, port, path, key };
|
|
10489
|
+
return parsePeerJsServerUrl(this.url);
|
|
10176
10490
|
}
|
|
10177
10491
|
shouldUseWebSocketFallback() {
|
|
10178
10492
|
return !this.isCustomPeerImpl && typeof globalThis.RTCPeerConnection !== "function";
|
|
@@ -10452,6 +10766,205 @@ var require_in_memory_network = __commonJS({
|
|
|
10452
10766
|
}
|
|
10453
10767
|
});
|
|
10454
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
|
+
|
|
10455
10968
|
// src/persistence/indexeddb-persistence.js
|
|
10456
10969
|
var require_indexeddb_persistence = __commonJS({
|
|
10457
10970
|
"src/persistence/indexeddb-persistence.js"(exports2, module2) {
|
|
@@ -10514,6 +11027,7 @@ var require_indexeddb_persistence = __commonJS({
|
|
|
10514
11027
|
collection,
|
|
10515
11028
|
id,
|
|
10516
11029
|
ownerId: record.ownerId,
|
|
11030
|
+
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
10517
11031
|
data: { ...record.data },
|
|
10518
11032
|
createdAt: record.createdAt,
|
|
10519
11033
|
updatedAt: record.updatedAt,
|
|
@@ -10573,6 +11087,7 @@ var require_indexeddb_persistence = __commonJS({
|
|
|
10573
11087
|
this.node.restoreRecord(stored.collection, {
|
|
10574
11088
|
id: stored.id,
|
|
10575
11089
|
ownerId: stored.ownerId,
|
|
11090
|
+
collaboratorIds: stored.collaboratorIds,
|
|
10576
11091
|
data: stored.data,
|
|
10577
11092
|
createdAt: stored.createdAt,
|
|
10578
11093
|
updatedAt: stored.updatedAt,
|
|
@@ -10619,6 +11134,10 @@ var {
|
|
|
10619
11134
|
InMemoryNetworkHub,
|
|
10620
11135
|
InMemoryNetworkAdapter
|
|
10621
11136
|
} = require_in_memory_network();
|
|
11137
|
+
var {
|
|
11138
|
+
PeerJSNetworkAdapter,
|
|
11139
|
+
createPeerJSNetworkAdapter
|
|
11140
|
+
} = require_peerjs_network();
|
|
10622
11141
|
var IndexedDBPersistence = require_indexeddb_persistence();
|
|
10623
11142
|
var {
|
|
10624
11143
|
DEFAULT_CLOUDFLARE_SIGNALING_URLS,
|
|
@@ -10638,6 +11157,8 @@ module.exports = {
|
|
|
10638
11157
|
PeerJSSignalingProvider,
|
|
10639
11158
|
InMemoryNetworkHub,
|
|
10640
11159
|
InMemoryNetworkAdapter,
|
|
11160
|
+
PeerJSNetworkAdapter,
|
|
11161
|
+
createPeerJSNetworkAdapter,
|
|
10641
11162
|
IndexedDBPersistence,
|
|
10642
11163
|
DEFAULT_CLOUDFLARE_SIGNALING_URLS,
|
|
10643
11164
|
DEFAULT_SIGNALING_FALLBACK_URLS,
|