@abraca/dabra 1.2.0 → 1.3.0
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 +54 -3
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +54 -3
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +13 -3
- package/package.json +1 -1
- package/src/CryptoIdentityKeystore.ts +23 -0
- package/src/IdentityDoc.ts +2 -2
- package/src/webrtc/AbracadabraWebRTC.ts +61 -29
- package/src/webrtc/types.ts +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -584,6 +584,11 @@ declare class CryptoIdentityKeystore {
|
|
|
584
584
|
* Returns the locally-cached username label, or null if no identity is cached.
|
|
585
585
|
*/
|
|
586
586
|
getUsername(credentialIdHint?: string): Promise<string | null>;
|
|
587
|
+
/**
|
|
588
|
+
* Updates the cached username for a given credential (or the first cached identity).
|
|
589
|
+
* Call this after the user sets/changes their display name so it persists across devices.
|
|
590
|
+
*/
|
|
591
|
+
setUsername(username: string, credentialIdHint?: string): Promise<void>;
|
|
587
592
|
/** Returns true if an identity is cached in IndexedDB. */
|
|
588
593
|
hasIdentity(): Promise<boolean>;
|
|
589
594
|
/** Remove cached identity record(s) from IndexedDB. The passkey itself
|
|
@@ -2060,7 +2065,7 @@ interface AbracadabraWebRTCConfiguration {
|
|
|
2060
2065
|
* When provided, all data channel messages (except key-exchange) are
|
|
2061
2066
|
* encrypted with AES-256-GCM using X25519 ECDH-derived session keys.
|
|
2062
2067
|
*/
|
|
2063
|
-
e2ee?: E2EEIdentity;
|
|
2068
|
+
e2ee?: E2EEIdentity | (() => Promise<E2EEIdentity>);
|
|
2064
2069
|
/** WebSocket polyfill for signaling (e.g. for Node.js). */
|
|
2065
2070
|
WebSocketPolyfill?: any;
|
|
2066
2071
|
}
|
|
@@ -2121,6 +2126,9 @@ declare class AbracadabraWebRTC extends EventEmitter {
|
|
|
2121
2126
|
private fileChannels;
|
|
2122
2127
|
private e2eeChannels;
|
|
2123
2128
|
private readonly config;
|
|
2129
|
+
/** Cached resolved E2EE identity (lazily resolved from factory on first peer connect). */
|
|
2130
|
+
private _resolvedE2ee;
|
|
2131
|
+
private _resolveE2eePromise;
|
|
2124
2132
|
readonly peers: Map<string, PeerState>;
|
|
2125
2133
|
localPeerId: string | null;
|
|
2126
2134
|
isConnected: boolean;
|
|
@@ -2157,6 +2165,8 @@ declare class AbracadabraWebRTC extends EventEmitter {
|
|
|
2157
2165
|
private removePeer;
|
|
2158
2166
|
private removeAllPeers;
|
|
2159
2167
|
private createPeerConnection;
|
|
2168
|
+
/** Resolve the E2EE identity, supporting both pre-resolved objects and lazy factories. */
|
|
2169
|
+
private resolveE2ee;
|
|
2160
2170
|
private attachDataHandlers;
|
|
2161
2171
|
private startDataSync;
|
|
2162
2172
|
private initiateConnection;
|
|
@@ -2530,8 +2540,8 @@ interface IdentityDocConfiguration {
|
|
|
2530
2540
|
*/
|
|
2531
2541
|
webrtc?: {
|
|
2532
2542
|
/** Server URL to use for signaling (any connected server works). */signalingServerUrl: string; /** Token for the signaling server. */
|
|
2533
|
-
token: string | (() => string) | (() => Promise<string>); /** E2EE identity for the data channel. */
|
|
2534
|
-
e2ee?: E2EEIdentity; /** ICE servers. */
|
|
2543
|
+
token: string | (() => string) | (() => Promise<string>); /** E2EE identity for the data channel. Accepts a factory for lazy derivation. */
|
|
2544
|
+
e2ee?: E2EEIdentity | (() => Promise<E2EEIdentity>); /** ICE servers. */
|
|
2535
2545
|
iceServers?: RTCIceServer[];
|
|
2536
2546
|
};
|
|
2537
2547
|
/** Disable IndexedDB offline store. */
|
package/package.json
CHANGED
|
@@ -322,6 +322,29 @@ export class CryptoIdentityKeystore {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Updates the cached username for a given credential (or the first cached identity).
|
|
327
|
+
* Call this after the user sets/changes their display name so it persists across devices.
|
|
328
|
+
*/
|
|
329
|
+
async setUsername(username: string, credentialIdHint?: string): Promise<void> {
|
|
330
|
+
const db = await openDb();
|
|
331
|
+
try {
|
|
332
|
+
if (credentialIdHint) {
|
|
333
|
+
const stored = await dbGet(db, credentialIdHint);
|
|
334
|
+
if (stored) {
|
|
335
|
+
await dbPut(db, credentialIdHint, { ...stored, username });
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
const all = await dbGetAll(db);
|
|
339
|
+
if (all.length > 0) {
|
|
340
|
+
await dbPut(db, all[0].key, { ...all[0].value, username });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} finally {
|
|
344
|
+
db.close();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
325
348
|
/** Returns true if an identity is cached in IndexedDB. */
|
|
326
349
|
async hasIdentity(): Promise<boolean> {
|
|
327
350
|
const db = await openDb();
|
package/src/IdentityDoc.ts
CHANGED
|
@@ -132,8 +132,8 @@ export interface IdentityDocConfiguration {
|
|
|
132
132
|
signalingServerUrl: string;
|
|
133
133
|
/** Token for the signaling server. */
|
|
134
134
|
token: string | (() => string) | (() => Promise<string>);
|
|
135
|
-
/** E2EE identity for the data channel. */
|
|
136
|
-
e2ee?: E2EEIdentity;
|
|
135
|
+
/** E2EE identity for the data channel. Accepts a factory for lazy derivation. */
|
|
136
|
+
e2ee?: E2EEIdentity | (() => Promise<E2EEIdentity>);
|
|
137
137
|
/** ICE servers. */
|
|
138
138
|
iceServers?: RTCIceServer[];
|
|
139
139
|
};
|
|
@@ -49,10 +49,14 @@ export class AbracadabraWebRTC extends EventEmitter {
|
|
|
49
49
|
enableAwarenessSync: boolean;
|
|
50
50
|
enableFileTransfer: boolean;
|
|
51
51
|
fileChunkSize: number;
|
|
52
|
-
e2ee: E2EEIdentity | null;
|
|
52
|
+
e2ee: E2EEIdentity | (() => Promise<E2EEIdentity>) | null;
|
|
53
53
|
WebSocketPolyfill: any;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
/** Cached resolved E2EE identity (lazily resolved from factory on first peer connect). */
|
|
57
|
+
private _resolvedE2ee: E2EEIdentity | null = null;
|
|
58
|
+
private _resolveE2eePromise: Promise<E2EEIdentity> | null = null;
|
|
59
|
+
|
|
56
60
|
public readonly peers = new Map<string, PeerState>();
|
|
57
61
|
public localPeerId: string | null = null;
|
|
58
62
|
public isConnected = false;
|
|
@@ -471,40 +475,68 @@ export class AbracadabraWebRTC extends EventEmitter {
|
|
|
471
475
|
return pc;
|
|
472
476
|
}
|
|
473
477
|
|
|
478
|
+
/** Resolve the E2EE identity, supporting both pre-resolved objects and lazy factories. */
|
|
479
|
+
private async resolveE2ee(): Promise<E2EEIdentity | null> {
|
|
480
|
+
if (this._resolvedE2ee) return this._resolvedE2ee;
|
|
481
|
+
if (!this.config.e2ee) return null;
|
|
482
|
+
if (typeof this.config.e2ee === "function") {
|
|
483
|
+
if (!this._resolveE2eePromise) {
|
|
484
|
+
this._resolveE2eePromise = this.config.e2ee().then((id) => {
|
|
485
|
+
this._resolvedE2ee = id;
|
|
486
|
+
return id;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return this._resolveE2eePromise;
|
|
490
|
+
}
|
|
491
|
+
this._resolvedE2ee = this.config.e2ee;
|
|
492
|
+
return this._resolvedE2ee;
|
|
493
|
+
}
|
|
494
|
+
|
|
474
495
|
private attachDataHandlers(peerId: string, pc: PeerConnection): void {
|
|
475
496
|
// Set up E2EE if configured.
|
|
476
497
|
if (this.config.e2ee) {
|
|
477
|
-
|
|
478
|
-
this.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
pc.router.on("channelMessage", async ({ name, data }: { name: string; data: any }) => {
|
|
483
|
-
if (name === KEY_EXCHANGE_CHANNEL) {
|
|
484
|
-
try {
|
|
485
|
-
const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
486
|
-
await e2ee.handleKeyExchange(buf);
|
|
487
|
-
} catch (err) {
|
|
488
|
-
this.emit("e2eeFailed", { peerId, error: err });
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
// When key-exchange channel opens, send our public key.
|
|
494
|
-
pc.router.on("channelOpen", ({ name, channel }: { name: string; channel: RTCDataChannel }) => {
|
|
495
|
-
if (name === KEY_EXCHANGE_CHANNEL) {
|
|
496
|
-
channel.send(e2ee.getKeyExchangeMessage());
|
|
498
|
+
// Resolve E2EE identity (may be lazy — e.g. passkey-derived X25519 key).
|
|
499
|
+
this.resolveE2ee().then((identity) => {
|
|
500
|
+
if (!identity) {
|
|
501
|
+
this.startDataSync(peerId, pc);
|
|
502
|
+
return;
|
|
497
503
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
504
|
+
const e2ee = new E2EEChannel(identity, this.config.docId);
|
|
505
|
+
this.e2eeChannels.set(peerId, e2ee);
|
|
506
|
+
pc.router.setEncryptor(e2ee);
|
|
507
|
+
|
|
508
|
+
// Listen for key-exchange messages on the router.
|
|
509
|
+
pc.router.on("channelMessage", async ({ name, data }: { name: string; data: any }) => {
|
|
510
|
+
if (name === KEY_EXCHANGE_CHANNEL) {
|
|
511
|
+
try {
|
|
512
|
+
const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
513
|
+
await e2ee.handleKeyExchange(buf);
|
|
514
|
+
} catch (err) {
|
|
515
|
+
this.emit("e2eeFailed", { peerId, error: err });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
});
|
|
505
519
|
|
|
506
|
-
|
|
520
|
+
// When key-exchange channel opens, send our public key.
|
|
521
|
+
pc.router.on("channelOpen", ({ name, channel }: { name: string; channel: RTCDataChannel }) => {
|
|
522
|
+
if (name === KEY_EXCHANGE_CHANNEL) {
|
|
523
|
+
channel.send(e2ee.getKeyExchangeMessage());
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
e2ee.on("established", () => {
|
|
528
|
+
this.emit("e2eeEstablished", { peerId });
|
|
529
|
+
// Now that E2EE is ready, start Y.js sync (deferred).
|
|
530
|
+
this.startDataSync(peerId, pc);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
e2ee.on("error", (err: Error) => {
|
|
534
|
+
this.emit("e2eeFailed", { peerId, error: err });
|
|
535
|
+
});
|
|
536
|
+
}).catch((err) => {
|
|
507
537
|
this.emit("e2eeFailed", { peerId, error: err });
|
|
538
|
+
// Fall back to unencrypted sync on E2EE resolution failure.
|
|
539
|
+
this.startDataSync(peerId, pc);
|
|
508
540
|
});
|
|
509
541
|
} else {
|
|
510
542
|
// No E2EE — start data sync immediately.
|
package/src/webrtc/types.ts
CHANGED
|
@@ -158,7 +158,7 @@ export interface AbracadabraWebRTCConfiguration {
|
|
|
158
158
|
* When provided, all data channel messages (except key-exchange) are
|
|
159
159
|
* encrypted with AES-256-GCM using X25519 ECDH-derived session keys.
|
|
160
160
|
*/
|
|
161
|
-
e2ee?: import("./E2EEChannel.ts").E2EEIdentity;
|
|
161
|
+
e2ee?: import("./E2EEChannel.ts").E2EEIdentity | (() => Promise<import("./E2EEChannel.ts").E2EEIdentity>);
|
|
162
162
|
|
|
163
163
|
/** WebSocket polyfill for signaling (e.g. for Node.js). */
|
|
164
164
|
WebSocketPolyfill?: any;
|