@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.
@@ -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
- let channel = pc.router.getChannel(channelName);
9939
- if (!channel || channel.readyState !== "open") {
9940
- channel = pc.router.createChannel(channelName, { ordered: true });
9941
- channel.onopen = () => {
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 data = new TextEncoder().encode(payload);
9948
- pc.router.send(channelName, data);
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
- export { AbracadabraBaseProvider, AbracadabraClient, AbracadabraProvider, AbracadabraWS, AbracadabraWebRTC, AuthMessageType, AwarenessError, BackgroundSyncManager, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, ConnectionTimeout, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, DevicePairingChannel, 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 };
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