keri-ts 2.1.0 → 2.2.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 +30 -2
- package/esm/keri/npm/src/acdc/authoring.js +195 -0
- package/esm/keri/npm/src/acdc/custody.js +164 -0
- package/esm/keri/npm/src/acdc/dhking.js +244 -32
- package/esm/keri/npm/src/acdc/regbasing.js +10 -0
- package/esm/keri/npm/src/acdc/salting.js +16 -1
- package/esm/keri/npm/src/acdc/scheming.js +93 -54
- package/esm/keri/npm/src/acdc/unblinding.js +118 -95
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +4 -6
- package/esm/keri/npm/src/app/bootstrap/oobi-resolving.js +41 -0
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +4 -6
- package/esm/keri/npm/src/app/credentials/credential-revoking.js +1 -0
- package/esm/keri/npm/src/app/credentials/credential-workflows.js +2 -0
- package/esm/keri/npm/src/app/credentials/registry-settling.js +44 -0
- package/esm/keri/npm/src/app/credentials/root-replicating.js +68 -60
- package/esm/keri/npm/src/app/credentials/root-replicator.js +198 -0
- package/esm/keri/npm/src/app/credentials/tel-completion.js +36 -0
- package/esm/keri/npm/src/app/delivery/forwarding.js +16 -3
- package/esm/keri/npm/src/app/exchanges/exchanging.js +14 -4
- package/esm/keri/npm/src/app/identity/challenging.js +50 -0
- package/esm/keri/npm/src/app/identity/delegating.js +77 -77
- package/esm/keri/npm/src/app/identity/delegation-approval.js +333 -0
- package/esm/keri/npm/src/app/identity/event-completion.js +154 -0
- package/esm/keri/npm/src/app/identity/habbing.js +129 -6
- package/esm/keri/npm/src/app/identity/keeping.js +77 -4
- package/esm/keri/npm/src/app/ipex/group-ipex.js +327 -0
- package/esm/keri/npm/src/app/ipex/group-issuance.js +902 -0
- package/esm/keri/npm/src/app/ipex/ipex-admitting.js +120 -0
- package/esm/keri/npm/src/app/ipex/ipex-conversation.js +197 -0
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +591 -0
- package/esm/keri/npm/src/app/ipex/ipex-granting.js +93 -0
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +34 -11
- package/esm/keri/npm/src/app/ipex/issuance.js +888 -0
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +46 -0
- package/esm/keri/npm/src/app/ipex/presentation-files.js +200 -0
- package/esm/keri/npm/src/app/ipex/presenting.js +401 -0
- package/esm/keri/npm/src/app/ipex/receive-issuance.js +86 -0
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +660 -0
- package/esm/keri/npm/src/app/mailbox/ipex-arrival.js +103 -0
- package/esm/keri/npm/src/app/mailbox/kel-publisher.js +322 -0
- package/esm/keri/npm/src/app/mailbox/kel-publishing.js +65 -0
- package/esm/keri/npm/src/app/mailbox/kel-submission.js +24 -0
- package/esm/keri/npm/src/app/mailbox/mailbox-admin.js +208 -0
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +212 -47
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +10 -5
- package/esm/keri/npm/src/app/multisig/counseling.js +482 -0
- package/esm/keri/npm/src/app/multisig/group-members.js +18 -0
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +94 -139
- package/esm/keri/npm/src/app/multisig/proposals.js +281 -0
- package/esm/keri/npm/src/app/multisig/proposing.js +122 -0
- package/esm/keri/npm/src/app/notifications/signaling.js +5 -5
- package/esm/keri/npm/src/app/protocol/parsering.js +26 -1
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +9 -3
- package/esm/keri/npm/src/app/queries/query-workflows.js +98 -0
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +106 -55
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +3 -6
- package/esm/keri/npm/src/app/runtime/delivery-outbox.js +87 -0
- package/esm/keri/npm/src/app/runtime/doing.js +17 -0
- package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +207 -0
- package/esm/keri/npm/src/app/runtime/operation-owners.js +151 -0
- package/esm/keri/npm/src/app/runtime/retry-backoff.js +93 -0
- package/esm/keri/npm/src/app/runtime/workflow-contract.js +80 -0
- package/esm/keri/npm/src/app/transport/http-client.js +25 -6
- package/esm/keri/npm/src/app/witness/http-endpoints.js +4 -4
- package/esm/keri/npm/src/cli/common/event-completion.js +49 -0
- package/esm/keri/npm/src/cli/common/runtime-workers.js +23 -0
- package/esm/keri/npm/src/cli/credentials/ipex.js +157 -190
- package/esm/keri/npm/src/cli/credentials/vc.js +53 -72
- package/esm/keri/npm/src/cli/endpoints/did.js +12 -3
- package/esm/keri/npm/src/cli/endpoints/oobi.js +10 -13
- package/esm/keri/npm/src/cli/identity/challenge.js +8 -34
- package/esm/keri/npm/src/cli/identity/delegate.js +81 -258
- package/esm/keri/npm/src/cli/identity/incept.js +15 -32
- package/esm/keri/npm/src/cli/identity/interact.js +5 -17
- package/esm/keri/npm/src/cli/identity/rotate.js +8 -36
- package/esm/keri/npm/src/cli/index.js +3 -1
- package/esm/keri/npm/src/cli/messaging/group-join.js +79 -0
- package/esm/keri/npm/src/cli/messaging/group-wait.js +88 -0
- package/esm/keri/npm/src/cli/messaging/multisig.js +163 -284
- package/esm/keri/npm/src/cli/messaging/root-copy-lines.js +31 -0
- package/esm/keri/npm/src/core/kel/eventing.js +66 -10
- package/esm/keri/npm/src/core/mailbox/kram-policy.js +39 -6
- package/esm/keri/npm/src/core/mailbox/kraming.js +2 -0
- package/esm/keri/npm/src/core/records.js +23 -0
- package/esm/keri/npm/src/core/routing.js +13 -0
- package/esm/keri/npm/src/db/basing.js +34 -2
- package/esm/keri/npm/src/db/core/indexeddb-persistence.js +221 -0
- package/esm/keri/npm/src/db/indexeddb.js +98 -0
- package/esm/keri/npm/src/db/keeping.js +11 -2
- package/esm/keri/npm/src/db/store-lock.js +150 -0
- package/esm/keri/npm/src/npm/db-indexeddb.browser.js +2 -0
- package/esm/keri/npm/src/npm/db-indexeddb.js +3 -0
- package/esm/keri/npm/src/npm/db.browser.js +2 -0
- package/esm/keri/npm/src/npm/index.browser.js +9 -0
- package/esm/keri/npm/src/npm/runtime-web.browser.js +2 -0
- package/esm/keri/npm/src/npm/runtime-web.js +3 -0
- package/esm/keri/npm/src/npm/runtime.browser.js +2 -0
- package/esm/keri/npm/src/runtime/index.js +51 -4
- package/esm/keri/npm/src/runtime/web/output-barrier.js +47 -0
- package/esm/keri/npm/src/runtime/web/persistent-storage.js +37 -0
- package/esm/keri/npm/src/runtime/web.js +10 -0
- package/esm/keri/npm/src/vdr/tel-publication.js +16 -0
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +15 -2
- package/types/acdc/authoring.d.ts +54 -0
- package/types/acdc/authoring.d.ts.map +1 -0
- package/types/acdc/custody.d.ts +77 -0
- package/types/acdc/custody.d.ts.map +1 -0
- package/types/acdc/dhking.d.ts +94 -3
- package/types/acdc/dhking.d.ts.map +1 -1
- package/types/acdc/regbasing.d.ts +4 -0
- package/types/acdc/regbasing.d.ts.map +1 -1
- package/types/acdc/salting.d.ts +6 -1
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/acdc/scheming.d.ts +2 -0
- package/types/acdc/scheming.d.ts.map +1 -1
- package/types/acdc/unblinding.d.ts +58 -19
- package/types/acdc/unblinding.d.ts.map +1 -1
- package/types/app/bootstrap/authenticating.d.ts +3 -1
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobi-resolving.d.ts +92 -0
- package/types/app/bootstrap/oobi-resolving.d.ts.map +1 -0
- package/types/app/bootstrap/oobiery.d.ts +3 -1
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/credentials/credential-revoking.d.ts +3 -0
- package/types/app/credentials/credential-revoking.d.ts.map +1 -1
- package/types/app/credentials/credential-workflows.d.ts +5 -0
- package/types/app/credentials/credential-workflows.d.ts.map +1 -1
- package/types/app/credentials/registry-settling.d.ts +85 -0
- package/types/app/credentials/registry-settling.d.ts.map +1 -0
- package/types/app/credentials/root-replicating.d.ts +56 -44
- package/types/app/credentials/root-replicating.d.ts.map +1 -1
- package/types/app/credentials/root-replicator.d.ts +105 -0
- package/types/app/credentials/root-replicator.d.ts.map +1 -0
- package/types/app/credentials/tel-completion.d.ts +55 -0
- package/types/app/credentials/tel-completion.d.ts.map +1 -0
- package/types/app/delivery/forwarding.d.ts +6 -0
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/exchanges/exchanging.d.ts.map +1 -1
- package/types/app/identity/challenging.d.ts +54 -0
- package/types/app/identity/challenging.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts +21 -4
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/delegation-approval.d.ts +228 -0
- package/types/app/identity/delegation-approval.d.ts.map +1 -0
- package/types/app/identity/event-completion.d.ts +151 -0
- package/types/app/identity/event-completion.d.ts.map +1 -0
- package/types/app/identity/habbing.d.ts +67 -2
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +49 -3
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/ipex/group-ipex.d.ts +247 -0
- package/types/app/ipex/group-ipex.d.ts.map +1 -0
- package/types/app/ipex/group-issuance.d.ts +281 -0
- package/types/app/ipex/group-issuance.d.ts.map +1 -0
- package/types/app/ipex/ipex-admitting.d.ts +47 -0
- package/types/app/ipex/ipex-admitting.d.ts.map +1 -0
- package/types/app/ipex/ipex-conversation.d.ts +195 -0
- package/types/app/ipex/ipex-conversation.d.ts.map +1 -0
- package/types/app/ipex/ipex-discloser.d.ts +156 -0
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -0
- package/types/app/ipex/ipex-granting.d.ts +145 -0
- package/types/app/ipex/ipex-granting.d.ts.map +1 -0
- package/types/app/ipex/ipex-grouping.d.ts +18 -0
- package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +443 -0
- package/types/app/ipex/issuance.d.ts.map +1 -0
- package/types/app/ipex/pending-presentations.d.ts +25 -0
- package/types/app/ipex/pending-presentations.d.ts.map +1 -0
- package/types/app/ipex/presentation-files.d.ts +47 -0
- package/types/app/ipex/presentation-files.d.ts.map +1 -0
- package/types/app/ipex/presenting.d.ts +298 -0
- package/types/app/ipex/presenting.d.ts.map +1 -0
- package/types/app/ipex/receive-issuance.d.ts +99 -0
- package/types/app/ipex/receive-issuance.d.ts.map +1 -0
- package/types/app/ipex/unblinding-workflow.d.ts +255 -0
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -0
- package/types/app/mailbox/ipex-arrival.d.ts +104 -0
- package/types/app/mailbox/ipex-arrival.d.ts.map +1 -0
- package/types/app/mailbox/kel-publisher.d.ts +141 -0
- package/types/app/mailbox/kel-publisher.d.ts.map +1 -0
- package/types/app/mailbox/kel-publishing.d.ts +70 -0
- package/types/app/mailbox/kel-publishing.d.ts.map +1 -0
- package/types/app/mailbox/kel-submission.d.ts +18 -0
- package/types/app/mailbox/kel-submission.d.ts.map +1 -0
- package/types/app/mailbox/mailbox-admin.d.ts +113 -0
- package/types/app/mailbox/mailbox-admin.d.ts.map +1 -0
- package/types/app/mailbox/mailbox-poller.d.ts +92 -3
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +3 -0
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +196 -0
- package/types/app/multisig/counseling.d.ts.map +1 -0
- package/types/app/multisig/group-members.d.ts +11 -0
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +39 -20
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/multisig-workflows.d.ts +91 -22
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposals.d.ts +129 -0
- package/types/app/multisig/proposals.d.ts.map +1 -0
- package/types/app/multisig/proposing.d.ts +125 -0
- package/types/app/multisig/proposing.d.ts.map +1 -0
- package/types/app/notifications/signaling.d.ts +3 -1
- package/types/app/notifications/signaling.d.ts.map +1 -1
- package/types/app/protocol/parsering.d.ts +13 -0
- package/types/app/protocol/parsering.d.ts.map +1 -1
- package/types/app/protocol/protocol-processing.d.ts +7 -1
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/query-workflows.d.ts +98 -0
- package/types/app/queries/query-workflows.d.ts.map +1 -0
- package/types/app/runtime/agent-runtime.d.ts +72 -25
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/cue-runtime.d.ts +2 -1
- package/types/app/runtime/cue-runtime.d.ts.map +1 -1
- package/types/app/runtime/delivery-outbox.d.ts +68 -0
- package/types/app/runtime/delivery-outbox.d.ts.map +1 -0
- package/types/app/runtime/doing.d.ts +27 -0
- package/types/app/runtime/doing.d.ts.map +1 -0
- package/types/app/runtime/ipex-auxiliaries.d.ts +74 -0
- package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -0
- package/types/app/runtime/operation-owners.d.ts +137 -0
- package/types/app/runtime/operation-owners.d.ts.map +1 -0
- package/types/app/runtime/retry-backoff.d.ts +49 -0
- package/types/app/runtime/retry-backoff.d.ts.map +1 -0
- package/types/app/runtime/workflow-contract.d.ts +79 -0
- package/types/app/runtime/workflow-contract.d.ts.map +1 -0
- package/types/app/transport/http-client.d.ts.map +1 -1
- package/types/cli/common/event-completion.d.ts +26 -0
- package/types/cli/common/event-completion.d.ts.map +1 -0
- package/types/cli/common/runtime-workers.d.ts +23 -0
- package/types/cli/common/runtime-workers.d.ts.map +1 -0
- package/types/cli/credentials/ipex.d.ts.map +1 -1
- package/types/cli/credentials/vc.d.ts +1 -1
- package/types/cli/credentials/vc.d.ts.map +1 -1
- package/types/cli/endpoints/did.d.ts.map +1 -1
- package/types/cli/endpoints/oobi.d.ts +3 -1
- package/types/cli/endpoints/oobi.d.ts.map +1 -1
- package/types/cli/identity/challenge.d.ts +1 -1
- package/types/cli/identity/challenge.d.ts.map +1 -1
- package/types/cli/identity/delegate.d.ts +14 -1
- package/types/cli/identity/delegate.d.ts.map +1 -1
- package/types/cli/identity/incept.d.ts.map +1 -1
- package/types/cli/identity/interact.d.ts.map +1 -1
- package/types/cli/identity/rotate.d.ts.map +1 -1
- package/types/cli/index.d.ts +3 -1
- package/types/cli/index.d.ts.map +1 -1
- package/types/cli/messaging/group-join.d.ts +53 -0
- package/types/cli/messaging/group-join.d.ts.map +1 -0
- package/types/cli/messaging/group-wait.d.ts +34 -0
- package/types/cli/messaging/group-wait.d.ts.map +1 -0
- package/types/cli/messaging/multisig.d.ts +28 -5
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/cli/messaging/root-copy-lines.d.ts +39 -0
- package/types/cli/messaging/root-copy-lines.d.ts.map +1 -0
- package/types/core/cues.d.ts +11 -0
- package/types/core/cues.d.ts.map +1 -1
- package/types/core/kel/eventing.d.ts.map +1 -1
- package/types/core/mailbox/kram-policy.d.ts +14 -0
- package/types/core/mailbox/kram-policy.d.ts.map +1 -1
- package/types/core/mailbox/kraming.d.ts +1 -1
- package/types/core/mailbox/kraming.d.ts.map +1 -1
- package/types/core/records.d.ts +61 -0
- package/types/core/records.d.ts.map +1 -1
- package/types/core/routing.d.ts +6 -0
- package/types/core/routing.d.ts.map +1 -1
- package/types/db/basing.d.ts +8 -2
- package/types/db/basing.d.ts.map +1 -1
- package/types/db/core/indexeddb-persistence.d.ts +59 -0
- package/types/db/core/indexeddb-persistence.d.ts.map +1 -0
- package/types/db/indexeddb.d.ts +37 -0
- package/types/db/indexeddb.d.ts.map +1 -0
- package/types/db/keeping.d.ts +5 -2
- package/types/db/keeping.d.ts.map +1 -1
- package/types/db/store-lock.d.ts +72 -0
- package/types/db/store-lock.d.ts.map +1 -0
- package/types/npm/db-indexeddb.d.ts +4 -0
- package/types/npm/db-indexeddb.d.ts.map +1 -0
- package/types/npm/runtime-web.d.ts +4 -0
- package/types/npm/runtime-web.d.ts.map +1 -0
- package/types/runtime/index.d.ts +41 -6
- package/types/runtime/index.d.ts.map +1 -1
- package/types/runtime/web/output-barrier.d.ts +30 -0
- package/types/runtime/web/output-barrier.d.ts.map +1 -0
- package/types/runtime/web/persistent-storage.d.ts +36 -0
- package/types/runtime/web/persistent-storage.d.ts.map +1 -0
- package/types/runtime/web.d.ts +11 -0
- package/types/runtime/web.d.ts.map +1 -0
- package/types/vdr/tel-publication.d.ts +6 -0
- package/types/vdr/tel-publication.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
- package/esm/keri/npm/src/cli/common/tel-completion.js +0 -24
- package/types/cli/common/tel-completion.d.ts +0 -8
- package/types/cli/common/tel-completion.d.ts.map +0 -1
|
@@ -175,10 +175,10 @@ export class Kevery {
|
|
|
175
175
|
return false;
|
|
176
176
|
const query = envelope.serder.ked?.q;
|
|
177
177
|
const pre = typeof query?.i === "string" ? query.i : null;
|
|
178
|
-
if (!pre
|
|
178
|
+
if (!pre)
|
|
179
179
|
return false;
|
|
180
180
|
const recipient = this.db.getKever(pre, { refresh: true });
|
|
181
|
-
return !!recipient && authenticatesMailboxQuery(envelope, recipient);
|
|
181
|
+
return !!recipient && authenticatesMailboxQuery(envelope, recipient, this.db);
|
|
182
182
|
}
|
|
183
183
|
/**
|
|
184
184
|
* Decide how one live query should be handled without mutating escrow state.
|
|
@@ -280,14 +280,11 @@ export class Kevery {
|
|
|
280
280
|
}]);
|
|
281
281
|
}
|
|
282
282
|
case "mbx": {
|
|
283
|
-
if (dest !== pre) {
|
|
284
|
-
return dropQuery("unauthorizedMailboxRequester");
|
|
285
|
-
}
|
|
286
283
|
const recipient = this.db.getKever(pre, { refresh: true });
|
|
287
284
|
if (!recipient) {
|
|
288
285
|
return escrowQuery("missingKever");
|
|
289
286
|
}
|
|
290
|
-
if (!authenticatesMailboxQuery(envelope, recipient)) {
|
|
287
|
+
if (!authenticatesMailboxQuery(envelope, recipient, this.db)) {
|
|
291
288
|
return dropQuery("unauthorizedMailboxRequester");
|
|
292
289
|
}
|
|
293
290
|
return acceptQuery([{
|
|
@@ -2375,22 +2372,81 @@ function queryRequester(envelope) {
|
|
|
2375
2372
|
* Mailbox reads authorize private recipient bytes, unlike public KEL queries.
|
|
2376
2373
|
* Require the recipient's current signing threshold; merely naming its prefix
|
|
2377
2374
|
* in endorsement material is not authentication. Repeated indices count once.
|
|
2375
|
+
*
|
|
2376
|
+
* Who may read a transferable recipient's mailbox on this host: the recipient itself, and, for a group, any
|
|
2377
|
+
* signing member that signs the poll as itself (the shape KERIpy's `GroupHab.query` emits: `q.i` is the group,
|
|
2378
|
+
* sender and signer are the member) when {@link provesGroupKeyControl} holds. Forwarded mail is plaintext CESR
|
|
2379
|
+
* unless the sender used ESSR, so this check is the read protection. KERIpy hosts are open: they do no read
|
|
2380
|
+
* authorization of `mbx` queries, so a poll KERIpy accepts can be refused here.
|
|
2381
|
+
*
|
|
2382
|
+
* Two refusals are by design. The host must hold the member's KEL: the keri-ts poller and `KelPublisher` submit it
|
|
2383
|
+
* (see `collectKelSubmission`), but a KERIpy member's poller does not, so its KEL has to reach the host by OOBI or
|
|
2384
|
+
* a mailbox add, and until then its poll is refused as unauthorized. And a rotation-only member (named in the
|
|
2385
|
+
* group's `rmids` but not its `smids`) holds no current group key, so it is refused until a rotation makes it a
|
|
2386
|
+
* signing member.
|
|
2378
2387
|
*/
|
|
2379
|
-
function authenticatesMailboxQuery(envelope, recipient) {
|
|
2388
|
+
function authenticatesMailboxQuery(envelope, recipient, db) {
|
|
2380
2389
|
if (!recipient.transferable) {
|
|
2381
2390
|
return (envelope.cigars ?? []).some((cigar) => cigar.verfer?.qb64 === recipient.pre
|
|
2382
2391
|
&& !cigar.verfer.transferable
|
|
2383
2392
|
&& cigar.verfer.verify(cigar.raw, envelope.serder.raw));
|
|
2384
2393
|
}
|
|
2385
|
-
|
|
2394
|
+
const requester = envelope.source?.qb64;
|
|
2395
|
+
if (!requester)
|
|
2396
|
+
return false;
|
|
2397
|
+
if (requester === recipient.pre)
|
|
2398
|
+
return satisfiesThreshold(envelope, recipient);
|
|
2399
|
+
// A group's poll is signed by one of its local members (the poller cannot sign for the group alone). Accept a
|
|
2400
|
+
// member whose own key state this host holds when its signatures meet its own current threshold and it provably
|
|
2401
|
+
// controlled one of the group's current keys.
|
|
2402
|
+
const member = db.getKever(requester, { refresh: true });
|
|
2403
|
+
if (!member?.transferable || !satisfiesThreshold(envelope, member))
|
|
2386
2404
|
return false;
|
|
2405
|
+
return provesGroupKeyControl(envelope, member, recipient, db);
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* Whether `member` provably controlled a key in `group`'s current key list, by either of two proofs.
|
|
2409
|
+
*
|
|
2410
|
+
* Listing a group key is not control of it: anyone can incept an identifier whose key list names a victim's
|
|
2411
|
+
* public key beside their own, so a key list or an event's `k` field proves nothing. Only a signature by that
|
|
2412
|
+
* key does. The proofs are:
|
|
2413
|
+
* - the poll itself carries a signature that verifies against a key at that index in `member`'s current key list
|
|
2414
|
+
* that is also in the group's current key list; or
|
|
2415
|
+
* - some establishment event in `member`'s KEL, as this host holds it, lists a group-current key at index i and
|
|
2416
|
+
* the host holds a signature by that index-i key on that same event that verifies. That is proof of possession
|
|
2417
|
+
* at the time the member used the key, and the KEL chain ties that event to the member's current keys, so a
|
|
2418
|
+
* member that rotated its own identifier ahead of the group is still recognised.
|
|
2419
|
+
*/
|
|
2420
|
+
function provesGroupKeyControl(envelope, member, group, db) {
|
|
2421
|
+
const groupKeys = new Set(group.verfers.map((verfer) => verfer.qb64));
|
|
2422
|
+
const signedPoll = (envelope.sigers ?? []).some((siger) => {
|
|
2423
|
+
const verfer = member.verfers[siger.index];
|
|
2424
|
+
return !!verfer && groupKeys.has(verfer.qb64) && verfer.verify(siger.raw, envelope.serder.raw);
|
|
2425
|
+
});
|
|
2426
|
+
if (signedPoll)
|
|
2427
|
+
return true;
|
|
2428
|
+
for (const [, said] of db.getKelItemIter(member.pre)) {
|
|
2429
|
+
const event = db.getEvtSerder(member.pre, said);
|
|
2430
|
+
if (!event?.estive)
|
|
2431
|
+
continue;
|
|
2432
|
+
const verfers = event.verfers;
|
|
2433
|
+
for (const siger of db.sigs.get(dgKey(member.pre, said))) {
|
|
2434
|
+
const verfer = verfers[siger.index];
|
|
2435
|
+
if (verfer && groupKeys.has(verfer.qb64) && verfer.verify(siger.raw, event.raw))
|
|
2436
|
+
return true;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
return false;
|
|
2440
|
+
}
|
|
2441
|
+
/** The indexed signatures on `envelope` that verify against `kever`'s current keys meet its signing threshold. */
|
|
2442
|
+
function satisfiesThreshold(envelope, kever) {
|
|
2387
2443
|
const indices = new Set();
|
|
2388
2444
|
for (const siger of envelope.sigers ?? []) {
|
|
2389
|
-
const verfer =
|
|
2445
|
+
const verfer = kever.verfers[siger.index];
|
|
2390
2446
|
if (verfer && verfer.verify(siger.raw, envelope.serder.raw))
|
|
2391
2447
|
indices.add(siger.index);
|
|
2392
2448
|
}
|
|
2393
|
-
return
|
|
2449
|
+
return kever.tholder?.satisfy([...indices]) ?? false;
|
|
2394
2450
|
}
|
|
2395
2451
|
/** Return the reply destination derived from one normalized query envelope. */
|
|
2396
2452
|
function queryReplyDest(envelope) {
|
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
/** KRAM cache selection and safe, receiver-clocked configuration transitions. */
|
|
2
2
|
import { intToB64 } from "cesr-ts";
|
|
3
3
|
import { CacheTypeRecord } from "../records.js";
|
|
4
|
+
/**
|
|
5
|
+
* Windows an enabled policy uses when no caches are configured: one catchall row, in milliseconds
|
|
6
|
+
* `[d, sl, ll, xl, psl, pll, pxl]` (1 s drift, 5 s single-key, 60 s multi-key and 5 min exchange acceptance,
|
|
7
|
+
* pruned no earlier than they expire). KERIpy defines no built-in windows and requires `kram.caches` in
|
|
8
|
+
* configuration; these are the values its own tests and the keri-ts interop suites use, so an unconfigured
|
|
9
|
+
* runtime behaves like a configured KERIpy peer.
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_KRAM_CACHES = Object.freeze({
|
|
12
|
+
"~": Object.freeze([1000, 5000, 60000, 300000, 5000, 60000, 300000]),
|
|
13
|
+
});
|
|
4
14
|
/** Only this key owns the policy transition metadata in the existing settings table. */
|
|
5
15
|
const policyKey = "kram-policy";
|
|
16
|
+
/** The stored cache type for exact route, then message type, then the catchall; null when none is configured. */
|
|
17
|
+
function storedCacheType(db, ilk, route) {
|
|
18
|
+
const record = (route ? db.ctyp.get(`${ilk}.R.${route}`) : null) ?? db.ctyp.get(ilk) ?? db.ctyp.get("~");
|
|
19
|
+
if (!record)
|
|
20
|
+
return null;
|
|
21
|
+
const [d, sl, ll, xl, psl, pll, pxl] = values(record);
|
|
22
|
+
return { d, sl, ll, xl, psl, pll, pxl };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* How long a single-key query on `route` stays timely under the windows this store was configured with, in
|
|
26
|
+
* milliseconds: the single-key lag. A sender uses its own windows as the estimate of its peer's, as KERIpy's
|
|
27
|
+
* `requery` does. Null when this store has no KRAM cache types (KRAM never ran on it).
|
|
28
|
+
*/
|
|
29
|
+
export function singleKeyQueryWindow(db, route) {
|
|
30
|
+
return storedCacheType(db, "qry", route)?.sl ?? null;
|
|
31
|
+
}
|
|
6
32
|
/** Validate caller configuration before any durable policy mutation. */
|
|
7
33
|
function windows(value, transitional = false) {
|
|
8
34
|
if (!Array.isArray(value) || value.length !== 7 || value.some((v) => !Number.isSafeInteger(v) || v < 0)) {
|
|
@@ -148,11 +174,20 @@ export class KramPolicy {
|
|
|
148
174
|
return [intToB64(version[0], 1) + intToB64(version[1], 2), ...(type ? [type] : []), ...(route ? [route] : [])]
|
|
149
175
|
.join(".") + (route ? "" : ".");
|
|
150
176
|
});
|
|
151
|
-
const requested = configuration(config.caches);
|
|
177
|
+
const requested = configuration(config.caches ?? (this.enabled ? DEFAULT_KRAM_CACHES : undefined));
|
|
152
178
|
const stored = db.hbys.get(policyKey);
|
|
153
179
|
this.storedState = stored;
|
|
154
180
|
if (stored !== null) {
|
|
155
181
|
this.state = policyState(stored, db);
|
|
182
|
+
if (!Object.keys(this.state.caches).length && Object.keys(requested).length) {
|
|
183
|
+
// A store first opened without caches (KRAM off) has no rows to protect: install the windows directly.
|
|
184
|
+
this.state = { caches: requested, pending: {} };
|
|
185
|
+
this.commit(() => {
|
|
186
|
+
for (const [key, tuple] of Object.entries(requested))
|
|
187
|
+
db.ctyp.pin(key, cacheRecord(tuple));
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
156
191
|
const identical = sameKeys(Object.keys(requested), Object.keys(this.state.caches))
|
|
157
192
|
&& Object.keys(requested).every((key) => requested[key].every((v, i) => v === this.state.caches[key][i]));
|
|
158
193
|
if (config.caches !== undefined && !identical)
|
|
@@ -173,12 +208,10 @@ export class KramPolicy {
|
|
|
173
208
|
}
|
|
174
209
|
/** Select exact route, message type, then catchall; absence is a configuration failure. */
|
|
175
210
|
cacheType(ilk, route) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
if (!record)
|
|
211
|
+
const type = storedCacheType(this.db, ilk, route);
|
|
212
|
+
if (!type)
|
|
179
213
|
throw new Error("No KRAM cache type for message");
|
|
180
|
-
|
|
181
|
-
return { d, sl, ll, xl, psl, pll, pxl };
|
|
214
|
+
return type;
|
|
182
215
|
}
|
|
183
216
|
/** Stage expansions and reject coverage holes before changing interoperable rows. */
|
|
184
217
|
change(caches, override) {
|
|
@@ -3,6 +3,8 @@ import { Deck } from "../deck.js";
|
|
|
3
3
|
import { KramPolicy } from "./kram-policy.js";
|
|
4
4
|
import { KramPruner } from "./kram-pruning.js";
|
|
5
5
|
import { stageKram } from "./kram-staging.js";
|
|
6
|
+
/** Explicit receiver-owned cache and exemption configuration. */
|
|
7
|
+
export { DEFAULT_KRAM_CACHES } from "./kram-policy.js";
|
|
6
8
|
/** Optional receiver intake returns ordinary decisions without owning parser input. */
|
|
7
9
|
export class Kramer {
|
|
8
10
|
constructor(db, { config = {}, cues = new Deck(), now = Date.now } = {}) {
|
|
@@ -350,3 +350,26 @@ export class TxnMsgCacheRecord extends RawRecord {
|
|
|
350
350
|
*/
|
|
351
351
|
export class WellKnownAuthN extends RawRecord {
|
|
352
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* A `/registry/root` copy the Poster reported sent: coordinates only, never the root.
|
|
355
|
+
*
|
|
356
|
+
* keri-ts only. Stored through `Komer` in `Baser.rrsn`, keyed (group, registry, recipient).
|
|
357
|
+
*/
|
|
358
|
+
export class RootSentRecord extends RawRecord {
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* The KEL a mailbox was last given for an identifier: coordinates only. A 2xx is not acceptance (the host may
|
|
362
|
+
* escrow), which is why the poller still repairs a 403.
|
|
363
|
+
*
|
|
364
|
+
* keri-ts only. Stored through `Komer` in `Baser.kpub`, keyed (identifier, mailbox).
|
|
365
|
+
*/
|
|
366
|
+
export class KelPublishedRecord extends RawRecord {
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Public derivation coordinates of one blinded registry update: which credential, state and binding the
|
|
370
|
+
* update's opening is derived for. Never a salt or an opening.
|
|
371
|
+
*
|
|
372
|
+
* keri-ts only. Stored through `Komer` in `Baser.dvus`, keyed by the BUP SAID.
|
|
373
|
+
*/
|
|
374
|
+
export class DerivedUpdateRecord extends RawRecord {
|
|
375
|
+
}
|
|
@@ -321,6 +321,17 @@ export class Revery {
|
|
|
321
321
|
writable: true,
|
|
322
322
|
value: void 0
|
|
323
323
|
});
|
|
324
|
+
/**
|
|
325
|
+
* Told after an `/end/role` reply is accepted, with the controller, role, endpoint and whether the role is
|
|
326
|
+
* allowed. The runtime uses it to have the mailbox poller and KEL publisher look at a controller whose mailbox
|
|
327
|
+
* role just changed, rather than at their next sweep.
|
|
328
|
+
*/
|
|
329
|
+
Object.defineProperty(this, "onEndRole", {
|
|
330
|
+
enumerable: true,
|
|
331
|
+
configurable: true,
|
|
332
|
+
writable: true,
|
|
333
|
+
value: void 0
|
|
334
|
+
});
|
|
324
335
|
this.db = db;
|
|
325
336
|
this.rtr = rtr ?? new Router();
|
|
326
337
|
this.cues = cues ?? new Deck();
|
|
@@ -843,6 +854,8 @@ export class BasicReplyRouteHandler {
|
|
|
843
854
|
});
|
|
844
855
|
if (queryCues.length)
|
|
845
856
|
this.rvy.cues.extend(queryCues);
|
|
857
|
+
if (decision.kind === "accept")
|
|
858
|
+
this.rvy.onEndRole?.(cid, role, eid, allowed);
|
|
846
859
|
return decision;
|
|
847
860
|
}
|
|
848
861
|
/**
|
|
@@ -5,7 +5,7 @@ import { attachmentCounterPayloadQb64b, attachmentCounterQb64b, attachmentOrdina
|
|
|
5
5
|
import { DatabaseOperationError, ValidationError } from "../core/errors.js";
|
|
6
6
|
import { Kever } from "../core/kel/kever.js";
|
|
7
7
|
import { consoleLogger } from "../core/logger.js";
|
|
8
|
-
import { CacheTypeRecord, EndpointRecord, EventSourceRecord, HabitatRecord, KeyStateRecord, LocationRecord, MsgCacheRecord, ObservedRecord, OobiRecord, TopicsRecord, TxnMsgCacheRecord, WellKnownAuthN, } from "../core/records.js";
|
|
8
|
+
import { CacheTypeRecord, EndpointRecord, EventSourceRecord, HabitatRecord, KelPublishedRecord, KeyStateRecord, LocationRecord, MsgCacheRecord, ObservedRecord, OobiRecord, RootSentRecord, TopicsRecord, TxnMsgCacheRecord, WellKnownAuthN, } from "../core/records.js";
|
|
9
9
|
import { encodeDateTimeToDater, makeNowIso8601 } from "../time/mod.js";
|
|
10
10
|
import { dgKey, splitOnKey } from "./core/keys.js";
|
|
11
11
|
import { StoreOwner } from "./core/store-owner.js";
|
|
@@ -13,7 +13,7 @@ import { ExchangeRecord, ExchangeRegistryStateRecord } from "./exchange/exchange
|
|
|
13
13
|
import { IoSetKomer, Komer } from "./koming.js";
|
|
14
14
|
import { CapturedKelReplay } from "./replay/kel-replay.js";
|
|
15
15
|
import { CapturedWitnessReplay } from "./replay/witness-replay.js";
|
|
16
|
-
import { B64OnIoDupSuber, BytesIoSetSuber, CatCesrIoSetSuber, CatCesrSuber, CesrIoSetSuber, CesrOnSuber, CesrSuber, IoSetSuber, OnIoDupSuber, OnSuber, SchemerSuber, SerderSuber, Suber, } from "./subing.js";
|
|
16
|
+
import { B64OnIoDupSuber, BytesIoSetSuber, BytesSuber, CatCesrIoSetSuber, CatCesrSuber, CesrIoSetSuber, CesrOnSuber, CesrSuber, IoSetSuber, OnIoDupSuber, OnSuber, SchemerSuber, SerderSuber, Suber, } from "./subing.js";
|
|
17
17
|
function isMissingReloadEventError(error) {
|
|
18
18
|
return error instanceof Error
|
|
19
19
|
&& error.message.startsWith("Missing accepted event for reloaded Kever state ");
|
|
@@ -510,6 +510,20 @@ export class Baser extends StoreOwner {
|
|
|
510
510
|
writable: true,
|
|
511
511
|
value: void 0
|
|
512
512
|
});
|
|
513
|
+
/** keri-ts only. Sent `/registry/root` copies; (group, registry, recipient) -> recipient key sealed to and send time. Never a root. */
|
|
514
|
+
Object.defineProperty(this, "rrsn", {
|
|
515
|
+
enumerable: true,
|
|
516
|
+
configurable: true,
|
|
517
|
+
writable: true,
|
|
518
|
+
value: void 0
|
|
519
|
+
});
|
|
520
|
+
/** keri-ts only. The KEL a mailbox was last given: (identifier, mailbox) -> establishment SAID, time and outcome. */
|
|
521
|
+
Object.defineProperty(this, "kpub", {
|
|
522
|
+
enumerable: true,
|
|
523
|
+
configurable: true,
|
|
524
|
+
writable: true,
|
|
525
|
+
value: void 0
|
|
526
|
+
});
|
|
513
527
|
Object.defineProperty(this, "chas", {
|
|
514
528
|
enumerable: true,
|
|
515
529
|
configurable: true,
|
|
@@ -714,6 +728,18 @@ export class Baser extends StoreOwner {
|
|
|
714
728
|
writable: true,
|
|
715
729
|
value: void 0
|
|
716
730
|
}); // Shared KRAM exchange opener time keyed by XID.
|
|
731
|
+
Object.defineProperty(this, "heldq", {
|
|
732
|
+
enumerable: true,
|
|
733
|
+
configurable: true,
|
|
734
|
+
writable: true,
|
|
735
|
+
value: void 0
|
|
736
|
+
}); // Authenticated DH queries received before consent, keyed by XID.
|
|
737
|
+
Object.defineProperty(this, "heldu", {
|
|
738
|
+
enumerable: true,
|
|
739
|
+
configurable: true,
|
|
740
|
+
writable: true,
|
|
741
|
+
value: void 0
|
|
742
|
+
}); // Authenticated Unblind queries a discloser owes a reply to, keyed by `(discloser, XID)`.
|
|
717
743
|
Object.defineProperty(this, "tmsc", {
|
|
718
744
|
enumerable: true,
|
|
719
745
|
configurable: true,
|
|
@@ -1094,6 +1120,10 @@ export class Baser extends StoreOwner {
|
|
|
1094
1120
|
});
|
|
1095
1121
|
// Registry root copies awaiting automatic retention, keyed by exchange SAID.
|
|
1096
1122
|
this.rrpe = new CesrSuber(this.store, { subkey: "rrpe.", ctor: Dater });
|
|
1123
|
+
// Registry root copies sent, keyed by group, registry and recipient.
|
|
1124
|
+
this.rrsn = new Komer(this.store, { subkey: "rrsn.", recordClass: RootSentRecord });
|
|
1125
|
+
// KEL last given to each mailbox, keyed by identifier and mailbox.
|
|
1126
|
+
this.kpub = new Komer(this.store, { subkey: "kpub.", recordClass: KelPublishedRecord });
|
|
1097
1127
|
// Accepted signed challenge-response exchange-message SAIDs keyed by the
|
|
1098
1128
|
// signer prefix.
|
|
1099
1129
|
this.chas = new CesrIoSetSuber(this.store, {
|
|
@@ -1258,6 +1288,8 @@ export class Baser extends StoreOwner {
|
|
|
1258
1288
|
subkey: "tmsc.",
|
|
1259
1289
|
recordClass: TxnMsgCacheRecord,
|
|
1260
1290
|
});
|
|
1291
|
+
this.heldq = new BytesSuber(this.store, { subkey: "heldq." });
|
|
1292
|
+
this.heldu = new BytesSuber(this.store, { subkey: "heldu." });
|
|
1261
1293
|
this.ulgs = new CesrIoSetSuber(this.store, { subkey: "ulgs.", ctor: Prefixer });
|
|
1262
1294
|
this.cigs = new CatCesrIoSetSuber(this.store, { subkey: "cigs.", ctor: [Verfer, Cigar] });
|
|
1263
1295
|
// KRAM partially signed multi-key messages keyed by `(AID, MID)`.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDB `Persistence` for `WebDBer`.
|
|
3
|
+
*
|
|
4
|
+
* One database per store family and identity holds every logical table in a
|
|
5
|
+
* single `rows` object store, plus a `meta` store for the version marker and each table's
|
|
6
|
+
* dupsort flag. Tables are not object stores. Adding a table is therefore a
|
|
7
|
+
* plain write, never a schema `versionchange`, which another open connection
|
|
8
|
+
* could block indefinitely. This departs from the plan's "one object store per
|
|
9
|
+
* table", which ADR-0015 permits as the alternative to handling `versionchange`.
|
|
10
|
+
*
|
|
11
|
+
* Rows have out-of-line array keys `[table, keyBytes, valueOrEmpty]`. On a
|
|
12
|
+
* dupsort table the third part is the duplicate value, so each duplicate is its
|
|
13
|
+
* own row. On a plain table it is empty and the record holds the value. Empty
|
|
14
|
+
* dupsort values are rejected upstream, so an empty third part always means
|
|
15
|
+
* "plain". Binary parts are `ArrayBuffer`s. IndexedDB's key order is never
|
|
16
|
+
* relied on, because `WebDBer` sorts what it loads.
|
|
17
|
+
*
|
|
18
|
+
* The family is part of the database name because a composition's stores share
|
|
19
|
+
* one `name` and `base`; without it a Baser and Keeper would share a database,
|
|
20
|
+
* a version marker and a `clear`. A temp store is named apart from a durable one
|
|
21
|
+
* so clearing it cannot delete durable data of the same name.
|
|
22
|
+
*
|
|
23
|
+
* `write` is one `readwrite` transaction with strict durability and settles on
|
|
24
|
+
* the transaction's `complete` event, never on a request's `success`.
|
|
25
|
+
*
|
|
26
|
+
* Another tab's upgrade or delete fires `versionchange` here. The connection
|
|
27
|
+
* closes at once so that tab is never blocked, and this adapter is then lost:
|
|
28
|
+
* its next `load` or `write` rejects, which latches the queue, because writing
|
|
29
|
+
* on after another context deleted the database would break prefix durability.
|
|
30
|
+
* Recovery is a new open, whose `meta` reconnects. Single-writer exclusion is
|
|
31
|
+
* the composition's job, not this adapter's.
|
|
32
|
+
*
|
|
33
|
+
* A blocked `clear` rejects. Our own connection is already closed, and ours
|
|
34
|
+
* close on `versionchange`, so only a foreign connection can block. Reads settle on
|
|
35
|
+
* their transaction's `complete`, like writes: WebKit reports `blocked` for a
|
|
36
|
+
* connection whose close is still waiting on a finishing read transaction. The browser
|
|
37
|
+
* keeps the delete request pending and performs it once that connection closes,
|
|
38
|
+
* which is what `clear` asked for.
|
|
39
|
+
*/
|
|
40
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
41
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
42
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
43
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
44
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
45
|
+
};
|
|
46
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
47
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
48
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
49
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
50
|
+
};
|
|
51
|
+
var _IndexedDbPersistence_instances, _IndexedDbPersistence_databaseName, _IndexedDbPersistence_factory, _IndexedDbPersistence_keyRange, _IndexedDbPersistence_dupsort, _IndexedDbPersistence_db, _IndexedDbPersistence_lost, _IndexedDbPersistence_connect, _IndexedDbPersistence_connection;
|
|
52
|
+
import { DatabaseOperationError } from "../../core/errors.js";
|
|
53
|
+
const ROWS = "rows";
|
|
54
|
+
const META = "meta";
|
|
55
|
+
const VERSION_KEY = "version";
|
|
56
|
+
const TABLE_PREFIX = "table:";
|
|
57
|
+
const EMPTY = new ArrayBuffer(0);
|
|
58
|
+
/** An exact-length `ArrayBuffer` copy, since `.buffer` of a view may span more bytes. */
|
|
59
|
+
const buffer = (bytes) => bytes.slice().buffer;
|
|
60
|
+
/** Settle with a request's result. Only valid inside a transaction that is still open. */
|
|
61
|
+
function result(request) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
request.onsuccess = () => resolve(request.result);
|
|
64
|
+
request.onerror = () => reject(request.error);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Settle on `complete` or fail with the real cause on `abort` or `error`. */
|
|
68
|
+
function completion(transaction) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
let cause;
|
|
71
|
+
transaction.oncomplete = () => resolve();
|
|
72
|
+
transaction.onerror = (event) => cause ??= event.target?.error;
|
|
73
|
+
transaction.onabort = () => reject(new DatabaseOperationError("IndexedDB transaction aborted", {
|
|
74
|
+
cause: transaction.error ?? cause,
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** `Persistence` over IndexedDB. Methods are called one at a time by `WebDBer`. */
|
|
79
|
+
export class IndexedDbPersistence {
|
|
80
|
+
/**
|
|
81
|
+
* @param family Store family; with `identity` it chooses the database.
|
|
82
|
+
* @param identity Store identity; `name`, `base` and `temp` choose the database.
|
|
83
|
+
* @param factory Defaults to the global `indexedDB`.
|
|
84
|
+
* @param keyRange Defaults to the global `IDBKeyRange`; inject it with `factory` outside a browser.
|
|
85
|
+
*/
|
|
86
|
+
constructor(family, identity, factory = globalThis.indexedDB, keyRange = globalThis.IDBKeyRange) {
|
|
87
|
+
_IndexedDbPersistence_instances.add(this);
|
|
88
|
+
_IndexedDbPersistence_databaseName.set(this, void 0);
|
|
89
|
+
_IndexedDbPersistence_factory.set(this, void 0);
|
|
90
|
+
_IndexedDbPersistence_keyRange.set(this, void 0);
|
|
91
|
+
/** Dupsort flag per table, learned from `meta` and from each batch that creates one. */
|
|
92
|
+
_IndexedDbPersistence_dupsort.set(this, new Map());
|
|
93
|
+
_IndexedDbPersistence_db.set(this, null);
|
|
94
|
+
_IndexedDbPersistence_lost.set(this, null);
|
|
95
|
+
const place = [identity.base ?? "", identity.name ?? "", family.tail, identity.temp ? "temp" : "durable"];
|
|
96
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_databaseName, `keri-ts:${JSON.stringify(place)}`, "f");
|
|
97
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_factory, factory, "f");
|
|
98
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_keyRange, keyRange, "f");
|
|
99
|
+
}
|
|
100
|
+
async meta() {
|
|
101
|
+
const db = await __classPrivateFieldGet(this, _IndexedDbPersistence_instances, "m", _IndexedDbPersistence_connect).call(this);
|
|
102
|
+
const transaction = db.transaction(META, "readonly");
|
|
103
|
+
const store = transaction.objectStore(META);
|
|
104
|
+
const range = __classPrivateFieldGet(this, _IndexedDbPersistence_keyRange, "f").bound(TABLE_PREFIX, `${TABLE_PREFIX}`);
|
|
105
|
+
// Settle on `complete`: a connection with a transaction still finishing is not yet closed.
|
|
106
|
+
const [, version, names, flags] = await Promise.all([
|
|
107
|
+
completion(transaction),
|
|
108
|
+
result(store.get(VERSION_KEY)),
|
|
109
|
+
result(store.getAllKeys(range)),
|
|
110
|
+
result(store.getAll(range)),
|
|
111
|
+
]);
|
|
112
|
+
__classPrivateFieldGet(this, _IndexedDbPersistence_dupsort, "f").clear();
|
|
113
|
+
const tables = names.map((key, index) => {
|
|
114
|
+
const name = String(key).slice(TABLE_PREFIX.length);
|
|
115
|
+
const dupsort = flags[index] === true;
|
|
116
|
+
__classPrivateFieldGet(this, _IndexedDbPersistence_dupsort, "f").set(name, dupsort);
|
|
117
|
+
return { name, dupsort };
|
|
118
|
+
});
|
|
119
|
+
return { version: typeof version === "string" ? version : null, tables };
|
|
120
|
+
}
|
|
121
|
+
async load(table) {
|
|
122
|
+
const transaction = __classPrivateFieldGet(this, _IndexedDbPersistence_instances, "m", _IndexedDbPersistence_connection).call(this).transaction(ROWS, "readonly");
|
|
123
|
+
const store = transaction.objectStore(ROWS);
|
|
124
|
+
// Binary sorts below array, so `[table, []]` bounds every `[table, key, value]`.
|
|
125
|
+
const range = __classPrivateFieldGet(this, _IndexedDbPersistence_keyRange, "f").bound([table], [table, []]);
|
|
126
|
+
const [, keys, records] = await Promise.all([
|
|
127
|
+
completion(transaction),
|
|
128
|
+
result(store.getAllKeys(range)),
|
|
129
|
+
result(store.getAll(range)),
|
|
130
|
+
]);
|
|
131
|
+
return keys.map((row, index) => {
|
|
132
|
+
const [, key, duplicate] = row;
|
|
133
|
+
const value = duplicate.byteLength > 0 ? duplicate : records[index];
|
|
134
|
+
return { key: new Uint8Array(key), value: new Uint8Array(value) };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
write(batch) {
|
|
138
|
+
let transaction;
|
|
139
|
+
try {
|
|
140
|
+
transaction = __classPrivateFieldGet(this, _IndexedDbPersistence_instances, "m", _IndexedDbPersistence_connection).call(this).transaction([ROWS, META], "readwrite", { durability: "strict" });
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
return Promise.reject(error);
|
|
144
|
+
}
|
|
145
|
+
const done = completion(transaction);
|
|
146
|
+
try {
|
|
147
|
+
const meta = transaction.objectStore(META);
|
|
148
|
+
const rows = transaction.objectStore(ROWS);
|
|
149
|
+
for (const { name, dupsort } of batch.tables) {
|
|
150
|
+
__classPrivateFieldGet(this, _IndexedDbPersistence_dupsort, "f").set(name, dupsort);
|
|
151
|
+
meta.put(dupsort, `${TABLE_PREFIX}${name}`);
|
|
152
|
+
}
|
|
153
|
+
if (batch.version !== undefined)
|
|
154
|
+
meta.put(batch.version, VERSION_KEY);
|
|
155
|
+
for (const { table, key, value } of batch.puts) {
|
|
156
|
+
const dupsort = __classPrivateFieldGet(this, _IndexedDbPersistence_dupsort, "f").get(table);
|
|
157
|
+
if (dupsort === undefined)
|
|
158
|
+
throw new DatabaseOperationError(`Unknown table ${table}`);
|
|
159
|
+
rows.put(dupsort ? null : buffer(value), [table, buffer(key), dupsort ? buffer(value) : EMPTY]);
|
|
160
|
+
}
|
|
161
|
+
for (const { table, key, value } of batch.deletes) {
|
|
162
|
+
rows.delete([table, buffer(key), value ? buffer(value) : EMPTY]);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
transaction.abort();
|
|
167
|
+
return done.catch(() => {
|
|
168
|
+
throw error;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return done;
|
|
172
|
+
}
|
|
173
|
+
async clear() {
|
|
174
|
+
await this.close();
|
|
175
|
+
await new Promise((resolve, reject) => {
|
|
176
|
+
const request = __classPrivateFieldGet(this, _IndexedDbPersistence_factory, "f").deleteDatabase(__classPrivateFieldGet(this, _IndexedDbPersistence_databaseName, "f"));
|
|
177
|
+
request.onsuccess = () => resolve();
|
|
178
|
+
request.onerror = () => reject(request.error);
|
|
179
|
+
request.onblocked = () => reject(new DatabaseOperationError(`Cannot clear ${__classPrivateFieldGet(this, _IndexedDbPersistence_databaseName, "f")}: another connection holds it open; the delete stays pending until it closes`));
|
|
180
|
+
});
|
|
181
|
+
__classPrivateFieldGet(this, _IndexedDbPersistence_dupsort, "f").clear();
|
|
182
|
+
}
|
|
183
|
+
close() {
|
|
184
|
+
__classPrivateFieldGet(this, _IndexedDbPersistence_db, "f")?.close();
|
|
185
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_db, null, "f");
|
|
186
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_lost, null, "f");
|
|
187
|
+
return Promise.resolve();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
_IndexedDbPersistence_databaseName = new WeakMap(), _IndexedDbPersistence_factory = new WeakMap(), _IndexedDbPersistence_keyRange = new WeakMap(), _IndexedDbPersistence_dupsort = new WeakMap(), _IndexedDbPersistence_db = new WeakMap(), _IndexedDbPersistence_lost = new WeakMap(), _IndexedDbPersistence_instances = new WeakSet(), _IndexedDbPersistence_connect = function _IndexedDbPersistence_connect() {
|
|
191
|
+
if (__classPrivateFieldGet(this, _IndexedDbPersistence_db, "f"))
|
|
192
|
+
return Promise.resolve(__classPrivateFieldGet(this, _IndexedDbPersistence_db, "f"));
|
|
193
|
+
if (!__classPrivateFieldGet(this, _IndexedDbPersistence_factory, "f"))
|
|
194
|
+
throw new DatabaseOperationError("IndexedDB is not available in this environment");
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
const request = __classPrivateFieldGet(this, _IndexedDbPersistence_factory, "f").open(__classPrivateFieldGet(this, _IndexedDbPersistence_databaseName, "f"), 1);
|
|
197
|
+
request.onupgradeneeded = () => {
|
|
198
|
+
const db = request.result;
|
|
199
|
+
db.createObjectStore(ROWS);
|
|
200
|
+
db.createObjectStore(META);
|
|
201
|
+
};
|
|
202
|
+
request.onerror = () => reject(request.error);
|
|
203
|
+
request.onsuccess = () => {
|
|
204
|
+
const db = request.result;
|
|
205
|
+
db.onversionchange = () => {
|
|
206
|
+
db.close();
|
|
207
|
+
if (__classPrivateFieldGet(this, _IndexedDbPersistence_db, "f") === db) {
|
|
208
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_db, null, "f");
|
|
209
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_lost, new DatabaseOperationError("IndexedDB connection closed by another context's versionchange"), "f");
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_lost, null, "f");
|
|
213
|
+
__classPrivateFieldSet(this, _IndexedDbPersistence_db, db, "f");
|
|
214
|
+
resolve(db);
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
}, _IndexedDbPersistence_connection = function _IndexedDbPersistence_connection() {
|
|
218
|
+
if (__classPrivateFieldGet(this, _IndexedDbPersistence_db, "f"))
|
|
219
|
+
return __classPrivateFieldGet(this, _IndexedDbPersistence_db, "f");
|
|
220
|
+
throw __classPrivateFieldGet(this, _IndexedDbPersistence_lost, "f") ?? new DatabaseOperationError("IndexedDB persistence is not connected");
|
|
221
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser IndexedDB backend surface for `keri-ts/db/indexeddb`.
|
|
3
|
+
*
|
|
4
|
+
* Kept off `keri-ts/db` because `IndexedDbPersistence` names DOM types
|
|
5
|
+
* (`IDBFactory`, `IDBKeyRange`) that Node consumers should not have to type-check.
|
|
6
|
+
*/
|
|
7
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
8
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
11
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
12
|
+
};
|
|
13
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
14
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
15
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
16
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
17
|
+
};
|
|
18
|
+
var _LockedWebDBer_instances, _LockedWebDBer_group, _LockedWebDBer_identity, _LockedWebDBer_ref, _LockedWebDBer_reopening, _LockedWebDBer_letGo, _LockedStoreOpener_group;
|
|
19
|
+
import { call } from "effection";
|
|
20
|
+
import { IndexedDbPersistence } from "./core/indexeddb-persistence.js";
|
|
21
|
+
import { WebDBer, WebStoreOpener } from "./core/webdber.js";
|
|
22
|
+
import { acquireStoreLock, StoreLockGroup, StoreLockHeldError, storeLockKey, StoreLockUnsupportedError, } from "./store-lock.js";
|
|
23
|
+
export { acquireStoreLock, IndexedDbPersistence, StoreLockHeldError, storeLockKey, StoreLockUnsupportedError, };
|
|
24
|
+
/** A `WebDBer` that holds a reference on its opener's lock for as long as it is open. */
|
|
25
|
+
class LockedWebDBer extends WebDBer {
|
|
26
|
+
constructor(identity, persistence, queue, group) {
|
|
27
|
+
super(identity, persistence, queue);
|
|
28
|
+
_LockedWebDBer_instances.add(this);
|
|
29
|
+
_LockedWebDBer_group.set(this, void 0);
|
|
30
|
+
_LockedWebDBer_identity.set(this, void 0);
|
|
31
|
+
_LockedWebDBer_ref.set(this, void 0);
|
|
32
|
+
_LockedWebDBer_reopening.set(this, false);
|
|
33
|
+
__classPrivateFieldSet(this, _LockedWebDBer_group, group, "f");
|
|
34
|
+
__classPrivateFieldSet(this, _LockedWebDBer_identity, identity, "f");
|
|
35
|
+
}
|
|
36
|
+
/** Take the lock before `WebDBer.reopen` loads anything; any failure or refusal to open lets it go. */
|
|
37
|
+
*reopen(options = {}) {
|
|
38
|
+
// Assigned inside the promise so a halt while waiting cannot orphan the reference.
|
|
39
|
+
yield* call(async () => {
|
|
40
|
+
__classPrivateFieldSet(this, _LockedWebDBer_ref, __classPrivateFieldGet(this, _LockedWebDBer_ref, "f") ?? await __classPrivateFieldGet(this, _LockedWebDBer_group, "f").acquire(__classPrivateFieldGet(this, _LockedWebDBer_identity, "f")), "f");
|
|
41
|
+
});
|
|
42
|
+
__classPrivateFieldSet(this, _LockedWebDBer_reopening, true, "f");
|
|
43
|
+
let opened = false;
|
|
44
|
+
try {
|
|
45
|
+
opened = yield* super.reopen(options);
|
|
46
|
+
return opened;
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
__classPrivateFieldSet(this, _LockedWebDBer_reopening, false, "f");
|
|
50
|
+
if (!opened)
|
|
51
|
+
yield* __classPrivateFieldGet(this, _LockedWebDBer_instances, "m", _LockedWebDBer_letGo).call(this);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Close, then release the lock even when closing fails. `reopen`'s own inner close keeps it. */
|
|
55
|
+
*close(clear = false) {
|
|
56
|
+
try {
|
|
57
|
+
return yield* super.close(clear);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
if (!__classPrivateFieldGet(this, _LockedWebDBer_reopening, "f"))
|
|
61
|
+
yield* __classPrivateFieldGet(this, _LockedWebDBer_instances, "m", _LockedWebDBer_letGo).call(this);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
_LockedWebDBer_group = new WeakMap(), _LockedWebDBer_identity = new WeakMap(), _LockedWebDBer_ref = new WeakMap(), _LockedWebDBer_reopening = new WeakMap(), _LockedWebDBer_instances = new WeakSet(), _LockedWebDBer_letGo = function* _LockedWebDBer_letGo() {
|
|
66
|
+
const ref = __classPrivateFieldGet(this, _LockedWebDBer_ref, "f");
|
|
67
|
+
__classPrivateFieldSet(this, _LockedWebDBer_ref, undefined, "f");
|
|
68
|
+
if (ref)
|
|
69
|
+
yield* call(() => ref.release());
|
|
70
|
+
};
|
|
71
|
+
/** `WebStoreOpener` whose stores share one reference-counted lock per store identity. */
|
|
72
|
+
class LockedStoreOpener extends WebStoreOpener {
|
|
73
|
+
constructor(persistenceFor, locks) {
|
|
74
|
+
super(persistenceFor);
|
|
75
|
+
_LockedStoreOpener_group.set(this, void 0);
|
|
76
|
+
__classPrivateFieldSet(this, _LockedStoreOpener_group, new StoreLockGroup(locks), "f");
|
|
77
|
+
}
|
|
78
|
+
create(family, identity) {
|
|
79
|
+
return new LockedWebDBer(identity, this.persistenceFor(family, identity), this.queue, __classPrivateFieldGet(this, _LockedStoreOpener_group, "f"));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
_LockedStoreOpener_group = new WeakMap();
|
|
83
|
+
/**
|
|
84
|
+
* A `WebStoreOpener` whose stores persist to IndexedDB, one database per store
|
|
85
|
+
* family and identity. Inject `factory` and `keyRange` together outside a browser.
|
|
86
|
+
*
|
|
87
|
+
* Single-writer by default: the first store to open an identity (`base`, `name`, `temp`) takes a
|
|
88
|
+
* Web Locks lock, shared by every family the opener creates for it and released when the last of
|
|
89
|
+
* them closes. A second opener, tab or window on that identity is refused with
|
|
90
|
+
* `StoreLockHeldError`, and an engine without `navigator.locks` with `StoreLockUnsupportedError`;
|
|
91
|
+
* nothing runs unlocked unless `unsafeSkipStoreLock` says so.
|
|
92
|
+
*/
|
|
93
|
+
export function indexedDbOpener(options = {}) {
|
|
94
|
+
const persistenceFor = (family, identity) => new IndexedDbPersistence(family, identity, options.factory, options.keyRange);
|
|
95
|
+
return options.unsafeSkipStoreLock
|
|
96
|
+
? new WebStoreOpener(persistenceFor)
|
|
97
|
+
: new LockedStoreOpener(persistenceFor, options.locks === undefined ? {} : { locks: options.locks });
|
|
98
|
+
}
|