decane-connect-kit 2.22.0 → 2.23.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.cjs CHANGED
@@ -3766,6 +3766,7 @@ async function createDecaneConnect(config) {
3766
3766
  let lastUserId = loadLastUser(LAST_USER_KEY);
3767
3767
  let ethProvider = null;
3768
3768
  const persistSession = config.persistSession !== false;
3769
+ const deferDeviceProtection = config.deferDeviceProtection === true;
3769
3770
  const serverAssistedRecovery = true;
3770
3771
  const RECOVERY_OFFER_TIMEOUT_MS = 5 * 6e4;
3771
3772
  const selfHealAttempted = /* @__PURE__ */ new Set();
@@ -4077,11 +4078,12 @@ async function createDecaneConnect(config) {
4077
4078
  }
4078
4079
  async function firstTimeSetup(userId, jwt) {
4079
4080
  prewarmAttestation(teeUrl);
4080
- let unlockMethod;
4081
- let wrappingKey;
4081
+ let unlockMethod = "password";
4082
+ let wrappingKey = null;
4082
4083
  let meta = {};
4083
4084
  let unlockTokenKey = null;
4084
- if (isPasskeySupported()) {
4085
+ if (deferDeviceProtection) {
4086
+ } else if (isPasskeySupported()) {
4085
4087
  const prfSalt = generateRandomBytes(32);
4086
4088
  try {
4087
4089
  const { credentialId, prfOutput } = await registerWebAuthnCredential({ api, jwt, rpId, rpName, prfSalt });
@@ -4122,32 +4124,37 @@ async function createDecaneConnect(config) {
4122
4124
  } finally {
4123
4125
  zeroize(master);
4124
4126
  }
4125
- const iv = generateRandomBytes(12);
4126
- const wrappedDeviceShare = await aesGcmEncrypt(
4127
- wrappingKey,
4128
- shareSet.device,
4129
- iv
4130
- );
4131
- await storage.putShare({
4132
- userId,
4133
- wrappedDeviceShare,
4134
- iv,
4135
- unlockMethod,
4136
- credentialId: meta.credentialId ? b64uToBuffer(meta.credentialId) : void 0,
4137
- prfSalt: meta.prfSalt ? b64uToBuffer(meta.prfSalt) : void 0,
4138
- pinSalt: meta.pinSalt ? b64uToBuffer(meta.pinSalt) : void 0,
4139
- pinIterations: meta.pinIterations,
4140
- passwordSalt: meta.passwordSalt ? b64uToBuffer(meta.passwordSalt) : void 0,
4141
- passwordIterations: meta.passwordIterations,
4142
- version: 1
4143
- });
4144
- if (unlockTokenKey) await attachUnlockToken(userId, jwt, unlockTokenKey);
4127
+ let wrappedDeviceShare = null;
4128
+ let iv = null;
4129
+ if (wrappingKey) {
4130
+ iv = generateRandomBytes(12);
4131
+ wrappedDeviceShare = await aesGcmEncrypt(
4132
+ wrappingKey,
4133
+ shareSet.device,
4134
+ iv
4135
+ );
4136
+ await storage.putShare({
4137
+ userId,
4138
+ wrappedDeviceShare,
4139
+ iv,
4140
+ unlockMethod,
4141
+ credentialId: meta.credentialId ? b64uToBuffer(meta.credentialId) : void 0,
4142
+ prfSalt: meta.prfSalt ? b64uToBuffer(meta.prfSalt) : void 0,
4143
+ pinSalt: meta.pinSalt ? b64uToBuffer(meta.pinSalt) : void 0,
4144
+ pinIterations: meta.pinIterations,
4145
+ passwordSalt: meta.passwordSalt ? b64uToBuffer(meta.passwordSalt) : void 0,
4146
+ passwordIterations: meta.passwordIterations,
4147
+ version: 1
4148
+ });
4149
+ if (unlockTokenKey) await attachUnlockToken(userId, jwt, unlockTokenKey);
4150
+ }
4145
4151
  const serverIv = generateRandomBytes(12);
4146
4152
  await withDeadline(api.putShare(jwt, {
4147
4153
  ciphertext: shareSet.server,
4148
4154
  iv: serverIv,
4149
4155
  wrappedShareMeta: {
4150
- unlockMethod
4156
+ // The server records this on the wallet_created event and nothing else.
4157
+ unlockMethod: deferDeviceProtection ? "deferred" : unlockMethod
4151
4158
  // credential IDs, prfSalt, pinSalt stay on-device — never sent to server
4152
4159
  },
4153
4160
  evmAddress,
@@ -4156,7 +4163,7 @@ async function createDecaneConnect(config) {
4156
4163
  }), remainingBudget(), "storing your wallet");
4157
4164
  zeroize(shareSet.server);
4158
4165
  const addresses = { evm: evmAddress, solana: solanaAddress, tron: tronAddress };
4159
- if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt) {
4166
+ if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt && wrappedDeviceShare && iv) {
4160
4167
  try {
4161
4168
  await api.putRecoveryBackup(jwt, {
4162
4169
  wrappedDeviceShare,
@@ -4239,7 +4246,7 @@ async function createDecaneConnect(config) {
4239
4246
  const tee = new TeeClient(teeUrl);
4240
4247
  const provisioned = await tee.provisionDeviceShare(jwt);
4241
4248
  if (provisioned) {
4242
- await persistProvisionedShare(userId, jwt, provisioned);
4249
+ if (!deferDeviceProtection) await persistProvisionedShare(userId, jwt, provisioned);
4243
4250
  let addrs;
4244
4251
  try {
4245
4252
  addrs = await session.start(provisioned, jwt, recordedAddresses);
@@ -4283,7 +4290,7 @@ async function createDecaneConnect(config) {
4283
4290
  });
4284
4291
  deviceShare = recovered.deviceShare;
4285
4292
  joinedViaPasskeySync = recovered.flow === "passkey-sync";
4286
- if (!await storage.getShare(userId) && isPasskeySupported()) {
4293
+ if (!deferDeviceProtection && !await storage.getShare(userId) && isPasskeySupported()) {
4287
4294
  await registerPasskeyAndBackup(userId, jwt, deviceShare);
4288
4295
  }
4289
4296
  } else {
@@ -4754,6 +4761,24 @@ async function createDecaneConnect(config) {
4754
4761
  zeroize(deviceShare);
4755
4762
  }
4756
4763
  },
4764
+ async deviceProtected() {
4765
+ if (!auth) return false;
4766
+ return await storage.getShare(auth.userId) !== null;
4767
+ },
4768
+ async protectDevice() {
4769
+ if (!auth) throw new ReconnectRequiredError();
4770
+ if (await storage.getShare(auth.userId)) return true;
4771
+ if (!serverAssistedRecovery) throw new NewDeviceError();
4772
+ const tee = new TeeClient(teeUrl);
4773
+ const provisioned = await tee.provisionDeviceShare(auth.jwt);
4774
+ if (!provisioned) throw new NewDeviceError();
4775
+ try {
4776
+ await persistProvisionedShare(auth.userId, auth.jwt, provisioned);
4777
+ } finally {
4778
+ zeroize(provisioned);
4779
+ }
4780
+ return await storage.getShare(auth.userId) !== null;
4781
+ },
4757
4782
  async unlock() {
4758
4783
  if (!auth) {
4759
4784
  const candidate = await passkeyLoginCandidate();
@@ -7376,6 +7401,7 @@ function DecaneKit({ config, children }) {
7376
7401
  chains,
7377
7402
  rpcUrls,
7378
7403
  requireAssertionPerSignature,
7404
+ deferDeviceProtection,
7379
7405
  // Recovery callbacks. Forwarding these is what makes recovery
7380
7406
  // configurable at all from <DecaneKit>; before, they were silently
7381
7407
  // dropped and the headless client was constructed without them.
@@ -7396,6 +7422,7 @@ function DecaneKit({ config, children }) {
7396
7422
  promptPin,
7397
7423
  promptUnlockPassword,
7398
7424
  requireAssertionPerSignature,
7425
+ deferDeviceProtection,
7399
7426
  offerRecoveryAtSignup,
7400
7427
  onRecoveryShareOffer,
7401
7428
  onRecoveryFileReady,
@@ -7642,6 +7669,16 @@ function DecaneKit({ config, children }) {
7642
7669
  if (!sdk) throw new Error("Social wallet not connected");
7643
7670
  return sdk.addPasskey();
7644
7671
  }, []);
7672
+ const socialDeviceProtected = (0, import_react5.useCallback)(async () => {
7673
+ const sdk = socialRef.current;
7674
+ if (!sdk) return false;
7675
+ return sdk.deviceProtected();
7676
+ }, []);
7677
+ const socialProtectDevice = (0, import_react5.useCallback)(async () => {
7678
+ const sdk = socialRef.current;
7679
+ if (!sdk) throw new Error("Social wallet not connected");
7680
+ return sdk.protectDevice();
7681
+ }, []);
7645
7682
  const socialSetUnlockPassword = (0, import_react5.useCallback)(async (password) => {
7646
7683
  const sdk = socialRef.current;
7647
7684
  if (!sdk) throw new Error("Social wallet not connected");
@@ -7734,6 +7771,8 @@ function DecaneKit({ config, children }) {
7734
7771
  socialHasRecoveryShare,
7735
7772
  socialHasPasskey,
7736
7773
  socialAddPasskey,
7774
+ socialDeviceProtected,
7775
+ socialProtectDevice,
7737
7776
  socialSetUnlockPassword,
7738
7777
  socialCanUnlockWithPassword,
7739
7778
  socialForgetLastUser,
@@ -8044,6 +8083,8 @@ function useSocialWallet() {
8044
8083
  socialHasRecoveryShare,
8045
8084
  socialHasPasskey,
8046
8085
  socialAddPasskey,
8086
+ socialDeviceProtected,
8087
+ socialProtectDevice,
8047
8088
  socialSetUnlockPassword,
8048
8089
  socialCanUnlockWithPassword,
8049
8090
  socialForgetLastUser,
@@ -8146,6 +8187,16 @@ function useSocialWallet() {
8146
8187
  throw e;
8147
8188
  }
8148
8189
  }, [socialAddPasskey]);
8190
+ const deviceProtected = (0, import_react6.useCallback)(() => run(() => socialDeviceProtected()), [socialDeviceProtected]);
8191
+ const protectDevice = (0, import_react6.useCallback)(async () => {
8192
+ setError(null);
8193
+ try {
8194
+ return await socialProtectDevice();
8195
+ } catch (e) {
8196
+ setError(e.message);
8197
+ throw e;
8198
+ }
8199
+ }, [socialProtectDevice]);
8149
8200
  const setUnlockPassword = (0, import_react6.useCallback)(async (password) => {
8150
8201
  setError(null);
8151
8202
  try {
@@ -8234,6 +8285,10 @@ function useSocialWallet() {
8234
8285
  // Passkey management for THIS device
8235
8286
  hasPasskey,
8236
8287
  addPasskey,
8288
+ // Deferred protection (deferDeviceProtection): whether this device holds
8289
+ // a wrapped share, and the call that stores one — passkey or password.
8290
+ deviceProtected,
8291
+ protectDevice,
8237
8292
  // "Unlock with your password": setUnlockPassword needs a live session;
8238
8293
  // canUnlockWithPassword and unlockWithPassword are the signed-out pair that
8239
8294
  // open one without an identity provider or a passkey.