@abraca/dabra 1.0.20 → 1.0.22
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/dist/abracadabra-provider.cjs +595 -5
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +593 -6
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +251 -1
- package/package.json +1 -1
- package/src/AbracadabraClient.ts +27 -0
- package/src/AbracadabraProvider.ts +10 -1
- package/src/IdentityDoc.ts +497 -0
- package/src/index.ts +7 -0
- package/src/types.ts +3 -0
- package/src/webrtc/AbracadabraWebRTC.ts +8 -4
- package/src/webrtc/DevicePairingChannel.ts +384 -0
- package/src/webrtc/index.ts +2 -0
|
@@ -2822,7 +2822,7 @@ var AbracadabraProvider = class AbracadabraProvider extends AbracadabraBaseProvi
|
|
|
2822
2822
|
this._client = client;
|
|
2823
2823
|
this.abracadabraConfig = configuration;
|
|
2824
2824
|
this.subdocLoading = configuration.subdocLoading ?? "lazy";
|
|
2825
|
-
const serverOrigin = AbracadabraProvider.deriveServerOrigin(configuration, client);
|
|
2825
|
+
const serverOrigin = configuration.serverAgnostic ? void 0 : AbracadabraProvider.deriveServerOrigin(configuration, client);
|
|
2826
2826
|
this.offlineStore = configuration.disableOfflineStore ? null : new OfflineStore(configuration.name, serverOrigin);
|
|
2827
2827
|
this.on("subdocRegistered", configuration.onSubdocRegistered ?? (() => null));
|
|
2828
2828
|
this.on("subdocLoaded", configuration.onSubdocLoaded ?? (() => null));
|
|
@@ -3228,10 +3228,30 @@ var AbracadabraClient = class {
|
|
|
3228
3228
|
async listKeys() {
|
|
3229
3229
|
return (await this.request("GET", "/auth/keys")).keys;
|
|
3230
3230
|
}
|
|
3231
|
+
/** Rename a registered device key. */
|
|
3232
|
+
async renameKey(keyId, deviceName) {
|
|
3233
|
+
await this.request("PATCH", `/auth/keys/${encodeURIComponent(keyId)}`, { body: { deviceName } });
|
|
3234
|
+
}
|
|
3231
3235
|
/** Revoke a public key by its ID. */
|
|
3232
3236
|
async revokeKey(keyId) {
|
|
3233
3237
|
await this.request("DELETE", `/auth/keys/${encodeURIComponent(keyId)}`);
|
|
3234
3238
|
}
|
|
3239
|
+
/** Create a single-use device invite code for pairing a new device to this account. */
|
|
3240
|
+
async createDeviceInvite(opts) {
|
|
3241
|
+
return this.request("POST", "/auth/device-invite", { body: opts?.expiresIn != null ? { expiresIn: opts.expiresIn } : {} });
|
|
3242
|
+
}
|
|
3243
|
+
/** Redeem a device invite code to register a new device key. Returns a JWT token. */
|
|
3244
|
+
async redeemDeviceInvite(opts) {
|
|
3245
|
+
return this.request("POST", "/auth/device-redeem", {
|
|
3246
|
+
body: {
|
|
3247
|
+
code: opts.code,
|
|
3248
|
+
publicKey: opts.publicKey,
|
|
3249
|
+
x25519Key: opts.x25519Key,
|
|
3250
|
+
deviceName: opts.deviceName
|
|
3251
|
+
},
|
|
3252
|
+
auth: false
|
|
3253
|
+
});
|
|
3254
|
+
}
|
|
3235
3255
|
/** Get encryption info for a document. */
|
|
3236
3256
|
async getDocEncryption(docId) {
|
|
3237
3257
|
return this.request("GET", `/docs/${encodeURIComponent(docId)}/encryption`);
|
|
@@ -9949,19 +9969,23 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
|
|
|
9949
9969
|
}
|
|
9950
9970
|
/**
|
|
9951
9971
|
* Send a custom string message to a specific peer via a data channel.
|
|
9972
|
+
* When E2EE is active, the message is encrypted through the router.
|
|
9952
9973
|
*/
|
|
9953
9974
|
sendCustomMessage(peerId, payload) {
|
|
9954
9975
|
const pc = this.peerConnections.get(peerId);
|
|
9955
9976
|
if (!pc) return;
|
|
9956
|
-
|
|
9977
|
+
const channelName = "custom";
|
|
9978
|
+
let channel = pc.router.getChannel(channelName);
|
|
9957
9979
|
if (!channel || channel.readyState !== "open") {
|
|
9958
|
-
channel = pc.router.createChannel(
|
|
9980
|
+
channel = pc.router.createChannel(channelName, { ordered: true });
|
|
9959
9981
|
channel.onopen = () => {
|
|
9960
|
-
|
|
9982
|
+
const data = new TextEncoder().encode(payload);
|
|
9983
|
+
pc.router.send(channelName, data);
|
|
9961
9984
|
};
|
|
9962
9985
|
return;
|
|
9963
9986
|
}
|
|
9964
|
-
|
|
9987
|
+
const data = new TextEncoder().encode(payload);
|
|
9988
|
+
pc.router.send(channelName, data);
|
|
9965
9989
|
}
|
|
9966
9990
|
/**
|
|
9967
9991
|
* Send a custom string message to all connected peers.
|
|
@@ -10308,6 +10332,270 @@ var ManualSignaling = class extends EventEmitter {
|
|
|
10308
10332
|
}
|
|
10309
10333
|
};
|
|
10310
10334
|
|
|
10335
|
+
//#endregion
|
|
10336
|
+
//#region packages/provider/src/webrtc/DevicePairingChannel.ts
|
|
10337
|
+
/**
|
|
10338
|
+
* DevicePairingChannel
|
|
10339
|
+
*
|
|
10340
|
+
* Enables cross-device identity pairing over an E2EE WebRTC data channel.
|
|
10341
|
+
* Device A (approver) creates a pairing session with a short code. Device B
|
|
10342
|
+
* (requester) joins with the code. After E2EE is established, Device B sends
|
|
10343
|
+
* its public key and Device A registers it via `addKey()`.
|
|
10344
|
+
*
|
|
10345
|
+
* Reuses the existing WebRTC stack: SignalingSocket, PeerConnection,
|
|
10346
|
+
* DataChannelRouter, and E2EEChannel (X25519 ECDH + AES-256-GCM).
|
|
10347
|
+
*/
|
|
10348
|
+
/** Ambiguity-free charset (no 0/O/1/I). */
|
|
10349
|
+
const CODE_CHARSET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
10350
|
+
const CODE_LENGTH = 6;
|
|
10351
|
+
const PAIRING_TIMEOUT_MS = 300 * 1e3;
|
|
10352
|
+
function generatePairingCode() {
|
|
10353
|
+
const bytes = crypto.getRandomValues(new Uint8Array(CODE_LENGTH));
|
|
10354
|
+
return Array.from(bytes).map((b) => CODE_CHARSET[b % 32]).join("");
|
|
10355
|
+
}
|
|
10356
|
+
function codeToRoomId(code) {
|
|
10357
|
+
const hash = sha256(new TextEncoder().encode(code.toUpperCase()));
|
|
10358
|
+
return `__pairing_${Array.from(hash.slice(0, 8)).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
10359
|
+
}
|
|
10360
|
+
var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
|
|
10361
|
+
constructor(role, pairingCode, config) {
|
|
10362
|
+
super();
|
|
10363
|
+
this.config = config;
|
|
10364
|
+
this.webrtc = null;
|
|
10365
|
+
this.timeoutHandle = null;
|
|
10366
|
+
this._destroyed = false;
|
|
10367
|
+
this._pendingRequest = null;
|
|
10368
|
+
this._connectedPeerId = null;
|
|
10369
|
+
this.role = role;
|
|
10370
|
+
this.pairingCode = pairingCode;
|
|
10371
|
+
}
|
|
10372
|
+
/**
|
|
10373
|
+
* Create an approver session (Device A). Returns the channel and a
|
|
10374
|
+
* 6-character pairing code to share with Device B.
|
|
10375
|
+
*/
|
|
10376
|
+
static createApprover(config) {
|
|
10377
|
+
const code = generatePairingCode();
|
|
10378
|
+
const channel = new DevicePairingChannel("approver", code, config);
|
|
10379
|
+
channel.start();
|
|
10380
|
+
return {
|
|
10381
|
+
channel,
|
|
10382
|
+
pairingCode: code
|
|
10383
|
+
};
|
|
10384
|
+
}
|
|
10385
|
+
/**
|
|
10386
|
+
* Create a requester session (Device B). Joins with a pairing code
|
|
10387
|
+
* obtained from Device A.
|
|
10388
|
+
*/
|
|
10389
|
+
static createRequester(config, pairingCode) {
|
|
10390
|
+
const channel = new DevicePairingChannel("requester", pairingCode.toUpperCase().replace(/[^A-Z2-9]/g, ""), config);
|
|
10391
|
+
channel.start();
|
|
10392
|
+
return channel;
|
|
10393
|
+
}
|
|
10394
|
+
/**
|
|
10395
|
+
* Approve the pending pairing request. Calls `client.addKey()` to
|
|
10396
|
+
* register Device B's public key, then notifies Device B.
|
|
10397
|
+
*/
|
|
10398
|
+
async approve(client) {
|
|
10399
|
+
if (this.role !== "approver") return {
|
|
10400
|
+
success: false,
|
|
10401
|
+
error: "Only the approver can approve"
|
|
10402
|
+
};
|
|
10403
|
+
if (!this._pendingRequest) return {
|
|
10404
|
+
success: false,
|
|
10405
|
+
error: "No pending pairing request"
|
|
10406
|
+
};
|
|
10407
|
+
if (!this._connectedPeerId) return {
|
|
10408
|
+
success: false,
|
|
10409
|
+
error: "Peer not connected"
|
|
10410
|
+
};
|
|
10411
|
+
const req = this._pendingRequest;
|
|
10412
|
+
try {
|
|
10413
|
+
await client.addKey({
|
|
10414
|
+
publicKey: req.publicKey,
|
|
10415
|
+
deviceName: req.deviceName,
|
|
10416
|
+
x25519Key: req.x25519Key
|
|
10417
|
+
});
|
|
10418
|
+
this.sendMessage({ type: "pair-approved" });
|
|
10419
|
+
this._pendingRequest = null;
|
|
10420
|
+
this.emit("pairingComplete", { success: true });
|
|
10421
|
+
return { success: true };
|
|
10422
|
+
} catch (err) {
|
|
10423
|
+
const error = err?.message ?? "Failed to register device key";
|
|
10424
|
+
this.sendMessage({
|
|
10425
|
+
type: "pair-rejected",
|
|
10426
|
+
reason: error
|
|
10427
|
+
});
|
|
10428
|
+
this._pendingRequest = null;
|
|
10429
|
+
const result = {
|
|
10430
|
+
success: false,
|
|
10431
|
+
error
|
|
10432
|
+
};
|
|
10433
|
+
this.emit("pairingComplete", result);
|
|
10434
|
+
return result;
|
|
10435
|
+
}
|
|
10436
|
+
}
|
|
10437
|
+
/**
|
|
10438
|
+
* Approve via server-side device invite. Creates a single-use invite code
|
|
10439
|
+
* and sends it to Device B over the E2EE channel. Device B redeems it
|
|
10440
|
+
* independently via HTTP — Device A can go offline after this.
|
|
10441
|
+
*/
|
|
10442
|
+
async approveWithInvite(client) {
|
|
10443
|
+
if (this.role !== "approver") return {
|
|
10444
|
+
success: false,
|
|
10445
|
+
error: "Only the approver can approve"
|
|
10446
|
+
};
|
|
10447
|
+
if (!this._pendingRequest) return {
|
|
10448
|
+
success: false,
|
|
10449
|
+
error: "No pending pairing request"
|
|
10450
|
+
};
|
|
10451
|
+
if (!this._connectedPeerId) return {
|
|
10452
|
+
success: false,
|
|
10453
|
+
error: "Peer not connected"
|
|
10454
|
+
};
|
|
10455
|
+
try {
|
|
10456
|
+
const { code } = await client.createDeviceInvite();
|
|
10457
|
+
this.sendMessage({
|
|
10458
|
+
type: "pair-invite-code",
|
|
10459
|
+
code
|
|
10460
|
+
});
|
|
10461
|
+
this._pendingRequest = null;
|
|
10462
|
+
this.emit("pairingComplete", { success: true });
|
|
10463
|
+
return { success: true };
|
|
10464
|
+
} catch (err) {
|
|
10465
|
+
const error = err?.message ?? "Failed to create device invite";
|
|
10466
|
+
this.sendMessage({
|
|
10467
|
+
type: "pair-rejected",
|
|
10468
|
+
reason: error
|
|
10469
|
+
});
|
|
10470
|
+
this._pendingRequest = null;
|
|
10471
|
+
const result = {
|
|
10472
|
+
success: false,
|
|
10473
|
+
error
|
|
10474
|
+
};
|
|
10475
|
+
this.emit("pairingComplete", result);
|
|
10476
|
+
return result;
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
/**
|
|
10480
|
+
* Reject the pending pairing request.
|
|
10481
|
+
*/
|
|
10482
|
+
reject(reason = "Rejected by user") {
|
|
10483
|
+
if (this.role !== "approver" || !this._pendingRequest) return;
|
|
10484
|
+
this.sendMessage({
|
|
10485
|
+
type: "pair-rejected",
|
|
10486
|
+
reason
|
|
10487
|
+
});
|
|
10488
|
+
this._pendingRequest = null;
|
|
10489
|
+
this.emit("pairingComplete", {
|
|
10490
|
+
success: false,
|
|
10491
|
+
error: reason
|
|
10492
|
+
});
|
|
10493
|
+
}
|
|
10494
|
+
/**
|
|
10495
|
+
* Send a pairing request to Device A. Call this after the "connected"
|
|
10496
|
+
* event fires.
|
|
10497
|
+
*/
|
|
10498
|
+
requestPairing(request) {
|
|
10499
|
+
if (this.role !== "requester") return;
|
|
10500
|
+
this.sendMessage({
|
|
10501
|
+
type: "pair-request",
|
|
10502
|
+
publicKey: request.publicKey,
|
|
10503
|
+
x25519Key: request.x25519Key,
|
|
10504
|
+
deviceName: request.deviceName
|
|
10505
|
+
});
|
|
10506
|
+
}
|
|
10507
|
+
get isDestroyed() {
|
|
10508
|
+
return this._destroyed;
|
|
10509
|
+
}
|
|
10510
|
+
destroy() {
|
|
10511
|
+
if (this._destroyed) return;
|
|
10512
|
+
this._destroyed = true;
|
|
10513
|
+
if (this.timeoutHandle) {
|
|
10514
|
+
clearTimeout(this.timeoutHandle);
|
|
10515
|
+
this.timeoutHandle = null;
|
|
10516
|
+
}
|
|
10517
|
+
if (this.webrtc) {
|
|
10518
|
+
this.webrtc.destroy();
|
|
10519
|
+
this.webrtc = null;
|
|
10520
|
+
}
|
|
10521
|
+
this.removeAllListeners();
|
|
10522
|
+
}
|
|
10523
|
+
start() {
|
|
10524
|
+
this.webrtc = new AbracadabraWebRTC({
|
|
10525
|
+
docId: codeToRoomId(this.pairingCode),
|
|
10526
|
+
url: this.config.serverUrl,
|
|
10527
|
+
token: this.config.token,
|
|
10528
|
+
iceServers: this.config.iceServers,
|
|
10529
|
+
e2ee: this.config.e2ee,
|
|
10530
|
+
enableDocSync: false,
|
|
10531
|
+
enableAwarenessSync: false,
|
|
10532
|
+
enableFileTransfer: false,
|
|
10533
|
+
autoConnect: false,
|
|
10534
|
+
WebSocketPolyfill: this.config.WebSocketPolyfill
|
|
10535
|
+
});
|
|
10536
|
+
this.webrtc.on("e2eeEstablished", ({ peerId }) => {
|
|
10537
|
+
this._connectedPeerId = peerId;
|
|
10538
|
+
this.emit("connected");
|
|
10539
|
+
});
|
|
10540
|
+
this.webrtc.on("customMessage", ({ peerId, payload }) => {
|
|
10541
|
+
this.handleMessage(peerId, payload);
|
|
10542
|
+
});
|
|
10543
|
+
this.webrtc.on("peerLeft", () => {
|
|
10544
|
+
if (!this._destroyed) this.emit("error", /* @__PURE__ */ new Error("Peer disconnected"));
|
|
10545
|
+
});
|
|
10546
|
+
this.webrtc.on("signalingError", (err) => {
|
|
10547
|
+
this.emit("error", /* @__PURE__ */ new Error(`Signaling: ${err.message}`));
|
|
10548
|
+
});
|
|
10549
|
+
this.timeoutHandle = setTimeout(() => {
|
|
10550
|
+
if (!this._destroyed) {
|
|
10551
|
+
this.emit("error", /* @__PURE__ */ new Error("Pairing timed out"));
|
|
10552
|
+
this.destroy();
|
|
10553
|
+
}
|
|
10554
|
+
}, PAIRING_TIMEOUT_MS);
|
|
10555
|
+
this.webrtc.connect();
|
|
10556
|
+
}
|
|
10557
|
+
sendMessage(msg) {
|
|
10558
|
+
if (!this.webrtc || !this._connectedPeerId) return;
|
|
10559
|
+
this.webrtc.sendCustomMessage(this._connectedPeerId, JSON.stringify(msg));
|
|
10560
|
+
}
|
|
10561
|
+
handleMessage(peerId, payload) {
|
|
10562
|
+
let msg;
|
|
10563
|
+
try {
|
|
10564
|
+
msg = JSON.parse(payload);
|
|
10565
|
+
} catch {
|
|
10566
|
+
return;
|
|
10567
|
+
}
|
|
10568
|
+
switch (msg.type) {
|
|
10569
|
+
case "pair-request":
|
|
10570
|
+
if (this.role !== "approver") return;
|
|
10571
|
+
this._pendingRequest = {
|
|
10572
|
+
publicKey: msg.publicKey,
|
|
10573
|
+
x25519Key: msg.x25519Key,
|
|
10574
|
+
deviceName: msg.deviceName
|
|
10575
|
+
};
|
|
10576
|
+
this.emit("pairingRequest", this._pendingRequest);
|
|
10577
|
+
break;
|
|
10578
|
+
case "pair-approved":
|
|
10579
|
+
if (this.role !== "requester") return;
|
|
10580
|
+
this.emit("approved");
|
|
10581
|
+
this.emit("pairingComplete", { success: true });
|
|
10582
|
+
break;
|
|
10583
|
+
case "pair-rejected":
|
|
10584
|
+
if (this.role !== "requester") return;
|
|
10585
|
+
this.emit("rejected", msg.reason);
|
|
10586
|
+
this.emit("pairingComplete", {
|
|
10587
|
+
success: false,
|
|
10588
|
+
error: msg.reason
|
|
10589
|
+
});
|
|
10590
|
+
break;
|
|
10591
|
+
case "pair-invite-code":
|
|
10592
|
+
if (this.role !== "requester") return;
|
|
10593
|
+
this.emit("inviteCode", msg.code);
|
|
10594
|
+
break;
|
|
10595
|
+
}
|
|
10596
|
+
}
|
|
10597
|
+
};
|
|
10598
|
+
|
|
10311
10599
|
//#endregion
|
|
10312
10600
|
//#region packages/provider/src/sync/BroadcastChannelSync.ts
|
|
10313
10601
|
/**
|
|
@@ -10459,6 +10747,305 @@ var BroadcastChannelSync = class BroadcastChannelSync extends EventEmitter {
|
|
|
10459
10747
|
}
|
|
10460
10748
|
};
|
|
10461
10749
|
|
|
10750
|
+
//#endregion
|
|
10751
|
+
//#region packages/provider/src/IdentityDoc.ts
|
|
10752
|
+
/**
|
|
10753
|
+
* Derives a deterministic UUID from an Ed25519 account-level public key.
|
|
10754
|
+
*
|
|
10755
|
+
* The result is a valid UUID v5-style string that any device sharing the
|
|
10756
|
+
* same identity can independently compute. The `abracadabra:identity:`
|
|
10757
|
+
* prefix prevents collisions with randomly generated doc UUIDs.
|
|
10758
|
+
*
|
|
10759
|
+
* @param publicKeyB64 Base64url-encoded Ed25519 public key (32 bytes).
|
|
10760
|
+
*/
|
|
10761
|
+
function deriveIdentityDocId(publicKeyB64) {
|
|
10762
|
+
const hash = sha256(new TextEncoder().encode(`abracadabra:identity:${publicKeyB64}`));
|
|
10763
|
+
const bytes = new Uint8Array(hash.buffer, hash.byteOffset, 16);
|
|
10764
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
10765
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
10766
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
10767
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
10768
|
+
}
|
|
10769
|
+
/**
|
|
10770
|
+
* Manages a Y.Doc dedicated to user identity that syncs across trusted
|
|
10771
|
+
* targets only: a designated sync server, a local Tauri server, and/or
|
|
10772
|
+
* WebRTC P2P.
|
|
10773
|
+
*
|
|
10774
|
+
* The Y.Doc contains cross-device settings (profile, servers, spaces,
|
|
10775
|
+
* plugins, preferences). Each sync target gets its own
|
|
10776
|
+
* AbracadabraProvider sharing the same Y.Doc, with `serverAgnostic: true`
|
|
10777
|
+
* so they all use one IndexedDB store.
|
|
10778
|
+
*/
|
|
10779
|
+
var IdentityDocProvider = class extends EventEmitter {
|
|
10780
|
+
constructor(configuration) {
|
|
10781
|
+
super();
|
|
10782
|
+
this.providers = /* @__PURE__ */ new Map();
|
|
10783
|
+
this.websockets = /* @__PURE__ */ new Map();
|
|
10784
|
+
this.webrtc = null;
|
|
10785
|
+
this._destroyed = false;
|
|
10786
|
+
this.config = configuration;
|
|
10787
|
+
this.docId = deriveIdentityDocId(configuration.publicKey);
|
|
10788
|
+
this.document = new yjs.Doc({ guid: this.docId });
|
|
10789
|
+
if (configuration.autoConnect !== false) this.connect();
|
|
10790
|
+
}
|
|
10791
|
+
connect() {
|
|
10792
|
+
if (this._destroyed) return;
|
|
10793
|
+
const targets = [];
|
|
10794
|
+
if (this.config.localServerUrl) targets.push({
|
|
10795
|
+
url: this.config.localServerUrl,
|
|
10796
|
+
key: "local"
|
|
10797
|
+
});
|
|
10798
|
+
if (this.config.syncServerUrl) targets.push({
|
|
10799
|
+
url: this.config.syncServerUrl,
|
|
10800
|
+
key: "sync"
|
|
10801
|
+
});
|
|
10802
|
+
for (const { url, key } of targets) {
|
|
10803
|
+
if (this.providers.has(key)) continue;
|
|
10804
|
+
this._connectToServer(key, url);
|
|
10805
|
+
}
|
|
10806
|
+
if (this.config.webrtc && !this.webrtc) this._connectWebRTC();
|
|
10807
|
+
}
|
|
10808
|
+
_connectToServer(key, serverUrl) {
|
|
10809
|
+
const token = this.config.tokens?.[serverUrl] ?? this.config.token ?? "";
|
|
10810
|
+
const ws = new AbracadabraWS({
|
|
10811
|
+
url: serverUrl.replace(/^http/, "ws").replace(/\/$/, "").concat("/ws"),
|
|
10812
|
+
WebSocketPolyfill: void 0
|
|
10813
|
+
});
|
|
10814
|
+
this.websockets.set(key, ws);
|
|
10815
|
+
const provider = new AbracadabraProvider({
|
|
10816
|
+
name: this.docId,
|
|
10817
|
+
document: this.document,
|
|
10818
|
+
websocketProvider: ws,
|
|
10819
|
+
token,
|
|
10820
|
+
serverAgnostic: true,
|
|
10821
|
+
disableOfflineStore: key !== "local" && key !== "sync" ? true : this.config.disableOfflineStore ?? false,
|
|
10822
|
+
...this.config.providerDefaults
|
|
10823
|
+
});
|
|
10824
|
+
provider.on("synced", () => this.emit("synced", { server: key }));
|
|
10825
|
+
provider.on("status", (data) => this.emit("status", {
|
|
10826
|
+
server: key,
|
|
10827
|
+
...data
|
|
10828
|
+
}));
|
|
10829
|
+
this.providers.set(key, provider);
|
|
10830
|
+
}
|
|
10831
|
+
_connectWebRTC() {
|
|
10832
|
+
const rtcConfig = this.config.webrtc;
|
|
10833
|
+
if (!rtcConfig) return;
|
|
10834
|
+
this.webrtc = new AbracadabraWebRTC({
|
|
10835
|
+
docId: this.docId,
|
|
10836
|
+
url: rtcConfig.signalingServerUrl,
|
|
10837
|
+
token: rtcConfig.token,
|
|
10838
|
+
document: this.document,
|
|
10839
|
+
enableDocSync: true,
|
|
10840
|
+
enableAwarenessSync: false,
|
|
10841
|
+
enableFileTransfer: false,
|
|
10842
|
+
e2ee: rtcConfig.e2ee,
|
|
10843
|
+
iceServers: rtcConfig.iceServers
|
|
10844
|
+
});
|
|
10845
|
+
}
|
|
10846
|
+
get profileMap() {
|
|
10847
|
+
return this.document.getMap("profile");
|
|
10848
|
+
}
|
|
10849
|
+
get serversMap() {
|
|
10850
|
+
return this.document.getMap("servers");
|
|
10851
|
+
}
|
|
10852
|
+
get spacesArray() {
|
|
10853
|
+
return this.document.getArray("spaces");
|
|
10854
|
+
}
|
|
10855
|
+
get pluginsMap() {
|
|
10856
|
+
return this.document.getMap("plugins");
|
|
10857
|
+
}
|
|
10858
|
+
get preferencesMap() {
|
|
10859
|
+
return this.document.getMap("preferences");
|
|
10860
|
+
}
|
|
10861
|
+
getProfile() {
|
|
10862
|
+
const m = this.profileMap;
|
|
10863
|
+
return {
|
|
10864
|
+
username: m.get("username"),
|
|
10865
|
+
displayName: m.get("displayName"),
|
|
10866
|
+
colorName: m.get("colorName"),
|
|
10867
|
+
neutralColorName: m.get("neutralColorName"),
|
|
10868
|
+
locale: m.get("locale"),
|
|
10869
|
+
avatarUrl: m.get("avatarUrl")
|
|
10870
|
+
};
|
|
10871
|
+
}
|
|
10872
|
+
setProfile(profile) {
|
|
10873
|
+
const m = this.profileMap;
|
|
10874
|
+
this.document.transact(() => {
|
|
10875
|
+
for (const [key, value] of Object.entries(profile)) if (value !== void 0) m.set(key, value);
|
|
10876
|
+
});
|
|
10877
|
+
}
|
|
10878
|
+
getServer(url) {
|
|
10879
|
+
const entry = this.serversMap.get(url);
|
|
10880
|
+
if (!entry) return void 0;
|
|
10881
|
+
return {
|
|
10882
|
+
label: entry.get("label") ?? url,
|
|
10883
|
+
hubDocId: entry.get("hubDocId"),
|
|
10884
|
+
entryDocId: entry.get("entryDocId"),
|
|
10885
|
+
defaultRole: entry.get("defaultRole"),
|
|
10886
|
+
spacesEnabled: entry.get("spacesEnabled"),
|
|
10887
|
+
addedAt: entry.get("addedAt") ?? 0
|
|
10888
|
+
};
|
|
10889
|
+
}
|
|
10890
|
+
setServer(url, entry) {
|
|
10891
|
+
const m = this.serversMap;
|
|
10892
|
+
this.document.transact(() => {
|
|
10893
|
+
let yEntry = m.get(url);
|
|
10894
|
+
if (!yEntry) {
|
|
10895
|
+
yEntry = new yjs.Map();
|
|
10896
|
+
m.set(url, yEntry);
|
|
10897
|
+
}
|
|
10898
|
+
for (const [key, value] of Object.entries(entry)) if (value !== void 0) yEntry.set(key, value);
|
|
10899
|
+
});
|
|
10900
|
+
}
|
|
10901
|
+
removeServer(url) {
|
|
10902
|
+
this.serversMap.delete(url);
|
|
10903
|
+
}
|
|
10904
|
+
getServers() {
|
|
10905
|
+
const result = /* @__PURE__ */ new Map();
|
|
10906
|
+
this.serversMap.forEach((entry, url) => {
|
|
10907
|
+
result.set(url, {
|
|
10908
|
+
label: entry.get("label") ?? url,
|
|
10909
|
+
hubDocId: entry.get("hubDocId"),
|
|
10910
|
+
entryDocId: entry.get("entryDocId"),
|
|
10911
|
+
defaultRole: entry.get("defaultRole"),
|
|
10912
|
+
spacesEnabled: entry.get("spacesEnabled"),
|
|
10913
|
+
addedAt: entry.get("addedAt") ?? 0
|
|
10914
|
+
});
|
|
10915
|
+
});
|
|
10916
|
+
return result;
|
|
10917
|
+
}
|
|
10918
|
+
getSpaces() {
|
|
10919
|
+
const result = [];
|
|
10920
|
+
this.spacesArray.forEach((yMap) => {
|
|
10921
|
+
result.push({
|
|
10922
|
+
id: yMap.get("id"),
|
|
10923
|
+
name: yMap.get("name"),
|
|
10924
|
+
type: yMap.get("type") ?? "remote",
|
|
10925
|
+
serverUrl: yMap.get("serverUrl") ?? null,
|
|
10926
|
+
docId: yMap.get("docId"),
|
|
10927
|
+
remoteSpaceId: yMap.get("remoteSpaceId"),
|
|
10928
|
+
visibility: yMap.get("visibility") ?? "private",
|
|
10929
|
+
isHub: yMap.get("isHub") ?? false,
|
|
10930
|
+
order: yMap.get("order") ?? 0,
|
|
10931
|
+
lastSyncedAt: yMap.get("lastSyncedAt"),
|
|
10932
|
+
createdAt: yMap.get("createdAt") ?? 0
|
|
10933
|
+
});
|
|
10934
|
+
});
|
|
10935
|
+
return result;
|
|
10936
|
+
}
|
|
10937
|
+
addSpace(space) {
|
|
10938
|
+
const yMap = new yjs.Map();
|
|
10939
|
+
this.document.transact(() => {
|
|
10940
|
+
for (const [key, value] of Object.entries(space)) if (value !== void 0) yMap.set(key, value);
|
|
10941
|
+
this.spacesArray.push([yMap]);
|
|
10942
|
+
});
|
|
10943
|
+
}
|
|
10944
|
+
removeSpace(spaceId) {
|
|
10945
|
+
const arr = this.spacesArray;
|
|
10946
|
+
for (let i = 0; i < arr.length; i++) if (arr.get(i).get("id") === spaceId) {
|
|
10947
|
+
arr.delete(i, 1);
|
|
10948
|
+
return;
|
|
10949
|
+
}
|
|
10950
|
+
}
|
|
10951
|
+
updateSpace(spaceId, updates) {
|
|
10952
|
+
const arr = this.spacesArray;
|
|
10953
|
+
for (let i = 0; i < arr.length; i++) {
|
|
10954
|
+
const yMap = arr.get(i);
|
|
10955
|
+
if (yMap.get("id") === spaceId) {
|
|
10956
|
+
this.document.transact(() => {
|
|
10957
|
+
for (const [key, value] of Object.entries(updates)) if (value !== void 0) yMap.set(key, value);
|
|
10958
|
+
});
|
|
10959
|
+
return;
|
|
10960
|
+
}
|
|
10961
|
+
}
|
|
10962
|
+
}
|
|
10963
|
+
getExternalPlugins() {
|
|
10964
|
+
let arr = this.pluginsMap.get("external");
|
|
10965
|
+
if (!arr) {
|
|
10966
|
+
arr = new yjs.Array();
|
|
10967
|
+
this.pluginsMap.set("external", arr);
|
|
10968
|
+
}
|
|
10969
|
+
return arr;
|
|
10970
|
+
}
|
|
10971
|
+
getDisabledBuiltins() {
|
|
10972
|
+
let arr = this.pluginsMap.get("disabledBuiltins");
|
|
10973
|
+
if (!arr) {
|
|
10974
|
+
arr = new yjs.Array();
|
|
10975
|
+
this.pluginsMap.set("disabledBuiltins", arr);
|
|
10976
|
+
}
|
|
10977
|
+
return arr;
|
|
10978
|
+
}
|
|
10979
|
+
getPreference(key) {
|
|
10980
|
+
return this.preferencesMap.get(key);
|
|
10981
|
+
}
|
|
10982
|
+
setPreference(key, value) {
|
|
10983
|
+
this.preferencesMap.set(key, value);
|
|
10984
|
+
}
|
|
10985
|
+
/**
|
|
10986
|
+
* Observe deep changes on a specific top-level map.
|
|
10987
|
+
* Returns an unsubscribe function.
|
|
10988
|
+
*/
|
|
10989
|
+
observe(mapName, callback) {
|
|
10990
|
+
const map = this.document.getMap(mapName);
|
|
10991
|
+
map.observeDeep(callback);
|
|
10992
|
+
return () => map.unobserveDeep(callback);
|
|
10993
|
+
}
|
|
10994
|
+
/**
|
|
10995
|
+
* Observe changes to the spaces array.
|
|
10996
|
+
* Returns an unsubscribe function.
|
|
10997
|
+
*/
|
|
10998
|
+
observeSpaces(callback) {
|
|
10999
|
+
const arr = this.spacesArray;
|
|
11000
|
+
arr.observe(callback);
|
|
11001
|
+
return () => arr.unobserve(callback);
|
|
11002
|
+
}
|
|
11003
|
+
/**
|
|
11004
|
+
* Returns true if the identity doc has no profile data yet (first use).
|
|
11005
|
+
* Call this to decide whether to run migration from localStorage.
|
|
11006
|
+
*/
|
|
11007
|
+
isEmpty() {
|
|
11008
|
+
return this.profileMap.size === 0 && this.serversMap.size === 0;
|
|
11009
|
+
}
|
|
11010
|
+
/**
|
|
11011
|
+
* Update the sync server URL at runtime (e.g. when user changes their
|
|
11012
|
+
* designated sync server in settings).
|
|
11013
|
+
*/
|
|
11014
|
+
setSyncServer(url) {
|
|
11015
|
+
const existing = this.providers.get("sync");
|
|
11016
|
+
if (existing) {
|
|
11017
|
+
existing.destroy();
|
|
11018
|
+
this.providers.delete("sync");
|
|
11019
|
+
}
|
|
11020
|
+
const existingWs = this.websockets.get("sync");
|
|
11021
|
+
if (existingWs) {
|
|
11022
|
+
existingWs.destroy();
|
|
11023
|
+
this.websockets.delete("sync");
|
|
11024
|
+
}
|
|
11025
|
+
if (url) {
|
|
11026
|
+
this.config = {
|
|
11027
|
+
...this.config,
|
|
11028
|
+
syncServerUrl: url
|
|
11029
|
+
};
|
|
11030
|
+
this._connectToServer("sync", url);
|
|
11031
|
+
}
|
|
11032
|
+
}
|
|
11033
|
+
getProvider(key) {
|
|
11034
|
+
return this.providers.get(key);
|
|
11035
|
+
}
|
|
11036
|
+
destroy() {
|
|
11037
|
+
if (this._destroyed) return;
|
|
11038
|
+
this._destroyed = true;
|
|
11039
|
+
for (const [, provider] of this.providers) provider.destroy();
|
|
11040
|
+
for (const [, ws] of this.websockets) ws.destroy();
|
|
11041
|
+
if (this.webrtc) this.webrtc.disconnect?.();
|
|
11042
|
+
this.providers.clear();
|
|
11043
|
+
this.websockets.clear();
|
|
11044
|
+
this.webrtc = null;
|
|
11045
|
+
this.document.destroy();
|
|
11046
|
+
}
|
|
11047
|
+
};
|
|
11048
|
+
|
|
10462
11049
|
//#endregion
|
|
10463
11050
|
exports.AbracadabraBaseProvider = AbracadabraBaseProvider;
|
|
10464
11051
|
exports.AbracadabraClient = AbracadabraClient;
|
|
@@ -10476,6 +11063,7 @@ exports.CryptoIdentityKeystore = CryptoIdentityKeystore;
|
|
|
10476
11063
|
exports.DEFAULT_FILE_CHUNK_SIZE = DEFAULT_FILE_CHUNK_SIZE;
|
|
10477
11064
|
exports.DEFAULT_ICE_SERVERS = DEFAULT_ICE_SERVERS;
|
|
10478
11065
|
exports.DataChannelRouter = DataChannelRouter;
|
|
11066
|
+
exports.DevicePairingChannel = DevicePairingChannel;
|
|
10479
11067
|
exports.DocKeyManager = DocKeyManager;
|
|
10480
11068
|
exports.DocumentCache = DocumentCache;
|
|
10481
11069
|
exports.E2EAbracadabraProvider = E2EAbracadabraProvider;
|
|
@@ -10489,6 +11077,7 @@ exports.FileTransferHandle = FileTransferHandle;
|
|
|
10489
11077
|
exports.Forbidden = Forbidden;
|
|
10490
11078
|
exports.HocuspocusProvider = HocuspocusProvider;
|
|
10491
11079
|
exports.HocuspocusProviderWebsocket = HocuspocusProviderWebsocket;
|
|
11080
|
+
exports.IdentityDocProvider = IdentityDocProvider;
|
|
10492
11081
|
exports.KEY_EXCHANGE_CHANNEL = KEY_EXCHANGE_CHANNEL;
|
|
10493
11082
|
exports.ManualSignaling = ManualSignaling;
|
|
10494
11083
|
exports.MessageTooBig = MessageTooBig;
|
|
@@ -10506,6 +11095,7 @@ exports.YjsDataChannel = YjsDataChannel;
|
|
|
10506
11095
|
exports.attachUpdatedAtObserver = attachUpdatedAtObserver;
|
|
10507
11096
|
exports.awarenessStatesToArray = awarenessStatesToArray;
|
|
10508
11097
|
exports.decryptField = decryptField;
|
|
11098
|
+
exports.deriveIdentityDocId = deriveIdentityDocId;
|
|
10509
11099
|
exports.encryptField = encryptField;
|
|
10510
11100
|
exports.makeEncryptedYMap = makeEncryptedYMap;
|
|
10511
11101
|
exports.makeEncryptedYText = makeEncryptedYText;
|