@sovereignbase/explorer 0.0.0-alpha.3 → 0.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/class.cjs +53 -33
- package/dist/class.cjs.map +1 -1
- package/dist/class.d.cts +40 -10
- package/dist/class.d.cts.map +1 -1
- package/dist/class.d.ts +40 -10
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +54 -35
- package/dist/class.js.map +1 -1
- package/package.json +4 -3
package/dist/class.cjs
CHANGED
|
@@ -57,10 +57,11 @@ function unknownError(cause, operation) {
|
|
|
57
57
|
}
|
|
58
58
|
// src/CredentialEntries/class.ts
|
|
59
59
|
/**
|
|
60
|
-
* Maintains the database's
|
|
60
|
+
* Maintains the database's registered credentials.
|
|
61
61
|
*
|
|
62
|
-
* Entries are identified by
|
|
63
|
-
* a sequenced array
|
|
62
|
+
* Entries are identified by their object and credential identifiers.
|
|
63
|
+
* Changes are synchronized through a sequenced array and persisted
|
|
64
|
+
* as encrypted snapshots.
|
|
64
65
|
*/
|
|
65
66
|
var CredentialEntries = class {
|
|
66
67
|
id;
|
|
@@ -74,7 +75,7 @@ var CredentialEntries = class {
|
|
|
74
75
|
*
|
|
75
76
|
* @param id The identifier of the persisted entries object.
|
|
76
77
|
* @param cipherKey The key used to encrypt the entries object.
|
|
77
|
-
* @param sessionCredentialID The credential ID of the current session.
|
|
78
|
+
* @param sessionCredentialID The WebAuthn credential ID of the current session.
|
|
78
79
|
* @param actorID The actor identifier used by the sequenced array.
|
|
79
80
|
* @param trustedSnapshot An optional snapshot used to initialize the state.
|
|
80
81
|
*/
|
|
@@ -100,13 +101,17 @@ var CredentialEntries = class {
|
|
|
100
101
|
this.state.addEventListener("change", ({ detail }) => {
|
|
101
102
|
this.dispatchEvent("change", detail);
|
|
102
103
|
});
|
|
103
|
-
const
|
|
104
|
+
const seenObjects = /* @__PURE__ */ new Set();
|
|
105
|
+
const seenCredentials = /* @__PURE__ */ new Set();
|
|
104
106
|
const duplicates = [];
|
|
105
107
|
for (let index = 0; index < this.state.length; index++) {
|
|
106
108
|
const entry = this.state.at(index);
|
|
107
109
|
if (!entry) continue;
|
|
108
|
-
if (
|
|
109
|
-
else
|
|
110
|
+
if (seenObjects.has(entry.objectID) || seenCredentials.has(entry.credentialID)) duplicates.push(index);
|
|
111
|
+
else {
|
|
112
|
+
seenObjects.add(entry.objectID);
|
|
113
|
+
seenCredentials.add(entry.credentialID);
|
|
114
|
+
}
|
|
110
115
|
}
|
|
111
116
|
for (let index = duplicates.length - 1; index >= 0; index--) this.state.delete(duplicates[index]);
|
|
112
117
|
if (duplicates.length > 0 || this.deltaBuffer.length > 0) this.state.snapshot();
|
|
@@ -127,17 +132,17 @@ var CredentialEntries = class {
|
|
|
127
132
|
record(credentialEvent) {
|
|
128
133
|
try {
|
|
129
134
|
if (!credentialEvent || typeof credentialEvent !== "object") throw new ExplorerError("INVALID_CREDENTIAL_EVENT", "A credential event must be an object.");
|
|
130
|
-
const {
|
|
131
|
-
if (typeof
|
|
135
|
+
const { objectID, credentialID, timestamp, userAgent, name, entropy, storage } = credentialEvent;
|
|
136
|
+
if (typeof objectID !== "string" || typeof timestamp !== "number" || !Number.isFinite(timestamp) || typeof userAgent !== "string") throw new ExplorerError("INVALID_CREDENTIAL_EVENT", "A credential event requires an object ID, a finite timestamp, and a user agent.");
|
|
132
137
|
let existing;
|
|
133
138
|
for (let index = this.state.length - 1; index >= 0; index--) {
|
|
134
139
|
const entry = this.state.at(index);
|
|
135
|
-
if (entry?.
|
|
140
|
+
if (entry?.objectID === objectID || credentialID !== void 0 && entry?.credentialID === credentialID) {
|
|
136
141
|
existing = entry;
|
|
137
142
|
break;
|
|
138
143
|
}
|
|
139
144
|
}
|
|
140
|
-
if (!existing && (typeof name !== "string" || !(entropy instanceof Uint8Array) || typeof storage !== "string")) throw new ExplorerError("MISSING_CREDENTIAL_SETUP_PROPERTIES", "A new credential requires a name, entropy, and binding storage.");
|
|
145
|
+
if (!existing && (typeof credentialID !== "string" || typeof name !== "string" || !(entropy instanceof Uint8Array) || typeof storage !== "string")) throw new ExplorerError("MISSING_CREDENTIAL_SETUP_PROPERTIES", "A new credential requires a credential ID, name, entropy, and binding storage.");
|
|
141
146
|
const userAgents = existing?.userAgents.map((agent) => ({ ...agent })) ?? [];
|
|
142
147
|
const agent = userAgents.find((agent) => agent.name === userAgent);
|
|
143
148
|
if (agent) agent.lastSeen = Math.max(agent.lastSeen, timestamp);
|
|
@@ -146,14 +151,18 @@ var CredentialEntries = class {
|
|
|
146
151
|
lastSeen: timestamp
|
|
147
152
|
});
|
|
148
153
|
const entry = {
|
|
149
|
-
|
|
154
|
+
objectID,
|
|
155
|
+
credentialID: credentialID ?? existing.credentialID,
|
|
150
156
|
name: name ?? existing.name,
|
|
151
157
|
entropy: entropy ?? existing.entropy,
|
|
152
158
|
storage: storage ?? existing.storage,
|
|
153
159
|
lastUsed: Math.max(existing?.lastUsed ?? timestamp, timestamp),
|
|
154
160
|
userAgents
|
|
155
161
|
};
|
|
156
|
-
for (let index = this.state.length - 1; index >= 0; index--)
|
|
162
|
+
for (let index = this.state.length - 1; index >= 0; index--) {
|
|
163
|
+
const current = this.state.at(index);
|
|
164
|
+
if (current?.objectID === entry.objectID || current?.credentialID === entry.credentialID) this.state.delete(index);
|
|
165
|
+
}
|
|
157
166
|
this.state.unshift(entry);
|
|
158
167
|
this.state.snapshot();
|
|
159
168
|
} catch (cause) {
|
|
@@ -161,24 +170,32 @@ var CredentialEntries = class {
|
|
|
161
170
|
}
|
|
162
171
|
}
|
|
163
172
|
/**
|
|
164
|
-
* Revokes a credential
|
|
173
|
+
* Revokes a credential and removes its registered entries.
|
|
165
174
|
*
|
|
166
|
-
*
|
|
175
|
+
* Deletes the credential's persisted objects before removing its entries.
|
|
176
|
+
* The entries remain unchanged if deletion fails. Credential invalidation
|
|
177
|
+
* is requested on a best-effort basis after successful removal.
|
|
167
178
|
*
|
|
168
|
-
* @param credentialID The
|
|
179
|
+
* @param credentialID The WebAuthn credential identifier to revoke.
|
|
169
180
|
*/
|
|
170
181
|
async revoke(credentialID) {
|
|
171
182
|
try {
|
|
172
|
-
|
|
183
|
+
const objectIDs = /* @__PURE__ */ new Set();
|
|
173
184
|
for (let index = this.state.length - 1; index >= 0; index--) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
break;
|
|
185
|
+
const entry = this.state.at(index);
|
|
186
|
+
if (entry?.credentialID === credentialID) objectIDs.add(entry.objectID);
|
|
177
187
|
}
|
|
178
|
-
if (
|
|
179
|
-
await Explorer.deleteObjectByID(
|
|
180
|
-
|
|
181
|
-
this.state.
|
|
188
|
+
if (objectIDs.size === 0) return;
|
|
189
|
+
for (const objectID of objectIDs) await Explorer.deleteObjectByID(objectID);
|
|
190
|
+
let removed = false;
|
|
191
|
+
for (let index = this.state.length - 1; index >= 0; index--) if (this.state.at(index)?.credentialID === credentialID) {
|
|
192
|
+
this.state.delete(index);
|
|
193
|
+
removed = true;
|
|
194
|
+
}
|
|
195
|
+
if (removed) this.state.snapshot();
|
|
196
|
+
try {
|
|
197
|
+
await (0, _sovereignbase_hardware_bound.invalidateDeviceBinding)(credentialID);
|
|
198
|
+
} catch {}
|
|
182
199
|
} catch (cause) {
|
|
183
200
|
throw unknownError(cause, "CredentialEntries.revoke()");
|
|
184
201
|
}
|
|
@@ -186,11 +203,11 @@ var CredentialEntries = class {
|
|
|
186
203
|
/**
|
|
187
204
|
* Returns the credential entries ordered by most recent use.
|
|
188
205
|
*
|
|
189
|
-
* Entries with equal timestamps are ordered by identifier.
|
|
206
|
+
* Entries with equal timestamps are ordered by credential identifier.
|
|
190
207
|
*/
|
|
191
208
|
list() {
|
|
192
209
|
try {
|
|
193
|
-
return this.state.slice().sort((a, b) => b.lastUsed - a.lastUsed || a.
|
|
210
|
+
return this.state.slice().sort((a, b) => b.lastUsed - a.lastUsed || a.credentialID.localeCompare(b.credentialID));
|
|
194
211
|
} catch (cause) {
|
|
195
212
|
throw unknownError(cause, "CredentialEntries.list()");
|
|
196
213
|
}
|
|
@@ -478,26 +495,28 @@ var Explorer = class {
|
|
|
478
495
|
static async authenticate(dataPIN, storageURL, setupParams) {
|
|
479
496
|
try {
|
|
480
497
|
this.storageURL = storageURL;
|
|
481
|
-
const
|
|
482
|
-
if (!
|
|
498
|
+
const deviceBinding = await (0, _sovereignbase_hardware_bound.accessDeviceBinding)();
|
|
499
|
+
if (!Array.isArray(deviceBinding)) return "authenticator-error";
|
|
500
|
+
const [credentialID, deviceEntropy] = deviceBinding;
|
|
483
501
|
this.sessionCredential = {
|
|
484
502
|
timestamp: Date.now(),
|
|
485
503
|
userAgent: (0, _sovereignbase_dom_utils.readableUserAgent)()
|
|
486
504
|
};
|
|
487
505
|
const dataPINBytes = _sovereignbase_bytecodec.Bytes.utf8.decode(dataPIN);
|
|
488
506
|
const credentialSecret = await _sovereignbase_bytecodec.Bytes.derive(deviceEntropy, dataPINBytes, 32);
|
|
489
|
-
const
|
|
490
|
-
this.sessionCredential.
|
|
507
|
+
const credentialObjectID = await _sovereignbase_cryptosuite.Cryptographic.identifier.derive(credentialSecret, _sovereignbase_bytecodec.Bytes.utf8.decode("@sovereignbase/explorer:CredentialObjectID"), 32);
|
|
508
|
+
this.sessionCredential.objectID = credentialObjectID;
|
|
509
|
+
this.sessionCredential.credentialID = credentialID;
|
|
491
510
|
const credentialCipherKey = await _sovereignbase_cryptosuite.Cryptographic.cipherMessage.deriveKey(credentialSecret, _sovereignbase_bytecodec.Bytes.utf8.decode("@sovereignbase/explorer:CredentialCipherKey"));
|
|
492
511
|
let virtualEntropy;
|
|
493
512
|
if ((0, _sovereignbase_utils.isRecord)(setupParams)) {
|
|
494
513
|
virtualEntropy = _sovereignbase_bytecodec.Bytes.generate(64);
|
|
495
|
-
await this.storeObjectByID(
|
|
514
|
+
await this.storeObjectByID(credentialObjectID, virtualEntropy, credentialCipherKey);
|
|
496
515
|
this.sessionCredential.name = setupParams.credentialName;
|
|
497
516
|
this.sessionCredential.entropy = deviceEntropy;
|
|
498
517
|
this.sessionCredential.storage = setupParams.bindingStorage;
|
|
499
518
|
} else {
|
|
500
|
-
const result = await this.loadObjectByID(
|
|
519
|
+
const result = await this.loadObjectByID(credentialObjectID, credentialCipherKey);
|
|
501
520
|
if (!(result instanceof Uint8Array) || result.byteLength !== 64) return "invalid-data-pin";
|
|
502
521
|
virtualEntropy = result;
|
|
503
522
|
}
|
|
@@ -748,7 +767,7 @@ var Explorer = class {
|
|
|
748
767
|
const id = await _sovereignbase_cryptosuite.Cryptographic.identifier.derive(this.databaseSecret, _sovereignbase_bytecodec.Bytes.utf8.decode("@sovereignbase/explorer:CredentialEntriesID"), 32);
|
|
749
768
|
const ck = await _sovereignbase_cryptosuite.Cryptographic.cipherMessage.deriveKey(this.databaseSecret, _sovereignbase_bytecodec.Bytes.utf8.decode("@sovereignbase/explorer:CredentialEntriesCipherKey"));
|
|
750
769
|
const trustedSnapshot = await this.loadObjectByID(id, ck, (snapshot) => void this.credentialEntries.merge(snapshot));
|
|
751
|
-
this.credentialEntries = new CredentialEntries(id, ck, this.sessionCredential.
|
|
770
|
+
this.credentialEntries = new CredentialEntries(id, ck, this.sessionCredential.credentialID, 0, trustedSnapshot);
|
|
752
771
|
return this.credentialEntries;
|
|
753
772
|
} catch (cause) {
|
|
754
773
|
throw unknownError(cause, "Explorer.loadCredentialEntries()");
|
|
@@ -756,5 +775,6 @@ var Explorer = class {
|
|
|
756
775
|
}
|
|
757
776
|
};
|
|
758
777
|
exports.Explorer = Explorer;
|
|
778
|
+
exports.ExplorerError = ExplorerError;
|
|
759
779
|
|
|
760
780
|
//# sourceMappingURL=class.cjs.map
|
package/dist/class.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class.cjs","names":["Sequenced","afterIdleFor","Sequenced","afterIdleFor","deriveDeviceEntropy","readableUserAgent","Bytes","Cryptographic","isRecord","loadObject","storeObject","deleteObject"],"sources":["../src/.errors/class.ts","../src/.errors/unkown.ts","../src/CredentialEntries/class.ts","../src/RecentEntries/class.ts","../src/class.ts"],"sourcesContent":["/** Identifies an Explorer failure independently of its human-readable message. */\nexport type ExplorerErrorCode =\n /** An unexpected failure was wrapped with its original cause. */\n | 'UNKNOWN_ERROR'\n /** A credential event is missing required identifying or usage information. */\n | 'INVALID_CREDENTIAL_EVENT'\n /** A new credential does not provide all required setup properties. */\n | 'MISSING_CREDENTIAL_SETUP_PROPERTIES'\n /** An operation requires an authenticated Explorer session. */\n | 'NOT_AUTHENTICATED'\n\n/**\n * An Explorer error with a stable, machine-readable code and an optional cause.\n *\n * The message is prefixed with the library name. Use {@link code} rather than\n * parsing the message when handling a particular failure.\n */\nexport class ExplorerError extends Error {\n /** The machine-readable category of the failure. */\n readonly code: ExplorerErrorCode\n\n /**\n * Creates an error whose name is `ExplorerError`.\n *\n * @param code The category of the failure.\n * @param message The detail to prefix with the library name; defaults to `code`.\n * @param options Error options, including the original `cause`.\n */\n constructor(\n code: ExplorerErrorCode,\n message?: string,\n options?: ErrorOptions\n ) {\n super(`{@sovereignbase/explorer} ${message ?? code}`, options)\n this.code = code\n this.name = 'ExplorerError'\n }\n}\n","import { ExplorerError } from './class.js'\n\n/**\n * Preserves an existing library error or wraps an unexpected failure once.\n * @param cause The value thrown by the failed operation.\n * @param operation The operation name to include in the error message.\n * @returns The original library error, or an `UNKNOWN_ERROR` retaining `cause`.\n */\nexport function unknownError(cause: unknown, operation: string): ExplorerError {\n return cause instanceof ExplorerError\n ? cause\n : new ExplorerError('UNKNOWN_ERROR', `${operation} failed.`, { cause })\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\nimport type { DeviceBindingStorage } from '@sovereignbase/hardware-bound'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { ExplorerError } from '../.errors/class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes a credential registered with the database.\n */\nexport type CredentialEntry = {\n /** The identifier of the credential. */\n id: string\n\n /** The display name of the credential. */\n name: string\n\n /** The entropy associated with the credential. */\n entropy: Uint8Array\n\n /** The storage mechanism used for device binding. */\n storage: DeviceBindingStorage\n\n /** The timestamp of the most recent use of the credential. */\n lastUsed: number\n\n /** The user agents that have used the credential. */\n userAgents: Array<{\n /** The user agent string. */\n name: string\n\n /** The timestamp of the most recent use by the user agent. */\n lastSeen: number\n }>\n}\n\n/**\n * Describes the properties used to create or update a credential entry.\n *\n * The identifier, timestamp, and user agent are required to record use.\n * Creating a credential additionally requires a name, entropy, and storage.\n */\nexport type CredentialEvent = {\n /** The identifier of the credential. */\n id?: string\n\n /** The timestamp of the credential use. */\n timestamp?: number\n\n /** The user agent associated with the credential use. */\n userAgent?: string\n\n /** The display name of the credential. */\n name?: string\n\n /** The entropy associated with the credential. */\n entropy?: Uint8Array\n\n /** The storage mechanism used for device binding. */\n storage?: DeviceBindingStorage\n}\n\n/**\n * Represents a sequenced delta produced by the credential entries list.\n */\nexport type CredentialEntriesDelta = Readonly<\n ['credentialEntries', DeepReadonly<SequencedArrayDelta<CredentialEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the credential entries list.\n */\nexport type CredentialEntriesChange = DeepReadonly<\n SequencedArrayChange<CredentialEntry>\n>\n\n/**\n * Maps credential entries event names to their detail types.\n */\nexport type CredentialEntriesEventMap = {\n /** Fired when the credential entries list changes. */\n change: CredentialEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link CredentialEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type CredentialEntriesEventListener<\n K extends keyof CredentialEntriesEventMap,\n> =\n | ((event: CustomEvent<CredentialEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<CredentialEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type CredentialEntriesEventListenerFor<K extends string> =\n K extends keyof CredentialEntriesEventMap\n ? CredentialEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the database's credential entries.\n *\n * Entries are identified by credential ID and synchronized through\n * a sequenced array. Changes are persisted as encrypted snapshots.\n */\nexport class CredentialEntries {\n private state: SequencedArray<CredentialEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<CredentialEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a credential entries list.\n *\n * @param id The identifier of the persisted entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param sessionCredentialID The credential ID of the current session.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n public readonly sessionCredentialID: string,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'credentialEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<CredentialEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n\n const seen = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (seen.has(entry.id)) duplicates.push(index)\n else seen.add(entry.id)\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.constructor()')\n }\n }\n\n /**\n * Creates or updates a credential entry.\n *\n * Updates the credential's last-used timestamp and the corresponding\n * user agent's last-seen timestamp. Existing properties are preserved\n * unless replacements are provided.\n *\n * @param credentialEvent The credential properties to apply.\n * @throws {ExplorerError} If the event is invalid or setup properties are missing.\n */\n public record(credentialEvent: CredentialEvent): void {\n try {\n if (!credentialEvent || typeof credentialEvent !== 'object')\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event must be an object.'\n )\n\n const { id, timestamp, userAgent, name, entropy, storage } =\n credentialEvent\n\n if (\n typeof id !== 'string' ||\n typeof timestamp !== 'number' ||\n !Number.isFinite(timestamp) ||\n typeof userAgent !== 'string'\n )\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event requires an ID, a finite timestamp, and a user agent.'\n )\n\n let existing: CredentialEntry | undefined\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const entry = this.state.at(index)\n if (entry?.id === id) {\n existing = entry\n break\n }\n }\n\n if (\n !existing &&\n (typeof name !== 'string' ||\n !(entropy instanceof Uint8Array) ||\n typeof storage !== 'string')\n )\n throw new ExplorerError(\n 'MISSING_CREDENTIAL_SETUP_PROPERTIES',\n 'A new credential requires a name, entropy, and binding storage.'\n )\n\n const userAgents =\n existing?.userAgents.map((agent) => ({ ...agent })) ?? []\n const agent = userAgents.find((agent) => agent.name === userAgent)\n\n if (agent) {\n agent.lastSeen = Math.max(agent.lastSeen, timestamp)\n } else {\n userAgents.push({ name: userAgent, lastSeen: timestamp })\n }\n\n const entry: CredentialEntry = {\n id,\n name: name ?? existing!.name,\n entropy: entropy ?? existing!.entropy,\n storage: storage ?? existing!.storage,\n lastUsed: Math.max(existing?.lastUsed ?? timestamp, timestamp),\n userAgents,\n }\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id === id) void this.state.delete(index)\n }\n\n void this.state.unshift(entry)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.record()')\n }\n }\n\n /**\n * Revokes a credential by deleting its persisted data and list entries.\n *\n * The list remains unchanged if deletion of the persisted credential fails.\n *\n * @param credentialID The identifier of the credential to revoke.\n */\n public async revoke(credentialID: string): Promise<void> {\n try {\n let exists = false\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id !== credentialID) continue\n exists = true\n break\n }\n\n if (!exists) return\n\n await Explorer.deleteObjectByID(credentialID)\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id === credentialID)\n void this.state.delete(index)\n }\n\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.revoke()')\n }\n }\n\n /**\n * Returns the credential entries ordered by most recent use.\n *\n * Entries with equal timestamps are ordered by identifier.\n */\n public list(): DeepReadonly<Array<CredentialEntry>> {\n try {\n return this.state\n .slice()\n .sort((a, b) => b.lastUsed - a.lastUsed || a.id.localeCompare(b.id))\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.list()')\n }\n }\n\n /**\n * Applies a sequenced delta to the credential entries list.\n *\n * @param delta The delta to apply.\n */\n public apply(delta: unknown): void {\n try {\n void this.state.apply(delta)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.apply()')\n }\n }\n\n /**\n * Merges a snapshot into the credential entries list.\n *\n * @param snapshot The snapshot to merge.\n */\n public merge(snapshot: unknown): void {\n try {\n void this.state.merge(snapshot)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.merge()')\n }\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public addEventListener<K extends keyof CredentialEntriesEventMap>(\n type: K,\n listener: CredentialEntriesEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public removeEventListener<K extends keyof CredentialEntriesEventMap>(\n type: K,\n listener: CredentialEntriesEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public dispatchEvent<K extends keyof CredentialEntriesEventMap>(\n type: K,\n detail: CredentialEntriesEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.dispatchEvent()')\n }\n }\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes an entry in the recent objects list.\n */\nexport type RecentEntry = {\n /** The identifier of the referenced object. */\n id: string\n\n /** The display name of the referenced object. */\n name: string\n\n /** The cipher key used to access the referenced object. */\n cipherKey: CipherKey\n\n /** The date and time at which the object was most recently accessed. */\n dateAccessed: number\n}\n\n/**\n * Represents a sequenced delta produced by the recent entries list.\n */\nexport type RecentEntriesDelta = Readonly<\n ['recentEntries', DeepReadonly<SequencedArrayDelta<RecentEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the recent entries list.\n */\nexport type RecentEntriesChange = DeepReadonly<\n SequencedArrayChange<RecentEntry>\n>\n\n/**\n * Maps recent entries event names to their detail types.\n */\nexport type RecentEntriesEventMap = {\n /** Fired when the recent entries list changes. */\n change: RecentEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link RecentEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type RecentEntriesEventListener<K extends keyof RecentEntriesEventMap> =\n | ((event: CustomEvent<RecentEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<RecentEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type RecentEntriesEventListenerFor<K extends string> =\n K extends keyof RecentEntriesEventMap\n ? RecentEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the ordered list of recently accessed objects.\n *\n * Entries are ordered from most recently accessed to least recently accessed,\n * deduplicated by identifier, and limited to 20 entries.\n */\nexport class RecentEntries {\n private state: SequencedArray<RecentEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<RecentEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a recent entries list.\n *\n * @param id The identifier of the persisted recent entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n //\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'recentEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n //\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<RecentEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n //\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n //\n const seen = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (seen.has(entry.id) || seen.size >= 20) duplicates.push(index)\n else seen.add(entry.id)\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n //\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n //\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.constructor()')\n }\n }\n\n /**\n * Marks an entry as most recently accessed.\n *\n * Any existing entry with the same identifier is removed before the new\n * entry is inserted at the beginning of the list.\n *\n * @param accessed The entry that was accessed.\n */\n public update(accessed: RecentEntry): void {\n try {\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id === accessed.id)\n void this.state.delete(index)\n }\n\n void this.state.unshift(accessed)\n\n while (this.state.length > 20) {\n void this.state.pop()\n }\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.update()')\n }\n }\n\n /**\n * Removes an entry from the recent entries list.\n *\n * The referenced object is not deleted.\n *\n * @param id The identifier of the entry to remove.\n */\n public delete(id: string): void {\n try {\n let deleted = false\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id !== id) continue\n void this.state.delete(index)\n deleted = true\n }\n if (deleted) void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.delete()')\n }\n }\n\n /**\n * Returns the recent entries ordered by most recent access.\n */\n public list(): DeepReadonly<Array<RecentEntry>> {\n try {\n return this.state\n .slice()\n .sort(\n (a, b) => b.dateAccessed - a.dateAccessed || a.id.localeCompare(b.id)\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.list()')\n }\n }\n //\n /**\n * Applies a sequenced delta to the recent entries list.\n *\n * @param delta The delta to apply.\n */\n public apply(delta: unknown): void {\n try {\n void this.state.apply(delta)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.apply()')\n }\n }\n\n /**\n * Merges a snapshot into the recent entries list.\n *\n * @param snapshot The snapshot to merge.\n */\n public merge(snapshot: unknown): void {\n try {\n void this.state.merge(snapshot)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.merge()')\n }\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public addEventListener<K extends keyof RecentEntriesEventMap>(\n type: K,\n listener: RecentEntriesEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public removeEventListener<K extends keyof RecentEntriesEventMap>(\n type: K,\n listener: RecentEntriesEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public dispatchEvent<K extends keyof RecentEntriesEventMap>(\n type: K,\n detail: RecentEntriesEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.dispatchEvent()')\n }\n }\n}\n","import {\n deleteObject,\n loadObject,\n storeObject,\n type URLString,\n} from '@sovereignbase/storage'\nimport { isRecord, type DeepReadonly } from '@sovereignbase/utils'\nimport { Bytes } from '@sovereignbase/bytecodec'\nimport { readableUserAgent } from '@sovereignbase/dom-utils'\nimport { deriveDeviceEntropy } from '@sovereignbase/hardware-bound'\nimport { Cryptographic, type CipherKey } from '@sovereignbase/cryptosuite'\n//\nimport type {\n AuthenticationResult,\n AuthenticationSetupParams,\n //\n ExplorerEventMap,\n ExplorerEventListenerFor,\n //\n ExplorerDelta,\n} from './.types/type.js'\n//\nimport { ExplorerError } from './.errors/class.js'\nimport { unknownError } from './.errors/unkown.js'\n//\nimport {\n CredentialEntries,\n type CredentialEntry,\n type CredentialEvent,\n type CredentialEntriesChange,\n} from './CredentialEntries/class.js'\n//\nimport {\n RecentEntries,\n type RecentEntry,\n type RecentEntriesChange,\n} from './RecentEntries/class.js'\n\n/**\n * Provides authentication, encrypted object storage, and access to\n * the database's synchronized entries.\n *\n * Maintains the authenticated database context and exposes static methods\n * for managing stored objects, observing entries, and applying remote deltas.\n */\nexport class Explorer {\n /** The identifier of the authenticated database. */\n public static databaseID: string\n\n /** Indicates whether the database has been authenticated. */\n public static isAuthenticated: boolean\n\n //\n private static storageURL: URLString\n private static eventTarget?: EventTarget\n private static databaseSecret: Readonly<Uint8Array>\n private static sessionCredential?: CredentialEvent = {}\n\n /**\n * Caches cipher keys by object identifier.\n *\n * Existing entries are preserved when an object is accessed with\n * a cipher key.\n */\n public static readonly cipherKeyCache: Map<string, CipherKey> = new Map()\n\n /**\n * Authenticates a database using a data PIN and device-bound entropy.\n *\n * Derives the credential and database identifiers from cryptographic\n * secrets. When setup parameters are provided, creates and stores a\n * new credential. Otherwise, loads the existing credential.\n *\n * Records the authenticated credential in the database's credential list.\n *\n * @param dataPIN The PIN used to derive the credential and database secrets.\n * @param storageURL The base URL used to access persisted database objects.\n * @param setupParams Optional parameters for creating a new credential.\n * @returns The authentication result.\n */\n public static async authenticate(\n dataPIN: string,\n storageURL: URLString,\n setupParams?: AuthenticationSetupParams\n ): Promise<AuthenticationResult> {\n try {\n this.storageURL = storageURL\n //\n const deviceEntropy = await deriveDeviceEntropy()\n if (!deviceEntropy) return 'authenticator-error'\n //\n this.sessionCredential = {\n timestamp: Date.now(),\n userAgent: readableUserAgent(),\n }\n //\n const dataPINBytes = Bytes.utf8.decode(dataPIN)\n //\n const credentialSecret = await Bytes.derive(\n deviceEntropy,\n dataPINBytes,\n 32\n )\n //\n const credentialID = await Cryptographic.identifier.derive(\n credentialSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialID'),\n 32\n )\n this.sessionCredential.id = credentialID\n //\n const credentialCipherKey = await Cryptographic.cipherMessage.deriveKey(\n credentialSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialCipherKey')\n )\n //\n let virtualEntropy: Uint8Array\n ////////\n // SETUP\n if (isRecord(setupParams)) {\n virtualEntropy = Bytes.generate(64)\n\n void (await this.storeObjectByID(\n credentialID,\n virtualEntropy,\n credentialCipherKey\n ))\n\n this.sessionCredential.name = setupParams.credentialName\n this.sessionCredential.entropy = deviceEntropy\n this.sessionCredential.storage = setupParams.bindingStorage\n }\n ////////\n // LOGIN\n else {\n const result = await this.loadObjectByID(\n credentialID,\n credentialCipherKey\n )\n if (!(result instanceof Uint8Array) || result.byteLength !== 64)\n return 'invalid-data-pin'\n\n virtualEntropy = result\n }\n //\n this.databaseSecret = await Bytes.derive(virtualEntropy, dataPINBytes, 64)\n\n this.databaseID = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:DatabaseID'),\n 32\n )\n\n this.isAuthenticated = true\n\n void (await this.loadCredentialEntries()).record(this.sessionCredential)\n delete this.sessionCredential\n\n void this.dispatchEvent('authenticated', true)\n\n return 'authenticated'\n } catch (cause) {\n throw unknownError(cause, 'Explorer.authenticate()')\n }\n }\n\n /**\n * Permanently revokes a credential and deletes its persisted data.\n *\n * Removes the credential from the database's registered credentials.\n *\n * @param credentialID The identifier of the credential to revoke.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async permanentlyDeleteCredential(\n credentialID: string\n ): Promise<void> {\n try {\n return void (await (\n await this.loadCredentialEntries()\n ).revoke(credentialID))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.permanentlyDeleteCredential()')\n }\n }\n\n /**\n * Returns the registered credentials and optionally observes their changes.\n *\n * The returned tuple contains a cleanup function, the current credential\n * entries, and the identifier of the current session's credential.\n *\n * The cleanup function removes the registered change listener, if any.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function, credential entries, and session credential ID.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async listCredentialEntries(\n onChange?: (splice: CredentialEntriesChange) => void\n ): Promise<\n [\n cleanup: () => void,\n entries: DeepReadonly<Array<CredentialEntry>>,\n sessionCredentialID: string,\n ]\n > {\n try {\n const credentialEntries = await this.loadCredentialEntries()\n\n const listener = onChange\n ? ({ detail }: CustomEvent<CredentialEntriesChange>) => onChange(detail)\n : undefined\n\n if (listener) credentialEntries.addEventListener('change', listener)\n\n return [\n () => {\n if (listener)\n credentialEntries.removeEventListener('change', listener)\n },\n credentialEntries.list(),\n credentialEntries.sessionCredentialID,\n ]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.listCredentialEntries()')\n }\n }\n\n /**\n * Returns the recently accessed objects and optionally observes their changes.\n *\n * The returned tuple contains a cleanup function and the current recent\n * entries. The cleanup function removes the registered change listener,\n * if any.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function and recent entries.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async listRecentEntries(\n onChange?: (splice: RecentEntriesChange) => void\n ): Promise<[cleanup: () => void, entries: DeepReadonly<Array<RecentEntry>>]> {\n try {\n const recentEntries = await this.loadRecentEntries()\n\n const listener = onChange\n ? ({ detail }: CustomEvent<RecentEntriesChange>) => onChange(detail)\n : undefined\n\n if (listener) recentEntries.addEventListener('change', listener)\n\n return [\n () => {\n if (listener) recentEntries.removeEventListener('change', listener)\n },\n recentEntries.list(),\n ]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.listRecentEntries()')\n }\n }\n\n /**\n * Loads an encrypted object using its identifier and cipher key.\n *\n * Caches the cipher key if the identifier has not been encountered before.\n *\n * @typeParam T The expected type of the loaded object.\n * @param id The identifier of the object to load.\n * @param cipherKey The key used to decrypt the object.\n * @param onNetworkResponse An optional callback for network responses.\n * @returns The loaded object, or `undefined` if no object is available.\n */\n public static async loadObjectByID<T>(\n id: string,\n cipherKey: CipherKey,\n onNetworkResponse?: (data: unknown) => void | Promise<void>\n ): Promise<T | undefined> {\n try {\n if (!this.cipherKeyCache.has(id))\n void this.cipherKeyCache.set(id, cipherKey)\n //\n return (await loadObject(\n (this.storageURL + id) as URLString,\n cipherKey,\n false,\n onNetworkResponse\n )) as T\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadObjectByID()')\n }\n }\n\n /**\n * Encrypts and stores an object using its identifier and cipher key.\n *\n * Caches the cipher key if the identifier has not been encountered before.\n *\n * @typeParam T The type of the object to store.\n * @param id The identifier of the object to store.\n * @param object The object to encrypt and store.\n * @param cipherKey The key used to encrypt the object.\n */\n public static async storeObjectByID<T>(\n id: string,\n object: T,\n cipherKey: CipherKey\n ): Promise<void> {\n try {\n if (!this.cipherKeyCache.has(id))\n void this.cipherKeyCache.set(id, cipherKey)\n //\n return void (await storeObject(\n (this.storageURL + id) as URLString,\n object,\n cipherKey\n ))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.storeObjectByID()')\n }\n }\n\n /**\n * Deletes a persisted object using its identifier.\n *\n * @param id The identifier of the object to delete.\n */\n public static async deleteObjectByID(id: string): Promise<void> {\n try {\n void (await deleteObject((this.storageURL + id) as URLString))\n return void (await this.loadRecentEntries()).delete(id)\n } catch (cause) {\n throw unknownError(cause, 'Explorer.deleteObjectByID()')\n }\n }\n\n /**\n * Schedules the application of a synchronized Explorer delta.\n *\n * Recognized deltas are forwarded to their corresponding entries list.\n * Invalid or unrecognized delta types are ignored.\n *\n * Processing is scheduled in a microtask and is not awaited by the caller.\n *\n * @param data The delta to apply.\n */\n public static apply(data: unknown): void {\n return void queueMicrotask(async () => {\n try {\n if (!this.isExplorerDelta(data)) return\n switch (data[0]) {\n case 'recentEntries': {\n void (await this.loadRecentEntries()).apply(data[1])\n break\n }\n case 'credentialEntries': {\n void (await this.loadCredentialEntries()).apply(data[1])\n break\n }\n }\n return\n } catch (cause) {\n throw unknownError(cause, 'Explorer.apply()')\n }\n })\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public static addEventListener<K extends keyof ExplorerEventMap>(\n type: K,\n listener: ExplorerEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'Explorer.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public static removeEventListener<K extends keyof ExplorerEventMap>(\n type: K,\n listener: ExplorerEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'Explorer.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public static dispatchEvent<K extends keyof ExplorerEventMap>(\n type: K,\n detail: ExplorerEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.dispatchEvent()')\n }\n }\n\n //* ██╗ ██╗███████╗██╗ ██████╗ ███████╗██████╗ ███████╗\n //* ██║ ██║██╔════╝██║ ██╔══██╗██╔════╝██╔══██╗██╔════╝\n //* ███████║█████╗ ██║ ██████╔╝█████╗ ██████╔╝███████╗\n //* ██╔══██║██╔══╝ ██║ ██╔═══╝ ██╔══╝ ██╔══██╗╚════██║\n //* ██║ ██║███████╗███████╗██║ ███████╗██║ ██║███████║\n //* ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝\n\n /**\n * Determines whether a value has the basic structure of an Explorer delta.\n *\n * @param data The value to inspect.\n * @returns Whether the value has a string discriminator.\n */\n private static isExplorerDelta(data: unknown): data is ExplorerDelta {\n return Array.isArray(data) && typeof data[0] === 'string'\n }\n\n private static recentEntries?: RecentEntries\n\n /**\n * Loads or returns the cached recent entries list.\n *\n * Derives the list identifier and cipher key from the database secret\n * and merges subsequent network responses into the loaded state.\n *\n * @returns The recent entries list.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n private static async loadRecentEntries(): Promise<RecentEntries> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (this.recentEntries) return this.recentEntries\n\n const id = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:RecentEntriesID'),\n 32\n )\n const ck = await Cryptographic.cipherMessage.deriveKey(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:RecentEntriesCipherKey')\n )\n\n const trustedSnapshot = await this.loadObjectByID(\n id,\n ck,\n (snapshot) => void this.recentEntries!.merge(snapshot)\n )\n\n this.recentEntries = new RecentEntries(id, ck, 0, trustedSnapshot)\n\n return this.recentEntries\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadRecentEntries()')\n }\n }\n\n private static credentialEntries?: CredentialEntries\n\n /**\n * Loads or returns the cached credential entries list.\n *\n * Derives the list identifier and cipher key from the database secret\n * and merges subsequent network responses into the loaded state.\n *\n * @returns The credential entries list.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n private static async loadCredentialEntries(): Promise<CredentialEntries> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (this.credentialEntries) return this.credentialEntries\n\n const id = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialEntriesID'),\n 32\n )\n const ck = await Cryptographic.cipherMessage.deriveKey(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialEntriesCipherKey')\n )\n\n const trustedSnapshot = await this.loadObjectByID(\n id,\n ck,\n (snapshot) => void this.credentialEntries!.merge(snapshot)\n )\n\n this.credentialEntries = new CredentialEntries(\n id,\n ck,\n this.sessionCredential!.id!,\n 0,\n trustedSnapshot\n )\n\n return this.credentialEntries\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadCredentialEntries()')\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"class.cjs","names":["Sequenced","afterIdleFor","invalidateDeviceBinding","Sequenced","afterIdleFor","accessDeviceBinding","readableUserAgent","Bytes","Cryptographic","isRecord","loadObject","storeObject","deleteObject"],"sources":["../src/.errors/class.ts","../src/.errors/unkown.ts","../src/CredentialEntries/class.ts","../src/RecentEntries/class.ts","../src/class.ts"],"sourcesContent":["/** Identifies an Explorer failure independently of its human-readable message. */\nexport type ExplorerErrorCode =\n /** An unexpected failure was wrapped with its original cause. */\n | 'UNKNOWN_ERROR'\n /** A credential event is missing required identifying or usage information. */\n | 'INVALID_CREDENTIAL_EVENT'\n /** A new credential does not provide all required setup properties. */\n | 'MISSING_CREDENTIAL_SETUP_PROPERTIES'\n /** An operation requires an authenticated Explorer session. */\n | 'NOT_AUTHENTICATED'\n\n/**\n * An Explorer error with a stable, machine-readable code and an optional cause.\n *\n * The message is prefixed with the library name. Use {@link code} rather than\n * parsing the message when handling a particular failure.\n */\nexport class ExplorerError extends Error {\n /** The machine-readable category of the failure. */\n readonly code: ExplorerErrorCode\n\n /**\n * Creates an error whose name is `ExplorerError`.\n *\n * @param code The category of the failure.\n * @param message The detail to prefix with the library name; defaults to `code`.\n * @param options Error options, including the original `cause`.\n */\n constructor(\n code: ExplorerErrorCode,\n message?: string,\n options?: ErrorOptions\n ) {\n super(`{@sovereignbase/explorer} ${message ?? code}`, options)\n this.code = code\n this.name = 'ExplorerError'\n }\n}\n","import { ExplorerError } from './class.js'\n\n/**\n * Preserves an existing library error or wraps an unexpected failure once.\n * @param cause The value thrown by the failed operation.\n * @param operation The operation name to include in the error message.\n * @returns The original library error, or an `UNKNOWN_ERROR` retaining `cause`.\n */\nexport function unknownError(cause: unknown, operation: string): ExplorerError {\n return cause instanceof ExplorerError\n ? cause\n : new ExplorerError('UNKNOWN_ERROR', `${operation} failed.`, { cause })\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\nimport {\n invalidateDeviceBinding,\n type DeviceBindingStorage,\n} from '@sovereignbase/hardware-bound'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { ExplorerError } from '../.errors/class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes a credential registered with the database.\n */\nexport type CredentialEntry = {\n /** The display name of the credential. */\n name: string\n\n /** The entropy associated with the credential. */\n entropy: Uint8Array\n\n /** The storage characteristics of the device binding. */\n storage: DeviceBindingStorage\n\n /** The identifier of the object storing virtual entropy protected by the credential. */\n objectID: string\n\n /** The WebAuthn credential identifier. */\n credentialID: string\n\n /** The timestamp of the most recent use of the credential. */\n lastUsed: number\n\n /** The user agents that have used the credential. */\n userAgents: Array<{\n /** The user agent string. */\n name: string\n\n /** The timestamp of the most recent use by the user agent. */\n lastSeen: number\n }>\n}\n\n/**\n * Describes the properties used to create or update a credential entry.\n *\n * The object identifier, timestamp, and user agent are required to record use.\n * Creating an entry additionally requires the credential identifier,\n * display name, entropy, and storage characteristics.\n */\nexport type CredentialEvent = {\n /** The identifier of the object storing the credential's virtual entropy. */\n objectID?: string\n\n /** The WebAuthn credential identifier. */\n credentialID?: string\n\n /** The timestamp of the credential use. */\n timestamp?: number\n\n /** The user agent associated with the credential use. */\n userAgent?: string\n\n /** The display name of the credential. */\n name?: string\n\n /** The entropy associated with the credential. */\n entropy?: Uint8Array\n\n /** The storage characteristics of the device binding. */\n storage?: DeviceBindingStorage\n}\n\n/**\n * Represents a sequenced delta produced by the credential entries list.\n */\nexport type CredentialEntriesDelta = Readonly<\n ['credentialEntries', DeepReadonly<SequencedArrayDelta<CredentialEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the credential entries list.\n */\nexport type CredentialEntriesChange = DeepReadonly<\n SequencedArrayChange<CredentialEntry>\n>\n\n/**\n * Maps credential entries event names to their detail types.\n */\nexport type CredentialEntriesEventMap = {\n /** Fired when the credential entries list changes. */\n change: CredentialEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link CredentialEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type CredentialEntriesEventListener<\n K extends keyof CredentialEntriesEventMap,\n> =\n | ((event: CustomEvent<CredentialEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<CredentialEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type CredentialEntriesEventListenerFor<K extends string> =\n K extends keyof CredentialEntriesEventMap\n ? CredentialEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the database's registered credentials.\n *\n * Entries are identified by their object and credential identifiers.\n * Changes are synchronized through a sequenced array and persisted\n * as encrypted snapshots.\n */\nexport class CredentialEntries {\n private state: SequencedArray<CredentialEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<CredentialEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a credential entries list.\n *\n * @param id The identifier of the persisted entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param sessionCredentialID The WebAuthn credential ID of the current session.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n public readonly sessionCredentialID: string,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'credentialEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<CredentialEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n\n const seenObjects = new Set<string>()\n const seenCredentials = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (\n seenObjects.has(entry.objectID) ||\n seenCredentials.has(entry.credentialID)\n ) {\n duplicates.push(index)\n } else {\n seenObjects.add(entry.objectID)\n seenCredentials.add(entry.credentialID)\n }\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.constructor()')\n }\n }\n\n /**\n * Creates or updates a credential entry.\n *\n * Updates the credential's last-used timestamp and the corresponding\n * user agent's last-seen timestamp. Existing properties are preserved\n * unless replacements are provided.\n *\n * @param credentialEvent The credential properties to apply.\n * @throws {ExplorerError} If the event is invalid or setup properties are missing.\n */\n public record(credentialEvent: CredentialEvent): void {\n try {\n if (!credentialEvent || typeof credentialEvent !== 'object')\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event must be an object.'\n )\n\n const {\n objectID,\n credentialID,\n timestamp,\n userAgent,\n name,\n entropy,\n storage,\n } = credentialEvent\n\n if (\n typeof objectID !== 'string' ||\n typeof timestamp !== 'number' ||\n !Number.isFinite(timestamp) ||\n typeof userAgent !== 'string'\n )\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event requires an object ID, a finite timestamp, and a user agent.'\n )\n\n let existing: CredentialEntry | undefined\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const entry = this.state.at(index)\n\n if (\n entry?.objectID === objectID ||\n (credentialID !== undefined && entry?.credentialID === credentialID)\n ) {\n existing = entry\n break\n }\n }\n\n if (\n !existing &&\n (typeof credentialID !== 'string' ||\n typeof name !== 'string' ||\n !(entropy instanceof Uint8Array) ||\n typeof storage !== 'string')\n )\n throw new ExplorerError(\n 'MISSING_CREDENTIAL_SETUP_PROPERTIES',\n 'A new credential requires a credential ID, name, entropy, and binding storage.'\n )\n\n const userAgents =\n existing?.userAgents.map((agent) => ({ ...agent })) ?? []\n\n const agent = userAgents.find((agent) => agent.name === userAgent)\n\n if (agent) {\n agent.lastSeen = Math.max(agent.lastSeen, timestamp)\n } else {\n userAgents.push({ name: userAgent, lastSeen: timestamp })\n }\n\n const entry: CredentialEntry = {\n objectID,\n credentialID: credentialID ?? existing!.credentialID,\n name: name ?? existing!.name,\n entropy: entropy ?? existing!.entropy,\n storage: storage ?? existing!.storage,\n lastUsed: Math.max(existing?.lastUsed ?? timestamp, timestamp),\n userAgents,\n }\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const current = this.state.at(index)\n\n if (\n current?.objectID === entry.objectID ||\n current?.credentialID === entry.credentialID\n )\n void this.state.delete(index)\n }\n\n void this.state.unshift(entry)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.record()')\n }\n }\n\n /**\n * Revokes a credential and removes its registered entries.\n *\n * Deletes the credential's persisted objects before removing its entries.\n * The entries remain unchanged if deletion fails. Credential invalidation\n * is requested on a best-effort basis after successful removal.\n *\n * @param credentialID The WebAuthn credential identifier to revoke.\n */\n public async revoke(credentialID: string): Promise<void> {\n try {\n const objectIDs = new Set<string>()\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const entry = this.state.at(index)\n\n if (entry?.credentialID === credentialID) objectIDs.add(entry.objectID)\n }\n\n if (objectIDs.size === 0) return\n\n for (const objectID of objectIDs)\n await Explorer.deleteObjectByID(objectID)\n\n let removed = false\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.credentialID === credentialID) {\n void this.state.delete(index)\n removed = true\n }\n }\n\n if (removed) void this.state.snapshot()\n\n try {\n await invalidateDeviceBinding(credentialID)\n } catch {\n // Credential invalidation is best-effort.\n }\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.revoke()')\n }\n }\n\n /**\n * Returns the credential entries ordered by most recent use.\n *\n * Entries with equal timestamps are ordered by credential identifier.\n */\n public list(): DeepReadonly<Array<CredentialEntry>> {\n try {\n return this.state\n .slice()\n .sort(\n (a, b) =>\n b.lastUsed - a.lastUsed ||\n a.credentialID.localeCompare(b.credentialID)\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.list()')\n }\n }\n\n /**\n * Applies a sequenced delta to the credential entries list.\n *\n * @param delta The delta to apply.\n */\n public apply(delta: unknown): void {\n try {\n void this.state.apply(delta)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.apply()')\n }\n }\n\n /**\n * Merges a snapshot into the credential entries list.\n *\n * @param snapshot The snapshot to merge.\n */\n public merge(snapshot: unknown): void {\n try {\n void this.state.merge(snapshot)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.merge()')\n }\n }\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public addEventListener<K extends keyof CredentialEntriesEventMap>(\n type: K,\n listener: CredentialEntriesEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public removeEventListener<K extends keyof CredentialEntriesEventMap>(\n type: K,\n listener: CredentialEntriesEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public dispatchEvent<K extends keyof CredentialEntriesEventMap>(\n type: K,\n detail: CredentialEntriesEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.dispatchEvent()')\n }\n }\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes an entry in the recent objects list.\n */\nexport type RecentEntry = {\n /** The identifier of the referenced object. */\n id: string\n\n /** The display name of the referenced object. */\n name: string\n\n /** The cipher key used to access the referenced object. */\n cipherKey: CipherKey\n\n /** The date and time at which the object was most recently accessed. */\n dateAccessed: number\n}\n\n/**\n * Represents a sequenced delta produced by the recent entries list.\n */\nexport type RecentEntriesDelta = Readonly<\n ['recentEntries', DeepReadonly<SequencedArrayDelta<RecentEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the recent entries list.\n */\nexport type RecentEntriesChange = DeepReadonly<\n SequencedArrayChange<RecentEntry>\n>\n\n/**\n * Maps recent entries event names to their detail types.\n */\nexport type RecentEntriesEventMap = {\n /** Fired when the recent entries list changes. */\n change: RecentEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link RecentEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type RecentEntriesEventListener<K extends keyof RecentEntriesEventMap> =\n | ((event: CustomEvent<RecentEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<RecentEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type RecentEntriesEventListenerFor<K extends string> =\n K extends keyof RecentEntriesEventMap\n ? RecentEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the ordered list of recently accessed objects.\n *\n * Entries are ordered from most recently accessed to least recently accessed,\n * deduplicated by identifier, and limited to 20 entries.\n */\nexport class RecentEntries {\n private state: SequencedArray<RecentEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<RecentEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a recent entries list.\n *\n * @param id The identifier of the persisted recent entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n //\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'recentEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n //\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<RecentEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n //\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n //\n const seen = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (seen.has(entry.id) || seen.size >= 20) duplicates.push(index)\n else seen.add(entry.id)\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n //\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n //\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.constructor()')\n }\n }\n\n /**\n * Marks an entry as most recently accessed.\n *\n * Any existing entry with the same identifier is removed before the new\n * entry is inserted at the beginning of the list.\n *\n * @param accessed The entry that was accessed.\n */\n public update(accessed: RecentEntry): void {\n try {\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id === accessed.id)\n void this.state.delete(index)\n }\n\n void this.state.unshift(accessed)\n\n while (this.state.length > 20) {\n void this.state.pop()\n }\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.update()')\n }\n }\n\n /**\n * Removes an entry from the recent entries list.\n *\n * The referenced object is not deleted.\n *\n * @param id The identifier of the entry to remove.\n */\n public delete(id: string): void {\n try {\n let deleted = false\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id !== id) continue\n void this.state.delete(index)\n deleted = true\n }\n if (deleted) void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.delete()')\n }\n }\n\n /**\n * Returns the recent entries ordered by most recent access.\n */\n public list(): DeepReadonly<Array<RecentEntry>> {\n try {\n return this.state\n .slice()\n .sort(\n (a, b) => b.dateAccessed - a.dateAccessed || a.id.localeCompare(b.id)\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.list()')\n }\n }\n //\n /**\n * Applies a sequenced delta to the recent entries list.\n *\n * @param delta The delta to apply.\n */\n public apply(delta: unknown): void {\n try {\n void this.state.apply(delta)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.apply()')\n }\n }\n\n /**\n * Merges a snapshot into the recent entries list.\n *\n * @param snapshot The snapshot to merge.\n */\n public merge(snapshot: unknown): void {\n try {\n void this.state.merge(snapshot)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.merge()')\n }\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public addEventListener<K extends keyof RecentEntriesEventMap>(\n type: K,\n listener: RecentEntriesEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public removeEventListener<K extends keyof RecentEntriesEventMap>(\n type: K,\n listener: RecentEntriesEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public dispatchEvent<K extends keyof RecentEntriesEventMap>(\n type: K,\n detail: RecentEntriesEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.dispatchEvent()')\n }\n }\n}\n","import {\n deleteObject,\n loadObject,\n storeObject,\n type URLString,\n} from '@sovereignbase/storage'\nimport { isRecord, type DeepReadonly } from '@sovereignbase/utils'\nimport { Bytes } from '@sovereignbase/bytecodec'\nimport { readableUserAgent } from '@sovereignbase/dom-utils'\nimport { accessDeviceBinding } from '@sovereignbase/hardware-bound'\nimport { Cryptographic, type CipherKey } from '@sovereignbase/cryptosuite'\n//\nimport type {\n AuthenticationResult,\n AuthenticationSetupParams,\n //\n ExplorerEventMap,\n ExplorerEventListenerFor,\n //\n ExplorerDelta,\n} from './.types/type.js'\n//\nimport { ExplorerError } from './.errors/class.js'\nimport { unknownError } from './.errors/unkown.js'\n//\nimport {\n CredentialEntries,\n type CredentialEntry,\n type CredentialEvent,\n type CredentialEntriesChange,\n} from './CredentialEntries/class.js'\n//\nimport {\n RecentEntries,\n type RecentEntry,\n type RecentEntriesChange,\n} from './RecentEntries/class.js'\n\n/**\n * Provides authentication, encrypted object storage, and access to\n * the database's synchronized entries.\n *\n * Maintains the authenticated database context and exposes static methods\n * for managing stored objects, observing entries, and applying remote deltas.\n */\nexport class Explorer {\n /** The identifier of the authenticated database. */\n public static databaseID: string\n\n /** Indicates whether the database has been authenticated. */\n public static isAuthenticated: boolean\n\n //\n private static storageURL: URLString\n private static eventTarget?: EventTarget\n private static databaseSecret: Readonly<Uint8Array>\n private static sessionCredential?: CredentialEvent = {}\n\n /**\n * Caches cipher keys by object identifier.\n *\n * Existing entries are preserved when an object is accessed with\n * a cipher key.\n */\n public static readonly cipherKeyCache: Map<string, CipherKey> = new Map()\n\n /**\n * Authenticates a database using a data PIN and device-bound entropy.\n *\n * Derives the credential and database identifiers from cryptographic\n * secrets. When setup parameters are provided, creates and stores a\n * new credential. Otherwise, loads the existing credential.\n *\n * Records the authenticated credential in the database's credential list.\n *\n * @param dataPIN The PIN used to derive the credential and database secrets.\n * @param storageURL The base URL used to access persisted database objects.\n * @param setupParams Optional parameters for creating a new credential.\n * @returns The authentication result.\n */\n public static async authenticate(\n dataPIN: string,\n storageURL: URLString,\n setupParams?: AuthenticationSetupParams\n ): Promise<AuthenticationResult> {\n try {\n this.storageURL = storageURL\n //\n const deviceBinding = await accessDeviceBinding()\n if (!Array.isArray(deviceBinding)) return 'authenticator-error'\n const [credentialID, deviceEntropy] = deviceBinding\n //\n this.sessionCredential = {\n timestamp: Date.now(),\n userAgent: readableUserAgent(),\n }\n //\n const dataPINBytes = Bytes.utf8.decode(dataPIN)\n //\n const credentialSecret = await Bytes.derive(\n deviceEntropy,\n dataPINBytes,\n 32\n )\n //\n const credentialObjectID = await Cryptographic.identifier.derive(\n credentialSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialObjectID'),\n 32\n )\n this.sessionCredential.objectID = credentialObjectID\n this.sessionCredential.credentialID = credentialID\n //\n const credentialCipherKey = await Cryptographic.cipherMessage.deriveKey(\n credentialSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialCipherKey')\n )\n //\n let virtualEntropy: Uint8Array\n ////////\n // SETUP\n if (isRecord(setupParams)) {\n virtualEntropy = Bytes.generate(64)\n\n void (await this.storeObjectByID(\n credentialObjectID,\n virtualEntropy,\n credentialCipherKey\n ))\n\n this.sessionCredential.name = setupParams.credentialName\n this.sessionCredential.entropy = deviceEntropy\n this.sessionCredential.storage = setupParams.bindingStorage\n }\n ////////\n // LOGIN\n else {\n const result = await this.loadObjectByID(\n credentialObjectID,\n credentialCipherKey\n )\n if (!(result instanceof Uint8Array) || result.byteLength !== 64)\n return 'invalid-data-pin'\n\n virtualEntropy = result\n }\n //\n this.databaseSecret = await Bytes.derive(virtualEntropy, dataPINBytes, 64)\n\n this.databaseID = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:DatabaseID'),\n 32\n )\n\n this.isAuthenticated = true\n\n void (await this.loadCredentialEntries()).record(this.sessionCredential)\n delete this.sessionCredential\n\n void this.dispatchEvent('authenticated', true)\n\n return 'authenticated'\n } catch (cause) {\n throw unknownError(cause, 'Explorer.authenticate()')\n }\n }\n\n /**\n * Permanently revokes a credential and deletes its persisted data.\n *\n * Removes the credential from the database's registered credentials.\n *\n * @param credentialID The identifier of the credential to revoke.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async permanentlyDeleteCredential(\n credentialID: string\n ): Promise<void> {\n try {\n return void (await (\n await this.loadCredentialEntries()\n ).revoke(credentialID))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.permanentlyDeleteCredential()')\n }\n }\n\n /**\n * Returns the registered credentials and optionally observes their changes.\n *\n * The returned tuple contains a cleanup function, the current credential\n * entries, and the identifier of the current session's credential.\n *\n * The cleanup function removes the registered change listener, if any.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function, credential entries, and session credential ID.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async listCredentialEntries(\n onChange?: (splice: CredentialEntriesChange) => void\n ): Promise<\n [\n cleanup: () => void,\n entries: DeepReadonly<Array<CredentialEntry>>,\n sessionCredentialID: string,\n ]\n > {\n try {\n const credentialEntries = await this.loadCredentialEntries()\n\n const listener = onChange\n ? ({ detail }: CustomEvent<CredentialEntriesChange>) => onChange(detail)\n : undefined\n\n if (listener) credentialEntries.addEventListener('change', listener)\n\n return [\n () => {\n if (listener)\n credentialEntries.removeEventListener('change', listener)\n },\n credentialEntries.list(),\n credentialEntries.sessionCredentialID,\n ]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.listCredentialEntries()')\n }\n }\n\n /**\n * Returns the recently accessed objects and optionally observes their changes.\n *\n * The returned tuple contains a cleanup function and the current recent\n * entries. The cleanup function removes the registered change listener,\n * if any.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function and recent entries.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async listRecentEntries(\n onChange?: (splice: RecentEntriesChange) => void\n ): Promise<[cleanup: () => void, entries: DeepReadonly<Array<RecentEntry>>]> {\n try {\n const recentEntries = await this.loadRecentEntries()\n\n const listener = onChange\n ? ({ detail }: CustomEvent<RecentEntriesChange>) => onChange(detail)\n : undefined\n\n if (listener) recentEntries.addEventListener('change', listener)\n\n return [\n () => {\n if (listener) recentEntries.removeEventListener('change', listener)\n },\n recentEntries.list(),\n ]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.listRecentEntries()')\n }\n }\n\n /**\n * Loads an encrypted object using its identifier and cipher key.\n *\n * Caches the cipher key if the identifier has not been encountered before.\n *\n * @typeParam T The expected type of the loaded object.\n * @param id The identifier of the object to load.\n * @param cipherKey The key used to decrypt the object.\n * @param onNetworkResponse An optional callback for network responses.\n * @returns The loaded object, or `undefined` if no object is available.\n */\n public static async loadObjectByID<T>(\n id: string,\n cipherKey: CipherKey,\n onNetworkResponse?: (data: unknown) => void | Promise<void>\n ): Promise<T | undefined> {\n try {\n if (!this.cipherKeyCache.has(id))\n void this.cipherKeyCache.set(id, cipherKey)\n //\n return (await loadObject(\n (this.storageURL + id) as URLString,\n cipherKey,\n false,\n onNetworkResponse\n )) as T\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadObjectByID()')\n }\n }\n\n /**\n * Encrypts and stores an object using its identifier and cipher key.\n *\n * Caches the cipher key if the identifier has not been encountered before.\n *\n * @typeParam T The type of the object to store.\n * @param id The identifier of the object to store.\n * @param object The object to encrypt and store.\n * @param cipherKey The key used to encrypt the object.\n */\n public static async storeObjectByID<T>(\n id: string,\n object: T,\n cipherKey: CipherKey\n ): Promise<void> {\n try {\n if (!this.cipherKeyCache.has(id))\n void this.cipherKeyCache.set(id, cipherKey)\n //\n return void (await storeObject(\n (this.storageURL + id) as URLString,\n object,\n cipherKey\n ))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.storeObjectByID()')\n }\n }\n\n /**\n * Deletes a persisted object using its identifier.\n *\n * @param id The identifier of the object to delete.\n */\n public static async deleteObjectByID(id: string): Promise<void> {\n try {\n void (await deleteObject((this.storageURL + id) as URLString))\n return void (await this.loadRecentEntries()).delete(id)\n } catch (cause) {\n throw unknownError(cause, 'Explorer.deleteObjectByID()')\n }\n }\n\n /**\n * Schedules the application of a synchronized Explorer delta.\n *\n * Recognized deltas are forwarded to their corresponding entries list.\n * Invalid or unrecognized delta types are ignored.\n *\n * Processing is scheduled in a microtask and is not awaited by the caller.\n *\n * @param data The delta to apply.\n */\n public static apply(data: unknown): void {\n return void queueMicrotask(async () => {\n try {\n if (!this.isExplorerDelta(data)) return\n switch (data[0]) {\n case 'recentEntries': {\n void (await this.loadRecentEntries()).apply(data[1])\n break\n }\n case 'credentialEntries': {\n void (await this.loadCredentialEntries()).apply(data[1])\n break\n }\n }\n return\n } catch (cause) {\n throw unknownError(cause, 'Explorer.apply()')\n }\n })\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public static addEventListener<K extends keyof ExplorerEventMap>(\n type: K,\n listener: ExplorerEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'Explorer.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public static removeEventListener<K extends keyof ExplorerEventMap>(\n type: K,\n listener: ExplorerEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'Explorer.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public static dispatchEvent<K extends keyof ExplorerEventMap>(\n type: K,\n detail: ExplorerEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.dispatchEvent()')\n }\n }\n\n //* ██╗ ██╗███████╗██╗ ██████╗ ███████╗██████╗ ███████╗\n //* ██║ ██║██╔════╝██║ ██╔══██╗██╔════╝██╔══██╗██╔════╝\n //* ███████║█████╗ ██║ ██████╔╝█████╗ ██████╔╝███████╗\n //* ██╔══██║██╔══╝ ██║ ██╔═══╝ ██╔══╝ ██╔══██╗╚════██║\n //* ██║ ██║███████╗███████╗██║ ███████╗██║ ██║███████║\n //* ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝\n\n /**\n * Determines whether a value has the basic structure of an Explorer delta.\n *\n * @param data The value to inspect.\n * @returns Whether the value has a string discriminator.\n */\n private static isExplorerDelta(data: unknown): data is ExplorerDelta {\n return Array.isArray(data) && typeof data[0] === 'string'\n }\n\n private static recentEntries?: RecentEntries\n\n /**\n * Loads or returns the cached recent entries list.\n *\n * Derives the list identifier and cipher key from the database secret\n * and merges subsequent network responses into the loaded state.\n *\n * @returns The recent entries list.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n private static async loadRecentEntries(): Promise<RecentEntries> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (this.recentEntries) return this.recentEntries\n\n const id = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:RecentEntriesID'),\n 32\n )\n const ck = await Cryptographic.cipherMessage.deriveKey(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:RecentEntriesCipherKey')\n )\n\n const trustedSnapshot = await this.loadObjectByID(\n id,\n ck,\n (snapshot) => void this.recentEntries!.merge(snapshot)\n )\n\n this.recentEntries = new RecentEntries(id, ck, 0, trustedSnapshot)\n\n return this.recentEntries\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadRecentEntries()')\n }\n }\n\n private static credentialEntries?: CredentialEntries\n\n /**\n * Loads or returns the cached credential entries list.\n *\n * Derives the list identifier and cipher key from the database secret\n * and merges subsequent network responses into the loaded state.\n *\n * @returns The credential entries list.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n private static async loadCredentialEntries(): Promise<CredentialEntries> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (this.credentialEntries) return this.credentialEntries\n\n const id = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialEntriesID'),\n 32\n )\n const ck = await Cryptographic.cipherMessage.deriveKey(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialEntriesCipherKey')\n )\n\n const trustedSnapshot = await this.loadObjectByID(\n id,\n ck,\n (snapshot) => void this.credentialEntries!.merge(snapshot)\n )\n\n this.credentialEntries = new CredentialEntries(\n id,\n ck,\n this.sessionCredential!.credentialID!,\n 0,\n trustedSnapshot\n )\n\n return this.credentialEntries\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadCredentialEntries()')\n }\n }\n}\n\nexport { ExplorerError, type ExplorerErrorCode } from './.errors/class.js'\n\nexport * from './.types/type.js'\n"],"mappings":"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"}
|
package/dist/class.d.cts
CHANGED
|
@@ -29,14 +29,16 @@ type RecentEntriesChange = DeepReadonly<SequencedArrayChange<RecentEntry>>;
|
|
|
29
29
|
* Describes a credential registered with the database.
|
|
30
30
|
*/
|
|
31
31
|
type CredentialEntry = {
|
|
32
|
-
/** The identifier of the credential. */
|
|
33
|
-
id: string;
|
|
34
32
|
/** The display name of the credential. */
|
|
35
33
|
name: string;
|
|
36
34
|
/** The entropy associated with the credential. */
|
|
37
35
|
entropy: Uint8Array;
|
|
38
|
-
/** The storage
|
|
36
|
+
/** The storage characteristics of the device binding. */
|
|
39
37
|
storage: DeviceBindingStorage;
|
|
38
|
+
/** The identifier of the object storing virtual entropy protected by the credential. */
|
|
39
|
+
objectID: string;
|
|
40
|
+
/** The WebAuthn credential identifier. */
|
|
41
|
+
credentialID: string;
|
|
40
42
|
/** The timestamp of the most recent use of the credential. */
|
|
41
43
|
lastUsed: number;
|
|
42
44
|
/** The user agents that have used the credential. */
|
|
@@ -58,7 +60,7 @@ type CredentialEntriesChange = DeepReadonly<SequencedArrayChange<CredentialEntry
|
|
|
58
60
|
/**
|
|
59
61
|
* Represents the outcome of a database authentication attempt.
|
|
60
62
|
*/
|
|
61
|
-
type AuthenticationResult =
|
|
63
|
+
export type AuthenticationResult =
|
|
62
64
|
/** The database was successfully authenticated. */
|
|
63
65
|
'authenticated' |
|
|
64
66
|
/** The device authenticator could not provide the required entropy. */
|
|
@@ -68,7 +70,7 @@ type AuthenticationResult =
|
|
|
68
70
|
/**
|
|
69
71
|
* Describes the parameters used to register a new database credential.
|
|
70
72
|
*/
|
|
71
|
-
type AuthenticationSetupParams = Readonly<{
|
|
73
|
+
export type AuthenticationSetupParams = Readonly<{
|
|
72
74
|
/** The display name assigned to the credential. */
|
|
73
75
|
credentialName: string;
|
|
74
76
|
/** The storage mechanism used to bind the credential to the device. */
|
|
@@ -80,11 +82,11 @@ type AuthenticationSetupParams = Readonly<{
|
|
|
80
82
|
* Includes changes to recent and credential entries, as well as
|
|
81
83
|
* a reserved variant for other delta types.
|
|
82
84
|
*/
|
|
83
|
-
type ExplorerDelta = RecentEntriesDelta | CredentialEntriesDelta | ['other' | []];
|
|
85
|
+
export type ExplorerDelta = RecentEntriesDelta | CredentialEntriesDelta | ['other' | []];
|
|
84
86
|
/**
|
|
85
87
|
* Maps Explorer event names to their detail types.
|
|
86
88
|
*/
|
|
87
|
-
type ExplorerEventMap = {
|
|
89
|
+
export type ExplorerEventMap = {
|
|
88
90
|
/** Fired when the database authentication state changes. */
|
|
89
91
|
authenticated: boolean;
|
|
90
92
|
/** Fired when a synchronized delta is produced. */
|
|
@@ -95,7 +97,7 @@ type ExplorerEventMap = {
|
|
|
95
97
|
*
|
|
96
98
|
* @typeParam K The event name that determines the event detail type.
|
|
97
99
|
*/
|
|
98
|
-
type ExplorerEventListener<K extends keyof ExplorerEventMap> = ((event: CustomEvent<ExplorerEventMap[K]>) => void) | {
|
|
100
|
+
export type ExplorerEventListener<K extends keyof ExplorerEventMap> = ((event: CustomEvent<ExplorerEventMap[K]>) => void) | {
|
|
99
101
|
handleEvent(event: CustomEvent<ExplorerEventMap[K]>): void;
|
|
100
102
|
};
|
|
101
103
|
/**
|
|
@@ -103,7 +105,35 @@ type ExplorerEventListener<K extends keyof ExplorerEventMap> = ((event: CustomEv
|
|
|
103
105
|
*
|
|
104
106
|
* @typeParam K The event name to resolve.
|
|
105
107
|
*/
|
|
106
|
-
type ExplorerEventListenerFor<K extends string> = K extends keyof ExplorerEventMap ? ExplorerEventListener<K> : EventListenerOrEventListenerObject;
|
|
108
|
+
export type ExplorerEventListenerFor<K extends string> = K extends keyof ExplorerEventMap ? ExplorerEventListener<K> : EventListenerOrEventListenerObject;
|
|
109
|
+
/** Identifies an Explorer failure independently of its human-readable message. */
|
|
110
|
+
type ExplorerErrorCode =
|
|
111
|
+
/** An unexpected failure was wrapped with its original cause. */
|
|
112
|
+
'UNKNOWN_ERROR' |
|
|
113
|
+
/** A credential event is missing required identifying or usage information. */
|
|
114
|
+
'INVALID_CREDENTIAL_EVENT' |
|
|
115
|
+
/** A new credential does not provide all required setup properties. */
|
|
116
|
+
'MISSING_CREDENTIAL_SETUP_PROPERTIES' |
|
|
117
|
+
/** An operation requires an authenticated Explorer session. */
|
|
118
|
+
'NOT_AUTHENTICATED';
|
|
119
|
+
/**
|
|
120
|
+
* An Explorer error with a stable, machine-readable code and an optional cause.
|
|
121
|
+
*
|
|
122
|
+
* The message is prefixed with the library name. Use {@link code} rather than
|
|
123
|
+
* parsing the message when handling a particular failure.
|
|
124
|
+
*/
|
|
125
|
+
export declare class ExplorerError extends Error {
|
|
126
|
+
/** The machine-readable category of the failure. */
|
|
127
|
+
readonly code: ExplorerErrorCode;
|
|
128
|
+
/**
|
|
129
|
+
* Creates an error whose name is `ExplorerError`.
|
|
130
|
+
*
|
|
131
|
+
* @param code The category of the failure.
|
|
132
|
+
* @param message The detail to prefix with the library name; defaults to `code`.
|
|
133
|
+
* @param options Error options, including the original `cause`.
|
|
134
|
+
*/
|
|
135
|
+
constructor(code: ExplorerErrorCode, message?: string, options?: ErrorOptions);
|
|
136
|
+
}
|
|
107
137
|
/**
|
|
108
138
|
* Provides authentication, encrypted object storage, and access to
|
|
109
139
|
* the database's synchronized entries.
|
|
@@ -270,5 +300,5 @@ export declare class Explorer {
|
|
|
270
300
|
*/
|
|
271
301
|
private static loadCredentialEntries;
|
|
272
302
|
}
|
|
273
|
-
export {};
|
|
303
|
+
export type { ExplorerErrorCode };
|
|
274
304
|
//# sourceMappingURL=class.d.cts.map
|
package/dist/class.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class.d.cts","names":[],"sources":["../src/RecentEntries/class.ts","../src/CredentialEntries/class.ts","../src/.types/type.ts","../src/class.ts"],"mappings":";;;;;;;;;KAsBY;;EAEV;;EAGA;;EAGA,WAAW;;EAGX;;;;;KAMU,qBAAqB,2BACb,aAAa,oBAAoB;;;;KAMzC,sBAAsB,aAChC,qBAAqB;;;;
|
|
1
|
+
{"version":3,"file":"class.d.cts","names":[],"sources":["../src/RecentEntries/class.ts","../src/CredentialEntries/class.ts","../src/.types/type.ts","../src/.errors/class.ts","../src/class.ts"],"mappings":";;;;;;;;;KAsBY;;EAEV;;EAGA;;EAGA,WAAW;;EAGX;;;;;KAMU,qBAAqB,2BACb,aAAa,oBAAoB;;;;KAMzC,sBAAsB,aAChC,qBAAqB;;;;KCpBX;;EAEV;;EAGA,SAAS;;EAGT,SAAS;;EAGT;;EAGA;;EAGA;;EAGA,YAAY;;IAEV;;IAGA;;;;;;KAqCQ,yBAAyB,+BACb,aAAa,oBAAoB;;;;KAM7C,0BAA0B,aACpC,qBAAqB;;;;YC1FX;;;;;;;;;;YAWA,4BAA4B;;EAEtC;;EAGA,gBAAgB;;;;;;;;YASN,gBACV,qBAAqB;;;;YAKX;;EAEV;;EAGA,OAAO;;;;;;;YAQG,sBAAsB,gBAAgB,sBAC5C,OAAO,YAAY,iBAAiB;EACpC,YAAY,OAAO,YAAY,iBAAiB;;;;;;;YAO1C,yBAAyB,oBACnC,gBAAgB,mBACZ,sBAAsB,KACtB;;KC9DM;;;;;;;;;;;;;;;qBAgBC,sBAAsB;;WAExB,MAAM;;;;;;;;EAUb,YAAA,MAAM,mBACN,kBACA,UAAU;;;;;;;;;qBCcD;;SAEG;;SAGA;iBAGC;iBACA;iBACA;iBACA;;;;;;;kBAQQ,gBAAgB,YAAY;;;;;;;;;;;;;;;SAgB/B,aAClB,iBACA,YAAY,WACZ,cAAc,4BACb,QAAQ;;;;;;;;;SA4FS,4BAClB,uBACC;;;;;;;;;;;;;SAsBiB,sBAClB,YAAY,QAAQ,mCACnB,SAEC,qBACA,SAAS,aAAa,MAAM,mBAC5B;;;;;;;;;;;;SAoCgB,kBAClB,YAAY,QAAQ,+BACnB,SAAS,qBAAqB,SAAS,aAAa,MAAM;;;;;;;;;;;;SAgCzC,eAAe,GACjC,YACA,WAAW,WACX,qBAAqB,yBAAyB,gBAC7C,QAAQ;;;;;;;;;;;SA0BS,gBAAgB,GAClC,YACA,QAAQ,GACR,WAAW,YACV;;;;;;SAoBiB,iBAAiB,aAAa;;;;;;;;;;;SAmBpC,MAAM;;;;;;;;SAmCN,iBAAiB,gBAAgB,kBAC7C,MAAM,GACN,UAAU,yBAAyB,WACnC,oBAAoB;;;;;;;;;SAqBR,oBAAoB,gBAAgB,kBAChD,MAAM,GACN,UAAU,yBAAyB,WACnC,oBAAoB;;;;;;;SAmBR,cAAc,gBAAgB,kBAC1C,MAAM,GACN,QAAQ,iBAAiB;;;;;;;iBAsBZ;iBAIA;;;;;;;;;;iBAWM;iBA8BN;;;;;;;;;;iBAWM"}
|
package/dist/class.d.ts
CHANGED
|
@@ -29,14 +29,16 @@ type RecentEntriesChange = DeepReadonly<SequencedArrayChange<RecentEntry>>;
|
|
|
29
29
|
* Describes a credential registered with the database.
|
|
30
30
|
*/
|
|
31
31
|
type CredentialEntry = {
|
|
32
|
-
/** The identifier of the credential. */
|
|
33
|
-
id: string;
|
|
34
32
|
/** The display name of the credential. */
|
|
35
33
|
name: string;
|
|
36
34
|
/** The entropy associated with the credential. */
|
|
37
35
|
entropy: Uint8Array;
|
|
38
|
-
/** The storage
|
|
36
|
+
/** The storage characteristics of the device binding. */
|
|
39
37
|
storage: DeviceBindingStorage;
|
|
38
|
+
/** The identifier of the object storing virtual entropy protected by the credential. */
|
|
39
|
+
objectID: string;
|
|
40
|
+
/** The WebAuthn credential identifier. */
|
|
41
|
+
credentialID: string;
|
|
40
42
|
/** The timestamp of the most recent use of the credential. */
|
|
41
43
|
lastUsed: number;
|
|
42
44
|
/** The user agents that have used the credential. */
|
|
@@ -58,7 +60,7 @@ type CredentialEntriesChange = DeepReadonly<SequencedArrayChange<CredentialEntry
|
|
|
58
60
|
/**
|
|
59
61
|
* Represents the outcome of a database authentication attempt.
|
|
60
62
|
*/
|
|
61
|
-
type AuthenticationResult =
|
|
63
|
+
export type AuthenticationResult =
|
|
62
64
|
/** The database was successfully authenticated. */
|
|
63
65
|
'authenticated' |
|
|
64
66
|
/** The device authenticator could not provide the required entropy. */
|
|
@@ -68,7 +70,7 @@ type AuthenticationResult =
|
|
|
68
70
|
/**
|
|
69
71
|
* Describes the parameters used to register a new database credential.
|
|
70
72
|
*/
|
|
71
|
-
type AuthenticationSetupParams = Readonly<{
|
|
73
|
+
export type AuthenticationSetupParams = Readonly<{
|
|
72
74
|
/** The display name assigned to the credential. */
|
|
73
75
|
credentialName: string;
|
|
74
76
|
/** The storage mechanism used to bind the credential to the device. */
|
|
@@ -80,11 +82,11 @@ type AuthenticationSetupParams = Readonly<{
|
|
|
80
82
|
* Includes changes to recent and credential entries, as well as
|
|
81
83
|
* a reserved variant for other delta types.
|
|
82
84
|
*/
|
|
83
|
-
type ExplorerDelta = RecentEntriesDelta | CredentialEntriesDelta | ['other' | []];
|
|
85
|
+
export type ExplorerDelta = RecentEntriesDelta | CredentialEntriesDelta | ['other' | []];
|
|
84
86
|
/**
|
|
85
87
|
* Maps Explorer event names to their detail types.
|
|
86
88
|
*/
|
|
87
|
-
type ExplorerEventMap = {
|
|
89
|
+
export type ExplorerEventMap = {
|
|
88
90
|
/** Fired when the database authentication state changes. */
|
|
89
91
|
authenticated: boolean;
|
|
90
92
|
/** Fired when a synchronized delta is produced. */
|
|
@@ -95,7 +97,7 @@ type ExplorerEventMap = {
|
|
|
95
97
|
*
|
|
96
98
|
* @typeParam K The event name that determines the event detail type.
|
|
97
99
|
*/
|
|
98
|
-
type ExplorerEventListener<K extends keyof ExplorerEventMap> = ((event: CustomEvent<ExplorerEventMap[K]>) => void) | {
|
|
100
|
+
export type ExplorerEventListener<K extends keyof ExplorerEventMap> = ((event: CustomEvent<ExplorerEventMap[K]>) => void) | {
|
|
99
101
|
handleEvent(event: CustomEvent<ExplorerEventMap[K]>): void;
|
|
100
102
|
};
|
|
101
103
|
/**
|
|
@@ -103,7 +105,35 @@ type ExplorerEventListener<K extends keyof ExplorerEventMap> = ((event: CustomEv
|
|
|
103
105
|
*
|
|
104
106
|
* @typeParam K The event name to resolve.
|
|
105
107
|
*/
|
|
106
|
-
type ExplorerEventListenerFor<K extends string> = K extends keyof ExplorerEventMap ? ExplorerEventListener<K> : EventListenerOrEventListenerObject;
|
|
108
|
+
export type ExplorerEventListenerFor<K extends string> = K extends keyof ExplorerEventMap ? ExplorerEventListener<K> : EventListenerOrEventListenerObject;
|
|
109
|
+
/** Identifies an Explorer failure independently of its human-readable message. */
|
|
110
|
+
type ExplorerErrorCode =
|
|
111
|
+
/** An unexpected failure was wrapped with its original cause. */
|
|
112
|
+
'UNKNOWN_ERROR' |
|
|
113
|
+
/** A credential event is missing required identifying or usage information. */
|
|
114
|
+
'INVALID_CREDENTIAL_EVENT' |
|
|
115
|
+
/** A new credential does not provide all required setup properties. */
|
|
116
|
+
'MISSING_CREDENTIAL_SETUP_PROPERTIES' |
|
|
117
|
+
/** An operation requires an authenticated Explorer session. */
|
|
118
|
+
'NOT_AUTHENTICATED';
|
|
119
|
+
/**
|
|
120
|
+
* An Explorer error with a stable, machine-readable code and an optional cause.
|
|
121
|
+
*
|
|
122
|
+
* The message is prefixed with the library name. Use {@link code} rather than
|
|
123
|
+
* parsing the message when handling a particular failure.
|
|
124
|
+
*/
|
|
125
|
+
export declare class ExplorerError extends Error {
|
|
126
|
+
/** The machine-readable category of the failure. */
|
|
127
|
+
readonly code: ExplorerErrorCode;
|
|
128
|
+
/**
|
|
129
|
+
* Creates an error whose name is `ExplorerError`.
|
|
130
|
+
*
|
|
131
|
+
* @param code The category of the failure.
|
|
132
|
+
* @param message The detail to prefix with the library name; defaults to `code`.
|
|
133
|
+
* @param options Error options, including the original `cause`.
|
|
134
|
+
*/
|
|
135
|
+
constructor(code: ExplorerErrorCode, message?: string, options?: ErrorOptions);
|
|
136
|
+
}
|
|
107
137
|
/**
|
|
108
138
|
* Provides authentication, encrypted object storage, and access to
|
|
109
139
|
* the database's synchronized entries.
|
|
@@ -270,5 +300,5 @@ export declare class Explorer {
|
|
|
270
300
|
*/
|
|
271
301
|
private static loadCredentialEntries;
|
|
272
302
|
}
|
|
273
|
-
export {};
|
|
303
|
+
export type { ExplorerErrorCode };
|
|
274
304
|
//# sourceMappingURL=class.d.ts.map
|
package/dist/class.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class.d.ts","names":[],"sources":["../src/RecentEntries/class.ts","../src/CredentialEntries/class.ts","../src/.types/type.ts","../src/class.ts"],"mappings":";;;;;;;;;KAsBY;;EAEV;;EAGA;;EAGA,WAAW;;EAGX;;;;;KAMU,qBAAqB,2BACb,aAAa,oBAAoB;;;;KAMzC,sBAAsB,aAChC,qBAAqB;;;;
|
|
1
|
+
{"version":3,"file":"class.d.ts","names":[],"sources":["../src/RecentEntries/class.ts","../src/CredentialEntries/class.ts","../src/.types/type.ts","../src/.errors/class.ts","../src/class.ts"],"mappings":";;;;;;;;;KAsBY;;EAEV;;EAGA;;EAGA,WAAW;;EAGX;;;;;KAMU,qBAAqB,2BACb,aAAa,oBAAoB;;;;KAMzC,sBAAsB,aAChC,qBAAqB;;;;KCpBX;;EAEV;;EAGA,SAAS;;EAGT,SAAS;;EAGT;;EAGA;;EAGA;;EAGA,YAAY;;IAEV;;IAGA;;;;;;KAqCQ,yBAAyB,+BACb,aAAa,oBAAoB;;;;KAM7C,0BAA0B,aACpC,qBAAqB;;;;YC1FX;;;;;;;;;;YAWA,4BAA4B;;EAEtC;;EAGA,gBAAgB;;;;;;;;YASN,gBACV,qBAAqB;;;;YAKX;;EAEV;;EAGA,OAAO;;;;;;;YAQG,sBAAsB,gBAAgB,sBAC5C,OAAO,YAAY,iBAAiB;EACpC,YAAY,OAAO,YAAY,iBAAiB;;;;;;;YAO1C,yBAAyB,oBACnC,gBAAgB,mBACZ,sBAAsB,KACtB;;KC9DM;;;;;;;;;;;;;;;qBAgBC,sBAAsB;;WAExB,MAAM;;;;;;;;EAUb,YAAA,MAAM,mBACN,kBACA,UAAU;;;;;;;;;qBCcD;;SAEG;;SAGA;iBAGC;iBACA;iBACA;iBACA;;;;;;;kBAQQ,gBAAgB,YAAY;;;;;;;;;;;;;;;SAgB/B,aAClB,iBACA,YAAY,WACZ,cAAc,4BACb,QAAQ;;;;;;;;;SA4FS,4BAClB,uBACC;;;;;;;;;;;;;SAsBiB,sBAClB,YAAY,QAAQ,mCACnB,SAEC,qBACA,SAAS,aAAa,MAAM,mBAC5B;;;;;;;;;;;;SAoCgB,kBAClB,YAAY,QAAQ,+BACnB,SAAS,qBAAqB,SAAS,aAAa,MAAM;;;;;;;;;;;;SAgCzC,eAAe,GACjC,YACA,WAAW,WACX,qBAAqB,yBAAyB,gBAC7C,QAAQ;;;;;;;;;;;SA0BS,gBAAgB,GAClC,YACA,QAAQ,GACR,WAAW,YACV;;;;;;SAoBiB,iBAAiB,aAAa;;;;;;;;;;;SAmBpC,MAAM;;;;;;;;SAmCN,iBAAiB,gBAAgB,kBAC7C,MAAM,GACN,UAAU,yBAAyB,WACnC,oBAAoB;;;;;;;;;SAqBR,oBAAoB,gBAAgB,kBAChD,MAAM,GACN,UAAU,yBAAyB,WACnC,oBAAoB;;;;;;;SAmBR,cAAc,gBAAgB,kBAC1C,MAAM,GACN,QAAQ,iBAAiB;;;;;;;iBAsBZ;iBAIA;;;;;;;;;;iBAWM;iBA8BN;;;;;;;;;;iBAWM"}
|
package/dist/class.js
CHANGED
|
@@ -18,7 +18,7 @@ import { deleteObject, loadObject, storeObject } from "@sovereignbase/storage";
|
|
|
18
18
|
import { afterIdleFor, isRecord } from "@sovereignbase/utils";
|
|
19
19
|
import { Bytes } from "@sovereignbase/bytecodec";
|
|
20
20
|
import { readableUserAgent } from "@sovereignbase/dom-utils";
|
|
21
|
-
import {
|
|
21
|
+
import { accessDeviceBinding, invalidateDeviceBinding } from "@sovereignbase/hardware-bound";
|
|
22
22
|
import { Cryptographic } from "@sovereignbase/cryptosuite";
|
|
23
23
|
import { Sequenced } from "@sovereignbase/sequenced";
|
|
24
24
|
// src/.errors/class.ts
|
|
@@ -56,10 +56,11 @@ function unknownError(cause, operation) {
|
|
|
56
56
|
}
|
|
57
57
|
// src/CredentialEntries/class.ts
|
|
58
58
|
/**
|
|
59
|
-
* Maintains the database's
|
|
59
|
+
* Maintains the database's registered credentials.
|
|
60
60
|
*
|
|
61
|
-
* Entries are identified by
|
|
62
|
-
* a sequenced array
|
|
61
|
+
* Entries are identified by their object and credential identifiers.
|
|
62
|
+
* Changes are synchronized through a sequenced array and persisted
|
|
63
|
+
* as encrypted snapshots.
|
|
63
64
|
*/
|
|
64
65
|
var CredentialEntries = class {
|
|
65
66
|
id;
|
|
@@ -73,7 +74,7 @@ var CredentialEntries = class {
|
|
|
73
74
|
*
|
|
74
75
|
* @param id The identifier of the persisted entries object.
|
|
75
76
|
* @param cipherKey The key used to encrypt the entries object.
|
|
76
|
-
* @param sessionCredentialID The credential ID of the current session.
|
|
77
|
+
* @param sessionCredentialID The WebAuthn credential ID of the current session.
|
|
77
78
|
* @param actorID The actor identifier used by the sequenced array.
|
|
78
79
|
* @param trustedSnapshot An optional snapshot used to initialize the state.
|
|
79
80
|
*/
|
|
@@ -99,13 +100,17 @@ var CredentialEntries = class {
|
|
|
99
100
|
this.state.addEventListener("change", ({ detail }) => {
|
|
100
101
|
this.dispatchEvent("change", detail);
|
|
101
102
|
});
|
|
102
|
-
const
|
|
103
|
+
const seenObjects = /* @__PURE__ */ new Set();
|
|
104
|
+
const seenCredentials = /* @__PURE__ */ new Set();
|
|
103
105
|
const duplicates = [];
|
|
104
106
|
for (let index = 0; index < this.state.length; index++) {
|
|
105
107
|
const entry = this.state.at(index);
|
|
106
108
|
if (!entry) continue;
|
|
107
|
-
if (
|
|
108
|
-
else
|
|
109
|
+
if (seenObjects.has(entry.objectID) || seenCredentials.has(entry.credentialID)) duplicates.push(index);
|
|
110
|
+
else {
|
|
111
|
+
seenObjects.add(entry.objectID);
|
|
112
|
+
seenCredentials.add(entry.credentialID);
|
|
113
|
+
}
|
|
109
114
|
}
|
|
110
115
|
for (let index = duplicates.length - 1; index >= 0; index--) this.state.delete(duplicates[index]);
|
|
111
116
|
if (duplicates.length > 0 || this.deltaBuffer.length > 0) this.state.snapshot();
|
|
@@ -126,17 +131,17 @@ var CredentialEntries = class {
|
|
|
126
131
|
record(credentialEvent) {
|
|
127
132
|
try {
|
|
128
133
|
if (!credentialEvent || typeof credentialEvent !== "object") throw new ExplorerError("INVALID_CREDENTIAL_EVENT", "A credential event must be an object.");
|
|
129
|
-
const {
|
|
130
|
-
if (typeof
|
|
134
|
+
const { objectID, credentialID, timestamp, userAgent, name, entropy, storage } = credentialEvent;
|
|
135
|
+
if (typeof objectID !== "string" || typeof timestamp !== "number" || !Number.isFinite(timestamp) || typeof userAgent !== "string") throw new ExplorerError("INVALID_CREDENTIAL_EVENT", "A credential event requires an object ID, a finite timestamp, and a user agent.");
|
|
131
136
|
let existing;
|
|
132
137
|
for (let index = this.state.length - 1; index >= 0; index--) {
|
|
133
138
|
const entry = this.state.at(index);
|
|
134
|
-
if (entry?.
|
|
139
|
+
if (entry?.objectID === objectID || credentialID !== void 0 && entry?.credentialID === credentialID) {
|
|
135
140
|
existing = entry;
|
|
136
141
|
break;
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
|
-
if (!existing && (typeof name !== "string" || !(entropy instanceof Uint8Array) || typeof storage !== "string")) throw new ExplorerError("MISSING_CREDENTIAL_SETUP_PROPERTIES", "A new credential requires a name, entropy, and binding storage.");
|
|
144
|
+
if (!existing && (typeof credentialID !== "string" || typeof name !== "string" || !(entropy instanceof Uint8Array) || typeof storage !== "string")) throw new ExplorerError("MISSING_CREDENTIAL_SETUP_PROPERTIES", "A new credential requires a credential ID, name, entropy, and binding storage.");
|
|
140
145
|
const userAgents = existing?.userAgents.map((agent) => ({ ...agent })) ?? [];
|
|
141
146
|
const agent = userAgents.find((agent) => agent.name === userAgent);
|
|
142
147
|
if (agent) agent.lastSeen = Math.max(agent.lastSeen, timestamp);
|
|
@@ -145,14 +150,18 @@ var CredentialEntries = class {
|
|
|
145
150
|
lastSeen: timestamp
|
|
146
151
|
});
|
|
147
152
|
const entry = {
|
|
148
|
-
|
|
153
|
+
objectID,
|
|
154
|
+
credentialID: credentialID ?? existing.credentialID,
|
|
149
155
|
name: name ?? existing.name,
|
|
150
156
|
entropy: entropy ?? existing.entropy,
|
|
151
157
|
storage: storage ?? existing.storage,
|
|
152
158
|
lastUsed: Math.max(existing?.lastUsed ?? timestamp, timestamp),
|
|
153
159
|
userAgents
|
|
154
160
|
};
|
|
155
|
-
for (let index = this.state.length - 1; index >= 0; index--)
|
|
161
|
+
for (let index = this.state.length - 1; index >= 0; index--) {
|
|
162
|
+
const current = this.state.at(index);
|
|
163
|
+
if (current?.objectID === entry.objectID || current?.credentialID === entry.credentialID) this.state.delete(index);
|
|
164
|
+
}
|
|
156
165
|
this.state.unshift(entry);
|
|
157
166
|
this.state.snapshot();
|
|
158
167
|
} catch (cause) {
|
|
@@ -160,24 +169,32 @@ var CredentialEntries = class {
|
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
/**
|
|
163
|
-
* Revokes a credential
|
|
172
|
+
* Revokes a credential and removes its registered entries.
|
|
164
173
|
*
|
|
165
|
-
*
|
|
174
|
+
* Deletes the credential's persisted objects before removing its entries.
|
|
175
|
+
* The entries remain unchanged if deletion fails. Credential invalidation
|
|
176
|
+
* is requested on a best-effort basis after successful removal.
|
|
166
177
|
*
|
|
167
|
-
* @param credentialID The
|
|
178
|
+
* @param credentialID The WebAuthn credential identifier to revoke.
|
|
168
179
|
*/
|
|
169
180
|
async revoke(credentialID) {
|
|
170
181
|
try {
|
|
171
|
-
|
|
182
|
+
const objectIDs = /* @__PURE__ */ new Set();
|
|
172
183
|
for (let index = this.state.length - 1; index >= 0; index--) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
break;
|
|
184
|
+
const entry = this.state.at(index);
|
|
185
|
+
if (entry?.credentialID === credentialID) objectIDs.add(entry.objectID);
|
|
176
186
|
}
|
|
177
|
-
if (
|
|
178
|
-
await Explorer.deleteObjectByID(
|
|
179
|
-
|
|
180
|
-
this.state.
|
|
187
|
+
if (objectIDs.size === 0) return;
|
|
188
|
+
for (const objectID of objectIDs) await Explorer.deleteObjectByID(objectID);
|
|
189
|
+
let removed = false;
|
|
190
|
+
for (let index = this.state.length - 1; index >= 0; index--) if (this.state.at(index)?.credentialID === credentialID) {
|
|
191
|
+
this.state.delete(index);
|
|
192
|
+
removed = true;
|
|
193
|
+
}
|
|
194
|
+
if (removed) this.state.snapshot();
|
|
195
|
+
try {
|
|
196
|
+
await invalidateDeviceBinding(credentialID);
|
|
197
|
+
} catch {}
|
|
181
198
|
} catch (cause) {
|
|
182
199
|
throw unknownError(cause, "CredentialEntries.revoke()");
|
|
183
200
|
}
|
|
@@ -185,11 +202,11 @@ var CredentialEntries = class {
|
|
|
185
202
|
/**
|
|
186
203
|
* Returns the credential entries ordered by most recent use.
|
|
187
204
|
*
|
|
188
|
-
* Entries with equal timestamps are ordered by identifier.
|
|
205
|
+
* Entries with equal timestamps are ordered by credential identifier.
|
|
189
206
|
*/
|
|
190
207
|
list() {
|
|
191
208
|
try {
|
|
192
|
-
return this.state.slice().sort((a, b) => b.lastUsed - a.lastUsed || a.
|
|
209
|
+
return this.state.slice().sort((a, b) => b.lastUsed - a.lastUsed || a.credentialID.localeCompare(b.credentialID));
|
|
193
210
|
} catch (cause) {
|
|
194
211
|
throw unknownError(cause, "CredentialEntries.list()");
|
|
195
212
|
}
|
|
@@ -477,26 +494,28 @@ var Explorer = class {
|
|
|
477
494
|
static async authenticate(dataPIN, storageURL, setupParams) {
|
|
478
495
|
try {
|
|
479
496
|
this.storageURL = storageURL;
|
|
480
|
-
const
|
|
481
|
-
if (!
|
|
497
|
+
const deviceBinding = await accessDeviceBinding();
|
|
498
|
+
if (!Array.isArray(deviceBinding)) return "authenticator-error";
|
|
499
|
+
const [credentialID, deviceEntropy] = deviceBinding;
|
|
482
500
|
this.sessionCredential = {
|
|
483
501
|
timestamp: Date.now(),
|
|
484
502
|
userAgent: readableUserAgent()
|
|
485
503
|
};
|
|
486
504
|
const dataPINBytes = Bytes.utf8.decode(dataPIN);
|
|
487
505
|
const credentialSecret = await Bytes.derive(deviceEntropy, dataPINBytes, 32);
|
|
488
|
-
const
|
|
489
|
-
this.sessionCredential.
|
|
506
|
+
const credentialObjectID = await Cryptographic.identifier.derive(credentialSecret, Bytes.utf8.decode("@sovereignbase/explorer:CredentialObjectID"), 32);
|
|
507
|
+
this.sessionCredential.objectID = credentialObjectID;
|
|
508
|
+
this.sessionCredential.credentialID = credentialID;
|
|
490
509
|
const credentialCipherKey = await Cryptographic.cipherMessage.deriveKey(credentialSecret, Bytes.utf8.decode("@sovereignbase/explorer:CredentialCipherKey"));
|
|
491
510
|
let virtualEntropy;
|
|
492
511
|
if (isRecord(setupParams)) {
|
|
493
512
|
virtualEntropy = Bytes.generate(64);
|
|
494
|
-
await this.storeObjectByID(
|
|
513
|
+
await this.storeObjectByID(credentialObjectID, virtualEntropy, credentialCipherKey);
|
|
495
514
|
this.sessionCredential.name = setupParams.credentialName;
|
|
496
515
|
this.sessionCredential.entropy = deviceEntropy;
|
|
497
516
|
this.sessionCredential.storage = setupParams.bindingStorage;
|
|
498
517
|
} else {
|
|
499
|
-
const result = await this.loadObjectByID(
|
|
518
|
+
const result = await this.loadObjectByID(credentialObjectID, credentialCipherKey);
|
|
500
519
|
if (!(result instanceof Uint8Array) || result.byteLength !== 64) return "invalid-data-pin";
|
|
501
520
|
virtualEntropy = result;
|
|
502
521
|
}
|
|
@@ -747,13 +766,13 @@ var Explorer = class {
|
|
|
747
766
|
const id = await Cryptographic.identifier.derive(this.databaseSecret, Bytes.utf8.decode("@sovereignbase/explorer:CredentialEntriesID"), 32);
|
|
748
767
|
const ck = await Cryptographic.cipherMessage.deriveKey(this.databaseSecret, Bytes.utf8.decode("@sovereignbase/explorer:CredentialEntriesCipherKey"));
|
|
749
768
|
const trustedSnapshot = await this.loadObjectByID(id, ck, (snapshot) => void this.credentialEntries.merge(snapshot));
|
|
750
|
-
this.credentialEntries = new CredentialEntries(id, ck, this.sessionCredential.
|
|
769
|
+
this.credentialEntries = new CredentialEntries(id, ck, this.sessionCredential.credentialID, 0, trustedSnapshot);
|
|
751
770
|
return this.credentialEntries;
|
|
752
771
|
} catch (cause) {
|
|
753
772
|
throw unknownError(cause, "Explorer.loadCredentialEntries()");
|
|
754
773
|
}
|
|
755
774
|
}
|
|
756
775
|
};
|
|
757
|
-
export { Explorer };
|
|
776
|
+
export { Explorer, ExplorerError };
|
|
758
777
|
|
|
759
778
|
//# sourceMappingURL=class.js.map
|
package/dist/class.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class.js","names":[],"sources":["../src/.errors/class.ts","../src/.errors/unkown.ts","../src/CredentialEntries/class.ts","../src/RecentEntries/class.ts","../src/class.ts"],"sourcesContent":["/** Identifies an Explorer failure independently of its human-readable message. */\nexport type ExplorerErrorCode =\n /** An unexpected failure was wrapped with its original cause. */\n | 'UNKNOWN_ERROR'\n /** A credential event is missing required identifying or usage information. */\n | 'INVALID_CREDENTIAL_EVENT'\n /** A new credential does not provide all required setup properties. */\n | 'MISSING_CREDENTIAL_SETUP_PROPERTIES'\n /** An operation requires an authenticated Explorer session. */\n | 'NOT_AUTHENTICATED'\n\n/**\n * An Explorer error with a stable, machine-readable code and an optional cause.\n *\n * The message is prefixed with the library name. Use {@link code} rather than\n * parsing the message when handling a particular failure.\n */\nexport class ExplorerError extends Error {\n /** The machine-readable category of the failure. */\n readonly code: ExplorerErrorCode\n\n /**\n * Creates an error whose name is `ExplorerError`.\n *\n * @param code The category of the failure.\n * @param message The detail to prefix with the library name; defaults to `code`.\n * @param options Error options, including the original `cause`.\n */\n constructor(\n code: ExplorerErrorCode,\n message?: string,\n options?: ErrorOptions\n ) {\n super(`{@sovereignbase/explorer} ${message ?? code}`, options)\n this.code = code\n this.name = 'ExplorerError'\n }\n}\n","import { ExplorerError } from './class.js'\n\n/**\n * Preserves an existing library error or wraps an unexpected failure once.\n * @param cause The value thrown by the failed operation.\n * @param operation The operation name to include in the error message.\n * @returns The original library error, or an `UNKNOWN_ERROR` retaining `cause`.\n */\nexport function unknownError(cause: unknown, operation: string): ExplorerError {\n return cause instanceof ExplorerError\n ? cause\n : new ExplorerError('UNKNOWN_ERROR', `${operation} failed.`, { cause })\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\nimport type { DeviceBindingStorage } from '@sovereignbase/hardware-bound'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { ExplorerError } from '../.errors/class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes a credential registered with the database.\n */\nexport type CredentialEntry = {\n /** The identifier of the credential. */\n id: string\n\n /** The display name of the credential. */\n name: string\n\n /** The entropy associated with the credential. */\n entropy: Uint8Array\n\n /** The storage mechanism used for device binding. */\n storage: DeviceBindingStorage\n\n /** The timestamp of the most recent use of the credential. */\n lastUsed: number\n\n /** The user agents that have used the credential. */\n userAgents: Array<{\n /** The user agent string. */\n name: string\n\n /** The timestamp of the most recent use by the user agent. */\n lastSeen: number\n }>\n}\n\n/**\n * Describes the properties used to create or update a credential entry.\n *\n * The identifier, timestamp, and user agent are required to record use.\n * Creating a credential additionally requires a name, entropy, and storage.\n */\nexport type CredentialEvent = {\n /** The identifier of the credential. */\n id?: string\n\n /** The timestamp of the credential use. */\n timestamp?: number\n\n /** The user agent associated with the credential use. */\n userAgent?: string\n\n /** The display name of the credential. */\n name?: string\n\n /** The entropy associated with the credential. */\n entropy?: Uint8Array\n\n /** The storage mechanism used for device binding. */\n storage?: DeviceBindingStorage\n}\n\n/**\n * Represents a sequenced delta produced by the credential entries list.\n */\nexport type CredentialEntriesDelta = Readonly<\n ['credentialEntries', DeepReadonly<SequencedArrayDelta<CredentialEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the credential entries list.\n */\nexport type CredentialEntriesChange = DeepReadonly<\n SequencedArrayChange<CredentialEntry>\n>\n\n/**\n * Maps credential entries event names to their detail types.\n */\nexport type CredentialEntriesEventMap = {\n /** Fired when the credential entries list changes. */\n change: CredentialEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link CredentialEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type CredentialEntriesEventListener<\n K extends keyof CredentialEntriesEventMap,\n> =\n | ((event: CustomEvent<CredentialEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<CredentialEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type CredentialEntriesEventListenerFor<K extends string> =\n K extends keyof CredentialEntriesEventMap\n ? CredentialEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the database's credential entries.\n *\n * Entries are identified by credential ID and synchronized through\n * a sequenced array. Changes are persisted as encrypted snapshots.\n */\nexport class CredentialEntries {\n private state: SequencedArray<CredentialEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<CredentialEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a credential entries list.\n *\n * @param id The identifier of the persisted entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param sessionCredentialID The credential ID of the current session.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n public readonly sessionCredentialID: string,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'credentialEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<CredentialEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n\n const seen = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (seen.has(entry.id)) duplicates.push(index)\n else seen.add(entry.id)\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.constructor()')\n }\n }\n\n /**\n * Creates or updates a credential entry.\n *\n * Updates the credential's last-used timestamp and the corresponding\n * user agent's last-seen timestamp. Existing properties are preserved\n * unless replacements are provided.\n *\n * @param credentialEvent The credential properties to apply.\n * @throws {ExplorerError} If the event is invalid or setup properties are missing.\n */\n public record(credentialEvent: CredentialEvent): void {\n try {\n if (!credentialEvent || typeof credentialEvent !== 'object')\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event must be an object.'\n )\n\n const { id, timestamp, userAgent, name, entropy, storage } =\n credentialEvent\n\n if (\n typeof id !== 'string' ||\n typeof timestamp !== 'number' ||\n !Number.isFinite(timestamp) ||\n typeof userAgent !== 'string'\n )\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event requires an ID, a finite timestamp, and a user agent.'\n )\n\n let existing: CredentialEntry | undefined\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const entry = this.state.at(index)\n if (entry?.id === id) {\n existing = entry\n break\n }\n }\n\n if (\n !existing &&\n (typeof name !== 'string' ||\n !(entropy instanceof Uint8Array) ||\n typeof storage !== 'string')\n )\n throw new ExplorerError(\n 'MISSING_CREDENTIAL_SETUP_PROPERTIES',\n 'A new credential requires a name, entropy, and binding storage.'\n )\n\n const userAgents =\n existing?.userAgents.map((agent) => ({ ...agent })) ?? []\n const agent = userAgents.find((agent) => agent.name === userAgent)\n\n if (agent) {\n agent.lastSeen = Math.max(agent.lastSeen, timestamp)\n } else {\n userAgents.push({ name: userAgent, lastSeen: timestamp })\n }\n\n const entry: CredentialEntry = {\n id,\n name: name ?? existing!.name,\n entropy: entropy ?? existing!.entropy,\n storage: storage ?? existing!.storage,\n lastUsed: Math.max(existing?.lastUsed ?? timestamp, timestamp),\n userAgents,\n }\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id === id) void this.state.delete(index)\n }\n\n void this.state.unshift(entry)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.record()')\n }\n }\n\n /**\n * Revokes a credential by deleting its persisted data and list entries.\n *\n * The list remains unchanged if deletion of the persisted credential fails.\n *\n * @param credentialID The identifier of the credential to revoke.\n */\n public async revoke(credentialID: string): Promise<void> {\n try {\n let exists = false\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id !== credentialID) continue\n exists = true\n break\n }\n\n if (!exists) return\n\n await Explorer.deleteObjectByID(credentialID)\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id === credentialID)\n void this.state.delete(index)\n }\n\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.revoke()')\n }\n }\n\n /**\n * Returns the credential entries ordered by most recent use.\n *\n * Entries with equal timestamps are ordered by identifier.\n */\n public list(): DeepReadonly<Array<CredentialEntry>> {\n try {\n return this.state\n .slice()\n .sort((a, b) => b.lastUsed - a.lastUsed || a.id.localeCompare(b.id))\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.list()')\n }\n }\n\n /**\n * Applies a sequenced delta to the credential entries list.\n *\n * @param delta The delta to apply.\n */\n public apply(delta: unknown): void {\n try {\n void this.state.apply(delta)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.apply()')\n }\n }\n\n /**\n * Merges a snapshot into the credential entries list.\n *\n * @param snapshot The snapshot to merge.\n */\n public merge(snapshot: unknown): void {\n try {\n void this.state.merge(snapshot)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.merge()')\n }\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public addEventListener<K extends keyof CredentialEntriesEventMap>(\n type: K,\n listener: CredentialEntriesEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public removeEventListener<K extends keyof CredentialEntriesEventMap>(\n type: K,\n listener: CredentialEntriesEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public dispatchEvent<K extends keyof CredentialEntriesEventMap>(\n type: K,\n detail: CredentialEntriesEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.dispatchEvent()')\n }\n }\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes an entry in the recent objects list.\n */\nexport type RecentEntry = {\n /** The identifier of the referenced object. */\n id: string\n\n /** The display name of the referenced object. */\n name: string\n\n /** The cipher key used to access the referenced object. */\n cipherKey: CipherKey\n\n /** The date and time at which the object was most recently accessed. */\n dateAccessed: number\n}\n\n/**\n * Represents a sequenced delta produced by the recent entries list.\n */\nexport type RecentEntriesDelta = Readonly<\n ['recentEntries', DeepReadonly<SequencedArrayDelta<RecentEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the recent entries list.\n */\nexport type RecentEntriesChange = DeepReadonly<\n SequencedArrayChange<RecentEntry>\n>\n\n/**\n * Maps recent entries event names to their detail types.\n */\nexport type RecentEntriesEventMap = {\n /** Fired when the recent entries list changes. */\n change: RecentEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link RecentEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type RecentEntriesEventListener<K extends keyof RecentEntriesEventMap> =\n | ((event: CustomEvent<RecentEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<RecentEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type RecentEntriesEventListenerFor<K extends string> =\n K extends keyof RecentEntriesEventMap\n ? RecentEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the ordered list of recently accessed objects.\n *\n * Entries are ordered from most recently accessed to least recently accessed,\n * deduplicated by identifier, and limited to 20 entries.\n */\nexport class RecentEntries {\n private state: SequencedArray<RecentEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<RecentEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a recent entries list.\n *\n * @param id The identifier of the persisted recent entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n //\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'recentEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n //\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<RecentEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n //\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n //\n const seen = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (seen.has(entry.id) || seen.size >= 20) duplicates.push(index)\n else seen.add(entry.id)\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n //\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n //\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.constructor()')\n }\n }\n\n /**\n * Marks an entry as most recently accessed.\n *\n * Any existing entry with the same identifier is removed before the new\n * entry is inserted at the beginning of the list.\n *\n * @param accessed The entry that was accessed.\n */\n public update(accessed: RecentEntry): void {\n try {\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id === accessed.id)\n void this.state.delete(index)\n }\n\n void this.state.unshift(accessed)\n\n while (this.state.length > 20) {\n void this.state.pop()\n }\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.update()')\n }\n }\n\n /**\n * Removes an entry from the recent entries list.\n *\n * The referenced object is not deleted.\n *\n * @param id The identifier of the entry to remove.\n */\n public delete(id: string): void {\n try {\n let deleted = false\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id !== id) continue\n void this.state.delete(index)\n deleted = true\n }\n if (deleted) void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.delete()')\n }\n }\n\n /**\n * Returns the recent entries ordered by most recent access.\n */\n public list(): DeepReadonly<Array<RecentEntry>> {\n try {\n return this.state\n .slice()\n .sort(\n (a, b) => b.dateAccessed - a.dateAccessed || a.id.localeCompare(b.id)\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.list()')\n }\n }\n //\n /**\n * Applies a sequenced delta to the recent entries list.\n *\n * @param delta The delta to apply.\n */\n public apply(delta: unknown): void {\n try {\n void this.state.apply(delta)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.apply()')\n }\n }\n\n /**\n * Merges a snapshot into the recent entries list.\n *\n * @param snapshot The snapshot to merge.\n */\n public merge(snapshot: unknown): void {\n try {\n void this.state.merge(snapshot)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.merge()')\n }\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public addEventListener<K extends keyof RecentEntriesEventMap>(\n type: K,\n listener: RecentEntriesEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public removeEventListener<K extends keyof RecentEntriesEventMap>(\n type: K,\n listener: RecentEntriesEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public dispatchEvent<K extends keyof RecentEntriesEventMap>(\n type: K,\n detail: RecentEntriesEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.dispatchEvent()')\n }\n }\n}\n","import {\n deleteObject,\n loadObject,\n storeObject,\n type URLString,\n} from '@sovereignbase/storage'\nimport { isRecord, type DeepReadonly } from '@sovereignbase/utils'\nimport { Bytes } from '@sovereignbase/bytecodec'\nimport { readableUserAgent } from '@sovereignbase/dom-utils'\nimport { deriveDeviceEntropy } from '@sovereignbase/hardware-bound'\nimport { Cryptographic, type CipherKey } from '@sovereignbase/cryptosuite'\n//\nimport type {\n AuthenticationResult,\n AuthenticationSetupParams,\n //\n ExplorerEventMap,\n ExplorerEventListenerFor,\n //\n ExplorerDelta,\n} from './.types/type.js'\n//\nimport { ExplorerError } from './.errors/class.js'\nimport { unknownError } from './.errors/unkown.js'\n//\nimport {\n CredentialEntries,\n type CredentialEntry,\n type CredentialEvent,\n type CredentialEntriesChange,\n} from './CredentialEntries/class.js'\n//\nimport {\n RecentEntries,\n type RecentEntry,\n type RecentEntriesChange,\n} from './RecentEntries/class.js'\n\n/**\n * Provides authentication, encrypted object storage, and access to\n * the database's synchronized entries.\n *\n * Maintains the authenticated database context and exposes static methods\n * for managing stored objects, observing entries, and applying remote deltas.\n */\nexport class Explorer {\n /** The identifier of the authenticated database. */\n public static databaseID: string\n\n /** Indicates whether the database has been authenticated. */\n public static isAuthenticated: boolean\n\n //\n private static storageURL: URLString\n private static eventTarget?: EventTarget\n private static databaseSecret: Readonly<Uint8Array>\n private static sessionCredential?: CredentialEvent = {}\n\n /**\n * Caches cipher keys by object identifier.\n *\n * Existing entries are preserved when an object is accessed with\n * a cipher key.\n */\n public static readonly cipherKeyCache: Map<string, CipherKey> = new Map()\n\n /**\n * Authenticates a database using a data PIN and device-bound entropy.\n *\n * Derives the credential and database identifiers from cryptographic\n * secrets. When setup parameters are provided, creates and stores a\n * new credential. Otherwise, loads the existing credential.\n *\n * Records the authenticated credential in the database's credential list.\n *\n * @param dataPIN The PIN used to derive the credential and database secrets.\n * @param storageURL The base URL used to access persisted database objects.\n * @param setupParams Optional parameters for creating a new credential.\n * @returns The authentication result.\n */\n public static async authenticate(\n dataPIN: string,\n storageURL: URLString,\n setupParams?: AuthenticationSetupParams\n ): Promise<AuthenticationResult> {\n try {\n this.storageURL = storageURL\n //\n const deviceEntropy = await deriveDeviceEntropy()\n if (!deviceEntropy) return 'authenticator-error'\n //\n this.sessionCredential = {\n timestamp: Date.now(),\n userAgent: readableUserAgent(),\n }\n //\n const dataPINBytes = Bytes.utf8.decode(dataPIN)\n //\n const credentialSecret = await Bytes.derive(\n deviceEntropy,\n dataPINBytes,\n 32\n )\n //\n const credentialID = await Cryptographic.identifier.derive(\n credentialSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialID'),\n 32\n )\n this.sessionCredential.id = credentialID\n //\n const credentialCipherKey = await Cryptographic.cipherMessage.deriveKey(\n credentialSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialCipherKey')\n )\n //\n let virtualEntropy: Uint8Array\n ////////\n // SETUP\n if (isRecord(setupParams)) {\n virtualEntropy = Bytes.generate(64)\n\n void (await this.storeObjectByID(\n credentialID,\n virtualEntropy,\n credentialCipherKey\n ))\n\n this.sessionCredential.name = setupParams.credentialName\n this.sessionCredential.entropy = deviceEntropy\n this.sessionCredential.storage = setupParams.bindingStorage\n }\n ////////\n // LOGIN\n else {\n const result = await this.loadObjectByID(\n credentialID,\n credentialCipherKey\n )\n if (!(result instanceof Uint8Array) || result.byteLength !== 64)\n return 'invalid-data-pin'\n\n virtualEntropy = result\n }\n //\n this.databaseSecret = await Bytes.derive(virtualEntropy, dataPINBytes, 64)\n\n this.databaseID = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:DatabaseID'),\n 32\n )\n\n this.isAuthenticated = true\n\n void (await this.loadCredentialEntries()).record(this.sessionCredential)\n delete this.sessionCredential\n\n void this.dispatchEvent('authenticated', true)\n\n return 'authenticated'\n } catch (cause) {\n throw unknownError(cause, 'Explorer.authenticate()')\n }\n }\n\n /**\n * Permanently revokes a credential and deletes its persisted data.\n *\n * Removes the credential from the database's registered credentials.\n *\n * @param credentialID The identifier of the credential to revoke.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async permanentlyDeleteCredential(\n credentialID: string\n ): Promise<void> {\n try {\n return void (await (\n await this.loadCredentialEntries()\n ).revoke(credentialID))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.permanentlyDeleteCredential()')\n }\n }\n\n /**\n * Returns the registered credentials and optionally observes their changes.\n *\n * The returned tuple contains a cleanup function, the current credential\n * entries, and the identifier of the current session's credential.\n *\n * The cleanup function removes the registered change listener, if any.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function, credential entries, and session credential ID.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async listCredentialEntries(\n onChange?: (splice: CredentialEntriesChange) => void\n ): Promise<\n [\n cleanup: () => void,\n entries: DeepReadonly<Array<CredentialEntry>>,\n sessionCredentialID: string,\n ]\n > {\n try {\n const credentialEntries = await this.loadCredentialEntries()\n\n const listener = onChange\n ? ({ detail }: CustomEvent<CredentialEntriesChange>) => onChange(detail)\n : undefined\n\n if (listener) credentialEntries.addEventListener('change', listener)\n\n return [\n () => {\n if (listener)\n credentialEntries.removeEventListener('change', listener)\n },\n credentialEntries.list(),\n credentialEntries.sessionCredentialID,\n ]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.listCredentialEntries()')\n }\n }\n\n /**\n * Returns the recently accessed objects and optionally observes their changes.\n *\n * The returned tuple contains a cleanup function and the current recent\n * entries. The cleanup function removes the registered change listener,\n * if any.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function and recent entries.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async listRecentEntries(\n onChange?: (splice: RecentEntriesChange) => void\n ): Promise<[cleanup: () => void, entries: DeepReadonly<Array<RecentEntry>>]> {\n try {\n const recentEntries = await this.loadRecentEntries()\n\n const listener = onChange\n ? ({ detail }: CustomEvent<RecentEntriesChange>) => onChange(detail)\n : undefined\n\n if (listener) recentEntries.addEventListener('change', listener)\n\n return [\n () => {\n if (listener) recentEntries.removeEventListener('change', listener)\n },\n recentEntries.list(),\n ]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.listRecentEntries()')\n }\n }\n\n /**\n * Loads an encrypted object using its identifier and cipher key.\n *\n * Caches the cipher key if the identifier has not been encountered before.\n *\n * @typeParam T The expected type of the loaded object.\n * @param id The identifier of the object to load.\n * @param cipherKey The key used to decrypt the object.\n * @param onNetworkResponse An optional callback for network responses.\n * @returns The loaded object, or `undefined` if no object is available.\n */\n public static async loadObjectByID<T>(\n id: string,\n cipherKey: CipherKey,\n onNetworkResponse?: (data: unknown) => void | Promise<void>\n ): Promise<T | undefined> {\n try {\n if (!this.cipherKeyCache.has(id))\n void this.cipherKeyCache.set(id, cipherKey)\n //\n return (await loadObject(\n (this.storageURL + id) as URLString,\n cipherKey,\n false,\n onNetworkResponse\n )) as T\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadObjectByID()')\n }\n }\n\n /**\n * Encrypts and stores an object using its identifier and cipher key.\n *\n * Caches the cipher key if the identifier has not been encountered before.\n *\n * @typeParam T The type of the object to store.\n * @param id The identifier of the object to store.\n * @param object The object to encrypt and store.\n * @param cipherKey The key used to encrypt the object.\n */\n public static async storeObjectByID<T>(\n id: string,\n object: T,\n cipherKey: CipherKey\n ): Promise<void> {\n try {\n if (!this.cipherKeyCache.has(id))\n void this.cipherKeyCache.set(id, cipherKey)\n //\n return void (await storeObject(\n (this.storageURL + id) as URLString,\n object,\n cipherKey\n ))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.storeObjectByID()')\n }\n }\n\n /**\n * Deletes a persisted object using its identifier.\n *\n * @param id The identifier of the object to delete.\n */\n public static async deleteObjectByID(id: string): Promise<void> {\n try {\n void (await deleteObject((this.storageURL + id) as URLString))\n return void (await this.loadRecentEntries()).delete(id)\n } catch (cause) {\n throw unknownError(cause, 'Explorer.deleteObjectByID()')\n }\n }\n\n /**\n * Schedules the application of a synchronized Explorer delta.\n *\n * Recognized deltas are forwarded to their corresponding entries list.\n * Invalid or unrecognized delta types are ignored.\n *\n * Processing is scheduled in a microtask and is not awaited by the caller.\n *\n * @param data The delta to apply.\n */\n public static apply(data: unknown): void {\n return void queueMicrotask(async () => {\n try {\n if (!this.isExplorerDelta(data)) return\n switch (data[0]) {\n case 'recentEntries': {\n void (await this.loadRecentEntries()).apply(data[1])\n break\n }\n case 'credentialEntries': {\n void (await this.loadCredentialEntries()).apply(data[1])\n break\n }\n }\n return\n } catch (cause) {\n throw unknownError(cause, 'Explorer.apply()')\n }\n })\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public static addEventListener<K extends keyof ExplorerEventMap>(\n type: K,\n listener: ExplorerEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'Explorer.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public static removeEventListener<K extends keyof ExplorerEventMap>(\n type: K,\n listener: ExplorerEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'Explorer.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public static dispatchEvent<K extends keyof ExplorerEventMap>(\n type: K,\n detail: ExplorerEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.dispatchEvent()')\n }\n }\n\n //* ██╗ ██╗███████╗██╗ ██████╗ ███████╗██████╗ ███████╗\n //* ██║ ██║██╔════╝██║ ██╔══██╗██╔════╝██╔══██╗██╔════╝\n //* ███████║█████╗ ██║ ██████╔╝█████╗ ██████╔╝███████╗\n //* ██╔══██║██╔══╝ ██║ ██╔═══╝ ██╔══╝ ██╔══██╗╚════██║\n //* ██║ ██║███████╗███████╗██║ ███████╗██║ ██║███████║\n //* ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝\n\n /**\n * Determines whether a value has the basic structure of an Explorer delta.\n *\n * @param data The value to inspect.\n * @returns Whether the value has a string discriminator.\n */\n private static isExplorerDelta(data: unknown): data is ExplorerDelta {\n return Array.isArray(data) && typeof data[0] === 'string'\n }\n\n private static recentEntries?: RecentEntries\n\n /**\n * Loads or returns the cached recent entries list.\n *\n * Derives the list identifier and cipher key from the database secret\n * and merges subsequent network responses into the loaded state.\n *\n * @returns The recent entries list.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n private static async loadRecentEntries(): Promise<RecentEntries> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (this.recentEntries) return this.recentEntries\n\n const id = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:RecentEntriesID'),\n 32\n )\n const ck = await Cryptographic.cipherMessage.deriveKey(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:RecentEntriesCipherKey')\n )\n\n const trustedSnapshot = await this.loadObjectByID(\n id,\n ck,\n (snapshot) => void this.recentEntries!.merge(snapshot)\n )\n\n this.recentEntries = new RecentEntries(id, ck, 0, trustedSnapshot)\n\n return this.recentEntries\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadRecentEntries()')\n }\n }\n\n private static credentialEntries?: CredentialEntries\n\n /**\n * Loads or returns the cached credential entries list.\n *\n * Derives the list identifier and cipher key from the database secret\n * and merges subsequent network responses into the loaded state.\n *\n * @returns The credential entries list.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n private static async loadCredentialEntries(): Promise<CredentialEntries> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (this.credentialEntries) return this.credentialEntries\n\n const id = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialEntriesID'),\n 32\n )\n const ck = await Cryptographic.cipherMessage.deriveKey(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialEntriesCipherKey')\n )\n\n const trustedSnapshot = await this.loadObjectByID(\n id,\n ck,\n (snapshot) => void this.credentialEntries!.merge(snapshot)\n )\n\n this.credentialEntries = new CredentialEntries(\n id,\n ck,\n this.sessionCredential!.id!,\n 0,\n trustedSnapshot\n )\n\n return this.credentialEntries\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadCredentialEntries()')\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"class.js","names":[],"sources":["../src/.errors/class.ts","../src/.errors/unkown.ts","../src/CredentialEntries/class.ts","../src/RecentEntries/class.ts","../src/class.ts"],"sourcesContent":["/** Identifies an Explorer failure independently of its human-readable message. */\nexport type ExplorerErrorCode =\n /** An unexpected failure was wrapped with its original cause. */\n | 'UNKNOWN_ERROR'\n /** A credential event is missing required identifying or usage information. */\n | 'INVALID_CREDENTIAL_EVENT'\n /** A new credential does not provide all required setup properties. */\n | 'MISSING_CREDENTIAL_SETUP_PROPERTIES'\n /** An operation requires an authenticated Explorer session. */\n | 'NOT_AUTHENTICATED'\n\n/**\n * An Explorer error with a stable, machine-readable code and an optional cause.\n *\n * The message is prefixed with the library name. Use {@link code} rather than\n * parsing the message when handling a particular failure.\n */\nexport class ExplorerError extends Error {\n /** The machine-readable category of the failure. */\n readonly code: ExplorerErrorCode\n\n /**\n * Creates an error whose name is `ExplorerError`.\n *\n * @param code The category of the failure.\n * @param message The detail to prefix with the library name; defaults to `code`.\n * @param options Error options, including the original `cause`.\n */\n constructor(\n code: ExplorerErrorCode,\n message?: string,\n options?: ErrorOptions\n ) {\n super(`{@sovereignbase/explorer} ${message ?? code}`, options)\n this.code = code\n this.name = 'ExplorerError'\n }\n}\n","import { ExplorerError } from './class.js'\n\n/**\n * Preserves an existing library error or wraps an unexpected failure once.\n * @param cause The value thrown by the failed operation.\n * @param operation The operation name to include in the error message.\n * @returns The original library error, or an `UNKNOWN_ERROR` retaining `cause`.\n */\nexport function unknownError(cause: unknown, operation: string): ExplorerError {\n return cause instanceof ExplorerError\n ? cause\n : new ExplorerError('UNKNOWN_ERROR', `${operation} failed.`, { cause })\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\nimport {\n invalidateDeviceBinding,\n type DeviceBindingStorage,\n} from '@sovereignbase/hardware-bound'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { ExplorerError } from '../.errors/class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes a credential registered with the database.\n */\nexport type CredentialEntry = {\n /** The display name of the credential. */\n name: string\n\n /** The entropy associated with the credential. */\n entropy: Uint8Array\n\n /** The storage characteristics of the device binding. */\n storage: DeviceBindingStorage\n\n /** The identifier of the object storing virtual entropy protected by the credential. */\n objectID: string\n\n /** The WebAuthn credential identifier. */\n credentialID: string\n\n /** The timestamp of the most recent use of the credential. */\n lastUsed: number\n\n /** The user agents that have used the credential. */\n userAgents: Array<{\n /** The user agent string. */\n name: string\n\n /** The timestamp of the most recent use by the user agent. */\n lastSeen: number\n }>\n}\n\n/**\n * Describes the properties used to create or update a credential entry.\n *\n * The object identifier, timestamp, and user agent are required to record use.\n * Creating an entry additionally requires the credential identifier,\n * display name, entropy, and storage characteristics.\n */\nexport type CredentialEvent = {\n /** The identifier of the object storing the credential's virtual entropy. */\n objectID?: string\n\n /** The WebAuthn credential identifier. */\n credentialID?: string\n\n /** The timestamp of the credential use. */\n timestamp?: number\n\n /** The user agent associated with the credential use. */\n userAgent?: string\n\n /** The display name of the credential. */\n name?: string\n\n /** The entropy associated with the credential. */\n entropy?: Uint8Array\n\n /** The storage characteristics of the device binding. */\n storage?: DeviceBindingStorage\n}\n\n/**\n * Represents a sequenced delta produced by the credential entries list.\n */\nexport type CredentialEntriesDelta = Readonly<\n ['credentialEntries', DeepReadonly<SequencedArrayDelta<CredentialEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the credential entries list.\n */\nexport type CredentialEntriesChange = DeepReadonly<\n SequencedArrayChange<CredentialEntry>\n>\n\n/**\n * Maps credential entries event names to their detail types.\n */\nexport type CredentialEntriesEventMap = {\n /** Fired when the credential entries list changes. */\n change: CredentialEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link CredentialEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type CredentialEntriesEventListener<\n K extends keyof CredentialEntriesEventMap,\n> =\n | ((event: CustomEvent<CredentialEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<CredentialEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type CredentialEntriesEventListenerFor<K extends string> =\n K extends keyof CredentialEntriesEventMap\n ? CredentialEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the database's registered credentials.\n *\n * Entries are identified by their object and credential identifiers.\n * Changes are synchronized through a sequenced array and persisted\n * as encrypted snapshots.\n */\nexport class CredentialEntries {\n private state: SequencedArray<CredentialEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<CredentialEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a credential entries list.\n *\n * @param id The identifier of the persisted entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param sessionCredentialID The WebAuthn credential ID of the current session.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n public readonly sessionCredentialID: string,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'credentialEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<CredentialEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n\n const seenObjects = new Set<string>()\n const seenCredentials = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (\n seenObjects.has(entry.objectID) ||\n seenCredentials.has(entry.credentialID)\n ) {\n duplicates.push(index)\n } else {\n seenObjects.add(entry.objectID)\n seenCredentials.add(entry.credentialID)\n }\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.constructor()')\n }\n }\n\n /**\n * Creates or updates a credential entry.\n *\n * Updates the credential's last-used timestamp and the corresponding\n * user agent's last-seen timestamp. Existing properties are preserved\n * unless replacements are provided.\n *\n * @param credentialEvent The credential properties to apply.\n * @throws {ExplorerError} If the event is invalid or setup properties are missing.\n */\n public record(credentialEvent: CredentialEvent): void {\n try {\n if (!credentialEvent || typeof credentialEvent !== 'object')\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event must be an object.'\n )\n\n const {\n objectID,\n credentialID,\n timestamp,\n userAgent,\n name,\n entropy,\n storage,\n } = credentialEvent\n\n if (\n typeof objectID !== 'string' ||\n typeof timestamp !== 'number' ||\n !Number.isFinite(timestamp) ||\n typeof userAgent !== 'string'\n )\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event requires an object ID, a finite timestamp, and a user agent.'\n )\n\n let existing: CredentialEntry | undefined\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const entry = this.state.at(index)\n\n if (\n entry?.objectID === objectID ||\n (credentialID !== undefined && entry?.credentialID === credentialID)\n ) {\n existing = entry\n break\n }\n }\n\n if (\n !existing &&\n (typeof credentialID !== 'string' ||\n typeof name !== 'string' ||\n !(entropy instanceof Uint8Array) ||\n typeof storage !== 'string')\n )\n throw new ExplorerError(\n 'MISSING_CREDENTIAL_SETUP_PROPERTIES',\n 'A new credential requires a credential ID, name, entropy, and binding storage.'\n )\n\n const userAgents =\n existing?.userAgents.map((agent) => ({ ...agent })) ?? []\n\n const agent = userAgents.find((agent) => agent.name === userAgent)\n\n if (agent) {\n agent.lastSeen = Math.max(agent.lastSeen, timestamp)\n } else {\n userAgents.push({ name: userAgent, lastSeen: timestamp })\n }\n\n const entry: CredentialEntry = {\n objectID,\n credentialID: credentialID ?? existing!.credentialID,\n name: name ?? existing!.name,\n entropy: entropy ?? existing!.entropy,\n storage: storage ?? existing!.storage,\n lastUsed: Math.max(existing?.lastUsed ?? timestamp, timestamp),\n userAgents,\n }\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const current = this.state.at(index)\n\n if (\n current?.objectID === entry.objectID ||\n current?.credentialID === entry.credentialID\n )\n void this.state.delete(index)\n }\n\n void this.state.unshift(entry)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.record()')\n }\n }\n\n /**\n * Revokes a credential and removes its registered entries.\n *\n * Deletes the credential's persisted objects before removing its entries.\n * The entries remain unchanged if deletion fails. Credential invalidation\n * is requested on a best-effort basis after successful removal.\n *\n * @param credentialID The WebAuthn credential identifier to revoke.\n */\n public async revoke(credentialID: string): Promise<void> {\n try {\n const objectIDs = new Set<string>()\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const entry = this.state.at(index)\n\n if (entry?.credentialID === credentialID) objectIDs.add(entry.objectID)\n }\n\n if (objectIDs.size === 0) return\n\n for (const objectID of objectIDs)\n await Explorer.deleteObjectByID(objectID)\n\n let removed = false\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.credentialID === credentialID) {\n void this.state.delete(index)\n removed = true\n }\n }\n\n if (removed) void this.state.snapshot()\n\n try {\n await invalidateDeviceBinding(credentialID)\n } catch {\n // Credential invalidation is best-effort.\n }\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.revoke()')\n }\n }\n\n /**\n * Returns the credential entries ordered by most recent use.\n *\n * Entries with equal timestamps are ordered by credential identifier.\n */\n public list(): DeepReadonly<Array<CredentialEntry>> {\n try {\n return this.state\n .slice()\n .sort(\n (a, b) =>\n b.lastUsed - a.lastUsed ||\n a.credentialID.localeCompare(b.credentialID)\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.list()')\n }\n }\n\n /**\n * Applies a sequenced delta to the credential entries list.\n *\n * @param delta The delta to apply.\n */\n public apply(delta: unknown): void {\n try {\n void this.state.apply(delta)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.apply()')\n }\n }\n\n /**\n * Merges a snapshot into the credential entries list.\n *\n * @param snapshot The snapshot to merge.\n */\n public merge(snapshot: unknown): void {\n try {\n void this.state.merge(snapshot)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.merge()')\n }\n }\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public addEventListener<K extends keyof CredentialEntriesEventMap>(\n type: K,\n listener: CredentialEntriesEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public removeEventListener<K extends keyof CredentialEntriesEventMap>(\n type: K,\n listener: CredentialEntriesEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public dispatchEvent<K extends keyof CredentialEntriesEventMap>(\n type: K,\n detail: CredentialEntriesEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.dispatchEvent()')\n }\n }\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes an entry in the recent objects list.\n */\nexport type RecentEntry = {\n /** The identifier of the referenced object. */\n id: string\n\n /** The display name of the referenced object. */\n name: string\n\n /** The cipher key used to access the referenced object. */\n cipherKey: CipherKey\n\n /** The date and time at which the object was most recently accessed. */\n dateAccessed: number\n}\n\n/**\n * Represents a sequenced delta produced by the recent entries list.\n */\nexport type RecentEntriesDelta = Readonly<\n ['recentEntries', DeepReadonly<SequencedArrayDelta<RecentEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the recent entries list.\n */\nexport type RecentEntriesChange = DeepReadonly<\n SequencedArrayChange<RecentEntry>\n>\n\n/**\n * Maps recent entries event names to their detail types.\n */\nexport type RecentEntriesEventMap = {\n /** Fired when the recent entries list changes. */\n change: RecentEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link RecentEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type RecentEntriesEventListener<K extends keyof RecentEntriesEventMap> =\n | ((event: CustomEvent<RecentEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<RecentEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type RecentEntriesEventListenerFor<K extends string> =\n K extends keyof RecentEntriesEventMap\n ? RecentEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the ordered list of recently accessed objects.\n *\n * Entries are ordered from most recently accessed to least recently accessed,\n * deduplicated by identifier, and limited to 20 entries.\n */\nexport class RecentEntries {\n private state: SequencedArray<RecentEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<RecentEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a recent entries list.\n *\n * @param id The identifier of the persisted recent entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n //\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'recentEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n //\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<RecentEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n //\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n //\n const seen = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (seen.has(entry.id) || seen.size >= 20) duplicates.push(index)\n else seen.add(entry.id)\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n //\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n //\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.constructor()')\n }\n }\n\n /**\n * Marks an entry as most recently accessed.\n *\n * Any existing entry with the same identifier is removed before the new\n * entry is inserted at the beginning of the list.\n *\n * @param accessed The entry that was accessed.\n */\n public update(accessed: RecentEntry): void {\n try {\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id === accessed.id)\n void this.state.delete(index)\n }\n\n void this.state.unshift(accessed)\n\n while (this.state.length > 20) {\n void this.state.pop()\n }\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.update()')\n }\n }\n\n /**\n * Removes an entry from the recent entries list.\n *\n * The referenced object is not deleted.\n *\n * @param id The identifier of the entry to remove.\n */\n public delete(id: string): void {\n try {\n let deleted = false\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.id !== id) continue\n void this.state.delete(index)\n deleted = true\n }\n if (deleted) void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.delete()')\n }\n }\n\n /**\n * Returns the recent entries ordered by most recent access.\n */\n public list(): DeepReadonly<Array<RecentEntry>> {\n try {\n return this.state\n .slice()\n .sort(\n (a, b) => b.dateAccessed - a.dateAccessed || a.id.localeCompare(b.id)\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.list()')\n }\n }\n //\n /**\n * Applies a sequenced delta to the recent entries list.\n *\n * @param delta The delta to apply.\n */\n public apply(delta: unknown): void {\n try {\n void this.state.apply(delta)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.apply()')\n }\n }\n\n /**\n * Merges a snapshot into the recent entries list.\n *\n * @param snapshot The snapshot to merge.\n */\n public merge(snapshot: unknown): void {\n try {\n void this.state.merge(snapshot)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.merge()')\n }\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public addEventListener<K extends keyof RecentEntriesEventMap>(\n type: K,\n listener: RecentEntriesEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public removeEventListener<K extends keyof RecentEntriesEventMap>(\n type: K,\n listener: RecentEntriesEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public dispatchEvent<K extends keyof RecentEntriesEventMap>(\n type: K,\n detail: RecentEntriesEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'RecentEntries.dispatchEvent()')\n }\n }\n}\n","import {\n deleteObject,\n loadObject,\n storeObject,\n type URLString,\n} from '@sovereignbase/storage'\nimport { isRecord, type DeepReadonly } from '@sovereignbase/utils'\nimport { Bytes } from '@sovereignbase/bytecodec'\nimport { readableUserAgent } from '@sovereignbase/dom-utils'\nimport { accessDeviceBinding } from '@sovereignbase/hardware-bound'\nimport { Cryptographic, type CipherKey } from '@sovereignbase/cryptosuite'\n//\nimport type {\n AuthenticationResult,\n AuthenticationSetupParams,\n //\n ExplorerEventMap,\n ExplorerEventListenerFor,\n //\n ExplorerDelta,\n} from './.types/type.js'\n//\nimport { ExplorerError } from './.errors/class.js'\nimport { unknownError } from './.errors/unkown.js'\n//\nimport {\n CredentialEntries,\n type CredentialEntry,\n type CredentialEvent,\n type CredentialEntriesChange,\n} from './CredentialEntries/class.js'\n//\nimport {\n RecentEntries,\n type RecentEntry,\n type RecentEntriesChange,\n} from './RecentEntries/class.js'\n\n/**\n * Provides authentication, encrypted object storage, and access to\n * the database's synchronized entries.\n *\n * Maintains the authenticated database context and exposes static methods\n * for managing stored objects, observing entries, and applying remote deltas.\n */\nexport class Explorer {\n /** The identifier of the authenticated database. */\n public static databaseID: string\n\n /** Indicates whether the database has been authenticated. */\n public static isAuthenticated: boolean\n\n //\n private static storageURL: URLString\n private static eventTarget?: EventTarget\n private static databaseSecret: Readonly<Uint8Array>\n private static sessionCredential?: CredentialEvent = {}\n\n /**\n * Caches cipher keys by object identifier.\n *\n * Existing entries are preserved when an object is accessed with\n * a cipher key.\n */\n public static readonly cipherKeyCache: Map<string, CipherKey> = new Map()\n\n /**\n * Authenticates a database using a data PIN and device-bound entropy.\n *\n * Derives the credential and database identifiers from cryptographic\n * secrets. When setup parameters are provided, creates and stores a\n * new credential. Otherwise, loads the existing credential.\n *\n * Records the authenticated credential in the database's credential list.\n *\n * @param dataPIN The PIN used to derive the credential and database secrets.\n * @param storageURL The base URL used to access persisted database objects.\n * @param setupParams Optional parameters for creating a new credential.\n * @returns The authentication result.\n */\n public static async authenticate(\n dataPIN: string,\n storageURL: URLString,\n setupParams?: AuthenticationSetupParams\n ): Promise<AuthenticationResult> {\n try {\n this.storageURL = storageURL\n //\n const deviceBinding = await accessDeviceBinding()\n if (!Array.isArray(deviceBinding)) return 'authenticator-error'\n const [credentialID, deviceEntropy] = deviceBinding\n //\n this.sessionCredential = {\n timestamp: Date.now(),\n userAgent: readableUserAgent(),\n }\n //\n const dataPINBytes = Bytes.utf8.decode(dataPIN)\n //\n const credentialSecret = await Bytes.derive(\n deviceEntropy,\n dataPINBytes,\n 32\n )\n //\n const credentialObjectID = await Cryptographic.identifier.derive(\n credentialSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialObjectID'),\n 32\n )\n this.sessionCredential.objectID = credentialObjectID\n this.sessionCredential.credentialID = credentialID\n //\n const credentialCipherKey = await Cryptographic.cipherMessage.deriveKey(\n credentialSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialCipherKey')\n )\n //\n let virtualEntropy: Uint8Array\n ////////\n // SETUP\n if (isRecord(setupParams)) {\n virtualEntropy = Bytes.generate(64)\n\n void (await this.storeObjectByID(\n credentialObjectID,\n virtualEntropy,\n credentialCipherKey\n ))\n\n this.sessionCredential.name = setupParams.credentialName\n this.sessionCredential.entropy = deviceEntropy\n this.sessionCredential.storage = setupParams.bindingStorage\n }\n ////////\n // LOGIN\n else {\n const result = await this.loadObjectByID(\n credentialObjectID,\n credentialCipherKey\n )\n if (!(result instanceof Uint8Array) || result.byteLength !== 64)\n return 'invalid-data-pin'\n\n virtualEntropy = result\n }\n //\n this.databaseSecret = await Bytes.derive(virtualEntropy, dataPINBytes, 64)\n\n this.databaseID = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:DatabaseID'),\n 32\n )\n\n this.isAuthenticated = true\n\n void (await this.loadCredentialEntries()).record(this.sessionCredential)\n delete this.sessionCredential\n\n void this.dispatchEvent('authenticated', true)\n\n return 'authenticated'\n } catch (cause) {\n throw unknownError(cause, 'Explorer.authenticate()')\n }\n }\n\n /**\n * Permanently revokes a credential and deletes its persisted data.\n *\n * Removes the credential from the database's registered credentials.\n *\n * @param credentialID The identifier of the credential to revoke.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async permanentlyDeleteCredential(\n credentialID: string\n ): Promise<void> {\n try {\n return void (await (\n await this.loadCredentialEntries()\n ).revoke(credentialID))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.permanentlyDeleteCredential()')\n }\n }\n\n /**\n * Returns the registered credentials and optionally observes their changes.\n *\n * The returned tuple contains a cleanup function, the current credential\n * entries, and the identifier of the current session's credential.\n *\n * The cleanup function removes the registered change listener, if any.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function, credential entries, and session credential ID.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async listCredentialEntries(\n onChange?: (splice: CredentialEntriesChange) => void\n ): Promise<\n [\n cleanup: () => void,\n entries: DeepReadonly<Array<CredentialEntry>>,\n sessionCredentialID: string,\n ]\n > {\n try {\n const credentialEntries = await this.loadCredentialEntries()\n\n const listener = onChange\n ? ({ detail }: CustomEvent<CredentialEntriesChange>) => onChange(detail)\n : undefined\n\n if (listener) credentialEntries.addEventListener('change', listener)\n\n return [\n () => {\n if (listener)\n credentialEntries.removeEventListener('change', listener)\n },\n credentialEntries.list(),\n credentialEntries.sessionCredentialID,\n ]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.listCredentialEntries()')\n }\n }\n\n /**\n * Returns the recently accessed objects and optionally observes their changes.\n *\n * The returned tuple contains a cleanup function and the current recent\n * entries. The cleanup function removes the registered change listener,\n * if any.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function and recent entries.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n public static async listRecentEntries(\n onChange?: (splice: RecentEntriesChange) => void\n ): Promise<[cleanup: () => void, entries: DeepReadonly<Array<RecentEntry>>]> {\n try {\n const recentEntries = await this.loadRecentEntries()\n\n const listener = onChange\n ? ({ detail }: CustomEvent<RecentEntriesChange>) => onChange(detail)\n : undefined\n\n if (listener) recentEntries.addEventListener('change', listener)\n\n return [\n () => {\n if (listener) recentEntries.removeEventListener('change', listener)\n },\n recentEntries.list(),\n ]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.listRecentEntries()')\n }\n }\n\n /**\n * Loads an encrypted object using its identifier and cipher key.\n *\n * Caches the cipher key if the identifier has not been encountered before.\n *\n * @typeParam T The expected type of the loaded object.\n * @param id The identifier of the object to load.\n * @param cipherKey The key used to decrypt the object.\n * @param onNetworkResponse An optional callback for network responses.\n * @returns The loaded object, or `undefined` if no object is available.\n */\n public static async loadObjectByID<T>(\n id: string,\n cipherKey: CipherKey,\n onNetworkResponse?: (data: unknown) => void | Promise<void>\n ): Promise<T | undefined> {\n try {\n if (!this.cipherKeyCache.has(id))\n void this.cipherKeyCache.set(id, cipherKey)\n //\n return (await loadObject(\n (this.storageURL + id) as URLString,\n cipherKey,\n false,\n onNetworkResponse\n )) as T\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadObjectByID()')\n }\n }\n\n /**\n * Encrypts and stores an object using its identifier and cipher key.\n *\n * Caches the cipher key if the identifier has not been encountered before.\n *\n * @typeParam T The type of the object to store.\n * @param id The identifier of the object to store.\n * @param object The object to encrypt and store.\n * @param cipherKey The key used to encrypt the object.\n */\n public static async storeObjectByID<T>(\n id: string,\n object: T,\n cipherKey: CipherKey\n ): Promise<void> {\n try {\n if (!this.cipherKeyCache.has(id))\n void this.cipherKeyCache.set(id, cipherKey)\n //\n return void (await storeObject(\n (this.storageURL + id) as URLString,\n object,\n cipherKey\n ))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.storeObjectByID()')\n }\n }\n\n /**\n * Deletes a persisted object using its identifier.\n *\n * @param id The identifier of the object to delete.\n */\n public static async deleteObjectByID(id: string): Promise<void> {\n try {\n void (await deleteObject((this.storageURL + id) as URLString))\n return void (await this.loadRecentEntries()).delete(id)\n } catch (cause) {\n throw unknownError(cause, 'Explorer.deleteObjectByID()')\n }\n }\n\n /**\n * Schedules the application of a synchronized Explorer delta.\n *\n * Recognized deltas are forwarded to their corresponding entries list.\n * Invalid or unrecognized delta types are ignored.\n *\n * Processing is scheduled in a microtask and is not awaited by the caller.\n *\n * @param data The delta to apply.\n */\n public static apply(data: unknown): void {\n return void queueMicrotask(async () => {\n try {\n if (!this.isExplorerDelta(data)) return\n switch (data[0]) {\n case 'recentEntries': {\n void (await this.loadRecentEntries()).apply(data[1])\n break\n }\n case 'credentialEntries': {\n void (await this.loadCredentialEntries()).apply(data[1])\n break\n }\n }\n return\n } catch (cause) {\n throw unknownError(cause, 'Explorer.apply()')\n }\n })\n }\n\n //* ███████╗██╗ ██╗███████╗███╗ ██╗████████╗ ████████╗ █████╗ ██████╗ ██████╗ ███████╗████████╗\n //* ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██╔══██╗██╔════╝ ██╔════╝╚══██╔══╝\n //* █████╗ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███████║██████╔╝██║ ███╗█████╗ ██║\n //* ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║\n //* ███████╗ ╚████╔╝ ███████╗██║ ╚████║ ██║ ██║ ██║ ██║██║ ██║╚██████╔╝███████╗ ██║\n //* ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝\n\n /**\n * Registers an event listener for the specified event type.\n *\n * @param type The event type.\n * @param listener The event listener, or `null`.\n * @param options The event listener options.\n */\n public static addEventListener<K extends keyof ExplorerEventMap>(\n type: K,\n listener: ExplorerEventListenerFor<K> | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n try {\n void (this.eventTarget ??= new EventTarget()).addEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'Explorer.addEventListener()')\n }\n }\n\n /**\n * Removes an event listener previously registered with\n * {@link addEventListener}.\n *\n * @param type The event type.\n * @param listener The event listener to remove, or `null`.\n * @param options The event listener options.\n */\n public static removeEventListener<K extends keyof ExplorerEventMap>(\n type: K,\n listener: ExplorerEventListenerFor<K> | null,\n options?: boolean | EventListenerOptions\n ): void {\n try {\n void this.eventTarget?.removeEventListener(\n type,\n listener as EventListenerOrEventListenerObject | null,\n options\n )\n } catch (cause) {\n throw unknownError(cause, 'Explorer.removeEventListener()')\n }\n }\n\n /**\n * Dispatches an event to the registered listeners.\n *\n * @param type The event type.\n * @param detail The value exposed through `CustomEvent.detail`.\n */\n public static dispatchEvent<K extends keyof ExplorerEventMap>(\n type: K,\n detail: ExplorerEventMap[K]\n ): void {\n try {\n void this.eventTarget?.dispatchEvent(new CustomEvent(type, { detail }))\n } catch (cause) {\n throw unknownError(cause, 'Explorer.dispatchEvent()')\n }\n }\n\n //* ██╗ ██╗███████╗██╗ ██████╗ ███████╗██████╗ ███████╗\n //* ██║ ██║██╔════╝██║ ██╔══██╗██╔════╝██╔══██╗██╔════╝\n //* ███████║█████╗ ██║ ██████╔╝█████╗ ██████╔╝███████╗\n //* ██╔══██║██╔══╝ ██║ ██╔═══╝ ██╔══╝ ██╔══██╗╚════██║\n //* ██║ ██║███████╗███████╗██║ ███████╗██║ ██║███████║\n //* ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝\n\n /**\n * Determines whether a value has the basic structure of an Explorer delta.\n *\n * @param data The value to inspect.\n * @returns Whether the value has a string discriminator.\n */\n private static isExplorerDelta(data: unknown): data is ExplorerDelta {\n return Array.isArray(data) && typeof data[0] === 'string'\n }\n\n private static recentEntries?: RecentEntries\n\n /**\n * Loads or returns the cached recent entries list.\n *\n * Derives the list identifier and cipher key from the database secret\n * and merges subsequent network responses into the loaded state.\n *\n * @returns The recent entries list.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n private static async loadRecentEntries(): Promise<RecentEntries> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (this.recentEntries) return this.recentEntries\n\n const id = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:RecentEntriesID'),\n 32\n )\n const ck = await Cryptographic.cipherMessage.deriveKey(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:RecentEntriesCipherKey')\n )\n\n const trustedSnapshot = await this.loadObjectByID(\n id,\n ck,\n (snapshot) => void this.recentEntries!.merge(snapshot)\n )\n\n this.recentEntries = new RecentEntries(id, ck, 0, trustedSnapshot)\n\n return this.recentEntries\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadRecentEntries()')\n }\n }\n\n private static credentialEntries?: CredentialEntries\n\n /**\n * Loads or returns the cached credential entries list.\n *\n * Derives the list identifier and cipher key from the database secret\n * and merges subsequent network responses into the loaded state.\n *\n * @returns The credential entries list.\n * @throws {ExplorerError} If the database is not authenticated.\n */\n private static async loadCredentialEntries(): Promise<CredentialEntries> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (this.credentialEntries) return this.credentialEntries\n\n const id = await Cryptographic.identifier.derive(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialEntriesID'),\n 32\n )\n const ck = await Cryptographic.cipherMessage.deriveKey(\n this.databaseSecret,\n Bytes.utf8.decode('@sovereignbase/explorer:CredentialEntriesCipherKey')\n )\n\n const trustedSnapshot = await this.loadObjectByID(\n id,\n ck,\n (snapshot) => void this.credentialEntries!.merge(snapshot)\n )\n\n this.credentialEntries = new CredentialEntries(\n id,\n ck,\n this.sessionCredential!.credentialID!,\n 0,\n trustedSnapshot\n )\n\n return this.credentialEntries\n } catch (cause) {\n throw unknownError(cause, 'Explorer.loadCredentialEntries()')\n }\n }\n}\n\nexport { ExplorerError, type ExplorerErrorCode } from './.errors/class.js'\n\nexport * from './.types/type.js'\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
4
|
"name": "@sovereignbase/explorer",
|
|
5
|
-
"version": "0.0.0-alpha.
|
|
5
|
+
"version": "0.0.0-alpha.5",
|
|
6
6
|
"description": "WIP",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"typescript",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"test:vitest": "vitest run --coverage",
|
|
37
37
|
"test:e2e": "playwright test",
|
|
38
38
|
"bench": "npm run build && node benchmark/bench.js",
|
|
39
|
-
"prepare": "husky"
|
|
39
|
+
"prepare": "husky",
|
|
40
|
+
"prepublishOnly": "npm run build:package"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@commitlint/cli": "^21.2.4",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"@sovereignbase/bytecodec": "^2.0.1",
|
|
65
66
|
"@sovereignbase/cryptosuite": "^3.2.0",
|
|
66
67
|
"@sovereignbase/dom-utils": "^1.0.1",
|
|
67
|
-
"@sovereignbase/hardware-bound": "^
|
|
68
|
+
"@sovereignbase/hardware-bound": "^3.0.0",
|
|
68
69
|
"@sovereignbase/sequenced": "^0.0.2",
|
|
69
70
|
"@sovereignbase/sequencer": "^1.1.1",
|
|
70
71
|
"@sovereignbase/storage": "^3.2.0",
|