decane-connect-kit 2.5.0 → 2.6.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 +368 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -7
- package/dist/index.d.ts +138 -7
- package/dist/index.js +366 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -49,12 +49,13 @@ __export(errors_exports, {
|
|
|
49
49
|
RecoveryWalletMismatchError: () => RecoveryWalletMismatchError,
|
|
50
50
|
SessionExpiredError: () => SessionExpiredError,
|
|
51
51
|
ShareCorruptedError: () => ShareCorruptedError,
|
|
52
|
+
StaleDeviceShareError: () => StaleDeviceShareError,
|
|
52
53
|
UnlockRequiredError: () => UnlockRequiredError,
|
|
53
54
|
UserCancelledError: () => UserCancelledError,
|
|
54
55
|
WebAuthnNotSupportedError: () => WebAuthnNotSupportedError,
|
|
55
56
|
WrongPasswordError: () => WrongPasswordError
|
|
56
57
|
});
|
|
57
|
-
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
|
|
58
|
+
var DecaneConnectError, InvalidApiKeyError, OriginNotAllowedError, SessionExpiredError, UnlockRequiredError, ReconnectRequiredError, UserCancelledError, WebAuthnNotSupportedError, InvalidPinError, CallSequenceError, NetworkError, ShareCorruptedError, NewDeviceError, StaleDeviceShareError, PromptPinNotConfiguredError, AttestationError, RecoveryFailedError, InvalidRecoveryFileError, WrongPasswordError, RecoveryWalletMismatchError, ExportNotAuthorizedError;
|
|
58
59
|
var init_errors = __esm({
|
|
59
60
|
"src/social/errors.ts"() {
|
|
60
61
|
"use strict";
|
|
@@ -130,6 +131,15 @@ var init_errors = __esm({
|
|
|
130
131
|
super("NEW_DEVICE", "No device share found. Re-authenticate to set up this device.");
|
|
131
132
|
}
|
|
132
133
|
};
|
|
134
|
+
StaleDeviceShareError = class extends DecaneConnectError {
|
|
135
|
+
constructor() {
|
|
136
|
+
super(
|
|
137
|
+
"STALE_DEVICE_SHARE",
|
|
138
|
+
"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.",
|
|
139
|
+
true
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
133
143
|
PromptPinNotConfiguredError = class extends DecaneConnectError {
|
|
134
144
|
constructor() {
|
|
135
145
|
super("PROMPT_PIN_NOT_CONFIGURED", "Passkey unavailable and no promptPin callback was provided in config");
|
|
@@ -200,6 +210,7 @@ __export(index_exports, {
|
|
|
200
210
|
SessionExpiredError: () => SessionExpiredError,
|
|
201
211
|
ShareCorruptedError: () => ShareCorruptedError,
|
|
202
212
|
SocialWalletModal: () => SocialWalletModal,
|
|
213
|
+
StaleDeviceShareError: () => StaleDeviceShareError,
|
|
203
214
|
UnlockRequiredError: () => UnlockRequiredError,
|
|
204
215
|
UserCancelledError: () => UserCancelledError,
|
|
205
216
|
WalletSelector: () => WalletSelector,
|
|
@@ -208,6 +219,7 @@ __export(index_exports, {
|
|
|
208
219
|
createDecane: () => createDecane,
|
|
209
220
|
createDecaneConnect: () => createDecaneConnect,
|
|
210
221
|
createEthereumProvider: () => createEthereumProvider,
|
|
222
|
+
loginWithKingsChatPopup: () => loginWithKingsChatPopup,
|
|
211
223
|
startEmailAuth: () => startEmailAuth,
|
|
212
224
|
useChainSwitch: () => useChainSwitch,
|
|
213
225
|
useDecane: () => useDecane,
|
|
@@ -2030,6 +2042,32 @@ var ApiClient = class {
|
|
|
2030
2042
|
async getGoogleInitUrl() {
|
|
2031
2043
|
return this._request("/auth/google/init", { method: "GET" });
|
|
2032
2044
|
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Exchange a Google ID token the app already holds for a Decane session.
|
|
2047
|
+
* Resolves to the SAME identity as the redirect flow, so one Google account
|
|
2048
|
+
* means one wallet across web and mobile.
|
|
2049
|
+
*/
|
|
2050
|
+
async authGoogleToken(idToken) {
|
|
2051
|
+
return this._request("/auth/google/token", {
|
|
2052
|
+
method: "POST",
|
|
2053
|
+
body: JSON.stringify({ idToken })
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
/** The project's KingsChat clientId, so the dApp never has to hardcode it. */
|
|
2057
|
+
async getKingsChatConfig() {
|
|
2058
|
+
return this._request("/auth/kingschat/config", { method: "GET" });
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Exchange a KingsChat access token for a Decane session. The backend spends
|
|
2062
|
+
* the token against KingsChat's own API to establish the identity — it is
|
|
2063
|
+
* never trusted on its face, and never stored.
|
|
2064
|
+
*/
|
|
2065
|
+
async authKingsChat(accessToken) {
|
|
2066
|
+
return this._request("/auth/kingschat", {
|
|
2067
|
+
method: "POST",
|
|
2068
|
+
body: JSON.stringify({ accessToken })
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2033
2071
|
async authEmailStart(email) {
|
|
2034
2072
|
await this._request("/auth/email/start", {
|
|
2035
2073
|
method: "POST",
|
|
@@ -2354,6 +2392,13 @@ var TeeClient = class {
|
|
|
2354
2392
|
requireAssertionPerSignature
|
|
2355
2393
|
})
|
|
2356
2394
|
});
|
|
2395
|
+
if (res.status === 403) {
|
|
2396
|
+
const body2 = await res.json().catch(() => ({}));
|
|
2397
|
+
if (body2?.error === "share_mismatch") {
|
|
2398
|
+
const { StaleDeviceShareError: StaleDeviceShareError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
2399
|
+
throw new StaleDeviceShareError2();
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2357
2402
|
if (!res.ok) throw new Error("session open failed");
|
|
2358
2403
|
const body = await res.json();
|
|
2359
2404
|
this.handle = body.handle;
|
|
@@ -2413,6 +2458,45 @@ var TeeClient = class {
|
|
|
2413
2458
|
}
|
|
2414
2459
|
return { challengeId, assertion };
|
|
2415
2460
|
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Enrol this wallet for server-assisted provisioning by handing the enclave
|
|
2463
|
+
* the recovery share to seal.
|
|
2464
|
+
*
|
|
2465
|
+
* After this, a new device gets a working share from login alone — no passkey
|
|
2466
|
+
* sync, no recovery file. The enclave seals the share under a key only it can
|
|
2467
|
+
* derive, so the backend stores ciphertext it cannot read; but the enclave
|
|
2468
|
+
* then holds two of three shares and can reconstruct on its own. That is the
|
|
2469
|
+
* trade this call makes, which is why it is opt-out rather than implicit.
|
|
2470
|
+
*/
|
|
2471
|
+
async enrolAuthShare(jwt, recoveryShare) {
|
|
2472
|
+
await this.ensureAttested();
|
|
2473
|
+
const res = await fetch(`${this.teeUrl}/signer/auth-share`, {
|
|
2474
|
+
method: "POST",
|
|
2475
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2476
|
+
body: JSON.stringify({ share: bytesToBase64(recoveryShare) })
|
|
2477
|
+
});
|
|
2478
|
+
if (!res.ok) throw new Error("auth share enrolment failed");
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Ask the enclave for a fresh device share for this authenticated user.
|
|
2482
|
+
* Returns null when the wallet was never enrolled — the signal to fall back
|
|
2483
|
+
* to passkey sync or the recovery file rather than fail.
|
|
2484
|
+
*
|
|
2485
|
+
* Every successful call re-splits the set, so any share this device or any
|
|
2486
|
+
* other held is now dead. Intentional: a leaked device share stops working the
|
|
2487
|
+
* next time the user signs in anywhere.
|
|
2488
|
+
*/
|
|
2489
|
+
async provisionDeviceShare(jwt) {
|
|
2490
|
+
await this.ensureAttested();
|
|
2491
|
+
const res = await fetch(`${this.teeUrl}/signer/provision`, {
|
|
2492
|
+
method: "POST",
|
|
2493
|
+
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" }
|
|
2494
|
+
});
|
|
2495
|
+
if (res.status === 409) return null;
|
|
2496
|
+
if (!res.ok) throw new Error("device share provisioning failed");
|
|
2497
|
+
const body = await res.json();
|
|
2498
|
+
return base64ToBytes(body.deviceShare);
|
|
2499
|
+
}
|
|
2416
2500
|
/** Voluntary rotation — e.g. after a suspected device compromise, or to invalidate an old recovery file. */
|
|
2417
2501
|
async rotateShares(jwt) {
|
|
2418
2502
|
if (!this.handle) throw new Error("no session");
|
|
@@ -2799,6 +2883,79 @@ async function verifyEmailCode(api, email, code) {
|
|
|
2799
2883
|
return api.authEmailVerify(email, code);
|
|
2800
2884
|
}
|
|
2801
2885
|
|
|
2886
|
+
// src/social/auth/kingschat.ts
|
|
2887
|
+
init_errors();
|
|
2888
|
+
var KINGSCHAT_AUTH_ORIGIN = "https://accounts.kingsch.at";
|
|
2889
|
+
function readTokens(data) {
|
|
2890
|
+
if (!data || typeof data !== "object") return null;
|
|
2891
|
+
const d = data;
|
|
2892
|
+
const accessToken = d.accessToken ?? d.access_token;
|
|
2893
|
+
if (typeof accessToken !== "string" || !accessToken) return null;
|
|
2894
|
+
const refreshToken = d.refreshToken ?? d.refresh_token;
|
|
2895
|
+
const expires = d.expiresInMillis ?? d.expires_in_millis;
|
|
2896
|
+
return {
|
|
2897
|
+
accessToken,
|
|
2898
|
+
refreshToken: typeof refreshToken === "string" ? refreshToken : void 0,
|
|
2899
|
+
expiresInMillis: typeof expires === "number" ? expires : void 0
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2902
|
+
function buildAuthUrl(clientId, scopes) {
|
|
2903
|
+
const url = new URL(KINGSCHAT_AUTH_ORIGIN);
|
|
2904
|
+
url.searchParams.set("client_id", clientId);
|
|
2905
|
+
url.searchParams.set("scopes", JSON.stringify(scopes));
|
|
2906
|
+
url.searchParams.set("redirect_uri", window.location.origin);
|
|
2907
|
+
url.searchParams.set("post_message", "1");
|
|
2908
|
+
return url.toString();
|
|
2909
|
+
}
|
|
2910
|
+
function loginWithKingsChatPopup(opts) {
|
|
2911
|
+
const scopes = opts.scopes ?? ["send_chat_message"];
|
|
2912
|
+
const popup = window.open(
|
|
2913
|
+
buildAuthUrl(opts.clientId, scopes),
|
|
2914
|
+
"decane-kingschat",
|
|
2915
|
+
"width=480,height=720,menubar=no,toolbar=no"
|
|
2916
|
+
);
|
|
2917
|
+
if (!popup) {
|
|
2918
|
+
throw new CallSequenceError(
|
|
2919
|
+
"The KingsChat sign-in popup was blocked. Call connectWithKingsChat() directly from a click handler."
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
return new Promise((resolve, reject) => {
|
|
2923
|
+
let settled = false;
|
|
2924
|
+
const finish = (fn) => {
|
|
2925
|
+
if (settled) return;
|
|
2926
|
+
settled = true;
|
|
2927
|
+
window.removeEventListener("message", onMessage);
|
|
2928
|
+
clearInterval(closedPoll);
|
|
2929
|
+
clearTimeout(timeout);
|
|
2930
|
+
try {
|
|
2931
|
+
popup.close();
|
|
2932
|
+
} catch {
|
|
2933
|
+
}
|
|
2934
|
+
fn();
|
|
2935
|
+
};
|
|
2936
|
+
const onMessage = (event) => {
|
|
2937
|
+
if (event.origin !== KINGSCHAT_AUTH_ORIGIN) return;
|
|
2938
|
+
const data = event.data;
|
|
2939
|
+
if (data && typeof data === "object" && data.error) {
|
|
2940
|
+
finish(() => reject(new CallSequenceError(`KingsChat sign-in failed: ${String(data.error)}`)));
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
const tokens2 = readTokens(event.data);
|
|
2944
|
+
if (tokens2) finish(() => resolve(tokens2));
|
|
2945
|
+
};
|
|
2946
|
+
window.addEventListener("message", onMessage);
|
|
2947
|
+
const closedPoll = setInterval(() => {
|
|
2948
|
+
if (popup.closed) finish(() => reject(new UserCancelledError()));
|
|
2949
|
+
}, 400);
|
|
2950
|
+
const timeout = setTimeout(
|
|
2951
|
+
() => finish(
|
|
2952
|
+
() => reject(new CallSequenceError("KingsChat sign-in timed out before returning a token."))
|
|
2953
|
+
),
|
|
2954
|
+
opts.timeoutMs ?? 5 * 6e4
|
|
2955
|
+
);
|
|
2956
|
+
});
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2802
2959
|
// src/social/signers/evm.ts
|
|
2803
2960
|
var import_ethers2 = require("ethers");
|
|
2804
2961
|
async function signEvmTransaction(tee, chain, tx, jwt) {
|
|
@@ -3161,7 +3318,7 @@ async function recoverDeviceShare(input) {
|
|
|
3161
3318
|
prfSalt: backup.prfSalt,
|
|
3162
3319
|
version: 1
|
|
3163
3320
|
});
|
|
3164
|
-
return deviceShare;
|
|
3321
|
+
return { deviceShare, flow: "passkey-sync" };
|
|
3165
3322
|
} catch (e) {
|
|
3166
3323
|
input.onProgress?.({
|
|
3167
3324
|
type: "passkey-failed",
|
|
@@ -3189,7 +3346,7 @@ async function recoverDeviceShare(input) {
|
|
|
3189
3346
|
input.onProgress?.({ type: "rotated", shareSetVersion: result.shareSetVersion });
|
|
3190
3347
|
await input.onNewRecoveryShare?.(result.recoveryShare, result.shareSetVersion);
|
|
3191
3348
|
zeroize(result.recoveryShare);
|
|
3192
|
-
return result.deviceShare;
|
|
3349
|
+
return { deviceShare: result.deviceShare, flow: "recovery-file" };
|
|
3193
3350
|
}
|
|
3194
3351
|
|
|
3195
3352
|
// src/social/recovery/portableBackup.ts
|
|
@@ -3307,6 +3464,11 @@ async function createDecaneConnect(config) {
|
|
|
3307
3464
|
if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
|
|
3308
3465
|
if (!config.chains?.length)
|
|
3309
3466
|
throw new Error("decane-connect-kit: at least one chain is required");
|
|
3467
|
+
if (config.rotateOnNewDeviceRecovery && !config.onRecoveryRotated) {
|
|
3468
|
+
throw new Error(
|
|
3469
|
+
"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."
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3310
3472
|
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE;
|
|
3311
3473
|
const durationMs = (config.sessionDurationMinutes ?? DEFAULT_SESSION_MINUTES) * 6e4;
|
|
3312
3474
|
const rpId = config.rpId ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
|
|
@@ -3326,6 +3488,7 @@ async function createDecaneConnect(config) {
|
|
|
3326
3488
|
let persisted = loadPersisted(PERSIST_KEY);
|
|
3327
3489
|
let ethProvider = null;
|
|
3328
3490
|
const persistSession = config.persistSession !== false;
|
|
3491
|
+
const serverAssistedRecovery = config.serverAssistedRecovery !== false;
|
|
3329
3492
|
const SESSION_KEY = sessionPersistKey(config.apiKey);
|
|
3330
3493
|
function persistLiveSession() {
|
|
3331
3494
|
if (!persistSession) return;
|
|
@@ -3551,6 +3714,17 @@ async function createDecaneConnect(config) {
|
|
|
3551
3714
|
console.warn("decane-connect-kit: recovery share offer failed or was skipped.", e);
|
|
3552
3715
|
}
|
|
3553
3716
|
}
|
|
3717
|
+
if (serverAssistedRecovery) {
|
|
3718
|
+
try {
|
|
3719
|
+
const tee = new TeeClient(teeUrl);
|
|
3720
|
+
await tee.enrolAuthShare(jwt, shareSet.recovery);
|
|
3721
|
+
} catch (e) {
|
|
3722
|
+
console.warn(
|
|
3723
|
+
"decane-connect-kit: could not enrol this wallet for server-assisted recovery; a recovery file will be required on a new device.",
|
|
3724
|
+
e
|
|
3725
|
+
);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3554
3728
|
zeroize(shareSet.recovery);
|
|
3555
3729
|
return addresses;
|
|
3556
3730
|
}
|
|
@@ -3559,13 +3733,36 @@ async function createDecaneConnect(config) {
|
|
|
3559
3733
|
const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana, tron: rawAddresses.tron ?? evmToTronAddress2(rawAddresses.evm) } : null;
|
|
3560
3734
|
const stored = await storage.getShare(userId);
|
|
3561
3735
|
let deviceShare;
|
|
3736
|
+
let joinedViaPasskeySync = false;
|
|
3562
3737
|
if (!stored) {
|
|
3738
|
+
if (serverAssistedRecovery) {
|
|
3739
|
+
try {
|
|
3740
|
+
const tee = new TeeClient(teeUrl);
|
|
3741
|
+
const provisioned = await tee.provisionDeviceShare(jwt);
|
|
3742
|
+
if (provisioned) {
|
|
3743
|
+
await registerPasskeyAndBackup(userId, jwt, provisioned).catch(() => {
|
|
3744
|
+
});
|
|
3745
|
+
let addrs;
|
|
3746
|
+
try {
|
|
3747
|
+
addrs = await session.start(provisioned, jwt, recordedAddresses);
|
|
3748
|
+
} finally {
|
|
3749
|
+
zeroize(provisioned);
|
|
3750
|
+
}
|
|
3751
|
+
return addrs;
|
|
3752
|
+
}
|
|
3753
|
+
} catch (e) {
|
|
3754
|
+
console.warn(
|
|
3755
|
+
"decane-connect-kit: server-assisted provisioning unavailable; falling back to passkey/recovery-file recovery.",
|
|
3756
|
+
e
|
|
3757
|
+
);
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3563
3760
|
if (!config.onRecoveryRotated) {
|
|
3564
3761
|
throw new CallSequenceError(
|
|
3565
3762
|
"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."
|
|
3566
3763
|
);
|
|
3567
3764
|
}
|
|
3568
|
-
|
|
3765
|
+
const recovered = await recoverDeviceShare({
|
|
3569
3766
|
userId,
|
|
3570
3767
|
jwt,
|
|
3571
3768
|
rpId,
|
|
@@ -3583,6 +3780,8 @@ async function createDecaneConnect(config) {
|
|
|
3583
3780
|
await api.markRecoveryShareSaved(jwt);
|
|
3584
3781
|
}
|
|
3585
3782
|
});
|
|
3783
|
+
deviceShare = recovered.deviceShare;
|
|
3784
|
+
joinedViaPasskeySync = recovered.flow === "passkey-sync";
|
|
3586
3785
|
if (!await storage.getShare(userId) && isPasskeySupported()) {
|
|
3587
3786
|
await registerPasskeyAndBackup(userId, jwt, deviceShare);
|
|
3588
3787
|
}
|
|
@@ -3598,15 +3797,66 @@ async function createDecaneConnect(config) {
|
|
|
3598
3797
|
throw stored.unlockMethod === "pin" ? new InvalidPinError() : new ShareCorruptedError();
|
|
3599
3798
|
}
|
|
3600
3799
|
}
|
|
3601
|
-
|
|
3800
|
+
let addresses;
|
|
3801
|
+
try {
|
|
3802
|
+
addresses = await session.start(deviceShare, jwt, recordedAddresses);
|
|
3803
|
+
} catch (e) {
|
|
3804
|
+
if (e instanceof StaleDeviceShareError) {
|
|
3805
|
+
zeroize(deviceShare);
|
|
3806
|
+
await storage.deleteShare(userId);
|
|
3807
|
+
if (stored) return returningUserUnlock(userId, jwt);
|
|
3808
|
+
} else {
|
|
3809
|
+
zeroize(deviceShare);
|
|
3810
|
+
}
|
|
3811
|
+
throw e;
|
|
3812
|
+
}
|
|
3602
3813
|
zeroize(deviceShare);
|
|
3603
3814
|
if (recordedAddresses && recordedAddresses.evm.toLowerCase() !== addresses.evm.toLowerCase()) {
|
|
3604
3815
|
console.warn(
|
|
3605
3816
|
`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.`
|
|
3606
3817
|
);
|
|
3607
3818
|
}
|
|
3819
|
+
if (joinedViaPasskeySync && config.rotateOnNewDeviceRecovery) {
|
|
3820
|
+
await rotateForNewDevice(userId, jwt);
|
|
3821
|
+
}
|
|
3608
3822
|
return addresses;
|
|
3609
3823
|
}
|
|
3824
|
+
async function rotateForNewDevice(userId, jwt) {
|
|
3825
|
+
const tee = session.requireActive();
|
|
3826
|
+
const result = await tee.rotateShares(jwt);
|
|
3827
|
+
try {
|
|
3828
|
+
await persistRotatedDeviceShare(userId, result.deviceShare);
|
|
3829
|
+
const refreshed = await storage.getShare(userId);
|
|
3830
|
+
if (refreshed?.unlockMethod === "passkey" && refreshed.credentialId && refreshed.prfSalt) {
|
|
3831
|
+
await api.putRecoveryBackup(jwt, {
|
|
3832
|
+
wrappedDeviceShare: refreshed.wrappedDeviceShare,
|
|
3833
|
+
iv: refreshed.iv,
|
|
3834
|
+
unlockMethod: "passkey",
|
|
3835
|
+
credentialId: refreshed.credentialId,
|
|
3836
|
+
prfSalt: refreshed.prfSalt
|
|
3837
|
+
}).catch((e) => {
|
|
3838
|
+
console.warn("decane-connect-kit: backup refresh after device rotation failed; the next new device will need the recovery file.", e);
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
const addrs = await deriveAddressesFromShares(result.deviceShare, result.recoveryShare);
|
|
3842
|
+
const { password, passwordHint } = await config.onRecoveryRotated({
|
|
3843
|
+
evmAddress: addrs.evm,
|
|
3844
|
+
solanaAddress: addrs.solana
|
|
3845
|
+
});
|
|
3846
|
+
const file = await buildRecoveryShareFile(
|
|
3847
|
+
result.recoveryShare,
|
|
3848
|
+
password,
|
|
3849
|
+
addrs,
|
|
3850
|
+
result.shareSetVersion,
|
|
3851
|
+
passwordHint
|
|
3852
|
+
);
|
|
3853
|
+
await config.onRecoveryFileReady?.(file, recoveryShareFileName(addrs.evm));
|
|
3854
|
+
await api.markRecoveryShareSaved(jwt);
|
|
3855
|
+
} finally {
|
|
3856
|
+
zeroize(result.deviceShare);
|
|
3857
|
+
zeroize(result.recoveryShare);
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3610
3860
|
async function passkeyLoginCandidate() {
|
|
3611
3861
|
if (!persisted?.userId || !isPasskeySupported()) return null;
|
|
3612
3862
|
const stored = await storage.getShare(persisted.userId);
|
|
@@ -3681,6 +3931,7 @@ async function createDecaneConnect(config) {
|
|
|
3681
3931
|
const method = config.authMethods[0];
|
|
3682
3932
|
if (!method) throw new CallSequenceError("No authMethods configured");
|
|
3683
3933
|
if (method === "google") return client.connectWithGoogle();
|
|
3934
|
+
if (method === "kingschat") return client.connectWithKingsChat();
|
|
3684
3935
|
if (method === "email")
|
|
3685
3936
|
throw new CallSequenceError(
|
|
3686
3937
|
"Use connectWithEmail(email) for email auth"
|
|
@@ -3710,6 +3961,25 @@ async function createDecaneConnect(config) {
|
|
|
3710
3961
|
return new Promise(() => {
|
|
3711
3962
|
});
|
|
3712
3963
|
},
|
|
3964
|
+
async connectWithGoogleToken(idToken) {
|
|
3965
|
+
if (!idToken) throw new CallSequenceError("connectWithGoogleToken requires an ID token");
|
|
3966
|
+
return handleAuthResult(await api.authGoogleToken(idToken));
|
|
3967
|
+
},
|
|
3968
|
+
async connectWithKingsChat() {
|
|
3969
|
+
const { clientId } = await api.getKingsChatConfig();
|
|
3970
|
+
const tokens2 = await loginWithKingsChatPopup({
|
|
3971
|
+
clientId,
|
|
3972
|
+
scopes: config.kingsChatScopes
|
|
3973
|
+
});
|
|
3974
|
+
return client.connectWithKingsChatToken(tokens2.accessToken);
|
|
3975
|
+
},
|
|
3976
|
+
async connectWithKingsChatToken(accessToken) {
|
|
3977
|
+
if (!accessToken) {
|
|
3978
|
+
throw new CallSequenceError("connectWithKingsChatToken requires an access token");
|
|
3979
|
+
}
|
|
3980
|
+
const result = await api.authKingsChat(accessToken);
|
|
3981
|
+
return handleAuthResult(result);
|
|
3982
|
+
},
|
|
3713
3983
|
async connectWithToken(token, opts) {
|
|
3714
3984
|
if (!token) throw new CallSequenceError("connectWithToken requires a token");
|
|
3715
3985
|
const result = await api.authExternal(token, opts?.providerId);
|
|
@@ -4482,6 +4752,7 @@ function WalletSelector({
|
|
|
4482
4752
|
mode = "wallets",
|
|
4483
4753
|
socialAuthMethods = ["google", "email"],
|
|
4484
4754
|
onGoogleSignIn,
|
|
4755
|
+
onKingsChatSignIn,
|
|
4485
4756
|
onEmailStart,
|
|
4486
4757
|
onEmailVerify,
|
|
4487
4758
|
customAuthLabel,
|
|
@@ -4559,6 +4830,18 @@ function WalletSelector({
|
|
|
4559
4830
|
setSocialLoad(false);
|
|
4560
4831
|
}
|
|
4561
4832
|
}
|
|
4833
|
+
async function handleKingsChatClick() {
|
|
4834
|
+
if (!onKingsChatSignIn) return;
|
|
4835
|
+
setSocialLoad(true);
|
|
4836
|
+
setSocialErr("");
|
|
4837
|
+
try {
|
|
4838
|
+
await onKingsChatSignIn();
|
|
4839
|
+
} catch (e) {
|
|
4840
|
+
setSocialErr(e.message);
|
|
4841
|
+
} finally {
|
|
4842
|
+
setSocialLoad(false);
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4562
4845
|
async function handleCustomAuthClick() {
|
|
4563
4846
|
if (!onCustomAuthSignIn) return;
|
|
4564
4847
|
setSocialLoad(true);
|
|
@@ -4981,6 +5264,38 @@ function WalletSelector({
|
|
|
4981
5264
|
},
|
|
4982
5265
|
/* @__PURE__ */ import_react2.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none" }, /* @__PURE__ */ import_react2.default.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__ */ import_react2.default.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__ */ import_react2.default.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__ */ import_react2.default.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" })),
|
|
4983
5266
|
socialLoad ? "Redirecting\u2026" : "Continue with Google"
|
|
5267
|
+
), !passkeyFirst && socialAuthMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ import_react2.default.createElement(
|
|
5268
|
+
"button",
|
|
5269
|
+
{
|
|
5270
|
+
onClick: handleKingsChatClick,
|
|
5271
|
+
disabled: socialLoad,
|
|
5272
|
+
style: {
|
|
5273
|
+
display: "flex",
|
|
5274
|
+
alignItems: "center",
|
|
5275
|
+
justifyContent: "center",
|
|
5276
|
+
gap: 10,
|
|
5277
|
+
width: "100%",
|
|
5278
|
+
padding: "11px 16px",
|
|
5279
|
+
background: "var(--dc-surface)",
|
|
5280
|
+
border: "1px solid var(--dc-border)",
|
|
5281
|
+
borderRadius: 10,
|
|
5282
|
+
cursor: socialLoad ? "not-allowed" : "pointer",
|
|
5283
|
+
fontSize: 14,
|
|
5284
|
+
fontWeight: 600,
|
|
5285
|
+
color: "var(--dc-text)",
|
|
5286
|
+
fontFamily: "inherit",
|
|
5287
|
+
transition: "border-color 0.12s, background 0.12s",
|
|
5288
|
+
opacity: socialLoad ? 0.6 : 1
|
|
5289
|
+
},
|
|
5290
|
+
onMouseEnter: (e) => {
|
|
5291
|
+
if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
|
|
5292
|
+
},
|
|
5293
|
+
onMouseLeave: (e) => {
|
|
5294
|
+
e.currentTarget.style.borderColor = "var(--dc-border)";
|
|
5295
|
+
}
|
|
5296
|
+
},
|
|
5297
|
+
/* @__PURE__ */ import_react2.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "9", cy: "9", r: "9", fill: "var(--dc-accent)" }), /* @__PURE__ */ import_react2.default.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)" })),
|
|
5298
|
+
socialLoad ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
|
|
4984
5299
|
), !passkeyFirst && socialAuthMethods.includes("google") && socialAuthMethods.includes("email") && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } }), /* @__PURE__ */ import_react2.default.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", whiteSpace: "nowrap" } }, "or continue with email"), /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), !passkeyFirst && socialAuthMethods.includes("email") && (emailStep === "input" ? /* @__PURE__ */ import_react2.default.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ import_react2.default.createElement(
|
|
4985
5300
|
"input",
|
|
4986
5301
|
{
|
|
@@ -5227,6 +5542,7 @@ function AddressRow({
|
|
|
5227
5542
|
}
|
|
5228
5543
|
function SignInView({
|
|
5229
5544
|
onGoogleSignIn,
|
|
5545
|
+
onKingsChatSignIn,
|
|
5230
5546
|
onEmailStart,
|
|
5231
5547
|
onEmailVerify,
|
|
5232
5548
|
authMethods,
|
|
@@ -5238,6 +5554,7 @@ function SignInView({
|
|
|
5238
5554
|
onPasskeySignIn
|
|
5239
5555
|
}) {
|
|
5240
5556
|
const [googleLoading, setGoogleLoading] = (0, import_react3.useState)(false);
|
|
5557
|
+
const [kingsChatLoading, setKingsChatLoading] = (0, import_react3.useState)(false);
|
|
5241
5558
|
const [email, setEmail] = (0, import_react3.useState)("");
|
|
5242
5559
|
const [code, setCode] = (0, import_react3.useState)("");
|
|
5243
5560
|
const [emailSent, setEmailSent] = (0, import_react3.useState)(false);
|
|
@@ -5272,6 +5589,18 @@ function SignInView({
|
|
|
5272
5589
|
setGoogleLoading(false);
|
|
5273
5590
|
}
|
|
5274
5591
|
}
|
|
5592
|
+
async function handleKingsChat() {
|
|
5593
|
+
if (!onKingsChatSignIn) return;
|
|
5594
|
+
setError("");
|
|
5595
|
+
setKingsChatLoading(true);
|
|
5596
|
+
try {
|
|
5597
|
+
await onKingsChatSignIn();
|
|
5598
|
+
} catch (e) {
|
|
5599
|
+
setError(e.message);
|
|
5600
|
+
} finally {
|
|
5601
|
+
setKingsChatLoading(false);
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5275
5604
|
async function handleEmailStart(e) {
|
|
5276
5605
|
e.preventDefault();
|
|
5277
5606
|
setError("");
|
|
@@ -5382,6 +5711,27 @@ function SignInView({
|
|
|
5382
5711
|
}
|
|
5383
5712
|
},
|
|
5384
5713
|
googleLoading ? "Redirecting\u2026" : "Continue with Google"
|
|
5714
|
+
), !passkeyFirst && authMethods.includes("kingschat") && onKingsChatSignIn && /* @__PURE__ */ import_react3.default.createElement(
|
|
5715
|
+
"button",
|
|
5716
|
+
{
|
|
5717
|
+
onClick: handleKingsChat,
|
|
5718
|
+
disabled: kingsChatLoading,
|
|
5719
|
+
style: {
|
|
5720
|
+
width: "100%",
|
|
5721
|
+
padding: "10px 0",
|
|
5722
|
+
marginBottom: 10,
|
|
5723
|
+
background: "var(--dc-surface)",
|
|
5724
|
+
color: "var(--dc-text)",
|
|
5725
|
+
border: "1px solid var(--dc-border)",
|
|
5726
|
+
borderRadius: 10,
|
|
5727
|
+
fontSize: 13,
|
|
5728
|
+
fontWeight: 700,
|
|
5729
|
+
cursor: kingsChatLoading ? "not-allowed" : "pointer",
|
|
5730
|
+
opacity: kingsChatLoading ? 0.7 : 1,
|
|
5731
|
+
fontFamily: "inherit"
|
|
5732
|
+
}
|
|
5733
|
+
},
|
|
5734
|
+
kingsChatLoading ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
|
|
5385
5735
|
), !passkeyFirst && authMethods.includes("email") && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, authMethods.includes("google") && /* @__PURE__ */ import_react3.default.createElement("div", { style: { textAlign: "center", color: "var(--dc-text-muted)", fontSize: 12, margin: "10px 0" } }, "\u2014 or \u2014"), !emailSent ? /* @__PURE__ */ import_react3.default.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", gap: 8 } }, /* @__PURE__ */ import_react3.default.createElement(
|
|
5386
5736
|
"input",
|
|
5387
5737
|
{
|
|
@@ -5555,6 +5905,7 @@ function SocialWalletModal({
|
|
|
5555
5905
|
onUnlock,
|
|
5556
5906
|
onDisconnect,
|
|
5557
5907
|
onGoogleSignIn,
|
|
5908
|
+
onKingsChatSignIn,
|
|
5558
5909
|
onEmailStart,
|
|
5559
5910
|
onEmailVerify,
|
|
5560
5911
|
authMethods = ["google", "email"],
|
|
@@ -5688,6 +6039,7 @@ function SocialWalletModal({
|
|
|
5688
6039
|
SignInView,
|
|
5689
6040
|
{
|
|
5690
6041
|
onGoogleSignIn,
|
|
6042
|
+
onKingsChatSignIn,
|
|
5691
6043
|
onEmailStart,
|
|
5692
6044
|
onEmailVerify,
|
|
5693
6045
|
authMethods,
|
|
@@ -6009,6 +6361,12 @@ function DecaneKit({ config, children }) {
|
|
|
6009
6361
|
if (!sdk) throw new Error("Social sign-in not configured");
|
|
6010
6362
|
await sdk.connectWithGoogle();
|
|
6011
6363
|
}, []);
|
|
6364
|
+
const connectWithKingsChat = (0, import_react5.useCallback)(async () => {
|
|
6365
|
+
const sdk = socialRef.current;
|
|
6366
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
6367
|
+
const result = await sdk.connectWithKingsChat();
|
|
6368
|
+
applyResult(result);
|
|
6369
|
+
}, [applyResult]);
|
|
6012
6370
|
const connectWithEmail = (0, import_react5.useCallback)(async (email) => {
|
|
6013
6371
|
const sdk = socialRef.current;
|
|
6014
6372
|
if (!sdk) throw new Error("Social sign-in not configured");
|
|
@@ -6175,6 +6533,7 @@ function DecaneKit({ config, children }) {
|
|
|
6175
6533
|
socialAuth,
|
|
6176
6534
|
signInWithPasskey,
|
|
6177
6535
|
connectWithGoogle,
|
|
6536
|
+
connectWithKingsChat,
|
|
6178
6537
|
connectWithEmail,
|
|
6179
6538
|
verifyEmailCode: verifyEmailCode2,
|
|
6180
6539
|
connectWithToken,
|
|
@@ -6217,6 +6576,7 @@ function DecaneKit({ config, children }) {
|
|
|
6217
6576
|
mode,
|
|
6218
6577
|
socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
|
|
6219
6578
|
onGoogleSignIn: connectWithGoogle,
|
|
6579
|
+
onKingsChatSignIn: connectWithKingsChat,
|
|
6220
6580
|
onEmailStart: connectWithEmail,
|
|
6221
6581
|
onEmailVerify: verifyEmailCode2,
|
|
6222
6582
|
canUsePasskey: socialAuth.canUsePasskey,
|
|
@@ -6238,6 +6598,7 @@ function DecaneKit({ config, children }) {
|
|
|
6238
6598
|
customAuthLabel: customAuthConfig?.label,
|
|
6239
6599
|
onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
|
|
6240
6600
|
onGoogleSignIn: connectWithGoogle,
|
|
6601
|
+
onKingsChatSignIn: connectWithKingsChat,
|
|
6241
6602
|
onEmailStart: connectWithEmail,
|
|
6242
6603
|
onEmailVerify: verifyEmailCode2,
|
|
6243
6604
|
authMethods: config?.social?.authMethods ?? ["google", "email"],
|
|
@@ -6882,6 +7243,7 @@ function useSignTypedData() {
|
|
|
6882
7243
|
SessionExpiredError,
|
|
6883
7244
|
ShareCorruptedError,
|
|
6884
7245
|
SocialWalletModal,
|
|
7246
|
+
StaleDeviceShareError,
|
|
6885
7247
|
UnlockRequiredError,
|
|
6886
7248
|
UserCancelledError,
|
|
6887
7249
|
WalletSelector,
|
|
@@ -6890,6 +7252,7 @@ function useSignTypedData() {
|
|
|
6890
7252
|
createDecane,
|
|
6891
7253
|
createDecaneConnect,
|
|
6892
7254
|
createEthereumProvider,
|
|
7255
|
+
loginWithKingsChatPopup,
|
|
6893
7256
|
startEmailAuth,
|
|
6894
7257
|
useChainSwitch,
|
|
6895
7258
|
useDecane,
|