@abraca/dabra 1.3.0 → 1.3.1
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 +68 -17
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +68 -17
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +22 -0
- package/package.json +1 -1
- package/src/AbracadabraClient.ts +37 -0
- package/src/webrtc/AbracadabraWebRTC.ts +64 -42
package/dist/index.d.ts
CHANGED
|
@@ -340,6 +340,28 @@ declare class AbracadabraClient {
|
|
|
340
340
|
publicKey: string;
|
|
341
341
|
};
|
|
342
342
|
}>;
|
|
343
|
+
/** Request a device session token after successful crypto auth. Requires valid JWT. */
|
|
344
|
+
requestDeviceSession(opts: {
|
|
345
|
+
publicKey: string;
|
|
346
|
+
deviceName?: string;
|
|
347
|
+
}): Promise<{
|
|
348
|
+
sessionId: string;
|
|
349
|
+
sessionToken: string;
|
|
350
|
+
expiresAt: number;
|
|
351
|
+
}>;
|
|
352
|
+
/** Exchange a device session token for a fresh JWT. No biometric/passkey needed. */
|
|
353
|
+
refreshWithDeviceSession(sessionToken: string): Promise<string>;
|
|
354
|
+
/** List active device sessions for the authenticated user. */
|
|
355
|
+
listDeviceSessions(): Promise<Array<{
|
|
356
|
+
id: string;
|
|
357
|
+
keyId: string;
|
|
358
|
+
deviceName?: string;
|
|
359
|
+
issuedAt: number;
|
|
360
|
+
expiresAt: number;
|
|
361
|
+
lastUsedAt?: number;
|
|
362
|
+
}>>;
|
|
363
|
+
/** Revoke a device session by ID. */
|
|
364
|
+
revokeDeviceSession(sessionId: string): Promise<void>;
|
|
343
365
|
/**
|
|
344
366
|
* Fetch a short-lived anonymous pairing token for WebRTC signaling.
|
|
345
367
|
* No authentication required. The token only grants access to `__pairing_*` rooms.
|
package/package.json
CHANGED
package/src/AbracadabraClient.ts
CHANGED
|
@@ -227,6 +227,43 @@ export class AbracadabraClient {
|
|
|
227
227
|
});
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
// ── Device Sessions ────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
/** Request a device session token after successful crypto auth. Requires valid JWT. */
|
|
233
|
+
async requestDeviceSession(opts: {
|
|
234
|
+
publicKey: string;
|
|
235
|
+
deviceName?: string;
|
|
236
|
+
}): Promise<{ sessionId: string; sessionToken: string; expiresAt: number }> {
|
|
237
|
+
return this.request("POST", "/auth/device-session", {
|
|
238
|
+
body: { publicKey: opts.publicKey, deviceName: opts.deviceName },
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Exchange a device session token for a fresh JWT. No biometric/passkey needed. */
|
|
243
|
+
async refreshWithDeviceSession(sessionToken: string): Promise<string> {
|
|
244
|
+
const res = await this.request<{ token: string }>("POST", "/auth/refresh", {
|
|
245
|
+
body: { sessionToken },
|
|
246
|
+
auth: false,
|
|
247
|
+
});
|
|
248
|
+
this.token = res.token;
|
|
249
|
+
return res.token;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** List active device sessions for the authenticated user. */
|
|
253
|
+
async listDeviceSessions(): Promise<
|
|
254
|
+
Array<{ id: string; keyId: string; deviceName?: string; issuedAt: number; expiresAt: number; lastUsedAt?: number }>
|
|
255
|
+
> {
|
|
256
|
+
const res = await this.request<{
|
|
257
|
+
sessions: Array<{ id: string; keyId: string; deviceName?: string; issuedAt: number; expiresAt: number; lastUsedAt?: number }>;
|
|
258
|
+
}>("GET", "/auth/device-session");
|
|
259
|
+
return res.sessions;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Revoke a device session by ID. */
|
|
263
|
+
async revokeDeviceSession(sessionId: string): Promise<void> {
|
|
264
|
+
await this.request("DELETE", `/auth/device-session/${encodeURIComponent(sessionId)}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
230
267
|
// ── Pairing ─────────────────────────────────────────────────────────────
|
|
231
268
|
|
|
232
269
|
/**
|
|
@@ -288,6 +288,9 @@ export class AbracadabraWebRTC extends EventEmitter {
|
|
|
288
288
|
this.signaling = null;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
this._resolvedE2ee = null;
|
|
292
|
+
this._resolveE2eePromise = null;
|
|
293
|
+
|
|
291
294
|
this.removeAllListeners();
|
|
292
295
|
}
|
|
293
296
|
|
|
@@ -493,55 +496,74 @@ export class AbracadabraWebRTC extends EventEmitter {
|
|
|
493
496
|
}
|
|
494
497
|
|
|
495
498
|
private attachDataHandlers(peerId: string, pc: PeerConnection): void {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
if (!identity) {
|
|
501
|
-
this.startDataSync(peerId, pc);
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
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
|
-
});
|
|
499
|
+
if (!this.config.e2ee) {
|
|
500
|
+
this.startDataSync(peerId, pc);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
519
503
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
504
|
+
// E2EE identity may resolve asynchronously (e.g. lazy passkey-derived key).
|
|
505
|
+
// Register handlers synchronously so no messages are lost during resolution.
|
|
506
|
+
let e2ee: E2EEChannel | null = null;
|
|
507
|
+
const pendingMessages: Uint8Array[] = [];
|
|
508
|
+
let pendingKeyExchangeChannel: RTCDataChannel | null = null;
|
|
509
|
+
|
|
510
|
+
pc.router.on("channelMessage", async ({ name, data }: { name: string; data: any }) => {
|
|
511
|
+
if (name !== KEY_EXCHANGE_CHANNEL) return;
|
|
512
|
+
const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
513
|
+
if (!e2ee) {
|
|
514
|
+
pendingMessages.push(buf);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
await e2ee.handleKeyExchange(buf);
|
|
519
|
+
} catch (err) {
|
|
520
|
+
this.emit("e2eeFailed", { peerId, error: err });
|
|
521
|
+
}
|
|
522
|
+
});
|
|
526
523
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
524
|
+
pc.router.on("channelOpen", ({ name, channel }: { name: string; channel: RTCDataChannel }) => {
|
|
525
|
+
if (name !== KEY_EXCHANGE_CHANNEL) return;
|
|
526
|
+
if (!e2ee) {
|
|
527
|
+
pendingKeyExchangeChannel = channel;
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
channel.send(e2ee.getKeyExchangeMessage());
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
this.resolveE2ee().then(async (identity) => {
|
|
534
|
+
if (!identity) {
|
|
535
|
+
this.startDataSync(peerId, pc);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
e2ee = new E2EEChannel(identity, this.config.docId);
|
|
539
|
+
this.e2eeChannels.set(peerId, e2ee);
|
|
540
|
+
pc.router.setEncryptor(e2ee);
|
|
532
541
|
|
|
533
|
-
|
|
542
|
+
// Drain buffered key-exchange channel open
|
|
543
|
+
if (pendingKeyExchangeChannel) {
|
|
544
|
+
pendingKeyExchangeChannel.send(e2ee.getKeyExchangeMessage());
|
|
545
|
+
}
|
|
546
|
+
// Drain buffered messages
|
|
547
|
+
for (const msg of pendingMessages) {
|
|
548
|
+
try {
|
|
549
|
+
await e2ee.handleKeyExchange(msg);
|
|
550
|
+
} catch (err) {
|
|
534
551
|
this.emit("e2eeFailed", { peerId, error: err });
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
e2ee.on("established", () => {
|
|
556
|
+
this.emit("e2eeEstablished", { peerId });
|
|
539
557
|
this.startDataSync(peerId, pc);
|
|
540
558
|
});
|
|
541
|
-
|
|
542
|
-
|
|
559
|
+
|
|
560
|
+
e2ee.on("error", (err: Error) => {
|
|
561
|
+
this.emit("e2eeFailed", { peerId, error: err });
|
|
562
|
+
});
|
|
563
|
+
}).catch((err) => {
|
|
564
|
+
this.emit("e2eeFailed", { peerId, error: err });
|
|
543
565
|
this.startDataSync(peerId, pc);
|
|
544
|
-
}
|
|
566
|
+
});
|
|
545
567
|
}
|
|
546
568
|
|
|
547
569
|
private startDataSync(peerId: string, pc: PeerConnection): void {
|