decane-connect-kit 2.0.3 → 2.2.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 +454 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -7
- package/dist/index.d.ts +113 -7
- package/dist/index.js +455 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1935,7 +1935,12 @@ function startBitcoinDiscovery(registry) {
|
|
|
1935
1935
|
}
|
|
1936
1936
|
|
|
1937
1937
|
// src/factory.ts
|
|
1938
|
-
function createDecane(config
|
|
1938
|
+
function createDecane(config) {
|
|
1939
|
+
if (!config?.appId) {
|
|
1940
|
+
throw new Error(
|
|
1941
|
+
"decane-connect-kit: appId is required. Pass your dashboard project ID as config.appId."
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1939
1944
|
const emitter = createEmitter();
|
|
1940
1945
|
const registry = createRegistry(emitter);
|
|
1941
1946
|
const session = createSessionManager();
|
|
@@ -1981,9 +1986,10 @@ var ERROR_MAP = {
|
|
|
1981
1986
|
ORIGIN_NOT_ALLOWED: OriginNotAllowedError
|
|
1982
1987
|
};
|
|
1983
1988
|
var ApiClient = class {
|
|
1984
|
-
constructor(baseUrl, apiKey) {
|
|
1989
|
+
constructor(baseUrl, apiKey, appId) {
|
|
1985
1990
|
this.baseUrl = baseUrl;
|
|
1986
1991
|
this.apiKey = apiKey;
|
|
1992
|
+
this.appId = appId;
|
|
1987
1993
|
}
|
|
1988
1994
|
async _request(path, init) {
|
|
1989
1995
|
let res;
|
|
@@ -1993,6 +1999,7 @@ var ApiClient = class {
|
|
|
1993
1999
|
headers: {
|
|
1994
2000
|
"Content-Type": "application/json",
|
|
1995
2001
|
"X-API-Key": this.apiKey,
|
|
2002
|
+
"X-App-Id": this.appId,
|
|
1996
2003
|
...init?.headers
|
|
1997
2004
|
}
|
|
1998
2005
|
});
|
|
@@ -2059,6 +2066,35 @@ var ApiClient = class {
|
|
|
2059
2066
|
body: JSON.stringify({ response, origin })
|
|
2060
2067
|
});
|
|
2061
2068
|
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Exchange a token from the developer's own auth system for a Decane
|
|
2071
|
+
* session. `providerId` is only needed when the project has more than one
|
|
2072
|
+
* provider configured.
|
|
2073
|
+
*/
|
|
2074
|
+
async authExternal(token, providerId) {
|
|
2075
|
+
return this._request("/auth/external", {
|
|
2076
|
+
method: "POST",
|
|
2077
|
+
body: JSON.stringify(providerId ? { token, providerId } : { token })
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Returns null when this user has no server-registered passkey, which is the
|
|
2082
|
+
* expected answer for wallets created before passkey sign-in existed or for
|
|
2083
|
+
* PIN-only users — the caller falls back to the normal sign-in flow.
|
|
2084
|
+
*/
|
|
2085
|
+
async getWebAuthnLoginOptions(userId, rpId) {
|
|
2086
|
+
return this._request("/webauthn/login-options", {
|
|
2087
|
+
method: "POST",
|
|
2088
|
+
body: JSON.stringify({ userId, rpId }),
|
|
2089
|
+
allow404: true
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
async postWebAuthnLogin(userId, response, origin) {
|
|
2093
|
+
return this._request("/webauthn/login", {
|
|
2094
|
+
method: "POST",
|
|
2095
|
+
body: JSON.stringify({ userId, response, origin })
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2062
2098
|
async revoke(jwt) {
|
|
2063
2099
|
await this._request("/auth/revoke", {
|
|
2064
2100
|
method: "POST",
|
|
@@ -2231,6 +2267,12 @@ var TeeClient = class {
|
|
|
2231
2267
|
this.teeUrl = teeUrl;
|
|
2232
2268
|
this.handle = null;
|
|
2233
2269
|
this.attested = false;
|
|
2270
|
+
/** Mirrors what the signer confirmed for this session, not what we asked for. */
|
|
2271
|
+
this.assertionPerSignature = false;
|
|
2272
|
+
}
|
|
2273
|
+
/** Whether this session actually got per-signature assertions. */
|
|
2274
|
+
requiresAssertionPerSignature() {
|
|
2275
|
+
return this.assertionPerSignature;
|
|
2234
2276
|
}
|
|
2235
2277
|
async ensureAttested() {
|
|
2236
2278
|
if (this.attested) return;
|
|
@@ -2246,16 +2288,20 @@ var TeeClient = class {
|
|
|
2246
2288
|
* couldn't derive them (older signer build, or a transient share-fetch
|
|
2247
2289
|
* failure); callers fall back to the backend's addresses in that case.
|
|
2248
2290
|
*/
|
|
2249
|
-
async openSession(deviceShare, jwt) {
|
|
2291
|
+
async openSession(deviceShare, jwt, requireAssertionPerSignature = false) {
|
|
2250
2292
|
await this.ensureAttested();
|
|
2251
2293
|
const res = await fetch(`${this.teeUrl}/signer/session`, {
|
|
2252
2294
|
method: "POST",
|
|
2253
2295
|
headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
|
|
2254
|
-
body: JSON.stringify({
|
|
2296
|
+
body: JSON.stringify({
|
|
2297
|
+
deviceShare: bytesToBase64(deviceShare),
|
|
2298
|
+
requireAssertionPerSignature
|
|
2299
|
+
})
|
|
2255
2300
|
});
|
|
2256
2301
|
if (!res.ok) throw new Error("session open failed");
|
|
2257
2302
|
const body = await res.json();
|
|
2258
2303
|
this.handle = body.handle;
|
|
2304
|
+
this.assertionPerSignature = body.requireAssertionPerSignature === true;
|
|
2259
2305
|
return body.addresses ?? null;
|
|
2260
2306
|
}
|
|
2261
2307
|
/**
|
|
@@ -2346,14 +2392,33 @@ var TeeClient = class {
|
|
|
2346
2392
|
hasSession() {
|
|
2347
2393
|
return this.handle !== null;
|
|
2348
2394
|
}
|
|
2349
|
-
|
|
2395
|
+
/**
|
|
2396
|
+
* `jwt` is only needed when this session requires per-signature assertions —
|
|
2397
|
+
* it authenticates the challenge request. Passed per call rather than cached
|
|
2398
|
+
* on the instance deliberately: the backend's JWTs are shorter-lived than the
|
|
2399
|
+
* TEE session TTL, so a stored copy would go stale mid-session.
|
|
2400
|
+
*/
|
|
2401
|
+
async sign(req, jwt) {
|
|
2350
2402
|
if (!this.handle) throw new Error("no session");
|
|
2403
|
+
let gate = null;
|
|
2404
|
+
if (this.assertionPerSignature) {
|
|
2405
|
+
if (!jwt) throw new Error("sign requires a jwt when per-signature assertion is enabled");
|
|
2406
|
+
gate = await this.completeAssertion(jwt);
|
|
2407
|
+
}
|
|
2351
2408
|
const res = await fetch(`${this.teeUrl}/signer/sign`, {
|
|
2352
2409
|
method: "POST",
|
|
2353
2410
|
headers: { "Content-Type": "application/json" },
|
|
2354
|
-
body: JSON.stringify({
|
|
2411
|
+
body: JSON.stringify({
|
|
2412
|
+
handle: this.handle,
|
|
2413
|
+
...req,
|
|
2414
|
+
...gate ? { ...gate, origin: window.location.origin } : {}
|
|
2415
|
+
})
|
|
2355
2416
|
});
|
|
2356
2417
|
if (res.status === 401) {
|
|
2418
|
+
const body = await res.clone().json().catch(() => ({}));
|
|
2419
|
+
if (body?.error === "assertion_required") {
|
|
2420
|
+
throw new ExportNotAuthorizedError();
|
|
2421
|
+
}
|
|
2357
2422
|
this.handle = null;
|
|
2358
2423
|
throw new SessionExpiredError();
|
|
2359
2424
|
}
|
|
@@ -2376,10 +2441,11 @@ var TeeClient = class {
|
|
|
2376
2441
|
// src/social/session.ts
|
|
2377
2442
|
init_errors();
|
|
2378
2443
|
var SessionManager = class {
|
|
2379
|
-
constructor(events, durationMs, teeUrl) {
|
|
2444
|
+
constructor(events, durationMs, teeUrl, requireAssertionPerSignature = false) {
|
|
2380
2445
|
this.events = events;
|
|
2381
2446
|
this.durationMs = durationMs;
|
|
2382
2447
|
this.teeUrl = teeUrl;
|
|
2448
|
+
this.requireAssertionPerSignature = requireAssertionPerSignature;
|
|
2383
2449
|
this.tee = null;
|
|
2384
2450
|
this.evmAddress = null;
|
|
2385
2451
|
this.solanaAddress = null;
|
|
@@ -2399,7 +2465,18 @@ var SessionManager = class {
|
|
|
2399
2465
|
*/
|
|
2400
2466
|
async start(deviceShare, jwt, fallback) {
|
|
2401
2467
|
this.tee = new TeeClient(this.teeUrl);
|
|
2402
|
-
const verified = await this.tee.openSession(
|
|
2468
|
+
const verified = await this.tee.openSession(
|
|
2469
|
+
deviceShare,
|
|
2470
|
+
jwt,
|
|
2471
|
+
this.requireAssertionPerSignature
|
|
2472
|
+
);
|
|
2473
|
+
if (this.requireAssertionPerSignature && !this.tee.requiresAssertionPerSignature()) {
|
|
2474
|
+
void this.tee.closeSession();
|
|
2475
|
+
this.tee = null;
|
|
2476
|
+
throw new Error(
|
|
2477
|
+
"requireAssertionPerSignature was requested but this signer does not support it \u2014 refusing to open a session with weaker authority than configured."
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2403
2480
|
const addresses = verified ?? fallback;
|
|
2404
2481
|
if (!addresses) {
|
|
2405
2482
|
this.tee = null;
|
|
@@ -2518,6 +2595,12 @@ function validatePin(pin) {
|
|
|
2518
2595
|
// src/social/crypto/passkey.ts
|
|
2519
2596
|
var import_browser2 = require("@simplewebauthn/browser");
|
|
2520
2597
|
init_errors();
|
|
2598
|
+
function b64u(u) {
|
|
2599
|
+
return btoa(String.fromCharCode(...u)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
2600
|
+
}
|
|
2601
|
+
function fromB64u(s) {
|
|
2602
|
+
return Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
|
2603
|
+
}
|
|
2521
2604
|
function buf3(u) {
|
|
2522
2605
|
return u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
|
|
2523
2606
|
}
|
|
@@ -2544,6 +2627,29 @@ async function registerPasskey(opts) {
|
|
|
2544
2627
|
);
|
|
2545
2628
|
return { credentialId, prfOutput, response };
|
|
2546
2629
|
}
|
|
2630
|
+
async function authenticateWithPasskey(opts) {
|
|
2631
|
+
const optionsJSON = {
|
|
2632
|
+
...opts.optionsJSON,
|
|
2633
|
+
extensions: {
|
|
2634
|
+
...opts.optionsJSON.extensions,
|
|
2635
|
+
prf: { eval: { first: b64u(opts.prfSalt) } }
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
let response;
|
|
2639
|
+
try {
|
|
2640
|
+
response = await (0, import_browser2.startAuthentication)({ optionsJSON });
|
|
2641
|
+
} catch (e) {
|
|
2642
|
+
if (e.name === "NotAllowedError") throw new UserCancelledError();
|
|
2643
|
+
throw e;
|
|
2644
|
+
}
|
|
2645
|
+
const ext = response.clientExtensionResults;
|
|
2646
|
+
const prfResults = ext?.["prf"]?.results;
|
|
2647
|
+
let prfOutput;
|
|
2648
|
+
if (prfResults?.first) {
|
|
2649
|
+
prfOutput = typeof prfResults.first === "string" ? fromB64u(prfResults.first) : new Uint8Array(prfResults.first);
|
|
2650
|
+
}
|
|
2651
|
+
return { response, prfOutput };
|
|
2652
|
+
}
|
|
2547
2653
|
async function getPrfOutput(opts) {
|
|
2548
2654
|
const challenge = crypto.getRandomValues(new Uint8Array(32));
|
|
2549
2655
|
let cred;
|
|
@@ -2599,15 +2705,15 @@ async function verifyEmailCode(api, email, code) {
|
|
|
2599
2705
|
|
|
2600
2706
|
// src/social/signers/evm.ts
|
|
2601
2707
|
var import_ethers = require("ethers");
|
|
2602
|
-
async function signEvmTransaction(tee, chain, tx) {
|
|
2708
|
+
async function signEvmTransaction(tee, chain, tx, jwt) {
|
|
2603
2709
|
const resolved = await (0, import_ethers.resolveProperties)(tx);
|
|
2604
2710
|
const unsigned = import_ethers.Transaction.from(resolved).unsignedSerialized;
|
|
2605
2711
|
const payload = unsigned.startsWith("0x") ? unsigned.slice(2) : unsigned;
|
|
2606
2712
|
const txHash = (0, import_ethers.keccak256)("0x" + payload);
|
|
2607
|
-
return tee.sign({ chain, kind: "transaction", payload, txHash });
|
|
2713
|
+
return tee.sign({ chain, kind: "transaction", payload, txHash }, jwt);
|
|
2608
2714
|
}
|
|
2609
|
-
async function signEvmMessage(tee, chain, message) {
|
|
2610
|
-
return tee.sign({ chain, kind: "message", payload: message });
|
|
2715
|
+
async function signEvmMessage(tee, chain, message, jwt) {
|
|
2716
|
+
return tee.sign({ chain, kind: "message", payload: message }, jwt);
|
|
2611
2717
|
}
|
|
2612
2718
|
|
|
2613
2719
|
// src/social/signers/solana.ts
|
|
@@ -2620,13 +2726,13 @@ function b64(bytes) {
|
|
|
2620
2726
|
function unb64(s) {
|
|
2621
2727
|
return Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
2622
2728
|
}
|
|
2623
|
-
async function signSolanaTransaction(tee, chain, tx) {
|
|
2729
|
+
async function signSolanaTransaction(tee, chain, tx, jwt) {
|
|
2624
2730
|
const bytes = tx instanceof import_web3.VersionedTransaction ? tx.serialize() : tx.serialize({ requireAllSignatures: false });
|
|
2625
|
-
const signed = await tee.sign({ chain, kind: "transaction", payload: b64(bytes) });
|
|
2731
|
+
const signed = await tee.sign({ chain, kind: "transaction", payload: b64(bytes) }, jwt);
|
|
2626
2732
|
return unb64(signed);
|
|
2627
2733
|
}
|
|
2628
|
-
async function signSolanaMessage(tee, chain, message) {
|
|
2629
|
-
const sig = await tee.sign({ chain, kind: "message", payload: b64(message) });
|
|
2734
|
+
async function signSolanaMessage(tee, chain, message, jwt) {
|
|
2735
|
+
const sig = await tee.sign({ chain, kind: "message", payload: b64(message) }, jwt);
|
|
2630
2736
|
return unb64(sig);
|
|
2631
2737
|
}
|
|
2632
2738
|
|
|
@@ -2886,6 +2992,7 @@ function clearPersisted(key) {
|
|
|
2886
2992
|
}
|
|
2887
2993
|
}
|
|
2888
2994
|
async function createDecaneConnect(config) {
|
|
2995
|
+
if (!config.appId) throw new Error("decane-connect-kit: appId is required");
|
|
2889
2996
|
if (!config.apiKey) throw new Error("decane-connect-kit: apiKey is required");
|
|
2890
2997
|
if (!config.chains?.length)
|
|
2891
2998
|
throw new Error("decane-connect-kit: at least one chain is required");
|
|
@@ -2896,9 +3003,14 @@ async function createDecaneConnect(config) {
|
|
|
2896
3003
|
const PERSIST_KEY = makePersistKey(config.apiKey);
|
|
2897
3004
|
const teeUrl = config.teeUrl ?? DEFAULT_TEE_URL;
|
|
2898
3005
|
const events = new EventEmitter();
|
|
2899
|
-
const api = new ApiClient(baseUrl, config.apiKey);
|
|
3006
|
+
const api = new ApiClient(baseUrl, config.apiKey, config.appId);
|
|
2900
3007
|
const storage = new Storage();
|
|
2901
|
-
const session = new SessionManager(
|
|
3008
|
+
const session = new SessionManager(
|
|
3009
|
+
events,
|
|
3010
|
+
durationMs,
|
|
3011
|
+
teeUrl,
|
|
3012
|
+
config.requireAssertionPerSignature === true
|
|
3013
|
+
);
|
|
2902
3014
|
let auth = null;
|
|
2903
3015
|
let persisted = loadPersisted(PERSIST_KEY);
|
|
2904
3016
|
async function pinSetup() {
|
|
@@ -3146,6 +3258,45 @@ async function createDecaneConnect(config) {
|
|
|
3146
3258
|
}
|
|
3147
3259
|
return addresses;
|
|
3148
3260
|
}
|
|
3261
|
+
async function passkeyLoginCandidate() {
|
|
3262
|
+
if (!persisted?.userId || !isPasskeySupported()) return null;
|
|
3263
|
+
const stored = await storage.getShare(persisted.userId);
|
|
3264
|
+
if (!stored || stored.unlockMethod !== "passkey") return null;
|
|
3265
|
+
if (!stored.credentialId || !stored.prfSalt) return null;
|
|
3266
|
+
return stored;
|
|
3267
|
+
}
|
|
3268
|
+
async function passkeyLogin(stored) {
|
|
3269
|
+
const userId = stored.userId;
|
|
3270
|
+
const options = await api.getWebAuthnLoginOptions(userId, rpId);
|
|
3271
|
+
if (!options) {
|
|
3272
|
+
throw new NewDeviceError();
|
|
3273
|
+
}
|
|
3274
|
+
const { response, prfOutput } = await authenticateWithPasskey({
|
|
3275
|
+
optionsJSON: options,
|
|
3276
|
+
prfSalt: stored.prfSalt
|
|
3277
|
+
});
|
|
3278
|
+
if (!prfOutput) throw new ShareCorruptedError();
|
|
3279
|
+
const result = await api.postWebAuthnLogin(userId, response, window.location.origin);
|
|
3280
|
+
let deviceShare;
|
|
3281
|
+
try {
|
|
3282
|
+
deviceShare = await aesGcmDecrypt(
|
|
3283
|
+
await importAesKey(prfOutput),
|
|
3284
|
+
stored.wrappedDeviceShare,
|
|
3285
|
+
stored.iv
|
|
3286
|
+
);
|
|
3287
|
+
} catch {
|
|
3288
|
+
throw new ShareCorruptedError();
|
|
3289
|
+
}
|
|
3290
|
+
const rawAddresses = await api.getAddresses(result.jwt);
|
|
3291
|
+
const recordedAddresses = rawAddresses.evm && rawAddresses.solana ? { evm: rawAddresses.evm, solana: rawAddresses.solana } : null;
|
|
3292
|
+
const addresses = await session.start(deviceShare, result.jwt, recordedAddresses);
|
|
3293
|
+
zeroize(deviceShare);
|
|
3294
|
+
auth = { jwt: result.jwt, userId, addresses };
|
|
3295
|
+
persisted = { userId, addresses };
|
|
3296
|
+
savePersisted(PERSIST_KEY, persisted);
|
|
3297
|
+
events.emit("connected");
|
|
3298
|
+
return { addresses, isNewUser: false };
|
|
3299
|
+
}
|
|
3149
3300
|
async function handleAuthResult(result) {
|
|
3150
3301
|
let addresses;
|
|
3151
3302
|
if (!result.hasShare) {
|
|
@@ -3164,6 +3315,15 @@ async function createDecaneConnect(config) {
|
|
|
3164
3315
|
if (auth && session.isActive()) {
|
|
3165
3316
|
return { addresses: auth.addresses, isNewUser: false };
|
|
3166
3317
|
}
|
|
3318
|
+
const candidate = await passkeyLoginCandidate();
|
|
3319
|
+
if (candidate) {
|
|
3320
|
+
try {
|
|
3321
|
+
return await passkeyLogin(candidate);
|
|
3322
|
+
} catch (e) {
|
|
3323
|
+
if (e instanceof UserCancelledError) throw e;
|
|
3324
|
+
console.warn("decane-connect-kit: passkey sign-in unavailable, falling back to standard sign-in.", e);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3167
3327
|
const method = config.authMethods[0];
|
|
3168
3328
|
if (!method) throw new CallSequenceError("No authMethods configured");
|
|
3169
3329
|
if (method === "google") return client.connectWithGoogle();
|
|
@@ -3196,6 +3356,11 @@ async function createDecaneConnect(config) {
|
|
|
3196
3356
|
return new Promise(() => {
|
|
3197
3357
|
});
|
|
3198
3358
|
},
|
|
3359
|
+
async connectWithToken(token, opts) {
|
|
3360
|
+
if (!token) throw new CallSequenceError("connectWithToken requires a token");
|
|
3361
|
+
const result = await api.authExternal(token, opts?.providerId);
|
|
3362
|
+
return handleAuthResult(result);
|
|
3363
|
+
},
|
|
3199
3364
|
async connectWithEmail(email) {
|
|
3200
3365
|
await startEmailAuth(api, email);
|
|
3201
3366
|
return { codeRequired: true };
|
|
@@ -3204,7 +3369,22 @@ async function createDecaneConnect(config) {
|
|
|
3204
3369
|
const result = await verifyEmailCode(api, email, code);
|
|
3205
3370
|
return handleAuthResult(result);
|
|
3206
3371
|
},
|
|
3372
|
+
async canSignInWithPasskey() {
|
|
3373
|
+
return await passkeyLoginCandidate() !== null;
|
|
3374
|
+
},
|
|
3375
|
+
async signInWithPasskey() {
|
|
3376
|
+
const candidate = await passkeyLoginCandidate();
|
|
3377
|
+
if (!candidate) throw new NewDeviceError();
|
|
3378
|
+
return passkeyLogin(candidate);
|
|
3379
|
+
},
|
|
3207
3380
|
async unlock() {
|
|
3381
|
+
if (!auth) {
|
|
3382
|
+
const candidate = await passkeyLoginCandidate();
|
|
3383
|
+
if (candidate) {
|
|
3384
|
+
await passkeyLogin(candidate);
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3208
3388
|
if (!auth) {
|
|
3209
3389
|
throw persisted ? new ReconnectRequiredError() : new CallSequenceError("Not connected \u2014 call connect() first");
|
|
3210
3390
|
}
|
|
@@ -3229,11 +3409,11 @@ async function createDecaneConnect(config) {
|
|
|
3229
3409
|
async signMessage(req) {
|
|
3230
3410
|
const tee = session.requireActive();
|
|
3231
3411
|
if (req.chain.startsWith("evm:")) {
|
|
3232
|
-
return signEvmMessage(tee, req.chain, req.message);
|
|
3412
|
+
return signEvmMessage(tee, req.chain, req.message, auth?.jwt);
|
|
3233
3413
|
}
|
|
3234
3414
|
if (req.chain.startsWith("solana:")) {
|
|
3235
3415
|
const msg = typeof req.message === "string" ? new TextEncoder().encode(req.message) : req.message;
|
|
3236
|
-
const sig = await signSolanaMessage(tee, req.chain, msg);
|
|
3416
|
+
const sig = await signSolanaMessage(tee, req.chain, msg, auth?.jwt);
|
|
3237
3417
|
return bufferToB64u(sig);
|
|
3238
3418
|
}
|
|
3239
3419
|
throw new CallSequenceError(`Unsupported chain: ${req.chain}`);
|
|
@@ -3281,7 +3461,7 @@ async function createDecaneConnect(config) {
|
|
|
3281
3461
|
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? 0n
|
|
3282
3462
|
} : { gasPrice: req.gasPrice ?? feeData.gasPrice ?? void 0 }
|
|
3283
3463
|
};
|
|
3284
|
-
const signedTx = await signEvmTransaction(tee, req.chain, tx);
|
|
3464
|
+
const signedTx = await signEvmTransaction(tee, req.chain, tx, auth?.jwt);
|
|
3285
3465
|
const txResponse = await provider.broadcastTransaction(signedTx);
|
|
3286
3466
|
return txResponse.hash;
|
|
3287
3467
|
},
|
|
@@ -3294,7 +3474,7 @@ async function createDecaneConnect(config) {
|
|
|
3294
3474
|
tx = import_web32.Transaction.from(serializedTx);
|
|
3295
3475
|
}
|
|
3296
3476
|
const solChain = config.chains.find((c) => c.startsWith("solana:")) ?? "solana:mainnet";
|
|
3297
|
-
return signSolanaTransaction(tee, solChain, tx);
|
|
3477
|
+
return signSolanaTransaction(tee, solChain, tx, auth?.jwt);
|
|
3298
3478
|
},
|
|
3299
3479
|
async disconnect() {
|
|
3300
3480
|
session.end();
|
|
@@ -3407,8 +3587,8 @@ async function createDecaneConnect(config) {
|
|
|
3407
3587
|
function bufferToB64u(buf6) {
|
|
3408
3588
|
return btoa(String.fromCharCode(...buf6)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
3409
3589
|
}
|
|
3410
|
-
function b64uToBuffer(
|
|
3411
|
-
const b644 =
|
|
3590
|
+
function b64uToBuffer(b64u2) {
|
|
3591
|
+
const b644 = b64u2.replace(/-/g, "+").replace(/_/g, "/");
|
|
3412
3592
|
return Uint8Array.from(atob(b644), (c) => c.charCodeAt(0));
|
|
3413
3593
|
}
|
|
3414
3594
|
function toHex(bytes) {
|
|
@@ -3795,11 +3975,16 @@ function WalletSelector({
|
|
|
3795
3975
|
socialAuthMethods = ["google", "email"],
|
|
3796
3976
|
onGoogleSignIn,
|
|
3797
3977
|
onEmailStart,
|
|
3798
|
-
onEmailVerify
|
|
3978
|
+
onEmailVerify,
|
|
3979
|
+
customAuthLabel,
|
|
3980
|
+
onCustomAuthSignIn,
|
|
3981
|
+
canUsePasskey = false,
|
|
3982
|
+
passkeyAddress = null,
|
|
3983
|
+
onPasskeySignIn
|
|
3799
3984
|
}) {
|
|
3800
3985
|
const showSocial = hasSocialMode(mode);
|
|
3801
3986
|
const showWallets = mode !== "social";
|
|
3802
|
-
const [view, setView] = (0, import_react2.useState)(
|
|
3987
|
+
const [view, setView] = (0, import_react2.useState)(showSocial ? "social" : "wallets");
|
|
3803
3988
|
const [search, setSearch] = (0, import_react2.useState)("");
|
|
3804
3989
|
const [filter, setFilter] = (0, import_react2.useState)("all");
|
|
3805
3990
|
const [pendingWallet, setPendingWallet] = (0, import_react2.useState)(null);
|
|
@@ -3808,6 +3993,8 @@ function WalletSelector({
|
|
|
3808
3993
|
const [codeVal, setCodeVal] = (0, import_react2.useState)("");
|
|
3809
3994
|
const [socialLoad, setSocialLoad] = (0, import_react2.useState)(false);
|
|
3810
3995
|
const [socialErr, setSocialErr] = (0, import_react2.useState)("");
|
|
3996
|
+
const [showOtherMethods, setShowOtherMethods] = (0, import_react2.useState)(false);
|
|
3997
|
+
const passkeyFirst = canUsePasskey && !!onPasskeySignIn && !showOtherMethods;
|
|
3811
3998
|
(0, import_react2.useEffect)(() => {
|
|
3812
3999
|
if (!open) return;
|
|
3813
4000
|
injectFont();
|
|
@@ -3823,17 +4010,36 @@ function WalletSelector({
|
|
|
3823
4010
|
setSearch("");
|
|
3824
4011
|
setFilter("all");
|
|
3825
4012
|
setPendingWallet(null);
|
|
3826
|
-
setView(
|
|
4013
|
+
setView(showSocial ? "social" : "wallets");
|
|
3827
4014
|
setEmailStep("input");
|
|
3828
4015
|
setEmailVal("");
|
|
3829
4016
|
setCodeVal("");
|
|
3830
4017
|
setSocialErr("");
|
|
4018
|
+
setShowOtherMethods(false);
|
|
3831
4019
|
return;
|
|
3832
4020
|
}
|
|
3833
4021
|
return () => {
|
|
3834
4022
|
document.getElementById("decane-tokens")?.remove();
|
|
3835
4023
|
};
|
|
3836
4024
|
}, [open, showWallets]);
|
|
4025
|
+
async function handlePasskeySignIn() {
|
|
4026
|
+
if (!onPasskeySignIn) return;
|
|
4027
|
+
setSocialLoad(true);
|
|
4028
|
+
setSocialErr("");
|
|
4029
|
+
try {
|
|
4030
|
+
await onPasskeySignIn();
|
|
4031
|
+
} catch (e) {
|
|
4032
|
+
const err = e;
|
|
4033
|
+
if (err?.name !== "UserCancelledError") {
|
|
4034
|
+
setSocialErr(err?.message ?? "Passkey sign-in failed");
|
|
4035
|
+
setShowOtherMethods(true);
|
|
4036
|
+
}
|
|
4037
|
+
setSocialLoad(false);
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
async function handlePasskeyClick() {
|
|
4041
|
+
void handlePasskeySignIn();
|
|
4042
|
+
}
|
|
3837
4043
|
async function handleGoogleClick() {
|
|
3838
4044
|
if (!onGoogleSignIn) return;
|
|
3839
4045
|
setSocialLoad(true);
|
|
@@ -3845,6 +4051,17 @@ function WalletSelector({
|
|
|
3845
4051
|
setSocialLoad(false);
|
|
3846
4052
|
}
|
|
3847
4053
|
}
|
|
4054
|
+
async function handleCustomAuthClick() {
|
|
4055
|
+
if (!onCustomAuthSignIn) return;
|
|
4056
|
+
setSocialLoad(true);
|
|
4057
|
+
setSocialErr("");
|
|
4058
|
+
try {
|
|
4059
|
+
await onCustomAuthSignIn();
|
|
4060
|
+
} catch (e) {
|
|
4061
|
+
setSocialErr(e.message);
|
|
4062
|
+
setSocialLoad(false);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
3848
4065
|
async function handleEmailStart(e) {
|
|
3849
4066
|
e.preventDefault();
|
|
3850
4067
|
if (!onEmailStart) return;
|
|
@@ -4119,7 +4336,7 @@ function WalletSelector({
|
|
|
4119
4336
|
}
|
|
4120
4337
|
},
|
|
4121
4338
|
/* @__PURE__ */ import_react2.default.createElement("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M1.5 1.5L10.5 10.5M10.5 1.5L1.5 10.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }))
|
|
4122
|
-
)), showWallets && showSocial && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 14, background: "var(--dc-surface)", borderRadius: 10, padding: 3 } }, ["
|
|
4339
|
+
)), showWallets && showSocial && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 14, background: "var(--dc-surface)", borderRadius: 10, padding: 3 } }, ["social", "wallets"].map((v) => /* @__PURE__ */ import_react2.default.createElement(
|
|
4123
4340
|
"button",
|
|
4124
4341
|
{
|
|
4125
4342
|
key: v,
|
|
@@ -4178,7 +4395,53 @@ function WalletSelector({
|
|
|
4178
4395
|
e.currentTarget.style.borderColor = "var(--dc-border)";
|
|
4179
4396
|
}
|
|
4180
4397
|
}
|
|
4181
|
-
))), view === "social" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "4px 20px 24px", display: "flex", flexDirection: "column", gap: 10 } }, socialErr && /* @__PURE__ */ import_react2.default.createElement("div", { style: { background: "rgba(204,51,0,0.1)", border: "1px solid var(--dc-danger)", borderRadius: 8, padding: "9px 12px", fontSize: 12, color: "var(--dc-danger)" } }, socialErr),
|
|
4398
|
+
))), view === "social" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "4px 20px 24px", display: "flex", flexDirection: "column", gap: 10 } }, socialErr && /* @__PURE__ */ import_react2.default.createElement("div", { style: { background: "rgba(204,51,0,0.1)", border: "1px solid var(--dc-danger)", borderRadius: 8, padding: "9px 12px", fontSize: 12, color: "var(--dc-danger)" } }, socialErr), passkeyFirst && /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement(
|
|
4399
|
+
"button",
|
|
4400
|
+
{
|
|
4401
|
+
onClick: handlePasskeyClick,
|
|
4402
|
+
disabled: socialLoad,
|
|
4403
|
+
style: {
|
|
4404
|
+
display: "flex",
|
|
4405
|
+
alignItems: "center",
|
|
4406
|
+
justifyContent: "center",
|
|
4407
|
+
gap: 10,
|
|
4408
|
+
width: "100%",
|
|
4409
|
+
padding: "13px 16px",
|
|
4410
|
+
background: "var(--dc-accent)",
|
|
4411
|
+
border: "none",
|
|
4412
|
+
borderRadius: 10,
|
|
4413
|
+
cursor: socialLoad ? "not-allowed" : "pointer",
|
|
4414
|
+
color: "var(--dc-accent-text)",
|
|
4415
|
+
fontSize: 14,
|
|
4416
|
+
fontWeight: 700,
|
|
4417
|
+
fontFamily: "inherit",
|
|
4418
|
+
opacity: socialLoad ? 0.6 : 1
|
|
4419
|
+
}
|
|
4420
|
+
},
|
|
4421
|
+
/* @__PURE__ */ import_react2.default.createElement("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "8", r: "4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M6 21v-1a4 4 0 0 1 4-4h1" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M17 15v6M14.5 18h5" })),
|
|
4422
|
+
socialLoad ? "Verifying\u2026" : "Continue with passkey"
|
|
4423
|
+
), passkeyAddress && /* @__PURE__ */ import_react2.default.createElement("div", { style: { textAlign: "center", fontSize: 12, color: "var(--dc-text-muted)" } }, "Resuming ", passkeyAddress.slice(0, 6), "\u2026", passkeyAddress.slice(-4)), /* @__PURE__ */ import_react2.default.createElement(
|
|
4424
|
+
"button",
|
|
4425
|
+
{
|
|
4426
|
+
onClick: () => {
|
|
4427
|
+
setShowOtherMethods(true);
|
|
4428
|
+
setSocialErr("");
|
|
4429
|
+
},
|
|
4430
|
+
disabled: socialLoad,
|
|
4431
|
+
style: {
|
|
4432
|
+
background: "none",
|
|
4433
|
+
border: "none",
|
|
4434
|
+
padding: "6px 0",
|
|
4435
|
+
color: "var(--dc-text-muted)",
|
|
4436
|
+
fontSize: 12.5,
|
|
4437
|
+
cursor: socialLoad ? "not-allowed" : "pointer",
|
|
4438
|
+
fontFamily: "inherit",
|
|
4439
|
+
textDecoration: "underline",
|
|
4440
|
+
textUnderlineOffset: 3
|
|
4441
|
+
}
|
|
4442
|
+
},
|
|
4443
|
+
"Use a different account"
|
|
4444
|
+
)), !passkeyFirst && socialAuthMethods.includes("google") && /* @__PURE__ */ import_react2.default.createElement(
|
|
4182
4445
|
"button",
|
|
4183
4446
|
{
|
|
4184
4447
|
onClick: handleGoogleClick,
|
|
@@ -4210,7 +4473,7 @@ function WalletSelector({
|
|
|
4210
4473
|
},
|
|
4211
4474
|
/* @__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" })),
|
|
4212
4475
|
socialLoad ? "Redirecting\u2026" : "Continue with Google"
|
|
4213
|
-
), 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)" } })), 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(
|
|
4476
|
+
), !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(
|
|
4214
4477
|
"input",
|
|
4215
4478
|
{
|
|
4216
4479
|
type: "email",
|
|
@@ -4329,7 +4592,38 @@ function WalletSelector({
|
|
|
4329
4592
|
}
|
|
4330
4593
|
},
|
|
4331
4594
|
socialLoad ? "Verifying\u2026" : "Verify"
|
|
4332
|
-
))))
|
|
4595
|
+
)))), !passkeyFirst && customAuthLabel && onCustomAuthSignIn && /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, (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"), /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), /* @__PURE__ */ import_react2.default.createElement(
|
|
4596
|
+
"button",
|
|
4597
|
+
{
|
|
4598
|
+
onClick: handleCustomAuthClick,
|
|
4599
|
+
disabled: socialLoad,
|
|
4600
|
+
style: {
|
|
4601
|
+
display: "flex",
|
|
4602
|
+
alignItems: "center",
|
|
4603
|
+
justifyContent: "center",
|
|
4604
|
+
gap: 10,
|
|
4605
|
+
width: "100%",
|
|
4606
|
+
padding: "11px 16px",
|
|
4607
|
+
background: "var(--dc-surface)",
|
|
4608
|
+
border: "1px solid var(--dc-border)",
|
|
4609
|
+
borderRadius: 10,
|
|
4610
|
+
cursor: socialLoad ? "not-allowed" : "pointer",
|
|
4611
|
+
fontSize: 14,
|
|
4612
|
+
fontWeight: 600,
|
|
4613
|
+
color: "var(--dc-text)",
|
|
4614
|
+
fontFamily: "inherit",
|
|
4615
|
+
transition: "border-color 0.12s, background 0.12s",
|
|
4616
|
+
opacity: socialLoad ? 0.6 : 1
|
|
4617
|
+
},
|
|
4618
|
+
onMouseEnter: (e) => {
|
|
4619
|
+
if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
|
|
4620
|
+
},
|
|
4621
|
+
onMouseLeave: (e) => {
|
|
4622
|
+
e.currentTarget.style.borderColor = "var(--dc-border)";
|
|
4623
|
+
}
|
|
4624
|
+
},
|
|
4625
|
+
socialLoad ? "Connecting\u2026" : customAuthLabel
|
|
4626
|
+
))), view === "wallets" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ import_react2.default.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ import_react2.default.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), view === "wallets" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "10px 20px 14px", borderTop: "1px solid var(--dc-border)", textAlign: "center", fontSize: 11, color: "var(--dc-text-muted)" } }, "Don't see your wallet? Make sure it's installed and unlocked"))
|
|
4333
4627
|
)
|
|
4334
4628
|
)
|
|
4335
4629
|
);
|
|
@@ -4451,13 +4745,16 @@ function SignInView({
|
|
|
4451
4745
|
onEmailStart,
|
|
4452
4746
|
onEmailVerify,
|
|
4453
4747
|
authMethods,
|
|
4454
|
-
resumeHint
|
|
4748
|
+
resumeHint,
|
|
4749
|
+
customAuthLabel,
|
|
4750
|
+
onCustomAuthSignIn
|
|
4455
4751
|
}) {
|
|
4456
4752
|
const [googleLoading, setGoogleLoading] = (0, import_react3.useState)(false);
|
|
4457
4753
|
const [email, setEmail] = (0, import_react3.useState)("");
|
|
4458
4754
|
const [code, setCode] = (0, import_react3.useState)("");
|
|
4459
4755
|
const [emailSent, setEmailSent] = (0, import_react3.useState)(false);
|
|
4460
4756
|
const [emailLoading, setEmailLoading] = (0, import_react3.useState)(false);
|
|
4757
|
+
const [customLoading, setCustomLoading] = (0, import_react3.useState)(false);
|
|
4461
4758
|
const [error, setError] = (0, import_react3.useState)("");
|
|
4462
4759
|
async function handleGoogle() {
|
|
4463
4760
|
setError("");
|
|
@@ -4494,6 +4791,18 @@ function SignInView({
|
|
|
4494
4791
|
setEmailLoading(false);
|
|
4495
4792
|
}
|
|
4496
4793
|
}
|
|
4794
|
+
async function handleCustomAuth() {
|
|
4795
|
+
if (!onCustomAuthSignIn) return;
|
|
4796
|
+
setError("");
|
|
4797
|
+
setCustomLoading(true);
|
|
4798
|
+
try {
|
|
4799
|
+
await onCustomAuthSignIn();
|
|
4800
|
+
} catch (e) {
|
|
4801
|
+
setError(e.message);
|
|
4802
|
+
} finally {
|
|
4803
|
+
setCustomLoading(false);
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4497
4806
|
const inputStyle = {
|
|
4498
4807
|
width: "100%",
|
|
4499
4808
|
background: "var(--dc-surface)",
|
|
@@ -4566,7 +4875,27 @@ function SignInView({
|
|
|
4566
4875
|
fontWeight: 700,
|
|
4567
4876
|
cursor: "pointer",
|
|
4568
4877
|
fontFamily: "inherit"
|
|
4569
|
-
} }, emailLoading ? "\u2026" : "Verify"))),
|
|
4878
|
+
} }, emailLoading ? "\u2026" : "Verify"))), customAuthLabel && onCustomAuthSignIn && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, (authMethods.includes("google") || authMethods.includes("email")) && /* @__PURE__ */ import_react3.default.createElement("div", { style: { textAlign: "center", color: "var(--dc-text-muted)", fontSize: 12, margin: "10px 0" } }, "\u2014 or \u2014"), /* @__PURE__ */ import_react3.default.createElement(
|
|
4879
|
+
"button",
|
|
4880
|
+
{
|
|
4881
|
+
onClick: handleCustomAuth,
|
|
4882
|
+
disabled: customLoading,
|
|
4883
|
+
style: {
|
|
4884
|
+
width: "100%",
|
|
4885
|
+
padding: "10px 0",
|
|
4886
|
+
background: "var(--dc-surface)",
|
|
4887
|
+
color: "var(--dc-text)",
|
|
4888
|
+
border: "1px solid var(--dc-border)",
|
|
4889
|
+
borderRadius: 10,
|
|
4890
|
+
fontSize: 13,
|
|
4891
|
+
fontWeight: 600,
|
|
4892
|
+
cursor: customLoading ? "not-allowed" : "pointer",
|
|
4893
|
+
opacity: customLoading ? 0.7 : 1,
|
|
4894
|
+
fontFamily: "inherit"
|
|
4895
|
+
}
|
|
4896
|
+
},
|
|
4897
|
+
customLoading ? "Connecting\u2026" : customAuthLabel
|
|
4898
|
+
)), error && /* @__PURE__ */ import_react3.default.createElement("div", { style: { marginTop: 10, fontSize: 12, color: "var(--dc-danger, #ff5533)" } }, error));
|
|
4570
4899
|
}
|
|
4571
4900
|
function RecoverySection({
|
|
4572
4901
|
addresses,
|
|
@@ -4683,6 +5012,8 @@ function SocialWalletModal({
|
|
|
4683
5012
|
onEmailStart,
|
|
4684
5013
|
onEmailVerify,
|
|
4685
5014
|
authMethods = ["google", "email"],
|
|
5015
|
+
customAuthLabel,
|
|
5016
|
+
onCustomAuthSignIn,
|
|
4686
5017
|
onRotateShares,
|
|
4687
5018
|
onExportPortableBackup,
|
|
4688
5019
|
rpcUrls,
|
|
@@ -4838,7 +5169,9 @@ function SocialWalletModal({
|
|
|
4838
5169
|
onEmailStart,
|
|
4839
5170
|
onEmailVerify,
|
|
4840
5171
|
authMethods,
|
|
4841
|
-
resumeHint: socialAuth.needsReconnect ? "your wallet" : void 0
|
|
5172
|
+
resumeHint: socialAuth.needsReconnect ? "your wallet" : void 0,
|
|
5173
|
+
customAuthLabel,
|
|
5174
|
+
onCustomAuthSignIn
|
|
4842
5175
|
}
|
|
4843
5176
|
) : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("div", { style: {
|
|
4844
5177
|
margin: "0 16px 14px",
|
|
@@ -4924,7 +5257,8 @@ var DEFAULT_SOCIAL = {
|
|
|
4924
5257
|
isNewUser: false,
|
|
4925
5258
|
isUnlocked: false,
|
|
4926
5259
|
sessionExpiresAt: null,
|
|
4927
|
-
needsReconnect: false
|
|
5260
|
+
needsReconnect: false,
|
|
5261
|
+
canUsePasskey: false
|
|
4928
5262
|
};
|
|
4929
5263
|
var DecaneContext = (0, import_react4.createContext)(null);
|
|
4930
5264
|
function hasSocial(mode) {
|
|
@@ -4939,6 +5273,16 @@ function deriveNetworks(mode, explicit) {
|
|
|
4939
5273
|
}
|
|
4940
5274
|
function DecaneKit({ config, children }) {
|
|
4941
5275
|
const mode = config?.mode ?? "wallets";
|
|
5276
|
+
if (!config?.appId) {
|
|
5277
|
+
throw new Error(
|
|
5278
|
+
"DecaneKit: config.appId is required. Pass your dashboard project ID."
|
|
5279
|
+
);
|
|
5280
|
+
}
|
|
5281
|
+
if (hasSocial(mode) && !config.social?.apiKey) {
|
|
5282
|
+
throw new Error(
|
|
5283
|
+
`DecaneKit: config.social.apiKey is required for social sign-in (mode "${mode}"). Create an API key for this project in the dashboard.`
|
|
5284
|
+
);
|
|
5285
|
+
}
|
|
4942
5286
|
const walletRef = (0, import_react4.useRef)(null);
|
|
4943
5287
|
if (!walletRef.current) walletRef.current = createDecane(config);
|
|
4944
5288
|
const socialRef = (0, import_react4.useRef)(null);
|
|
@@ -4969,6 +5313,7 @@ function DecaneKit({ config, children }) {
|
|
|
4969
5313
|
socialInitialized.current = true;
|
|
4970
5314
|
const { apiKey, authMethods, promptPin, apiBaseUrl, chains, rpcUrls } = config.social;
|
|
4971
5315
|
createDecaneConnect({
|
|
5316
|
+
appId: config.appId,
|
|
4972
5317
|
apiKey,
|
|
4973
5318
|
apiBaseUrl,
|
|
4974
5319
|
authMethods: authMethods ?? ["google", "email"],
|
|
@@ -4986,6 +5331,8 @@ function DecaneKit({ config, children }) {
|
|
|
4986
5331
|
if (saved) {
|
|
4987
5332
|
setSocialAuth((p) => ({ ...p, addresses: saved, isUnlocked: false, needsReconnect: true }));
|
|
4988
5333
|
}
|
|
5334
|
+
sdk.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
|
|
5335
|
+
});
|
|
4989
5336
|
const params = new URLSearchParams(window.location.search);
|
|
4990
5337
|
if (params.has("decane_jwt") || params.has("decane_error")) {
|
|
4991
5338
|
sdk.connectWithGoogle().then(applyResult).catch(console.error);
|
|
@@ -4999,9 +5346,20 @@ function DecaneKit({ config, children }) {
|
|
|
4999
5346
|
isNewUser: result.isNewUser,
|
|
5000
5347
|
isUnlocked: sdk?.isUnlocked() ?? false,
|
|
5001
5348
|
sessionExpiresAt: sdk?.sessionExpiresAt() ?? null,
|
|
5002
|
-
needsReconnect: false
|
|
5349
|
+
needsReconnect: false,
|
|
5350
|
+
canUsePasskey: false
|
|
5351
|
+
});
|
|
5352
|
+
sdk?.canSignInWithPasskey().then((can) => setSocialAuth((p) => ({ ...p, canUsePasskey: can }))).catch(() => {
|
|
5003
5353
|
});
|
|
5004
5354
|
}
|
|
5355
|
+
const signInWithPasskey = (0, import_react4.useCallback)(async () => {
|
|
5356
|
+
const sdk = socialRef.current;
|
|
5357
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
5358
|
+
const result = await sdk.signInWithPasskey();
|
|
5359
|
+
applyResult(result);
|
|
5360
|
+
setSelectorOpen(false);
|
|
5361
|
+
return result;
|
|
5362
|
+
}, []);
|
|
5005
5363
|
const connectWithGoogle = (0, import_react4.useCallback)(async () => {
|
|
5006
5364
|
const sdk = socialRef.current;
|
|
5007
5365
|
if (!sdk) throw new Error("Social sign-in not configured");
|
|
@@ -5019,6 +5377,19 @@ function DecaneKit({ config, children }) {
|
|
|
5019
5377
|
applyResult(result);
|
|
5020
5378
|
setSelectorOpen(false);
|
|
5021
5379
|
}, []);
|
|
5380
|
+
const connectWithToken = (0, import_react4.useCallback)(async (token, opts) => {
|
|
5381
|
+
const sdk = socialRef.current;
|
|
5382
|
+
if (!sdk) throw new Error("Social sign-in not configured");
|
|
5383
|
+
const result = await sdk.connectWithToken(token, opts);
|
|
5384
|
+
applyResult(result);
|
|
5385
|
+
setSelectorOpen(false);
|
|
5386
|
+
}, []);
|
|
5387
|
+
const customAuthConfig = config?.social?.customAuth;
|
|
5388
|
+
const connectWithCustomAuth = (0, import_react4.useCallback)(async () => {
|
|
5389
|
+
if (!customAuthConfig) throw new Error("Custom auth not configured");
|
|
5390
|
+
const token = await customAuthConfig.getToken();
|
|
5391
|
+
await connectWithToken(token, customAuthConfig.providerId ? { providerId: customAuthConfig.providerId } : void 0);
|
|
5392
|
+
}, [customAuthConfig, connectWithToken]);
|
|
5022
5393
|
const socialUnlock = (0, import_react4.useCallback)(async () => {
|
|
5023
5394
|
const sdk = socialRef.current;
|
|
5024
5395
|
if (!sdk) return;
|
|
@@ -5095,9 +5466,13 @@ function DecaneKit({ config, children }) {
|
|
|
5095
5466
|
setTheme,
|
|
5096
5467
|
mode,
|
|
5097
5468
|
socialAuth,
|
|
5469
|
+
signInWithPasskey,
|
|
5098
5470
|
connectWithGoogle,
|
|
5099
5471
|
connectWithEmail,
|
|
5100
5472
|
verifyEmailCode: verifyEmailCode2,
|
|
5473
|
+
connectWithToken,
|
|
5474
|
+
customAuthLabel: customAuthConfig?.label,
|
|
5475
|
+
connectWithCustomAuth: customAuthConfig ? connectWithCustomAuth : void 0,
|
|
5101
5476
|
socialUnlock,
|
|
5102
5477
|
socialDisconnect,
|
|
5103
5478
|
socialSignMessage,
|
|
@@ -5127,7 +5502,12 @@ function DecaneKit({ config, children }) {
|
|
|
5127
5502
|
socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
|
|
5128
5503
|
onGoogleSignIn: connectWithGoogle,
|
|
5129
5504
|
onEmailStart: connectWithEmail,
|
|
5130
|
-
onEmailVerify: verifyEmailCode2
|
|
5505
|
+
onEmailVerify: verifyEmailCode2,
|
|
5506
|
+
canUsePasskey: socialAuth.canUsePasskey,
|
|
5507
|
+
passkeyAddress: socialAuth.addresses?.evm ?? socialAuth.addresses?.solana ?? null,
|
|
5508
|
+
onPasskeySignIn: signInWithPasskey,
|
|
5509
|
+
customAuthLabel: customAuthConfig?.label,
|
|
5510
|
+
onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0
|
|
5131
5511
|
}
|
|
5132
5512
|
),
|
|
5133
5513
|
/* @__PURE__ */ import_react4.default.createElement(
|
|
@@ -5139,6 +5519,8 @@ function DecaneKit({ config, children }) {
|
|
|
5139
5519
|
socialAuth,
|
|
5140
5520
|
onUnlock: socialUnlock,
|
|
5141
5521
|
onDisconnect: socialDisconnect,
|
|
5522
|
+
customAuthLabel: customAuthConfig?.label,
|
|
5523
|
+
onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
|
|
5142
5524
|
onGoogleSignIn: connectWithGoogle,
|
|
5143
5525
|
onEmailStart: connectWithEmail,
|
|
5144
5526
|
onEmailVerify: verifyEmailCode2,
|
|
@@ -5169,11 +5551,16 @@ function useSocialAuth() {
|
|
|
5169
5551
|
connectWithGoogle,
|
|
5170
5552
|
connectWithEmail,
|
|
5171
5553
|
verifyEmailCode: verifyEmailCode2,
|
|
5554
|
+
connectWithToken,
|
|
5555
|
+
customAuthLabel,
|
|
5556
|
+
connectWithCustomAuth,
|
|
5172
5557
|
socialUnlock,
|
|
5173
5558
|
socialDisconnect
|
|
5174
5559
|
} = useDecaneContext();
|
|
5175
5560
|
const [googleLoading, setGoogleLoading] = (0, import_react5.useState)(false);
|
|
5176
5561
|
const [emailLoading, setEmailLoading] = (0, import_react5.useState)(false);
|
|
5562
|
+
const [tokenLoading, setTokenLoading] = (0, import_react5.useState)(false);
|
|
5563
|
+
const [customLoading, setCustomLoading] = (0, import_react5.useState)(false);
|
|
5177
5564
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
5178
5565
|
const signInWithGoogle = (0, import_react5.useCallback)(async () => {
|
|
5179
5566
|
setGoogleLoading(true);
|
|
@@ -5210,6 +5597,31 @@ function useSocialAuth() {
|
|
|
5210
5597
|
setEmailLoading(false);
|
|
5211
5598
|
}
|
|
5212
5599
|
}, [verifyEmailCode2]);
|
|
5600
|
+
const signInWithToken = (0, import_react5.useCallback)(async (token, opts) => {
|
|
5601
|
+
setTokenLoading(true);
|
|
5602
|
+
setError(null);
|
|
5603
|
+
try {
|
|
5604
|
+
await connectWithToken(token, opts);
|
|
5605
|
+
} catch (e) {
|
|
5606
|
+
setError(e.message);
|
|
5607
|
+
throw e;
|
|
5608
|
+
} finally {
|
|
5609
|
+
setTokenLoading(false);
|
|
5610
|
+
}
|
|
5611
|
+
}, [connectWithToken]);
|
|
5612
|
+
const signInWithCustomAuth = (0, import_react5.useCallback)(async () => {
|
|
5613
|
+
if (!connectWithCustomAuth) throw new Error("Custom auth not configured");
|
|
5614
|
+
setCustomLoading(true);
|
|
5615
|
+
setError(null);
|
|
5616
|
+
try {
|
|
5617
|
+
await connectWithCustomAuth();
|
|
5618
|
+
} catch (e) {
|
|
5619
|
+
setError(e.message);
|
|
5620
|
+
throw e;
|
|
5621
|
+
} finally {
|
|
5622
|
+
setCustomLoading(false);
|
|
5623
|
+
}
|
|
5624
|
+
}, [connectWithCustomAuth]);
|
|
5213
5625
|
const unlock = (0, import_react5.useCallback)(async () => {
|
|
5214
5626
|
setError(null);
|
|
5215
5627
|
try {
|
|
@@ -5241,11 +5653,16 @@ function useSocialAuth() {
|
|
|
5241
5653
|
needsReconnect: socialAuth.needsReconnect,
|
|
5242
5654
|
googleLoading,
|
|
5243
5655
|
emailLoading,
|
|
5656
|
+
tokenLoading,
|
|
5657
|
+
customLoading,
|
|
5658
|
+
customAuthLabel,
|
|
5244
5659
|
error,
|
|
5245
5660
|
clearError: () => setError(null),
|
|
5246
5661
|
signInWithGoogle,
|
|
5247
5662
|
sendEmailCode,
|
|
5248
5663
|
confirmEmailCode,
|
|
5664
|
+
signInWithToken,
|
|
5665
|
+
signInWithCustomAuth: connectWithCustomAuth ? signInWithCustomAuth : void 0,
|
|
5249
5666
|
unlock,
|
|
5250
5667
|
disconnect
|
|
5251
5668
|
};
|