@muhkoo/connect 0.1.0-alpha.1 → 0.2.0-alpha.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/browser/index.js +905 -49
- package/dist/browser/index.js.map +1 -1
- package/dist/connect.d.ts +496 -28
- package/dist/server/index.js +901 -49
- package/dist/server/index.js.map +1 -1
- package/dist/workers/index.js +21 -21
- package/dist/workers/index.js.map +1 -1
- package/package.json +8 -2
package/dist/connect.d.ts
CHANGED
|
@@ -2329,75 +2329,421 @@ declare class StorageNamespace {
|
|
|
2329
2329
|
}
|
|
2330
2330
|
|
|
2331
2331
|
/**
|
|
2332
|
-
*
|
|
2332
|
+
* Shared types for the Space fan-out group-encryption layer.
|
|
2333
2333
|
*
|
|
2334
|
-
* A
|
|
2335
|
-
*
|
|
2336
|
-
*
|
|
2337
|
-
*
|
|
2338
|
-
*
|
|
2334
|
+
* A Space is a group channel whose messages are sealed once with a symmetric
|
|
2335
|
+
* group key (`K_space`) rather than per-peer like the Double Ratchet. That
|
|
2336
|
+
* single ciphertext is decryptable by every member that holds the epoch key,
|
|
2337
|
+
* which is what lets the server persist + replay messages as history while
|
|
2338
|
+
* staying blind to their contents.
|
|
2339
|
+
*/
|
|
2340
|
+
/**
|
|
2341
|
+
* How a space treats history across membership changes.
|
|
2342
|
+
* - `static`: one group key for all time (epoch 0). New members can read the
|
|
2343
|
+
* entire history; a removed member keeps the key until you rotate manually.
|
|
2344
|
+
* - `rotate`: a new epoch key is minted on membership change. Members only
|
|
2345
|
+
* hold keys for the epochs they belonged to, so they cannot read messages
|
|
2346
|
+
* from epochs before they joined / after they left.
|
|
2347
|
+
*/
|
|
2348
|
+
type HistoryPolicy = "static" | "rotate";
|
|
2349
|
+
/**
|
|
2350
|
+
* An ECIES-wrapped copy of a group key, sealed to one member's identity ECDH
|
|
2351
|
+
* public key. Opaque to the server — useless without the member's private key.
|
|
2352
|
+
*/
|
|
2353
|
+
interface WrappedKey {
|
|
2354
|
+
/** Epoch this key belongs to (0 for static spaces). */
|
|
2355
|
+
epoch: number;
|
|
2356
|
+
/** Base64url-encoded JWK of the ephemeral ECDH public key used for the wrap. */
|
|
2357
|
+
ephemeralPub: string;
|
|
2358
|
+
/** Base64 AES-GCM IV (12 bytes). */
|
|
2359
|
+
iv: string;
|
|
2360
|
+
/** Base64 AES-GCM ciphertext: the wrapped 32-byte group key + tag. */
|
|
2361
|
+
ciphertext: string;
|
|
2362
|
+
/** Algorithm tag for forward-compat. */
|
|
2363
|
+
alg: string;
|
|
2364
|
+
}
|
|
2365
|
+
/** Cleartext space metadata the server records once at creation. */
|
|
2366
|
+
interface SpaceMetadata {
|
|
2367
|
+
/** Encoded space public key (the space's logical identity). */
|
|
2368
|
+
spaceId: string;
|
|
2369
|
+
historyPolicy: HistoryPolicy;
|
|
2370
|
+
/** Advisory monotonic epoch counter. */
|
|
2371
|
+
currentEpoch: number;
|
|
2372
|
+
}
|
|
2373
|
+
/**
|
|
2374
|
+
* A newcomer's request for the group key. The newcomer self-asserts its
|
|
2375
|
+
* identity public keys (trust-on-first-use at the client layer; the server is
|
|
2376
|
+
* blind and cannot validate them).
|
|
2377
|
+
*/
|
|
2378
|
+
interface JoinRequest {
|
|
2379
|
+
memberId: string;
|
|
2380
|
+
/** Base64url-encoded JWK of the member's identity ECDH public key. */
|
|
2381
|
+
identityEcdhPub: string;
|
|
2382
|
+
/** Base64url-encoded JWK of the member's identity ECDSA public key (optional). */
|
|
2383
|
+
identityEcdsaPub?: string;
|
|
2384
|
+
/** Epoch the newcomer wants a key for (defaults to current). */
|
|
2385
|
+
desiredEpoch?: number;
|
|
2386
|
+
ts?: number;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
/**
|
|
2390
|
+
* PacketCipher — the strategy `Network` uses to seal/open a packet's payload.
|
|
2339
2391
|
*
|
|
2340
|
-
*
|
|
2341
|
-
*
|
|
2342
|
-
*
|
|
2343
|
-
*
|
|
2392
|
+
* `Network` owns the wire shape (Message ↔ Packet, header placement, transport,
|
|
2393
|
+
* reconnect, checksum). The *encryption* is pluggable: the default
|
|
2394
|
+
* {@link DoubleRatchetCipher} reproduces the per-peer Double Ratchet behavior
|
|
2395
|
+
* Network has always had, while the Space layer supplies a group-key cipher.
|
|
2344
2396
|
*
|
|
2345
|
-
*
|
|
2346
|
-
*
|
|
2347
|
-
*
|
|
2397
|
+
* The sealed ciphertext always rides in `Packet.headers` (cleartext routing
|
|
2398
|
+
* fields stay readable to the server); `Packet.message` is left undefined on
|
|
2399
|
+
* the wire so the server never sees the real payload.
|
|
2400
|
+
*/
|
|
2401
|
+
|
|
2402
|
+
type PacketHeaders = Packet["headers"];
|
|
2403
|
+
/** Header fields a cipher merges into the outgoing packet. */
|
|
2404
|
+
type SealedHeaders = Record<string, string | number | boolean>;
|
|
2405
|
+
interface PacketCipher {
|
|
2406
|
+
/**
|
|
2407
|
+
* Seal the serialized `Message`. Returns the header fields to merge into
|
|
2408
|
+
* the outgoing packet (these carry the ciphertext). `Network` clears
|
|
2409
|
+
* `packet.message` after calling this.
|
|
2410
|
+
*/
|
|
2411
|
+
seal(serializedMessage: string, packet: Packet): Promise<SealedHeaders>;
|
|
2412
|
+
/** True if the inbound headers look like this cipher's sealed form. */
|
|
2413
|
+
handles(headers: PacketHeaders): boolean;
|
|
2414
|
+
/**
|
|
2415
|
+
* Open an inbound packet's headers back into a serialized `Message` string.
|
|
2416
|
+
* Returns `null` when the packet is not sealed for / not decryptable by us
|
|
2417
|
+
* (e.g. an epoch key we don't hold) — `Network` then leaves `packet.message`
|
|
2418
|
+
* untouched rather than throwing.
|
|
2419
|
+
*/
|
|
2420
|
+
open(headers: PacketHeaders): Promise<string | null>;
|
|
2421
|
+
}
|
|
2422
|
+
interface DoubleRatchetCipherDeps {
|
|
2423
|
+
ratchetManager: DoubleRatchetManager;
|
|
2424
|
+
clientId: string;
|
|
2425
|
+
serverId: string;
|
|
2426
|
+
sessionType: "global" | "specific";
|
|
2427
|
+
/** Resolves the live session id (Network manages the session lifecycle). */
|
|
2428
|
+
getSessionId: () => string | null;
|
|
2429
|
+
/** Whether this side is the ratchet "client" (default true). */
|
|
2430
|
+
isClient?: boolean;
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* Default cipher: per-peer Double Ratchet. Reproduces Network's original
|
|
2434
|
+
* `{ encrypted: true, cipherMessage: <json> }` header convention exactly so
|
|
2435
|
+
* existing consumers are unaffected.
|
|
2436
|
+
*/
|
|
2437
|
+
declare class DoubleRatchetCipher implements PacketCipher {
|
|
2438
|
+
private readonly deps;
|
|
2439
|
+
constructor(deps: DoubleRatchetCipherDeps);
|
|
2440
|
+
seal(serializedMessage: string): Promise<SealedHeaders>;
|
|
2441
|
+
handles(headers: PacketHeaders): boolean;
|
|
2442
|
+
open(headers: PacketHeaders): Promise<string | null>;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
/**
|
|
2446
|
+
* SpacePacketCipher — the {@link PacketCipher} that seals packets with a
|
|
2447
|
+
* space's group key instead of a per-peer Double Ratchet.
|
|
2448
|
+
*
|
|
2449
|
+
* It pulls the active epoch's key from an {@link EpochKeyProvider} (the
|
|
2450
|
+
* SpaceKeyring), so this adapter stays decoupled from the keyring's transport
|
|
2451
|
+
* and caching concerns — it only needs "what key seals epoch N".
|
|
2452
|
+
*/
|
|
2453
|
+
|
|
2454
|
+
/** Minimal view of the keyring the cipher needs. */
|
|
2455
|
+
interface EpochKeyProvider {
|
|
2456
|
+
/** Epoch new messages are sealed under (advances on rotation). */
|
|
2457
|
+
currentEpoch(): number;
|
|
2458
|
+
/** The 32-byte group key for an epoch, or undefined if we don't hold it. */
|
|
2459
|
+
keyForEpoch(epoch: number): Uint8Array | undefined;
|
|
2460
|
+
}
|
|
2461
|
+
declare class SpacePacketCipher implements PacketCipher {
|
|
2462
|
+
private readonly keys;
|
|
2463
|
+
private readonly contentType?;
|
|
2464
|
+
constructor(keys: EpochKeyProvider, contentType?: string | undefined);
|
|
2465
|
+
seal(serializedMessage: string): Promise<SealedHeaders>;
|
|
2466
|
+
handles(headers: PacketHeaders): boolean;
|
|
2467
|
+
/** Returns null for epochs we don't hold (surfaced as undecryptable). */
|
|
2468
|
+
open(headers: PacketHeaders): Promise<string | null>;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
/**
|
|
2472
|
+
* SpaceKeyring — the client's view of a space's group keys.
|
|
2473
|
+
*
|
|
2474
|
+
* Holds the per-epoch group keys this member possesses (in memory, optionally
|
|
2475
|
+
* mirrored to PersonalSpace), and orchestrates the server-blind distribution
|
|
2476
|
+
* dance against a {@link KeyringTransport}:
|
|
2477
|
+
*
|
|
2478
|
+
* - a newcomer posts a join request and pulls the wrapped key blob addressed
|
|
2479
|
+
* to it, unwrapping with its identity private key;
|
|
2480
|
+
* - an existing key-holder wraps the group key for a newcomer (ECIES to the
|
|
2481
|
+
* newcomer's identity public key) and posts the opaque blob;
|
|
2482
|
+
* - on membership change (rotate spaces) a fresh epoch key is minted and
|
|
2483
|
+
* re-wrapped to the current roster.
|
|
2484
|
+
*
|
|
2485
|
+
* The server only ever stores opaque {@link WrappedKey} blobs — it cannot read
|
|
2486
|
+
* the group key. Implements {@link EpochKeyProvider} so it can back a
|
|
2487
|
+
* {@link SpacePacketCipher} directly.
|
|
2488
|
+
*/
|
|
2489
|
+
|
|
2490
|
+
/**
|
|
2491
|
+
* Reserved member id for the app's "keeper" — a trusted always-available member
|
|
2492
|
+
* (the accelerator's AppDOv1) that holds the group key and re-issues it to
|
|
2493
|
+
* newcomers, so a channel is joinable with no human online. Admitting this id
|
|
2494
|
+
* at channel creation hands the key to the keeper. MUST match the accelerator's
|
|
2495
|
+
* `KEEPER_MEMBER_ID`.
|
|
2496
|
+
*/
|
|
2497
|
+
declare const KEEPER_MEMBER_ID = "__keeper__";
|
|
2498
|
+
/** Opaque-blob transport to the space's keyring (HTTP or in-memory test seam). */
|
|
2499
|
+
interface KeyringTransport {
|
|
2500
|
+
postJoinRequest(req: JoinRequest): Promise<void>;
|
|
2501
|
+
/** All wrapped blobs addressed to `memberId` (any epoch). */
|
|
2502
|
+
fetchBlobs(memberId: string): Promise<WrappedKey[]>;
|
|
2503
|
+
/** Post a wrapped key blob for `targetMemberId`. */
|
|
2504
|
+
postWrappedKey(targetMemberId: string, wrapped: WrappedKey, fromMemberId: string): Promise<void>;
|
|
2505
|
+
/** Outstanding join requests (for an online key-holder to fulfill). */
|
|
2506
|
+
fetchPending(): Promise<JoinRequest[]>;
|
|
2507
|
+
/** Current roster: member ids + their identity ECDH public keys. */
|
|
2508
|
+
fetchRoster(): Promise<Array<{
|
|
2509
|
+
memberId: string;
|
|
2510
|
+
identityEcdhPub: string;
|
|
2511
|
+
}>>;
|
|
2512
|
+
/** Advisory epoch bump; returns the agreed new epoch. */
|
|
2513
|
+
rotate(nextEpoch: number): Promise<{
|
|
2514
|
+
epoch: number;
|
|
2515
|
+
}>;
|
|
2516
|
+
fetchMetadata(): Promise<SpaceMetadata | null>;
|
|
2517
|
+
}
|
|
2518
|
+
/** PersonalSpace-backed cache of this member's group keys (epoch → base64 key). */
|
|
2519
|
+
interface SpaceKeyCache {
|
|
2520
|
+
loadKeys(spaceId: string): Promise<Record<string, string> | null>;
|
|
2521
|
+
saveKeys(spaceId: string, keys: Record<string, string>): Promise<void>;
|
|
2522
|
+
}
|
|
2523
|
+
interface SpaceKeyringDeps {
|
|
2524
|
+
spaceId: string;
|
|
2525
|
+
memberId: string;
|
|
2526
|
+
/** This member's identity ECDH public key, base64url JWK (for join requests). */
|
|
2527
|
+
identityEcdhPub: string;
|
|
2528
|
+
/** Resolves this member's identity ECDH private key (for unwrapping). */
|
|
2529
|
+
ownPrivateKey: () => CryptoKey | null;
|
|
2530
|
+
transport: KeyringTransport;
|
|
2531
|
+
cache?: SpaceKeyCache;
|
|
2532
|
+
historyPolicy?: HistoryPolicy;
|
|
2533
|
+
}
|
|
2534
|
+
declare class SpaceKeyring implements EpochKeyProvider {
|
|
2535
|
+
private readonly deps;
|
|
2536
|
+
private readonly keys;
|
|
2537
|
+
private epoch;
|
|
2538
|
+
private readonly policy;
|
|
2539
|
+
constructor(deps: SpaceKeyringDeps);
|
|
2540
|
+
currentEpoch(): number;
|
|
2541
|
+
keyForEpoch(epoch: number): Uint8Array | undefined;
|
|
2542
|
+
hasAnyKey(): boolean;
|
|
2543
|
+
/** Hydrate keys from the PersonalSpace cache, if available. Best-effort —
|
|
2544
|
+
* a cache miss/failure (e.g. not unlocked) must not block joining. */
|
|
2545
|
+
loadFromCache(): Promise<boolean>;
|
|
2546
|
+
private persist;
|
|
2547
|
+
/** Creator path: mint epoch-0 group key for a brand-new space. */
|
|
2548
|
+
bootstrapNew(): Promise<void>;
|
|
2549
|
+
/** Post a join request so an online key-holder can wrap a key for us. */
|
|
2550
|
+
requestKey(desiredEpoch?: number): Promise<void>;
|
|
2551
|
+
/**
|
|
2552
|
+
* Pull and unwrap any blobs addressed to us. Returns the number of new
|
|
2553
|
+
* epoch keys obtained. Safe to call repeatedly (idempotent).
|
|
2554
|
+
*/
|
|
2555
|
+
pullKeys(): Promise<number>;
|
|
2556
|
+
/**
|
|
2557
|
+
* Wrap our group key(s) for a newcomer and post the blobs. In a `static`
|
|
2558
|
+
* space the newcomer receives every epoch we hold (full history); in a
|
|
2559
|
+
* `rotate` space they receive only the current epoch.
|
|
2560
|
+
*/
|
|
2561
|
+
admit(targetMemberId: string, identityEcdhPub: string): Promise<void>;
|
|
2562
|
+
/**
|
|
2563
|
+
* Admit every outstanding join request (an online key-holder fulfilling
|
|
2564
|
+
* pending newcomers). Returns the ids admitted.
|
|
2565
|
+
*/
|
|
2566
|
+
admitPending(): Promise<string[]>;
|
|
2567
|
+
/**
|
|
2568
|
+
* Rotate to a fresh epoch (rotate spaces). Mints a new group key, advances
|
|
2569
|
+
* the epoch, and re-wraps it to every member in `roster` (plus ourselves).
|
|
2570
|
+
*/
|
|
2571
|
+
rotate(roster?: Array<{
|
|
2572
|
+
memberId: string;
|
|
2573
|
+
identityEcdhPub: string;
|
|
2574
|
+
}>): Promise<number>;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
/**
|
|
2578
|
+
* `Space` — a client handle for one shared space.
|
|
2579
|
+
*
|
|
2580
|
+
* A space is the unit everything is built on. It carries:
|
|
2581
|
+
* - **realtime transport** over a {@link BroadcastChannel} (group handshake,
|
|
2582
|
+
* roster frames, raw-frame passthrough);
|
|
2583
|
+
* - **fan-out group messaging** — `Message`s sealed once with the space's
|
|
2584
|
+
* group key (via {@link SpacePacketCipher}), wrapped in a cleartext
|
|
2585
|
+
* {@link Packet} envelope, broadcast AND persisted by the server as
|
|
2586
|
+
* history (the `spaceMessage` frame);
|
|
2587
|
+
* - **room-scoped file storage** via {@link FileStorage} over the shard
|
|
2588
|
+
* endpoint;
|
|
2589
|
+
* - **legacy per-peer Double Ratchet messaging** (`send`/`announce`) for the
|
|
2590
|
+
* ephemeral, no-history path — unchanged so existing callers (the chat hook
|
|
2591
|
+
* reached via `client.message.room(name)`) keep working.
|
|
2592
|
+
*
|
|
2593
|
+
* `Space` was formerly `Room`; `Room` remains exported as an alias. The
|
|
2594
|
+
* fan-out methods (`sendMessage`/`onMessage`/`history`/`join`/`admit`/`rotate`)
|
|
2595
|
+
* require a {@link SpaceKeyring} dep; without one only the transport + DR +
|
|
2596
|
+
* file surface is available.
|
|
2348
2597
|
*/
|
|
2349
2598
|
|
|
2350
2599
|
type Listener = (e: CustomEvent) => void;
|
|
2351
|
-
interface
|
|
2600
|
+
interface SpaceFileMetadata {
|
|
2352
2601
|
name: string;
|
|
2353
2602
|
type: string;
|
|
2354
2603
|
path?: string;
|
|
2355
2604
|
lastModified?: number;
|
|
2356
2605
|
}
|
|
2357
|
-
|
|
2606
|
+
/** A decrypted inbound fan-out message. */
|
|
2607
|
+
interface SpaceMessageEvent {
|
|
2608
|
+
from: string;
|
|
2609
|
+
channel: string;
|
|
2610
|
+
epoch: number;
|
|
2611
|
+
contentType?: string;
|
|
2612
|
+
message: Message;
|
|
2613
|
+
}
|
|
2614
|
+
interface SpaceDeps {
|
|
2615
|
+
/** Transport room id. For fan-out spaces this is the encoded space pubkey. */
|
|
2358
2616
|
name: string;
|
|
2359
2617
|
wsBaseUrl: string;
|
|
2360
2618
|
httpBaseUrl: string;
|
|
2361
2619
|
/** Header-injecting fetch from the client's HttpClient. */
|
|
2362
2620
|
fetch: typeof fetch;
|
|
2363
|
-
/** Resolves the user id used for the ratchet (
|
|
2621
|
+
/** Resolves the user id used for the ratchet / member id (signed-in username). */
|
|
2364
2622
|
myId: () => string;
|
|
2365
2623
|
/** Resolves a short-lived WS upgrade ticket (or null when keyless). */
|
|
2366
2624
|
fetchTicket: () => Promise<string | null>;
|
|
2625
|
+
/** Optional group-key keyring — required for the fan-out methods. */
|
|
2626
|
+
keyring?: SpaceKeyring;
|
|
2627
|
+
/** History policy for this space (advisory; defaults to static). */
|
|
2628
|
+
historyPolicy?: HistoryPolicy;
|
|
2629
|
+
/**
|
|
2630
|
+
* When true (default), a connected member that holds the group key
|
|
2631
|
+
* automatically wraps it for any newcomer that posts a join request —
|
|
2632
|
+
* the "any online key-holder admits" model. Set false to gate membership
|
|
2633
|
+
* yourself via `onJoinRequest` + `admit`.
|
|
2634
|
+
*/
|
|
2635
|
+
autoAdmit?: boolean;
|
|
2636
|
+
/**
|
|
2637
|
+
* Test seam — defaults to constructing a real {@link BroadcastChannel}.
|
|
2638
|
+
* A test can inject an in-memory channel to exercise the fan-out wire
|
|
2639
|
+
* without a live websocket.
|
|
2640
|
+
*/
|
|
2641
|
+
createChannel?: (url: string, myId: string) => SpaceChannelLike;
|
|
2367
2642
|
}
|
|
2368
|
-
|
|
2643
|
+
/** The subset of {@link BroadcastChannel} a {@link Space} drives. */
|
|
2644
|
+
interface SpaceChannelLike {
|
|
2645
|
+
on(event: string, handler: (e: CustomEvent) => void): void;
|
|
2646
|
+
off(event: string, handler: (e: CustomEvent) => void): void;
|
|
2647
|
+
connect(): Promise<void>;
|
|
2648
|
+
disconnect(): void;
|
|
2649
|
+
announce(): Promise<void>;
|
|
2650
|
+
send(plaintext: string): Promise<number>;
|
|
2651
|
+
sendRaw(frame: unknown): void;
|
|
2652
|
+
peers?(): string[];
|
|
2653
|
+
isConnected?(): boolean;
|
|
2654
|
+
}
|
|
2655
|
+
declare class Space {
|
|
2369
2656
|
private readonly deps;
|
|
2370
2657
|
readonly name: string;
|
|
2658
|
+
readonly historyPolicy: HistoryPolicy;
|
|
2371
2659
|
private channel;
|
|
2372
2660
|
private readonly pending;
|
|
2373
|
-
|
|
2374
|
-
/**
|
|
2661
|
+
private readonly cipher;
|
|
2662
|
+
/** Per-instance event bus for fan-out events (isolated across spaces). */
|
|
2663
|
+
private readonly events;
|
|
2664
|
+
private rawWired;
|
|
2665
|
+
constructor(deps: SpaceDeps);
|
|
2666
|
+
/** The encoded space id (same as the transport name for fan-out spaces). */
|
|
2667
|
+
get id(): string;
|
|
2668
|
+
/** The keyring backing this space's group keys, if any. */
|
|
2669
|
+
get keyring(): SpaceKeyring | undefined;
|
|
2670
|
+
/** Open the space websocket (resolves the ticket + identity lazily). */
|
|
2375
2671
|
connect(): Promise<void>;
|
|
2376
2672
|
disconnect(): void;
|
|
2377
|
-
/** Subscribe to a channel event. Buffered until `connect()` if early. */
|
|
2673
|
+
/** Subscribe to a raw channel event. Buffered until `connect()` if early. */
|
|
2378
2674
|
on(event: string, handler: Listener): void;
|
|
2379
2675
|
off(event: string, handler: Listener): void;
|
|
2380
2676
|
/** Advertise our keyExchange to the room (idempotent per connection). */
|
|
2381
2677
|
announce(): Promise<void>;
|
|
2382
|
-
/** E2E-encrypt `text` to every peer
|
|
2678
|
+
/** E2E-encrypt `text` to every peer via the Double Ratchet (no history). */
|
|
2383
2679
|
send(text: string): Promise<number>;
|
|
2384
2680
|
/** Send an arbitrary JSON frame (e.g. the `{name}` handshake). */
|
|
2385
2681
|
sendRaw(frame: unknown): void;
|
|
2386
2682
|
peers(): string[];
|
|
2387
2683
|
isConnected(): boolean;
|
|
2388
2684
|
/** The underlying channel, for advanced use. `null` before `connect()`. */
|
|
2389
|
-
get raw():
|
|
2390
|
-
/**
|
|
2391
|
-
|
|
2685
|
+
get raw(): SpaceChannelLike | null;
|
|
2686
|
+
/**
|
|
2687
|
+
* Seal `payload` once with the current group key and broadcast it as a
|
|
2688
|
+
* persisted `spaceMessage`. Requires a keyring.
|
|
2689
|
+
*/
|
|
2690
|
+
sendMessage(payload: unknown, opts?: {
|
|
2691
|
+
channel?: string;
|
|
2692
|
+
contentType?: string;
|
|
2693
|
+
}): Promise<void>;
|
|
2694
|
+
/** Subscribe to decrypted fan-out messages. Returns an unsubscribe fn. */
|
|
2695
|
+
onMessage(handler: (e: SpaceMessageEvent) => void): () => void;
|
|
2696
|
+
/** Subscribe to inbound join requests (for a key-holder to admit). */
|
|
2697
|
+
onJoinRequest(handler: (req: JoinRequest) => void): () => void;
|
|
2698
|
+
/**
|
|
2699
|
+
* Fetch + decrypt persisted history beyond the live backlog.
|
|
2700
|
+
* `before` is an opaque cursor returned as `nextCursor`.
|
|
2701
|
+
*/
|
|
2702
|
+
history(opts?: {
|
|
2703
|
+
before?: string;
|
|
2704
|
+
limit?: number;
|
|
2705
|
+
}): Promise<{
|
|
2706
|
+
messages: SpaceMessageEvent[];
|
|
2707
|
+
nextCursor: string | null;
|
|
2708
|
+
}>;
|
|
2709
|
+
/**
|
|
2710
|
+
* Bring this member's group keys up to date: load from cache, and if we
|
|
2711
|
+
* hold nothing, request a key and poll until one arrives (or timeout).
|
|
2712
|
+
*/
|
|
2713
|
+
join(opts?: {
|
|
2714
|
+
timeoutMs?: number;
|
|
2715
|
+
pollMs?: number;
|
|
2716
|
+
}): Promise<boolean>;
|
|
2717
|
+
/** Creator path: mint the initial group key, then connect. */
|
|
2718
|
+
create(): Promise<void>;
|
|
2719
|
+
/** Wrap the group key for a newcomer and post it (key-holder action). */
|
|
2720
|
+
admit(memberId: string, identityEcdhPub: string): Promise<void>;
|
|
2721
|
+
/** Rotate to a fresh epoch and re-wrap to the roster (rotate spaces). */
|
|
2722
|
+
rotate(roster?: Array<{
|
|
2723
|
+
memberId: string;
|
|
2724
|
+
identityEcdhPub: string;
|
|
2725
|
+
}>): Promise<number>;
|
|
2726
|
+
putFile(file: File | Blob | Uint8Array, metadata: SpaceFileMetadata): Promise<{
|
|
2392
2727
|
manifest: FileManifest;
|
|
2393
2728
|
stat: FileStat;
|
|
2394
2729
|
}>;
|
|
2395
|
-
/** Fetch + decode a file previously shared in this room. */
|
|
2396
2730
|
getFile(manifest: FileManifest): Promise<{
|
|
2397
2731
|
data: Uint8Array;
|
|
2398
2732
|
stat: FileStat;
|
|
2399
2733
|
}>;
|
|
2400
2734
|
private fileStorage;
|
|
2735
|
+
/** Wire the RAW_FRAME handler that drives fan-out events. */
|
|
2736
|
+
private wireFanout;
|
|
2737
|
+
private onRawFrame;
|
|
2738
|
+
/**
|
|
2739
|
+
* Decode one persisted/broadcast frame into a SpaceMessageEvent.
|
|
2740
|
+
* `isRawFrame` true means `input` is the already-extracted `spaceMessage`
|
|
2741
|
+
* packet string; false means it's the full `{spaceMessage, name, ...}` frame.
|
|
2742
|
+
*/
|
|
2743
|
+
private decodeFrame;
|
|
2744
|
+
private emitEvent;
|
|
2745
|
+
private requireKeyring;
|
|
2746
|
+
private requireCipher;
|
|
2401
2747
|
}
|
|
2402
2748
|
|
|
2403
2749
|
/**
|
|
@@ -2484,7 +2830,7 @@ declare class MessageNamespace {
|
|
|
2484
2830
|
* primitive group chat is built on (`publish`/`subscribe`/`send` are the
|
|
2485
2831
|
* higher-level conveniences layered over the same space transport).
|
|
2486
2832
|
*/
|
|
2487
|
-
room(roomName: string):
|
|
2833
|
+
room(roomName: string): Space;
|
|
2488
2834
|
/** Close every open room connection (e.g. on logout). */
|
|
2489
2835
|
disconnect(): void;
|
|
2490
2836
|
private getRoom;
|
|
@@ -2494,6 +2840,86 @@ declare class MessageNamespace {
|
|
|
2494
2840
|
private myId;
|
|
2495
2841
|
}
|
|
2496
2842
|
|
|
2843
|
+
/**
|
|
2844
|
+
* `client.space` — the fan-out group-encryption surface.
|
|
2845
|
+
*
|
|
2846
|
+
* const space = await client.space.createSpace({ historyPolicy: "static" });
|
|
2847
|
+
* await space.sendMessage({ text: "hello" });
|
|
2848
|
+
* space.onMessage(e => …);
|
|
2849
|
+
*
|
|
2850
|
+
* const joined = await client.space.joinSpace(space.id); // newcomer
|
|
2851
|
+
*
|
|
2852
|
+
* A space owns a client-generated keypair whose public key is its id, a
|
|
2853
|
+
* symmetric group key distributed server-blind via the keyring, and a
|
|
2854
|
+
* persisted message log. This namespace wires the {@link Space} handle to the
|
|
2855
|
+
* HTTP {@link KeyringClient}, the member's identity keys (from {@link KeyStore},
|
|
2856
|
+
* shared with the Double Ratchet path), and a PersonalSpace-backed key cache.
|
|
2857
|
+
*/
|
|
2858
|
+
|
|
2859
|
+
/** Thrown by `joinChannel` when no channel with that name is registered. */
|
|
2860
|
+
declare class ChannelNotFoundError extends Error {
|
|
2861
|
+
readonly channelName: string;
|
|
2862
|
+
constructor(channelName: string);
|
|
2863
|
+
}
|
|
2864
|
+
/** Thrown by `createChannel` when the name is already registered. */
|
|
2865
|
+
declare class ChannelExistsError extends Error {
|
|
2866
|
+
readonly channelName: string;
|
|
2867
|
+
constructor(channelName: string);
|
|
2868
|
+
}
|
|
2869
|
+
interface SpaceNamespaceDeps {
|
|
2870
|
+
http: HttpClient;
|
|
2871
|
+
session: SessionState;
|
|
2872
|
+
/** WebSocket base (ws/wss); derived from the accelerator baseUrl. */
|
|
2873
|
+
wsBaseUrl: string;
|
|
2874
|
+
/** Optional PersonalSpace-backed cache of group keys (encrypted at rest). */
|
|
2875
|
+
cache?: SpaceKeyCache;
|
|
2876
|
+
}
|
|
2877
|
+
declare class SpaceNamespace {
|
|
2878
|
+
private readonly deps;
|
|
2879
|
+
constructor(deps: SpaceNamespaceDeps);
|
|
2880
|
+
/**
|
|
2881
|
+
* Create a new space: generate its keypair (public key = id), register
|
|
2882
|
+
* metadata, mint the initial group key, and connect. Returns the open Space.
|
|
2883
|
+
*/
|
|
2884
|
+
createSpace(opts?: {
|
|
2885
|
+
historyPolicy?: HistoryPolicy;
|
|
2886
|
+
}): Promise<Space>;
|
|
2887
|
+
/** Join an existing space by id (its public key). Resolves once a key arrives. */
|
|
2888
|
+
joinSpace(spaceId: string, opts?: {
|
|
2889
|
+
timeoutMs?: number;
|
|
2890
|
+
}): Promise<Space>;
|
|
2891
|
+
/** A handle to a space without auto-connecting (uses cached keys on demand). */
|
|
2892
|
+
get(spaceId: string, historyPolicy?: HistoryPolicy): Promise<Space>;
|
|
2893
|
+
/** List this app's channels (name → space-id) for the key's environment. */
|
|
2894
|
+
listChannels(): Promise<Array<{
|
|
2895
|
+
name: string;
|
|
2896
|
+
spaceId: string;
|
|
2897
|
+
}>>;
|
|
2898
|
+
/** Resolve a channel name to its space id, or null if it doesn't exist. */
|
|
2899
|
+
resolveChannel(name: string): Promise<string | null>;
|
|
2900
|
+
/**
|
|
2901
|
+
* Create a new channel: mint a fan-out space (the caller becomes its first
|
|
2902
|
+
* key-holder) and register `name → spaceId` in the app directory. Throws
|
|
2903
|
+
* {@link ChannelExistsError} if the name is already taken.
|
|
2904
|
+
*/
|
|
2905
|
+
createChannel(name: string, opts?: {
|
|
2906
|
+
historyPolicy?: HistoryPolicy;
|
|
2907
|
+
}): Promise<Space>;
|
|
2908
|
+
/**
|
|
2909
|
+
* Join an existing channel by name. Throws {@link ChannelNotFoundError} if
|
|
2910
|
+
* no channel with that name is registered (use {@link createChannel} first).
|
|
2911
|
+
*/
|
|
2912
|
+
joinChannel(name: string, opts?: {
|
|
2913
|
+
timeoutMs?: number;
|
|
2914
|
+
}): Promise<Space>;
|
|
2915
|
+
private build;
|
|
2916
|
+
/** Ensure the member has an identity ECDH keypair; return its public key. */
|
|
2917
|
+
private ensureIdentity;
|
|
2918
|
+
private fetchPolicy;
|
|
2919
|
+
private fetchTicket;
|
|
2920
|
+
private myId;
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2497
2923
|
/** The hosted Muhkoo Accelerator — the default {@link ClientOptions.baseUrl}. */
|
|
2498
2924
|
declare const DEFAULT_BASE_URL = "https://api.muhkoo.dev";
|
|
2499
2925
|
interface ClientOptions {
|
|
@@ -2535,6 +2961,8 @@ declare class Client {
|
|
|
2535
2961
|
readonly storage: StorageNamespace;
|
|
2536
2962
|
/** Realtime messaging — `client.message.subscribe(...)`, etc. */
|
|
2537
2963
|
readonly message: MessageNamespace;
|
|
2964
|
+
/** Fan-out group spaces — `client.space.createSpace(...)`, etc. */
|
|
2965
|
+
readonly space: SpaceNamespace;
|
|
2538
2966
|
private readonly session;
|
|
2539
2967
|
private readonly http;
|
|
2540
2968
|
constructor(options?: ClientOptions);
|
|
@@ -2544,5 +2972,45 @@ declare class Client {
|
|
|
2544
2972
|
get isAuthenticated(): boolean;
|
|
2545
2973
|
}
|
|
2546
2974
|
|
|
2547
|
-
|
|
2548
|
-
|
|
2975
|
+
/**
|
|
2976
|
+
* KeyringClient — HTTP implementation of {@link KeyringTransport} against the
|
|
2977
|
+
* accelerator's per-space keyring endpoints. All blobs are opaque to the
|
|
2978
|
+
* server; this client just moves them.
|
|
2979
|
+
*
|
|
2980
|
+
* POST /api/spaces/:id/keyring/join-request { ...JoinRequest }
|
|
2981
|
+
* GET /api/spaces/:id/keyring/blobs/:member -> WrappedKey[]
|
|
2982
|
+
* POST /api/spaces/:id/keyring/wrap { targetMemberId, fromMemberId, wrapped }
|
|
2983
|
+
* GET /api/spaces/:id/keyring/pending -> JoinRequest[]
|
|
2984
|
+
* GET /api/spaces/:id/keyring/roster -> { memberId, identityEcdhPub }[]
|
|
2985
|
+
* POST /api/spaces/:id/keyring/rotate { nextEpoch } -> { epoch }
|
|
2986
|
+
* GET /api/spaces/:id/metadata -> SpaceMetadata
|
|
2987
|
+
*/
|
|
2988
|
+
|
|
2989
|
+
interface KeyringClientDeps {
|
|
2990
|
+
spaceId: string;
|
|
2991
|
+
httpBaseUrl: string;
|
|
2992
|
+
/** Header-injecting fetch from the client's HttpClient. */
|
|
2993
|
+
fetch: typeof fetch;
|
|
2994
|
+
}
|
|
2995
|
+
declare class KeyringClient implements KeyringTransport {
|
|
2996
|
+
private readonly deps;
|
|
2997
|
+
constructor(deps: KeyringClientDeps);
|
|
2998
|
+
private url;
|
|
2999
|
+
private post;
|
|
3000
|
+
private get;
|
|
3001
|
+
postJoinRequest(req: JoinRequest): Promise<void>;
|
|
3002
|
+
fetchBlobs(memberId: string): Promise<WrappedKey[]>;
|
|
3003
|
+
postWrappedKey(targetMemberId: string, wrapped: WrappedKey, fromMemberId: string): Promise<void>;
|
|
3004
|
+
fetchPending(): Promise<JoinRequest[]>;
|
|
3005
|
+
fetchRoster(): Promise<Array<{
|
|
3006
|
+
memberId: string;
|
|
3007
|
+
identityEcdhPub: string;
|
|
3008
|
+
}>>;
|
|
3009
|
+
rotate(nextEpoch: number): Promise<{
|
|
3010
|
+
epoch: number;
|
|
3011
|
+
}>;
|
|
3012
|
+
fetchMetadata(): Promise<SpaceMetadata | null>;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
export { AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, AuthClient, AuthNamespace, AuthPublicInput, Authenticator, BroadcastChannel, BroadcastChannelEvents, ChannelExistsError, ChannelNotFoundError, ChunkCipher, Client, DEFAULT_BASE_URL, DoubleRatchet, DoubleRatchetCipher, DoubleRatchetManager, EncryptedSession, EventCore, EventCoreEvents, Field, Field as FieldElement, FileStorage, HashKnowledge, HttpClient, HttpError, KEEPER_MEMBER_ID, KeyStore, KeyringClient, LocalStorageSessionStore, MemorySessionStore, Message, Message as MessageDefault, MessageNamespace, PBKDF2_DEFAULT_ITERATIONS, PREIMAGE_POK_VERIFICATION_KEY, Packet, PersonalSpaceClient, Poseidon, PreimagePoK, ReedSolomonCodec, Space as Room, SerializeMessage, SessionState, ShardClient, SharedSpaceClient, Space, SpaceKeyring, SpaceNamespace, SpacePacketCipher, StorageNamespace, WSTransport, ZkAuth, buildCommitment, compilePrograms, decodeFromHex, decorators, decryptAesGcm, defaultCircuitUrls, defaultSessionStore, deriveAesKeyFromPassphrase, deriveBitsHkdf, deriveIdentity, encodeToHex, encryptAesGcm, exportPublicKeyBase64, exportPublicKeyHex, generateAuthProof, getSubtle, initBn128Wasm, initRsWasm, initializeCircuits, manifestToStat, poseidonHash, quickVerify, randomBytes, shardHash, signMessage, toField, unwrapWithPassphrase, verify, verifyGroth16, verifyHashKnowledge, verifyPreimagePoK, wrapWithPassphrase };
|
|
3016
|
+
export type { ACL, ACLEntry, AuthClientOptions, AuthKeyPair, AuthSuccess, AuthToken, AuthType, AuthUser, Bn128WasmInstance, BroadcastChannelEvent, BroadcastChannelOptions, ChannelLike, ChunkKeyMaterial, ChunkManifest, CipherFrame, CipherMessage, CipherMessageHeader, CircuitBufferConfig, CircuitUrls, ClientOptions, DehydratedKeypair, DehydratedKeys, DoubleRatchetCipherDeps, ECDHCurve, EncodeResult, EpochKeyProvider, FileManifest, FileStat, FileStorageOptions, Groth16Proof, HttpClientOptions, IncomingFrame, KeyExchangeFrame, KeyPair, Keypair, KeyringClientDeps, KeyringTransport, LoginOptions, MessageBody, MessageEvent, MessageHeaders, MessageNamespaceDeps, MessageOptions, MessageSubscription, MuhkooMessageEvent, OAuthProvider, PacketCipher, PacketHeaders, PacketOptions, Participant, Permission, PermissionCheckRequest, PermissionCheckResult, PersonalSpaceClientOptions, PublicKeyJWK, RatchetState, ReceiveResult, RegisterParams, Role, SpaceDeps as RoomDeps, SpaceFileMetadata as RoomFileMetadata, SealedHeaders, Session, SessionOptions, SessionStore, SetOptions, ShardClientOptions, SharedSpaceClientOptions, SnarkProof, SpaceChannelLike, SpaceDeps, SpaceFileMetadata, SpaceKeyCache, SpaceKeyringDeps, SpaceMessageEvent, SpaceNamespaceDeps, StorageChangeEvent, StorageNamespaceDeps, StoredSession, Subscription, UserIdentity, VerificationKey, WSMessage, WSMessageType, WSTransportOptions, WrappedPayload, WriteFileInput, WriteToShardsInput, ZkAuthDeps, ZkAuthenticateRequest, ZkChallengeResponse, ZkCompiled, ZkIdentity, ZkRegisterRequest, ZkRegisterResponse };
|