@sovereignbase/explorer 0.0.0-alpha.5 → 0.0.0-alpha.6
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 +115 -39
- package/dist/class.cjs.map +1 -1
- package/dist/class.d.cts +82 -34
- package/dist/class.d.cts.map +1 -1
- package/dist/class.d.ts +82 -34
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +116 -40
- package/dist/class.js.map +1 -1
- package/package.json +3 -3
package/dist/class.cjs
CHANGED
|
@@ -196,6 +196,7 @@ var CredentialEntries = class {
|
|
|
196
196
|
try {
|
|
197
197
|
await (0, _sovereignbase_hardware_bound.invalidateDeviceBinding)(credentialID);
|
|
198
198
|
} catch {}
|
|
199
|
+
if (credentialID === this.sessionCredentialID) window.location.reload();
|
|
199
200
|
} catch (cause) {
|
|
200
201
|
throw unknownError(cause, "CredentialEntries.revoke()");
|
|
201
202
|
}
|
|
@@ -456,20 +457,27 @@ var RecentEntries = class {
|
|
|
456
457
|
};
|
|
457
458
|
// src/class.ts
|
|
458
459
|
/**
|
|
459
|
-
* Provides
|
|
460
|
-
* the database's synchronized entries.
|
|
460
|
+
* Provides access to an authenticated Sovereignbase database.
|
|
461
461
|
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
462
|
+
* Manages database authentication, encrypted object storage, credential
|
|
463
|
+
* registration, database linking, and synchronized entries.
|
|
464
464
|
*/
|
|
465
465
|
var Explorer = class {
|
|
466
466
|
/** The identifier of the authenticated database. */
|
|
467
467
|
static databaseID;
|
|
468
|
-
/** Indicates whether
|
|
468
|
+
/** Indicates whether a database has been authenticated. */
|
|
469
469
|
static isAuthenticated;
|
|
470
|
+
/** The base URL used to access database objects. */
|
|
470
471
|
static storageURL;
|
|
472
|
+
/** The event target used for Explorer events. */
|
|
471
473
|
static eventTarget;
|
|
474
|
+
/** The data PIN associated with the current session. */
|
|
475
|
+
static dataPIN;
|
|
476
|
+
/** The virtual entropy associated with the authenticated database. */
|
|
477
|
+
static virtualEntropy;
|
|
478
|
+
/** The secret used to access the authenticated database. */
|
|
472
479
|
static databaseSecret;
|
|
480
|
+
/** The credential information associated with the current authentication. */
|
|
473
481
|
static sessionCredential = {};
|
|
474
482
|
/**
|
|
475
483
|
* Caches cipher keys by object identifier.
|
|
@@ -479,23 +487,26 @@ var Explorer = class {
|
|
|
479
487
|
*/
|
|
480
488
|
static cipherKeyCache = /* @__PURE__ */ new Map();
|
|
481
489
|
/**
|
|
482
|
-
* Authenticates a database using a data PIN and device
|
|
490
|
+
* Authenticates a database using a data PIN and an existing device binding.
|
|
483
491
|
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
* new credential. Otherwise, loads the existing credential.
|
|
492
|
+
* Establishes the authenticated database context and records the credential
|
|
493
|
+
* used for authentication.
|
|
487
494
|
*
|
|
488
|
-
*
|
|
495
|
+
* When setup parameters are provided, registers a credential using the
|
|
496
|
+
* supplied virtual entropy or generates new virtual entropy.
|
|
489
497
|
*
|
|
490
|
-
* @param dataPIN The PIN
|
|
491
|
-
* @param storageURL The base URL used to access
|
|
492
|
-
* @param setupParams Optional parameters for
|
|
498
|
+
* @param dataPIN The PIN associated with the database.
|
|
499
|
+
* @param storageURL The base URL used to access database objects.
|
|
500
|
+
* @param setupParams Optional parameters for registering a credential.
|
|
501
|
+
*
|
|
502
|
+
* @param credentialId An optional credential identifier used to restrict credential selection.
|
|
493
503
|
* @returns The authentication result.
|
|
494
504
|
*/
|
|
495
|
-
static async authenticate(dataPIN, storageURL, setupParams) {
|
|
505
|
+
static async authenticate(dataPIN, storageURL, setupParams, credentialId) {
|
|
496
506
|
try {
|
|
507
|
+
this.dataPIN = dataPIN;
|
|
497
508
|
this.storageURL = storageURL;
|
|
498
|
-
const deviceBinding = await (0, _sovereignbase_hardware_bound.accessDeviceBinding)();
|
|
509
|
+
const deviceBinding = await (0, _sovereignbase_hardware_bound.accessDeviceBinding)(credentialId);
|
|
499
510
|
if (!Array.isArray(deviceBinding)) return "authenticator-error";
|
|
500
511
|
const [credentialID, deviceEntropy] = deviceBinding;
|
|
501
512
|
this.sessionCredential = {
|
|
@@ -508,19 +519,18 @@ var Explorer = class {
|
|
|
508
519
|
this.sessionCredential.objectID = credentialObjectID;
|
|
509
520
|
this.sessionCredential.credentialID = credentialID;
|
|
510
521
|
const credentialCipherKey = await _sovereignbase_cryptosuite.Cryptographic.cipherMessage.deriveKey(credentialSecret, _sovereignbase_bytecodec.Bytes.utf8.decode("@sovereignbase/explorer:CredentialCipherKey"));
|
|
511
|
-
let virtualEntropy;
|
|
512
522
|
if ((0, _sovereignbase_utils.isRecord)(setupParams)) {
|
|
513
|
-
virtualEntropy = _sovereignbase_bytecodec.Bytes.generate(64);
|
|
514
|
-
await this.storeObjectByID(credentialObjectID, virtualEntropy, credentialCipherKey);
|
|
523
|
+
this.virtualEntropy = setupParams.virtualEntropy instanceof Uint8Array ? setupParams.virtualEntropy : _sovereignbase_bytecodec.Bytes.generate(64);
|
|
524
|
+
await this.storeObjectByID(credentialObjectID, this.virtualEntropy, credentialCipherKey);
|
|
515
525
|
this.sessionCredential.name = setupParams.credentialName;
|
|
516
526
|
this.sessionCredential.entropy = deviceEntropy;
|
|
517
527
|
this.sessionCredential.storage = setupParams.bindingStorage;
|
|
518
528
|
} else {
|
|
519
529
|
const result = await this.loadObjectByID(credentialObjectID, credentialCipherKey);
|
|
520
530
|
if (!(result instanceof Uint8Array) || result.byteLength !== 64) return "invalid-data-pin";
|
|
521
|
-
virtualEntropy = result;
|
|
531
|
+
this.virtualEntropy = result;
|
|
522
532
|
}
|
|
523
|
-
this.databaseSecret = await _sovereignbase_bytecodec.Bytes.derive(virtualEntropy, dataPINBytes, 64);
|
|
533
|
+
this.databaseSecret = await _sovereignbase_bytecodec.Bytes.derive(this.virtualEntropy, dataPINBytes, 64);
|
|
524
534
|
this.databaseID = await _sovereignbase_cryptosuite.Cryptographic.identifier.derive(this.databaseSecret, _sovereignbase_bytecodec.Bytes.utf8.decode("@sovereignbase/explorer:DatabaseID"), 32);
|
|
525
535
|
this.isAuthenticated = true;
|
|
526
536
|
(await this.loadCredentialEntries()).record(this.sessionCredential);
|
|
@@ -531,10 +541,71 @@ var Explorer = class {
|
|
|
531
541
|
throw unknownError(cause, "Explorer.authenticate()");
|
|
532
542
|
}
|
|
533
543
|
}
|
|
544
|
+
/** The number of consecutive failed database linking PIN attempts. */
|
|
545
|
+
static failCounter = 0;
|
|
546
|
+
/**
|
|
547
|
+
* Prepares an authenticated database for linking to another device.
|
|
548
|
+
*
|
|
549
|
+
* Verifies the current data PIN and prepares an encrypted transfer
|
|
550
|
+
* containing the information required to access the same database.
|
|
551
|
+
*
|
|
552
|
+
* Repeated PIN failures cause the current page to reload.
|
|
553
|
+
*
|
|
554
|
+
* @param dataPIN The PIN associated with the authenticated database.
|
|
555
|
+
* @param encapsulateKey The key used to establish encryption for the transfer.
|
|
556
|
+
* @returns The key offer and encrypted transfer message, or `false`
|
|
557
|
+
* if the supplied PIN is incorrect.
|
|
558
|
+
*/
|
|
559
|
+
static async prepareDatabaseLink(dataPIN, encapsulateKey) {
|
|
560
|
+
try {
|
|
561
|
+
if (!this.isAuthenticated) throw new ExplorerError("NOT_AUTHENTICATED");
|
|
562
|
+
if (dataPIN !== this.dataPIN) {
|
|
563
|
+
if (this.failCounter >= 2) window.location.reload();
|
|
564
|
+
this.failCounter++;
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
this.failCounter = 0;
|
|
568
|
+
const { keyOffer, cipherKey } = await _sovereignbase_cryptosuite.Cryptographic.keyAgreement.encapsulate(encapsulateKey);
|
|
569
|
+
return [keyOffer, await _sovereignbase_cryptosuite.Cryptographic.cipherMessage.encrypt(cipherKey, _sovereignbase_bytecodec.Bytes.concat([this.virtualEntropy, _sovereignbase_bytecodec.Bytes.utf8.decode(dataPIN)]))];
|
|
570
|
+
} catch (cause) {
|
|
571
|
+
throw unknownError(cause, "Explorer.prepareDatabaseLink()");
|
|
572
|
+
}
|
|
573
|
+
}
|
|
534
574
|
/**
|
|
535
|
-
*
|
|
575
|
+
* Links the current device to an existing database.
|
|
536
576
|
*
|
|
537
|
-
*
|
|
577
|
+
* Accesses the transferred database information, creates a device binding,
|
|
578
|
+
* and registers a credential for the existing database.
|
|
579
|
+
*
|
|
580
|
+
* @param keyOffer The key offer associated with the database link.
|
|
581
|
+
* @param cipherMessage The encrypted database linking message.
|
|
582
|
+
* @param decapsulateKey The key used to establish decryption for the transfer.
|
|
583
|
+
* @param credentialName The display name assigned to the new credential.
|
|
584
|
+
* @param storageURL The base URL used to access database objects.
|
|
585
|
+
* @returns `true` if the database was linked and authenticated;
|
|
586
|
+
* otherwise, `false`.
|
|
587
|
+
*/
|
|
588
|
+
static async linkToDatabase(keyOffer, cipherMessage, decapsulateKey, credentialName, storageURL) {
|
|
589
|
+
try {
|
|
590
|
+
const { cipherKey } = await _sovereignbase_cryptosuite.Cryptographic.keyAgreement.decapsulate(keyOffer, decapsulateKey);
|
|
591
|
+
const raw = await _sovereignbase_cryptosuite.Cryptographic.cipherMessage.decrypt(cipherKey, cipherMessage);
|
|
592
|
+
const setupResult = await (0, _sovereignbase_hardware_bound.createDeviceBinding)(credentialName);
|
|
593
|
+
if (!Array.isArray(setupResult)) return false;
|
|
594
|
+
const [credentialId, bindingStorage] = setupResult;
|
|
595
|
+
return await this.authenticate(_sovereignbase_bytecodec.Bytes.utf8.encode(raw.slice(64)), storageURL, {
|
|
596
|
+
credentialName,
|
|
597
|
+
virtualEntropy: raw.slice(0, 64),
|
|
598
|
+
bindingStorage
|
|
599
|
+
}, credentialId) === "authenticated" ? true : false;
|
|
600
|
+
} catch (cause) {
|
|
601
|
+
throw unknownError(cause, "Explorer.linkToDatabase()");
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Permanently revokes a registered database credential.
|
|
606
|
+
*
|
|
607
|
+
* Removes the credential's persisted data and registration from
|
|
608
|
+
* the authenticated database.
|
|
538
609
|
*
|
|
539
610
|
* @param credentialID The identifier of the credential to revoke.
|
|
540
611
|
* @throws {ExplorerError} If the database is not authenticated.
|
|
@@ -548,15 +619,14 @@ var Explorer = class {
|
|
|
548
619
|
}
|
|
549
620
|
}
|
|
550
621
|
/**
|
|
551
|
-
* Returns the registered credentials
|
|
552
|
-
*
|
|
553
|
-
* The returned tuple contains a cleanup function, the current credential
|
|
554
|
-
* entries, and the identifier of the current session's credential.
|
|
622
|
+
* Returns the registered database credentials.
|
|
555
623
|
*
|
|
556
|
-
*
|
|
624
|
+
* Optionally registers a listener for changes to the credential entries.
|
|
625
|
+
* The returned cleanup function removes the registered listener.
|
|
557
626
|
*
|
|
558
627
|
* @param onChange An optional callback invoked when the entries change.
|
|
559
|
-
* @returns The cleanup function, credential entries, and
|
|
628
|
+
* @returns The cleanup function, credential entries, and identifier
|
|
629
|
+
* of the current session's credential.
|
|
560
630
|
* @throws {ExplorerError} If the database is not authenticated.
|
|
561
631
|
*/
|
|
562
632
|
static async listCredentialEntries(onChange) {
|
|
@@ -576,11 +646,10 @@ var Explorer = class {
|
|
|
576
646
|
}
|
|
577
647
|
}
|
|
578
648
|
/**
|
|
579
|
-
* Returns the recently accessed objects
|
|
649
|
+
* Returns the recently accessed database objects.
|
|
580
650
|
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
* if any.
|
|
651
|
+
* Optionally registers a listener for changes to the recent entries.
|
|
652
|
+
* The returned cleanup function removes the registered listener.
|
|
584
653
|
*
|
|
585
654
|
* @param onChange An optional callback invoked when the entries change.
|
|
586
655
|
* @returns The cleanup function and recent entries.
|
|
@@ -599,9 +668,9 @@ var Explorer = class {
|
|
|
599
668
|
}
|
|
600
669
|
}
|
|
601
670
|
/**
|
|
602
|
-
* Loads an encrypted object
|
|
671
|
+
* Loads an encrypted database object by its identifier.
|
|
603
672
|
*
|
|
604
|
-
* Caches the cipher key if
|
|
673
|
+
* Caches the supplied cipher key if no key has been cached for the object.
|
|
605
674
|
*
|
|
606
675
|
* @typeParam T The expected type of the loaded object.
|
|
607
676
|
* @param id The identifier of the object to load.
|
|
@@ -618,9 +687,9 @@ var Explorer = class {
|
|
|
618
687
|
}
|
|
619
688
|
}
|
|
620
689
|
/**
|
|
621
|
-
* Encrypts and stores
|
|
690
|
+
* Encrypts and stores a database object by its identifier.
|
|
622
691
|
*
|
|
623
|
-
* Caches the cipher key if
|
|
692
|
+
* Caches the supplied cipher key if no key has been cached for the object.
|
|
624
693
|
*
|
|
625
694
|
* @typeParam T The type of the object to store.
|
|
626
695
|
* @param id The identifier of the object to store.
|
|
@@ -637,7 +706,9 @@ var Explorer = class {
|
|
|
637
706
|
}
|
|
638
707
|
}
|
|
639
708
|
/**
|
|
640
|
-
* Deletes a
|
|
709
|
+
* Deletes a database object by its identifier.
|
|
710
|
+
*
|
|
711
|
+
* Removes the persisted object and its corresponding recent entry.
|
|
641
712
|
*
|
|
642
713
|
* @param id The identifier of the object to delete.
|
|
643
714
|
*/
|
|
@@ -677,8 +748,9 @@ var Explorer = class {
|
|
|
677
748
|
});
|
|
678
749
|
}
|
|
679
750
|
/**
|
|
680
|
-
* Registers an event listener for the specified event
|
|
751
|
+
* Registers an event listener for the specified Explorer event.
|
|
681
752
|
*
|
|
753
|
+
* @typeParam K The event name that determines the event detail type.
|
|
682
754
|
* @param type The event type.
|
|
683
755
|
* @param listener The event listener, or `null`.
|
|
684
756
|
* @param options The event listener options.
|
|
@@ -694,6 +766,7 @@ var Explorer = class {
|
|
|
694
766
|
* Removes an event listener previously registered with
|
|
695
767
|
* {@link addEventListener}.
|
|
696
768
|
*
|
|
769
|
+
* @typeParam K The event name that determines the listener type.
|
|
697
770
|
* @param type The event type.
|
|
698
771
|
* @param listener The event listener to remove, or `null`.
|
|
699
772
|
* @param options The event listener options.
|
|
@@ -706,8 +779,9 @@ var Explorer = class {
|
|
|
706
779
|
}
|
|
707
780
|
}
|
|
708
781
|
/**
|
|
709
|
-
* Dispatches an event to
|
|
782
|
+
* Dispatches an Explorer event to registered listeners.
|
|
710
783
|
*
|
|
784
|
+
* @typeParam K The event name that determines the event detail type.
|
|
711
785
|
* @param type The event type.
|
|
712
786
|
* @param detail The value exposed through `CustomEvent.detail`.
|
|
713
787
|
*/
|
|
@@ -727,6 +801,7 @@ var Explorer = class {
|
|
|
727
801
|
static isExplorerDelta(data) {
|
|
728
802
|
return Array.isArray(data) && typeof data[0] === "string";
|
|
729
803
|
}
|
|
804
|
+
/** The cached recent entries list. */
|
|
730
805
|
static recentEntries;
|
|
731
806
|
/**
|
|
732
807
|
* Loads or returns the cached recent entries list.
|
|
@@ -750,6 +825,7 @@ var Explorer = class {
|
|
|
750
825
|
throw unknownError(cause, "Explorer.loadRecentEntries()");
|
|
751
826
|
}
|
|
752
827
|
}
|
|
828
|
+
/** The cached credential entries list. */
|
|
753
829
|
static credentialEntries;
|
|
754
830
|
/**
|
|
755
831
|
* Loads or returns the cached credential entries list.
|
package/dist/class.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"file":"class.cjs","names":["Sequenced","afterIdleFor","invalidateDeviceBinding","Sequenced","afterIdleFor","accessDeviceBinding","readableUserAgent","Bytes","Cryptographic","isRecord","createDeviceBinding","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\n // Log out.\n if (credentialID === this.sessionCredentialID)\n void window.location.reload()\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 {\n accessDeviceBinding,\n createDeviceBinding,\n} from '@sovereignbase/hardware-bound'\nimport {\n Cryptographic,\n type DecapsulateKey,\n type EncapsulateKey,\n type KeyOffer,\n type CipherKey,\n type CipherMessage,\n} 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 access to an authenticated Sovereignbase database.\n *\n * Manages database authentication, encrypted object storage, credential\n * registration, database linking, and synchronized entries.\n */\nexport class Explorer {\n /** The identifier of the authenticated database. */\n public static databaseID: string\n\n /** Indicates whether a database has been authenticated. */\n public static isAuthenticated: boolean\n\n /** The base URL used to access database objects. */\n private static storageURL: URLString\n\n /** The event target used for Explorer events. */\n private static eventTarget?: EventTarget\n\n /** The data PIN associated with the current session. */\n private static dataPIN: string\n\n /** The virtual entropy associated with the authenticated database. */\n private static virtualEntropy: Readonly<Uint8Array>\n\n /** The secret used to access the authenticated database. */\n private static databaseSecret: Readonly<Uint8Array>\n\n /** The credential information associated with the current authentication. */\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 an existing device binding.\n *\n * Establishes the authenticated database context and records the credential\n * used for authentication.\n *\n * When setup parameters are provided, registers a credential using the\n * supplied virtual entropy or generates new virtual entropy.\n *\n * @param dataPIN The PIN associated with the database.\n * @param storageURL The base URL used to access database objects.\n * @param setupParams Optional parameters for registering a credential.\n *\n * @param credentialId An optional credential identifier used to restrict credential selection.\n * @returns The authentication result.\n */\n public static async authenticate(\n dataPIN: string,\n storageURL: URLString,\n setupParams?: AuthenticationSetupParams,\n credentialId?: string\n ): Promise<AuthenticationResult> {\n try {\n this.dataPIN = dataPIN\n this.storageURL = storageURL\n //\n const deviceBinding = await accessDeviceBinding(credentialId)\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 ////////\n // SETUP\n if (isRecord(setupParams)) {\n this.virtualEntropy =\n setupParams.virtualEntropy instanceof Uint8Array\n ? setupParams.virtualEntropy\n : Bytes.generate(64)\n\n void (await this.storeObjectByID(\n credentialObjectID,\n this.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 this.virtualEntropy = result\n }\n //\n this.databaseSecret = await Bytes.derive(\n this.virtualEntropy,\n dataPINBytes,\n 64\n )\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 /** The number of consecutive failed database linking PIN attempts. */\n private static failCounter: number = 0\n\n /**\n * Prepares an authenticated database for linking to another device.\n *\n * Verifies the current data PIN and prepares an encrypted transfer\n * containing the information required to access the same database.\n *\n * Repeated PIN failures cause the current page to reload.\n *\n * @param dataPIN The PIN associated with the authenticated database.\n * @param encapsulateKey The key used to establish encryption for the transfer.\n * @returns The key offer and encrypted transfer message, or `false`\n * if the supplied PIN is incorrect.\n */\n public static async prepareDatabaseLink(\n dataPIN: string,\n encapsulateKey: EncapsulateKey\n ): Promise<[keyOffer: KeyOffer, cipherMessage: CipherMessage] | false> {\n try {\n if (!this.isAuthenticated) throw new ExplorerError('NOT_AUTHENTICATED')\n\n if (dataPIN !== this.dataPIN) {\n if (this.failCounter >= 2) void window.location.reload() // log out\n this.failCounter++\n return false\n }\n\n this.failCounter = 0\n\n const { keyOffer, cipherKey } =\n await Cryptographic.keyAgreement.encapsulate(encapsulateKey)\n\n const cipherMessage = await Cryptographic.cipherMessage.encrypt(\n cipherKey,\n Bytes.concat([this.virtualEntropy, Bytes.utf8.decode(dataPIN)])\n )\n\n return [keyOffer, cipherMessage]\n } catch (cause) {\n throw unknownError(cause, 'Explorer.prepareDatabaseLink()')\n }\n }\n\n /**\n * Links the current device to an existing database.\n *\n * Accesses the transferred database information, creates a device binding,\n * and registers a credential for the existing database.\n *\n * @param keyOffer The key offer associated with the database link.\n * @param cipherMessage The encrypted database linking message.\n * @param decapsulateKey The key used to establish decryption for the transfer.\n * @param credentialName The display name assigned to the new credential.\n * @param storageURL The base URL used to access database objects.\n * @returns `true` if the database was linked and authenticated;\n * otherwise, `false`.\n */\n public static async linkToDatabase(\n keyOffer: KeyOffer,\n cipherMessage: CipherMessage,\n decapsulateKey: DecapsulateKey,\n credentialName: string,\n storageURL: URLString\n ): Promise<boolean> {\n try {\n const { cipherKey } = await Cryptographic.keyAgreement.decapsulate(\n keyOffer,\n decapsulateKey\n )\n const raw = await Cryptographic.cipherMessage.decrypt(\n cipherKey,\n cipherMessage\n )\n\n const setupResult = await createDeviceBinding(credentialName)\n if (!Array.isArray(setupResult)) return false\n const [credentialId, bindingStorage] = setupResult\n\n const result = await this.authenticate(\n Bytes.utf8.encode(raw.slice(64)),\n storageURL,\n {\n credentialName,\n virtualEntropy: raw.slice(0, 64),\n bindingStorage,\n },\n credentialId\n )\n\n return result === 'authenticated' ? true : false\n } catch (cause) {\n throw unknownError(cause, 'Explorer.linkToDatabase()')\n }\n }\n\n /**\n * Permanently revokes a registered database credential.\n *\n * Removes the credential's persisted data and registration from\n * the authenticated database.\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 database credentials.\n *\n * Optionally registers a listener for changes to the credential entries.\n * The returned cleanup function removes the registered listener.\n *\n * @param onChange An optional callback invoked when the entries change.\n * @returns The cleanup function, credential entries, and identifier\n * of the current session's credential.\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 database objects.\n *\n * Optionally registers a listener for changes to the recent entries.\n * The returned cleanup function removes the registered listener.\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 database object by its identifier.\n *\n * Caches the supplied cipher key if no key has been cached for the object.\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 a database object by its identifier.\n *\n * Caches the supplied cipher key if no key has been cached for the object.\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 database object by its identifier.\n *\n * Removes the persisted object and its corresponding recent entry.\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 Explorer event.\n *\n * @typeParam K The event name that determines the event detail type.\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 * @typeParam K The event name that determines the listener type.\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 Explorer event to registered listeners.\n *\n * @typeParam K The event name that determines the event detail type.\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 /** The cached recent entries list. */\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 /** The cached credential entries list. */\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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"}
|