@abraca/dabra 1.0.24 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abracadabra-provider.cjs +274 -9
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +274 -10
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +87 -2
- package/package.json +1 -1
- package/src/BackgroundSyncManager.ts +26 -0
- package/src/DeviceRegistrationService.ts +243 -0
- package/src/IdentityDoc.ts +143 -1
- package/src/index.ts +4 -0
- package/src/webrtc/AbracadabraWebRTC.ts +22 -11
|
@@ -8672,6 +8672,15 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8672
8672
|
const childProvider = await this.rootProvider.loadChild(docId);
|
|
8673
8673
|
await childProvider.ready;
|
|
8674
8674
|
await this._waitForSynced(childProvider);
|
|
8675
|
+
{
|
|
8676
|
+
const treeEntry = this.rootProvider.document.getMap("doc-tree").get(docId);
|
|
8677
|
+
this.emit("docSynced", {
|
|
8678
|
+
docId,
|
|
8679
|
+
document: childProvider.document,
|
|
8680
|
+
label: treeEntry?.label ?? "",
|
|
8681
|
+
meta: treeEntry?.meta
|
|
8682
|
+
});
|
|
8683
|
+
}
|
|
8675
8684
|
if (this.opts.prefetchFiles && this.fileBlobStore) this._prefetchDocFiles(docId, childProvider.document).catch(() => null);
|
|
8676
8685
|
if (!alreadyCached) this.rootProvider.unloadChild(docId);
|
|
8677
8686
|
return {
|
|
@@ -8701,6 +8710,15 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8701
8710
|
try {
|
|
8702
8711
|
await childProvider.ready;
|
|
8703
8712
|
await this._waitForSynced(childProvider);
|
|
8713
|
+
{
|
|
8714
|
+
const treeEntry = this.rootProvider.document.getMap("doc-tree").get(docId);
|
|
8715
|
+
this.emit("docSynced", {
|
|
8716
|
+
docId,
|
|
8717
|
+
document: childDoc,
|
|
8718
|
+
label: treeEntry?.label ?? "",
|
|
8719
|
+
meta: treeEntry?.meta
|
|
8720
|
+
});
|
|
8721
|
+
}
|
|
8704
8722
|
if (this.opts.prefetchFiles && this.fileBlobStore) this._prefetchDocFiles(docId, childDoc).catch(() => null);
|
|
8705
8723
|
return {
|
|
8706
8724
|
docId,
|
|
@@ -9987,17 +10005,20 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
|
|
|
9987
10005
|
const pc = this.peerConnections.get(peerId);
|
|
9988
10006
|
if (!pc) return;
|
|
9989
10007
|
const channelName = "custom";
|
|
9990
|
-
|
|
9991
|
-
if (
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
const data = new TextEncoder().encode(payload);
|
|
9995
|
-
pc.router.send(channelName, data).catch(() => {});
|
|
9996
|
-
};
|
|
10008
|
+
const channel = pc.router.getChannel(channelName);
|
|
10009
|
+
if (channel && channel.readyState === "open") {
|
|
10010
|
+
const data = new TextEncoder().encode(payload);
|
|
10011
|
+
pc.router.send(channelName, data).catch(() => {});
|
|
9997
10012
|
return;
|
|
9998
10013
|
}
|
|
9999
|
-
const
|
|
10000
|
-
|
|
10014
|
+
const onOpen = ({ name }) => {
|
|
10015
|
+
if (name === channelName) {
|
|
10016
|
+
pc.router.off("channelOpen", onOpen);
|
|
10017
|
+
const data = new TextEncoder().encode(payload);
|
|
10018
|
+
pc.router.send(channelName, data).catch(() => {});
|
|
10019
|
+
}
|
|
10020
|
+
};
|
|
10021
|
+
pc.router.on("channelOpen", onOpen);
|
|
10001
10022
|
}
|
|
10002
10023
|
/**
|
|
10003
10024
|
* Send a custom string message to all connected peers.
|
|
@@ -10153,6 +10174,7 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
|
|
|
10153
10174
|
enableAwareness: this.config.enableAwarenessSync,
|
|
10154
10175
|
enableFileTransfer: this.config.enableFileTransfer
|
|
10155
10176
|
});
|
|
10177
|
+
pc.router.createChannel("custom", { ordered: true });
|
|
10156
10178
|
try {
|
|
10157
10179
|
const sdp = await pc.createOffer();
|
|
10158
10180
|
this.signaling?.sendOffer(peerId, sdp);
|
|
@@ -10949,6 +10971,9 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
10949
10971
|
get pluginsMap() {
|
|
10950
10972
|
return this.document.getMap("plugins");
|
|
10951
10973
|
}
|
|
10974
|
+
get devicesMap() {
|
|
10975
|
+
return this.document.getMap("devices");
|
|
10976
|
+
}
|
|
10952
10977
|
get preferencesMap() {
|
|
10953
10978
|
return this.document.getMap("preferences");
|
|
10954
10979
|
}
|
|
@@ -11076,6 +11101,97 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
11076
11101
|
setPreference(key, value) {
|
|
11077
11102
|
this.preferencesMap.set(key, value);
|
|
11078
11103
|
}
|
|
11104
|
+
getDevice(publicKey) {
|
|
11105
|
+
const entry = this.devicesMap.get(publicKey);
|
|
11106
|
+
if (!entry) return void 0;
|
|
11107
|
+
const servers = /* @__PURE__ */ new Map();
|
|
11108
|
+
const serversYMap = entry.get("servers");
|
|
11109
|
+
if (serversYMap) serversYMap.forEach((sEntry, url) => {
|
|
11110
|
+
servers.set(url, {
|
|
11111
|
+
registered: sEntry.get("registered") ?? false,
|
|
11112
|
+
registeredAt: sEntry.get("registeredAt") ?? null,
|
|
11113
|
+
keyId: sEntry.get("keyId") ?? null,
|
|
11114
|
+
lastVerifiedAt: sEntry.get("lastVerifiedAt") ?? null,
|
|
11115
|
+
error: sEntry.get("error") ?? null
|
|
11116
|
+
});
|
|
11117
|
+
});
|
|
11118
|
+
return {
|
|
11119
|
+
deviceName: entry.get("deviceName") ?? "Unknown",
|
|
11120
|
+
tier: entry.get("tier") ?? "paired",
|
|
11121
|
+
x25519Key: entry.get("x25519Key") ?? null,
|
|
11122
|
+
addedAt: entry.get("addedAt") ?? 0,
|
|
11123
|
+
revokedAt: entry.get("revokedAt") ?? null,
|
|
11124
|
+
revokedBy: entry.get("revokedBy") ?? null,
|
|
11125
|
+
servers
|
|
11126
|
+
};
|
|
11127
|
+
}
|
|
11128
|
+
getDevices() {
|
|
11129
|
+
const result = /* @__PURE__ */ new Map();
|
|
11130
|
+
this.devicesMap.forEach((_entry, pubKey) => {
|
|
11131
|
+
const device = this.getDevice(pubKey);
|
|
11132
|
+
if (device) result.set(pubKey, device);
|
|
11133
|
+
});
|
|
11134
|
+
return result;
|
|
11135
|
+
}
|
|
11136
|
+
addDevice(publicKey, opts) {
|
|
11137
|
+
this.document.transact(() => {
|
|
11138
|
+
let entry = this.devicesMap.get(publicKey);
|
|
11139
|
+
if (!entry) {
|
|
11140
|
+
entry = new yjs.Map();
|
|
11141
|
+
this.devicesMap.set(publicKey, entry);
|
|
11142
|
+
}
|
|
11143
|
+
entry.set("deviceName", opts.deviceName);
|
|
11144
|
+
entry.set("tier", opts.tier);
|
|
11145
|
+
if (opts.x25519Key) entry.set("x25519Key", opts.x25519Key);
|
|
11146
|
+
entry.set("addedAt", Date.now());
|
|
11147
|
+
entry.set("revokedAt", null);
|
|
11148
|
+
entry.set("revokedBy", null);
|
|
11149
|
+
if (opts.serverUrl) {
|
|
11150
|
+
let servers = entry.get("servers");
|
|
11151
|
+
if (!servers) {
|
|
11152
|
+
servers = new yjs.Map();
|
|
11153
|
+
entry.set("servers", servers);
|
|
11154
|
+
}
|
|
11155
|
+
const sEntry = new yjs.Map();
|
|
11156
|
+
sEntry.set("registered", true);
|
|
11157
|
+
sEntry.set("registeredAt", Date.now());
|
|
11158
|
+
sEntry.set("keyId", opts.keyId ?? null);
|
|
11159
|
+
sEntry.set("lastVerifiedAt", Date.now());
|
|
11160
|
+
sEntry.set("error", null);
|
|
11161
|
+
servers.set(opts.serverUrl, sEntry);
|
|
11162
|
+
}
|
|
11163
|
+
});
|
|
11164
|
+
}
|
|
11165
|
+
revokeDevice(publicKey, revokedBy) {
|
|
11166
|
+
const entry = this.devicesMap.get(publicKey);
|
|
11167
|
+
if (!entry) return;
|
|
11168
|
+
this.document.transact(() => {
|
|
11169
|
+
entry.set("revokedAt", Date.now());
|
|
11170
|
+
entry.set("revokedBy", revokedBy);
|
|
11171
|
+
});
|
|
11172
|
+
}
|
|
11173
|
+
setDeviceServerStatus(devicePubKey, serverUrl, status) {
|
|
11174
|
+
const entry = this.devicesMap.get(devicePubKey);
|
|
11175
|
+
if (!entry) return;
|
|
11176
|
+
this.document.transact(() => {
|
|
11177
|
+
let servers = entry.get("servers");
|
|
11178
|
+
if (!servers) {
|
|
11179
|
+
servers = new yjs.Map();
|
|
11180
|
+
entry.set("servers", servers);
|
|
11181
|
+
}
|
|
11182
|
+
let sEntry = servers.get(serverUrl);
|
|
11183
|
+
if (!sEntry) {
|
|
11184
|
+
sEntry = new yjs.Map();
|
|
11185
|
+
sEntry.set("registered", false);
|
|
11186
|
+
sEntry.set("registeredAt", null);
|
|
11187
|
+
sEntry.set("keyId", null);
|
|
11188
|
+
sEntry.set("lastVerifiedAt", null);
|
|
11189
|
+
sEntry.set("error", null);
|
|
11190
|
+
servers.set(serverUrl, sEntry);
|
|
11191
|
+
}
|
|
11192
|
+
for (const [key, value] of Object.entries(status)) if (value !== void 0) sEntry.set(key, value);
|
|
11193
|
+
});
|
|
11194
|
+
}
|
|
11079
11195
|
/**
|
|
11080
11196
|
* Observe deep changes on a specific top-level map.
|
|
11081
11197
|
* Returns an unsubscribe function.
|
|
@@ -11140,6 +11256,154 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
11140
11256
|
}
|
|
11141
11257
|
};
|
|
11142
11258
|
|
|
11259
|
+
//#endregion
|
|
11260
|
+
//#region packages/provider/src/DeviceRegistrationService.ts
|
|
11261
|
+
/**
|
|
11262
|
+
* Handles cross-server device key registration and revocation propagation.
|
|
11263
|
+
*
|
|
11264
|
+
* All methods operate on the identity Y.Doc's `devicesMap` and authenticate
|
|
11265
|
+
* independently on each server using the master key's `signChallenge`.
|
|
11266
|
+
*/
|
|
11267
|
+
var DeviceRegistrationService = class {
|
|
11268
|
+
/**
|
|
11269
|
+
* Fan out a device key to all servers in `serversMap` where it isn't registered.
|
|
11270
|
+
*
|
|
11271
|
+
* Must be called from a master device that can sign challenges with the
|
|
11272
|
+
* account-level private key.
|
|
11273
|
+
*/
|
|
11274
|
+
static async fanOutDeviceKey(opts) {
|
|
11275
|
+
const { devicePubKey, x25519Key, deviceName, identityDoc, masterPubKey, signChallenge, skipServerUrl } = opts;
|
|
11276
|
+
const servers = identityDoc.getServers();
|
|
11277
|
+
const results = [];
|
|
11278
|
+
if (!identityDoc.getDevice(devicePubKey)) identityDoc.addDevice(devicePubKey, {
|
|
11279
|
+
deviceName: deviceName ?? "Unknown",
|
|
11280
|
+
tier: "paired",
|
|
11281
|
+
x25519Key
|
|
11282
|
+
});
|
|
11283
|
+
for (const [serverUrl] of servers) {
|
|
11284
|
+
if (skipServerUrl && serverUrl === skipServerUrl) continue;
|
|
11285
|
+
if ((identityDoc.getDevice(devicePubKey)?.servers.get(serverUrl))?.registered) continue;
|
|
11286
|
+
try {
|
|
11287
|
+
const client = new AbracadabraClient({ url: serverUrl });
|
|
11288
|
+
await client.loginWithKey(masterPubKey, signChallenge);
|
|
11289
|
+
await client.addKey({
|
|
11290
|
+
publicKey: devicePubKey,
|
|
11291
|
+
x25519Key,
|
|
11292
|
+
deviceName
|
|
11293
|
+
});
|
|
11294
|
+
const match = (await client.listKeys()).find((k) => k.publicKey === devicePubKey && !k.revoked);
|
|
11295
|
+
identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, {
|
|
11296
|
+
registered: true,
|
|
11297
|
+
registeredAt: Date.now(),
|
|
11298
|
+
keyId: match?.id ?? null,
|
|
11299
|
+
lastVerifiedAt: Date.now(),
|
|
11300
|
+
error: null
|
|
11301
|
+
});
|
|
11302
|
+
results.push({
|
|
11303
|
+
serverUrl,
|
|
11304
|
+
success: true
|
|
11305
|
+
});
|
|
11306
|
+
} catch (e) {
|
|
11307
|
+
identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, { error: e?.message ?? "Registration failed" });
|
|
11308
|
+
results.push({
|
|
11309
|
+
serverUrl,
|
|
11310
|
+
success: false,
|
|
11311
|
+
error: e?.message ?? "Registration failed"
|
|
11312
|
+
});
|
|
11313
|
+
}
|
|
11314
|
+
}
|
|
11315
|
+
return results;
|
|
11316
|
+
}
|
|
11317
|
+
/**
|
|
11318
|
+
* Propagate pending revocations from the identity Y.Doc to all servers.
|
|
11319
|
+
*
|
|
11320
|
+
* Scans `devicesMap` for entries with `revokedAt` set and revokes them
|
|
11321
|
+
* on every server where they're still registered. Must be called from
|
|
11322
|
+
* a master device.
|
|
11323
|
+
*/
|
|
11324
|
+
static async propagateRevocations(opts) {
|
|
11325
|
+
const { identityDoc, masterPubKey, signChallenge } = opts;
|
|
11326
|
+
const devices = identityDoc.getDevices();
|
|
11327
|
+
const results = [];
|
|
11328
|
+
for (const [devicePubKey, device] of devices) {
|
|
11329
|
+
if (!device.revokedAt) continue;
|
|
11330
|
+
for (const [serverUrl, serverStatus] of device.servers) {
|
|
11331
|
+
if (!serverStatus.registered || !serverStatus.keyId) continue;
|
|
11332
|
+
try {
|
|
11333
|
+
const client = new AbracadabraClient({ url: serverUrl });
|
|
11334
|
+
await client.loginWithKey(masterPubKey, signChallenge);
|
|
11335
|
+
await client.revokeKey(serverStatus.keyId);
|
|
11336
|
+
identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, {
|
|
11337
|
+
registered: false,
|
|
11338
|
+
error: null
|
|
11339
|
+
});
|
|
11340
|
+
results.push({
|
|
11341
|
+
serverUrl,
|
|
11342
|
+
success: true
|
|
11343
|
+
});
|
|
11344
|
+
} catch (e) {
|
|
11345
|
+
results.push({
|
|
11346
|
+
serverUrl,
|
|
11347
|
+
success: false,
|
|
11348
|
+
error: e?.message ?? "Revocation failed"
|
|
11349
|
+
});
|
|
11350
|
+
}
|
|
11351
|
+
}
|
|
11352
|
+
}
|
|
11353
|
+
return results;
|
|
11354
|
+
}
|
|
11355
|
+
/**
|
|
11356
|
+
* Reconcile the identity Y.Doc's `devicesMap` with a server's actual
|
|
11357
|
+
* device key list. Call this on every server connection.
|
|
11358
|
+
*
|
|
11359
|
+
* - Updates registration status in Y.Doc to match server reality
|
|
11360
|
+
* - Imports unknown server keys as legacy entries
|
|
11361
|
+
* - Executes pending revocations if the caller is a master device
|
|
11362
|
+
*/
|
|
11363
|
+
static async reconcile(opts) {
|
|
11364
|
+
const { identityDoc, serverUrl, client, isMaster } = opts;
|
|
11365
|
+
let serverKeys;
|
|
11366
|
+
try {
|
|
11367
|
+
serverKeys = await client.listKeys();
|
|
11368
|
+
} catch {
|
|
11369
|
+
return;
|
|
11370
|
+
}
|
|
11371
|
+
const devicesMap = identityDoc.devicesMap;
|
|
11372
|
+
devicesMap.forEach((yEntry, devicePub) => {
|
|
11373
|
+
const serverEntry = yEntry.get("servers")?.get(serverUrl);
|
|
11374
|
+
const serverKey = serverKeys.find((k) => k.publicKey === devicePub);
|
|
11375
|
+
const revokedAt = yEntry.get("revokedAt");
|
|
11376
|
+
if (serverEntry?.get("registered") && !serverKey) identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
|
|
11377
|
+
registered: false,
|
|
11378
|
+
keyId: null
|
|
11379
|
+
});
|
|
11380
|
+
if (serverKey && !serverKey.revoked) {
|
|
11381
|
+
if (!serverEntry?.get("registered")) identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
|
|
11382
|
+
registered: true,
|
|
11383
|
+
keyId: serverKey.id,
|
|
11384
|
+
lastVerifiedAt: Date.now()
|
|
11385
|
+
});
|
|
11386
|
+
else identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
|
|
11387
|
+
lastVerifiedAt: Date.now(),
|
|
11388
|
+
keyId: serverKey.id
|
|
11389
|
+
});
|
|
11390
|
+
if (revokedAt && isMaster) client.revokeKey(serverKey.id).then(() => {
|
|
11391
|
+
identityDoc.setDeviceServerStatus(devicePub, serverUrl, { registered: false });
|
|
11392
|
+
}).catch(() => {});
|
|
11393
|
+
}
|
|
11394
|
+
});
|
|
11395
|
+
for (const key of serverKeys) {
|
|
11396
|
+
if (key.revoked) continue;
|
|
11397
|
+
if (!devicesMap.has(key.publicKey)) identityDoc.addDevice(key.publicKey, {
|
|
11398
|
+
deviceName: key.deviceName ?? "Unknown",
|
|
11399
|
+
tier: "paired",
|
|
11400
|
+
serverUrl,
|
|
11401
|
+
keyId: key.id
|
|
11402
|
+
});
|
|
11403
|
+
}
|
|
11404
|
+
}
|
|
11405
|
+
};
|
|
11406
|
+
|
|
11143
11407
|
//#endregion
|
|
11144
11408
|
exports.AbracadabraBaseProvider = AbracadabraBaseProvider;
|
|
11145
11409
|
exports.AbracadabraClient = AbracadabraClient;
|
|
@@ -11158,6 +11422,7 @@ exports.DEFAULT_FILE_CHUNK_SIZE = DEFAULT_FILE_CHUNK_SIZE;
|
|
|
11158
11422
|
exports.DEFAULT_ICE_SERVERS = DEFAULT_ICE_SERVERS;
|
|
11159
11423
|
exports.DataChannelRouter = DataChannelRouter;
|
|
11160
11424
|
exports.DevicePairingChannel = DevicePairingChannel;
|
|
11425
|
+
exports.DeviceRegistrationService = DeviceRegistrationService;
|
|
11161
11426
|
exports.DocKeyManager = DocKeyManager;
|
|
11162
11427
|
exports.DocumentCache = DocumentCache;
|
|
11163
11428
|
exports.E2EAbracadabraProvider = E2EAbracadabraProvider;
|