@interncom/diplomatic 0.11.5 → 0.12.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.
Files changed (45) hide show
  1. package/dist/cli/index.mjs +2 -2
  2. package/dist/cli/shared/binary.d.ts +4 -0
  3. package/dist/cli/shared/codecs/bundleHost.d.ts +11 -0
  4. package/dist/cli/shared/codecs/identityBundle.d.ts +25 -0
  5. package/dist/cli/shared/codecs/pairPackage.d.ts +15 -0
  6. package/dist/cli/shared/crypto/enclave.d.ts +113 -2
  7. package/dist/cli/shared/crypto/entropy.d.ts +11 -0
  8. package/dist/cli/shared/seed.d.ts +25 -0
  9. package/dist/cli/shared/types.d.ts +4 -6
  10. package/dist/cli/shared/webauthn/common.d.ts +38 -0
  11. package/dist/cli/shared/webauthn/largeBlob.d.ts +19 -0
  12. package/dist/cli/shared/webauthn/prf.d.ts +33 -0
  13. package/dist/cli/shared/worker/embeddedWorkerSource.d.ts +1 -0
  14. package/dist/cli/shared/worker/spawn.d.ts +5 -0
  15. package/dist/cli/src/index.d.ts +10 -8
  16. package/dist/web/client.d.ts +3 -2
  17. package/dist/web/identity/pairPackage.d.ts +31 -0
  18. package/dist/web/index.d.ts +18 -6
  19. package/dist/web/index.mjs +1 -1
  20. package/dist/web/openClient.d.ts +14 -109
  21. package/dist/web/passkey/largeBlob.d.ts +17 -0
  22. package/dist/web/passkey/prf-store.d.ts +62 -0
  23. package/dist/web/passkey/seed.d.ts +13 -77
  24. package/dist/web/passkey/webauthn.d.ts +1 -0
  25. package/dist/web/react/useClient.d.ts +14 -21
  26. package/dist/web/shared/binary.d.ts +4 -0
  27. package/dist/web/shared/codecs/bundleHost.d.ts +11 -0
  28. package/dist/web/shared/codecs/identityBundle.d.ts +25 -0
  29. package/dist/web/shared/codecs/pairPackage.d.ts +15 -0
  30. package/dist/web/shared/codecs/pairPackageEnvelope.d.ts +18 -0
  31. package/dist/web/shared/crypto/enclave.d.ts +113 -2
  32. package/dist/web/shared/crypto/entropy.d.ts +11 -0
  33. package/dist/web/shared/seed.d.ts +25 -0
  34. package/dist/web/shared/types.d.ts +4 -6
  35. package/dist/web/shared/webauthn/common.d.ts +38 -0
  36. package/dist/web/shared/webauthn/largeBlob.d.ts +19 -0
  37. package/dist/web/shared/webauthn/prf.d.ts +33 -0
  38. package/dist/web/shared/worker/embeddedWorkerSource.d.ts +1 -0
  39. package/dist/web/shared/worker/spawn.d.ts +5 -0
  40. package/dist/web/stores/idb/seed.d.ts +24 -6
  41. package/dist/web/stores/memory/seed.d.ts +3 -4
  42. package/dist/web/types.d.ts +13 -7
  43. package/dist/web/worker/client.d.ts +26 -48
  44. package/dist/web/worker.mjs +1 -1
  45. package/package.json +1 -5
@@ -4,7 +4,10 @@ import { IAuthTimestamp } from "./codecs/authTimestamp.ts";
4
4
  import type { IBagPeekItem } from "./codecs/peekItem.ts";
5
5
  import { IUsageQuota } from "./codecs/usageQuota.ts";
6
6
  import { APICallName, Status } from "./consts.ts";
7
- import { ValStat } from "./valstat.ts";
7
+ import type { ValStat } from "./valstat.ts";
8
+ /** Identity seed brands — defined in seed.ts; re-exported for convenience. */
9
+ export type { MasterSeed, SealedMasterKey } from "./seed.ts";
10
+ export { asMasterSeed, asSealedMasterKey, MASTER_SEED_LEN, SEALED_MASTER_KEY_LEN, } from "./seed.ts";
8
11
  export type GroupID = string;
9
12
  declare const entityIDSymbol: unique symbol;
10
13
  export type EntityID = Uint8Array & {
@@ -98,15 +101,11 @@ export interface KeyPair {
98
101
  privateKey: PrivateKey;
99
102
  publicKey: PublicKey;
100
103
  }
101
- declare const masterSeedSymbol: unique symbol;
102
104
  declare const derivationSeedSymbol: unique symbol;
103
105
  declare const publicKeySymbol: unique symbol;
104
106
  declare const privateKeySymbol: unique symbol;
105
107
  declare const hostSpecificKeyPairSymbol: unique symbol;
106
108
  declare const hashSymbol: unique symbol;
107
- export type MasterSeed = Uint8Array & {
108
- readonly [masterSeedSymbol]: true;
109
- };
110
109
  export type DerivationSeed = Uint8Array & {
111
110
  readonly [derivationSeedSymbol]: true;
112
111
  };
@@ -214,4 +213,3 @@ export interface IUpsertMessage extends IMessage {
214
213
  export interface IDeleteMessage extends Omit<IMessage, "bod"> {
215
214
  len: 0;
216
215
  }
217
- export {};
@@ -0,0 +1,38 @@
1
+ import { Status } from "../consts.ts";
2
+ import { type ValStat } from "../valstat.ts";
3
+ export declare const WEBAUTHN_CHAL_LEN = 32;
4
+ /** COSE algorithm identifiers for WebAuthn `pubKeyCredParams`. */
5
+ export declare const COSE_ALG_ES256 = -7;
6
+ export declare const COSE_ALG_EDDSA = -8;
7
+ export declare const COSE_ALG_RS256 = -257;
8
+ /** Prefer modern curves; include RS256 for broader authenticator coverage. */
9
+ export declare const WEBAUTHN_PUB_KEY_PARAMS: PublicKeyCredentialParameters[];
10
+ export type WebAuthnRp = {
11
+ rpId?: string;
12
+ rpName?: string;
13
+ };
14
+ /**
15
+ * Default WebAuthn RP ID: the full hostname (no eTLD+1 collapse).
16
+ *
17
+ * Credentials are scoped to this exact host. That is deliberate and simple —
18
+ * no Public Suffix List, no multi-part-suffix guesses. Apps that want a shared
19
+ * RP across subdomains (e.g. `app.example.com` + `www.example.com` →
20
+ * `example.com`) must pass an explicit `rpId` that is a valid suffix of the
21
+ * origin (WebAuthn requires the RP ID equal the effective domain or a
22
+ * registrable suffix of it).
23
+ *
24
+ * Normalizes case and trailing dots. Empty → `"localhost"`.
25
+ */
26
+ export declare function defaultWebAuthnRpId(hostname: string): string;
27
+ /** Copy into a fresh ArrayBuffer-backed view (DOM BufferSource typing). */
28
+ export declare function copyToArrayBuffer(src: Uint8Array): Uint8Array;
29
+ /** Resolve RP id from opts or `location.hostname`. */
30
+ export declare function resolveWebAuthnRpId(opts?: WebAuthnRp): ValStat<string>;
31
+ /** Success if WebAuthn credentials API is available in this environment. */
32
+ export declare function checkWebAuthn(): Status;
33
+ export declare function asPublicKeyCredential(cred: Credential | null): ValStat<PublicKeyCredential>;
34
+ /**
35
+ * Best-effort capability probe (not all UAs expose this).
36
+ * `extKey` e.g. `"extension:largeBlob"`, `"extension:prf"`.
37
+ */
38
+ export declare function webAuthnExtensionCapable(extKey: string): Promise<boolean>;
@@ -0,0 +1,19 @@
1
+ import { Status } from "../consts.ts";
2
+ import { type ValStat } from "../valstat.ts";
3
+ import { type WebAuthnRp } from "./common.ts";
4
+ export type LargeBlobRp = WebAuthnRp;
5
+ export type LargeBlobCreateOpts = LargeBlobRp & {
6
+ userName?: string;
7
+ authenticatorAttachment?: string;
8
+ };
9
+ /** Best-effort capability probe. */
10
+ export declare function largeBlobCapable(): Promise<boolean>;
11
+ /** Create discoverable largeBlob-capable credential; returns credential id. */
12
+ export declare function largeBlobCreateCred(opts?: LargeBlobCreateOpts): Promise<ValStat<Uint8Array>>;
13
+ /** UV write of opaque bytes to largeBlob. */
14
+ export declare function largeBlobWrite(credId: Uint8Array, data: Uint8Array, opts?: LargeBlobRp): Promise<Status>;
15
+ /** UV read from largeBlob; `credId` undefined = discoverable assertion. */
16
+ export declare function largeBlobRead(credId: Uint8Array | undefined, opts?: LargeBlobRp): Promise<ValStat<{
17
+ blob: Uint8Array;
18
+ credId: Uint8Array;
19
+ }>>;
@@ -0,0 +1,33 @@
1
+ import { type ValStat } from "../valstat.ts";
2
+ import { type WebAuthnRp } from "./common.ts";
3
+ export type PrfRp = WebAuthnRp;
4
+ export type PrfCreateOpts = PrfRp & {
5
+ userName?: string;
6
+ authenticatorAttachment?: string;
7
+ };
8
+ /** Default salt for DIPLOMATIC PRF eval (UTF-8). */
9
+ export declare const DEFAULT_PRF_SALT: Uint8Array<ArrayBufferLike>;
10
+ /** Bytes of PRF output we consume (first N of `results.first`). */
11
+ export declare const PRF_OUTPUT_LEN = 32;
12
+ export type PrfCreateResult = {
13
+ credId: Uint8Array;
14
+ prfEnabled: boolean;
15
+ };
16
+ /** Internal: PRF output + cred id. For Enclave only — do not re-export to apps. */
17
+ export type PrfEvalResult = {
18
+ prf: Uint8Array;
19
+ credId: Uint8Array;
20
+ };
21
+ export type PrfEvalOpts = PrfRp & {
22
+ credId?: Uint8Array;
23
+ salt?: Uint8Array;
24
+ };
25
+ /** Best-effort: client advertises PRF extension. */
26
+ export declare function prfCapable(): Promise<boolean>;
27
+ /** Create a discoverable credential that enables PRF. */
28
+ export declare function createPrfCred(opts?: PrfCreateOpts): Promise<ValStat<PrfCreateResult>>;
29
+ /**
30
+ * Evaluate PRF (UV). Returns PRF bytes — only Enclave should call this and must
31
+ * zero/drop PRF after seal/unseal. Apps must not hold PRF alongside sealed meta.
32
+ */
33
+ export declare function evalPrf(opts?: PrfEvalOpts): Promise<ValStat<PrfEvalResult>>;
@@ -0,0 +1 @@
1
+ export declare const DIPLOMATIC_WORKER_SOURCE_B64 = "";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Construct a DIPLOMATIC sync Worker from the embedded bundle.
3
+ * Does not inject seed — use {@link Enclave.spawnSyncWorker} for that.
4
+ */
5
+ export declare function spawnDiplomaticSyncWorker(): Worker;
@@ -1,15 +1,33 @@
1
1
  import { Enclave } from "../../shared/crypto/enclave";
2
- import { MasterSeed } from "../../shared/types";
3
- import { ISeedStore, type SetSeedOpts } from "../../types";
2
+ import type { ICrypto } from "../../shared/types";
3
+ import type { ISeedStore, SetSeedOpts } from "../../types";
4
+ import { type PrfSeedMeta, PrfSeedStore, type PrfSeedStoreOpts } from "../../passkey/prf-store";
4
5
  export declare class IDBSeedStore implements ISeedStore {
6
+ #private;
5
7
  enclave?: Enclave;
6
8
  db: IDBDatabase;
7
- constructor(db: IDBDatabase);
8
- save(seed: MasterSeed, opts?: SetSeedOpts): Promise<Enclave>;
9
+ constructor(db: IDBDatabase, crypto: ICrypto);
10
+ /**
11
+ * Hold enclave in memory only. Durable identity is sealed meta
12
+ * ({@link openPrfStore} / {@link persistPrfMeta}), never plain seed.
13
+ * `opts.persist` is ignored (kept for API compatibility).
14
+ *
15
+ * TODO(passphrase): when PRF is missing, `persist: true` (or a dedicated
16
+ * wrap API) should seal under a passphrase KDF and write sealed meta here —
17
+ * same shape as prfMeta, not a return of raw seed.
18
+ */
19
+ save(enclave: Enclave, _opts?: SetSeedOpts): Promise<Enclave>;
9
20
  load(): Promise<Enclave | undefined>;
10
21
  /**
11
- * Explicit identity clear only. Not invoked by {@link IDBStore.wipe}
12
- * (that deletes the whole protocol DB, including legacy seedMeta).
22
+ * Drop in-memory enclave and any leftover legacy hex. Keeps {@link K_PRF_META}
23
+ * so daily unlock via PRF still works after a soft lock.
13
24
  */
25
+ clearSession(): Promise<void>;
14
26
  wipe(): Promise<void>;
27
+ loadPrfMeta(): Promise<PrfSeedMeta | undefined>;
28
+ persistPrfMeta(meta: PrfSeedMeta | undefined): Promise<void>;
29
+ hasPrfMeta(): Promise<boolean>;
30
+ openPrfStore(opts: Omit<PrfSeedStoreOpts, "crypto" | "persistMeta" | "meta"> & {
31
+ crypto?: ICrypto;
32
+ }): Promise<PrfSeedStore>;
15
33
  }
@@ -1,9 +1,8 @@
1
- import { Enclave } from "../../shared/crypto/enclave";
2
- import { MasterSeed } from "../../shared/types";
3
- import { ISeedStore, type SetSeedOpts } from "../../types";
1
+ import type { Enclave } from "../../shared/crypto/enclave";
2
+ import type { ISeedStore, SetSeedOpts } from "../../types";
4
3
  export declare class MemorySeedStore implements ISeedStore {
5
4
  enclave?: Enclave;
6
- save(seed: MasterSeed, _opts?: SetSeedOpts): Promise<Enclave>;
5
+ save(enclave: Enclave, _opts?: SetSeedOpts): Promise<Enclave>;
7
6
  load(): Promise<Enclave | undefined>;
8
7
  wipe(): Promise<void>;
9
8
  }
@@ -2,7 +2,7 @@ import type { SyncProgressEvent } from "./progress";
2
2
  import { Status } from "./shared/consts";
3
3
  import type { Enclave } from "./shared/crypto/enclave";
4
4
  import type { EncodedMessage } from "./shared/message";
5
- import type { EntityID, Hash, HostHandle, IDeleteParams, IEntRev, IHostConnectionInfo, IHostMetadata, IInsertParams, IMessageHead, IOp, IUpdateParams, MasterSeed, SerializedContent } from "./shared/types";
5
+ import type { EntityID, Hash, HostHandle, IDeleteParams, IEntRev, IHostConnectionInfo, IHostMetadata, IInsertParams, IMessageHead, IOp, IUpdateParams, SerializedContent } from "./shared/types";
6
6
  import { ValStat } from "./shared/valstat";
7
7
  import { ICrypto } from "./shared/types";
8
8
  export interface IMsgParts {
@@ -29,12 +29,17 @@ export type Applier = (ops: IOp[]) => Promise<{
29
29
  types: Set<string>;
30
30
  eids: EntityID[];
31
31
  }>;
32
- /** Options for loading a seed into the enclave. */
32
+ /** Options when installing a session {@link Enclave}. */
33
33
  export type SetSeedOpts = {
34
34
  /**
35
- * When true, write seed to the durable store (e.g. IndexedDB).
36
- * Default false (memory only) IDB is readable by other local apps.
37
- * Opt in only when the app accepts that tradeoff (or has no better store).
35
+ * When true, request durable storage **if the store supports a non-plaintext
36
+ * durable form** (e.g. passkey largeBlob as IdentityBundle wire).
37
+ * IDB holds the enclave in memory only durable identity is PRF-sealed meta
38
+ * via {@link IDBSeedStore.openPrfStore}, never plain seed.
39
+ * Default false (session enclave only).
40
+ *
41
+ * Future: when PRF is unavailable, durable path is passphrase-sealed meta
42
+ * (KDF + AEAD inside Enclave), not plain seed on disk.
38
43
  */
39
44
  persist?: boolean;
40
45
  };
@@ -60,7 +65,7 @@ export type WipeOpts = {
60
65
  seed?: boolean;
61
66
  };
62
67
  export interface ISeedStore {
63
- save: (seed: MasterSeed, opts?: SetSeedOpts) => Promise<Enclave>;
68
+ save: (enclave: Enclave, opts?: SetSeedOpts) => Promise<Enclave>;
64
69
  load: () => Promise<Enclave | void>;
65
70
  /**
66
71
  * Clear durable seed material for this store (IDB row, largeBlob overwrite, …)
@@ -285,7 +290,8 @@ export interface IStateEmitter<T> {
285
290
  listen(listener: (state: T) => void): UnlistenFunc;
286
291
  }
287
292
  export interface IClient<Handle extends HostHandle> {
288
- setSeed(seed: MasterSeed, opts?: SetSeedOpts): Promise<void>;
293
+ /** Install session enclave (master seed stays inside enclave). */
294
+ setSeed(enclave: Enclave, opts?: SetSeedOpts): Promise<void>;
289
295
  link(host: IHostConnectionInfo<Handle>): Promise<void>;
290
296
  unlink(label: string): Promise<void>;
291
297
  /** Linked hosts from the protocol store (handle, label, lastSeq, …). */
@@ -1,30 +1,18 @@
1
1
  import { IClock } from "../shared/clock";
2
2
  import { Status } from "../shared/consts";
3
- import type { EntityID, Hash, IDeleteParams, IEntRev, IHostConnectionInfo, IInsertParams, IMessageHead, IStateManager, IUpdateParams, MasterSeed, SerializedContent } from "../shared/types";
3
+ import type { Enclave } from "../shared/crypto/enclave";
4
+ import type { EntityID, Hash, IDeleteParams, IEntRev, IHostConnectionInfo, IInsertParams, IMessageHead, IStateManager, IUpdateParams, SerializedContent } from "../shared/types";
4
5
  import { type ValStat } from "../shared/valstat";
5
6
  import type { ApldState, IClient, IDiplomaticClientState, IDiplomaticClientXferState, IHostRow, IStateEmitter, IStore, IStoredMessage, ListMsgsOpts, ReconcileOpts, ReconcileReport, SetSeedOpts, WipeOpts } from "../types";
6
7
  /**
7
- * Options for attaching to an app-owned sync Worker.
8
- *
9
- * Provide a live `Worker` the library never constructs one. See
10
- * `openDiplomaticClient` for bundler/CDN instantiation recipes.
11
- *
12
- * Handshake is race-safe: the worker posts unsolicited `{ kind: "ready" }`, but
13
- * connect also probes with `ping`. Early construction (module scope) is fine even
14
- * if `ready` fired before `onmessage` was set — the ping still succeeds once the
15
- * worker has finished init (cmds are held until then on the worker side).
16
- *
17
- * After the ready barrier, connect hydrates `clientState` / `xferState` from the
18
- * shared main-thread store (and recovers via getClientState/getXferState if the
19
- * unsolicited events were dropped). Without that, the façade defaults to
20
- * `hasSeed: false` and apps flash the unauthenticated UI until a later event.
8
+ * Options for the worker-mode façade.
9
+ * Prefer {@link openDiplomaticClient} with `worker: true`.
10
+ * The sync Worker is created only inside {@link WorkerClient.setSeed}
11
+ * via {@link Enclave.spawnSyncWorker}.
21
12
  */
22
13
  export type WorkerClientOptions = {
23
- /** Already-constructed module Worker running `@interncom/diplomatic/worker`. */
24
- worker: Worker;
25
14
  /**
26
- * Max wait for handshake (`ready` event or probe ping; default 15s).
27
- * Failures throw; no fallback.
15
+ * Max wait for Enclave.spawnSyncWorker handshake (setSeed reply; default 15s).
28
16
  */
29
17
  readyTimeoutMs?: number;
30
18
  clock?: IClock;
@@ -35,6 +23,7 @@ export type WorkerClientOptions = {
35
23
  syncDebounceMs?: number;
36
24
  };
37
25
  export declare class WorkerClient implements IClient<URL> {
26
+ /** Set only by setSeed → Enclave.spawnSyncWorker. */
38
27
  private worker;
39
28
  private state;
40
29
  /** Shared protocol store (main connection). */
@@ -43,9 +32,8 @@ export declare class WorkerClient implements IClient<URL> {
43
32
  private local;
44
33
  private nextId;
45
34
  private pending;
46
- private ready;
47
- private resolveReady;
48
- private rejectReady;
35
+ /** Max wait for setSeed worker handshake. */
36
+ private readyTimeoutMs;
49
37
  /** Debounced local write → worker upload/sync. */
50
38
  private scheduledSync;
51
39
  private cachedClientState;
@@ -53,54 +41,44 @@ export declare class WorkerClient implements IClient<URL> {
53
41
  clientState: IStateEmitter<IDiplomaticClientState>;
54
42
  xferState: IStateEmitter<IDiplomaticClientXferState>;
55
43
  private constructor();
56
- private failReady;
44
+ /** Wire handlers; terminate any previous worker. */
45
+ private bindWorker;
46
+ /** Reject all in-flight RPC (worker death / init failure). */
47
+ private failPending;
57
48
  /**
58
49
  * Run any pending debounced worker sync now and await it.
59
50
  * Useful in tests (with real debounce) or after a burst of local writes.
60
51
  */
61
52
  flushScheduledSync(): Promise<void>;
62
53
  /**
63
- * Attach to an app-provided Worker. `store` is the shared protocol IDB (main
64
- * connection) used for local msg writes; worker opens its own connection.
65
- * Throws if the worker never becomes ready — does not fall back to main thread.
66
- *
67
- * Handshake: wait for unsolicited `ready` **or** a successful probe `ping`.
68
- * The probe covers the common case where the app started the Worker early and
69
- * `ready` was dropped before this thread set `onmessage`.
70
- *
71
- * Then hydrate client/xfer state from the shared store (and RPC) so
72
- * `clientState.get()` is correct before connect returns — unsolicited
73
- * `clientState` events are often lost when the Worker starts before
74
- * `onmessage` is attached.
54
+ * Open the worker-mode façade without spawning a Worker.
55
+ * Shared store hydrates client/xfer state; network starts after setSeed.
75
56
  */
76
- static connect(state: IStateManager, store: IStore<URL>, opts: WorkerClientOptions): Promise<WorkerClient>;
57
+ static open(state: IStateManager, store: IStore<URL>, opts?: WorkerClientOptions): Promise<WorkerClient>;
77
58
  /**
78
59
  * Snapshot seed/host/queues from the shared main-thread store.
79
60
  * `connected` stays false until the worker reports otherwise.
80
61
  */
81
62
  private hydrateStateFromStore;
82
63
  /**
83
- * Request current client/xfer state from the worker. Recovers when unsolicited
84
- * `clientState` / `xferState` events fired before `onmessage` was set.
64
+ * Request current client/xfer state from the worker (after setSeed).
85
65
  */
86
66
  private pullRemoteState;
87
- /** Resolve the ready barrier (idempotent). */
88
- private markReady;
89
- /**
90
- * Active handshake: post ping without waiting for the ready event.
91
- * On pong, mark ready so connect can proceed even if `ready` was missed.
92
- */
93
- private probeReady;
94
- /** Lightweight RPC check after connect. */
67
+ private requireWorker;
68
+ /** Lightweight RPC check after setSeed. */
95
69
  ping(): Promise<void>;
96
- /** Terminate the worker (drops protocol DB connection in that thread). */
70
+ /** Terminate the worker if any (drops protocol DB connection in that thread). */
97
71
  terminate(): void;
98
72
  private onMessage;
99
73
  private allocId;
100
74
  private request;
101
75
  private requestStatus;
102
76
  private serializeHost;
103
- setSeed(seed: MasterSeed, opts?: SetSeedOpts): Promise<void>;
77
+ /**
78
+ * Persist seed on main, then Enclave.spawnSyncWorker (only worker spawn path).
79
+ * Awaits the worker's setSeed reply (worker boots, then applies seed).
80
+ */
81
+ setSeed(enclave: Enclave, opts?: SetSeedOpts): Promise<void>;
104
82
  link(host: IHostConnectionInfo<URL>, connect?: boolean): Promise<void>;
105
83
  unlink(label: string): Promise<void>;
106
84
  /** Hosts live in shared main IDB (same as local writer). */