@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.
@@ -9987,17 +9987,20 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
9987
9987
  const pc = this.peerConnections.get(peerId);
9988
9988
  if (!pc) return;
9989
9989
  const channelName = "custom";
9990
- let channel = pc.router.getChannel(channelName);
9991
- if (!channel || channel.readyState !== "open") {
9992
- channel = pc.router.createChannel(channelName, { ordered: true });
9993
- channel.onopen = () => {
9994
- const data = new TextEncoder().encode(payload);
9995
- pc.router.send(channelName, data);
9996
- };
9990
+ const channel = pc.router.getChannel(channelName);
9991
+ if (channel && channel.readyState === "open") {
9992
+ const data = new TextEncoder().encode(payload);
9993
+ pc.router.send(channelName, data).catch(() => {});
9997
9994
  return;
9998
9995
  }
9999
- const data = new TextEncoder().encode(payload);
10000
- pc.router.send(channelName, data);
9996
+ const onOpen = ({ name }) => {
9997
+ if (name === channelName) {
9998
+ pc.router.off("channelOpen", onOpen);
9999
+ const data = new TextEncoder().encode(payload);
10000
+ pc.router.send(channelName, data).catch(() => {});
10001
+ }
10002
+ };
10003
+ pc.router.on("channelOpen", onOpen);
10001
10004
  }
10002
10005
  /**
10003
10006
  * Send a custom string message to all connected peers.
@@ -10078,9 +10081,14 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
10078
10081
  this.e2eeChannels.set(peerId, e2ee);
10079
10082
  pc.router.setEncryptor(e2ee);
10080
10083
  pc.router.on("channelMessage", async ({ name, data }) => {
10081
- if (name === KEY_EXCHANGE_CHANNEL) {
10084
+ if (name === KEY_EXCHANGE_CHANNEL) try {
10082
10085
  const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
10083
10086
  await e2ee.handleKeyExchange(buf);
10087
+ } catch (err) {
10088
+ this.emit("e2eeFailed", {
10089
+ peerId,
10090
+ error: err
10091
+ });
10084
10092
  }
10085
10093
  });
10086
10094
  pc.router.on("channelOpen", ({ name, channel }) => {
@@ -10148,10 +10156,16 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
10148
10156
  enableAwareness: this.config.enableAwarenessSync,
10149
10157
  enableFileTransfer: this.config.enableFileTransfer
10150
10158
  });
10159
+ pc.router.createChannel("custom", { ordered: true });
10151
10160
  try {
10152
10161
  const sdp = await pc.createOffer();
10153
10162
  this.signaling?.sendOffer(peerId, sdp);
10154
- } catch {
10163
+ } catch (err) {
10164
+ this.emit("error", {
10165
+ type: "offer-failed",
10166
+ peerId,
10167
+ error: err
10168
+ });
10155
10169
  this.removePeer(peerId);
10156
10170
  }
10157
10171
  }
@@ -10169,7 +10183,12 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
10169
10183
  try {
10170
10184
  const answerSdp = await pc.setRemoteOffer(sdp);
10171
10185
  this.signaling?.sendAnswer(from, answerSdp);
10172
- } catch {
10186
+ } catch (err) {
10187
+ this.emit("error", {
10188
+ type: "answer-failed",
10189
+ peerId: from,
10190
+ error: err
10191
+ });
10173
10192
  this.removePeer(from);
10174
10193
  }
10175
10194
  }
@@ -10587,6 +10606,12 @@ var DevicePairingChannel = class DevicePairingChannel extends EventEmitter {
10587
10606
  this.webrtc.on("customMessage", ({ peerId, payload }) => {
10588
10607
  this.handleMessage(peerId, payload);
10589
10608
  });
10609
+ this.webrtc.on("e2eeFailed", ({ peerId, error }) => {
10610
+ if (!this._destroyed) this.emit("error", /* @__PURE__ */ new Error(`E2EE failed: ${error?.message ?? error}`));
10611
+ });
10612
+ this.webrtc.on("error", (err) => {
10613
+ if (!this._destroyed) this.emit("error", /* @__PURE__ */ new Error(`WebRTC: ${err?.type ?? err?.message ?? "unknown"}`));
10614
+ });
10590
10615
  this.webrtc.on("peerLeft", () => {
10591
10616
  if (!this._destroyed) this.emit("error", /* @__PURE__ */ new Error("Peer disconnected"));
10592
10617
  });
@@ -10928,6 +10953,9 @@ var IdentityDocProvider = class extends EventEmitter {
10928
10953
  get pluginsMap() {
10929
10954
  return this.document.getMap("plugins");
10930
10955
  }
10956
+ get devicesMap() {
10957
+ return this.document.getMap("devices");
10958
+ }
10931
10959
  get preferencesMap() {
10932
10960
  return this.document.getMap("preferences");
10933
10961
  }
@@ -11055,6 +11083,97 @@ var IdentityDocProvider = class extends EventEmitter {
11055
11083
  setPreference(key, value) {
11056
11084
  this.preferencesMap.set(key, value);
11057
11085
  }
11086
+ getDevice(publicKey) {
11087
+ const entry = this.devicesMap.get(publicKey);
11088
+ if (!entry) return void 0;
11089
+ const servers = /* @__PURE__ */ new Map();
11090
+ const serversYMap = entry.get("servers");
11091
+ if (serversYMap) serversYMap.forEach((sEntry, url) => {
11092
+ servers.set(url, {
11093
+ registered: sEntry.get("registered") ?? false,
11094
+ registeredAt: sEntry.get("registeredAt") ?? null,
11095
+ keyId: sEntry.get("keyId") ?? null,
11096
+ lastVerifiedAt: sEntry.get("lastVerifiedAt") ?? null,
11097
+ error: sEntry.get("error") ?? null
11098
+ });
11099
+ });
11100
+ return {
11101
+ deviceName: entry.get("deviceName") ?? "Unknown",
11102
+ tier: entry.get("tier") ?? "paired",
11103
+ x25519Key: entry.get("x25519Key") ?? null,
11104
+ addedAt: entry.get("addedAt") ?? 0,
11105
+ revokedAt: entry.get("revokedAt") ?? null,
11106
+ revokedBy: entry.get("revokedBy") ?? null,
11107
+ servers
11108
+ };
11109
+ }
11110
+ getDevices() {
11111
+ const result = /* @__PURE__ */ new Map();
11112
+ this.devicesMap.forEach((_entry, pubKey) => {
11113
+ const device = this.getDevice(pubKey);
11114
+ if (device) result.set(pubKey, device);
11115
+ });
11116
+ return result;
11117
+ }
11118
+ addDevice(publicKey, opts) {
11119
+ this.document.transact(() => {
11120
+ let entry = this.devicesMap.get(publicKey);
11121
+ if (!entry) {
11122
+ entry = new yjs.Map();
11123
+ this.devicesMap.set(publicKey, entry);
11124
+ }
11125
+ entry.set("deviceName", opts.deviceName);
11126
+ entry.set("tier", opts.tier);
11127
+ if (opts.x25519Key) entry.set("x25519Key", opts.x25519Key);
11128
+ entry.set("addedAt", Date.now());
11129
+ entry.set("revokedAt", null);
11130
+ entry.set("revokedBy", null);
11131
+ if (opts.serverUrl) {
11132
+ let servers = entry.get("servers");
11133
+ if (!servers) {
11134
+ servers = new yjs.Map();
11135
+ entry.set("servers", servers);
11136
+ }
11137
+ const sEntry = new yjs.Map();
11138
+ sEntry.set("registered", true);
11139
+ sEntry.set("registeredAt", Date.now());
11140
+ sEntry.set("keyId", opts.keyId ?? null);
11141
+ sEntry.set("lastVerifiedAt", Date.now());
11142
+ sEntry.set("error", null);
11143
+ servers.set(opts.serverUrl, sEntry);
11144
+ }
11145
+ });
11146
+ }
11147
+ revokeDevice(publicKey, revokedBy) {
11148
+ const entry = this.devicesMap.get(publicKey);
11149
+ if (!entry) return;
11150
+ this.document.transact(() => {
11151
+ entry.set("revokedAt", Date.now());
11152
+ entry.set("revokedBy", revokedBy);
11153
+ });
11154
+ }
11155
+ setDeviceServerStatus(devicePubKey, serverUrl, status) {
11156
+ const entry = this.devicesMap.get(devicePubKey);
11157
+ if (!entry) return;
11158
+ this.document.transact(() => {
11159
+ let servers = entry.get("servers");
11160
+ if (!servers) {
11161
+ servers = new yjs.Map();
11162
+ entry.set("servers", servers);
11163
+ }
11164
+ let sEntry = servers.get(serverUrl);
11165
+ if (!sEntry) {
11166
+ sEntry = new yjs.Map();
11167
+ sEntry.set("registered", false);
11168
+ sEntry.set("registeredAt", null);
11169
+ sEntry.set("keyId", null);
11170
+ sEntry.set("lastVerifiedAt", null);
11171
+ sEntry.set("error", null);
11172
+ servers.set(serverUrl, sEntry);
11173
+ }
11174
+ for (const [key, value] of Object.entries(status)) if (value !== void 0) sEntry.set(key, value);
11175
+ });
11176
+ }
11058
11177
  /**
11059
11178
  * Observe deep changes on a specific top-level map.
11060
11179
  * Returns an unsubscribe function.
@@ -11119,6 +11238,154 @@ var IdentityDocProvider = class extends EventEmitter {
11119
11238
  }
11120
11239
  };
11121
11240
 
11241
+ //#endregion
11242
+ //#region packages/provider/src/DeviceRegistrationService.ts
11243
+ /**
11244
+ * Handles cross-server device key registration and revocation propagation.
11245
+ *
11246
+ * All methods operate on the identity Y.Doc's `devicesMap` and authenticate
11247
+ * independently on each server using the master key's `signChallenge`.
11248
+ */
11249
+ var DeviceRegistrationService = class {
11250
+ /**
11251
+ * Fan out a device key to all servers in `serversMap` where it isn't registered.
11252
+ *
11253
+ * Must be called from a master device that can sign challenges with the
11254
+ * account-level private key.
11255
+ */
11256
+ static async fanOutDeviceKey(opts) {
11257
+ const { devicePubKey, x25519Key, deviceName, identityDoc, masterPubKey, signChallenge, skipServerUrl } = opts;
11258
+ const servers = identityDoc.getServers();
11259
+ const results = [];
11260
+ if (!identityDoc.getDevice(devicePubKey)) identityDoc.addDevice(devicePubKey, {
11261
+ deviceName: deviceName ?? "Unknown",
11262
+ tier: "paired",
11263
+ x25519Key
11264
+ });
11265
+ for (const [serverUrl] of servers) {
11266
+ if (skipServerUrl && serverUrl === skipServerUrl) continue;
11267
+ if ((identityDoc.getDevice(devicePubKey)?.servers.get(serverUrl))?.registered) continue;
11268
+ try {
11269
+ const client = new AbracadabraClient({ url: serverUrl });
11270
+ await client.loginWithKey(masterPubKey, signChallenge);
11271
+ await client.addKey({
11272
+ publicKey: devicePubKey,
11273
+ x25519Key,
11274
+ deviceName
11275
+ });
11276
+ const match = (await client.listKeys()).find((k) => k.publicKey === devicePubKey && !k.revoked);
11277
+ identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, {
11278
+ registered: true,
11279
+ registeredAt: Date.now(),
11280
+ keyId: match?.id ?? null,
11281
+ lastVerifiedAt: Date.now(),
11282
+ error: null
11283
+ });
11284
+ results.push({
11285
+ serverUrl,
11286
+ success: true
11287
+ });
11288
+ } catch (e) {
11289
+ identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, { error: e?.message ?? "Registration failed" });
11290
+ results.push({
11291
+ serverUrl,
11292
+ success: false,
11293
+ error: e?.message ?? "Registration failed"
11294
+ });
11295
+ }
11296
+ }
11297
+ return results;
11298
+ }
11299
+ /**
11300
+ * Propagate pending revocations from the identity Y.Doc to all servers.
11301
+ *
11302
+ * Scans `devicesMap` for entries with `revokedAt` set and revokes them
11303
+ * on every server where they're still registered. Must be called from
11304
+ * a master device.
11305
+ */
11306
+ static async propagateRevocations(opts) {
11307
+ const { identityDoc, masterPubKey, signChallenge } = opts;
11308
+ const devices = identityDoc.getDevices();
11309
+ const results = [];
11310
+ for (const [devicePubKey, device] of devices) {
11311
+ if (!device.revokedAt) continue;
11312
+ for (const [serverUrl, serverStatus] of device.servers) {
11313
+ if (!serverStatus.registered || !serverStatus.keyId) continue;
11314
+ try {
11315
+ const client = new AbracadabraClient({ url: serverUrl });
11316
+ await client.loginWithKey(masterPubKey, signChallenge);
11317
+ await client.revokeKey(serverStatus.keyId);
11318
+ identityDoc.setDeviceServerStatus(devicePubKey, serverUrl, {
11319
+ registered: false,
11320
+ error: null
11321
+ });
11322
+ results.push({
11323
+ serverUrl,
11324
+ success: true
11325
+ });
11326
+ } catch (e) {
11327
+ results.push({
11328
+ serverUrl,
11329
+ success: false,
11330
+ error: e?.message ?? "Revocation failed"
11331
+ });
11332
+ }
11333
+ }
11334
+ }
11335
+ return results;
11336
+ }
11337
+ /**
11338
+ * Reconcile the identity Y.Doc's `devicesMap` with a server's actual
11339
+ * device key list. Call this on every server connection.
11340
+ *
11341
+ * - Updates registration status in Y.Doc to match server reality
11342
+ * - Imports unknown server keys as legacy entries
11343
+ * - Executes pending revocations if the caller is a master device
11344
+ */
11345
+ static async reconcile(opts) {
11346
+ const { identityDoc, serverUrl, client, isMaster } = opts;
11347
+ let serverKeys;
11348
+ try {
11349
+ serverKeys = await client.listKeys();
11350
+ } catch {
11351
+ return;
11352
+ }
11353
+ const devicesMap = identityDoc.devicesMap;
11354
+ devicesMap.forEach((yEntry, devicePub) => {
11355
+ const serverEntry = yEntry.get("servers")?.get(serverUrl);
11356
+ const serverKey = serverKeys.find((k) => k.publicKey === devicePub);
11357
+ const revokedAt = yEntry.get("revokedAt");
11358
+ if (serverEntry?.get("registered") && !serverKey) identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
11359
+ registered: false,
11360
+ keyId: null
11361
+ });
11362
+ if (serverKey && !serverKey.revoked) {
11363
+ if (!serverEntry?.get("registered")) identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
11364
+ registered: true,
11365
+ keyId: serverKey.id,
11366
+ lastVerifiedAt: Date.now()
11367
+ });
11368
+ else identityDoc.setDeviceServerStatus(devicePub, serverUrl, {
11369
+ lastVerifiedAt: Date.now(),
11370
+ keyId: serverKey.id
11371
+ });
11372
+ if (revokedAt && isMaster) client.revokeKey(serverKey.id).then(() => {
11373
+ identityDoc.setDeviceServerStatus(devicePub, serverUrl, { registered: false });
11374
+ }).catch(() => {});
11375
+ }
11376
+ });
11377
+ for (const key of serverKeys) {
11378
+ if (key.revoked) continue;
11379
+ if (!devicesMap.has(key.publicKey)) identityDoc.addDevice(key.publicKey, {
11380
+ deviceName: key.deviceName ?? "Unknown",
11381
+ tier: "paired",
11382
+ serverUrl,
11383
+ keyId: key.id
11384
+ });
11385
+ }
11386
+ }
11387
+ };
11388
+
11122
11389
  //#endregion
11123
11390
  exports.AbracadabraBaseProvider = AbracadabraBaseProvider;
11124
11391
  exports.AbracadabraClient = AbracadabraClient;
@@ -11137,6 +11404,7 @@ exports.DEFAULT_FILE_CHUNK_SIZE = DEFAULT_FILE_CHUNK_SIZE;
11137
11404
  exports.DEFAULT_ICE_SERVERS = DEFAULT_ICE_SERVERS;
11138
11405
  exports.DataChannelRouter = DataChannelRouter;
11139
11406
  exports.DevicePairingChannel = DevicePairingChannel;
11407
+ exports.DeviceRegistrationService = DeviceRegistrationService;
11140
11408
  exports.DocKeyManager = DocKeyManager;
11141
11409
  exports.DocumentCache = DocumentCache;
11142
11410
  exports.E2EAbracadabraProvider = E2EAbracadabraProvider;