@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
|
@@ -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).catch(() => {});
|
|
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.
|
|
@@ -10101,6 +10104,7 @@ var AbracadabraWebRTC = class AbracadabraWebRTC extends EventEmitter {
|
|
|
10101
10104
|
enableAwareness: this.config.enableAwarenessSync,
|
|
10102
10105
|
enableFileTransfer: this.config.enableFileTransfer
|
|
10103
10106
|
});
|
|
10107
|
+
pc.router.createChannel("custom", { ordered: true });
|
|
10104
10108
|
try {
|
|
10105
10109
|
const sdp = await pc.createOffer();
|
|
10106
10110
|
this.signaling?.sendOffer(peerId, sdp);
|
|
@@ -10897,6 +10901,9 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
10897
10901
|
get pluginsMap() {
|
|
10898
10902
|
return this.document.getMap("plugins");
|
|
10899
10903
|
}
|
|
10904
|
+
get devicesMap() {
|
|
10905
|
+
return this.document.getMap("devices");
|
|
10906
|
+
}
|
|
10900
10907
|
get preferencesMap() {
|
|
10901
10908
|
return this.document.getMap("preferences");
|
|
10902
10909
|
}
|
|
@@ -11024,6 +11031,97 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
11024
11031
|
setPreference(key, value) {
|
|
11025
11032
|
this.preferencesMap.set(key, value);
|
|
11026
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
|
+
}
|
|
11027
11125
|
/**
|
|
11028
11126
|
* Observe deep changes on a specific top-level map.
|
|
11029
11127
|
* Returns an unsubscribe function.
|
|
@@ -11089,5 +11187,153 @@ var IdentityDocProvider = class extends EventEmitter {
|
|
|
11089
11187
|
};
|
|
11090
11188
|
|
|
11091
11189
|
//#endregion
|
|
11092
|
-
|
|
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 };
|
|
11093
11339
|
//# sourceMappingURL=abracadabra-provider.esm.js.map
|