decane-connect-kit 2.7.3 → 2.7.4

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/index.cjs CHANGED
@@ -2352,6 +2352,26 @@ async function teeFetch(url, init) {
2352
2352
  clearTimeout(timer);
2353
2353
  }
2354
2354
  }
2355
+ var ATTESTATION_TTL_MS = 10 * 6e4;
2356
+ var attestationCache = /* @__PURE__ */ new Map();
2357
+ function attestOnce(teeUrl, verify) {
2358
+ const hit = attestationCache.get(teeUrl);
2359
+ if (hit && Date.now() - hit.at < ATTESTATION_TTL_MS) return hit.promise;
2360
+ const promise = verify().catch((e) => {
2361
+ attestationCache.delete(teeUrl);
2362
+ throw e;
2363
+ });
2364
+ attestationCache.set(teeUrl, { at: Date.now(), promise });
2365
+ return promise;
2366
+ }
2367
+ function prewarmAttestation(teeUrl) {
2368
+ void attestOnce(teeUrl, async () => {
2369
+ const nonce = crypto.getRandomValues(new Uint8Array(16));
2370
+ const nonceHex = Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join("");
2371
+ await verifyTeeAttestation(teeUrl, nonceHex);
2372
+ }).catch(() => {
2373
+ });
2374
+ }
2355
2375
  var TeeClient = class {
2356
2376
  constructor(teeUrl) {
2357
2377
  this.teeUrl = teeUrl;
@@ -2393,9 +2413,11 @@ var TeeClient = class {
2393
2413
  }
2394
2414
  async ensureAttested() {
2395
2415
  if (this.attested) return;
2396
- const nonce = crypto.getRandomValues(new Uint8Array(16));
2397
- const nonceHex = Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join("");
2398
- await verifyTeeAttestation(this.teeUrl, nonceHex);
2416
+ await attestOnce(this.teeUrl, async () => {
2417
+ const nonce = crypto.getRandomValues(new Uint8Array(16));
2418
+ const nonceHex = Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join("");
2419
+ await verifyTeeAttestation(this.teeUrl, nonceHex);
2420
+ });
2399
2421
  this.attested = true;
2400
2422
  }
2401
2423
  /**
@@ -3541,6 +3563,26 @@ async function createDecaneConnect(config) {
3541
3563
  zeroize(share);
3542
3564
  }
3543
3565
  }
3566
+ async function enrolInBackground(jwt, share) {
3567
+ try {
3568
+ for (let attempt = 1; attempt <= 2; attempt++) {
3569
+ try {
3570
+ await new TeeClient(teeUrl).enrolAuthShare(jwt, share);
3571
+ return;
3572
+ } catch (e) {
3573
+ if (attempt === 2) {
3574
+ events.emit("enrolment-failed");
3575
+ console.warn(
3576
+ "decane-connect-kit: could not enrol this wallet for server-assisted recovery. The wallet is fine and fully usable; it will be re-enrolled automatically on the next unlock. Until then a brand-new device would need a passkey backup or a recovery file.",
3577
+ e
3578
+ );
3579
+ }
3580
+ }
3581
+ }
3582
+ } finally {
3583
+ zeroize(share);
3584
+ }
3585
+ }
3544
3586
  const PROVISIONING_BUDGET_MS = 9e4;
3545
3587
  function withDeadline(promise, ms, step) {
3546
3588
  let timer;
@@ -3671,6 +3713,7 @@ async function createDecaneConnect(config) {
3671
3713
  }
3672
3714
  }
3673
3715
  async function firstTimeSetup(userId, jwt) {
3716
+ prewarmAttestation(teeUrl);
3674
3717
  let unlockMethod;
3675
3718
  let wrappingKey;
3676
3719
  let meta = {};
@@ -3774,49 +3817,49 @@ async function createDecaneConnect(config) {
3774
3817
  addresses.solana = solanaAddress = confirmed.solana;
3775
3818
  addresses.tron = tronAddress = confirmed.tron ?? evmToTronAddress2(confirmed.evm);
3776
3819
  if (serverAssistedRecovery) {
3777
- for (let attempt = 1; attempt <= 2; attempt++) {
3778
- try {
3779
- const tee = new TeeClient(teeUrl);
3780
- await tee.enrolAuthShare(jwt, shareSet.recovery);
3781
- break;
3782
- } catch (e) {
3783
- if (attempt === 2) {
3784
- console.warn(
3785
- "decane-connect-kit: could not enrol this wallet for server-assisted recovery; a recovery file will be required on a new device. This wallet will get a 409 not_enrolled from /signer/provision until it is re-enrolled.",
3786
- e
3787
- );
3788
- }
3789
- }
3790
- }
3820
+ void enrolInBackground(jwt, new Uint8Array(shareSet.recovery));
3791
3821
  }
3792
- if (config.offerRecoveryAtSignup !== false && config.onRecoveryShareOffer) {
3793
- try {
3794
- const choice = await withTimeout(
3795
- config.onRecoveryShareOffer({ evmAddress, solanaAddress }),
3796
- RECOVERY_OFFER_TIMEOUT_MS
3797
- );
3798
- if (!choice) {
3799
- console.warn(
3800
- `decane-connect-kit: onRecoveryShareOffer did not settle within ${RECOVERY_OFFER_TIMEOUT_MS / 6e4} minutes, so wallet creation continued without offering a recovery file. The wallet is fully created and enrolled; the user can generate a recovery share later via rotateShares(). Check that your handler always resolves or rejects.`
3801
- );
3802
- } else if (choice.wants) {
3803
- const file = await buildRecoveryShareFile(
3804
- shareSet.recovery,
3805
- choice.password,
3806
- addresses,
3807
- shareSet.version,
3808
- choice.passwordHint
3809
- );
3810
- await config.onRecoveryFileReady?.(file, recoveryShareFileName(evmAddress));
3811
- await api.markRecoveryShareSaved(jwt);
3812
- }
3813
- } catch (e) {
3814
- console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
3815
- }
3822
+ if (config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
3823
+ void offerRecoveryShare(
3824
+ jwt,
3825
+ new Uint8Array(shareSet.recovery),
3826
+ { ...addresses },
3827
+ shareSet.version,
3828
+ evmAddress,
3829
+ solanaAddress
3830
+ );
3816
3831
  }
3817
3832
  zeroize(shareSet.recovery);
3818
3833
  return addresses;
3819
3834
  }
3835
+ async function offerRecoveryShare(jwt, recoveryShare, addresses, shareSetVersion, evmAddress, solanaAddress) {
3836
+ try {
3837
+ const choice = await withTimeout(
3838
+ config.onRecoveryShareOffer({ evmAddress, solanaAddress }),
3839
+ RECOVERY_OFFER_TIMEOUT_MS
3840
+ );
3841
+ if (!choice) {
3842
+ console.warn(
3843
+ `decane-connect-kit: onRecoveryShareOffer did not settle within ${RECOVERY_OFFER_TIMEOUT_MS / 6e4} minutes, so no recovery file was offered. The wallet is unaffected \u2014 it was already created and enrolled \u2014 and the user can generate a recovery share later via rotateShares(). Check that your handler always resolves or rejects.`
3844
+ );
3845
+ return;
3846
+ }
3847
+ if (!choice.wants) return;
3848
+ const file = await buildRecoveryShareFile(
3849
+ recoveryShare,
3850
+ choice.password,
3851
+ addresses,
3852
+ shareSetVersion,
3853
+ choice.passwordHint
3854
+ );
3855
+ await config.onRecoveryFileReady?.(file, recoveryShareFileName(evmAddress));
3856
+ await api.markRecoveryShareSaved(jwt);
3857
+ } catch (e) {
3858
+ console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
3859
+ } finally {
3860
+ zeroize(recoveryShare);
3861
+ }
3862
+ }
3820
3863
  async function returningUserUnlock(userId, jwt) {
3821
3864
  const rawAddresses = await api.getAddresses(jwt);
3822
3865
  const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;