@openvtc/pnm-core 0.1.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/README.md +129 -0
- package/dist/did/derive-signing-key.d.ts +19 -0
- package/dist/did/derive-signing-key.d.ts.map +1 -0
- package/dist/did/derive-signing-key.js +96 -0
- package/dist/did/derive-signing-key.js.map +1 -0
- package/dist/did/index.d.ts +5 -0
- package/dist/did/index.d.ts.map +1 -0
- package/dist/did/index.js +5 -0
- package/dist/did/index.js.map +1 -0
- package/dist/did/peer.d.ts +37 -0
- package/dist/did/peer.d.ts.map +1 -0
- package/dist/did/peer.js +49 -0
- package/dist/did/peer.js.map +1 -0
- package/dist/did/verification-method.d.ts +43 -0
- package/dist/did/verification-method.d.ts.map +1 -0
- package/dist/did/verification-method.js +32 -0
- package/dist/did/verification-method.js.map +1 -0
- package/dist/did/verify.d.ts +49 -0
- package/dist/did/verify.d.ts.map +1 -0
- package/dist/did/verify.js +89 -0
- package/dist/did/verify.js.map +1 -0
- package/dist/didcomm/index.d.ts +235 -0
- package/dist/didcomm/index.d.ts.map +1 -0
- package/dist/didcomm/index.js +415 -0
- package/dist/didcomm/index.js.map +1 -0
- package/dist/inbound/confirm.d.ts +50 -0
- package/dist/inbound/confirm.d.ts.map +1 -0
- package/dist/inbound/confirm.js +64 -0
- package/dist/inbound/confirm.js.map +1 -0
- package/dist/inbound/dedup.d.ts +9 -0
- package/dist/inbound/dedup.d.ts.map +1 -0
- package/dist/inbound/dedup.js +31 -0
- package/dist/inbound/dedup.js.map +1 -0
- package/dist/inbound/index.d.ts +3 -0
- package/dist/inbound/index.d.ts.map +1 -0
- package/dist/inbound/index.js +3 -0
- package/dist/inbound/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/onboarding/index.d.ts +2 -0
- package/dist/onboarding/index.d.ts.map +1 -0
- package/dist/onboarding/index.js +2 -0
- package/dist/onboarding/index.js.map +1 -0
- package/dist/onboarding/swap.d.ts +60 -0
- package/dist/onboarding/swap.d.ts.map +1 -0
- package/dist/onboarding/swap.js +148 -0
- package/dist/onboarding/swap.js.map +1 -0
- package/dist/provision/adopt.d.ts +31 -0
- package/dist/provision/adopt.d.ts.map +1 -0
- package/dist/provision/adopt.js +114 -0
- package/dist/provision/adopt.js.map +1 -0
- package/dist/provision/armor.d.ts +19 -0
- package/dist/provision/armor.d.ts.map +1 -0
- package/dist/provision/armor.js +243 -0
- package/dist/provision/armor.js.map +1 -0
- package/dist/provision/crc24.d.ts +5 -0
- package/dist/provision/crc24.d.ts.map +1 -0
- package/dist/provision/crc24.js +30 -0
- package/dist/provision/crc24.js.map +1 -0
- package/dist/provision/hpke.d.ts +17 -0
- package/dist/provision/hpke.d.ts.map +1 -0
- package/dist/provision/hpke.js +60 -0
- package/dist/provision/hpke.js.map +1 -0
- package/dist/provision/index.d.ts +10 -0
- package/dist/provision/index.d.ts.map +1 -0
- package/dist/provision/index.js +16 -0
- package/dist/provision/index.js.map +1 -0
- package/dist/provision/open.d.ts +28 -0
- package/dist/provision/open.d.ts.map +1 -0
- package/dist/provision/open.js +224 -0
- package/dist/provision/open.js.map +1 -0
- package/dist/provision/request.d.ts +65 -0
- package/dist/provision/request.d.ts.map +1 -0
- package/dist/provision/request.js +53 -0
- package/dist/provision/request.js.map +1 -0
- package/dist/provision/run.d.ts +76 -0
- package/dist/provision/run.d.ts.map +1 -0
- package/dist/provision/run.js +110 -0
- package/dist/provision/run.js.map +1 -0
- package/dist/provision/send.d.ts +85 -0
- package/dist/provision/send.d.ts.map +1 -0
- package/dist/provision/send.js +87 -0
- package/dist/provision/send.js.map +1 -0
- package/dist/provision/types.d.ts +110 -0
- package/dist/provision/types.d.ts.map +1 -0
- package/dist/provision/types.js +17 -0
- package/dist/provision/types.js.map +1 -0
- package/dist/rp-login/didcomm.d.ts +34 -0
- package/dist/rp-login/didcomm.d.ts.map +1 -0
- package/dist/rp-login/didcomm.js +72 -0
- package/dist/rp-login/didcomm.js.map +1 -0
- package/dist/rp-login/index.d.ts +3 -0
- package/dist/rp-login/index.d.ts.map +1 -0
- package/dist/rp-login/index.js +3 -0
- package/dist/rp-login/index.js.map +1 -0
- package/dist/rp-login/step-up.d.ts +43 -0
- package/dist/rp-login/step-up.d.ts.map +1 -0
- package/dist/rp-login/step-up.js +118 -0
- package/dist/rp-login/step-up.js.map +1 -0
- package/dist/siop/index.d.ts +3 -0
- package/dist/siop/index.d.ts.map +1 -0
- package/dist/siop/index.js +3 -0
- package/dist/siop/index.js.map +1 -0
- package/dist/siop/login-client.d.ts +29 -0
- package/dist/siop/login-client.d.ts.map +1 -0
- package/dist/siop/login-client.js +79 -0
- package/dist/siop/login-client.js.map +1 -0
- package/dist/siop/self-issued.d.ts +96 -0
- package/dist/siop/self-issued.d.ts.map +1 -0
- package/dist/siop/self-issued.js +162 -0
- package/dist/siop/self-issued.js.map +1 -0
- package/dist/store/holder-identity.d.ts +241 -0
- package/dist/store/holder-identity.d.ts.map +1 -0
- package/dist/store/holder-identity.js +441 -0
- package/dist/store/holder-identity.js.map +1 -0
- package/dist/store/index.d.ts +4 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +4 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/kv-store.d.ts +51 -0
- package/dist/store/kv-store.d.ts.map +1 -0
- package/dist/store/kv-store.js +100 -0
- package/dist/store/kv-store.js.map +1 -0
- package/dist/store/secret-wrap.d.ts +109 -0
- package/dist/store/secret-wrap.d.ts.map +1 -0
- package/dist/store/secret-wrap.js +85 -0
- package/dist/store/secret-wrap.js.map +1 -0
- package/dist/trust-tasks/index.d.ts +2 -0
- package/dist/trust-tasks/index.d.ts.map +1 -0
- package/dist/trust-tasks/index.js +2 -0
- package/dist/trust-tasks/index.js.map +1 -0
- package/dist/trust-tasks/sign.d.ts +31 -0
- package/dist/trust-tasks/sign.d.ts.map +1 -0
- package/dist/trust-tasks/sign.js +141 -0
- package/dist/trust-tasks/sign.js.map +1 -0
- package/dist/util/timing.d.ts +14 -0
- package/dist/util/timing.d.ts.map +1 -0
- package/dist/util/timing.js +20 -0
- package/dist/util/timing.js.map +1 -0
- package/dist/vault/delete.d.ts +19 -0
- package/dist/vault/delete.d.ts.map +1 -0
- package/dist/vault/delete.js +35 -0
- package/dist/vault/delete.js.map +1 -0
- package/dist/vault/index.d.ts +8 -0
- package/dist/vault/index.d.ts.map +1 -0
- package/dist/vault/index.js +7 -0
- package/dist/vault/index.js.map +1 -0
- package/dist/vault/list.d.ts +96 -0
- package/dist/vault/list.d.ts.map +1 -0
- package/dist/vault/list.js +106 -0
- package/dist/vault/list.js.map +1 -0
- package/dist/vault/proxy-login.d.ts +100 -0
- package/dist/vault/proxy-login.d.ts.map +1 -0
- package/dist/vault/proxy-login.js +106 -0
- package/dist/vault/proxy-login.js.map +1 -0
- package/dist/vault/release.d.ts +33 -0
- package/dist/vault/release.d.ts.map +1 -0
- package/dist/vault/release.js +83 -0
- package/dist/vault/release.js.map +1 -0
- package/dist/vault/sign-trust-task.d.ts +26 -0
- package/dist/vault/sign-trust-task.d.ts.map +1 -0
- package/dist/vault/sign-trust-task.js +53 -0
- package/dist/vault/sign-trust-task.js.map +1 -0
- package/dist/vault/transport.d.ts +50 -0
- package/dist/vault/transport.d.ts.map +1 -0
- package/dist/vault/transport.js +118 -0
- package/dist/vault/transport.js.map +1 -0
- package/dist/vault/upsert.d.ts +102 -0
- package/dist/vault/upsert.d.ts.map +1 -0
- package/dist/vault/upsert.js +92 -0
- package/dist/vault/upsert.js.map +1 -0
- package/dist/vta/bridge-mediator-session.d.ts +26 -0
- package/dist/vta/bridge-mediator-session.d.ts.map +1 -0
- package/dist/vta/bridge-mediator-session.js +37 -0
- package/dist/vta/bridge-mediator-session.js.map +1 -0
- package/dist/vta/bridge-memory.d.ts +80 -0
- package/dist/vta/bridge-memory.d.ts.map +1 -0
- package/dist/vta/bridge-memory.js +162 -0
- package/dist/vta/bridge-memory.js.map +1 -0
- package/dist/vta/client.d.ts +40 -0
- package/dist/vta/client.d.ts.map +1 -0
- package/dist/vta/client.js +91 -0
- package/dist/vta/client.js.map +1 -0
- package/dist/vta/contexts.d.ts +60 -0
- package/dist/vta/contexts.d.ts.map +1 -0
- package/dist/vta/contexts.js +118 -0
- package/dist/vta/contexts.js.map +1 -0
- package/dist/vta/didcomm.d.ts +57 -0
- package/dist/vta/didcomm.d.ts.map +1 -0
- package/dist/vta/didcomm.js +138 -0
- package/dist/vta/didcomm.js.map +1 -0
- package/dist/vta/errors.d.ts +20 -0
- package/dist/vta/errors.d.ts.map +1 -0
- package/dist/vta/errors.js +64 -0
- package/dist/vta/errors.js.map +1 -0
- package/dist/vta/index.d.ts +15 -0
- package/dist/vta/index.d.ts.map +1 -0
- package/dist/vta/index.js +15 -0
- package/dist/vta/index.js.map +1 -0
- package/dist/vta/mediation.d.ts +80 -0
- package/dist/vta/mediation.d.ts.map +1 -0
- package/dist/vta/mediation.js +29 -0
- package/dist/vta/mediation.js.map +1 -0
- package/dist/vta/mediator-client.d.ts +66 -0
- package/dist/vta/mediator-client.d.ts.map +1 -0
- package/dist/vta/mediator-client.js +139 -0
- package/dist/vta/mediator-client.js.map +1 -0
- package/dist/vta/pickup.d.ts +81 -0
- package/dist/vta/pickup.d.ts.map +1 -0
- package/dist/vta/pickup.js +30 -0
- package/dist/vta/pickup.js.map +1 -0
- package/dist/vta/protocol.d.ts +76 -0
- package/dist/vta/protocol.d.ts.map +1 -0
- package/dist/vta/protocol.js +30 -0
- package/dist/vta/protocol.js.map +1 -0
- package/dist/vta/smoke.d.ts +59 -0
- package/dist/vta/smoke.d.ts.map +1 -0
- package/dist/vta/smoke.js +408 -0
- package/dist/vta/smoke.js.map +1 -0
- package/dist/vta/transport.d.ts +55 -0
- package/dist/vta/transport.d.ts.map +1 -0
- package/dist/vta/transport.js +2 -0
- package/dist/vta/transport.js.map +1 -0
- package/dist/vta/types.d.ts +50 -0
- package/dist/vta/types.d.ts.map +1 -0
- package/dist/vta/types.js +2 -0
- package/dist/vta/types.js.map +1 -0
- package/dist/vta/wallet-session.d.ts +87 -0
- package/dist/vta/wallet-session.d.ts.map +1 -0
- package/dist/vta/wallet-session.js +106 -0
- package/dist/vta/wallet-session.js.map +1 -0
- package/dist/webauthn/base64url.d.ts +3 -0
- package/dist/webauthn/base64url.d.ts.map +1 -0
- package/dist/webauthn/base64url.js +17 -0
- package/dist/webauthn/base64url.js.map +1 -0
- package/dist/webauthn/index.d.ts +4 -0
- package/dist/webauthn/index.d.ts.map +1 -0
- package/dist/webauthn/index.js +4 -0
- package/dist/webauthn/index.js.map +1 -0
- package/dist/webauthn/multikey.d.ts +26 -0
- package/dist/webauthn/multikey.d.ts.map +1 -0
- package/dist/webauthn/multikey.js +91 -0
- package/dist/webauthn/multikey.js.map +1 -0
- package/dist/webauthn/register.d.ts +36 -0
- package/dist/webauthn/register.d.ts.map +1 -0
- package/dist/webauthn/register.js +77 -0
- package/dist/webauthn/register.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { ed25519, x25519 } from "@noble/curves/ed25519.js";
|
|
2
|
+
import { base64url } from "@openvtc/vti-didcomm-js";
|
|
3
|
+
import { createDidPeer2 } from "../did/index.js";
|
|
4
|
+
import { Identity } from "../didcomm/index.js";
|
|
5
|
+
import { unwrapSecret, wrapSecret, } from "./secret-wrap.js";
|
|
6
|
+
// v3: the holder is a single **Ed25519-rooted `did:peer:2`** the wallet
|
|
7
|
+
// MINTS locally on first run. v2 used a `did:key`, which can sign + derive
|
|
8
|
+
// an X25519 keyAgreement key but CANNOT advertise a service endpoint — so
|
|
9
|
+
// an RP can't reach the wallet for inbound (RP-initiated) requests. A
|
|
10
|
+
// did:peer:2 encodes both keys plus the wallet's mediator service inline,
|
|
11
|
+
// making the wallet reachable, while staying self-certifying (resolves
|
|
12
|
+
// with no network).
|
|
13
|
+
//
|
|
14
|
+
// v4: the holder is an Ed25519 **`did:key`** the VTA MINTS during the
|
|
15
|
+
// provision-integration flow. The wallet ships its ephemeral did:key to
|
|
16
|
+
// the VTA, which mints a long-term admin DID + keys + authorization VC
|
|
17
|
+
// and ships them back HPKE-sealed; the wallet adopts the result as its
|
|
18
|
+
// holder. The DID method changes — did:key has no service endpoint, so
|
|
19
|
+
// the wallet is no longer reachable inbound at the holder layer. RP-
|
|
20
|
+
// initiated DIDComm is out-of-scope for v4 (the wallet only initiates).
|
|
21
|
+
//
|
|
22
|
+
// v4 reads ALWAYS preferred over v3 by the strict loader; v3 records left
|
|
23
|
+
// over from a pre-v4 wallet surface as `RequiresReonboardError` so the
|
|
24
|
+
// operator can re-onboard at a VTA and adopt a freshly-minted v4 identity.
|
|
25
|
+
//
|
|
26
|
+
// The Ed25519 secret is the persisted root in both shapes: it IS the
|
|
27
|
+
// authentication key, and the X25519 keyAgreement key is its Montgomery
|
|
28
|
+
// form, re-derived on every load.
|
|
29
|
+
const STORE_KEY = "pnm/holder-identity/v3";
|
|
30
|
+
// Legacy single-VTA v4 key. Read-only after multi-VTA landed; records
|
|
31
|
+
// found here are migrated to the per-vta path on first access and the
|
|
32
|
+
// legacy key is deleted. Kept defined (not exported) so the migration
|
|
33
|
+
// helper can read it without re-typing the constant.
|
|
34
|
+
const STORE_KEY_V4_LEGACY = "pnm/holder-identity/v4";
|
|
35
|
+
// Per-VTA v4 key prefix. Each VTA the wallet has been onboarded at
|
|
36
|
+
// has its own record under `{prefix}{vtaDid}`. The trailing `/`
|
|
37
|
+
// disambiguates the prefix-scan from the legacy singleton key so a
|
|
38
|
+
// caller listing records doesn't accidentally pick up the legacy
|
|
39
|
+
// row before its migration ran.
|
|
40
|
+
const STORE_KEY_V4_PREFIX = "pnm/holder-identity/v4/";
|
|
41
|
+
function v4Key(vtaDid) {
|
|
42
|
+
return STORE_KEY_V4_PREFIX + vtaDid;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Migrate the legacy single-VTA v4 record (if present) to its
|
|
46
|
+
* per-vta key, using the record's own `vtaDid` field as the path
|
|
47
|
+
* suffix. Idempotent: a second call after migration is a no-op
|
|
48
|
+
* (legacy key is gone, return value is `null`). Returns the migrated
|
|
49
|
+
* record so the immediate caller can use it without a second read.
|
|
50
|
+
*
|
|
51
|
+
* Called inline from every read path so installs predating multi-VTA
|
|
52
|
+
* are picked up on whichever read fires first after upgrade.
|
|
53
|
+
*/
|
|
54
|
+
async function migrateLegacyV4Record(store) {
|
|
55
|
+
const legacy = await store.get(STORE_KEY_V4_LEGACY);
|
|
56
|
+
if (!legacy)
|
|
57
|
+
return null;
|
|
58
|
+
// Write FIRST, delete after — a crash between the two leaves the
|
|
59
|
+
// legacy row in place; the next read re-runs the migration.
|
|
60
|
+
await store.put(v4Key(legacy.vtaDid), legacy);
|
|
61
|
+
await store.delete(STORE_KEY_V4_LEGACY);
|
|
62
|
+
return legacy;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generate-or-load the wallet's holder identity (a `did:peer:2`) from a
|
|
66
|
+
* `KVStore`. Only the Ed25519 secret is persisted; the DID + kids are
|
|
67
|
+
* persisted alongside it (the DID is immutable once minted), and the X25519
|
|
68
|
+
* keyAgreement key is re-derived on every load.
|
|
69
|
+
*
|
|
70
|
+
* Persistence is wrapped via the optional [`HolderIdentityOptions.secretWrap`]
|
|
71
|
+
* — production extensions supply a `WebAuthnPrfSecretWrap` so storage
|
|
72
|
+
* exfil yields ciphertext, not the wallet's signing key. Callers that
|
|
73
|
+
* omit the wrap fall back to plaintext (legacy behaviour); the
|
|
74
|
+
* loader handles both shapes for backward compatibility.
|
|
75
|
+
*/
|
|
76
|
+
export async function generateOrLoadHolderIdentity(store, opts) {
|
|
77
|
+
const persisted = await store.get(STORE_KEY);
|
|
78
|
+
if (persisted) {
|
|
79
|
+
let edSecret;
|
|
80
|
+
if (persisted.wrappedSecret) {
|
|
81
|
+
// New shape: read through the wrap. A wrap-algorithm
|
|
82
|
+
// mismatch (record wrapped with X, caller supplied Y)
|
|
83
|
+
// throws — the loader can't silently fall back without
|
|
84
|
+
// risking a secret leak.
|
|
85
|
+
edSecret = await unwrapSecret(persisted.wrappedSecret, opts?.secretWrap);
|
|
86
|
+
}
|
|
87
|
+
else if (persisted.edSecretB64u) {
|
|
88
|
+
// Legacy shape: plaintext base64url. Pre-H1 wallets.
|
|
89
|
+
edSecret = base64url.decode(persisted.edSecretB64u);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw new Error("persisted holder record missing both wrappedSecret and edSecretB64u");
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
...buildHolder(edSecret, persisted.did, persisted.signingKid, persisted.keyAgreementKid),
|
|
96
|
+
freshlyMinted: false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const edSecret = ed25519.utils.randomSecretKey();
|
|
100
|
+
const edPublic = ed25519.getPublicKey(edSecret);
|
|
101
|
+
const x25519Public = x25519.getPublicKey(ed25519.utils.toMontgomerySecret(edSecret));
|
|
102
|
+
const peer = createDidPeer2({
|
|
103
|
+
ed25519PublicKey: edPublic,
|
|
104
|
+
x25519PublicKey: x25519Public,
|
|
105
|
+
...(opts?.mediatorDid ? { service: { serviceEndpoint: opts.mediatorDid } } : {}),
|
|
106
|
+
});
|
|
107
|
+
const wrapped = await wrapSecret(edSecret, opts?.secretWrap);
|
|
108
|
+
const record = {
|
|
109
|
+
did: peer.did,
|
|
110
|
+
signingKid: peer.authKid,
|
|
111
|
+
keyAgreementKid: peer.keyAgreementKid,
|
|
112
|
+
wrappedSecret: wrapped,
|
|
113
|
+
...(opts?.mediatorDid ? { mediatorDid: opts.mediatorDid } : {}),
|
|
114
|
+
};
|
|
115
|
+
await store.put(STORE_KEY, record);
|
|
116
|
+
return {
|
|
117
|
+
...buildHolder(edSecret, peer.did, peer.authKid, peer.keyAgreementKid),
|
|
118
|
+
freshlyMinted: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Reconstruct the signing + DIDComm identities from the Ed25519 root secret
|
|
122
|
+
* and the (persisted/minted) did:peer + its VM ids. */
|
|
123
|
+
function buildHolder(edSecret, did, signingKid, keyAgreementKid) {
|
|
124
|
+
const edPublic = ed25519.getPublicKey(edSecret);
|
|
125
|
+
const xPrivate = ed25519.utils.toMontgomerySecret(edSecret);
|
|
126
|
+
const xPublic = x25519.getPublicKey(xPrivate);
|
|
127
|
+
const signing = {
|
|
128
|
+
did,
|
|
129
|
+
kid: signingKid,
|
|
130
|
+
privateKey: edSecret,
|
|
131
|
+
publicKey: edPublic,
|
|
132
|
+
};
|
|
133
|
+
const identity = Identity.fromSecretJwk({
|
|
134
|
+
did,
|
|
135
|
+
kid: keyAgreementKid,
|
|
136
|
+
jwk: {
|
|
137
|
+
kty: "OKP",
|
|
138
|
+
crv: "X25519",
|
|
139
|
+
x: base64url.encode(xPublic),
|
|
140
|
+
d: base64url.encode(xPrivate),
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return { identity, signing };
|
|
144
|
+
}
|
|
145
|
+
/** Forget every persisted holder identity. Mostly for tests / hard
|
|
146
|
+
* reset (the options page "wipe wallet" button). Clears the v3 row,
|
|
147
|
+
* the legacy single-VTA v4 row, AND every per-vta v4 record. After
|
|
148
|
+
* this the wallet looks freshly installed at every VTA. */
|
|
149
|
+
export async function clearHolderIdentity(store) {
|
|
150
|
+
await store.delete(STORE_KEY);
|
|
151
|
+
await store.delete(STORE_KEY_V4_LEGACY);
|
|
152
|
+
const perVtaKeys = await store.keys(STORE_KEY_V4_PREFIX);
|
|
153
|
+
for (const k of perVtaKeys) {
|
|
154
|
+
await store.delete(k);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Thrown by `loadHolderStrict` when neither a v3 nor a v4 holder record
|
|
158
|
+
* exists — the wallet is on a fresh install and the operator should
|
|
159
|
+
* proceed with onboarding. */
|
|
160
|
+
export class NoHolderError extends Error {
|
|
161
|
+
constructor() {
|
|
162
|
+
super("no persisted holder identity — onboard with a VTA");
|
|
163
|
+
this.name = "NoHolderError";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Thrown by `loadHolderStrict` when a v3 (self-derived did:peer) record
|
|
167
|
+
* exists but no v4 (VTA-minted did:key) record. The wallet was built
|
|
168
|
+
* before the M2C identity migration; the operator must re-onboard so the
|
|
169
|
+
* VTA mints a fresh long-term DID. The old did:peer is unusable as the
|
|
170
|
+
* wallet's holder going forward — every RP that recognised it must be
|
|
171
|
+
* re-granted with the new VTA-minted DID.
|
|
172
|
+
*
|
|
173
|
+
* `previousDid` is the v3 DID, surfaced for the migration UI so the
|
|
174
|
+
* operator can audit what they're abandoning. */
|
|
175
|
+
export class RequiresReonboardError extends Error {
|
|
176
|
+
previousDid;
|
|
177
|
+
constructor(previousDid) {
|
|
178
|
+
super(`pre-v4 holder identity (${previousDid}) — re-onboard required: ` +
|
|
179
|
+
"this build expects a VTA-minted holder DID. Connect to a VTA to mint a fresh identity.");
|
|
180
|
+
this.name = "RequiresReonboardError";
|
|
181
|
+
this.previousDid = previousDid;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Load the wallet's holder identity for the given VTA, strictly
|
|
185
|
+
* preferring v4.
|
|
186
|
+
*
|
|
187
|
+
* - v4 record for `vtaDid` present → return the VTA-minted holder.
|
|
188
|
+
* - no v4 record for `vtaDid` but a legacy single-VTA v4 record
|
|
189
|
+
* exists → migrate it to the per-vta path (transparently) and
|
|
190
|
+
* return if its `vtaDid` matches the requested one.
|
|
191
|
+
* - no v4 (for `vtaDid` or legacy) but v3 present → throw
|
|
192
|
+
* `RequiresReonboardError` (the operator needs to re-onboard so the
|
|
193
|
+
* VTA mints a v4 identity).
|
|
194
|
+
* - none of the above → throw `NoHolderError` (fresh install). */
|
|
195
|
+
export async function loadHolderStrict(store, opts) {
|
|
196
|
+
let v4 = await store.get(v4Key(opts.vtaDid));
|
|
197
|
+
if (!v4) {
|
|
198
|
+
// Inline migration of the legacy single-VTA v4 record. If the
|
|
199
|
+
// legacy row's vtaDid matches the requested one, use it now;
|
|
200
|
+
// otherwise fall through (the wallet is onboarded at a different
|
|
201
|
+
// VTA than the one being asked for).
|
|
202
|
+
const migrated = await migrateLegacyV4Record(store);
|
|
203
|
+
if (migrated && migrated.vtaDid === opts.vtaDid) {
|
|
204
|
+
v4 = migrated;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (v4) {
|
|
208
|
+
const edSecret = await unwrapSecret(v4.wrappedSecret, opts.secretWrap);
|
|
209
|
+
return {
|
|
210
|
+
...buildHolder(edSecret, v4.did, v4.signingKid, v4.keyAgreementKid),
|
|
211
|
+
freshlyMinted: false,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const v3 = await store.get(STORE_KEY);
|
|
215
|
+
if (v3) {
|
|
216
|
+
throw new RequiresReonboardError(v3.did);
|
|
217
|
+
}
|
|
218
|
+
throw new NoHolderError();
|
|
219
|
+
}
|
|
220
|
+
/** Persist a freshly-minted VTA holder as the wallet's v4 identity.
|
|
221
|
+
*
|
|
222
|
+
* Returns the rebuilt `HolderIdentityResult` so the caller can use the
|
|
223
|
+
* identity immediately without an extra load. Clears any pre-existing
|
|
224
|
+
* v3 record on a successful install — the migration is a one-way move
|
|
225
|
+
* and a stale v3 record sitting alongside v4 is just a footgun for a
|
|
226
|
+
* future loader. */
|
|
227
|
+
export async function installVtaMintedHolder(store, opts) {
|
|
228
|
+
if (opts.edSeed.length !== 32) {
|
|
229
|
+
throw new Error(`installVtaMintedHolder: edSeed must be 32 bytes (got ${opts.edSeed.length})`);
|
|
230
|
+
}
|
|
231
|
+
const wrapped = await wrapSecret(opts.edSeed, opts.secretWrap);
|
|
232
|
+
const record = {
|
|
233
|
+
did: opts.did,
|
|
234
|
+
signingKid: opts.signingKid,
|
|
235
|
+
keyAgreementKid: opts.keyAgreementKid,
|
|
236
|
+
wrappedSecret: wrapped,
|
|
237
|
+
vtaDid: opts.vtaDid,
|
|
238
|
+
...(opts.vtaUrl ? { vtaUrl: opts.vtaUrl } : {}),
|
|
239
|
+
schemaVersion: 4,
|
|
240
|
+
};
|
|
241
|
+
await store.put(v4Key(opts.vtaDid), record);
|
|
242
|
+
// Migration: drop the legacy did:peer record so the next strict load
|
|
243
|
+
// doesn't re-prompt the operator to re-onboard. Without this, a wallet
|
|
244
|
+
// that successfully onboarded but kept the v3 row would loop on its
|
|
245
|
+
// first reload.
|
|
246
|
+
await store.delete(STORE_KEY);
|
|
247
|
+
// Also drop the legacy single-VTA v4 record if present — the per-vta
|
|
248
|
+
// record is the new source of truth. Idempotent: a no-op if the
|
|
249
|
+
// legacy row was already migrated or never existed.
|
|
250
|
+
await store.delete(STORE_KEY_V4_LEGACY);
|
|
251
|
+
return {
|
|
252
|
+
...buildHolder(opts.edSeed, opts.did, opts.signingKid, opts.keyAgreementKid),
|
|
253
|
+
freshlyMinted: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/** Re-wrap the persisted v4 holder secret in place, preserving the
|
|
257
|
+
* wallet's DID + verification-method ids + VTA provenance.
|
|
258
|
+
*
|
|
259
|
+
* The canonical caller is the popup's post-onboard "Encrypt your
|
|
260
|
+
* wallet?" prompt: the operator clicks the button, the popup runs
|
|
261
|
+
* `navigator.credentials.create` (visible context, fresh user
|
|
262
|
+
* gesture), and re-wraps the existing passthrough record under
|
|
263
|
+
* the PRF-derived AES-GCM key. The wallet DID stays the same — no
|
|
264
|
+
* re-grant in any RP ACL, no re-onboarding.
|
|
265
|
+
*
|
|
266
|
+
* Mirrors the v3 `rewrapHolderSecret` but reads + writes the v4
|
|
267
|
+
* record (`STORE_KEY_V4`). Future cleanup could consolidate the
|
|
268
|
+
* two into one schema-version-aware function; kept separate for
|
|
269
|
+
* now so the v3 path's legacy `edSecretB64u` fallback doesn't
|
|
270
|
+
* leak into v4's cleaner shape.
|
|
271
|
+
*
|
|
272
|
+
* Throws if no v4 record exists. The popup should only invoke this
|
|
273
|
+
* AFTER `installVtaMintedHolder` has run. */
|
|
274
|
+
export async function rewrapHolderV4Secret(store, opts) {
|
|
275
|
+
let persisted = await store.get(v4Key(opts.vtaDid));
|
|
276
|
+
if (!persisted) {
|
|
277
|
+
// Try migrating a legacy single-VTA row into the per-vta path,
|
|
278
|
+
// then re-read. Same idempotent helper used by `loadHolderStrict`.
|
|
279
|
+
const migrated = await migrateLegacyV4Record(store);
|
|
280
|
+
if (migrated && migrated.vtaDid === opts.vtaDid) {
|
|
281
|
+
persisted = migrated;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (!persisted) {
|
|
285
|
+
throw new Error(`no persisted v4 holder identity to re-wrap for ${opts.vtaDid}`);
|
|
286
|
+
}
|
|
287
|
+
// 1. Recover the raw seed using the from-wrap. `unwrapSecret`
|
|
288
|
+
// dispatches on the stored record's `algorithm`, so a
|
|
289
|
+
// PassthroughWrap record opens regardless of which wrap the
|
|
290
|
+
// caller supplies (they're typically passing the new
|
|
291
|
+
// encryption-target wrap, not the existing passthrough).
|
|
292
|
+
const edSecret = await unwrapSecret(persisted.wrappedSecret, opts.fromWrap);
|
|
293
|
+
// 2. Re-wrap with the to-wrap and write back. Same shape as
|
|
294
|
+
// `installVtaMintedHolder`'s write path; only `wrappedSecret`
|
|
295
|
+
// actually changes.
|
|
296
|
+
const wrapped = await wrapSecret(edSecret, opts.toWrap);
|
|
297
|
+
const next = {
|
|
298
|
+
did: persisted.did,
|
|
299
|
+
signingKid: persisted.signingKid,
|
|
300
|
+
keyAgreementKid: persisted.keyAgreementKid,
|
|
301
|
+
wrappedSecret: wrapped,
|
|
302
|
+
vtaDid: persisted.vtaDid,
|
|
303
|
+
...(persisted.vtaUrl ? { vtaUrl: persisted.vtaUrl } : {}),
|
|
304
|
+
schemaVersion: 4,
|
|
305
|
+
};
|
|
306
|
+
await store.put(v4Key(persisted.vtaDid), next);
|
|
307
|
+
return {
|
|
308
|
+
...buildHolder(edSecret, persisted.did, persisted.signingKid, persisted.keyAgreementKid),
|
|
309
|
+
freshlyMinted: false,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/** Inspect the persisted state without throwing. Used by the popup to
|
|
313
|
+
* decide which onboarding screen to show.
|
|
314
|
+
*
|
|
315
|
+
* For v4 records, `wrapAlgorithm` reveals whether the secret is
|
|
316
|
+
* encrypted at rest. `"passthrough"` means plaintext (the operator
|
|
317
|
+
* hasn't enabled encryption); anything else (currently only
|
|
318
|
+
* `"webauthn-prf-aes-gcm"`) means the popup needs to run an
|
|
319
|
+
* unlock ceremony before offscreen can load the holder identity.
|
|
320
|
+
*
|
|
321
|
+
* When `vtaDid` is passed: returns the state for that specific
|
|
322
|
+
* VTA's holder record. Used by the popup's active-VTA probe.
|
|
323
|
+
* When `vtaDid` is omitted: returns the first v4 record found
|
|
324
|
+
* (after migrating any legacy single-VTA row), else falls back to
|
|
325
|
+
* v3 / none. Used for the initial fresh-install / migration-banner
|
|
326
|
+
* decision before an active VTA has been selected. */
|
|
327
|
+
export async function holderIdentityState(store, vtaDid) {
|
|
328
|
+
// Migrate the legacy row if present — it joins the per-vta keyspace
|
|
329
|
+
// and will be picked up by the lookups below.
|
|
330
|
+
await migrateLegacyV4Record(store);
|
|
331
|
+
if (vtaDid !== undefined) {
|
|
332
|
+
const v4 = await store.get(v4Key(vtaDid));
|
|
333
|
+
if (v4) {
|
|
334
|
+
return {
|
|
335
|
+
kind: "v4",
|
|
336
|
+
did: v4.did,
|
|
337
|
+
vtaDid: v4.vtaDid,
|
|
338
|
+
wrapAlgorithm: v4.wrappedSecret.algorithm,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// No specific VTA — scan the per-vta keyspace and return the
|
|
344
|
+
// first match. The popup uses this for the migration banner
|
|
345
|
+
// detection (any v4 present at all → skip the v3 banner).
|
|
346
|
+
const keys = await store.keys(STORE_KEY_V4_PREFIX);
|
|
347
|
+
if (keys.length > 0) {
|
|
348
|
+
const first = await store.get(keys[0]);
|
|
349
|
+
if (first) {
|
|
350
|
+
return {
|
|
351
|
+
kind: "v4",
|
|
352
|
+
did: first.did,
|
|
353
|
+
vtaDid: first.vtaDid,
|
|
354
|
+
wrapAlgorithm: first.wrappedSecret.algorithm,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const v3 = await store.get(STORE_KEY);
|
|
360
|
+
if (v3)
|
|
361
|
+
return { kind: "v3", did: v3.did };
|
|
362
|
+
return { kind: "none" };
|
|
363
|
+
}
|
|
364
|
+
/** Enumerate every v4 holder record on disk — one per VTA the wallet
|
|
365
|
+
* has been onboarded at. Powers the popup's multi-VTA dropdown
|
|
366
|
+
* (PR 2). Migrates the legacy single-VTA row inline so a wallet
|
|
367
|
+
* upgraded from a single-VTA build surfaces its one wallet here. */
|
|
368
|
+
export async function listHolderRecords(store) {
|
|
369
|
+
await migrateLegacyV4Record(store);
|
|
370
|
+
const keys = await store.keys(STORE_KEY_V4_PREFIX);
|
|
371
|
+
const out = [];
|
|
372
|
+
for (const k of keys) {
|
|
373
|
+
const r = await store.get(k);
|
|
374
|
+
if (!r)
|
|
375
|
+
continue;
|
|
376
|
+
out.push({
|
|
377
|
+
vtaDid: r.vtaDid,
|
|
378
|
+
did: r.did,
|
|
379
|
+
wrapAlgorithm: r.wrappedSecret.algorithm,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return out;
|
|
383
|
+
}
|
|
384
|
+
/** Delete the v4 holder record for a specific VTA. Companion to
|
|
385
|
+
* `installVtaMintedHolder`. Idempotent: a no-op when the record is
|
|
386
|
+
* already gone. Other VTAs' records are left alone — call
|
|
387
|
+
* `clearHolderIdentity` to wipe every wallet on this device. */
|
|
388
|
+
export async function forgetHolderRecord(store, vtaDid) {
|
|
389
|
+
await store.delete(v4Key(vtaDid));
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Re-wrap the persisted holder secret in place, preserving the
|
|
393
|
+
* wallet's DID + verification-method ids + mediator endpoint.
|
|
394
|
+
*
|
|
395
|
+
* Used by the extension's settings UI when the operator flips
|
|
396
|
+
* the `encryptHolderSecret` toggle — the existing wallet keeps
|
|
397
|
+
* its identity (no re-grant in any RP ACL) but the on-disk
|
|
398
|
+
* secret transitions between plaintext and wrap-encrypted.
|
|
399
|
+
*
|
|
400
|
+
* Returns the rebuilt `HolderIdentityResult` so the caller can
|
|
401
|
+
* report the (unchanged) DID back to the operator immediately.
|
|
402
|
+
*
|
|
403
|
+
* Errors if no persisted record exists — caller should check
|
|
404
|
+
* `freshlyMinted` semantics first (a fresh-mint wallet has no
|
|
405
|
+
* pre-existing secret to re-wrap).
|
|
406
|
+
*/
|
|
407
|
+
export async function rewrapHolderSecret(store, opts) {
|
|
408
|
+
const persisted = await store.get(STORE_KEY);
|
|
409
|
+
if (!persisted) {
|
|
410
|
+
throw new Error("no persisted holder identity to re-wrap");
|
|
411
|
+
}
|
|
412
|
+
// 1. Recover the raw secret using the from-wrap.
|
|
413
|
+
let edSecret;
|
|
414
|
+
if (persisted.wrappedSecret) {
|
|
415
|
+
edSecret = await unwrapSecret(persisted.wrappedSecret, opts.fromWrap);
|
|
416
|
+
}
|
|
417
|
+
else if (persisted.edSecretB64u) {
|
|
418
|
+
edSecret = base64url.decode(persisted.edSecretB64u);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
throw new Error("persisted record missing both wrappedSecret and edSecretB64u");
|
|
422
|
+
}
|
|
423
|
+
// 2. Re-wrap with the to-wrap and write back. Drop the legacy
|
|
424
|
+
// plaintext slot on the way out so a partially-migrated
|
|
425
|
+
// record can't load through the legacy path on the next
|
|
426
|
+
// boot.
|
|
427
|
+
const wrapped = await wrapSecret(edSecret, opts.toWrap);
|
|
428
|
+
const next = {
|
|
429
|
+
did: persisted.did,
|
|
430
|
+
signingKid: persisted.signingKid,
|
|
431
|
+
keyAgreementKid: persisted.keyAgreementKid,
|
|
432
|
+
wrappedSecret: wrapped,
|
|
433
|
+
...(persisted.mediatorDid ? { mediatorDid: persisted.mediatorDid } : {}),
|
|
434
|
+
};
|
|
435
|
+
await store.put(STORE_KEY, next);
|
|
436
|
+
return {
|
|
437
|
+
...buildHolder(edSecret, persisted.did, persisted.signingKid, persisted.keyAgreementKid),
|
|
438
|
+
freshlyMinted: false,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=holder-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"holder-identity.js","sourceRoot":"","sources":["../../src/store/holder-identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal async key/value store. Two implementations ship:
|
|
3
|
+
*
|
|
4
|
+
* - `InMemoryKVStore` — for tests and ephemeral sessions.
|
|
5
|
+
* - `IndexedDBKVStore` — for browser persistence.
|
|
6
|
+
*
|
|
7
|
+
* Anything stored MUST be JSON-serialisable. The store does not
|
|
8
|
+
* encrypt at rest — callers wrap values in WebAuthn PRF-derived keys
|
|
9
|
+
* (or similar) before writing if the value is sensitive (e.g. a
|
|
10
|
+
* holder identity's secret JWK).
|
|
11
|
+
*/
|
|
12
|
+
export interface KVStore {
|
|
13
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
14
|
+
put(key: string, value: unknown): Promise<void>;
|
|
15
|
+
delete(key: string): Promise<void>;
|
|
16
|
+
/** Enumerate keys, optionally filtered to those starting with
|
|
17
|
+
* `prefix`. Order is unspecified. Used for prefix-scanned
|
|
18
|
+
* collections (e.g. multi-VTA holder records under
|
|
19
|
+
* `pnm/holder-identity/v4/`). */
|
|
20
|
+
keys(prefix?: string): Promise<string[]>;
|
|
21
|
+
}
|
|
22
|
+
/** Backing for tests. Forgets state on process exit. */
|
|
23
|
+
export declare class InMemoryKVStore implements KVStore {
|
|
24
|
+
private readonly map;
|
|
25
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
26
|
+
put(key: string, value: unknown): Promise<void>;
|
|
27
|
+
delete(key: string): Promise<void>;
|
|
28
|
+
keys(prefix?: string): Promise<string[]>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `IndexedDB`-backed `KVStore`. Single object store, single
|
|
32
|
+
* database. Suitable for browser + extension contexts where
|
|
33
|
+
* `indexedDB` is available. Node 22+ has `indexedDB` via the
|
|
34
|
+
* `node:sqlite`-backed implementation behind a flag; tests should
|
|
35
|
+
* use `InMemoryKVStore` instead.
|
|
36
|
+
*/
|
|
37
|
+
export declare class IndexedDBKVStore implements KVStore {
|
|
38
|
+
private readonly dbName;
|
|
39
|
+
private readonly storeName;
|
|
40
|
+
private dbPromise;
|
|
41
|
+
constructor(opts?: {
|
|
42
|
+
dbName?: string;
|
|
43
|
+
storeName?: string;
|
|
44
|
+
});
|
|
45
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
46
|
+
put(key: string, value: unknown): Promise<void>;
|
|
47
|
+
delete(key: string): Promise<void>;
|
|
48
|
+
keys(prefix?: string): Promise<string[]>;
|
|
49
|
+
private openDb;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=kv-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kv-store.d.ts","sourceRoot":"","sources":["../../src/store/kv-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,OAAO;IACtB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACtD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC;;;sCAGkC;IAClC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC1C;AAED,wDAAwD;AACxD,qBAAa,eAAgB,YAAW,OAAO;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA8B;IAE5C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAK3C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAO/C;AAED;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,OAAO;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,SAAS,CAAqC;gBAE1C,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAKpD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAU3C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAoB9C,OAAO,CAAC,MAAM;CAef"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** Backing for tests. Forgets state on process exit. */
|
|
2
|
+
export class InMemoryKVStore {
|
|
3
|
+
map = new Map();
|
|
4
|
+
async get(key) {
|
|
5
|
+
const v = this.map.get(key);
|
|
6
|
+
return v === undefined ? undefined : structuredClone(v);
|
|
7
|
+
}
|
|
8
|
+
async put(key, value) {
|
|
9
|
+
this.map.set(key, structuredClone(value));
|
|
10
|
+
}
|
|
11
|
+
async delete(key) {
|
|
12
|
+
this.map.delete(key);
|
|
13
|
+
}
|
|
14
|
+
async keys(prefix) {
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const k of this.map.keys()) {
|
|
17
|
+
if (prefix === undefined || k.startsWith(prefix))
|
|
18
|
+
out.push(k);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `IndexedDB`-backed `KVStore`. Single object store, single
|
|
25
|
+
* database. Suitable for browser + extension contexts where
|
|
26
|
+
* `indexedDB` is available. Node 22+ has `indexedDB` via the
|
|
27
|
+
* `node:sqlite`-backed implementation behind a flag; tests should
|
|
28
|
+
* use `InMemoryKVStore` instead.
|
|
29
|
+
*/
|
|
30
|
+
export class IndexedDBKVStore {
|
|
31
|
+
dbName;
|
|
32
|
+
storeName;
|
|
33
|
+
dbPromise = null;
|
|
34
|
+
constructor(opts) {
|
|
35
|
+
this.dbName = opts?.dbName ?? "pnm";
|
|
36
|
+
this.storeName = opts?.storeName ?? "kv";
|
|
37
|
+
}
|
|
38
|
+
async get(key) {
|
|
39
|
+
const db = await this.openDb();
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const tx = db.transaction(this.storeName, "readonly");
|
|
42
|
+
const req = tx.objectStore(this.storeName).get(key);
|
|
43
|
+
req.onsuccess = () => resolve(req.result);
|
|
44
|
+
req.onerror = () => reject(req.error);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async put(key, value) {
|
|
48
|
+
const db = await this.openDb();
|
|
49
|
+
await new Promise((resolve, reject) => {
|
|
50
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
51
|
+
tx.objectStore(this.storeName).put(value, key);
|
|
52
|
+
tx.oncomplete = () => resolve();
|
|
53
|
+
tx.onerror = () => reject(tx.error);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async delete(key) {
|
|
57
|
+
const db = await this.openDb();
|
|
58
|
+
await new Promise((resolve, reject) => {
|
|
59
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
60
|
+
tx.objectStore(this.storeName).delete(key);
|
|
61
|
+
tx.oncomplete = () => resolve();
|
|
62
|
+
tx.onerror = () => reject(tx.error);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async keys(prefix) {
|
|
66
|
+
const db = await this.openDb();
|
|
67
|
+
// Use a key range when a prefix is provided so IndexedDB does the
|
|
68
|
+
// filtering — avoids walking the entire keyspace for a handful of
|
|
69
|
+
// matches. The upper bound is `prefix + ''` (the highest
|
|
70
|
+
// BMP code point), giving a half-open range that captures every
|
|
71
|
+
// string starting with `prefix`.
|
|
72
|
+
const range = prefix !== undefined
|
|
73
|
+
? IDBKeyRange.bound(prefix, prefix + "", false, false)
|
|
74
|
+
: undefined;
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const tx = db.transaction(this.storeName, "readonly");
|
|
77
|
+
const store = tx.objectStore(this.storeName);
|
|
78
|
+
const req = range !== undefined ? store.getAllKeys(range) : store.getAllKeys();
|
|
79
|
+
req.onsuccess = () => resolve(req.result.map(String));
|
|
80
|
+
req.onerror = () => reject(req.error);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
openDb() {
|
|
84
|
+
if (this.dbPromise)
|
|
85
|
+
return this.dbPromise;
|
|
86
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
87
|
+
const req = indexedDB.open(this.dbName, 1);
|
|
88
|
+
req.onupgradeneeded = () => {
|
|
89
|
+
const db = req.result;
|
|
90
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
91
|
+
db.createObjectStore(this.storeName);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
req.onsuccess = () => resolve(req.result);
|
|
95
|
+
req.onerror = () => reject(req.error);
|
|
96
|
+
});
|
|
97
|
+
return this.dbPromise;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=kv-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kv-store.js","sourceRoot":"","sources":["../../src/store/kv-store.ts"],"names":[],"mappings":"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"}
|