@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
|
@@ -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
|
-
|
|
9939
|
-
if (
|
|
9940
|
-
|
|
9941
|
-
|
|
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
|
|
9948
|
-
|
|
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
|
-
|
|
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
|