@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.js CHANGED
@@ -18,7 +18,7 @@ import { deleteObject, loadObject, storeObject } from "@sovereignbase/storage";
18
18
  import { afterIdleFor, isRecord } from "@sovereignbase/utils";
19
19
  import { Bytes } from "@sovereignbase/bytecodec";
20
20
  import { readableUserAgent } from "@sovereignbase/dom-utils";
21
- import { accessDeviceBinding, invalidateDeviceBinding } from "@sovereignbase/hardware-bound";
21
+ import { accessDeviceBinding, createDeviceBinding, invalidateDeviceBinding } from "@sovereignbase/hardware-bound";
22
22
  import { Cryptographic } from "@sovereignbase/cryptosuite";
23
23
  import { Sequenced } from "@sovereignbase/sequenced";
24
24
  // src/.errors/class.ts
@@ -195,6 +195,7 @@ var CredentialEntries = class {
195
195
  try {
196
196
  await invalidateDeviceBinding(credentialID);
197
197
  } catch {}
198
+ if (credentialID === this.sessionCredentialID) window.location.reload();
198
199
  } catch (cause) {
199
200
  throw unknownError(cause, "CredentialEntries.revoke()");
200
201
  }
@@ -455,20 +456,27 @@ var RecentEntries = class {
455
456
  };
456
457
  // src/class.ts
457
458
  /**
458
- * Provides authentication, encrypted object storage, and access to
459
- * the database's synchronized entries.
459
+ * Provides access to an authenticated Sovereignbase database.
460
460
  *
461
- * Maintains the authenticated database context and exposes static methods
462
- * for managing stored objects, observing entries, and applying remote deltas.
461
+ * Manages database authentication, encrypted object storage, credential
462
+ * registration, database linking, and synchronized entries.
463
463
  */
464
464
  var Explorer = class {
465
465
  /** The identifier of the authenticated database. */
466
466
  static databaseID;
467
- /** Indicates whether the database has been authenticated. */
467
+ /** Indicates whether a database has been authenticated. */
468
468
  static isAuthenticated;
469
+ /** The base URL used to access database objects. */
469
470
  static storageURL;
471
+ /** The event target used for Explorer events. */
470
472
  static eventTarget;
473
+ /** The data PIN associated with the current session. */
474
+ static dataPIN;
475
+ /** The virtual entropy associated with the authenticated database. */
476
+ static virtualEntropy;
477
+ /** The secret used to access the authenticated database. */
471
478
  static databaseSecret;
479
+ /** The credential information associated with the current authentication. */
472
480
  static sessionCredential = {};
473
481
  /**
474
482
  * Caches cipher keys by object identifier.
@@ -478,23 +486,26 @@ var Explorer = class {
478
486
  */
479
487
  static cipherKeyCache = /* @__PURE__ */ new Map();
480
488
  /**
481
- * Authenticates a database using a data PIN and device-bound entropy.
489
+ * Authenticates a database using a data PIN and an existing device binding.
482
490
  *
483
- * Derives the credential and database identifiers from cryptographic
484
- * secrets. When setup parameters are provided, creates and stores a
485
- * new credential. Otherwise, loads the existing credential.
491
+ * Establishes the authenticated database context and records the credential
492
+ * used for authentication.
486
493
  *
487
- * Records the authenticated credential in the database's credential list.
494
+ * When setup parameters are provided, registers a credential using the
495
+ * supplied virtual entropy or generates new virtual entropy.
488
496
  *
489
- * @param dataPIN The PIN used to derive the credential and database secrets.
490
- * @param storageURL The base URL used to access persisted database objects.
491
- * @param setupParams Optional parameters for creating a new credential.
497
+ * @param dataPIN The PIN associated with the database.
498
+ * @param storageURL The base URL used to access database objects.
499
+ * @param setupParams Optional parameters for registering a credential.
500
+ *
501
+ * @param credentialId An optional credential identifier used to restrict credential selection.
492
502
  * @returns The authentication result.
493
503
  */
494
- static async authenticate(dataPIN, storageURL, setupParams) {
504
+ static async authenticate(dataPIN, storageURL, setupParams, credentialId) {
495
505
  try {
506
+ this.dataPIN = dataPIN;
496
507
  this.storageURL = storageURL;
497
- const deviceBinding = await accessDeviceBinding();
508
+ const deviceBinding = await accessDeviceBinding(credentialId);
498
509
  if (!Array.isArray(deviceBinding)) return "authenticator-error";
499
510
  const [credentialID, deviceEntropy] = deviceBinding;
500
511
  this.sessionCredential = {
@@ -507,19 +518,18 @@ var Explorer = class {
507
518
  this.sessionCredential.objectID = credentialObjectID;
508
519
  this.sessionCredential.credentialID = credentialID;
509
520
  const credentialCipherKey = await Cryptographic.cipherMessage.deriveKey(credentialSecret, Bytes.utf8.decode("@sovereignbase/explorer:CredentialCipherKey"));
510
- let virtualEntropy;
511
521
  if (isRecord(setupParams)) {
512
- virtualEntropy = Bytes.generate(64);
513
- await this.storeObjectByID(credentialObjectID, virtualEntropy, credentialCipherKey);
522
+ this.virtualEntropy = setupParams.virtualEntropy instanceof Uint8Array ? setupParams.virtualEntropy : Bytes.generate(64);
523
+ await this.storeObjectByID(credentialObjectID, this.virtualEntropy, credentialCipherKey);
514
524
  this.sessionCredential.name = setupParams.credentialName;
515
525
  this.sessionCredential.entropy = deviceEntropy;
516
526
  this.sessionCredential.storage = setupParams.bindingStorage;
517
527
  } else {
518
528
  const result = await this.loadObjectByID(credentialObjectID, credentialCipherKey);
519
529
  if (!(result instanceof Uint8Array) || result.byteLength !== 64) return "invalid-data-pin";
520
- virtualEntropy = result;
530
+ this.virtualEntropy = result;
521
531
  }
522
- this.databaseSecret = await Bytes.derive(virtualEntropy, dataPINBytes, 64);
532
+ this.databaseSecret = await Bytes.derive(this.virtualEntropy, dataPINBytes, 64);
523
533
  this.databaseID = await Cryptographic.identifier.derive(this.databaseSecret, Bytes.utf8.decode("@sovereignbase/explorer:DatabaseID"), 32);
524
534
  this.isAuthenticated = true;
525
535
  (await this.loadCredentialEntries()).record(this.sessionCredential);
@@ -530,10 +540,71 @@ var Explorer = class {
530
540
  throw unknownError(cause, "Explorer.authenticate()");
531
541
  }
532
542
  }
543
+ /** The number of consecutive failed database linking PIN attempts. */
544
+ static failCounter = 0;
545
+ /**
546
+ * Prepares an authenticated database for linking to another device.
547
+ *
548
+ * Verifies the current data PIN and prepares an encrypted transfer
549
+ * containing the information required to access the same database.
550
+ *
551
+ * Repeated PIN failures cause the current page to reload.
552
+ *
553
+ * @param dataPIN The PIN associated with the authenticated database.
554
+ * @param encapsulateKey The key used to establish encryption for the transfer.
555
+ * @returns The key offer and encrypted transfer message, or `false`
556
+ * if the supplied PIN is incorrect.
557
+ */
558
+ static async prepareDatabaseLink(dataPIN, encapsulateKey) {
559
+ try {
560
+ if (!this.isAuthenticated) throw new ExplorerError("NOT_AUTHENTICATED");
561
+ if (dataPIN !== this.dataPIN) {
562
+ if (this.failCounter >= 2) window.location.reload();
563
+ this.failCounter++;
564
+ return false;
565
+ }
566
+ this.failCounter = 0;
567
+ const { keyOffer, cipherKey } = await Cryptographic.keyAgreement.encapsulate(encapsulateKey);
568
+ return [keyOffer, await Cryptographic.cipherMessage.encrypt(cipherKey, Bytes.concat([this.virtualEntropy, Bytes.utf8.decode(dataPIN)]))];
569
+ } catch (cause) {
570
+ throw unknownError(cause, "Explorer.prepareDatabaseLink()");
571
+ }
572
+ }
533
573
  /**
534
- * Permanently revokes a credential and deletes its persisted data.
574
+ * Links the current device to an existing database.
535
575
  *
536
- * Removes the credential from the database's registered credentials.
576
+ * Accesses the transferred database information, creates a device binding,
577
+ * and registers a credential for the existing database.
578
+ *
579
+ * @param keyOffer The key offer associated with the database link.
580
+ * @param cipherMessage The encrypted database linking message.
581
+ * @param decapsulateKey The key used to establish decryption for the transfer.
582
+ * @param credentialName The display name assigned to the new credential.
583
+ * @param storageURL The base URL used to access database objects.
584
+ * @returns `true` if the database was linked and authenticated;
585
+ * otherwise, `false`.
586
+ */
587
+ static async linkToDatabase(keyOffer, cipherMessage, decapsulateKey, credentialName, storageURL) {
588
+ try {
589
+ const { cipherKey } = await Cryptographic.keyAgreement.decapsulate(keyOffer, decapsulateKey);
590
+ const raw = await Cryptographic.cipherMessage.decrypt(cipherKey, cipherMessage);
591
+ const setupResult = await createDeviceBinding(credentialName);
592
+ if (!Array.isArray(setupResult)) return false;
593
+ const [credentialId, bindingStorage] = setupResult;
594
+ return await this.authenticate(Bytes.utf8.encode(raw.slice(64)), storageURL, {
595
+ credentialName,
596
+ virtualEntropy: raw.slice(0, 64),
597
+ bindingStorage
598
+ }, credentialId) === "authenticated" ? true : false;
599
+ } catch (cause) {
600
+ throw unknownError(cause, "Explorer.linkToDatabase()");
601
+ }
602
+ }
603
+ /**
604
+ * Permanently revokes a registered database credential.
605
+ *
606
+ * Removes the credential's persisted data and registration from
607
+ * the authenticated database.
537
608
  *
538
609
  * @param credentialID The identifier of the credential to revoke.
539
610
  * @throws {ExplorerError} If the database is not authenticated.
@@ -547,15 +618,14 @@ var Explorer = class {
547
618
  }
548
619
  }
549
620
  /**
550
- * Returns the registered credentials and optionally observes their changes.
551
- *
552
- * The returned tuple contains a cleanup function, the current credential
553
- * entries, and the identifier of the current session's credential.
621
+ * Returns the registered database credentials.
554
622
  *
555
- * The cleanup function removes the registered change listener, if any.
623
+ * Optionally registers a listener for changes to the credential entries.
624
+ * The returned cleanup function removes the registered listener.
556
625
  *
557
626
  * @param onChange An optional callback invoked when the entries change.
558
- * @returns The cleanup function, credential entries, and session credential ID.
627
+ * @returns The cleanup function, credential entries, and identifier
628
+ * of the current session's credential.
559
629
  * @throws {ExplorerError} If the database is not authenticated.
560
630
  */
561
631
  static async listCredentialEntries(onChange) {
@@ -575,11 +645,10 @@ var Explorer = class {
575
645
  }
576
646
  }
577
647
  /**
578
- * Returns the recently accessed objects and optionally observes their changes.
648
+ * Returns the recently accessed database objects.
579
649
  *
580
- * The returned tuple contains a cleanup function and the current recent
581
- * entries. The cleanup function removes the registered change listener,
582
- * if any.
650
+ * Optionally registers a listener for changes to the recent entries.
651
+ * The returned cleanup function removes the registered listener.
583
652
  *
584
653
  * @param onChange An optional callback invoked when the entries change.
585
654
  * @returns The cleanup function and recent entries.
@@ -598,9 +667,9 @@ var Explorer = class {
598
667
  }
599
668
  }
600
669
  /**
601
- * Loads an encrypted object using its identifier and cipher key.
670
+ * Loads an encrypted database object by its identifier.
602
671
  *
603
- * Caches the cipher key if the identifier has not been encountered before.
672
+ * Caches the supplied cipher key if no key has been cached for the object.
604
673
  *
605
674
  * @typeParam T The expected type of the loaded object.
606
675
  * @param id The identifier of the object to load.
@@ -617,9 +686,9 @@ var Explorer = class {
617
686
  }
618
687
  }
619
688
  /**
620
- * Encrypts and stores an object using its identifier and cipher key.
689
+ * Encrypts and stores a database object by its identifier.
621
690
  *
622
- * Caches the cipher key if the identifier has not been encountered before.
691
+ * Caches the supplied cipher key if no key has been cached for the object.
623
692
  *
624
693
  * @typeParam T The type of the object to store.
625
694
  * @param id The identifier of the object to store.
@@ -636,7 +705,9 @@ var Explorer = class {
636
705
  }
637
706
  }
638
707
  /**
639
- * Deletes a persisted object using its identifier.
708
+ * Deletes a database object by its identifier.
709
+ *
710
+ * Removes the persisted object and its corresponding recent entry.
640
711
  *
641
712
  * @param id The identifier of the object to delete.
642
713
  */
@@ -676,8 +747,9 @@ var Explorer = class {
676
747
  });
677
748
  }
678
749
  /**
679
- * Registers an event listener for the specified event type.
750
+ * Registers an event listener for the specified Explorer event.
680
751
  *
752
+ * @typeParam K The event name that determines the event detail type.
681
753
  * @param type The event type.
682
754
  * @param listener The event listener, or `null`.
683
755
  * @param options The event listener options.
@@ -693,6 +765,7 @@ var Explorer = class {
693
765
  * Removes an event listener previously registered with
694
766
  * {@link addEventListener}.
695
767
  *
768
+ * @typeParam K The event name that determines the listener type.
696
769
  * @param type The event type.
697
770
  * @param listener The event listener to remove, or `null`.
698
771
  * @param options The event listener options.
@@ -705,8 +778,9 @@ var Explorer = class {
705
778
  }
706
779
  }
707
780
  /**
708
- * Dispatches an event to the registered listeners.
781
+ * Dispatches an Explorer event to registered listeners.
709
782
  *
783
+ * @typeParam K The event name that determines the event detail type.
710
784
  * @param type The event type.
711
785
  * @param detail The value exposed through `CustomEvent.detail`.
712
786
  */
@@ -726,6 +800,7 @@ var Explorer = class {
726
800
  static isExplorerDelta(data) {
727
801
  return Array.isArray(data) && typeof data[0] === "string";
728
802
  }
803
+ /** The cached recent entries list. */
729
804
  static recentEntries;
730
805
  /**
731
806
  * Loads or returns the cached recent entries list.
@@ -749,6 +824,7 @@ var Explorer = class {
749
824
  throw unknownError(cause, "Explorer.loadRecentEntries()");
750
825
  }
751
826
  }
827
+ /** The cached credential entries list. */
752
828
  static credentialEntries;
753
829
  /**
754
830
  * Loads or returns the cached credential entries list.
package/dist/class.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"class.js","names":[],"sources":["../src/.errors/class.ts","../src/.errors/unkown.ts","../src/CredentialEntries/class.ts","../src/RecentEntries/class.ts","../src/class.ts"],"sourcesContent":["/** Identifies an Explorer failure independently of its human-readable message. */\nexport type ExplorerErrorCode =\n /** An unexpected failure was wrapped with its original cause. */\n | 'UNKNOWN_ERROR'\n /** A credential event is missing required identifying or usage information. */\n | 'INVALID_CREDENTIAL_EVENT'\n /** A new credential does not provide all required setup properties. */\n | 'MISSING_CREDENTIAL_SETUP_PROPERTIES'\n /** An operation requires an authenticated Explorer session. */\n | 'NOT_AUTHENTICATED'\n\n/**\n * An Explorer error with a stable, machine-readable code and an optional cause.\n *\n * The message is prefixed with the library name. Use {@link code} rather than\n * parsing the message when handling a particular failure.\n */\nexport class ExplorerError extends Error {\n /** The machine-readable category of the failure. */\n readonly code: ExplorerErrorCode\n\n /**\n * Creates an error whose name is `ExplorerError`.\n *\n * @param code The category of the failure.\n * @param message The detail to prefix with the library name; defaults to `code`.\n * @param options Error options, including the original `cause`.\n */\n constructor(\n code: ExplorerErrorCode,\n message?: string,\n options?: ErrorOptions\n ) {\n super(`{@sovereignbase/explorer} ${message ?? code}`, options)\n this.code = code\n this.name = 'ExplorerError'\n }\n}\n","import { ExplorerError } from './class.js'\n\n/**\n * Preserves an existing library error or wraps an unexpected failure once.\n * @param cause The value thrown by the failed operation.\n * @param operation The operation name to include in the error message.\n * @returns The original library error, or an `UNKNOWN_ERROR` retaining `cause`.\n */\nexport function unknownError(cause: unknown, operation: string): ExplorerError {\n return cause instanceof ExplorerError\n ? cause\n : new ExplorerError('UNKNOWN_ERROR', `${operation} failed.`, { cause })\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\nimport {\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.js","names":[],"sources":["../src/.errors/class.ts","../src/.errors/unkown.ts","../src/CredentialEntries/class.ts","../src/RecentEntries/class.ts","../src/class.ts"],"sourcesContent":["/** Identifies an Explorer failure independently of its human-readable message. */\nexport type ExplorerErrorCode =\n /** An unexpected failure was wrapped with its original cause. */\n | 'UNKNOWN_ERROR'\n /** A credential event is missing required identifying or usage information. */\n | 'INVALID_CREDENTIAL_EVENT'\n /** A new credential does not provide all required setup properties. */\n | 'MISSING_CREDENTIAL_SETUP_PROPERTIES'\n /** An operation requires an authenticated Explorer session. */\n | 'NOT_AUTHENTICATED'\n\n/**\n * An Explorer error with a stable, machine-readable code and an optional cause.\n *\n * The message is prefixed with the library name. Use {@link code} rather than\n * parsing the message when handling a particular failure.\n */\nexport class ExplorerError extends Error {\n /** The machine-readable category of the failure. */\n readonly code: ExplorerErrorCode\n\n /**\n * Creates an error whose name is `ExplorerError`.\n *\n * @param code The category of the failure.\n * @param message The detail to prefix with the library name; defaults to `code`.\n * @param options Error options, including the original `cause`.\n */\n constructor(\n code: ExplorerErrorCode,\n message?: string,\n options?: ErrorOptions\n ) {\n super(`{@sovereignbase/explorer} ${message ?? code}`, options)\n this.code = code\n this.name = 'ExplorerError'\n }\n}\n","import { ExplorerError } from './class.js'\n\n/**\n * Preserves an existing library error or wraps an unexpected failure once.\n * @param cause The value thrown by the failed operation.\n * @param operation The operation name to include in the error message.\n * @returns The original library error, or an `UNKNOWN_ERROR` retaining `cause`.\n */\nexport function unknownError(cause: unknown, operation: string): ExplorerError {\n return cause instanceof ExplorerError\n ? cause\n : new ExplorerError('UNKNOWN_ERROR', `${operation} failed.`, { cause })\n}\n","import {\n Sequenced,\n type SequencedArray,\n type SequencedArrayDelta,\n type SequencedArrayChange,\n type SequencedArraySnapshot,\n} from '@sovereignbase/sequenced'\n//\nimport type { CipherKey } from '@sovereignbase/cryptosuite'\nimport {\n invalidateDeviceBinding,\n type DeviceBindingStorage,\n} from '@sovereignbase/hardware-bound'\n//\nimport {\n afterIdleFor,\n type DeepReadonly,\n type WritableArray,\n} from '@sovereignbase/utils'\n//\nimport { Explorer } from '../class.js'\nimport { ExplorerError } from '../.errors/class.js'\nimport { unknownError } from '../.errors/unkown.js'\n\n/**\n * Describes a credential registered with the database.\n */\nexport type CredentialEntry = {\n /** The display name of the credential. */\n name: string\n\n /** The entropy associated with the credential. */\n entropy: Uint8Array\n\n /** The storage characteristics of the device binding. */\n storage: DeviceBindingStorage\n\n /** The identifier of the object storing virtual entropy protected by the credential. */\n objectID: string\n\n /** The WebAuthn credential identifier. */\n credentialID: string\n\n /** The timestamp of the most recent use of the credential. */\n lastUsed: number\n\n /** The user agents that have used the credential. */\n userAgents: Array<{\n /** The user agent string. */\n name: string\n\n /** The timestamp of the most recent use by the user agent. */\n lastSeen: number\n }>\n}\n\n/**\n * Describes the properties used to create or update a credential entry.\n *\n * The object identifier, timestamp, and user agent are required to record use.\n * Creating an entry additionally requires the credential identifier,\n * display name, entropy, and storage characteristics.\n */\nexport type CredentialEvent = {\n /** The identifier of the object storing the credential's virtual entropy. */\n objectID?: string\n\n /** The WebAuthn credential identifier. */\n credentialID?: string\n\n /** The timestamp of the credential use. */\n timestamp?: number\n\n /** The user agent associated with the credential use. */\n userAgent?: string\n\n /** The display name of the credential. */\n name?: string\n\n /** The entropy associated with the credential. */\n entropy?: Uint8Array\n\n /** The storage characteristics of the device binding. */\n storage?: DeviceBindingStorage\n}\n\n/**\n * Represents a sequenced delta produced by the credential entries list.\n */\nexport type CredentialEntriesDelta = Readonly<\n ['credentialEntries', DeepReadonly<SequencedArrayDelta<CredentialEntry>>]\n>\n\n/**\n * Represents a sequenced change produced by the credential entries list.\n */\nexport type CredentialEntriesChange = DeepReadonly<\n SequencedArrayChange<CredentialEntry>\n>\n\n/**\n * Maps credential entries event names to their detail types.\n */\nexport type CredentialEntriesEventMap = {\n /** Fired when the credential entries list changes. */\n change: CredentialEntriesChange\n}\n\n/**\n * Represents an event listener for a {@link CredentialEntries} event.\n *\n * @typeParam K The event name that determines the event detail type.\n */\nexport type CredentialEntriesEventListener<\n K extends keyof CredentialEntriesEventMap,\n> =\n | ((event: CustomEvent<CredentialEntriesEventMap[K]>) => void)\n | { handleEvent(event: CustomEvent<CredentialEntriesEventMap[K]>): void }\n\n/**\n * Resolves an event name to its corresponding listener type.\n *\n * @typeParam K The event name to resolve.\n */\nexport type CredentialEntriesEventListenerFor<K extends string> =\n K extends keyof CredentialEntriesEventMap\n ? CredentialEntriesEventListener<K>\n : EventListenerOrEventListenerObject\n\n/**\n * Maintains the database's registered credentials.\n *\n * Entries are identified by their object and credential identifiers.\n * Changes are synchronized through a sequenced array and persisted\n * as encrypted snapshots.\n */\nexport class CredentialEntries {\n private state: SequencedArray<CredentialEntry>\n private deltaBuffer: WritableArray<SequencedArrayDelta<CredentialEntry>> =\n new Array()\n private eventTarget?: EventTarget\n\n /**\n * Creates a credential entries list.\n *\n * @param id The identifier of the persisted entries object.\n * @param cipherKey The key used to encrypt the entries object.\n * @param sessionCredentialID The WebAuthn credential ID of the current session.\n * @param actorID The actor identifier used by the sequenced array.\n * @param trustedSnapshot An optional snapshot used to initialize the state.\n */\n constructor(\n private readonly id: string,\n private readonly cipherKey: CipherKey,\n public readonly sessionCredentialID: string,\n actorID: number,\n trustedSnapshot?: unknown\n ) {\n try {\n this.state = new Sequenced.Array(actorID, trustedSnapshot)\n\n const debouncedDispatch = afterIdleFor(250, () => {\n void Explorer.dispatchEvent('delta', [\n 'credentialEntries',\n this.deltaBuffer.splice(0),\n ])\n })\n\n void this.state.addEventListener('delta', ({ detail }) => {\n void this.deltaBuffer.push(...detail)\n void debouncedDispatch()\n })\n\n const debouncedSave = afterIdleFor(\n 250,\n async (snapshot: SequencedArraySnapshot<CredentialEntry>) => {\n void (await Explorer.storeObjectByID(\n this.id,\n snapshot,\n this.cipherKey\n ))\n }\n )\n\n void this.state.addEventListener('snapshot', ({ detail }) => {\n void debouncedSave(detail)\n })\n\n void this.state.addEventListener('change', ({ detail }) => {\n void this.dispatchEvent('change', detail)\n })\n\n const seenObjects = new Set<string>()\n const seenCredentials = new Set<string>()\n const duplicates: number[] = []\n\n for (let index = 0; index < this.state.length; index++) {\n const entry = this.state.at(index)\n if (!entry) continue\n\n if (\n seenObjects.has(entry.objectID) ||\n seenCredentials.has(entry.credentialID)\n ) {\n duplicates.push(index)\n } else {\n seenObjects.add(entry.objectID)\n seenCredentials.add(entry.credentialID)\n }\n }\n\n for (let index = duplicates.length - 1; index >= 0; index--)\n void this.state.delete(duplicates[index])\n\n if (duplicates.length > 0 || this.deltaBuffer.length > 0)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.constructor()')\n }\n }\n\n /**\n * Creates or updates a credential entry.\n *\n * Updates the credential's last-used timestamp and the corresponding\n * user agent's last-seen timestamp. Existing properties are preserved\n * unless replacements are provided.\n *\n * @param credentialEvent The credential properties to apply.\n * @throws {ExplorerError} If the event is invalid or setup properties are missing.\n */\n public record(credentialEvent: CredentialEvent): void {\n try {\n if (!credentialEvent || typeof credentialEvent !== 'object')\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event must be an object.'\n )\n\n const {\n objectID,\n credentialID,\n timestamp,\n userAgent,\n name,\n entropy,\n storage,\n } = credentialEvent\n\n if (\n typeof objectID !== 'string' ||\n typeof timestamp !== 'number' ||\n !Number.isFinite(timestamp) ||\n typeof userAgent !== 'string'\n )\n throw new ExplorerError(\n 'INVALID_CREDENTIAL_EVENT',\n 'A credential event requires an object ID, a finite timestamp, and a user agent.'\n )\n\n let existing: CredentialEntry | undefined\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const entry = this.state.at(index)\n\n if (\n entry?.objectID === objectID ||\n (credentialID !== undefined && entry?.credentialID === credentialID)\n ) {\n existing = entry\n break\n }\n }\n\n if (\n !existing &&\n (typeof credentialID !== 'string' ||\n typeof name !== 'string' ||\n !(entropy instanceof Uint8Array) ||\n typeof storage !== 'string')\n )\n throw new ExplorerError(\n 'MISSING_CREDENTIAL_SETUP_PROPERTIES',\n 'A new credential requires a credential ID, name, entropy, and binding storage.'\n )\n\n const userAgents =\n existing?.userAgents.map((agent) => ({ ...agent })) ?? []\n\n const agent = userAgents.find((agent) => agent.name === userAgent)\n\n if (agent) {\n agent.lastSeen = Math.max(agent.lastSeen, timestamp)\n } else {\n userAgents.push({ name: userAgent, lastSeen: timestamp })\n }\n\n const entry: CredentialEntry = {\n objectID,\n credentialID: credentialID ?? existing!.credentialID,\n name: name ?? existing!.name,\n entropy: entropy ?? existing!.entropy,\n storage: storage ?? existing!.storage,\n lastUsed: Math.max(existing?.lastUsed ?? timestamp, timestamp),\n userAgents,\n }\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const current = this.state.at(index)\n\n if (\n current?.objectID === entry.objectID ||\n current?.credentialID === entry.credentialID\n )\n void this.state.delete(index)\n }\n\n void this.state.unshift(entry)\n void this.state.snapshot()\n } catch (cause) {\n throw unknownError(cause, 'CredentialEntries.record()')\n }\n }\n\n /**\n * Revokes a credential and removes its registered entries.\n *\n * Deletes the credential's persisted objects before removing its entries.\n * The entries remain unchanged if deletion fails. Credential invalidation\n * is requested on a best-effort basis after successful removal.\n *\n * @param credentialID The WebAuthn credential identifier to revoke.\n */\n public async revoke(credentialID: string): Promise<void> {\n try {\n const objectIDs = new Set<string>()\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n const entry = this.state.at(index)\n\n if (entry?.credentialID === credentialID) objectIDs.add(entry.objectID)\n }\n\n if (objectIDs.size === 0) return\n\n for (const objectID of objectIDs)\n await Explorer.deleteObjectByID(objectID)\n\n let removed = false\n\n for (let index = this.state.length - 1; index >= 0; index--) {\n if (this.state.at(index)?.credentialID === credentialID) {\n void this.state.delete(index)\n removed = true\n }\n }\n\n if (removed) void this.state.snapshot()\n\n try {\n await invalidateDeviceBinding(credentialID)\n } catch {\n // Credential invalidation is best-effort.\n }\n\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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"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "license": "Apache-2.0",
4
4
  "name": "@sovereignbase/explorer",
5
- "version": "0.0.0-alpha.5",
5
+ "version": "0.0.0-alpha.6",
6
6
  "description": "WIP",
7
7
  "keywords": [
8
8
  "typescript",
@@ -48,7 +48,7 @@
48
48
  "husky": "^9.1.7",
49
49
  "playwright": "^1.64.1",
50
50
  "prettier": "^3.9.10",
51
- "tsdown": "^0.23.3",
51
+ "tsdown": "^0.23.4",
52
52
  "typedoc": "^0.28.20",
53
53
  "typescript": "^6.0.3",
54
54
  "vitest": "^5.0.5"
@@ -65,7 +65,7 @@
65
65
  "@sovereignbase/bytecodec": "^2.0.1",
66
66
  "@sovereignbase/cryptosuite": "^3.2.0",
67
67
  "@sovereignbase/dom-utils": "^1.0.1",
68
- "@sovereignbase/hardware-bound": "^3.0.0",
68
+ "@sovereignbase/hardware-bound": "^3.1.0",
69
69
  "@sovereignbase/sequenced": "^0.0.2",
70
70
  "@sovereignbase/sequencer": "^1.1.1",
71
71
  "@sovereignbase/storage": "^3.2.0",