decane-connect-kit 2.7.2 → 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 +165 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -3
- package/dist/index.d.ts +48 -3
- package/dist/index.js +164 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -256,6 +256,13 @@ declare class ApiClient {
|
|
|
256
256
|
savedAt: string | null;
|
|
257
257
|
nagged: boolean;
|
|
258
258
|
shareSetVersion: number;
|
|
259
|
+
/**
|
|
260
|
+
* Whether this wallet is enrolled for server-assisted provisioning, i.e.
|
|
261
|
+
* whether a new device can be given a device share from login alone.
|
|
262
|
+
* Optional because backends older than this field simply omit it — treat
|
|
263
|
+
* `undefined` as "unknown", never as "not enrolled".
|
|
264
|
+
*/
|
|
265
|
+
enrolled?: boolean;
|
|
259
266
|
}>;
|
|
260
267
|
dismissRecoveryNag(jwt: string): Promise<void>;
|
|
261
268
|
}
|
|
@@ -410,7 +417,22 @@ interface DecaneConnectConfig {
|
|
|
410
417
|
* page load (stricter, but the reload friction that motivated this returns).
|
|
411
418
|
*/
|
|
412
419
|
persistSession?: boolean;
|
|
413
|
-
/**
|
|
420
|
+
/**
|
|
421
|
+
* Offer to save a recovery-share file at signup. **Default false** since
|
|
422
|
+
* 2.7.4 — opt in explicitly with `true`.
|
|
423
|
+
*
|
|
424
|
+
* The offer existed because a new device could not otherwise get a device
|
|
425
|
+
* share. Server-assisted provisioning now covers that case: every wallet is
|
|
426
|
+
* enrolled at creation, un-enrolled wallets repair themselves on the next
|
|
427
|
+
* unlock, and a returning user is provisioned from their sign-in alone. For
|
|
428
|
+
* almost every app the dialog is now a password prompt during onboarding that
|
|
429
|
+
* buys nothing, so it is off unless asked for.
|
|
430
|
+
*
|
|
431
|
+
* Recovery files themselves are unchanged and still work — `rotateShares()`
|
|
432
|
+
* issues one on demand, and `promptForRecoveryFile` still accepts one during
|
|
433
|
+
* recovery. Turn this back on if you want users to hold an offline fallback
|
|
434
|
+
* that does not depend on Decane being reachable.
|
|
435
|
+
*/
|
|
414
436
|
offerRecoveryAtSignup?: boolean;
|
|
415
437
|
/** Ask the user whether they want a recovery share and, if so, a password for it. */
|
|
416
438
|
onRecoveryShareOffer?: (addresses: {
|
|
@@ -521,7 +543,16 @@ interface SignTypedDataRequest {
|
|
|
521
543
|
message: Record<string, unknown>;
|
|
522
544
|
primaryType?: string;
|
|
523
545
|
}
|
|
524
|
-
type SdkEvent = 'connected' | 'disconnected' | 'session-expired' | 'session-started' | 'wallet-creating' | 'wallet-unlocking'
|
|
546
|
+
type SdkEvent = 'connected' | 'disconnected' | 'session-expired' | 'session-started' | 'wallet-creating' | 'wallet-unlocking'
|
|
547
|
+
/**
|
|
548
|
+
* Emitted when this wallet could not be enrolled for server-assisted
|
|
549
|
+
* recovery. Not fatal — the wallet works — but until it is enrolled the user
|
|
550
|
+
* cannot get a device share from login alone, so a future new device will
|
|
551
|
+
* need a passkey backup or a recovery file. The SDK retries automatically on
|
|
552
|
+
* later unlocks; surface or report this if you want visibility rather than a
|
|
553
|
+
* console line.
|
|
554
|
+
*/
|
|
555
|
+
| 'enrolment-failed';
|
|
525
556
|
interface DecaneConnect {
|
|
526
557
|
connect(): Promise<ConnectResult>;
|
|
527
558
|
connectWithGoogle(): Promise<ConnectResult>;
|
|
@@ -808,6 +839,20 @@ declare class NetworkError extends DecaneConnectError {
|
|
|
808
839
|
declare class ShareCorruptedError extends DecaneConnectError {
|
|
809
840
|
constructor();
|
|
810
841
|
}
|
|
842
|
+
/**
|
|
843
|
+
* Wallet creation exceeded its time budget.
|
|
844
|
+
*
|
|
845
|
+
* Provisioning is bounded so a stalled network or an unresponsive enclave
|
|
846
|
+
* surfaces as an error the app can act on, rather than a signup that appears to
|
|
847
|
+
* hang forever. The budget covers only the SDK's own work — key generation,
|
|
848
|
+
* share storage, opening the signer session — never time spent waiting on the
|
|
849
|
+
* user (a passkey prompt, choosing a PIN, the recovery-file dialog).
|
|
850
|
+
*
|
|
851
|
+
* Retryable: nothing is left half-created that a later sign-in cannot complete.
|
|
852
|
+
*/
|
|
853
|
+
declare class ProvisioningTimeoutError extends DecaneConnectError {
|
|
854
|
+
constructor(step: string);
|
|
855
|
+
}
|
|
811
856
|
declare class NewDeviceError extends DecaneConnectError {
|
|
812
857
|
constructor();
|
|
813
858
|
}
|
|
@@ -1238,4 +1283,4 @@ declare function useSignTypedData(): {
|
|
|
1238
1283
|
reset: () => void;
|
|
1239
1284
|
};
|
|
1240
1285
|
|
|
1241
|
-
export { type ActiveSession, AttestationError, type AuthProfile, type AuthResult, BUILT_IN_CHAINS, type BalanceResult, type CAIP25Method, type CAIP25SessionRequest, type CAIP25SessionResult, CHAINS, CallSequenceError, type Chain, type ChainDescriptor, type ChainId, type ChainScope, type ChainSupport, type ChainType, type ConnectMode, type ConnectResult, type DecaneConfig, type DecaneConnect, type DecaneConnectConfig, DecaneConnectError, DecaneError, DecaneErrorCode, DecaneKit, DecaneLogo, DecaneMark, type DecaneProvider, DecaneWordmark, type Eip1193Provider, type EthereumProviderOptions, ExportNotAuthorizedError, InvalidApiKeyError, InvalidPinError, InvalidRecoveryFileError, type KingsChatTokens, NetworkError, NewDeviceError, OriginNotAllowedError, type PortableBackup, PromptPinNotConfiguredError, ReconnectRequiredError, type RecordTransactionInput, RecoveryFailedError, type RecoveryProgress, type RecoveryShareFile, RecoveryWalletMismatchError, type SdkEvent, type SendTransactionRequest, SessionExpiredError, ShareCorruptedError, type SignMessageRequest, type SignTypedDataRequest, type SignedAuthorization, type SocialAuthState, type SocialConfig, SocialWalletModal, StaleDeviceShareError, type Theme, type TransactionRecord, type TxParams, type UnifiedWalletAccount, UnlockRequiredError, UserCancelledError, WalletSelector, WebAuthnNotSupportedError, type WrappedShareMeta, WrongPasswordError, createDecane, createDecaneConnect, createEthereumProvider, loginWithKingsChatPopup, startEmailAuth, useChainSwitch, useDecane, useEthereumProvider, useSendTransaction, useSignMessage, useSignTypedData, useSocialAuth, useSocialBalance, useSocialWallet, useWalletSelector, verifyEmailCode };
|
|
1286
|
+
export { type ActiveSession, AttestationError, type AuthProfile, type AuthResult, BUILT_IN_CHAINS, type BalanceResult, type CAIP25Method, type CAIP25SessionRequest, type CAIP25SessionResult, CHAINS, CallSequenceError, type Chain, type ChainDescriptor, type ChainId, type ChainScope, type ChainSupport, type ChainType, type ConnectMode, type ConnectResult, type DecaneConfig, type DecaneConnect, type DecaneConnectConfig, DecaneConnectError, DecaneError, DecaneErrorCode, DecaneKit, DecaneLogo, DecaneMark, type DecaneProvider, DecaneWordmark, type Eip1193Provider, type EthereumProviderOptions, ExportNotAuthorizedError, InvalidApiKeyError, InvalidPinError, InvalidRecoveryFileError, type KingsChatTokens, NetworkError, NewDeviceError, OriginNotAllowedError, type PortableBackup, PromptPinNotConfiguredError, ProvisioningTimeoutError, ReconnectRequiredError, type RecordTransactionInput, RecoveryFailedError, type RecoveryProgress, type RecoveryShareFile, RecoveryWalletMismatchError, type SdkEvent, type SendTransactionRequest, SessionExpiredError, ShareCorruptedError, type SignMessageRequest, type SignTypedDataRequest, type SignedAuthorization, type SocialAuthState, type SocialConfig, SocialWalletModal, StaleDeviceShareError, type Theme, type TransactionRecord, type TxParams, type UnifiedWalletAccount, UnlockRequiredError, UserCancelledError, WalletSelector, WebAuthnNotSupportedError, type WrappedShareMeta, WrongPasswordError, createDecane, createDecaneConnect, createEthereumProvider, loginWithKingsChatPopup, startEmailAuth, useChainSwitch, useDecane, useEthereumProvider, useSendTransaction, useSignMessage, useSignTypedData, useSocialAuth, useSocialBalance, useSocialWallet, useWalletSelector, verifyEmailCode };
|
package/dist/index.d.ts
CHANGED
|
@@ -256,6 +256,13 @@ declare class ApiClient {
|
|
|
256
256
|
savedAt: string | null;
|
|
257
257
|
nagged: boolean;
|
|
258
258
|
shareSetVersion: number;
|
|
259
|
+
/**
|
|
260
|
+
* Whether this wallet is enrolled for server-assisted provisioning, i.e.
|
|
261
|
+
* whether a new device can be given a device share from login alone.
|
|
262
|
+
* Optional because backends older than this field simply omit it — treat
|
|
263
|
+
* `undefined` as "unknown", never as "not enrolled".
|
|
264
|
+
*/
|
|
265
|
+
enrolled?: boolean;
|
|
259
266
|
}>;
|
|
260
267
|
dismissRecoveryNag(jwt: string): Promise<void>;
|
|
261
268
|
}
|
|
@@ -410,7 +417,22 @@ interface DecaneConnectConfig {
|
|
|
410
417
|
* page load (stricter, but the reload friction that motivated this returns).
|
|
411
418
|
*/
|
|
412
419
|
persistSession?: boolean;
|
|
413
|
-
/**
|
|
420
|
+
/**
|
|
421
|
+
* Offer to save a recovery-share file at signup. **Default false** since
|
|
422
|
+
* 2.7.4 — opt in explicitly with `true`.
|
|
423
|
+
*
|
|
424
|
+
* The offer existed because a new device could not otherwise get a device
|
|
425
|
+
* share. Server-assisted provisioning now covers that case: every wallet is
|
|
426
|
+
* enrolled at creation, un-enrolled wallets repair themselves on the next
|
|
427
|
+
* unlock, and a returning user is provisioned from their sign-in alone. For
|
|
428
|
+
* almost every app the dialog is now a password prompt during onboarding that
|
|
429
|
+
* buys nothing, so it is off unless asked for.
|
|
430
|
+
*
|
|
431
|
+
* Recovery files themselves are unchanged and still work — `rotateShares()`
|
|
432
|
+
* issues one on demand, and `promptForRecoveryFile` still accepts one during
|
|
433
|
+
* recovery. Turn this back on if you want users to hold an offline fallback
|
|
434
|
+
* that does not depend on Decane being reachable.
|
|
435
|
+
*/
|
|
414
436
|
offerRecoveryAtSignup?: boolean;
|
|
415
437
|
/** Ask the user whether they want a recovery share and, if so, a password for it. */
|
|
416
438
|
onRecoveryShareOffer?: (addresses: {
|
|
@@ -521,7 +543,16 @@ interface SignTypedDataRequest {
|
|
|
521
543
|
message: Record<string, unknown>;
|
|
522
544
|
primaryType?: string;
|
|
523
545
|
}
|
|
524
|
-
type SdkEvent = 'connected' | 'disconnected' | 'session-expired' | 'session-started' | 'wallet-creating' | 'wallet-unlocking'
|
|
546
|
+
type SdkEvent = 'connected' | 'disconnected' | 'session-expired' | 'session-started' | 'wallet-creating' | 'wallet-unlocking'
|
|
547
|
+
/**
|
|
548
|
+
* Emitted when this wallet could not be enrolled for server-assisted
|
|
549
|
+
* recovery. Not fatal — the wallet works — but until it is enrolled the user
|
|
550
|
+
* cannot get a device share from login alone, so a future new device will
|
|
551
|
+
* need a passkey backup or a recovery file. The SDK retries automatically on
|
|
552
|
+
* later unlocks; surface or report this if you want visibility rather than a
|
|
553
|
+
* console line.
|
|
554
|
+
*/
|
|
555
|
+
| 'enrolment-failed';
|
|
525
556
|
interface DecaneConnect {
|
|
526
557
|
connect(): Promise<ConnectResult>;
|
|
527
558
|
connectWithGoogle(): Promise<ConnectResult>;
|
|
@@ -808,6 +839,20 @@ declare class NetworkError extends DecaneConnectError {
|
|
|
808
839
|
declare class ShareCorruptedError extends DecaneConnectError {
|
|
809
840
|
constructor();
|
|
810
841
|
}
|
|
842
|
+
/**
|
|
843
|
+
* Wallet creation exceeded its time budget.
|
|
844
|
+
*
|
|
845
|
+
* Provisioning is bounded so a stalled network or an unresponsive enclave
|
|
846
|
+
* surfaces as an error the app can act on, rather than a signup that appears to
|
|
847
|
+
* hang forever. The budget covers only the SDK's own work — key generation,
|
|
848
|
+
* share storage, opening the signer session — never time spent waiting on the
|
|
849
|
+
* user (a passkey prompt, choosing a PIN, the recovery-file dialog).
|
|
850
|
+
*
|
|
851
|
+
* Retryable: nothing is left half-created that a later sign-in cannot complete.
|
|
852
|
+
*/
|
|
853
|
+
declare class ProvisioningTimeoutError extends DecaneConnectError {
|
|
854
|
+
constructor(step: string);
|
|
855
|
+
}
|
|
811
856
|
declare class NewDeviceError extends DecaneConnectError {
|
|
812
857
|
constructor();
|
|
813
858
|
}
|
|
@@ -1238,4 +1283,4 @@ declare function useSignTypedData(): {
|
|
|
1238
1283
|
reset: () => void;
|
|
1239
1284
|
};
|
|
1240
1285
|
|
|
1241
|
-
export { type ActiveSession, AttestationError, type AuthProfile, type AuthResult, BUILT_IN_CHAINS, type BalanceResult, type CAIP25Method, type CAIP25SessionRequest, type CAIP25SessionResult, CHAINS, CallSequenceError, type Chain, type ChainDescriptor, type ChainId, type ChainScope, type ChainSupport, type ChainType, type ConnectMode, type ConnectResult, type DecaneConfig, type DecaneConnect, type DecaneConnectConfig, DecaneConnectError, DecaneError, DecaneErrorCode, DecaneKit, DecaneLogo, DecaneMark, type DecaneProvider, DecaneWordmark, type Eip1193Provider, type EthereumProviderOptions, ExportNotAuthorizedError, InvalidApiKeyError, InvalidPinError, InvalidRecoveryFileError, type KingsChatTokens, NetworkError, NewDeviceError, OriginNotAllowedError, type PortableBackup, PromptPinNotConfiguredError, ReconnectRequiredError, type RecordTransactionInput, RecoveryFailedError, type RecoveryProgress, type RecoveryShareFile, RecoveryWalletMismatchError, type SdkEvent, type SendTransactionRequest, SessionExpiredError, ShareCorruptedError, type SignMessageRequest, type SignTypedDataRequest, type SignedAuthorization, type SocialAuthState, type SocialConfig, SocialWalletModal, StaleDeviceShareError, type Theme, type TransactionRecord, type TxParams, type UnifiedWalletAccount, UnlockRequiredError, UserCancelledError, WalletSelector, WebAuthnNotSupportedError, type WrappedShareMeta, WrongPasswordError, createDecane, createDecaneConnect, createEthereumProvider, loginWithKingsChatPopup, startEmailAuth, useChainSwitch, useDecane, useEthereumProvider, useSendTransaction, useSignMessage, useSignTypedData, useSocialAuth, useSocialBalance, useSocialWallet, useWalletSelector, verifyEmailCode };
|
|
1286
|
+
export { type ActiveSession, AttestationError, type AuthProfile, type AuthResult, BUILT_IN_CHAINS, type BalanceResult, type CAIP25Method, type CAIP25SessionRequest, type CAIP25SessionResult, CHAINS, CallSequenceError, type Chain, type ChainDescriptor, type ChainId, type ChainScope, type ChainSupport, type ChainType, type ConnectMode, type ConnectResult, type DecaneConfig, type DecaneConnect, type DecaneConnectConfig, DecaneConnectError, DecaneError, DecaneErrorCode, DecaneKit, DecaneLogo, DecaneMark, type DecaneProvider, DecaneWordmark, type Eip1193Provider, type EthereumProviderOptions, ExportNotAuthorizedError, InvalidApiKeyError, InvalidPinError, InvalidRecoveryFileError, type KingsChatTokens, NetworkError, NewDeviceError, OriginNotAllowedError, type PortableBackup, PromptPinNotConfiguredError, ProvisioningTimeoutError, ReconnectRequiredError, type RecordTransactionInput, RecoveryFailedError, type RecoveryProgress, type RecoveryShareFile, RecoveryWalletMismatchError, type SdkEvent, type SendTransactionRequest, SessionExpiredError, ShareCorruptedError, type SignMessageRequest, type SignTypedDataRequest, type SignedAuthorization, type SocialAuthState, type SocialConfig, SocialWalletModal, StaleDeviceShareError, type Theme, type TransactionRecord, type TxParams, type UnifiedWalletAccount, UnlockRequiredError, UserCancelledError, WalletSelector, WebAuthnNotSupportedError, type WrappedShareMeta, WrongPasswordError, createDecane, createDecaneConnect, createEthereumProvider, loginWithKingsChatPopup, startEmailAuth, useChainSwitch, useDecane, useEthereumProvider, useSendTransaction, useSignMessage, useSignTypedData, useSocialAuth, useSocialBalance, useSocialWallet, useWalletSelector, verifyEmailCode };
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ __export(errors_exports, {
|
|
|
22
22
|
NewDeviceError: () => NewDeviceError,
|
|
23
23
|
OriginNotAllowedError: () => OriginNotAllowedError,
|
|
24
24
|
PromptPinNotConfiguredError: () => PromptPinNotConfiguredError,
|
|
25
|
+
ProvisioningTimeoutError: () => ProvisioningTimeoutError,
|
|
25
26
|
ReconnectRequiredError: () => ReconnectRequiredError,
|
|
26
27
|
RecoveryFailedError: () => RecoveryFailedError,
|
|
27
28
|
RecoveryWalletMismatchError: () => RecoveryWalletMismatchError,
|
|
@@ -33,7 +34,7 @@ __export(errors_exports, {
|
|
|
33
34
|
WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
|
|
34
35
|
WrongPasswordError: () => WrongPasswordError
|
|
35
36
|
});
|
|
36
|
-
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
|
|
37
|
+
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, ProvisioningTimeoutError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
|
|
37
38
|
var init_errors = __esm({
|
|
38
39
|
"src/social/errors.ts"() {
|
|
39
40
|
"use strict";
|
|
@@ -104,6 +105,11 @@ var init_errors = __esm({
|
|
|
104
105
|
super("SHARE_CORRUPTED", "Stored device share is corrupted");
|
|
105
106
|
}
|
|
106
107
|
};
|
|
108
|
+
ProvisioningTimeoutError = class extends DecaneConnectError {
|
|
109
|
+
constructor(step) {
|
|
110
|
+
super("PROVISIONING_TIMEOUT", `Wallet setup timed out while ${step}. Check your connection and try again.`, true);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
107
113
|
NewDeviceError = class extends DecaneConnectError {
|
|
108
114
|
constructor() {
|
|
109
115
|
super("NEW_DEVICE", "No device share found. Re-authenticate to set up this device.");
|
|
@@ -1932,6 +1938,7 @@ var ERROR_MAP = {
|
|
|
1932
1938
|
// generic failure message.
|
|
1933
1939
|
UNAUTHORIZED: ReconnectRequiredError
|
|
1934
1940
|
};
|
|
1941
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
1935
1942
|
var ApiClient = class {
|
|
1936
1943
|
constructor(baseUrl, apiKey, appId) {
|
|
1937
1944
|
this.baseUrl = baseUrl;
|
|
@@ -1940,9 +1947,12 @@ var ApiClient = class {
|
|
|
1940
1947
|
}
|
|
1941
1948
|
async _request(path, init) {
|
|
1942
1949
|
let res;
|
|
1950
|
+
const controller = new AbortController();
|
|
1951
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1943
1952
|
try {
|
|
1944
1953
|
res = await fetch(`${this.baseUrl}${path}`, {
|
|
1945
1954
|
...init,
|
|
1955
|
+
signal: controller.signal,
|
|
1946
1956
|
headers: {
|
|
1947
1957
|
"Content-Type": "application/json",
|
|
1948
1958
|
"X-API-Key": this.apiKey,
|
|
@@ -1952,6 +1962,8 @@ var ApiClient = class {
|
|
|
1952
1962
|
});
|
|
1953
1963
|
} catch {
|
|
1954
1964
|
throw new NetworkError();
|
|
1965
|
+
} finally {
|
|
1966
|
+
clearTimeout(timer);
|
|
1955
1967
|
}
|
|
1956
1968
|
if (res.status === 404 && init?.allow404) return null;
|
|
1957
1969
|
if (!res.ok) {
|
|
@@ -2254,6 +2266,36 @@ function bytesToBase64(b) {
|
|
|
2254
2266
|
function base64ToBytes(s) {
|
|
2255
2267
|
return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
2256
2268
|
}
|
|
2269
|
+
var TEE_TIMEOUT_MS = 3e4;
|
|
2270
|
+
async function teeFetch(url, init) {
|
|
2271
|
+
const controller = new AbortController();
|
|
2272
|
+
const timer = setTimeout(() => controller.abort(), TEE_TIMEOUT_MS);
|
|
2273
|
+
try {
|
|
2274
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2275
|
+
} finally {
|
|
2276
|
+
clearTimeout(timer);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
var ATTESTATION_TTL_MS = 10 * 6e4;
|
|
2280
|
+
var attestationCache = /* @__PURE__ */ new Map();
|
|
2281
|
+
function attestOnce(teeUrl, verify) {
|
|
2282
|
+
const hit = attestationCache.get(teeUrl);
|
|
2283
|
+
if (hit && Date.now() - hit.at < ATTESTATION_TTL_MS) return hit.promise;
|
|
2284
|
+
const promise = verify().catch((e) => {
|
|
2285
|
+
attestationCache.delete(teeUrl);
|
|
2286
|
+
throw e;
|
|
2287
|
+
});
|
|
2288
|
+
attestationCache.set(teeUrl, { at: Date.now(), promise });
|
|
2289
|
+
return promise;
|
|
2290
|
+
}
|
|
2291
|
+
function prewarmAttestation(teeUrl) {
|
|
2292
|
+
void attestOnce(teeUrl, async () => {
|
|
2293
|
+
const nonce = crypto.getRandomValues(new Uint8Array(16));
|
|
2294
|
+
const nonceHex = Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2295
|
+
await verifyTeeAttestation(teeUrl, nonceHex);
|
|
2296
|
+
}).catch(() => {
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2257
2299
|
var TeeClient = class {
|
|
2258
2300
|
constructor(teeUrl) {
|
|
2259
2301
|
this.teeUrl = teeUrl;
|
|
@@ -2295,9 +2337,11 @@ var TeeClient = class {
|
|
|
2295
2337
|
}
|
|
2296
2338
|
async ensureAttested() {
|
|
2297
2339
|
if (this.attested) return;
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2340
|
+
await attestOnce(this.teeUrl, async () => {
|
|
2341
|
+
const nonce = crypto.getRandomValues(new Uint8Array(16));
|
|
2342
|
+
const nonceHex = Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2343
|
+
await verifyTeeAttestation(this.teeUrl, nonceHex);
|
|
2344
|
+
});
|
|
2301
2345
|
this.attested = true;
|
|
2302
2346
|
}
|
|
2303
2347
|
/**
|
|
@@ -2309,7 +2353,7 @@ var TeeClient = class {
|
|
|
2309
2353
|
*/
|
|
2310
2354
|
async openSession(deviceShare, jwt, requireAssertionPerSignature = false) {
|
|
2311
2355
|
await this.ensureAttested();
|
|
2312
|
-
const res = await
|
|
2356
|
+
const res = await teeFetch(`${this.teeUrl}/signer/session`, {
|
|
2313
2357
|
method: "POST",
|
|
2314
2358
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2315
2359
|
body: JSON.stringify({
|
|
@@ -2341,7 +2385,7 @@ var TeeClient = class {
|
|
|
2341
2385
|
*/
|
|
2342
2386
|
async recoverWithShare(jwt, recoveryShare) {
|
|
2343
2387
|
await this.ensureAttested();
|
|
2344
|
-
const res = await
|
|
2388
|
+
const res = await teeFetch(`${this.teeUrl}/signer/recover`, {
|
|
2345
2389
|
method: "POST",
|
|
2346
2390
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2347
2391
|
body: JSON.stringify({ recoveryShare: bytesToBase64(recoveryShare) })
|
|
@@ -2366,7 +2410,7 @@ var TeeClient = class {
|
|
|
2366
2410
|
* require proof of fresh authenticator possession, not just an active session.
|
|
2367
2411
|
*/
|
|
2368
2412
|
async completeAssertion(jwt) {
|
|
2369
|
-
const res = await
|
|
2413
|
+
const res = await teeFetch(`${this.teeUrl}/signer/assertion-challenge`, {
|
|
2370
2414
|
method: "POST",
|
|
2371
2415
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2372
2416
|
body: JSON.stringify({ rpId: window.location.hostname })
|
|
@@ -2395,7 +2439,7 @@ var TeeClient = class {
|
|
|
2395
2439
|
*/
|
|
2396
2440
|
async enrolAuthShare(jwt, recoveryShare) {
|
|
2397
2441
|
await this.ensureAttested();
|
|
2398
|
-
const res = await
|
|
2442
|
+
const res = await teeFetch(`${this.teeUrl}/signer/auth-share`, {
|
|
2399
2443
|
method: "POST",
|
|
2400
2444
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2401
2445
|
body: JSON.stringify({ share: bytesToBase64(recoveryShare) })
|
|
@@ -2418,7 +2462,7 @@ var TeeClient = class {
|
|
|
2418
2462
|
*/
|
|
2419
2463
|
async provisionDeviceShare(jwt) {
|
|
2420
2464
|
await this.ensureAttested();
|
|
2421
|
-
const res = await
|
|
2465
|
+
const res = await teeFetch(`${this.teeUrl}/signer/provision`, {
|
|
2422
2466
|
method: "POST",
|
|
2423
2467
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
|
|
2424
2468
|
});
|
|
@@ -2436,7 +2480,7 @@ var TeeClient = class {
|
|
|
2436
2480
|
async rotateShares(jwt) {
|
|
2437
2481
|
if (!this.handle) throw new Error("no session");
|
|
2438
2482
|
const { challengeId, assertion } = await this.completeAssertion(jwt);
|
|
2439
|
-
const res = await
|
|
2483
|
+
const res = await teeFetch(`${this.teeUrl}/signer/rotate`, {
|
|
2440
2484
|
method: "POST",
|
|
2441
2485
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2442
2486
|
body: JSON.stringify({ handle: this.handle, challengeId, assertion, origin: window.location.origin })
|
|
@@ -2453,7 +2497,7 @@ var TeeClient = class {
|
|
|
2453
2497
|
async exportPortableBackup(jwt) {
|
|
2454
2498
|
if (!this.handle) throw new Error("no session");
|
|
2455
2499
|
const { challengeId, assertion } = await this.completeAssertion(jwt);
|
|
2456
|
-
const res = await
|
|
2500
|
+
const res = await teeFetch(`${this.teeUrl}/signer/export-portable`, {
|
|
2457
2501
|
method: "POST",
|
|
2458
2502
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2459
2503
|
body: JSON.stringify({ handle: this.handle, challengeId, assertion, origin: window.location.origin })
|
|
@@ -2483,7 +2527,7 @@ var TeeClient = class {
|
|
|
2483
2527
|
if (!jwt) throw new Error("sign requires a jwt when per-signature assertion is enabled");
|
|
2484
2528
|
gate = await this.completeAssertion(jwt);
|
|
2485
2529
|
}
|
|
2486
|
-
const res = await
|
|
2530
|
+
const res = await teeFetch(`${this.teeUrl}/signer/sign`, {
|
|
2487
2531
|
method: "POST",
|
|
2488
2532
|
headers: { "Content-Type": "application/json" },
|
|
2489
2533
|
body: JSON.stringify({
|
|
@@ -3424,6 +3468,64 @@ async function createDecaneConnect(config) {
|
|
|
3424
3468
|
let ethProvider = null;
|
|
3425
3469
|
const persistSession = config.persistSession !== false;
|
|
3426
3470
|
const serverAssistedRecovery = true;
|
|
3471
|
+
const RECOVERY_OFFER_TIMEOUT_MS = 5 * 6e4;
|
|
3472
|
+
const selfHealAttempted = /* @__PURE__ */ new Set();
|
|
3473
|
+
async function selfHealEnrolment(userId, jwt, share) {
|
|
3474
|
+
try {
|
|
3475
|
+
if (selfHealAttempted.has(userId)) return;
|
|
3476
|
+
const status = await api.getRecoveryStatus(jwt);
|
|
3477
|
+
if (status.enrolled !== false) return;
|
|
3478
|
+
selfHealAttempted.add(userId);
|
|
3479
|
+
await new TeeClient(teeUrl).enrolAuthShare(jwt, share);
|
|
3480
|
+
} catch (e) {
|
|
3481
|
+
events.emit("enrolment-failed");
|
|
3482
|
+
console.warn(
|
|
3483
|
+
"decane-connect-kit: this wallet is not enrolled for server-assisted recovery and the automatic repair failed. It still works on this device, but a new device will need a passkey backup or a recovery file. The SDK will try again on the next unlock.",
|
|
3484
|
+
e
|
|
3485
|
+
);
|
|
3486
|
+
} finally {
|
|
3487
|
+
zeroize(share);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
async function enrolInBackground(jwt, share) {
|
|
3491
|
+
try {
|
|
3492
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
3493
|
+
try {
|
|
3494
|
+
await new TeeClient(teeUrl).enrolAuthShare(jwt, share);
|
|
3495
|
+
return;
|
|
3496
|
+
} catch (e) {
|
|
3497
|
+
if (attempt === 2) {
|
|
3498
|
+
events.emit("enrolment-failed");
|
|
3499
|
+
console.warn(
|
|
3500
|
+
"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.",
|
|
3501
|
+
e
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
} finally {
|
|
3507
|
+
zeroize(share);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
const PROVISIONING_BUDGET_MS = 9e4;
|
|
3511
|
+
function withDeadline(promise, ms, step) {
|
|
3512
|
+
let timer;
|
|
3513
|
+
return Promise.race([
|
|
3514
|
+
promise,
|
|
3515
|
+
new Promise((_, reject) => {
|
|
3516
|
+
timer = setTimeout(() => reject(new ProvisioningTimeoutError(step)), Math.max(0, ms));
|
|
3517
|
+
})
|
|
3518
|
+
]).finally(() => clearTimeout(timer));
|
|
3519
|
+
}
|
|
3520
|
+
function withTimeout(promise, ms) {
|
|
3521
|
+
let timer;
|
|
3522
|
+
return Promise.race([
|
|
3523
|
+
promise,
|
|
3524
|
+
new Promise((resolve) => {
|
|
3525
|
+
timer = setTimeout(() => resolve(null), ms);
|
|
3526
|
+
})
|
|
3527
|
+
]).finally(() => clearTimeout(timer));
|
|
3528
|
+
}
|
|
3427
3529
|
const SESSION_KEY = sessionPersistKey(config.apiKey);
|
|
3428
3530
|
function persistLiveSession() {
|
|
3429
3531
|
if (!persistSession) return;
|
|
@@ -3535,6 +3637,7 @@ async function createDecaneConnect(config) {
|
|
|
3535
3637
|
}
|
|
3536
3638
|
}
|
|
3537
3639
|
async function firstTimeSetup(userId, jwt) {
|
|
3640
|
+
prewarmAttestation(teeUrl);
|
|
3538
3641
|
let unlockMethod;
|
|
3539
3642
|
let wrappingKey;
|
|
3540
3643
|
let meta = {};
|
|
@@ -3564,6 +3667,8 @@ async function createDecaneConnect(config) {
|
|
|
3564
3667
|
({ wrappingKey, meta } = await pinSetup());
|
|
3565
3668
|
unlockMethod = "pin";
|
|
3566
3669
|
}
|
|
3670
|
+
const provisioningStartedAt = Date.now();
|
|
3671
|
+
const remainingBudget = () => PROVISIONING_BUDGET_MS - (Date.now() - provisioningStartedAt);
|
|
3567
3672
|
const master = generateMaster();
|
|
3568
3673
|
let shareSet;
|
|
3569
3674
|
let evmAddress;
|
|
@@ -3595,7 +3700,7 @@ async function createDecaneConnect(config) {
|
|
|
3595
3700
|
version: 1
|
|
3596
3701
|
});
|
|
3597
3702
|
const serverIv = generateRandomBytes(12);
|
|
3598
|
-
await api.putShare(jwt, {
|
|
3703
|
+
await withDeadline(api.putShare(jwt, {
|
|
3599
3704
|
ciphertext: shareSet.server,
|
|
3600
3705
|
iv: serverIv,
|
|
3601
3706
|
wrappedShareMeta: {
|
|
@@ -3605,7 +3710,7 @@ async function createDecaneConnect(config) {
|
|
|
3605
3710
|
evmAddress,
|
|
3606
3711
|
solanaAddress,
|
|
3607
3712
|
tronAddress
|
|
3608
|
-
});
|
|
3713
|
+
}), remainingBudget(), "storing your wallet");
|
|
3609
3714
|
zeroize(shareSet.server);
|
|
3610
3715
|
const addresses = { evm: evmAddress, solana: solanaAddress, tron: tronAddress };
|
|
3611
3716
|
if (unlockMethod === "passkey" && meta.credentialId && meta.prfSalt) {
|
|
@@ -3621,7 +3726,11 @@ async function createDecaneConnect(config) {
|
|
|
3621
3726
|
console.warn("decane-connect-kit: wrapped share backup failed; user can retry from settings.", e);
|
|
3622
3727
|
}
|
|
3623
3728
|
}
|
|
3624
|
-
const confirmed = await
|
|
3729
|
+
const confirmed = await withDeadline(
|
|
3730
|
+
session.start(shareSet.device, jwt, addresses),
|
|
3731
|
+
remainingBudget(),
|
|
3732
|
+
"opening the signing session"
|
|
3733
|
+
);
|
|
3625
3734
|
zeroize(shareSet.device);
|
|
3626
3735
|
if (confirmed.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
|
|
3627
3736
|
console.warn(
|
|
@@ -3632,42 +3741,49 @@ async function createDecaneConnect(config) {
|
|
|
3632
3741
|
addresses.solana = solanaAddress = confirmed.solana;
|
|
3633
3742
|
addresses.tron = tronAddress = confirmed.tron ?? evmToTronAddress2(confirmed.evm);
|
|
3634
3743
|
if (serverAssistedRecovery) {
|
|
3635
|
-
|
|
3636
|
-
try {
|
|
3637
|
-
const tee = new TeeClient(teeUrl);
|
|
3638
|
-
await tee.enrolAuthShare(jwt, shareSet.recovery);
|
|
3639
|
-
break;
|
|
3640
|
-
} catch (e) {
|
|
3641
|
-
if (attempt === 2) {
|
|
3642
|
-
console.warn(
|
|
3643
|
-
"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.",
|
|
3644
|
-
e
|
|
3645
|
-
);
|
|
3646
|
-
}
|
|
3647
|
-
}
|
|
3648
|
-
}
|
|
3744
|
+
void enrolInBackground(jwt, new Uint8Array(shareSet.recovery));
|
|
3649
3745
|
}
|
|
3650
|
-
if (config.offerRecoveryAtSignup
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
choice.passwordHint
|
|
3660
|
-
);
|
|
3661
|
-
await config.onRecoveryFileReady?.(file, recoveryShareFileName(evmAddress));
|
|
3662
|
-
await api.markRecoveryShareSaved(jwt);
|
|
3663
|
-
}
|
|
3664
|
-
} catch (e) {
|
|
3665
|
-
console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
|
|
3666
|
-
}
|
|
3746
|
+
if (config.offerRecoveryAtSignup === true && config.onRecoveryShareOffer) {
|
|
3747
|
+
void offerRecoveryShare(
|
|
3748
|
+
jwt,
|
|
3749
|
+
new Uint8Array(shareSet.recovery),
|
|
3750
|
+
{ ...addresses },
|
|
3751
|
+
shareSet.version,
|
|
3752
|
+
evmAddress,
|
|
3753
|
+
solanaAddress
|
|
3754
|
+
);
|
|
3667
3755
|
}
|
|
3668
3756
|
zeroize(shareSet.recovery);
|
|
3669
3757
|
return addresses;
|
|
3670
3758
|
}
|
|
3759
|
+
async function offerRecoveryShare(jwt, recoveryShare, addresses, shareSetVersion, evmAddress, solanaAddress) {
|
|
3760
|
+
try {
|
|
3761
|
+
const choice = await withTimeout(
|
|
3762
|
+
config.onRecoveryShareOffer({ evmAddress, solanaAddress }),
|
|
3763
|
+
RECOVERY_OFFER_TIMEOUT_MS
|
|
3764
|
+
);
|
|
3765
|
+
if (!choice) {
|
|
3766
|
+
console.warn(
|
|
3767
|
+
`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.`
|
|
3768
|
+
);
|
|
3769
|
+
return;
|
|
3770
|
+
}
|
|
3771
|
+
if (!choice.wants) return;
|
|
3772
|
+
const file = await buildRecoveryShareFile(
|
|
3773
|
+
recoveryShare,
|
|
3774
|
+
choice.password,
|
|
3775
|
+
addresses,
|
|
3776
|
+
shareSetVersion,
|
|
3777
|
+
choice.passwordHint
|
|
3778
|
+
);
|
|
3779
|
+
await config.onRecoveryFileReady?.(file, recoveryShareFileName(evmAddress));
|
|
3780
|
+
await api.markRecoveryShareSaved(jwt);
|
|
3781
|
+
} catch (e) {
|
|
3782
|
+
console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
|
|
3783
|
+
} finally {
|
|
3784
|
+
zeroize(recoveryShare);
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3671
3787
|
async function returningUserUnlock(userId, jwt) {
|
|
3672
3788
|
const rawAddresses = await api.getAddresses(jwt);
|
|
3673
3789
|
const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
|
|
@@ -3753,6 +3869,7 @@ async function createDecaneConnect(config) {
|
|
|
3753
3869
|
}
|
|
3754
3870
|
throw e;
|
|
3755
3871
|
}
|
|
3872
|
+
void selfHealEnrolment(userId, jwt, new Uint8Array(deviceShare));
|
|
3756
3873
|
zeroize(deviceShare);
|
|
3757
3874
|
if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
|
|
3758
3875
|
console.warn(
|
|
@@ -7204,6 +7321,7 @@ export {
|
|
|
7204
7321
|
NewDeviceError,
|
|
7205
7322
|
OriginNotAllowedError,
|
|
7206
7323
|
PromptPinNotConfiguredError,
|
|
7324
|
+
ProvisioningTimeoutError,
|
|
7207
7325
|
ReconnectRequiredError,
|
|
7208
7326
|
RecoveryFailedError,
|
|
7209
7327
|
RecoveryWalletMismatchError,
|