@abraca/dabra 1.0.24 → 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 +256 -9
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +256 -10
- 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 +22 -11
|
@@ -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).catch(() => {});
|
|
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.
|
|
@@ -10153,6 +10156,7 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
|
|
|
10153
10156
|
enableAwareness: this.config.enableAwarenessSync,
|
|
10154
10157
|
enableFileTransfer: this.config.enableFileTransfer
|
|
10155
10158
|
});
|
|
10159
|
+
pc.router.createChannel("custom", { ordered: true });
|
|
10156
10160
|
try {
|
|
10157
10161
|
const sdp = await pc.createOffer();
|
|
10158
10162
|
this.signaling?.sendOffer(peerId, sdp);
|
|
@@ -10949,6 +10953,9 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
10949
10953
|
get pluginsMap() {
|
|
10950
10954
|
return this.document.getMap("plugins");
|
|
10951
10955
|
}
|
|
10956
|
+
get devicesMap() {
|
|
10957
|
+
return this.document.getMap("devices");
|
|
10958
|
+
}
|
|
10952
10959
|
get preferencesMap() {
|
|
10953
10960
|
return this.document.getMap("preferences");
|
|
10954
10961
|
}
|
|
@@ -11076,6 +11083,97 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
11076
11083
|
setPreference(key, value) {
|
|
11077
11084
|
this.preferencesMap.set(key, value);
|
|
11078
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
|
+
}
|
|
11079
11177
|
/**
|
|
11080
11178
|
* Observe deep changes on a specific top-level map.
|
|
11081
11179
|
* Returns an unsubscribe function.
|
|
@@ -11140,6 +11238,154 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
11140
11238
|
}
|
|
11141
11239
|
};
|
|
11142
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
|
+
|
|
11143
11389
|
//#endregion
|
|
11144
11390
|
exports.AbracadabraBaseProvider = AbracadabraBaseProvider;
|
|
11145
11391
|
exports.AbracadabraClient = AbracadabraClient;
|
|
@@ -11158,6 +11404,7 @@ exports.DEFAULT_FILE_CHUNK_SIZE = DEFAULT_FILE_CHUNK_SIZE;
|
|
|
11158
11404
|
exports.DEFAULT_ICE_SERVERS = DEFAULT_ICE_SERVERS;
|
|
11159
11405
|
exports.DataChannelRouter = DataChannelRouter;
|
|
11160
11406
|
exports.DevicePairingChannel = DevicePairingChannel;
|
|
11407
|
+
exports.DeviceRegistrationService = DeviceRegistrationService;
|
|
11161
11408
|
exports.DocKeyManager = DocKeyManager;
|
|
11162
11409
|
exports.DocumentCache = DocumentCache;
|
|
11163
11410
|
exports.E2EAbracadabraProvider = E2EAbracadabraProvider;
|