decane-connect-kit 2.5.0 → 2.6.1
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 +368 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -7
- package/dist/index.d.ts +122 -7
- package/dist/index.js +366 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -27,12 +27,13 @@ __export(errors_exports, {
|
|
|
27
27
|
RecoveryWalletMismatchError: () => RecoveryWalletMismatchError,
|
|
28
28
|
SessionExpiredError: () => SessionExpiredError,
|
|
29
29
|
ShareCorruptedError: () => ShareCorruptedError,
|
|
30
|
+
StaleDeviceShareError: () => StaleDeviceShareError,
|
|
30
31
|
UnlockRequiredError: () => UnlockRequiredError,
|
|
31
32
|
UserCancelledError: () => UserCancelledError,
|
|
32
33
|
WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
|
|
33
34
|
WrongPasswordError: () => WrongPasswordError
|
|
34
35
|
});
|
|
35
|
-
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
|
|
36
|
+
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
|
|
36
37
|
var init_errors = __esm({
|
|
37
38
|
"src/social/errors.ts"() {
|
|
38
39
|
"use strict";
|
|
@@ -108,6 +109,15 @@ var init_errors = __esm({
|
|
|
108
109
|
super("NEW_DEVICE", "No device share found. Re-authenticate to set up this device.");
|
|
109
110
|
}
|
|
110
111
|
};
|
|
112
|
+
StaleDeviceShareError = class extends DecaneConnectError {
|
|
113
|
+
constructor() {
|
|
114
|
+
super(
|
|
115
|
+
"STALE_DEVICE_SHARE",
|
|
116
|
+
"This device\u2019s wallet share is from a superseded share set (likely rotated from another device) and no longer matches this account. Reconnect to recover a fresh share.",
|
|
117
|
+
true
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
111
121
|
PromptPinNotConfiguredError = class extends DecaneConnectError {
|
|
112
122
|
constructor() {
|
|
113
123
|
super("PROMPT_PIN_NOT_CONFIGURED", "Passkey unavailable and no promptPin callback was provided in config");
|
|
@@ -1957,6 +1967,32 @@ var ApiClient = class {
|
|
|
1957
1967
|
async getGoogleInitUrl() {
|
|
1958
1968
|
return this._request("/auth/google/init", { method: "GET" });
|
|
1959
1969
|
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Exchange a Google ID token the app already holds for a Decane session.
|
|
1972
|
+
* Resolves to the SAME identity as the redirect flow, so one Google account
|
|
1973
|
+
* means one wallet across web and mobile.
|
|
1974
|
+
*/
|
|
1975
|
+
async authGoogleToken(idToken) {
|
|
1976
|
+
return this._request("/auth/google/token", {
|
|
1977
|
+
method: "POST",
|
|
1978
|
+
body: JSON.stringify({ idToken })
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
/** The project's KingsChat clientId, so the dApp never has to hardcode it. */
|
|
1982
|
+
async getKingsChatConfig() {
|
|
1983
|
+
return this._request("/auth/kingschat/config", { method: "GET" });
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Exchange a KingsChat access token for a Decane session. The backend spends
|
|
1987
|
+
* the token against KingsChat's own API to establish the identity — it is
|
|
1988
|
+
* never trusted on its face, and never stored.
|
|
1989
|
+
*/
|
|
1990
|
+
async authKingsChat(accessToken) {
|
|
1991
|
+
return this._request("/auth/kingschat", {
|
|
1992
|
+
method: "POST",
|
|
1993
|
+
body: JSON.stringify({ accessToken })
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1960
1996
|
async authEmailStart(email) {
|
|
1961
1997
|
await this._request("/auth/email/start", {
|
|
1962
1998
|
method: "POST",
|
|
@@ -2281,6 +2317,13 @@ var TeeClient = class {
|
|
|
2281
2317
|
requireAssertionPerSignature
|
|
2282
2318
|
})
|
|
2283
2319
|
});
|
|
2320
|
+
if (res.status === 403) {
|
|
2321
|
+
const body2 = await res.json().catch(() => ({}));
|
|
2322
|
+
if (body2?.error === "share_mismatch") {
|
|
2323
|
+
const { StaleDeviceShareError: StaleDeviceShareError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
2324
|
+
throw new StaleDeviceShareError2();
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2284
2327
|
if (!res.ok) throw new Error("session open failed");
|
|
2285
2328
|
const body = await res.json();
|
|
2286
2329
|
this.handle = body.handle;
|
|
@@ -2340,6 +2383,45 @@ var TeeClient = class {
|
|
|
2340
2383
|
}
|
|
2341
2384
|
return { challengeId, assertion };
|
|
2342
2385
|
}
|
|
2386
|
+
/**
|
|
2387
|
+
* Enrol this wallet for server-assisted provisioning by handing the enclave
|
|
2388
|
+
* the recovery share to seal.
|
|
2389
|
+
*
|
|
2390
|
+
* After this, a new device gets a working share from login alone — no passkey
|
|
2391
|
+
* sync, no recovery file. The enclave seals the share under a key only it can
|
|
2392
|
+
* derive, so the backend stores ciphertext it cannot read; but the enclave
|
|
2393
|
+
* then holds two of three shares and can reconstruct on its own. That is the
|
|
2394
|
+
* trade this call makes, which is why it is opt-out rather than implicit.
|
|
2395
|
+
*/
|
|
2396
|
+
async enrolAuthShare(jwt, recoveryShare) {
|
|
2397
|
+
await this.ensureAttested();
|
|
2398
|
+
const res = await fetch(`${this.teeUrl}/signer/auth-share`, {
|
|
2399
|
+
method: "POST",
|
|
2400
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2401
|
+
body: JSON.stringify({ share: bytesToBase64(recoveryShare) })
|
|
2402
|
+
});
|
|
2403
|
+
if (!res.ok) throw new Error("auth share enrolment failed");
|
|
2404
|
+
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Ask the enclave for a fresh device share for this authenticated user.
|
|
2407
|
+
* Returns null when the wallet was never enrolled — the signal to fall back
|
|
2408
|
+
* to passkey sync or the recovery file rather than fail.
|
|
2409
|
+
*
|
|
2410
|
+
* Every successful call re-splits the set, so any share this device or any
|
|
2411
|
+
* other held is now dead. Intentional: a leaked device share stops working the
|
|
2412
|
+
* next time the user signs in anywhere.
|
|
2413
|
+
*/
|
|
2414
|
+
async provisionDeviceShare(jwt) {
|
|
2415
|
+
await this.ensureAttested();
|
|
2416
|
+
const res = await fetch(`${this.teeUrl}/signer/provision`, {
|
|
2417
|
+
method: "POST",
|
|
2418
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
|
|
2419
|
+
});
|
|
2420
|
+
if (res.status === 409) return null;
|
|
2421
|
+
if (!res.ok) throw new Error("device share provisioning failed");
|
|
2422
|
+
const body = await res.json();
|
|
2423
|
+
return base64ToBytes(body.deviceShare);
|
|
2424
|
+
}
|
|
2343
2425
|
/** Voluntary rotation — e.g. after a suspected device compromise, or to invalidate an old recovery file. */
|
|
2344
2426
|
async rotateShares(jwt) {
|
|
2345
2427
|
if (!this.handle) throw new Error("no session");
|
|
@@ -2726,6 +2808,79 @@ async function verifyEmailCode(api, email, code) {
|
|
|
2726
2808
|
return api.authEmailVerify(email, code);
|
|
2727
2809
|
}
|
|
2728
2810
|
|
|
2811
|
+
// src/social/auth/kingschat.ts
|
|
2812
|
+
init_errors();
|
|
2813
|
+
var KINGSCHAT_AUTH_ORIGIN = "https://accounts.kingsch.at";
|
|
2814
|
+
function readTokens(data) {
|
|
2815
|
+
if (!data || typeof data !== "object") return null;
|
|
2816
|
+
const d = data;
|
|
2817
|
+
const accessToken = d.accessToken ?? d.access_token;
|
|
2818
|
+
if (typeof accessToken !== "string" || !accessToken) return null;
|
|
2819
|
+
const refreshToken = d.refreshToken ?? d.refresh_token;
|
|
2820
|
+
const expires = d.expiresInMillis ?? d.expires_in_millis;
|
|
2821
|
+
return {
|
|
2822
|
+
accessToken,
|
|
2823
|
+
refreshToken: typeof refreshToken === "string" ? refreshToken : void 0,
|
|
2824
|
+
expiresInMillis: typeof expires === "number" ? expires : void 0
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
function buildAuthUrl(clientId, scopes) {
|
|
2828
|
+
const url = new URL(KINGSCHAT_AUTH_ORIGIN);
|
|
2829
|
+
url.searchParams.set("client_id", clientId);
|
|
2830
|
+
url.searchParams.set("scopes", JSON.stringify(scopes));
|
|
2831
|
+
url.searchParams.set("redirect_uri", window.location.origin);
|
|
2832
|
+
url.searchParams.set("post_message", "1");
|
|
2833
|
+
return url.toString();
|
|
2834
|
+
}
|
|
2835
|
+
function loginWithKingsChatPopup(opts) {
|
|
2836
|
+
const scopes = opts.scopes ?? ["send_chat_message"];
|
|
2837
|
+
const popup = window.open(
|
|
2838
|
+
buildAuthUrl(opts.clientId, scopes),
|
|
2839
|
+
"decane-kingschat",
|
|
2840
|
+
"width=480,height=720,menubar=no,toolbar=no"
|
|
2841
|
+
);
|
|
2842
|
+
if (!popup) {
|
|
2843
|
+
throw new CallSequenceError(
|
|
2844
|
+
"The KingsChat sign-in popup was blocked. Call connectWithKingsChat() directly from a click handler."
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
return new Promise((resolve, reject) => {
|
|
2848
|
+
let settled = false;
|
|
2849
|
+
const finish = (fn) => {
|
|
2850
|
+
if (settled) return;
|
|
2851
|
+
settled = true;
|
|
2852
|
+
window.removeEventListener("message", onMessage);
|
|
2853
|
+
clearInterval(closedPoll);
|
|
2854
|
+
clearTimeout(timeout);
|
|
2855
|
+
try {
|
|
2856
|
+
popup.close();
|
|
2857
|
+
} catch {
|
|
2858
|
+
}
|
|
2859
|
+
fn();
|
|
2860
|
+
};
|
|
2861
|
+
const onMessage = (event) => {
|
|
2862
|
+
if (event.origin !== KINGSCHAT_AUTH_ORIGIN) return;
|
|
2863
|
+
const data = event.data;
|
|
2864
|
+
if (data && typeof data === "object" && data.error) {
|
|
2865
|
+
finish(() => reject(new CallSequenceError(`KingsChat sign-in failed: ${String(data.error)}`)));
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
const tokens2 = readTokens(event.data);
|
|
2869
|
+
if (tokens2) finish(() => resolve(tokens2));
|
|
2870
|
+
};
|
|
2871
|
+
window.addEventListener("message", onMessage);
|
|
2872
|
+
const closedPoll = setInterval(() => {
|
|
2873
|
+
if (popup.closed) finish(() => reject(new UserCancelledError()));
|
|
2874
|
+
}, 400);
|
|
2875
|
+
const timeout = setTimeout(
|
|
2876
|
+
() => finish(
|
|
2877
|
+
() => reject(new CallSequenceError("KingsChat sign-in timed out before returning a token."))
|
|
2878
|
+
),
|
|
2879
|
+
opts.timeoutMs ?? 5 * 6e4
|
|
2880
|
+
);
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2729
2884
|
// src/social/signers/evm.ts
|
|
2730
2885
|
import { keccak256, Transaction, resolveProperties } from "ethers";
|
|
2731
2886
|
async function signEvmTransaction(tee, chain, tx, jwt) {
|
|
@@ -3088,7 +3243,7 @@ async function recoverDeviceShare(input) {
|
|
|
3088
3243
|
prfSalt: backup.prfSalt,
|
|
3089
3244
|
version: 1
|
|
3090
3245
|
});
|
|
3091
|
-
return deviceShare;
|
|
3246
|
+
return { deviceShare, flow: "passkey-sync" };
|
|
3092
3247
|
} catch (e) {
|
|
3093
3248
|
input.onProgress?.({
|
|
3094
3249
|
type: "passkey-failed",
|
|
@@ -3116,7 +3271,7 @@ async function recoverDeviceShare(input) {
|
|
|
3116
3271
|
input.onProgress?.({ type: "rotated", shareSetVersion: result.shareSetVersion });
|
|
3117
3272
|
await input.onNewRecoveryShare?.(result.recoveryShare, result.shareSetVersion);
|
|
3118
3273
|
zeroize(result.recoveryShare);
|
|
3119
|
-
return result.deviceShare;
|
|
3274
|
+
return { deviceShare: result.deviceShare, flow: "recovery-file" };
|
|
3120
3275
|
}
|
|
3121
3276
|
|
|
3122
3277
|
// src/social/recovery/portableBackup.ts
|
|
@@ -3234,6 +3389,11 @@ async function createDecaneConnect(config) {
|
|
|
3234
3389
|
if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
|
|
3235
3390
|
if (!config.chains?.length)
|
|
3236
3391
|
throw new Error("decane-connect-kit: at least one chain is required");
|
|
3392
|
+
if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
|
|
3393
|
+
throw new Error(
|
|
3394
|
+
"decane-connect-kit: rotateOnNewDeviceRecovery requires an onRecoveryRotated callback \u2014 rotating on device join invalidates the user's existing recovery file, and the replacement must be saved."
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3237
3397
|
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE;
|
|
3238
3398
|
const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
|
|
3239
3399
|
const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
|
|
@@ -3253,6 +3413,7 @@ async function createDecaneConnect(config) {
|
|
|
3253
3413
|
let persisted = loadPersisted(PERSIST_KEY);
|
|
3254
3414
|
let ethProvider = null;
|
|
3255
3415
|
const persistSession = config.persistSession !== false;
|
|
3416
|
+
const serverAssistedRecovery = true;
|
|
3256
3417
|
const SESSION_KEY = sessionPersistKey(config.apiKey);
|
|
3257
3418
|
function persistLiveSession() {
|
|
3258
3419
|
if (!persistSession) return;
|
|
@@ -3478,6 +3639,17 @@ async function createDecaneConnect(config) {
|
|
|
3478
3639
|
console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
|
|
3479
3640
|
}
|
|
3480
3641
|
}
|
|
3642
|
+
if (serverAssistedRecovery) {
|
|
3643
|
+
try {
|
|
3644
|
+
const tee = new TeeClient(teeUrl);
|
|
3645
|
+
await tee.enrolAuthShare(jwt, shareSet.recovery);
|
|
3646
|
+
} catch (e) {
|
|
3647
|
+
console.warn(
|
|
3648
|
+
"decane-connect-kit: could not enrol this wallet for server-assisted recovery; a recovery file will be required on a new device.",
|
|
3649
|
+
e
|
|
3650
|
+
);
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3481
3653
|
zeroize(shareSet.recovery);
|
|
3482
3654
|
return addresses;
|
|
3483
3655
|
}
|
|
@@ -3486,13 +3658,36 @@ async function createDecaneConnect(config) {
|
|
|
3486
3658
|
const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
|
|
3487
3659
|
const stored = await storage.getShare(userId);
|
|
3488
3660
|
let deviceShare;
|
|
3661
|
+
let joinedViaPasskeySync = false;
|
|
3489
3662
|
if (!stored) {
|
|
3663
|
+
if (serverAssistedRecovery) {
|
|
3664
|
+
try {
|
|
3665
|
+
const tee = new TeeClient(teeUrl);
|
|
3666
|
+
const provisioned = await tee.provisionDeviceShare(jwt);
|
|
3667
|
+
if (provisioned) {
|
|
3668
|
+
await registerPasskeyAndBackup(userId, jwt, provisioned).catch(() => {
|
|
3669
|
+
});
|
|
3670
|
+
let addrs;
|
|
3671
|
+
try {
|
|
3672
|
+
addrs = await session.start(provisioned, jwt, recordedAddresses);
|
|
3673
|
+
} finally {
|
|
3674
|
+
zeroize(provisioned);
|
|
3675
|
+
}
|
|
3676
|
+
return addrs;
|
|
3677
|
+
}
|
|
3678
|
+
} catch (e) {
|
|
3679
|
+
console.warn(
|
|
3680
|
+
"decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
|
|
3681
|
+
e
|
|
3682
|
+
);
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3490
3685
|
if (!config.onRecoveryRotated) {
|
|
3491
3686
|
throw new CallSequenceError(
|
|
3492
3687
|
"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."
|
|
3493
3688
|
);
|
|
3494
3689
|
}
|
|
3495
|
-
|
|
3690
|
+
const recovered = await recoverDeviceShare({
|
|
3496
3691
|
userId,
|
|
3497
3692
|
jwt,
|
|
3498
3693
|
rpId,
|
|
@@ -3510,6 +3705,8 @@ async function createDecaneConnect(config) {
|
|
|
3510
3705
|
await api.markRecoveryShareSaved(jwt);
|
|
3511
3706
|
}
|
|
3512
3707
|
});
|
|
3708
|
+
deviceShare = recovered.deviceShare;
|
|
3709
|
+
joinedViaPasskeySync = recovered.flow === "passkey-sync";
|
|
3513
3710
|
if (!await storage.getShare(userId) && isPasskeySupported()) {
|
|
3514
3711
|
await registerPasskeyAndBackup(userId, jwt, deviceShare);
|
|
3515
3712
|
}
|
|
@@ -3525,15 +3722,66 @@ async function createDecaneConnect(config) {
|
|
|
3525
3722
|
throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
|
|
3526
3723
|
}
|
|
3527
3724
|
}
|
|
3528
|
-
|
|
3725
|
+
let addresses;
|
|
3726
|
+
try {
|
|
3727
|
+
addresses = await session.start(deviceShare, jwt, recordedAddresses);
|
|
3728
|
+
} catch (e) {
|
|
3729
|
+
if (e instanceof StaleDeviceShareError) {
|
|
3730
|
+
zeroize(deviceShare);
|
|
3731
|
+
await storage.deleteShare(userId);
|
|
3732
|
+
if (stored) return returningUserUnlock(userId, jwt);
|
|
3733
|
+
} else {
|
|
3734
|
+
zeroize(deviceShare);
|
|
3735
|
+
}
|
|
3736
|
+
throw e;
|
|
3737
|
+
}
|
|
3529
3738
|
zeroize(deviceShare);
|
|
3530
3739
|
if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
|
|
3531
3740
|
console.warn(
|
|
3532
3741
|
`decane-connect-kit: recorded EVM address (${recordedAddresses.evm}) does not match the signing key (${addresses.evm}). Using the signing key's address; the record has been corrected.`
|
|
3533
3742
|
);
|
|
3534
3743
|
}
|
|
3744
|
+
if (joinedViaPasskeySync && config.rotateOnNewDeviceRecovery) {
|
|
3745
|
+
await rotateForNewDevice(userId, jwt);
|
|
3746
|
+
}
|
|
3535
3747
|
return addresses;
|
|
3536
3748
|
}
|
|
3749
|
+
async function rotateForNewDevice(userId, jwt) {
|
|
3750
|
+
const tee = session.requireActive();
|
|
3751
|
+
const result = await tee.rotateShares(jwt);
|
|
3752
|
+
try {
|
|
3753
|
+
await persistRotatedDeviceShare(userId, result.deviceShare);
|
|
3754
|
+
const refreshed = await storage.getShare(userId);
|
|
3755
|
+
if (refreshed?.unlockMethod === "passkey" && refreshed.credentialId && refreshed.prfSalt) {
|
|
3756
|
+
await api.putRecoveryBackup(jwt, {
|
|
3757
|
+
wrappedDeviceShare: refreshed.wrappedDeviceShare,
|
|
3758
|
+
iv: refreshed.iv,
|
|
3759
|
+
unlockMethod: "passkey",
|
|
3760
|
+
credentialId: refreshed.credentialId,
|
|
3761
|
+
prfSalt: refreshed.prfSalt
|
|
3762
|
+
}).catch((e) => {
|
|
3763
|
+
console.warn("decane-connect-kit: backup refresh after device rotation failed; the next new device will need the recovery file.", e);
|
|
3764
|
+
});
|
|
3765
|
+
}
|
|
3766
|
+
const addrs = await deriveAddressesFromShares(result.deviceShare, result.recoveryShare);
|
|
3767
|
+
const { password, passwordHint } = await config.onRecoveryRotated({
|
|
3768
|
+
evmAddress: addrs.evm,
|
|
3769
|
+
solanaAddress: addrs.solana
|
|
3770
|
+
});
|
|
3771
|
+
const file = await buildRecoveryShareFile(
|
|
3772
|
+
result.recoveryShare,
|
|
3773
|
+
password,
|
|
3774
|
+
addrs,
|
|
3775
|
+
result.shareSetVersion,
|
|
3776
|
+
passwordHint
|
|
3777
|
+
);
|
|
3778
|
+
await config.onRecoveryFileReady?.(file, recoveryShareFileName(addrs.evm));
|
|
3779
|
+
await api.markRecoveryShareSaved(jwt);
|
|
3780
|
+
} finally {
|
|
3781
|
+
zeroize(result.deviceShare);
|
|
3782
|
+
zeroize(result.recoveryShare);
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3537
3785
|
async function passkeyLoginCandidate() {
|
|
3538
3786
|
if (!persisted?.userId || !isPasskeySupported()) return null;
|
|
3539
3787
|
const stored = await storage.getShare(persisted.userId);
|
|
@@ -3608,6 +3856,7 @@ async function createDecaneConnect(config) {
|
|
|
3608
3856
|
const method = config.authMethods[0];
|
|
3609
3857
|
if (!method) throw new CallSequenceError("No authMethods configured");
|
|
3610
3858
|
if (method === "google") return client.connectWithGoogle();
|
|
3859
|
+
if (method === "kingschat") return client.connectWithKingsChat();
|
|
3611
3860
|
if (method === "email")
|
|
3612
3861
|
throw new CallSequenceError(
|
|
3613
3862
|
"Use connectWithEmail(email) for email auth"
|
|
@@ -3637,6 +3886,25 @@ async function createDecaneConnect(config) {
|
|
|
3637
3886
|
return new Promise(() => {
|
|
3638
3887
|
});
|
|
3639
3888
|
},
|
|
3889
|
+
async connectWithGoogleToken(idToken) {
|
|
3890
|
+
if (!idToken) throw new CallSequenceError("connectWithGoogleToken requires an ID token");
|
|
3891
|
+
return handleAuthResult(await api.authGoogleToken(idToken));
|
|
3892
|
+
},
|
|
3893
|
+
async connectWithKingsChat() {
|
|
3894
|
+
const { clientId } = await api.getKingsChatConfig();
|
|
3895
|
+
const tokens2 = await loginWithKingsChatPopup({
|
|
3896
|
+
clientId,
|
|
3897
|
+
scopes: config.kingsChatScopes
|
|
3898
|
+
});
|
|
3899
|
+
return client.connectWithKingsChatToken(tokens2.accessToken);
|
|
3900
|
+
},
|
|
3901
|
+
async connectWithKingsChatToken(accessToken) {
|
|
3902
|
+
if (!accessToken) {
|
|
3903
|
+
throw new CallSequenceError("connectWithKingsChatToken requires an access token");
|
|
3904
|
+
}
|
|
3905
|
+
const result = await api.authKingsChat(accessToken);
|
|
3906
|
+
return handleAuthResult(result);
|
|
3907
|
+
},
|
|
3640
3908
|
async connectWithToken(token, opts) {
|
|
3641
3909
|
if (!token) throw new CallSequenceError("connectWithToken requires a token");
|
|
3642
3910
|
const result = await api.authExternal(token, opts?.providerId);
|
|
@@ -4409,6 +4677,7 @@ function WalletSelector({
|
|
|
4409
4677
|
mode = "wallets",
|
|
4410
4678
|
socialAuthMethods = ["google", "email"],
|
|
4411
4679
|
onGoogleSignIn,
|
|
4680
|
+
onKingsChatSignIn,
|
|
4412
4681
|
onEmailStart,
|
|
4413
4682
|
onEmailVerify,
|
|
4414
4683
|
customAuthLabel,
|
|
@@ -4486,6 +4755,18 @@ function WalletSelector({
|
|
|
4486
4755
|
setSocialLoad(false);
|
|
4487
4756
|
}
|
|
4488
4757
|
}
|
|
4758
|
+
async function handleKingsChatClick() {
|
|
4759
|
+
if (!onKingsChatSignIn) return;
|
|
4760
|
+
setSocialLoad(true);
|
|
4761
|
+
setSocialErr("");
|
|
4762
|
+
try {
|
|
4763
|
+
await onKingsChatSignIn();
|
|
4764
|
+
} catch (e) {
|
|
4765
|
+
setSocialErr(e.message);
|
|
4766
|
+
} finally {
|
|
4767
|
+
setSocialLoad(false);
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4489
4770
|
async function handleCustomAuthClick() {
|
|
4490
4771
|
if (!onCustomAuthSignIn) return;
|
|
4491
4772
|
setSocialLoad(true);
|
|
@@ -4908,6 +5189,38 @@ function WalletSelector({
|
|
|
4908
5189
|
},
|
|
4909
5190
|
/* @__PURE__ */ React2.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none" }, /* @__PURE__ */ React2.createElement("path", { d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.875 2.684-6.615z", fill: "#4285F4" }), /* @__PURE__ */ React2.createElement("path", { d: "M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z", fill: "#34A853" }), /* @__PURE__ */ React2.createElement("path", { d: "M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z", fill: "#FBBC05" }), /* @__PURE__ */ React2.createElement("path", { d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 6.29C4.672 4.163 6.656 3.58 9 3.58z", fill: "#EA4335" })),
|
|
4910
5191
|
socialLoad ? "Redirecting\u2026" : "Continue with Google"
|
|
5192
|
+
), !passkeyFirst && socialAuthMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ React2.createElement(
|
|
5193
|
+
"button",
|
|
5194
|
+
{
|
|
5195
|
+
onClick: handleKingsChatClick,
|
|
5196
|
+
disabled: socialLoad,
|
|
5197
|
+
style: {
|
|
5198
|
+
display: "flex",
|
|
5199
|
+
alignItems: "center",
|
|
5200
|
+
justifyContent: "center",
|
|
5201
|
+
gap: 10,
|
|
5202
|
+
width: "100%",
|
|
5203
|
+
padding: "11px 16px",
|
|
5204
|
+
background: "var(--dc-surface)",
|
|
5205
|
+
border: "1px solid var(--dc-border)",
|
|
5206
|
+
borderRadius: 10,
|
|
5207
|
+
cursor: socialLoad ? "not-allowed" : "pointer",
|
|
5208
|
+
fontSize: 14,
|
|
5209
|
+
fontWeight: 600,
|
|
5210
|
+
color: "var(--dc-text)",
|
|
5211
|
+
fontFamily: "inherit",
|
|
5212
|
+
transition: "border-color 0.12s, background 0.12s",
|
|
5213
|
+
opacity: socialLoad ? 0.6 : 1
|
|
5214
|
+
},
|
|
5215
|
+
onMouseEnter: (e) => {
|
|
5216
|
+
if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
|
|
5217
|
+
},
|
|
5218
|
+
onMouseLeave: (e) => {
|
|
5219
|
+
e.currentTarget.style.borderColor = "var(--dc-border)";
|
|
5220
|
+
}
|
|
5221
|
+
},
|
|
5222
|
+
/* @__PURE__ */ React2.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React2.createElement("circle", { cx: "9", cy: "9", r: "9", fill: "var(--dc-accent)" }), /* @__PURE__ */ React2.createElement("path", { d: "M5.5 4.5h1.9v3.6l3.1-3.6h2.3l-3.3 3.8 3.5 5.2h-2.3l-2.5-3.8-.8.9v2.9H5.5V4.5z", fill: "var(--dc-accent-text)" })),
|
|
5223
|
+
socialLoad ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
|
|
4911
5224
|
), !passkeyFirst && socialAuthMethods.includes("google") && socialAuthMethods.includes("email") && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React2.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } }), /* @__PURE__ */ React2.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", whiteSpace: "nowrap" } }, "or continue with email"), /* @__PURE__ */ React2.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), !passkeyFirst && socialAuthMethods.includes("email") && (emailStep === "input" ? /* @__PURE__ */ React2.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React2.createElement(
|
|
4912
5225
|
"input",
|
|
4913
5226
|
{
|
|
@@ -5154,6 +5467,7 @@ function AddressRow({
|
|
|
5154
5467
|
}
|
|
5155
5468
|
function SignInView({
|
|
5156
5469
|
onGoogleSignIn,
|
|
5470
|
+
onKingsChatSignIn,
|
|
5157
5471
|
onEmailStart,
|
|
5158
5472
|
onEmailVerify,
|
|
5159
5473
|
authMethods,
|
|
@@ -5165,6 +5479,7 @@ function SignInView({
|
|
|
5165
5479
|
onPasskeySignIn
|
|
5166
5480
|
}) {
|
|
5167
5481
|
const [googleLoading, setGoogleLoading] = useState2(false);
|
|
5482
|
+
const [kingsChatLoading, setKingsChatLoading] = useState2(false);
|
|
5168
5483
|
const [email, setEmail] = useState2("");
|
|
5169
5484
|
const [code, setCode] = useState2("");
|
|
5170
5485
|
const [emailSent, setEmailSent] = useState2(false);
|
|
@@ -5199,6 +5514,18 @@ function SignInView({
|
|
|
5199
5514
|
setGoogleLoading(false);
|
|
5200
5515
|
}
|
|
5201
5516
|
}
|
|
5517
|
+
async function handleKingsChat() {
|
|
5518
|
+
if (!onKingsChatSignIn) return;
|
|
5519
|
+
setError("");
|
|
5520
|
+
setKingsChatLoading(true);
|
|
5521
|
+
try {
|
|
5522
|
+
await onKingsChatSignIn();
|
|
5523
|
+
} catch (e) {
|
|
5524
|
+
setError(e.message);
|
|
5525
|
+
} finally {
|
|
5526
|
+
setKingsChatLoading(false);
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5202
5529
|
async function handleEmailStart(e) {
|
|
5203
5530
|
e.preventDefault();
|
|
5204
5531
|
setError("");
|
|
@@ -5309,6 +5636,27 @@ function SignInView({
|
|
|
5309
5636
|
}
|
|
5310
5637
|
},
|
|
5311
5638
|
googleLoading ? "Redirecting\u2026" : "Continue with Google"
|
|
5639
|
+
), !passkeyFirst && authMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ React3.createElement(
|
|
5640
|
+
"button",
|
|
5641
|
+
{
|
|
5642
|
+
onClick: handleKingsChat,
|
|
5643
|
+
disabled: kingsChatLoading,
|
|
5644
|
+
style: {
|
|
5645
|
+
width: "100%",
|
|
5646
|
+
padding: "10px 0",
|
|
5647
|
+
marginBottom: 10,
|
|
5648
|
+
background: "var(--dc-surface)",
|
|
5649
|
+
color: "var(--dc-text)",
|
|
5650
|
+
border: "1px solid var(--dc-border)",
|
|
5651
|
+
borderRadius: 10,
|
|
5652
|
+
fontSize: 13,
|
|
5653
|
+
fontWeight: 700,
|
|
5654
|
+
cursor: kingsChatLoading ? "not-allowed" : "pointer",
|
|
5655
|
+
opacity: kingsChatLoading ? 0.7 : 1,
|
|
5656
|
+
fontFamily: "inherit"
|
|
5657
|
+
}
|
|
5658
|
+
},
|
|
5659
|
+
kingsChatLoading ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
|
|
5312
5660
|
), !passkeyFirst && authMethods.includes("email") && /* @__PURE__ */ React3.createElement(React3.Fragment, null, authMethods.includes("google") && /* @__PURE__ */ React3.createElement("div", { style: { textAlign: "center", color: "var(--dc-text-muted)", fontSize: 12, margin: "10px 0" } }, "\u2014 or \u2014"), !emailSent ? /* @__PURE__ */ React3.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", gap: 8 } }, /* @__PURE__ */ React3.createElement(
|
|
5313
5661
|
"input",
|
|
5314
5662
|
{
|
|
@@ -5482,6 +5830,7 @@ function SocialWalletModal({
|
|
|
5482
5830
|
onUnlock,
|
|
5483
5831
|
onDisconnect,
|
|
5484
5832
|
onGoogleSignIn,
|
|
5833
|
+
onKingsChatSignIn,
|
|
5485
5834
|
onEmailStart,
|
|
5486
5835
|
onEmailVerify,
|
|
5487
5836
|
authMethods = ["google", "email"],
|
|
@@ -5615,6 +5964,7 @@ function SocialWalletModal({
|
|
|
5615
5964
|
SignInView,
|
|
5616
5965
|
{
|
|
5617
5966
|
onGoogleSignIn,
|
|
5967
|
+
onKingsChatSignIn,
|
|
5618
5968
|
onEmailStart,
|
|
5619
5969
|
onEmailVerify,
|
|
5620
5970
|
authMethods,
|
|
@@ -5943,6 +6293,12 @@ function DecaneKit({ config, children }) {
|
|
|
5943
6293
|
if (!sdk) throw new Error("Social sign-in not configured");
|
|
5944
6294
|
await sdk.connectWithGoogle();
|
|
5945
6295
|
}, []);
|
|
6296
|
+
const connectWithKingsChat = useCallback3(async () => {
|
|
6297
|
+
const sdk = socialRef.current;
|
|
6298
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
6299
|
+
const result = await sdk.connectWithKingsChat();
|
|
6300
|
+
applyResult(result);
|
|
6301
|
+
}, [applyResult]);
|
|
5946
6302
|
const connectWithEmail = useCallback3(async (email) => {
|
|
5947
6303
|
const sdk = socialRef.current;
|
|
5948
6304
|
if (!sdk) throw new Error("Social sign-in not configured");
|
|
@@ -6109,6 +6465,7 @@ function DecaneKit({ config, children }) {
|
|
|
6109
6465
|
socialAuth,
|
|
6110
6466
|
signInWithPasskey,
|
|
6111
6467
|
connectWithGoogle,
|
|
6468
|
+
connectWithKingsChat,
|
|
6112
6469
|
connectWithEmail,
|
|
6113
6470
|
verifyEmailCode: verifyEmailCode2,
|
|
6114
6471
|
connectWithToken,
|
|
@@ -6151,6 +6508,7 @@ function DecaneKit({ config, children }) {
|
|
|
6151
6508
|
mode,
|
|
6152
6509
|
socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
|
|
6153
6510
|
onGoogleSignIn: connectWithGoogle,
|
|
6511
|
+
onKingsChatSignIn: connectWithKingsChat,
|
|
6154
6512
|
onEmailStart: connectWithEmail,
|
|
6155
6513
|
onEmailVerify: verifyEmailCode2,
|
|
6156
6514
|
canUsePasskey: socialAuth.canUsePasskey,
|
|
@@ -6172,6 +6530,7 @@ function DecaneKit({ config, children }) {
|
|
|
6172
6530
|
customAuthLabel: customAuthConfig?.label,
|
|
6173
6531
|
onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
|
|
6174
6532
|
onGoogleSignIn: connectWithGoogle,
|
|
6533
|
+
onKingsChatSignIn: connectWithKingsChat,
|
|
6175
6534
|
onEmailStart: connectWithEmail,
|
|
6176
6535
|
onEmailVerify: verifyEmailCode2,
|
|
6177
6536
|
authMethods: config?.social?.authMethods ?? ["google", "email"],
|
|
@@ -6815,6 +7174,7 @@ export {
|
|
|
6815
7174
|
SessionExpiredError,
|
|
6816
7175
|
ShareCorruptedError,
|
|
6817
7176
|
SocialWalletModal,
|
|
7177
|
+
StaleDeviceShareError,
|
|
6818
7178
|
UnlockRequiredError,
|
|
6819
7179
|
UserCancelledError,
|
|
6820
7180
|
WalletSelector,
|
|
@@ -6823,6 +7183,7 @@ export {
|
|
|
6823
7183
|
createDecane,
|
|
6824
7184
|
createDecaneConnect,
|
|
6825
7185
|
createEthereumProvider,
|
|
7186
|
+
loginWithKingsChatPopup,
|
|
6826
7187
|
startEmailAuth,
|
|
6827
7188
|
useChainSwitch,
|
|
6828
7189
|
useDecane,
|