@abraca/dabra 1.0.23 → 1.0.25

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.
@@ -9935,17 +9935,20 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
9935
9935
  const pc = this.peerConnections.get(peerId);
9936
9936
  if (!pc) return;
9937
9937
  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);
9944
- };
9938
+ const channel = pc.router.getChannel(channelName);
9939
+ if (channel && channel.readyState === "open") {
9940
+ const data = new TextEncoder().encode(payload);
9941
+ pc.router.send(channelName, data).catch(() => {});
9945
9942
  return;
9946
9943
  }
9947
- const data = new TextEncoder().encode(payload);
9948
- pc.router.send(channelName, data);
9944
+ const onOpen = ({ name }) => {
9945
+ if (name === channelName) {
9946
+ pc.router.off("channelOpen", onOpen);
9947
+ const data = new TextEncoder().encode(payload);
9948
+ pc.router.send(channelName, data).catch(() => {});
9949
+ }
9950
+ };
9951
+ pc.router.on("channelOpen", onOpen);
9949
9952
  }
9950
9953
  /**
9951
9954
  * Send a custom string message to all connected peers.
@@ -10026,9 +10029,14 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
10026
10029
  this.e2eeChannels.set(peerId, e2ee);
10027
10030
  pc.router.setEncryptor(e2ee);
10028
10031
  pc.router.on("channelMessage", async ({ name, data }) => {
10029
- if (name === KEY_EXCHANGE_CHANNEL) {
10032
+ if (name === KEY_EXCHANGE_CHANNEL) try {
10030
10033
  const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
10031
10034
  await e2ee.handleKeyExchange(buf);
10035
+ } catch (err) {
10036
+ this.emit("e2eeFailed", {
10037
+ peerId,
10038
+ error: err
10039
+ });
10032
10040
  }
10033
10041
  });
10034
10042
  pc.router.on("channelOpen", ({ name, channel }) => {
@@ -10096,10 +10104,16 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
10096
10104
  enableAwareness: this.config.enableAwarenessSync,
10097
10105
  enableFileTransfer: this.config.enableFileTransfer
10098
10106
  });
10107
+ pc.router.createChannel("custom", { ordered: true });
10099
10108
  try {
10100
10109
  const sdp = await pc.createOffer();
10101
10110
  this.signaling?.sendOffer(peerId, sdp);
10102
- } catch {
10111
+ } catch (err) {
10112
+ this.emit("error", {
10113
+ type: "offer-failed",
10114
+ peerId,
10115
+ error: err
10116
+ });
10103
10117
  this.removePeer(peerId);
10104
10118
  }
10105
10119
  }
@@ -10117,7 +10131,12 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
10117
10131
  try {
10118
10132
  const answerSdp = await pc.setRemoteOffer(sdp);
10119
10133
  this.signaling?.sendAnswer(from, answerSdp);
10120
- } catch {
10134
+ } catch (err) {
10135
+ this.emit("error", {
10136
+ type: "answer-failed",
10137
+ peerId: from,
10138
+ error: err
10139
+ });
10121
10140
  this.removePeer(from);
10122
10141
  }
10123
10142
  }
@@ -10535,6 +10554,12 @@ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
10535
10554
  this.webrtc.on("customMessage", ({ peerId, payload }) => {
10536
10555
  this.handleMessage(peerId, payload);
10537
10556
  });
10557
+ this.webrtc.on("e2eeFailed", ({ peerId, error }) => {
10558
+ if (!this._destroyed) this.emit("error", /* @__PURE__ */ new Error(`E2EE failed: ${error?.message ?? error}`));
10559
+ });
10560
+ this.webrtc.on("error", (err) => {
10561
+ if (!this._destroyed) this.emit("error", /* @__PURE__ */ new Error(`WebRTC: ${err?.type ?? err?.message ?? "unknown"}`));
10562
+ });
10538
10563
  this.webrtc.on("peerLeft", () => {
10539
10564
  if (!this._destroyed) this.emit("error", /* @__PURE__ */ new Error("Peer disconnected"));
10540
10565
  });
@@ -10876,6 +10901,9 @@ var IdentityDocProvider = class extends EventEmitter {
10876
10901
  get pluginsMap() {
10877
10902
  return this.document.getMap("plugins");
10878
10903
  }
10904
+ get devicesMap() {
10905
+ return this.document.getMap("devices");
10906
+ }
10879
10907
  get preferencesMap() {
10880
10908
  return this.document.getMap("preferences");
10881
10909
  }
@@ -11003,6 +11031,97 @@ var IdentityDocProvider = class extends EventEmitter {
11003
11031
  setPreference(key, value) {
11004
11032
  this.preferencesMap.set(key, value);
11005
11033
  }
11034
+ getDevice(publicKey) {
11035
+ const entry = this.devicesMap.get(publicKey);
11036
+ if (!entry) return void 0;
11037
+ const servers = /* @__PURE__ */ new Map();
11038
+ const serversYMap = entry.get("servers");
11039
+ if (serversYMap) serversYMap.forEach((sEntry, url) => {
11040
+ servers.set(url, {
11041
+ registered: sEntry.get("registered") ?? false,
11042
+ registeredAt: sEntry.get("registeredAt") ?? null,
11043
+ keyId: sEntry.get("keyId") ?? null,
11044
+ lastVerifiedAt: sEntry.get("lastVerifiedAt") ?? null,
11045
+ error: sEntry.get("error") ?? null
11046
+ });
11047
+ });
11048
+ return {
11049
+ deviceName: entry.get("deviceName") ?? "Unknown",
11050
+ tier: entry.get("tier") ?? "paired",
11051
+ x25519Key: entry.get("x25519Key") ?? null,
11052
+ addedAt: entry.get("addedAt") ?? 0,
11053
+ revokedAt: entry.get("revokedAt") ?? null,
11054
+ revokedBy: entry.get("revokedBy") ?? null,
11055
+ servers
11056
+ };
11057
+ }
11058
+ getDevices() {
11059
+ const result = /* @__PURE__ */ new Map();
11060
+ this.devicesMap.forEach((_entry, pubKey) => {
11061
+ const device = this.getDevice(pubKey);
11062
+ if (device) result.set(pubKey, device);
11063
+ });
11064
+ return result;
11065
+ }
11066
+ addDevice(publicKey, opts) {
11067
+ this.document.transact(() => {
11068
+ let entry = this.devicesMap.get(publicKey);
11069
+ if (!entry) {
11070
+ entry = new Y.Map();
11071
+ this.devicesMap.set(publicKey, entry);
11072
+ }
11073
+ entry.set("deviceName", opts.deviceName);
11074
+ entry.set("tier", opts.tier);
11075
+ if (opts.x25519Key) entry.set("x25519Key", opts.x25519Key);
11076
+ entry.set("addedAt", Date.now());
11077
+ entry.set("revokedAt", null);
11078
+ entry.set("revokedBy", null);
11079
+ if (opts.serverUrl) {
11080
+ let servers = entry.get("servers");
11081
+ if (!servers) {
11082
+ servers = new Y.Map();
11083
+ entry.set("servers", servers);
11084
+ }
11085
+ const sEntry = new Y.Map();
11086
+ sEntry.set("registered", true);
11087
+ sEntry.set("registeredAt", Date.now());
11088
+ sEntry.set("keyId", opts.keyId ?? null);
11089
+ sEntry.set("lastVerifiedAt", Date.now());
11090
+ sEntry.set("error", null);
11091
+ servers.set(opts.serverUrl, sEntry);
11092
+ }
11093
+ });
11094
+ }
11095
+ revokeDevice(publicKey, revokedBy) {
11096
+ const entry = this.devicesMap.get(publicKey);
11097
+ if (!entry) return;
11098
+ this.document.transact(() => {
11099
+ entry.set("revokedAt", Date.now());
11100
+ entry.set("revokedBy", revokedBy);
11101
+ });
11102
+ }
11103
+ setDeviceServerStatus(devicePubKey, serverUrl, status) {
11104
+ const entry = this.devicesMap.get(devicePubKey);
11105
+ if (!entry) return;
11106
+ this.document.transact(() => {
11107
+ let servers = entry.get("servers");
11108
+ if (!servers) {
11109
+ servers = new Y.Map();
11110
+ entry.set("servers", servers);
11111
+ }
11112
+ let sEntry = servers.get(serverUrl);
11113
+ if (!sEntry) {
11114
+ sEntry = new Y.Map();
11115
+ sEntry.set("registered", false);
11116
+ sEntry.set("registeredAt", null);
11117
+ sEntry.set("keyId", null);
11118
+ sEntry.set("lastVerifiedAt", null);
11119
+ sEntry.set("error", null);
11120
+ servers.set(serverUrl, sEntry);
11121
+ }
11122
+ for (const [key, value] of Object.entries(status)) if (value !== void 0) sEntry.set(key, value);
11123
+ });
11124
+ }
11006
11125
  /**
11007
11126
  * Observe deep changes on a specific top-level map.
11008
11127
  * Returns an unsubscribe function.
@@ -11068,5 +11187,153 @@ var IdentityDocProvider = class extends EventEmitter {
11068
11187
  };
11069
11188
 
11070
11189
  //#endregion
11071
- 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 };
11190
+ //#region packages/provider/src/DeviceRegistrationService.ts
11191
+ /**
11192
+ * Handles cross-server device key registration and revocation propagation.
11193
+ *
11194
+ * All methods operate on the identity Y.Doc's `devicesMap` and authenticate
11195
+ * independently on each server using the master key's `signChallenge`.
11196
+ */
11197
+ var DeviceRegistrationService = class {
11198
+ /**
11199
+ * Fan out a device key to all servers in `serversMap` where it isn't registered.
11200
+ *
11201
+ * Must be called from a master device that can sign challenges with the
11202
+ * account-level private key.
11203
+ */
11204
+ static async fanOutDeviceKey(opts) {
11205
+ const { devicePubKey, x25519Key, deviceName, identityDoc, masterPubKey, signChallenge, skipServerUrl } = opts;
11206
+ const servers = identityDoc.getServers();
11207
+ const results = [];
11208
+ if (!identityDoc.getDevice(devicePubKey)) identityDoc.addDevice(devicePubKey, {
11209
+ deviceName: deviceName ?? "Unknown",
11210
+ tier: "paired",
11211
+ x25519Key
11212
+ });
11213
+ for (const [serverUrl] of servers) {
11214
+ if (skipServerUrl && serverUrl === skipServerUrl) continue;
11215
+ if ((identityDoc.getDevice(devicePubKey)?.servers.get(serverUrl))?.registered) continue;
11216
+ try {
11217
+ const client = new AbracadabraClient({ url: serverUrl });
11218
+ await client.loginWithKey(masterPubKey, signChallenge);
11219
+ await client.addKey({
11220
+ publicKey: devicePubKey,
11221
+ x25519Key,
11222
+ deviceName
11223
+ });
11224
+ const match = (await client.listKeys()).find((k) => k.publicKey === devicePubKey && !k.revoked);
11225
+ identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, {
11226
+ registered: true,
11227
+ registeredAt: Date.now(),
11228
+ keyId: match?.id ?? null,
11229
+ lastVerifiedAt: Date.now(),
11230
+ error: null
11231
+ });
11232
+ results.push({
11233
+ serverUrl,
11234
+ success: true
11235
+ });
11236
+ } catch (e) {
11237
+ identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, { error: e?.message ?? "Registration failed" });
11238
+ results.push({
11239
+ serverUrl,
11240
+ success: false,
11241
+ error: e?.message ?? "Registration failed"
11242
+ });
11243
+ }
11244
+ }
11245
+ return results;
11246
+ }
11247
+ /**
11248
+ * Propagate pending revocations from the identity Y.Doc to all servers.
11249
+ *
11250
+ * Scans `devicesMap` for entries with `revokedAt` set and revokes them
11251
+ * on every server where they're still registered. Must be called from
11252
+ * a master device.
11253
+ */
11254
+ static async propagateRevocations(opts) {
11255
+ const { identityDoc, masterPubKey, signChallenge } = opts;
11256
+ const devices = identityDoc.getDevices();
11257
+ const results = [];
11258
+ for (const [devicePubKey, device] of devices) {
11259
+ if (!device.revokedAt) continue;
11260
+ for (const [serverUrl, serverStatus] of device.servers) {
11261
+ if (!serverStatus.registered || !serverStatus.keyId) continue;
11262
+ try {
11263
+ const client = new AbracadabraClient({ url: serverUrl });
11264
+ await client.loginWithKey(masterPubKey, signChallenge);
11265
+ await client.revokeKey(serverStatus.keyId);
11266
+ identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, {
11267
+ registered: false,
11268
+ error: null
11269
+ });
11270
+ results.push({
11271
+ serverUrl,
11272
+ success: true
11273
+ });
11274
+ } catch (e) {
11275
+ results.push({
11276
+ serverUrl,
11277
+ success: false,
11278
+ error: e?.message ?? "Revocation failed"
11279
+ });
11280
+ }
11281
+ }
11282
+ }
11283
+ return results;
11284
+ }
11285
+ /**
11286
+ * Reconcile the identity Y.Doc's `devicesMap` with a server's actual
11287
+ * device key list. Call this on every server connection.
11288
+ *
11289
+ * - Updates registration status in Y.Doc to match server reality
11290
+ * - Imports unknown server keys as legacy entries
11291
+ * - Executes pending revocations if the caller is a master device
11292
+ */
11293
+ static async reconcile(opts) {
11294
+ const { identityDoc, serverUrl, client, isMaster } = opts;
11295
+ let serverKeys;
11296
+ try {
11297
+ serverKeys = await client.listKeys();
11298
+ } catch {
11299
+ return;
11300
+ }
11301
+ const devicesMap = identityDoc.devicesMap;
11302
+ devicesMap.forEach((yEntry, devicePub) => {
11303
+ const serverEntry = yEntry.get("servers")?.get(serverUrl);
11304
+ const serverKey = serverKeys.find((k) => k.publicKey === devicePub);
11305
+ const revokedAt = yEntry.get("revokedAt");
11306
+ if (serverEntry?.get("registered") && !serverKey) identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
11307
+ registered: false,
11308
+ keyId: null
11309
+ });
11310
+ if (serverKey && !serverKey.revoked) {
11311
+ if (!serverEntry?.get("registered")) identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
11312
+ registered: true,
11313
+ keyId: serverKey.id,
11314
+ lastVerifiedAt: Date.now()
11315
+ });
11316
+ else identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
11317
+ lastVerifiedAt: Date.now(),
11318
+ keyId: serverKey.id
11319
+ });
11320
+ if (revokedAt && isMaster) client.revokeKey(serverKey.id).then(() => {
11321
+ identityDoc.setDeviceServerStatus(devicePub, serverUrl, { registered: false });
11322
+ }).catch(() => {});
11323
+ }
11324
+ });
11325
+ for (const key of serverKeys) {
11326
+ if (key.revoked) continue;
11327
+ if (!devicesMap.has(key.publicKey)) identityDoc.addDevice(key.publicKey, {
11328
+ deviceName: key.deviceName ?? "Unknown",
11329
+ tier: "paired",
11330
+ serverUrl,
11331
+ keyId: key.id
11332
+ });
11333
+ }
11334
+ }
11335
+ };
11336
+
11337
+ //#endregion
11338
+ 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 };
11072
11339
  //# sourceMappingURL=abracadabra-provider.esm.js.map