decane-connect-kit 2.26.1 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ __export(errors_exports, {
14
14
  AttestationError: () => AttestationError,
15
15
  CallSequenceError: () => CallSequenceError,
16
16
  DecaneConnectError: () => DecaneConnectError,
17
+ EnclaveHttpError: () => EnclaveHttpError,
17
18
  ExportNotAuthorizedError: () => ExportNotAuthorizedError,
18
19
  InvalidAccessTokenError: () => InvalidAccessTokenError,
19
20
  InvalidApiKeyError: () => InvalidApiKeyError,
@@ -24,6 +25,7 @@ __export(errors_exports, {
24
25
  OriginNotAllowedError: () => OriginNotAllowedError,
25
26
  PromptPasswordNotConfiguredError: () => PromptPasswordNotConfiguredError,
26
27
  PromptPinNotConfiguredError: () => PromptPinNotConfiguredError,
28
+ ProvisioningRateLimitedError: () => ProvisioningRateLimitedError,
27
29
  ProvisioningTimeoutError: () => ProvisioningTimeoutError,
28
30
  ReconnectRequiredError: () => ReconnectRequiredError,
29
31
  RecoveryFailedError: () => RecoveryFailedError,
@@ -34,11 +36,13 @@ __export(errors_exports, {
34
36
  UnlockRequiredError: () => UnlockRequiredError,
35
37
  UnlockTokenRejectedError: () => UnlockTokenRejectedError,
36
38
  UserCancelledError: () => UserCancelledError,
39
+ WalletEnrolmentError: () => WalletEnrolmentError,
40
+ WalletExistsError: () => WalletExistsError,
37
41
  WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
38
42
  WrongPasswordError: () => WrongPasswordError,
39
43
  WrongUnlockPasswordError: () => WrongUnlockPasswordError
40
44
  });
41
- var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
45
+ var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, PromptPasswordNotConfiguredError, WrongUnlockPasswordError, InvalidAccessTokenError, UnlockTokenRejectedError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError, WalletExistsError, WalletEnrolmentError, ProvisioningRateLimitedError, EnclaveHttpError;
42
46
  var init_errors = __esm({
43
47
  "src/social/errors.ts"() {
44
48
  "use strict";
@@ -187,6 +191,36 @@ var init_errors = __esm({
187
191
  super("EXPORT_NOT_AUTHORIZED", "Export requires a fresh security check", true);
188
192
  }
189
193
  };
194
+ WalletExistsError = class extends DecaneConnectError {
195
+ constructor() {
196
+ super("WALLET_EXISTS", "This account already has a wallet; opening it instead.", true);
197
+ }
198
+ };
199
+ WalletEnrolmentError = class extends DecaneConnectError {
200
+ constructor(cause) {
201
+ super(
202
+ "WALLET_ENROLMENT_FAILED",
203
+ "Your wallet could not be set up: the signing enclave did not accept it. Nothing was created \u2014 sign in again to try again.",
204
+ true
205
+ );
206
+ if (cause !== void 0) this.cause = cause;
207
+ }
208
+ };
209
+ ProvisioningRateLimitedError = class extends DecaneConnectError {
210
+ constructor() {
211
+ super(
212
+ "PROVISIONING_RATE_LIMITED",
213
+ "This wallet has been opened on too many devices or tabs in the last hour. Wait a little and sign in again.",
214
+ true
215
+ );
216
+ }
217
+ };
218
+ EnclaveHttpError = class extends DecaneConnectError {
219
+ constructor(status, message) {
220
+ super("ENCLAVE_HTTP", message, status >= 500);
221
+ this.status = status;
222
+ }
223
+ };
190
224
  }
191
225
  });
192
226
 
@@ -1995,7 +2029,9 @@ var ERROR_MAP = {
1995
2029
  // The backend's authMiddleware code for an expired/revoked JWT. Surfacing it
1996
2030
  // as ReconnectRequiredError routes callers to the sign-in UI instead of a
1997
2031
  // generic failure message.
1998
- UNAUTHORIZED: ReconnectRequiredError
2032
+ UNAUTHORIZED: ReconnectRequiredError,
2033
+ // A restorable wallet already exists: the client provisions instead of creating.
2034
+ WALLET_EXISTS: WalletExistsError
1999
2035
  };
2000
2036
  var REQUEST_TIMEOUT_MS = 3e4;
2001
2037
  var ApiClient = class {
@@ -2114,7 +2150,8 @@ var ApiClient = class {
2114
2150
  wrappedShareMeta: btoa(JSON.stringify(share.wrappedShareMeta)),
2115
2151
  evmAddress: share.evmAddress,
2116
2152
  solanaAddress: share.solanaAddress,
2117
- tronAddress: share.tronAddress
2153
+ tronAddress: share.tronAddress,
2154
+ ...share.pendingEnrolment === void 0 ? {} : { pendingEnrolment: share.pendingEnrolment }
2118
2155
  })
2119
2156
  });
2120
2157
  }
@@ -2399,7 +2436,7 @@ function dropCollateral(teeUrl) {
2399
2436
  // src/social/attestation.ts
2400
2437
  init_errors();
2401
2438
 
2402
- // ../../infra/tee-measurement.json
2439
+ // ../infra/tee-measurement.json
2403
2440
  var tee_measurement_default = {
2404
2441
  signerVersion: "0.2.10",
2405
2442
  acceptedMrtd: [
@@ -2698,7 +2735,8 @@ var TeeClient = class {
2698
2735
  });
2699
2736
  if (!res.ok) {
2700
2737
  const detail = await res.text().catch(() => "");
2701
- throw new Error(
2738
+ throw new EnclaveHttpError(
2739
+ res.status,
2702
2740
  `auth share enrolment failed (HTTP ${res.status})${detail ? `: ${detail.slice(0, 200)}` : ""}`
2703
2741
  );
2704
2742
  }
@@ -2719,9 +2757,11 @@ var TeeClient = class {
2719
2757
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
2720
2758
  });
2721
2759
  if (res.status === 409) return null;
2760
+ if (res.status === 429) throw new ProvisioningRateLimitedError();
2722
2761
  if (!res.ok) {
2723
2762
  const detail = await res.text().catch(() => "");
2724
- throw new Error(
2763
+ throw new EnclaveHttpError(
2764
+ res.status,
2725
2765
  `device share provisioning failed (HTTP ${res.status})${detail ? `: ${detail.slice(0, 200)}` : ""}`
2726
2766
  );
2727
2767
  }
@@ -2936,6 +2976,35 @@ var SessionManager = class {
2936
2976
  }
2937
2977
  };
2938
2978
 
2979
+ // src/social/identityTier.ts
2980
+ function resolveProtectionTier(config) {
2981
+ if (config.protection) return config.protection;
2982
+ return config.deferDeviceProtection === true ? "identity" : "device";
2983
+ }
2984
+ function resumeAcrossTabsDefault(config, identityTier) {
2985
+ return config.resumeSessionAcrossTabs ?? identityTier;
2986
+ }
2987
+ var ENROL_BACKOFF_MS = [0, 1e3, 3e3];
2988
+ function enrolmentRetryable(status) {
2989
+ return status !== 429 && status !== 403;
2990
+ }
2991
+ function shouldTryProvisionFirst(identityTier, hasStoredShare) {
2992
+ return identityTier || !hasStoredShare;
2993
+ }
2994
+ function routeAfterProvision(input) {
2995
+ switch (input.outcome) {
2996
+ case "share":
2997
+ return "session";
2998
+ case "rate-limited":
2999
+ return "fail";
3000
+ case "not-enrolled":
3001
+ if (!input.identityTier) return "recover";
3002
+ return input.hasStoredShare ? "migrate" : "new-device";
3003
+ case "error":
3004
+ return input.identityTier ? "fail" : "recover";
3005
+ }
3006
+ }
3007
+
2939
3008
  // src/social/crypto/shares.ts
2940
3009
  import { secp256k1 } from "@noble/curves/secp256k1";
2941
3010
  import { split, combine } from "shamir-secret-sharing";
@@ -3846,7 +3915,8 @@ function clearSessionPersist(key) {
3846
3915
  async function createDecaneConnect(config) {
3847
3916
  if (!config.appId) throw new Error("decane-connect-kit: appId is required");
3848
3917
  if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
3849
- useCrossTabSessionStore(config.resumeSessionAcrossTabs === true);
3918
+ const identityTier = resolveProtectionTier(config) === "identity";
3919
+ useCrossTabSessionStore(resumeAcrossTabsDefault(config, identityTier));
3850
3920
  if (!config.chains?.length)
3851
3921
  throw new Error("decane-connect-kit: at least one chain is required");
3852
3922
  if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
@@ -3884,7 +3954,6 @@ async function createDecaneConnect(config) {
3884
3954
  let lastUserId = loadLastUser(LAST_USER_KEY) ?? (LEGACY_LAST_USER_KEY !== LAST_USER_KEY ? loadLastUser(LEGACY_LAST_USER_KEY) : null);
3885
3955
  let ethProvider = null;
3886
3956
  const persistSession = config.persistSession !== false;
3887
- const deferDeviceProtection = config.deferDeviceProtection === true;
3888
3957
  const serverAssistedRecovery = true;
3889
3958
  const RECOVERY_OFFER_TIMEOUT_MS = 5 * 6e4;
3890
3959
  const selfHealAttempted = /* @__PURE__ */ new Set();
@@ -3905,22 +3974,27 @@ async function createDecaneConnect(config) {
3905
3974
  zeroize(share);
3906
3975
  }
3907
3976
  }
3908
- async function enrolInBackground(jwt, share) {
3977
+ async function enrolBlocking(jwt, share, budgetMs) {
3978
+ const startedAt = Date.now();
3909
3979
  try {
3910
- for (let attempt = 1; attempt <= 2; attempt++) {
3980
+ let lastError;
3981
+ for (const wait of ENROL_BACKOFF_MS) {
3982
+ if (wait) await new Promise((r) => setTimeout(r, wait));
3911
3983
  try {
3912
- await new TeeClient(teeUrl).enrolAuthShare(jwt, share);
3984
+ await withDeadline(
3985
+ new TeeClient(teeUrl).enrolAuthShare(jwt, share),
3986
+ budgetMs - (Date.now() - startedAt),
3987
+ "enrolling your wallet"
3988
+ );
3913
3989
  return;
3914
3990
  } catch (e) {
3915
- if (attempt === 2) {
3916
- events.emit("enrolment-failed");
3917
- console.warn(
3918
- "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.",
3919
- e
3920
- );
3921
- }
3991
+ if (e instanceof AttestationError || e instanceof ProvisioningTimeoutError) throw e;
3992
+ lastError = e;
3993
+ if (!enrolmentRetryable(e.status)) break;
3922
3994
  }
3923
3995
  }
3996
+ events.emit("enrolment-failed");
3997
+ throw new WalletEnrolmentError(lastError);
3924
3998
  } finally {
3925
3999
  zeroize(share);
3926
4000
  }
@@ -4016,6 +4090,7 @@ async function createDecaneConnect(config) {
4016
4090
  else scheduleAccessTokenExpiry(saved.jwt, saved.expiresAt);
4017
4091
  auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
4018
4092
  persisted = { userId: saved.userId, addresses: saved.addresses };
4093
+ if (saved.handle) armTokenRefresh(saved.jwt);
4019
4094
  } catch {
4020
4095
  dropLiveSession();
4021
4096
  }
@@ -4025,6 +4100,13 @@ async function createDecaneConnect(config) {
4025
4100
  if (LEGACY_SESSION_KEY !== SESSION_KEY) clearSessionPersist(LEGACY_SESSION_KEY);
4026
4101
  }
4027
4102
  events.on("session-expired", () => dropLiveSession());
4103
+ function armTokenRefresh(jwt) {
4104
+ const expiresAt = decodeAccessToken(jwt)?.expiresAt;
4105
+ if (expiresAt) scheduleTokenRefresh(jwt, expiresAt);
4106
+ }
4107
+ events.on("connected", () => {
4108
+ if (auth) armTokenRefresh(auth.jwt);
4109
+ });
4028
4110
  async function passwordSetup() {
4029
4111
  if (!config.promptUnlockPassword) throw new PromptPasswordNotConfiguredError();
4030
4112
  const password = await config.promptUnlockPassword({ kind: "set" });
@@ -4249,7 +4331,7 @@ async function createDecaneConnect(config) {
4249
4331
  let wrappingKey = null;
4250
4332
  let meta = {};
4251
4333
  let unlockTokenKey = null;
4252
- if (deferDeviceProtection) {
4334
+ if (identityTier) {
4253
4335
  } else if (isPasskeySupported()) {
4254
4336
  const prfSalt = generateRandomBytes(32);
4255
4337
  try {
@@ -4316,19 +4398,47 @@ async function createDecaneConnect(config) {
4316
4398
  if (unlockTokenKey) await attachUnlockToken(userId, jwt, unlockTokenKey);
4317
4399
  }
4318
4400
  const serverIv = generateRandomBytes(12);
4319
- await withDeadline(api.putShare(jwt, {
4320
- ciphertext: shareSet.server,
4321
- iv: serverIv,
4322
- wrappedShareMeta: {
4323
- // The server records this on the wallet_created event and nothing else.
4324
- unlockMethod: deferDeviceProtection ? "deferred" : unlockMethod
4325
- // credential IDs, prfSalt, pinSalt stay on-device — never sent to server
4326
- },
4327
- evmAddress,
4328
- solanaAddress,
4329
- tronAddress
4330
- }), remainingBudget(), "storing your wallet");
4401
+ try {
4402
+ await withDeadline(api.putShare(jwt, {
4403
+ ciphertext: shareSet.server,
4404
+ iv: serverIv,
4405
+ wrappedShareMeta: {
4406
+ // The server records this on the wallet_created event and nothing else.
4407
+ unlockMethod: identityTier ? "identity" : unlockMethod
4408
+ // credential IDs, prfSalt, pinSalt stay on-device — never sent to server
4409
+ },
4410
+ evmAddress,
4411
+ solanaAddress,
4412
+ tronAddress,
4413
+ // Provisional until the enclave has sealed the auth share, just below. A
4414
+ // backend that knows the flag keeps this row invisible to hasShare until
4415
+ // then, so a creation that fails past this point leaves no wallet behind
4416
+ // and the next sign-in simply creates again — with a new key, which is
4417
+ // why the backend also replaces the recorded addresses outright.
4418
+ pendingEnrolment: true
4419
+ }), remainingBudget(), "storing your wallet");
4420
+ } catch (e) {
4421
+ if (e instanceof WalletExistsError) {
4422
+ zeroize(shareSet.server);
4423
+ zeroize(shareSet.device);
4424
+ zeroize(shareSet.recovery);
4425
+ if (wrappingKey) await storage2.deleteShare(userId).catch(() => {
4426
+ });
4427
+ events.emit("wallet-unlocking");
4428
+ return returningUserUnlock(userId, jwt);
4429
+ }
4430
+ throw e;
4431
+ }
4331
4432
  zeroize(shareSet.server);
4433
+ try {
4434
+ await enrolBlocking(jwt, new Uint8Array(shareSet.recovery), remainingBudget());
4435
+ } catch (e) {
4436
+ zeroize(shareSet.device);
4437
+ zeroize(shareSet.recovery);
4438
+ if (wrappingKey) await storage2.deleteShare(userId).catch(() => {
4439
+ });
4440
+ throw e;
4441
+ }
4332
4442
  const addresses = { evm: evmAddress, solana: solanaAddress, tron: tronAddress };
4333
4443
  if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt && wrappedDeviceShare && iv) {
4334
4444
  try {
@@ -4357,10 +4467,7 @@ async function createDecaneConnect(config) {
4357
4467
  addresses.evm = evmAddress = confirmed.evm;
4358
4468
  addresses.solana = solanaAddress = confirmed.solana;
4359
4469
  addresses.tron = tronAddress = confirmed.tron ?? evmToTronAddress2(confirmed.evm);
4360
- if (serverAssistedRecovery) {
4361
- void enrolInBackground(jwt, new Uint8Array(shareSet.recovery));
4362
- }
4363
- if (config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
4470
+ if (!identityTier && config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
4364
4471
  void offerRecoveryShare(
4365
4472
  jwt,
4366
4473
  new Uint8Array(shareSet.recovery),
@@ -4407,31 +4514,44 @@ async function createDecaneConnect(config) {
4407
4514
  const stored = await storage2.getShare(userId);
4408
4515
  let deviceShare;
4409
4516
  let joinedViaPasskeySync = false;
4410
- if (!stored) {
4411
- if (serverAssistedRecovery) {
4517
+ let migrating = false;
4518
+ if (serverAssistedRecovery && shouldTryProvisionFirst(identityTier, !!stored)) {
4519
+ let provisioned = null;
4520
+ let failure;
4521
+ let outcome;
4522
+ try {
4523
+ provisioned = await new TeeClient(teeUrl).provisionDeviceShare(jwt);
4524
+ outcome = provisioned ? "share" : "not-enrolled";
4525
+ } catch (e) {
4526
+ failure = e;
4527
+ outcome = e instanceof ProvisioningRateLimitedError ? "rate-limited" : "error";
4528
+ }
4529
+ const route = routeAfterProvision({ identityTier, hasStoredShare: !!stored, outcome });
4530
+ if (route === "session" && provisioned) {
4531
+ if (!identityTier) await persistProvisionedShare(userId, jwt, provisioned);
4532
+ let addrs;
4412
4533
  try {
4413
- const tee = new TeeClient(teeUrl);
4414
- const provisioned = await tee.provisionDeviceShare(jwt);
4415
- if (provisioned) {
4416
- if (!deferDeviceProtection) await persistProvisionedShare(userId, jwt, provisioned);
4417
- let addrs;
4418
- try {
4419
- addrs = await session.start(provisioned, jwt, recordedAddresses);
4420
- } finally {
4421
- zeroize(provisioned);
4422
- }
4423
- return addrs;
4424
- }
4425
- console.warn(
4426
- "decane-connect-kit: this wallet is not enrolled for server-assisted provisioning (/signer/provision returned 409 not_enrolled), so a device share cannot be issued from the JWT alone. Falling back to passkey sync, then the recovery file."
4427
- );
4428
- } catch (e) {
4429
- console.warn(
4430
- "decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
4431
- e
4432
- );
4534
+ addrs = await session.start(provisioned, jwt, recordedAddresses);
4535
+ } finally {
4536
+ zeroize(provisioned);
4433
4537
  }
4538
+ return addrs;
4539
+ }
4540
+ if (route === "fail") throw failure;
4541
+ if (route === "new-device") throw new NewDeviceError();
4542
+ if (route === "migrate") migrating = true;
4543
+ if (route === "recover" && outcome === "not-enrolled") {
4544
+ console.warn(
4545
+ "decane-connect-kit: this wallet is not enrolled for server-assisted provisioning (/signer/provision returned 409 not_enrolled), so a device share cannot be issued from the JWT alone. Falling back to passkey sync, then the recovery file."
4546
+ );
4547
+ } else if (route === "recover") {
4548
+ console.warn(
4549
+ "decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
4550
+ failure
4551
+ );
4434
4552
  }
4553
+ }
4554
+ if (!stored) {
4435
4555
  if (!config.onRecoveryRotated) {
4436
4556
  throw new CallSequenceError(
4437
4557
  "Recovery rotates the share set and issues a new recovery file, but no onRecoveryRotated callback is configured to save it. Configure onRecoveryRotated before allowing recovery."
@@ -4457,7 +4577,7 @@ async function createDecaneConnect(config) {
4457
4577
  });
4458
4578
  deviceShare = recovered.deviceShare;
4459
4579
  joinedViaPasskeySync = recovered.flow === "passkey-sync";
4460
- if (!deferDeviceProtection && !await storage2.getShare(userId) && isPasskeySupported()) {
4580
+ if (!identityTier && !await storage2.getShare(userId) && isPasskeySupported()) {
4461
4581
  await registerPasskeyAndBackup(userId, jwt, deviceShare);
4462
4582
  }
4463
4583
  } else {
@@ -4471,6 +4591,14 @@ async function createDecaneConnect(config) {
4471
4591
  } catch {
4472
4592
  throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
4473
4593
  }
4594
+ if (migrating) {
4595
+ try {
4596
+ await enrolBlocking(jwt, new Uint8Array(deviceShare), PROVISIONING_BUDGET_MS);
4597
+ } catch (e) {
4598
+ zeroize(deviceShare);
4599
+ throw e;
4600
+ }
4601
+ }
4474
4602
  }
4475
4603
  let addresses;
4476
4604
  try {
@@ -4485,7 +4613,11 @@ async function createDecaneConnect(config) {
4485
4613
  }
4486
4614
  throw e;
4487
4615
  }
4488
- void selfHealEnrolment(userId, jwt, new Uint8Array(deviceShare));
4616
+ if (migrating) {
4617
+ events.emit("migrated-to-identity");
4618
+ } else if (!identityTier) {
4619
+ void selfHealEnrolment(userId, jwt, new Uint8Array(deviceShare));
4620
+ }
4489
4621
  zeroize(deviceShare);
4490
4622
  if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
4491
4623
  console.warn(
@@ -4668,7 +4800,7 @@ async function createDecaneConnect(config) {
4668
4800
  if (auth && session.isActive()) {
4669
4801
  return { addresses: auth.addresses, isNewUser: false };
4670
4802
  }
4671
- const candidate = await passkeyLoginCandidate();
4803
+ const candidate = identityTier ? null : await passkeyLoginCandidate();
4672
4804
  if (candidate) {
4673
4805
  try {
4674
4806
  return await passkeyLogin(candidate);
@@ -4778,9 +4910,15 @@ async function createDecaneConnect(config) {
4778
4910
  return handleAuthResult(result);
4779
4911
  },
4780
4912
  async canSignInWithPasskey() {
4913
+ if (identityTier) return false;
4781
4914
  return canOfferPasskey();
4782
4915
  },
4783
4916
  async signInWithPasskey() {
4917
+ if (identityTier) {
4918
+ throw new CallSequenceError(
4919
+ "signInWithPasskey() is not available in the identity tier (protection: 'identity'); use connect()."
4920
+ );
4921
+ }
4784
4922
  const candidate = await passkeyDeviceCandidate();
4785
4923
  if (candidate) return passkeyLogin(candidate);
4786
4924
  const known = persisted?.userId ?? lastUserId;
@@ -4916,6 +5054,11 @@ async function createDecaneConnect(config) {
4916
5054
  return stored?.unlockMethod === "passkey";
4917
5055
  },
4918
5056
  async addPasskey() {
5057
+ if (identityTier) {
5058
+ throw new CallSequenceError(
5059
+ "addPasskey() is not available in the identity tier (protection: 'identity'): nothing is stored on the device for a passkey to protect."
5060
+ );
5061
+ }
4919
5062
  if (!auth) throw new ReconnectRequiredError();
4920
5063
  if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
4921
5064
  const stored = await storage2.getShare(auth.userId);
@@ -4954,6 +5097,7 @@ async function createDecaneConnect(config) {
4954
5097
  return await storage2.getShare(auth.userId) !== null;
4955
5098
  },
4956
5099
  async protectDevice() {
5100
+ if (identityTier) return false;
4957
5101
  if (!auth) throw new ReconnectRequiredError();
4958
5102
  if (await storage2.getShare(auth.userId)) return true;
4959
5103
  if (!serverAssistedRecovery) throw new NewDeviceError();
@@ -4969,7 +5113,7 @@ async function createDecaneConnect(config) {
4969
5113
  },
4970
5114
  async unlock() {
4971
5115
  if (!auth) {
4972
- const candidate = await passkeyLoginCandidate();
5116
+ const candidate = identityTier ? null : await passkeyLoginCandidate();
4973
5117
  if (candidate) {
4974
5118
  await passkeyLogin(candidate);
4975
5119
  return;
@@ -5138,6 +5282,8 @@ async function createDecaneConnect(config) {
5138
5282
  },
5139
5283
  async disconnect() {
5140
5284
  session.end();
5285
+ if (tokenRefresh) clearTimeout(tokenRefresh);
5286
+ tokenRefresh = null;
5141
5287
  if (auth?.jwt) {
5142
5288
  await api.revoke(auth.jwt).catch(() => {
5143
5289
  });
@@ -7595,6 +7741,7 @@ function DecaneKit({ config, children }) {
7595
7741
  chains,
7596
7742
  rpcUrls,
7597
7743
  requireAssertionPerSignature,
7744
+ protection,
7598
7745
  deferDeviceProtection,
7599
7746
  resumeSessionAcrossTabs,
7600
7747
  // Recovery callbacks. Forwarding these is what makes recovery
@@ -7617,6 +7764,7 @@ function DecaneKit({ config, children }) {
7617
7764
  promptPin,
7618
7765
  promptUnlockPassword,
7619
7766
  requireAssertionPerSignature,
7767
+ protection,
7620
7768
  deferDeviceProtection,
7621
7769
  resumeSessionAcrossTabs,
7622
7770
  offerRecoveryAtSignup,
@@ -8825,6 +8973,7 @@ export {
8825
8973
  DecaneLogo,
8826
8974
  DecaneMark,
8827
8975
  DecaneWordmark,
8976
+ EnclaveHttpError,
8828
8977
  ExportNotAuthorizedError,
8829
8978
  InvalidAccessTokenError,
8830
8979
  InvalidApiKeyError,
@@ -8836,6 +8985,7 @@ export {
8836
8985
  OriginNotAllowedError,
8837
8986
  PromptPasswordNotConfiguredError,
8838
8987
  PromptPinNotConfiguredError,
8988
+ ProvisioningRateLimitedError,
8839
8989
  ProvisioningTimeoutError,
8840
8990
  ReconnectRequiredError,
8841
8991
  RecoveryFailedError,
@@ -8847,6 +8997,8 @@ export {
8847
8997
  UnlockRequiredError,
8848
8998
  UnlockTokenRejectedError,
8849
8999
  UserCancelledError,
9000
+ WalletEnrolmentError,
9001
+ WalletExistsError,
8850
9002
  WalletSelector,
8851
9003
  WeakPasswordError,
8852
9004
  WebAuthnNotSupportedError,