@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
|
@@ -8620,6 +8620,15 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8620
8620
|
const childProvider = await this.rootProvider.loadChild(docId);
|
|
8621
8621
|
await childProvider.ready;
|
|
8622
8622
|
await this._waitForSynced(childProvider);
|
|
8623
|
+
{
|
|
8624
|
+
const treeEntry = this.rootProvider.document.getMap("doc-tree").get(docId);
|
|
8625
|
+
this.emit("docSynced", {
|
|
8626
|
+
docId,
|
|
8627
|
+
document: childProvider.document,
|
|
8628
|
+
label: treeEntry?.label ?? "",
|
|
8629
|
+
meta: treeEntry?.meta
|
|
8630
|
+
});
|
|
8631
|
+
}
|
|
8623
8632
|
if (this.opts.prefetchFiles && this.fileBlobStore) this._prefetchDocFiles(docId, childProvider.document).catch(() => null);
|
|
8624
8633
|
if (!alreadyCached) this.rootProvider.unloadChild(docId);
|
|
8625
8634
|
return {
|
|
@@ -8649,6 +8658,15 @@ var BackgroundSyncManager = class extends EventEmitter {
|
|
|
8649
8658
|
try {
|
|
8650
8659
|
await childProvider.ready;
|
|
8651
8660
|
await this._waitForSynced(childProvider);
|
|
8661
|
+
{
|
|
8662
|
+
const treeEntry = this.rootProvider.document.getMap("doc-tree").get(docId);
|
|
8663
|
+
this.emit("docSynced", {
|
|
8664
|
+
docId,
|
|
8665
|
+
document: childDoc,
|
|
8666
|
+
label: treeEntry?.label ?? "",
|
|
8667
|
+
meta: treeEntry?.meta
|
|
8668
|
+
});
|
|
8669
|
+
}
|
|
8652
8670
|
if (this.opts.prefetchFiles && this.fileBlobStore) this._prefetchDocFiles(docId, childDoc).catch(() => null);
|
|
8653
8671
|
return {
|
|
8654
8672
|
docId,
|
|
@@ -9935,17 +9953,20 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
|
|
|
9935
9953
|
const pc = this.peerConnections.get(peerId);
|
|
9936
9954
|
if (!pc) return;
|
|
9937
9955
|
const channelName = "custom";
|
|
9938
|
-
|
|
9939
|
-
if (
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
const data = new TextEncoder().encode(payload);
|
|
9943
|
-
pc.router.send(channelName, data).catch(() => {});
|
|
9944
|
-
};
|
|
9956
|
+
const channel = pc.router.getChannel(channelName);
|
|
9957
|
+
if (channel && channel.readyState === "open") {
|
|
9958
|
+
const data = new TextEncoder().encode(payload);
|
|
9959
|
+
pc.router.send(channelName, data).catch(() => {});
|
|
9945
9960
|
return;
|
|
9946
9961
|
}
|
|
9947
|
-
const
|
|
9948
|
-
|
|
9962
|
+
const onOpen = ({ name }) => {
|
|
9963
|
+
if (name === channelName) {
|
|
9964
|
+
pc.router.off("channelOpen", onOpen);
|
|
9965
|
+
const data = new TextEncoder().encode(payload);
|
|
9966
|
+
pc.router.send(channelName, data).catch(() => {});
|
|
9967
|
+
}
|
|
9968
|
+
};
|
|
9969
|
+
pc.router.on("channelOpen", onOpen);
|
|
9949
9970
|
}
|
|
9950
9971
|
/**
|
|
9951
9972
|
* Send a custom string message to all connected peers.
|
|
@@ -10101,6 +10122,7 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
|
|
|
10101
10122
|
enableAwareness: this.config.enableAwarenessSync,
|
|
10102
10123
|
enableFileTransfer: this.config.enableFileTransfer
|
|
10103
10124
|
});
|
|
10125
|
+
pc.router.createChannel("custom", { ordered: true });
|
|
10104
10126
|
try {
|
|
10105
10127
|
const sdp = await pc.createOffer();
|
|
10106
10128
|
this.signaling?.sendOffer(peerId, sdp);
|
|
@@ -10897,6 +10919,9 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
10897
10919
|
get pluginsMap() {
|
|
10898
10920
|
return this.document.getMap("plugins");
|
|
10899
10921
|
}
|
|
10922
|
+
get devicesMap() {
|
|
10923
|
+
return this.document.getMap("devices");
|
|
10924
|
+
}
|
|
10900
10925
|
get preferencesMap() {
|
|
10901
10926
|
return this.document.getMap("preferences");
|
|
10902
10927
|
}
|
|
@@ -11024,6 +11049,97 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
11024
11049
|
setPreference(key, value) {
|
|
11025
11050
|
this.preferencesMap.set(key, value);
|
|
11026
11051
|
}
|
|
11052
|
+
getDevice(publicKey) {
|
|
11053
|
+
const entry = this.devicesMap.get(publicKey);
|
|
11054
|
+
if (!entry) return void 0;
|
|
11055
|
+
const servers = /* @__PURE__ */ new Map();
|
|
11056
|
+
const serversYMap = entry.get("servers");
|
|
11057
|
+
if (serversYMap) serversYMap.forEach((sEntry, url) => {
|
|
11058
|
+
servers.set(url, {
|
|
11059
|
+
registered: sEntry.get("registered") ?? false,
|
|
11060
|
+
registeredAt: sEntry.get("registeredAt") ?? null,
|
|
11061
|
+
keyId: sEntry.get("keyId") ?? null,
|
|
11062
|
+
lastVerifiedAt: sEntry.get("lastVerifiedAt") ?? null,
|
|
11063
|
+
error: sEntry.get("error") ?? null
|
|
11064
|
+
});
|
|
11065
|
+
});
|
|
11066
|
+
return {
|
|
11067
|
+
deviceName: entry.get("deviceName") ?? "Unknown",
|
|
11068
|
+
tier: entry.get("tier") ?? "paired",
|
|
11069
|
+
x25519Key: entry.get("x25519Key") ?? null,
|
|
11070
|
+
addedAt: entry.get("addedAt") ?? 0,
|
|
11071
|
+
revokedAt: entry.get("revokedAt") ?? null,
|
|
11072
|
+
revokedBy: entry.get("revokedBy") ?? null,
|
|
11073
|
+
servers
|
|
11074
|
+
};
|
|
11075
|
+
}
|
|
11076
|
+
getDevices() {
|
|
11077
|
+
const result = /* @__PURE__ */ new Map();
|
|
11078
|
+
this.devicesMap.forEach((_entry, pubKey) => {
|
|
11079
|
+
const device = this.getDevice(pubKey);
|
|
11080
|
+
if (device) result.set(pubKey, device);
|
|
11081
|
+
});
|
|
11082
|
+
return result;
|
|
11083
|
+
}
|
|
11084
|
+
addDevice(publicKey, opts) {
|
|
11085
|
+
this.document.transact(() => {
|
|
11086
|
+
let entry = this.devicesMap.get(publicKey);
|
|
11087
|
+
if (!entry) {
|
|
11088
|
+
entry = new Y.Map();
|
|
11089
|
+
this.devicesMap.set(publicKey, entry);
|
|
11090
|
+
}
|
|
11091
|
+
entry.set("deviceName", opts.deviceName);
|
|
11092
|
+
entry.set("tier", opts.tier);
|
|
11093
|
+
if (opts.x25519Key) entry.set("x25519Key", opts.x25519Key);
|
|
11094
|
+
entry.set("addedAt", Date.now());
|
|
11095
|
+
entry.set("revokedAt", null);
|
|
11096
|
+
entry.set("revokedBy", null);
|
|
11097
|
+
if (opts.serverUrl) {
|
|
11098
|
+
let servers = entry.get("servers");
|
|
11099
|
+
if (!servers) {
|
|
11100
|
+
servers = new Y.Map();
|
|
11101
|
+
entry.set("servers", servers);
|
|
11102
|
+
}
|
|
11103
|
+
const sEntry = new Y.Map();
|
|
11104
|
+
sEntry.set("registered", true);
|
|
11105
|
+
sEntry.set("registeredAt", Date.now());
|
|
11106
|
+
sEntry.set("keyId", opts.keyId ?? null);
|
|
11107
|
+
sEntry.set("lastVerifiedAt", Date.now());
|
|
11108
|
+
sEntry.set("error", null);
|
|
11109
|
+
servers.set(opts.serverUrl, sEntry);
|
|
11110
|
+
}
|
|
11111
|
+
});
|
|
11112
|
+
}
|
|
11113
|
+
revokeDevice(publicKey, revokedBy) {
|
|
11114
|
+
const entry = this.devicesMap.get(publicKey);
|
|
11115
|
+
if (!entry) return;
|
|
11116
|
+
this.document.transact(() => {
|
|
11117
|
+
entry.set("revokedAt", Date.now());
|
|
11118
|
+
entry.set("revokedBy", revokedBy);
|
|
11119
|
+
});
|
|
11120
|
+
}
|
|
11121
|
+
setDeviceServerStatus(devicePubKey, serverUrl, status) {
|
|
11122
|
+
const entry = this.devicesMap.get(devicePubKey);
|
|
11123
|
+
if (!entry) return;
|
|
11124
|
+
this.document.transact(() => {
|
|
11125
|
+
let servers = entry.get("servers");
|
|
11126
|
+
if (!servers) {
|
|
11127
|
+
servers = new Y.Map();
|
|
11128
|
+
entry.set("servers", servers);
|
|
11129
|
+
}
|
|
11130
|
+
let sEntry = servers.get(serverUrl);
|
|
11131
|
+
if (!sEntry) {
|
|
11132
|
+
sEntry = new Y.Map();
|
|
11133
|
+
sEntry.set("registered", false);
|
|
11134
|
+
sEntry.set("registeredAt", null);
|
|
11135
|
+
sEntry.set("keyId", null);
|
|
11136
|
+
sEntry.set("lastVerifiedAt", null);
|
|
11137
|
+
sEntry.set("error", null);
|
|
11138
|
+
servers.set(serverUrl, sEntry);
|
|
11139
|
+
}
|
|
11140
|
+
for (const [key, value] of Object.entries(status)) if (value !== void 0) sEntry.set(key, value);
|
|
11141
|
+
});
|
|
11142
|
+
}
|
|
11027
11143
|
/**
|
|
11028
11144
|
* Observe deep changes on a specific top-level map.
|
|
11029
11145
|
* Returns an unsubscribe function.
|
|
@@ -11089,5 +11205,153 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
11089
11205
|
};
|
|
11090
11206
|
|
|
11091
11207
|
//#endregion
|
|
11092
|
-
|
|
11208
|
+
//#region packages/provider/src/DeviceRegistrationService.ts
|
|
11209
|
+
/**
|
|
11210
|
+
* Handles cross-server device key registration and revocation propagation.
|
|
11211
|
+
*
|
|
11212
|
+
* All methods operate on the identity Y.Doc's `devicesMap` and authenticate
|
|
11213
|
+
* independently on each server using the master key's `signChallenge`.
|
|
11214
|
+
*/
|
|
11215
|
+
var DeviceRegistrationService = class {
|
|
11216
|
+
/**
|
|
11217
|
+
* Fan out a device key to all servers in `serversMap` where it isn't registered.
|
|
11218
|
+
*
|
|
11219
|
+
* Must be called from a master device that can sign challenges with the
|
|
11220
|
+
* account-level private key.
|
|
11221
|
+
*/
|
|
11222
|
+
static async fanOutDeviceKey(opts) {
|
|
11223
|
+
const { devicePubKey, x25519Key, deviceName, identityDoc, masterPubKey, signChallenge, skipServerUrl } = opts;
|
|
11224
|
+
const servers = identityDoc.getServers();
|
|
11225
|
+
const results = [];
|
|
11226
|
+
if (!identityDoc.getDevice(devicePubKey)) identityDoc.addDevice(devicePubKey, {
|
|
11227
|
+
deviceName: deviceName ?? "Unknown",
|
|
11228
|
+
tier: "paired",
|
|
11229
|
+
x25519Key
|
|
11230
|
+
});
|
|
11231
|
+
for (const [serverUrl] of servers) {
|
|
11232
|
+
if (skipServerUrl && serverUrl === skipServerUrl) continue;
|
|
11233
|
+
if ((identityDoc.getDevice(devicePubKey)?.servers.get(serverUrl))?.registered) continue;
|
|
11234
|
+
try {
|
|
11235
|
+
const client = new AbracadabraClient({ url: serverUrl });
|
|
11236
|
+
await client.loginWithKey(masterPubKey, signChallenge);
|
|
11237
|
+
await client.addKey({
|
|
11238
|
+
publicKey: devicePubKey,
|
|
11239
|
+
x25519Key,
|
|
11240
|
+
deviceName
|
|
11241
|
+
});
|
|
11242
|
+
const match = (await client.listKeys()).find((k) => k.publicKey === devicePubKey && !k.revoked);
|
|
11243
|
+
identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, {
|
|
11244
|
+
registered: true,
|
|
11245
|
+
registeredAt: Date.now(),
|
|
11246
|
+
keyId: match?.id ?? null,
|
|
11247
|
+
lastVerifiedAt: Date.now(),
|
|
11248
|
+
error: null
|
|
11249
|
+
});
|
|
11250
|
+
results.push({
|
|
11251
|
+
serverUrl,
|
|
11252
|
+
success: true
|
|
11253
|
+
});
|
|
11254
|
+
} catch (e) {
|
|
11255
|
+
identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, { error: e?.message ?? "Registration failed" });
|
|
11256
|
+
results.push({
|
|
11257
|
+
serverUrl,
|
|
11258
|
+
success: false,
|
|
11259
|
+
error: e?.message ?? "Registration failed"
|
|
11260
|
+
});
|
|
11261
|
+
}
|
|
11262
|
+
}
|
|
11263
|
+
return results;
|
|
11264
|
+
}
|
|
11265
|
+
/**
|
|
11266
|
+
* Propagate pending revocations from the identity Y.Doc to all servers.
|
|
11267
|
+
*
|
|
11268
|
+
* Scans `devicesMap` for entries with `revokedAt` set and revokes them
|
|
11269
|
+
* on every server where they're still registered. Must be called from
|
|
11270
|
+
* a master device.
|
|
11271
|
+
*/
|
|
11272
|
+
static async propagateRevocations(opts) {
|
|
11273
|
+
const { identityDoc, masterPubKey, signChallenge } = opts;
|
|
11274
|
+
const devices = identityDoc.getDevices();
|
|
11275
|
+
const results = [];
|
|
11276
|
+
for (const [devicePubKey, device] of devices) {
|
|
11277
|
+
if (!device.revokedAt) continue;
|
|
11278
|
+
for (const [serverUrl, serverStatus] of device.servers) {
|
|
11279
|
+
if (!serverStatus.registered || !serverStatus.keyId) continue;
|
|
11280
|
+
try {
|
|
11281
|
+
const client = new AbracadabraClient({ url: serverUrl });
|
|
11282
|
+
await client.loginWithKey(masterPubKey, signChallenge);
|
|
11283
|
+
await client.revokeKey(serverStatus.keyId);
|
|
11284
|
+
identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, {
|
|
11285
|
+
registered: false,
|
|
11286
|
+
error: null
|
|
11287
|
+
});
|
|
11288
|
+
results.push({
|
|
11289
|
+
serverUrl,
|
|
11290
|
+
success: true
|
|
11291
|
+
});
|
|
11292
|
+
} catch (e) {
|
|
11293
|
+
results.push({
|
|
11294
|
+
serverUrl,
|
|
11295
|
+
success: false,
|
|
11296
|
+
error: e?.message ?? "Revocation failed"
|
|
11297
|
+
});
|
|
11298
|
+
}
|
|
11299
|
+
}
|
|
11300
|
+
}
|
|
11301
|
+
return results;
|
|
11302
|
+
}
|
|
11303
|
+
/**
|
|
11304
|
+
* Reconcile the identity Y.Doc's `devicesMap` with a server's actual
|
|
11305
|
+
* device key list. Call this on every server connection.
|
|
11306
|
+
*
|
|
11307
|
+
* - Updates registration status in Y.Doc to match server reality
|
|
11308
|
+
* - Imports unknown server keys as legacy entries
|
|
11309
|
+
* - Executes pending revocations if the caller is a master device
|
|
11310
|
+
*/
|
|
11311
|
+
static async reconcile(opts) {
|
|
11312
|
+
const { identityDoc, serverUrl, client, isMaster } = opts;
|
|
11313
|
+
let serverKeys;
|
|
11314
|
+
try {
|
|
11315
|
+
serverKeys = await client.listKeys();
|
|
11316
|
+
} catch {
|
|
11317
|
+
return;
|
|
11318
|
+
}
|
|
11319
|
+
const devicesMap = identityDoc.devicesMap;
|
|
11320
|
+
devicesMap.forEach((yEntry, devicePub) => {
|
|
11321
|
+
const serverEntry = yEntry.get("servers")?.get(serverUrl);
|
|
11322
|
+
const serverKey = serverKeys.find((k) => k.publicKey === devicePub);
|
|
11323
|
+
const revokedAt = yEntry.get("revokedAt");
|
|
11324
|
+
if (serverEntry?.get("registered") && !serverKey) identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
|
|
11325
|
+
registered: false,
|
|
11326
|
+
keyId: null
|
|
11327
|
+
});
|
|
11328
|
+
if (serverKey && !serverKey.revoked) {
|
|
11329
|
+
if (!serverEntry?.get("registered")) identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
|
|
11330
|
+
registered: true,
|
|
11331
|
+
keyId: serverKey.id,
|
|
11332
|
+
lastVerifiedAt: Date.now()
|
|
11333
|
+
});
|
|
11334
|
+
else identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
|
|
11335
|
+
lastVerifiedAt: Date.now(),
|
|
11336
|
+
keyId: serverKey.id
|
|
11337
|
+
});
|
|
11338
|
+
if (revokedAt && isMaster) client.revokeKey(serverKey.id).then(() => {
|
|
11339
|
+
identityDoc.setDeviceServerStatus(devicePub, serverUrl, { registered: false });
|
|
11340
|
+
}).catch(() => {});
|
|
11341
|
+
}
|
|
11342
|
+
});
|
|
11343
|
+
for (const key of serverKeys) {
|
|
11344
|
+
if (key.revoked) continue;
|
|
11345
|
+
if (!devicesMap.has(key.publicKey)) identityDoc.addDevice(key.publicKey, {
|
|
11346
|
+
deviceName: key.deviceName ?? "Unknown",
|
|
11347
|
+
tier: "paired",
|
|
11348
|
+
serverUrl,
|
|
11349
|
+
keyId: key.id
|
|
11350
|
+
});
|
|
11351
|
+
}
|
|
11352
|
+
}
|
|
11353
|
+
};
|
|
11354
|
+
|
|
11355
|
+
//#endregion
|
|
11356
|
+
export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ConnectionTimeout, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, DeviceRegistrationService, DocKeyManager, DocumentCache, E2EAbracadabraProvider, E2EEChannel, E2EOfflineStore, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, Forbidden, HocuspocusProvider, HocuspocusProviderWebsocket, IdentityDocProvider, KEY_EXCHANGE_CHANNEL, ManualSignaling, MessageTooBig, MessageType, OfflineStore, PeerConnection, ResetConnection, SearchIndex, SignalingSocket, SubdocMessage, Unauthorized, WebSocketStatus, WsReadyStates, YjsDataChannel, attachUpdatedAtObserver, awarenessStatesToArray, decryptField, deriveIdentityDocId, encryptField, makeEncryptedYMap, makeEncryptedYText, readAuthMessage, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
|
|
11093
11357
|
//# sourceMappingURL=abracadabra-provider.esm.js.map
|