@muhkoo/connect 0.9.0-alpha.0 → 0.10.1-alpha.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/connect.d.ts CHANGED
@@ -1494,6 +1494,9 @@ declare class ShardClient {
1494
1494
  * `null` for 404 so callers can distinguish "missing" from "errored".
1495
1495
  */
1496
1496
  getShard(hash: string): Promise<Uint8Array | null>;
1497
+ /** Fetch a shard from origin. Returns `null` for 404 (or offline when a
1498
+ * cache is present, so RS recovery can proceed); throws otherwise. */
1499
+ private fetchFromOrigin;
1497
1500
  /**
1498
1501
  * Check whether a shard exists. Lets the caller skip a PUT they'd otherwise
1499
1502
  * dedup-rewrite. The server can implement this as a HEAD or return a small
@@ -2511,6 +2514,21 @@ declare class ZkAuth {
2511
2514
  * to re-enable encryption/messaging without a full re-login.
2512
2515
  */
2513
2516
  restore(): Promise<AuthUser | null>;
2517
+ /**
2518
+ * Boot helper — call this once on app start. Restores the session AND, when a
2519
+ * passkey is enrolled, silently unlocks the identity via WebAuthn (recovering
2520
+ * the master seed so the ratchet keypair rehydrates and `client.kv` works).
2521
+ * The seed is never persisted, so a passkey (or password) is the only way to
2522
+ * unlock after a reload — this keeps that off the app's plate.
2523
+ *
2524
+ * Returns `{ user, unlocked }`. When `unlocked` is false (no passkey, the
2525
+ * user cancelled, or the browser required a gesture), prompt for a password
2526
+ * and call {@link unlock}. Apps need no key-management scaffolding beyond this.
2527
+ */
2528
+ resume(): Promise<{
2529
+ user: AuthUser | null;
2530
+ unlocked: boolean;
2531
+ }>;
2514
2532
  /**
2515
2533
  * Re-derive identity material for an already-authenticated (restored)
2516
2534
  * session, so the client can decrypt storage and run the ratchet. Verifies
@@ -3723,11 +3741,29 @@ declare class SpaceKeyring implements EpochKeyProvider {
3723
3741
  private readonly policy;
3724
3742
  /** Cached member directory: memberId → imported ECDSA verify key. */
3725
3743
  private readonly memberEcdsa;
3744
+ /** Coalesce concurrent roster refreshes (the parallel history/backlog decode
3745
+ * would otherwise fire one `roster` fetch PER message — a thundering herd). */
3746
+ private dirInFlight;
3747
+ private dirRefreshedAt;
3748
+ /** Min gap between non-forced roster refreshes. A miss that persists within
3749
+ * this window won't re-fetch (the roster hasn't changed that fast); genuine
3750
+ * membership changes use `{ force: true }` (join requests / history loads). */
3751
+ private static readonly DIR_COOLDOWN_MS;
3726
3752
  constructor(deps: SpaceKeyringDeps);
3727
3753
  /** The cached ECDSA verify key for a member, if known. */
3728
3754
  ecdsaKeyFor(memberId: string): CryptoKey | undefined;
3729
- /** Refresh the member directory (memberId → ECDSA key) from the roster. */
3730
- refreshDirectory(): Promise<void>;
3755
+ /**
3756
+ * Refresh the member directory (memberId → ECDSA key) from the roster.
3757
+ * Coalesces concurrent callers into ONE fetch, and skips a fetch when one
3758
+ * completed within {@link DIR_COOLDOWN_MS} (unless `force`). Without this, a
3759
+ * page of N messages decoded in parallel — each missing the cold directory —
3760
+ * fires N simultaneous `roster` requests that queue behind the browser's
3761
+ * connection limit and take many seconds.
3762
+ */
3763
+ refreshDirectory(opts?: {
3764
+ force?: boolean;
3765
+ }): Promise<void>;
3766
+ private doRefreshDirectory;
3731
3767
  currentEpoch(): number;
3732
3768
  keyForEpoch(epoch: number): Uint8Array | undefined;
3733
3769
  hasAnyKey(): boolean;
@@ -3748,6 +3784,13 @@ declare class SpaceKeyring implements EpochKeyProvider {
3748
3784
  * epoch keys obtained. Safe to call repeatedly (idempotent).
3749
3785
  */
3750
3786
  pullKeys(): Promise<number>;
3787
+ /**
3788
+ * Background freshness check for the cache-hit fast path: confirm we hold the
3789
+ * current epoch (picks up a rotation / our removal) WITHOUT the full newcomer
3790
+ * admit handshake. Cheap + idempotent; never blocks first paint. Returns the
3791
+ * number of newly-obtained epoch keys (0 = already current).
3792
+ */
3793
+ reconcile(): Promise<number>;
3751
3794
  /**
3752
3795
  * Wrap our group key(s) for a newcomer and post the blobs. In a `static`
3753
3796
  * space the newcomer receives every epoch we hold (full history); in a
@@ -4207,6 +4250,53 @@ declare class MessageNamespace {
4207
4250
  private myId;
4208
4251
  }
4209
4252
 
4253
+ /**
4254
+ * `ChatKeyVault` — SDK-owned lifecycle for a member's long-lived ratchet/space
4255
+ * keypair (the connect `KeyStore` entry keyed by the member id / username).
4256
+ *
4257
+ * Historically each app hand-rolled this (see the web app's old
4258
+ * `personal/spaceLoader.ts`): generate a keypair at register, seed-wrap it, and
4259
+ * PUT it to the personal space; on login GET + unwrap + hydrate the `KeyStore`.
4260
+ * That scaffolding now lives here so ANY app gets a **stable** member keypair
4261
+ * across reloads for free — without which the member re-admits to every space on
4262
+ * every load and the group-key cache can never round-trip.
4263
+ *
4264
+ * Security invariant: the wrapping secret is the **master seed** (not the
4265
+ * password), so the blob survives password changes and unlocks under a passkey.
4266
+ * The seed is only ever held in memory (never persisted), so this vault can only
4267
+ * provision/rehydrate while the client is unlocked.
4268
+ */
4269
+
4270
+ /** Opaque personal-space blob store (a `PersonalSpaceClient` or a kv-backed shim). */
4271
+ interface ChatKeyStore {
4272
+ get(key: string): Promise<WrappedPayload | null>;
4273
+ put(key: string, value: WrappedPayload): Promise<void>;
4274
+ }
4275
+ declare class ChatKeyVault {
4276
+ private readonly store;
4277
+ constructor(store: ChatKeyStore);
4278
+ /**
4279
+ * Register-time: ensure a keypair exists for `memberId`, seed-wrap the
4280
+ * dehydrated form, and persist it to the personal space. Idempotent — reuses
4281
+ * an existing in-tab keypair rather than minting a second one.
4282
+ */
4283
+ provision(memberId: string, seedSecret: string): Promise<void>;
4284
+ /**
4285
+ * Sign-in / unlock time: fetch the wrapped blob, unwrap with the seed, and
4286
+ * hydrate the `KeyStore` — restoring the SAME keypair the member had before.
4287
+ * Returns `true` when a keypair is now in hand (already present or hydrated),
4288
+ * `false` when there's no blob yet (a brand-new member → caller provisions).
4289
+ * Throws only on a genuine unwrap failure (wrong secret / tampered blob).
4290
+ */
4291
+ rehydrate(memberId: string, seedSecret: string): Promise<boolean>;
4292
+ /**
4293
+ * Ensure a stable keypair is in hand: rehydrate from the vault if a blob
4294
+ * exists, otherwise mint one and persist it (so it's stable next session).
4295
+ * Used at register and lazily when a space is first opened.
4296
+ */
4297
+ ensure(memberId: string, seedSecret: string): Promise<void>;
4298
+ }
4299
+
4210
4300
  /**
4211
4301
  * `client.space` — the fan-out group-encryption surface.
4212
4302
  *
@@ -4242,6 +4332,9 @@ interface SpaceNamespaceDeps {
4242
4332
  cache?: SpaceKeyCache;
4243
4333
  /** Offline manager — enables message caching + offline send replay. */
4244
4334
  offline?: OfflineManager;
4335
+ /** SDK-owned ratchet-keypair vault — gives a STABLE member keypair across
4336
+ * reloads (rehydrate-or-provision) instead of a fresh one each session. */
4337
+ chatKeys?: ChatKeyVault;
4245
4338
  /** P2P swarm config — enables peer block exchange among Space members. */
4246
4339
  p2p?: {
4247
4340
  enabled?: boolean;
@@ -4249,6 +4342,7 @@ interface SpaceNamespaceDeps {
4249
4342
  workerFactory?: () => Worker;
4250
4343
  iceServers?: RTCIceServer[];
4251
4344
  maxPeers?: number;
4345
+ debug?: boolean;
4252
4346
  };
4253
4347
  }
4254
4348
  declare class SpaceNamespace {
@@ -4727,6 +4821,8 @@ interface ClientOptions {
4727
4821
  workerFactory?: () => Worker;
4728
4822
  iceServers?: RTCIceServer[];
4729
4823
  maxPeers?: number;
4824
+ /** Log P2P mesh + block-exchange activity to the console (staging/dev). */
4825
+ debug?: boolean;
4730
4826
  };
4731
4827
  }
4732
4828
  declare class Client {
@@ -5245,10 +5341,13 @@ interface EngineHandle {
5245
5341
  interface PeerExchangeOptions {
5246
5342
  /** How long to wait for a peer before giving up to origin. Default 2500ms. */
5247
5343
  timeoutMs?: number;
5344
+ /** Log peer-served vs origin-fallback per block to the console. */
5345
+ debug?: boolean;
5248
5346
  }
5249
5347
  declare class PeerExchange implements PeerBlockSource {
5250
5348
  private readonly engine;
5251
5349
  private readonly timeoutMs;
5350
+ private readonly debug;
5252
5351
  constructor(engine: EngineHandle, opts?: PeerExchangeOptions);
5253
5352
  getBlock(hash: string, opts?: {
5254
5353
  timeoutMs?: number;
@@ -5404,6 +5503,8 @@ interface PeerNetworkOptions {
5404
5503
  timeoutMs?: number;
5405
5504
  /** Inject a transport (tests / a future libp2p transport). Default WebRTC. */
5406
5505
  transport?: PeerTransport;
5506
+ /** Log mesh + block-exchange activity to the console (use on staging/dev). */
5507
+ debug?: boolean;
5407
5508
  }
5408
5509
  declare class PeerNetwork {
5409
5510
  /** Pass this to a `ShardClient` as `peers`. */