decane-connect-kit 2.26.1 → 2.27.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 +182 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -6
- package/dist/index.d.ts +100 -6
- package/dist/index.js +179 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,12 @@ __export(errors_exports, {
|
|
|
34
36
|
UnlockRequiredError: () => UnlockRequiredError,
|
|
35
37
|
UnlockTokenRejectedError: () => UnlockTokenRejectedError,
|
|
36
38
|
UserCancelledError: () => UserCancelledError,
|
|
39
|
+
WalletEnrolmentError: () => WalletEnrolmentError,
|
|
37
40
|
WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
|
|
38
41
|
WrongPasswordError: () => WrongPasswordError,
|
|
39
42
|
WrongUnlockPasswordError: () => WrongUnlockPasswordError
|
|
40
43
|
});
|
|
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;
|
|
44
|
+
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, WalletEnrolmentError, ProvisioningRateLimitedError, EnclaveHttpError;
|
|
42
45
|
var init_errors = __esm({
|
|
43
46
|
"src/social/errors.ts"() {
|
|
44
47
|
"use strict";
|
|
@@ -187,6 +190,31 @@ var init_errors = __esm({
|
|
|
187
190
|
super("EXPORT_NOT_AUTHORIZED", "Export requires a fresh security check", true);
|
|
188
191
|
}
|
|
189
192
|
};
|
|
193
|
+
WalletEnrolmentError = class extends DecaneConnectError {
|
|
194
|
+
constructor(cause) {
|
|
195
|
+
super(
|
|
196
|
+
"WALLET_ENROLMENT_FAILED",
|
|
197
|
+
"Your wallet could not be set up: the signing enclave did not accept it. Nothing was created \u2014 sign in again to try again.",
|
|
198
|
+
true
|
|
199
|
+
);
|
|
200
|
+
if (cause !== void 0) this.cause = cause;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
ProvisioningRateLimitedError = class extends DecaneConnectError {
|
|
204
|
+
constructor() {
|
|
205
|
+
super(
|
|
206
|
+
"PROVISIONING_RATE_LIMITED",
|
|
207
|
+
"This wallet has been opened on too many devices or tabs in the last hour. Wait a little and sign in again.",
|
|
208
|
+
true
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
EnclaveHttpError = class extends DecaneConnectError {
|
|
213
|
+
constructor(status, message) {
|
|
214
|
+
super("ENCLAVE_HTTP", message, status >= 500);
|
|
215
|
+
this.status = status;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
190
218
|
}
|
|
191
219
|
});
|
|
192
220
|
|
|
@@ -2114,7 +2142,8 @@ var ApiClient = class {
|
|
|
2114
2142
|
wrappedShareMeta: btoa(JSON.stringify(share.wrappedShareMeta)),
|
|
2115
2143
|
evmAddress: share.evmAddress,
|
|
2116
2144
|
solanaAddress: share.solanaAddress,
|
|
2117
|
-
tronAddress: share.tronAddress
|
|
2145
|
+
tronAddress: share.tronAddress,
|
|
2146
|
+
...share.pendingEnrolment === void 0 ? {} : { pendingEnrolment: share.pendingEnrolment }
|
|
2118
2147
|
})
|
|
2119
2148
|
});
|
|
2120
2149
|
}
|
|
@@ -2698,7 +2727,8 @@ var TeeClient = class {
|
|
|
2698
2727
|
});
|
|
2699
2728
|
if (!res.ok) {
|
|
2700
2729
|
const detail = await res.text().catch(() => "");
|
|
2701
|
-
throw new
|
|
2730
|
+
throw new EnclaveHttpError(
|
|
2731
|
+
res.status,
|
|
2702
2732
|
`auth share enrolment failed (HTTP ${res.status})${detail ? `: ${detail.slice(0, 200)}` : ""}`
|
|
2703
2733
|
);
|
|
2704
2734
|
}
|
|
@@ -2719,9 +2749,11 @@ var TeeClient = class {
|
|
|
2719
2749
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
|
|
2720
2750
|
});
|
|
2721
2751
|
if (res.status === 409) return null;
|
|
2752
|
+
if (res.status === 429) throw new ProvisioningRateLimitedError();
|
|
2722
2753
|
if (!res.ok) {
|
|
2723
2754
|
const detail = await res.text().catch(() => "");
|
|
2724
|
-
throw new
|
|
2755
|
+
throw new EnclaveHttpError(
|
|
2756
|
+
res.status,
|
|
2725
2757
|
`device share provisioning failed (HTTP ${res.status})${detail ? `: ${detail.slice(0, 200)}` : ""}`
|
|
2726
2758
|
);
|
|
2727
2759
|
}
|
|
@@ -2936,6 +2968,35 @@ var SessionManager = class {
|
|
|
2936
2968
|
}
|
|
2937
2969
|
};
|
|
2938
2970
|
|
|
2971
|
+
// src/social/identityTier.ts
|
|
2972
|
+
function resolveProtectionTier(config) {
|
|
2973
|
+
if (config.protection) return config.protection;
|
|
2974
|
+
return config.deferDeviceProtection === true ? "identity" : "device";
|
|
2975
|
+
}
|
|
2976
|
+
function resumeAcrossTabsDefault(config, identityTier) {
|
|
2977
|
+
return config.resumeSessionAcrossTabs ?? identityTier;
|
|
2978
|
+
}
|
|
2979
|
+
var ENROL_BACKOFF_MS = [0, 1e3, 3e3];
|
|
2980
|
+
function enrolmentRetryable(status) {
|
|
2981
|
+
return status !== 429 && status !== 403;
|
|
2982
|
+
}
|
|
2983
|
+
function shouldTryProvisionFirst(identityTier, hasStoredShare) {
|
|
2984
|
+
return identityTier || !hasStoredShare;
|
|
2985
|
+
}
|
|
2986
|
+
function routeAfterProvision(input) {
|
|
2987
|
+
switch (input.outcome) {
|
|
2988
|
+
case "share":
|
|
2989
|
+
return "session";
|
|
2990
|
+
case "rate-limited":
|
|
2991
|
+
return "fail";
|
|
2992
|
+
case "not-enrolled":
|
|
2993
|
+
if (!input.identityTier) return "recover";
|
|
2994
|
+
return input.hasStoredShare ? "migrate" : "new-device";
|
|
2995
|
+
case "error":
|
|
2996
|
+
return input.identityTier ? "fail" : "recover";
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
|
|
2939
3000
|
// src/social/crypto/shares.ts
|
|
2940
3001
|
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
2941
3002
|
import { split, combine } from "shamir-secret-sharing";
|
|
@@ -3846,7 +3907,8 @@ function clearSessionPersist(key) {
|
|
|
3846
3907
|
async function createDecaneConnect(config) {
|
|
3847
3908
|
if (!config.appId) throw new Error("decane-connect-kit: appId is required");
|
|
3848
3909
|
if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
|
|
3849
|
-
|
|
3910
|
+
const identityTier = resolveProtectionTier(config) === "identity";
|
|
3911
|
+
useCrossTabSessionStore(resumeAcrossTabsDefault(config, identityTier));
|
|
3850
3912
|
if (!config.chains?.length)
|
|
3851
3913
|
throw new Error("decane-connect-kit: at least one chain is required");
|
|
3852
3914
|
if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
|
|
@@ -3884,7 +3946,6 @@ async function createDecaneConnect(config) {
|
|
|
3884
3946
|
let lastUserId = loadLastUser(LAST_USER_KEY) ?? (LEGACY_LAST_USER_KEY !== LAST_USER_KEY ? loadLastUser(LEGACY_LAST_USER_KEY) : null);
|
|
3885
3947
|
let ethProvider = null;
|
|
3886
3948
|
const persistSession = config.persistSession !== false;
|
|
3887
|
-
const deferDeviceProtection = config.deferDeviceProtection === true;
|
|
3888
3949
|
const serverAssistedRecovery = true;
|
|
3889
3950
|
const RECOVERY_OFFER_TIMEOUT_MS = 5 * 6e4;
|
|
3890
3951
|
const selfHealAttempted = /* @__PURE__ */ new Set();
|
|
@@ -3905,22 +3966,27 @@ async function createDecaneConnect(config) {
|
|
|
3905
3966
|
zeroize(share);
|
|
3906
3967
|
}
|
|
3907
3968
|
}
|
|
3908
|
-
async function
|
|
3969
|
+
async function enrolBlocking(jwt, share, budgetMs) {
|
|
3970
|
+
const startedAt = Date.now();
|
|
3909
3971
|
try {
|
|
3910
|
-
|
|
3972
|
+
let lastError;
|
|
3973
|
+
for (const wait of ENROL_BACKOFF_MS) {
|
|
3974
|
+
if (wait) await new Promise((r) => setTimeout(r, wait));
|
|
3911
3975
|
try {
|
|
3912
|
-
await
|
|
3976
|
+
await withDeadline(
|
|
3977
|
+
new TeeClient(teeUrl).enrolAuthShare(jwt, share),
|
|
3978
|
+
budgetMs - (Date.now() - startedAt),
|
|
3979
|
+
"enrolling your wallet"
|
|
3980
|
+
);
|
|
3913
3981
|
return;
|
|
3914
3982
|
} catch (e) {
|
|
3915
|
-
if (
|
|
3916
|
-
|
|
3917
|
-
|
|
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
|
-
}
|
|
3983
|
+
if (e instanceof AttestationError || e instanceof ProvisioningTimeoutError) throw e;
|
|
3984
|
+
lastError = e;
|
|
3985
|
+
if (!enrolmentRetryable(e.status)) break;
|
|
3922
3986
|
}
|
|
3923
3987
|
}
|
|
3988
|
+
events.emit("enrolment-failed");
|
|
3989
|
+
throw new WalletEnrolmentError(lastError);
|
|
3924
3990
|
} finally {
|
|
3925
3991
|
zeroize(share);
|
|
3926
3992
|
}
|
|
@@ -4016,6 +4082,7 @@ async function createDecaneConnect(config) {
|
|
|
4016
4082
|
else scheduleAccessTokenExpiry(saved.jwt, saved.expiresAt);
|
|
4017
4083
|
auth = { jwt: saved.jwt, userId: saved.userId, addresses: saved.addresses };
|
|
4018
4084
|
persisted = { userId: saved.userId, addresses: saved.addresses };
|
|
4085
|
+
if (saved.handle) armTokenRefresh(saved.jwt);
|
|
4019
4086
|
} catch {
|
|
4020
4087
|
dropLiveSession();
|
|
4021
4088
|
}
|
|
@@ -4025,6 +4092,13 @@ async function createDecaneConnect(config) {
|
|
|
4025
4092
|
if (LEGACY_SESSION_KEY !== SESSION_KEY) clearSessionPersist(LEGACY_SESSION_KEY);
|
|
4026
4093
|
}
|
|
4027
4094
|
events.on("session-expired", () => dropLiveSession());
|
|
4095
|
+
function armTokenRefresh(jwt) {
|
|
4096
|
+
const expiresAt = decodeAccessToken(jwt)?.expiresAt;
|
|
4097
|
+
if (expiresAt) scheduleTokenRefresh(jwt, expiresAt);
|
|
4098
|
+
}
|
|
4099
|
+
events.on("connected", () => {
|
|
4100
|
+
if (auth) armTokenRefresh(auth.jwt);
|
|
4101
|
+
});
|
|
4028
4102
|
async function passwordSetup() {
|
|
4029
4103
|
if (!config.promptUnlockPassword) throw new PromptPasswordNotConfiguredError();
|
|
4030
4104
|
const password = await config.promptUnlockPassword({ kind: "set" });
|
|
@@ -4249,7 +4323,7 @@ async function createDecaneConnect(config) {
|
|
|
4249
4323
|
let wrappingKey = null;
|
|
4250
4324
|
let meta = {};
|
|
4251
4325
|
let unlockTokenKey = null;
|
|
4252
|
-
if (
|
|
4326
|
+
if (identityTier) {
|
|
4253
4327
|
} else if (isPasskeySupported()) {
|
|
4254
4328
|
const prfSalt = generateRandomBytes(32);
|
|
4255
4329
|
try {
|
|
@@ -4321,14 +4395,29 @@ async function createDecaneConnect(config) {
|
|
|
4321
4395
|
iv: serverIv,
|
|
4322
4396
|
wrappedShareMeta: {
|
|
4323
4397
|
// The server records this on the wallet_created event and nothing else.
|
|
4324
|
-
unlockMethod:
|
|
4398
|
+
unlockMethod: identityTier ? "identity" : unlockMethod
|
|
4325
4399
|
// credential IDs, prfSalt, pinSalt stay on-device — never sent to server
|
|
4326
4400
|
},
|
|
4327
4401
|
evmAddress,
|
|
4328
4402
|
solanaAddress,
|
|
4329
|
-
tronAddress
|
|
4403
|
+
tronAddress,
|
|
4404
|
+
// Provisional until the enclave has sealed the auth share, just below. A
|
|
4405
|
+
// backend that knows the flag keeps this row invisible to hasShare until
|
|
4406
|
+
// then, so a creation that fails past this point leaves no wallet behind
|
|
4407
|
+
// and the next sign-in simply creates again — with a new key, which is
|
|
4408
|
+
// why the backend also replaces the recorded addresses outright.
|
|
4409
|
+
pendingEnrolment: true
|
|
4330
4410
|
}), remainingBudget(), "storing your wallet");
|
|
4331
4411
|
zeroize(shareSet.server);
|
|
4412
|
+
try {
|
|
4413
|
+
await enrolBlocking(jwt, new Uint8Array(shareSet.recovery), remainingBudget());
|
|
4414
|
+
} catch (e) {
|
|
4415
|
+
zeroize(shareSet.device);
|
|
4416
|
+
zeroize(shareSet.recovery);
|
|
4417
|
+
if (wrappingKey) await storage2.deleteShare(userId).catch(() => {
|
|
4418
|
+
});
|
|
4419
|
+
throw e;
|
|
4420
|
+
}
|
|
4332
4421
|
const addresses = { evm: evmAddress, solana: solanaAddress, tron: tronAddress };
|
|
4333
4422
|
if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt && wrappedDeviceShare && iv) {
|
|
4334
4423
|
try {
|
|
@@ -4357,10 +4446,7 @@ async function createDecaneConnect(config) {
|
|
|
4357
4446
|
addresses.evm = evmAddress = confirmed.evm;
|
|
4358
4447
|
addresses.solana = solanaAddress = confirmed.solana;
|
|
4359
4448
|
addresses.tron = tronAddress = confirmed.tron ?? evmToTronAddress2(confirmed.evm);
|
|
4360
|
-
if (
|
|
4361
|
-
void enrolInBackground(jwt, new Uint8Array(shareSet.recovery));
|
|
4362
|
-
}
|
|
4363
|
-
if (config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
|
|
4449
|
+
if (!identityTier && config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
|
|
4364
4450
|
void offerRecoveryShare(
|
|
4365
4451
|
jwt,
|
|
4366
4452
|
new Uint8Array(shareSet.recovery),
|
|
@@ -4407,31 +4493,44 @@ async function createDecaneConnect(config) {
|
|
|
4407
4493
|
const stored = await storage2.getShare(userId);
|
|
4408
4494
|
let deviceShare;
|
|
4409
4495
|
let joinedViaPasskeySync = false;
|
|
4410
|
-
|
|
4411
|
-
|
|
4496
|
+
let migrating = false;
|
|
4497
|
+
if (serverAssistedRecovery && shouldTryProvisionFirst(identityTier, !!stored)) {
|
|
4498
|
+
let provisioned = null;
|
|
4499
|
+
let failure;
|
|
4500
|
+
let outcome;
|
|
4501
|
+
try {
|
|
4502
|
+
provisioned = await new TeeClient(teeUrl).provisionDeviceShare(jwt);
|
|
4503
|
+
outcome = provisioned ? "share" : "not-enrolled";
|
|
4504
|
+
} catch (e) {
|
|
4505
|
+
failure = e;
|
|
4506
|
+
outcome = e instanceof ProvisioningRateLimitedError ? "rate-limited" : "error";
|
|
4507
|
+
}
|
|
4508
|
+
const route = routeAfterProvision({ identityTier, hasStoredShare: !!stored, outcome });
|
|
4509
|
+
if (route === "session" && provisioned) {
|
|
4510
|
+
if (!identityTier) await persistProvisionedShare(userId, jwt, provisioned);
|
|
4511
|
+
let addrs;
|
|
4412
4512
|
try {
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
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
|
-
);
|
|
4513
|
+
addrs = await session.start(provisioned, jwt, recordedAddresses);
|
|
4514
|
+
} finally {
|
|
4515
|
+
zeroize(provisioned);
|
|
4433
4516
|
}
|
|
4517
|
+
return addrs;
|
|
4518
|
+
}
|
|
4519
|
+
if (route === "fail") throw failure;
|
|
4520
|
+
if (route === "new-device") throw new NewDeviceError();
|
|
4521
|
+
if (route === "migrate") migrating = true;
|
|
4522
|
+
if (route === "recover" && outcome === "not-enrolled") {
|
|
4523
|
+
console.warn(
|
|
4524
|
+
"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."
|
|
4525
|
+
);
|
|
4526
|
+
} else if (route === "recover") {
|
|
4527
|
+
console.warn(
|
|
4528
|
+
"decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
|
|
4529
|
+
failure
|
|
4530
|
+
);
|
|
4434
4531
|
}
|
|
4532
|
+
}
|
|
4533
|
+
if (!stored) {
|
|
4435
4534
|
if (!config.onRecoveryRotated) {
|
|
4436
4535
|
throw new CallSequenceError(
|
|
4437
4536
|
"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 +4556,7 @@ async function createDecaneConnect(config) {
|
|
|
4457
4556
|
});
|
|
4458
4557
|
deviceShare = recovered.deviceShare;
|
|
4459
4558
|
joinedViaPasskeySync = recovered.flow === "passkey-sync";
|
|
4460
|
-
if (!
|
|
4559
|
+
if (!identityTier && !await storage2.getShare(userId) && isPasskeySupported()) {
|
|
4461
4560
|
await registerPasskeyAndBackup(userId, jwt, deviceShare);
|
|
4462
4561
|
}
|
|
4463
4562
|
} else {
|
|
@@ -4471,6 +4570,14 @@ async function createDecaneConnect(config) {
|
|
|
4471
4570
|
} catch {
|
|
4472
4571
|
throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
|
|
4473
4572
|
}
|
|
4573
|
+
if (migrating) {
|
|
4574
|
+
try {
|
|
4575
|
+
await enrolBlocking(jwt, new Uint8Array(deviceShare), PROVISIONING_BUDGET_MS);
|
|
4576
|
+
} catch (e) {
|
|
4577
|
+
zeroize(deviceShare);
|
|
4578
|
+
throw e;
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4474
4581
|
}
|
|
4475
4582
|
let addresses;
|
|
4476
4583
|
try {
|
|
@@ -4485,7 +4592,11 @@ async function createDecaneConnect(config) {
|
|
|
4485
4592
|
}
|
|
4486
4593
|
throw e;
|
|
4487
4594
|
}
|
|
4488
|
-
|
|
4595
|
+
if (migrating) {
|
|
4596
|
+
events.emit("migrated-to-identity");
|
|
4597
|
+
} else if (!identityTier) {
|
|
4598
|
+
void selfHealEnrolment(userId, jwt, new Uint8Array(deviceShare));
|
|
4599
|
+
}
|
|
4489
4600
|
zeroize(deviceShare);
|
|
4490
4601
|
if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
|
|
4491
4602
|
console.warn(
|
|
@@ -4668,7 +4779,7 @@ async function createDecaneConnect(config) {
|
|
|
4668
4779
|
if (auth && session.isActive()) {
|
|
4669
4780
|
return { addresses: auth.addresses, isNewUser: false };
|
|
4670
4781
|
}
|
|
4671
|
-
const candidate = await passkeyLoginCandidate();
|
|
4782
|
+
const candidate = identityTier ? null : await passkeyLoginCandidate();
|
|
4672
4783
|
if (candidate) {
|
|
4673
4784
|
try {
|
|
4674
4785
|
return await passkeyLogin(candidate);
|
|
@@ -4778,9 +4889,15 @@ async function createDecaneConnect(config) {
|
|
|
4778
4889
|
return handleAuthResult(result);
|
|
4779
4890
|
},
|
|
4780
4891
|
async canSignInWithPasskey() {
|
|
4892
|
+
if (identityTier) return false;
|
|
4781
4893
|
return canOfferPasskey();
|
|
4782
4894
|
},
|
|
4783
4895
|
async signInWithPasskey() {
|
|
4896
|
+
if (identityTier) {
|
|
4897
|
+
throw new CallSequenceError(
|
|
4898
|
+
"signInWithPasskey() is not available in the identity tier (protection: 'identity'); use connect()."
|
|
4899
|
+
);
|
|
4900
|
+
}
|
|
4784
4901
|
const candidate = await passkeyDeviceCandidate();
|
|
4785
4902
|
if (candidate) return passkeyLogin(candidate);
|
|
4786
4903
|
const known = persisted?.userId ?? lastUserId;
|
|
@@ -4916,6 +5033,11 @@ async function createDecaneConnect(config) {
|
|
|
4916
5033
|
return stored?.unlockMethod === "passkey";
|
|
4917
5034
|
},
|
|
4918
5035
|
async addPasskey() {
|
|
5036
|
+
if (identityTier) {
|
|
5037
|
+
throw new CallSequenceError(
|
|
5038
|
+
"addPasskey() is not available in the identity tier (protection: 'identity'): nothing is stored on the device for a passkey to protect."
|
|
5039
|
+
);
|
|
5040
|
+
}
|
|
4919
5041
|
if (!auth) throw new ReconnectRequiredError();
|
|
4920
5042
|
if (!isPasskeySupported()) throw new WebAuthnNotSupportedError();
|
|
4921
5043
|
const stored = await storage2.getShare(auth.userId);
|
|
@@ -4954,6 +5076,7 @@ async function createDecaneConnect(config) {
|
|
|
4954
5076
|
return await storage2.getShare(auth.userId) !== null;
|
|
4955
5077
|
},
|
|
4956
5078
|
async protectDevice() {
|
|
5079
|
+
if (identityTier) return false;
|
|
4957
5080
|
if (!auth) throw new ReconnectRequiredError();
|
|
4958
5081
|
if (await storage2.getShare(auth.userId)) return true;
|
|
4959
5082
|
if (!serverAssistedRecovery) throw new NewDeviceError();
|
|
@@ -4969,7 +5092,7 @@ async function createDecaneConnect(config) {
|
|
|
4969
5092
|
},
|
|
4970
5093
|
async unlock() {
|
|
4971
5094
|
if (!auth) {
|
|
4972
|
-
const candidate = await passkeyLoginCandidate();
|
|
5095
|
+
const candidate = identityTier ? null : await passkeyLoginCandidate();
|
|
4973
5096
|
if (candidate) {
|
|
4974
5097
|
await passkeyLogin(candidate);
|
|
4975
5098
|
return;
|
|
@@ -5138,6 +5261,8 @@ async function createDecaneConnect(config) {
|
|
|
5138
5261
|
},
|
|
5139
5262
|
async disconnect() {
|
|
5140
5263
|
session.end();
|
|
5264
|
+
if (tokenRefresh) clearTimeout(tokenRefresh);
|
|
5265
|
+
tokenRefresh = null;
|
|
5141
5266
|
if (auth?.jwt) {
|
|
5142
5267
|
await api.revoke(auth.jwt).catch(() => {
|
|
5143
5268
|
});
|
|
@@ -7595,6 +7720,7 @@ function DecaneKit({ config, children }) {
|
|
|
7595
7720
|
chains,
|
|
7596
7721
|
rpcUrls,
|
|
7597
7722
|
requireAssertionPerSignature,
|
|
7723
|
+
protection,
|
|
7598
7724
|
deferDeviceProtection,
|
|
7599
7725
|
resumeSessionAcrossTabs,
|
|
7600
7726
|
// Recovery callbacks. Forwarding these is what makes recovery
|
|
@@ -7617,6 +7743,7 @@ function DecaneKit({ config, children }) {
|
|
|
7617
7743
|
promptPin,
|
|
7618
7744
|
promptUnlockPassword,
|
|
7619
7745
|
requireAssertionPerSignature,
|
|
7746
|
+
protection,
|
|
7620
7747
|
deferDeviceProtection,
|
|
7621
7748
|
resumeSessionAcrossTabs,
|
|
7622
7749
|
offerRecoveryAtSignup,
|
|
@@ -8825,6 +8952,7 @@ export {
|
|
|
8825
8952
|
DecaneLogo,
|
|
8826
8953
|
DecaneMark,
|
|
8827
8954
|
DecaneWordmark,
|
|
8955
|
+
EnclaveHttpError,
|
|
8828
8956
|
ExportNotAuthorizedError,
|
|
8829
8957
|
InvalidAccessTokenError,
|
|
8830
8958
|
InvalidApiKeyError,
|
|
@@ -8836,6 +8964,7 @@ export {
|
|
|
8836
8964
|
OriginNotAllowedError,
|
|
8837
8965
|
PromptPasswordNotConfiguredError,
|
|
8838
8966
|
PromptPinNotConfiguredError,
|
|
8967
|
+
ProvisioningRateLimitedError,
|
|
8839
8968
|
ProvisioningTimeoutError,
|
|
8840
8969
|
ReconnectRequiredError,
|
|
8841
8970
|
RecoveryFailedError,
|
|
@@ -8847,6 +8976,7 @@ export {
|
|
|
8847
8976
|
UnlockRequiredError,
|
|
8848
8977
|
UnlockTokenRejectedError,
|
|
8849
8978
|
UserCancelledError,
|
|
8979
|
+
WalletEnrolmentError,
|
|
8850
8980
|
WalletSelector,
|
|
8851
8981
|
WeakPasswordError,
|
|
8852
8982
|
WebAuthnNotSupportedError,
|