@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.
- package/dist/abracadabra-provider.cjs +280 -12
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +280 -13
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +87 -2
- package/package.json +1 -1
- package/src/DeviceRegistrationService.ts +243 -0
- package/src/IdentityDoc.ts +143 -1
- package/src/index.ts +4 -0
- package/src/webrtc/AbracadabraWebRTC.ts +32 -15
- package/src/webrtc/DevicePairingChannel.ts +12 -0
|
@@ -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
|
-
|
|
9991
|
-
if (
|
|
9992
|
-
|
|
9993
|
-
|
|
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
|
|
10000
|
-
|
|
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;
|