@aithos/sdk 0.1.0-alpha.3 → 0.1.0-alpha.5

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 (46) hide show
  1. package/dist/src/auth-api.d.ts +41 -0
  2. package/dist/src/auth-api.js +82 -0
  3. package/dist/src/auth.d.ts +114 -75
  4. package/dist/src/auth.js +553 -73
  5. package/dist/src/compute.d.ts +8 -6
  6. package/dist/src/compute.js +19 -11
  7. package/dist/src/ethos.d.ts +117 -1
  8. package/dist/src/ethos.js +417 -16
  9. package/dist/src/index.d.ts +8 -4
  10. package/dist/src/index.js +26 -8
  11. package/dist/src/internal/delegate-bundle.d.ts +18 -0
  12. package/dist/src/internal/delegate-bundle.js +89 -0
  13. package/dist/src/internal/delegate-state.d.ts +45 -0
  14. package/dist/src/internal/delegate-state.js +120 -0
  15. package/dist/src/internal/owner-signers.d.ts +78 -0
  16. package/dist/src/internal/owner-signers.js +179 -0
  17. package/dist/src/internal/protocol-client-bridge.d.ts +8 -0
  18. package/dist/src/internal/protocol-client-bridge.js +20 -0
  19. package/dist/src/internal/recovery-file.d.ts +29 -0
  20. package/dist/src/internal/recovery-file.js +98 -0
  21. package/dist/src/internal/signer.d.ts +59 -0
  22. package/dist/src/internal/signer.js +86 -0
  23. package/dist/src/key-store.d.ts +128 -0
  24. package/dist/src/key-store.js +244 -0
  25. package/dist/src/mandates.d.ts +88 -1
  26. package/dist/src/mandates.js +185 -8
  27. package/dist/src/sdk.d.ts +36 -3
  28. package/dist/src/sdk.js +27 -23
  29. package/dist/src/session-store.d.ts +58 -0
  30. package/dist/src/session-store.js +158 -0
  31. package/dist/src/wallet.d.ts +4 -6
  32. package/dist/src/wallet.js +18 -8
  33. package/dist/test/auth-j3.test.d.ts +2 -0
  34. package/dist/test/auth-j3.test.js +360 -0
  35. package/dist/test/compute.test.js +22 -11
  36. package/dist/test/ethos.test.d.ts +2 -0
  37. package/dist/test/ethos.test.js +219 -0
  38. package/dist/test/key-store.test.d.ts +2 -0
  39. package/dist/test/key-store.test.js +161 -0
  40. package/dist/test/mandates.test.d.ts +2 -0
  41. package/dist/test/mandates.test.js +93 -0
  42. package/dist/test/sdk.test.js +64 -30
  43. package/dist/test/signer.test.d.ts +2 -0
  44. package/dist/test/signer.test.js +117 -0
  45. package/dist/test/wallet.test.js +20 -9
  46. package/package.json +4 -3
@@ -0,0 +1,98 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ import { AithosSDKError } from "../types.js";
4
+ const HEX_64 = /^[0-9a-f]{64}$/;
5
+ /**
6
+ * Parse a recovery JSON string. Throws {@link AithosSDKError} with
7
+ * `code === "auth_invalid_recovery_file"` if the input is malformed.
8
+ */
9
+ export function parseRecoveryFile(text) {
10
+ let obj;
11
+ try {
12
+ obj = JSON.parse(text);
13
+ }
14
+ catch {
15
+ throw bad("not valid JSON");
16
+ }
17
+ if (typeof obj !== "object" || obj === null) {
18
+ throw bad("not a JSON object");
19
+ }
20
+ const o = obj;
21
+ const ver = o["aithos_recovery_version"];
22
+ if (typeof ver !== "string" || !ver.startsWith("0.1.0")) {
23
+ throw bad(`unsupported aithos_recovery_version: ${String(ver)}`);
24
+ }
25
+ // Both shapes use snake_case for `display_name` and `seeds_hex`.
26
+ const handle = o["handle"];
27
+ const displayName = o["display_name"];
28
+ const did = o["did"];
29
+ const seedsRaw = o["seeds_hex"];
30
+ if (typeof handle !== "string" || !handle)
31
+ throw bad("missing handle");
32
+ if (typeof displayName !== "string")
33
+ throw bad("missing display_name");
34
+ if (typeof did !== "string" || !did.startsWith("did:")) {
35
+ throw bad("missing or malformed did");
36
+ }
37
+ if (typeof seedsRaw !== "object" || seedsRaw === null) {
38
+ throw bad("missing seeds_hex");
39
+ }
40
+ const seeds = seedsRaw;
41
+ for (const k of ["root", "public", "circle", "self"]) {
42
+ const v = seeds[k];
43
+ if (typeof v !== "string" || !HEX_64.test(v)) {
44
+ throw bad(`seeds_hex.${k}: expected 64-char lowercase hex`);
45
+ }
46
+ }
47
+ return {
48
+ handle,
49
+ displayName,
50
+ did,
51
+ seedsHex: {
52
+ root: seeds["root"],
53
+ public: seeds["public"],
54
+ circle: seeds["circle"],
55
+ self: seeds["self"],
56
+ },
57
+ };
58
+ }
59
+ /** Read the file (Blob or already-decoded string) into UTF-8 text. */
60
+ export async function readRecoveryFileText(file) {
61
+ if (typeof file === "string")
62
+ return file;
63
+ return file.text();
64
+ }
65
+ /**
66
+ * Build the recovery-file JSON blob from a fresh BrowserIdentity. Used
67
+ * by `signUp` so the same writer/reader code handles both ends of the
68
+ * round-trip.
69
+ */
70
+ export function serializeRecoveryFile(identity) {
71
+ const payload = {
72
+ aithos_recovery_version: "0.1.0-plaintext",
73
+ handle: identity.handle,
74
+ display_name: identity.displayName,
75
+ did: identity.did,
76
+ seeds_hex: {
77
+ root: bytesToHex(identity.root.seed),
78
+ public: bytesToHex(identity.public.seed),
79
+ circle: bytesToHex(identity.circle.seed),
80
+ self: bytesToHex(identity.self.seed),
81
+ },
82
+ saved_at: new Date().toISOString(),
83
+ };
84
+ return {
85
+ text: JSON.stringify(payload, null, 2),
86
+ filename: `aithos-recovery-${identity.handle}.json`,
87
+ };
88
+ }
89
+ function bad(detail) {
90
+ return new AithosSDKError("auth_invalid_recovery_file", `recovery file is invalid: ${detail}`);
91
+ }
92
+ function bytesToHex(b) {
93
+ let out = "";
94
+ for (let i = 0; i < b.length; i++)
95
+ out += b[i].toString(16).padStart(2, "0");
96
+ return out;
97
+ }
98
+ //# sourceMappingURL=recovery-file.js.map
@@ -0,0 +1,59 @@
1
+ import { type KeyPair } from "@aithos/protocol-client";
2
+ /**
3
+ * Capability to produce Ed25519 signatures over a fixed key. The key
4
+ * material itself is not exposed — only the public key (which is, by
5
+ * definition, public) and the {@link sign} method.
6
+ *
7
+ * `sign` returns a Promise<Uint8Array> even when the underlying
8
+ * implementation is synchronous (e.g. {@link RawSeedSigner}) so future
9
+ * implementations backed by `crypto.subtle.sign` can drop in without
10
+ * breaking callers.
11
+ */
12
+ export interface Signer {
13
+ /** 32-byte Ed25519 public key. */
14
+ readonly publicKey: Uint8Array;
15
+ /** Sign `message` and return the 64-byte Ed25519 signature. */
16
+ sign(message: Uint8Array): Promise<Uint8Array>;
17
+ /** Zeroize any private material the signer holds. Idempotent. */
18
+ destroy(): void;
19
+ }
20
+ /**
21
+ * Today's implementation: wraps a raw 32-byte Ed25519 seed and signs
22
+ * via `@noble/ed25519`. Holds the seed in a defensively-copied
23
+ * Uint8Array bound to a private field, so mutating the constructor
24
+ * input afterwards does not affect the signer.
25
+ *
26
+ * Migration note: when we move to Web Crypto non-extractable keys,
27
+ * this class is replaced by a `SubtleSigner` that holds a `CryptoKey`
28
+ * reference and calls `crypto.subtle.sign("Ed25519", key, message)`.
29
+ * The {@link Signer} interface stays the same, so all callers (the
30
+ * SessionVault, the auth namespace, the ethos publish path) keep
31
+ * working unchanged.
32
+ */
33
+ export declare class RawSeedSigner implements Signer {
34
+ #private;
35
+ readonly publicKey: Uint8Array;
36
+ /**
37
+ * @param seed 32-byte Ed25519 seed (the private half).
38
+ * @param publicKey 32-byte Ed25519 public key matching `seed`.
39
+ * Both arrays are defensively copied — the caller may zeroize their
40
+ * originals immediately.
41
+ */
42
+ constructor(seed: Uint8Array, publicKey: Uint8Array);
43
+ sign(message: Uint8Array): Promise<Uint8Array>;
44
+ destroy(): void;
45
+ /**
46
+ * Internal escape hatch for protocol-client interop. Returns a KeyPair
47
+ * usable with `buildSignedEnvelope({ signer })` and friends, which
48
+ * today take a raw seed. Marked `_unsafe` because it surfaces the
49
+ * private seed bytes — only callers within `@aithos/sdk` should use
50
+ * it, and never propagate the result outside the SDK boundary.
51
+ *
52
+ * When protocol-client gains a Signer-shaped API (post-alpha), this
53
+ * method goes away and SubtleSigner becomes pluggable upstream.
54
+ *
55
+ * @internal
56
+ */
57
+ _unsafeKeyPair(): KeyPair;
58
+ }
59
+ //# sourceMappingURL=signer.d.ts.map
@@ -0,0 +1,86 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ // Internal Signer abstraction.
4
+ //
5
+ // SDK-internal only — NOT exported from the package barrel. The whole
6
+ // point of this file is to draw the line between "what the SDK consumer
7
+ // sees" (no seeds, no key bytes, just verbs like `addSection` and
8
+ // `publish`) and "how the SDK signs internally" (today: raw Ed25519
9
+ // seeds via @noble; tomorrow: non-extractable CryptoKeys via
10
+ // crypto.subtle).
11
+ //
12
+ // The {@link Signer} interface is the contract. {@link RawSeedSigner}
13
+ // is today's implementation. When we migrate to Web Crypto
14
+ // non-extractable keys, a {@link SubtleSigner} drops in beside it
15
+ // implementing the same interface, and nothing visible to the SDK
16
+ // consumer changes.
17
+ //
18
+ // `sign` is async-shaped from the start so the migration to
19
+ // `crypto.subtle.sign` (which is async) doesn't ripple through the
20
+ // caller graph as a breaking interface change.
21
+ import { sign as ed25519Sign } from "@aithos/protocol-client";
22
+ /**
23
+ * Today's implementation: wraps a raw 32-byte Ed25519 seed and signs
24
+ * via `@noble/ed25519`. Holds the seed in a defensively-copied
25
+ * Uint8Array bound to a private field, so mutating the constructor
26
+ * input afterwards does not affect the signer.
27
+ *
28
+ * Migration note: when we move to Web Crypto non-extractable keys,
29
+ * this class is replaced by a `SubtleSigner` that holds a `CryptoKey`
30
+ * reference and calls `crypto.subtle.sign("Ed25519", key, message)`.
31
+ * The {@link Signer} interface stays the same, so all callers (the
32
+ * SessionVault, the auth namespace, the ethos publish path) keep
33
+ * working unchanged.
34
+ */
35
+ export class RawSeedSigner {
36
+ publicKey;
37
+ #seed;
38
+ #destroyed = false;
39
+ /**
40
+ * @param seed 32-byte Ed25519 seed (the private half).
41
+ * @param publicKey 32-byte Ed25519 public key matching `seed`.
42
+ * Both arrays are defensively copied — the caller may zeroize their
43
+ * originals immediately.
44
+ */
45
+ constructor(seed, publicKey) {
46
+ if (seed.length !== 32) {
47
+ throw new Error(`RawSeedSigner: seed must be 32 bytes, got ${seed.length}`);
48
+ }
49
+ if (publicKey.length !== 32) {
50
+ throw new Error(`RawSeedSigner: publicKey must be 32 bytes, got ${publicKey.length}`);
51
+ }
52
+ this.#seed = new Uint8Array(seed);
53
+ this.publicKey = new Uint8Array(publicKey);
54
+ }
55
+ async sign(message) {
56
+ if (this.#destroyed) {
57
+ throw new Error("RawSeedSigner: cannot sign with a destroyed signer");
58
+ }
59
+ return ed25519Sign(message, this.#seed);
60
+ }
61
+ destroy() {
62
+ if (this.#destroyed)
63
+ return;
64
+ this.#seed.fill(0);
65
+ this.#destroyed = true;
66
+ }
67
+ /**
68
+ * Internal escape hatch for protocol-client interop. Returns a KeyPair
69
+ * usable with `buildSignedEnvelope({ signer })` and friends, which
70
+ * today take a raw seed. Marked `_unsafe` because it surfaces the
71
+ * private seed bytes — only callers within `@aithos/sdk` should use
72
+ * it, and never propagate the result outside the SDK boundary.
73
+ *
74
+ * When protocol-client gains a Signer-shaped API (post-alpha), this
75
+ * method goes away and SubtleSigner becomes pluggable upstream.
76
+ *
77
+ * @internal
78
+ */
79
+ _unsafeKeyPair() {
80
+ if (this.#destroyed) {
81
+ throw new Error("RawSeedSigner: cannot use a destroyed signer");
82
+ }
83
+ return { seed: this.#seed, publicKey: this.publicKey };
84
+ }
85
+ }
86
+ //# sourceMappingURL=signer.js.map
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Stored owner identity material. Treat as opaque from the consumer's
3
+ * point of view — its internal shape may evolve between SDK versions
4
+ * (e.g. when we migrate to `CryptoKey` references). Custom KeyStore
5
+ * implementations should round-trip the value as-is rather than
6
+ * peeking inside.
7
+ *
8
+ * @public
9
+ */
10
+ export interface StoredOwnerKeys {
11
+ /** Schema version. Today: `"0.1.0-hex"`. */
12
+ readonly version: "0.1.0-hex";
13
+ readonly did: string;
14
+ readonly handle: string;
15
+ readonly displayName: string;
16
+ /** Hex-encoded 32-byte Ed25519 seeds — one per sphere. */
17
+ readonly seedsHex: {
18
+ readonly root: string;
19
+ readonly public: string;
20
+ readonly circle: string;
21
+ readonly self: string;
22
+ };
23
+ /** ISO-8601 timestamp of the original save. Informational. */
24
+ readonly savedAt: string;
25
+ }
26
+ /**
27
+ * Stored delegate session material. Opaque from the consumer's
28
+ * point of view; see {@link StoredOwnerKeys}.
29
+ *
30
+ * @public
31
+ */
32
+ export interface StoredDelegateKeys {
33
+ readonly version: "0.1.0-hex";
34
+ /** DID of the subject whose ethos this mandate authorizes. */
35
+ readonly subjectDid: string;
36
+ /** Mandate id — used as the storage key. */
37
+ readonly mandateId: string;
38
+ /** Full §4.2 SignedMandate, stored as opaque JSON. */
39
+ readonly mandate: Record<string, unknown>;
40
+ /** Grantee URN, e.g. `urn:aithos:agent:bob1`. */
41
+ readonly granteeId: string;
42
+ /** Multibase-encoded Ed25519 grantee pubkey, matches `mandate.grantee.pubkey`. */
43
+ readonly granteePubkeyMultibase: string;
44
+ /** Hex-encoded 32-byte delegate Ed25519 seed. */
45
+ readonly delegateSeedHex: string;
46
+ readonly importedAt: string;
47
+ }
48
+ /**
49
+ * Persistence backend for owner identity material and delegate
50
+ * bundles. Methods are async because an IndexedDB-backed
51
+ * implementation is the default and IDB's API is async — sync stores
52
+ * are still trivial to write (`memoryKeyStore`) by returning
53
+ * pre-resolved promises.
54
+ *
55
+ * Implementations should never throw on missing records: a fresh
56
+ * device returns `null` from `loadOwner` and `[]` from
57
+ * `listDelegates`. They should only throw on hard I/O failures
58
+ * (quota exceeded, schema corruption). The SDK treats throws as
59
+ * "store unavailable, fall through to re-auth".
60
+ *
61
+ * @public
62
+ */
63
+ export interface AithosKeyStore {
64
+ loadOwner(): Promise<StoredOwnerKeys | null>;
65
+ saveOwner(owner: StoredOwnerKeys): Promise<void>;
66
+ clearOwner(): Promise<void>;
67
+ listDelegates(): Promise<readonly StoredDelegateKeys[]>;
68
+ loadDelegate(mandateId: string): Promise<StoredDelegateKeys | null>;
69
+ saveDelegate(d: StoredDelegateKeys): Promise<void>;
70
+ removeDelegate(mandateId: string): Promise<void>;
71
+ clearAllDelegates(): Promise<void>;
72
+ }
73
+ /**
74
+ * In-memory key store. Loses everything on page reload — useful for
75
+ * tests, SSR, ephemeral CLI tools, and any workflow where you want a
76
+ * deliberately short-lived session.
77
+ *
78
+ * @public
79
+ */
80
+ export declare function memoryKeyStore(): AithosKeyStore;
81
+ /**
82
+ * Default IndexedDB database name used by {@link indexedDbKeyStore}.
83
+ * Apps that want to coexist with other Aithos-aware libs (or scope
84
+ * sessions per-tenant) can pass a custom `dbName`.
85
+ */
86
+ export declare const DEFAULT_KEYSTORE_DB_NAME = "aithos-sdk-keys";
87
+ interface IndexedDbKeyStoreOptions {
88
+ /** Database name. Defaults to {@link DEFAULT_KEYSTORE_DB_NAME}. */
89
+ readonly dbName?: string;
90
+ /**
91
+ * Inject a non-default IDBFactory — used by tests. Apps should not
92
+ * pass this; the global `indexedDB` is the right choice everywhere
93
+ * else.
94
+ *
95
+ * @internal
96
+ */
97
+ readonly factory?: IDBFactory;
98
+ }
99
+ /**
100
+ * IndexedDB-backed key store. Default in browser environments —
101
+ * persists across reloads and browser restarts, scoped to the
102
+ * page's origin.
103
+ *
104
+ * Storage shape today:
105
+ *
106
+ * - object store `owner` — single record at key `"self"`,
107
+ * shape {@link StoredOwnerKeys}
108
+ * - object store `delegates` — keyPath `mandateId`, shape
109
+ * {@link StoredDelegateKeys}
110
+ *
111
+ * Both stores live in DB `aithos-sdk-keys` (configurable via
112
+ * `dbName`) at version 1. Future versions will trigger an upgrade
113
+ * transaction that migrates records into the new shape.
114
+ *
115
+ * @public
116
+ */
117
+ export declare function indexedDbKeyStore(opts?: IndexedDbKeyStoreOptions): AithosKeyStore;
118
+ /**
119
+ * Pick a sensible default: {@link indexedDbKeyStore} when an IDBFactory
120
+ * is reachable (browser environments), {@link memoryKeyStore} otherwise
121
+ * (Node, edge runtimes — apps running there should pass their own
122
+ * keystore explicitly).
123
+ *
124
+ * @public
125
+ */
126
+ export declare function defaultKeyStore(): AithosKeyStore;
127
+ export {};
128
+ //# sourceMappingURL=key-store.d.ts.map
@@ -0,0 +1,244 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ /* -------------------------------------------------------------------------- */
4
+ /* In-memory implementation */
5
+ /* -------------------------------------------------------------------------- */
6
+ /**
7
+ * In-memory key store. Loses everything on page reload — useful for
8
+ * tests, SSR, ephemeral CLI tools, and any workflow where you want a
9
+ * deliberately short-lived session.
10
+ *
11
+ * @public
12
+ */
13
+ export function memoryKeyStore() {
14
+ let owner = null;
15
+ const delegates = new Map();
16
+ return {
17
+ async loadOwner() {
18
+ return owner;
19
+ },
20
+ async saveOwner(o) {
21
+ owner = o;
22
+ },
23
+ async clearOwner() {
24
+ owner = null;
25
+ },
26
+ async listDelegates() {
27
+ return [...delegates.values()];
28
+ },
29
+ async loadDelegate(mandateId) {
30
+ return delegates.get(mandateId) ?? null;
31
+ },
32
+ async saveDelegate(d) {
33
+ delegates.set(d.mandateId, d);
34
+ },
35
+ async removeDelegate(mandateId) {
36
+ delegates.delete(mandateId);
37
+ },
38
+ async clearAllDelegates() {
39
+ delegates.clear();
40
+ },
41
+ };
42
+ }
43
+ /* -------------------------------------------------------------------------- */
44
+ /* IndexedDB implementation */
45
+ /* -------------------------------------------------------------------------- */
46
+ /**
47
+ * Default IndexedDB database name used by {@link indexedDbKeyStore}.
48
+ * Apps that want to coexist with other Aithos-aware libs (or scope
49
+ * sessions per-tenant) can pass a custom `dbName`.
50
+ */
51
+ export const DEFAULT_KEYSTORE_DB_NAME = "aithos-sdk-keys";
52
+ const STORE_OWNER = "owner";
53
+ const STORE_DELEGATES = "delegates";
54
+ /** The single owner record always lives at this key — the store has no keyPath. */
55
+ const OWNER_KEY = "self";
56
+ /**
57
+ * IndexedDB-backed key store. Default in browser environments —
58
+ * persists across reloads and browser restarts, scoped to the
59
+ * page's origin.
60
+ *
61
+ * Storage shape today:
62
+ *
63
+ * - object store `owner` — single record at key `"self"`,
64
+ * shape {@link StoredOwnerKeys}
65
+ * - object store `delegates` — keyPath `mandateId`, shape
66
+ * {@link StoredDelegateKeys}
67
+ *
68
+ * Both stores live in DB `aithos-sdk-keys` (configurable via
69
+ * `dbName`) at version 1. Future versions will trigger an upgrade
70
+ * transaction that migrates records into the new shape.
71
+ *
72
+ * @public
73
+ */
74
+ export function indexedDbKeyStore(opts = {}) {
75
+ const dbName = opts.dbName ?? DEFAULT_KEYSTORE_DB_NAME;
76
+ const factory = opts.factory ??
77
+ (typeof indexedDB !== "undefined" ? indexedDB : undefined);
78
+ if (!factory) {
79
+ throw new Error("indexedDbKeyStore: no IDBFactory available — pass `factory` for non-browser environments or use memoryKeyStore() instead.");
80
+ }
81
+ const open = () => new Promise((resolve, reject) => {
82
+ const req = factory.open(dbName, 1);
83
+ req.onupgradeneeded = () => {
84
+ const db = req.result;
85
+ if (!db.objectStoreNames.contains(STORE_OWNER)) {
86
+ db.createObjectStore(STORE_OWNER);
87
+ }
88
+ if (!db.objectStoreNames.contains(STORE_DELEGATES)) {
89
+ db.createObjectStore(STORE_DELEGATES, { keyPath: "mandateId" });
90
+ }
91
+ };
92
+ req.onsuccess = () => resolve(req.result);
93
+ req.onerror = () => reject(req.error ?? new Error("indexedDB.open failed"));
94
+ });
95
+ const tx = async (stores, mode, run) => {
96
+ const db = await open();
97
+ try {
98
+ const t = db.transaction([...stores], mode);
99
+ const result = await Promise.resolve(run(t));
100
+ await new Promise((resolve, reject) => {
101
+ t.oncomplete = () => resolve();
102
+ t.onerror = () => reject(t.error ?? new Error("transaction failed"));
103
+ t.onabort = () => reject(t.error ?? new Error("transaction aborted"));
104
+ });
105
+ return result;
106
+ }
107
+ finally {
108
+ db.close();
109
+ }
110
+ };
111
+ const reqAsPromise = (req) => new Promise((resolve, reject) => {
112
+ req.onsuccess = () => resolve(req.result);
113
+ req.onerror = () => reject(req.error ?? new Error("IDBRequest failed"));
114
+ });
115
+ return {
116
+ async loadOwner() {
117
+ return tx([STORE_OWNER], "readonly", async (t) => {
118
+ const v = await reqAsPromise(t.objectStore(STORE_OWNER).get(OWNER_KEY));
119
+ return validOwnerOrNull(v);
120
+ });
121
+ },
122
+ async saveOwner(owner) {
123
+ await tx([STORE_OWNER], "readwrite", (t) => {
124
+ t.objectStore(STORE_OWNER).put(owner, OWNER_KEY);
125
+ });
126
+ },
127
+ async clearOwner() {
128
+ await tx([STORE_OWNER], "readwrite", (t) => {
129
+ t.objectStore(STORE_OWNER).delete(OWNER_KEY);
130
+ });
131
+ },
132
+ async listDelegates() {
133
+ return tx([STORE_DELEGATES], "readonly", async (t) => {
134
+ const all = await reqAsPromise(t.objectStore(STORE_DELEGATES).getAll());
135
+ return all
136
+ .map(validDelegateOrNull)
137
+ .filter((d) => d !== null);
138
+ });
139
+ },
140
+ async loadDelegate(mandateId) {
141
+ return tx([STORE_DELEGATES], "readonly", async (t) => {
142
+ const v = await reqAsPromise(t.objectStore(STORE_DELEGATES).get(mandateId));
143
+ return validDelegateOrNull(v);
144
+ });
145
+ },
146
+ async saveDelegate(d) {
147
+ await tx([STORE_DELEGATES], "readwrite", (t) => {
148
+ t.objectStore(STORE_DELEGATES).put(d);
149
+ });
150
+ },
151
+ async removeDelegate(mandateId) {
152
+ await tx([STORE_DELEGATES], "readwrite", (t) => {
153
+ t.objectStore(STORE_DELEGATES).delete(mandateId);
154
+ });
155
+ },
156
+ async clearAllDelegates() {
157
+ await tx([STORE_DELEGATES], "readwrite", (t) => {
158
+ t.objectStore(STORE_DELEGATES).clear();
159
+ });
160
+ },
161
+ };
162
+ }
163
+ /* -------------------------------------------------------------------------- */
164
+ /* Default picker */
165
+ /* -------------------------------------------------------------------------- */
166
+ /**
167
+ * Pick a sensible default: {@link indexedDbKeyStore} when an IDBFactory
168
+ * is reachable (browser environments), {@link memoryKeyStore} otherwise
169
+ * (Node, edge runtimes — apps running there should pass their own
170
+ * keystore explicitly).
171
+ *
172
+ * @public
173
+ */
174
+ export function defaultKeyStore() {
175
+ if (typeof indexedDB !== "undefined") {
176
+ try {
177
+ return indexedDbKeyStore();
178
+ }
179
+ catch {
180
+ return memoryKeyStore();
181
+ }
182
+ }
183
+ return memoryKeyStore();
184
+ }
185
+ /* -------------------------------------------------------------------------- */
186
+ /* Runtime validators */
187
+ /* */
188
+ /* Storage values come from JSON-shaped records that may have been written */
189
+ /* by an older SDK version, by a corrupted process, or by an attacker with */
190
+ /* IDB write access. The SDK trusts only records that round-trip through */
191
+ /* these validators; anything else is treated as "no record" so the resume */
192
+ /* path falls through to re-auth. */
193
+ /* -------------------------------------------------------------------------- */
194
+ function validOwnerOrNull(v) {
195
+ if (typeof v !== "object" || v === null)
196
+ return null;
197
+ const o = v;
198
+ if (o["version"] !== "0.1.0-hex")
199
+ return null;
200
+ if (typeof o["did"] !== "string")
201
+ return null;
202
+ if (typeof o["handle"] !== "string")
203
+ return null;
204
+ if (typeof o["displayName"] !== "string")
205
+ return null;
206
+ const seeds = o["seedsHex"];
207
+ if (typeof seeds !== "object" || seeds === null)
208
+ return null;
209
+ const s = seeds;
210
+ for (const k of ["root", "public", "circle", "self"]) {
211
+ if (typeof s[k] !== "string")
212
+ return null;
213
+ if (s[k].length !== 64)
214
+ return null;
215
+ }
216
+ if (typeof o["savedAt"] !== "string")
217
+ return null;
218
+ return o;
219
+ }
220
+ function validDelegateOrNull(v) {
221
+ if (typeof v !== "object" || v === null)
222
+ return null;
223
+ const o = v;
224
+ if (o["version"] !== "0.1.0-hex")
225
+ return null;
226
+ if (typeof o["subjectDid"] !== "string")
227
+ return null;
228
+ if (typeof o["mandateId"] !== "string")
229
+ return null;
230
+ if (typeof o["granteeId"] !== "string")
231
+ return null;
232
+ if (typeof o["granteePubkeyMultibase"] !== "string")
233
+ return null;
234
+ if (typeof o["delegateSeedHex"] !== "string")
235
+ return null;
236
+ if (o["delegateSeedHex"].length !== 64)
237
+ return null;
238
+ if (typeof o["mandate"] !== "object" || o["mandate"] === null)
239
+ return null;
240
+ if (typeof o["importedAt"] !== "string")
241
+ return null;
242
+ return o;
243
+ }
244
+ //# sourceMappingURL=key-store.js.map