@bounded-sh/client 0.0.55 → 0.0.57
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/REACT_NATIVE.md +31 -8
- package/dist/auth/bounded-login-widget.d.ts +25 -0
- package/dist/auth/bounded-widget-modal.d.ts +33 -0
- package/dist/auth/index.d.ts +42 -0
- package/dist/auth/oidc-auth.d.ts +18 -0
- package/dist/auth/providers/wallet-login-finalize.d.ts +17 -0
- package/dist/auth/providers/wallet-standard-discovery.d.ts +23 -0
- package/dist/auth/providers/wallet-standard-tx.d.ts +7 -0
- package/dist/auth/turnkey-auth.d.ts +17 -0
- package/dist/auth/turnkey-signer-bridge.d.ts +32 -0
- package/dist/auth/turnkey-wallet-widget.d.ts +17 -0
- package/dist/{email-auth-provider-BaZceaov.js → email-auth-provider-8k8iBIQL.js} +2 -2
- package/dist/{email-auth-provider-BaZceaov.js.map → email-auth-provider-8k8iBIQL.js.map} +1 -1
- package/dist/{email-auth-provider-lVwM8vRR.js → email-auth-provider-BlJI-8w9.js} +2 -2
- package/dist/{email-auth-provider-lVwM8vRR.js.map → email-auth-provider-BlJI-8w9.js.map} +1 -1
- package/dist/{email-auth-provider-yBbGzH2e.mjs → email-auth-provider-Bmah1GWS.mjs} +2 -2
- package/dist/{email-auth-provider-yBbGzH2e.mjs.map → email-auth-provider-Bmah1GWS.mjs.map} +1 -1
- package/dist/{email-auth-provider-BKl9GgHV.mjs → email-auth-provider-CfNSS8Du.mjs} +2 -2
- package/dist/{email-auth-provider-BKl9GgHV.mjs.map → email-auth-provider-CfNSS8Du.mjs.map} +1 -1
- package/dist/{guest-auth-provider-DhN9PoAi.js → guest-auth-provider-BRjJV8Zq.js} +2 -2
- package/dist/{guest-auth-provider-DhN9PoAi.js.map → guest-auth-provider-BRjJV8Zq.js.map} +1 -1
- package/dist/{guest-auth-provider-DTKczntw.js → guest-auth-provider-BjfjrpdW.js} +2 -2
- package/dist/{guest-auth-provider-DTKczntw.js.map → guest-auth-provider-BjfjrpdW.js.map} +1 -1
- package/dist/{guest-auth-provider-BnugW3TI.mjs → guest-auth-provider-D6h4eAtx.mjs} +2 -2
- package/dist/{guest-auth-provider-Dgs8zcnC.mjs.map → guest-auth-provider-D6h4eAtx.mjs.map} +1 -1
- package/dist/{guest-auth-provider-Dgs8zcnC.mjs → guest-auth-provider-DxhZ5sWN.mjs} +2 -2
- package/dist/{guest-auth-provider-BnugW3TI.mjs.map → guest-auth-provider-DxhZ5sWN.mjs.map} +1 -1
- package/dist/{index.native-aSo8M0A8.mjs → index-C7rOe6B2.mjs} +1745 -24
- package/dist/index-C7rOe6B2.mjs.map +1 -0
- package/dist/{index.native-CouprxZp.js → index-CnObYQhV.js} +1752 -22
- package/dist/index-CnObYQhV.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{index-DkdIADby.mjs → index.native-CQiw9baS.mjs} +29 -94
- package/dist/index.native-CQiw9baS.mjs.map +1 -0
- package/dist/{index-CXVwyR6_.js → index.native-hu3o3zXU.js} +28 -96
- package/dist/index.native-hu3o3zXU.js.map +1 -0
- package/dist/index.native.d.ts +2 -0
- package/dist/index.native.js +2 -1
- package/dist/index.native.js.map +1 -1
- package/dist/index.native.mjs +1 -1
- package/dist/{injected-evm-wallet-provider-CtrS-gLC.js → injected-evm-wallet-provider-CcwS6p3o.js} +2 -2
- package/dist/{injected-evm-wallet-provider-CtrS-gLC.js.map → injected-evm-wallet-provider-CcwS6p3o.js.map} +1 -1
- package/dist/{injected-evm-wallet-provider-BgiFW_kf.mjs → injected-evm-wallet-provider-DCGnUedJ.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-BgiFW_kf.mjs.map → injected-evm-wallet-provider-DCGnUedJ.mjs.map} +1 -1
- package/dist/{injected-evm-wallet-provider-D7fd42En.mjs → injected-evm-wallet-provider-DIWeWiAN.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-D7fd42En.mjs.map → injected-evm-wallet-provider-DIWeWiAN.mjs.map} +1 -1
- package/dist/{injected-evm-wallet-provider-CiCeS1n2.js → injected-evm-wallet-provider-Xbt2CUPi.js} +2 -2
- package/dist/{injected-evm-wallet-provider-CiCeS1n2.js.map → injected-evm-wallet-provider-Xbt2CUPi.js.map} +1 -1
- package/dist/{injected-wallet-provider-CST-B9EE.js → injected-wallet-provider-7ep-jcal.js} +2 -2
- package/dist/{injected-wallet-provider-CST-B9EE.js.map → injected-wallet-provider-7ep-jcal.js.map} +1 -1
- package/dist/{injected-wallet-provider-Bic6GpDr.js → injected-wallet-provider-B_jQFifE.js} +2 -2
- package/dist/{injected-wallet-provider-Bic6GpDr.js.map → injected-wallet-provider-B_jQFifE.js.map} +1 -1
- package/dist/{injected-wallet-provider-CRG9TaA9.mjs → injected-wallet-provider-Bps6a9IP.mjs} +2 -2
- package/dist/{injected-wallet-provider-CRG9TaA9.mjs.map → injected-wallet-provider-Bps6a9IP.mjs.map} +1 -1
- package/dist/{injected-wallet-provider-BhpwmpgW.mjs → injected-wallet-provider-CFnqSsHy.mjs} +2 -2
- package/dist/{injected-wallet-provider-BhpwmpgW.mjs.map → injected-wallet-provider-CFnqSsHy.mjs.map} +1 -1
- package/dist/wallet-standard-tx-C1HJFLjC.js +30 -0
- package/dist/wallet-standard-tx-C1HJFLjC.js.map +1 -0
- package/dist/wallet-standard-tx-DBliTUWH.mjs +27 -0
- package/dist/wallet-standard-tx-DBliTUWH.mjs.map +1 -0
- package/package.json +9 -3
- package/dist/index-CXVwyR6_.js.map +0 -1
- package/dist/index-DkdIADby.mjs.map +0 -1
- package/dist/index.native-CouprxZp.js.map +0 -1
- package/dist/index.native-aSo8M0A8.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertCanonicalBoundedRuntimeWriteCalldata, getActiveSessionManager, getConfig, WebSessionManager, ReactNativeSessionManager, revokeSession, init as init$1, reconnectWithNewAuth, deriveUserIdentityFromIdToken, getIdToken as getIdToken$1, convertRemainingAccounts, buildSetDocumentsTransaction, genAuthNonce, genSolanaMessage, createSessionWithSignature } from '@bounded-sh/core';
|
|
1
|
+
import { assertCanonicalBoundedRuntimeWriteCalldata, getActiveSessionManager, getConfig, WebSessionManager, ReactNativeSessionManager, revokeSession, init as init$1, reconnectWithNewAuth, deriveUserIdentityFromIdToken, subscribe, getIdToken as getIdToken$1, convertRemainingAccounts, buildSetDocumentsTransaction, genAuthNonce, genSolanaMessage, createSessionWithSignature } from '@bounded-sh/core';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -3454,7 +3454,7 @@ const EMBEDDED_WALLET_UNSUPPORTED = 'Unsupported for embedded smart wallets —
|
|
|
3454
3454
|
const NO_EMBEDDED_WALLET = 'This login has no embedded wallet. Enable `"auth": { "wallets": true }` in ' +
|
|
3455
3455
|
'the app policy so email logins get a non-custodial wallet (@user.address), ' +
|
|
3456
3456
|
'then sign from a user gesture.';
|
|
3457
|
-
function hasDOM$
|
|
3457
|
+
function hasDOM$4() {
|
|
3458
3458
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
3459
3459
|
}
|
|
3460
3460
|
function decodeJwt$1(token) {
|
|
@@ -3503,7 +3503,7 @@ async function signerPageUrl() {
|
|
|
3503
3503
|
const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
3504
3504
|
return { url: `${base}/wallet/signer`, origin: new URL(base).origin };
|
|
3505
3505
|
}
|
|
3506
|
-
let requestSeq = 0;
|
|
3506
|
+
let requestSeq$1 = 0;
|
|
3507
3507
|
/**
|
|
3508
3508
|
* Open the Bounded signer popup, send `action` to it, and resolve with the
|
|
3509
3509
|
* submitted transaction result. Must be called from a USER GESTURE (click/tap) or
|
|
@@ -3514,7 +3514,7 @@ let requestSeq = 0;
|
|
|
3514
3514
|
* wallet's owner, and validates that the wallet address matches the account.
|
|
3515
3515
|
*/
|
|
3516
3516
|
async function signViaSignerPopup(action) {
|
|
3517
|
-
if (!hasDOM$
|
|
3517
|
+
if (!hasDOM$4()) {
|
|
3518
3518
|
throw new Error('Embedded-wallet signing is only available in the browser (popup-based).');
|
|
3519
3519
|
}
|
|
3520
3520
|
const isEvm = action.kind === 'evmTransaction';
|
|
@@ -3522,7 +3522,7 @@ async function signViaSignerPopup(action) {
|
|
|
3522
3522
|
if (!claims)
|
|
3523
3523
|
throw new Error(NO_EMBEDDED_WALLET);
|
|
3524
3524
|
const { url, origin: signerOrigin } = await signerPageUrl();
|
|
3525
|
-
const requestId = `bsign_${Date.now()}_${++requestSeq}`;
|
|
3525
|
+
const requestId = `bsign_${Date.now()}_${++requestSeq$1}`;
|
|
3526
3526
|
// Centered popup, sized for the OTP form (matches the OIDC login popup shape).
|
|
3527
3527
|
const w = 420, h = 600;
|
|
3528
3528
|
const left = window.screenX + Math.max(0, (window.outerWidth - w) / 2);
|
|
@@ -3690,7 +3690,7 @@ const STORED_AUTH_METHOD_KEY$1 = 'bounded_last_auth_method';
|
|
|
3690
3690
|
// not by `typeof window` (truthy on RN). OIDC redirect/popup is web-only, but the
|
|
3691
3691
|
// session it mints is read back through this same selector everywhere.
|
|
3692
3692
|
const sessionManager = getActiveSessionManager;
|
|
3693
|
-
function hasDOM() {
|
|
3693
|
+
function hasDOM$3() {
|
|
3694
3694
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
3695
3695
|
}
|
|
3696
3696
|
function decodeJwt(token) {
|
|
@@ -3809,6 +3809,11 @@ function appendHostedLoginHints(params, opts) {
|
|
|
3809
3809
|
const provider = normalizeMethod(opts.provider);
|
|
3810
3810
|
if (provider)
|
|
3811
3811
|
params.set('bounded_provider', provider);
|
|
3812
|
+
// login_hint: an identifier (usually an email) to prefill on the hosted page.
|
|
3813
|
+
// Kept short and free of control chars; the hosted page validates before use.
|
|
3814
|
+
const hint = String(opts.loginHint || '').trim();
|
|
3815
|
+
if (hint && hint.length <= 320 && !/[\s<>"']/.test(hint))
|
|
3816
|
+
params.set('login_hint', hint);
|
|
3812
3817
|
// Standard OIDC prompt (e.g. 'login' to force a fresh sign-in / account choice).
|
|
3813
3818
|
const prompt = normalizeMethod(opts.prompt);
|
|
3814
3819
|
if (prompt)
|
|
@@ -3825,7 +3830,7 @@ async function buildAuthorize(opts) {
|
|
|
3825
3830
|
throw new Error('appId is not configured (init the SDK with your appId)');
|
|
3826
3831
|
// Web: default to the current page so callers don't have to pass redirectUri. Native has no
|
|
3827
3832
|
// window — it MUST pass an https universal link (the system auth session needs a real URL).
|
|
3828
|
-
const redirectUri = opts.redirectUri || (hasDOM() ? window.location.origin + window.location.pathname : undefined);
|
|
3833
|
+
const redirectUri = opts.redirectUri || (hasDOM$3() ? window.location.origin + window.location.pathname : undefined);
|
|
3829
3834
|
if (!redirectUri)
|
|
3830
3835
|
throw new Error('redirectUri is required on React Native — pass the https universal link the app owner registered.');
|
|
3831
3836
|
const { verifier, challenge } = await makePkce();
|
|
@@ -3933,7 +3938,7 @@ async function runNativeLogin(opts) {
|
|
|
3933
3938
|
async function loginWithRedirect(opts) {
|
|
3934
3939
|
if (isNative())
|
|
3935
3940
|
return runNativeLogin(opts);
|
|
3936
|
-
if (!hasDOM())
|
|
3941
|
+
if (!hasDOM$3())
|
|
3937
3942
|
throw new Error('loginWithRedirect requires a browser or a configured React Native platform');
|
|
3938
3943
|
const { url, pkce } = await buildAuthorize(opts);
|
|
3939
3944
|
// Stash the verifier + state so the redirect_uri page can finish the exchange. sessionStorage
|
|
@@ -3949,7 +3954,7 @@ async function loginWithRedirect(opts) {
|
|
|
3949
3954
|
* loginWithRedirect() (which resolves with the User), so there is no separate redirect
|
|
3950
3955
|
* page to finish from. Calling it on RN is harmless. */
|
|
3951
3956
|
async function completeLoginFromRedirect() {
|
|
3952
|
-
if (!hasDOM())
|
|
3957
|
+
if (!hasDOM$3())
|
|
3953
3958
|
return null;
|
|
3954
3959
|
const url = new URL(window.location.href);
|
|
3955
3960
|
const code = url.searchParams.get('code');
|
|
@@ -3995,7 +4000,7 @@ async function completeLoginFromRedirect() {
|
|
|
3995
4000
|
* redirect_uri page posts the code back. The redirect_uri page just calls the usual
|
|
3996
4001
|
* completeLoginFromRedirect() (which auto-detects the popup) — no separate call needed. */
|
|
3997
4002
|
async function loginWithPopup(opts) {
|
|
3998
|
-
if (!hasDOM())
|
|
4003
|
+
if (!hasDOM$3())
|
|
3999
4004
|
throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
|
|
4000
4005
|
// Open synchronously, before PKCE/config awaits consume the click's user-
|
|
4001
4006
|
// activation window. Opening only after buildAuthorize() intermittently
|
|
@@ -4135,7 +4140,7 @@ async function loginWithPopup(opts) {
|
|
|
4135
4140
|
/** Call this on the redirect_uri page when login was opened via loginWithPopup(): posts the
|
|
4136
4141
|
* code back to the opener and closes. (For the redirect variant use completeLoginFromRedirect.) */
|
|
4137
4142
|
function completeLoginInPopup(openerOrigin) {
|
|
4138
|
-
if (!hasDOM() || !window.opener)
|
|
4143
|
+
if (!hasDOM$3() || !window.opener)
|
|
4139
4144
|
return;
|
|
4140
4145
|
const url = new URL(window.location.href);
|
|
4141
4146
|
const code = url.searchParams.get('code');
|
|
@@ -4151,6 +4156,34 @@ function completeLoginInPopup(openerOrigin) {
|
|
|
4151
4156
|
}
|
|
4152
4157
|
catch ( /* ignore */_b) { /* ignore */ }
|
|
4153
4158
|
}
|
|
4159
|
+
// Establish a Bounded session from a server-minted inline-session response (the same
|
|
4160
|
+
// { idToken, accessToken, refreshToken } shape as /session/refresh). Used by the
|
|
4161
|
+
// Turnkey-native login path (authMode:"turnkey"), which mints the session server-side
|
|
4162
|
+
// after Turnkey verifies the email OTP - there is no OIDC code to exchange. Wiring is
|
|
4163
|
+
// identical to exchangeCode's tail so the realtime worker cannot tell them apart.
|
|
4164
|
+
async function establishInlineSession(data) {
|
|
4165
|
+
const base = await issuerBase();
|
|
4166
|
+
const idToken = tokenField(data, 'id_token', 'idToken');
|
|
4167
|
+
if (!idToken)
|
|
4168
|
+
throw new Error('No id_token in the inline session response');
|
|
4169
|
+
const accessToken = tokenField(data, 'access_token', 'accessToken') || idToken;
|
|
4170
|
+
const refreshToken = boundedRefreshTokenField(data);
|
|
4171
|
+
const user = userFromIdToken(idToken);
|
|
4172
|
+
await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base);
|
|
4173
|
+
try {
|
|
4174
|
+
getPlatform().storage.setItem(STORED_AUTH_METHOD_KEY$1, 'email');
|
|
4175
|
+
}
|
|
4176
|
+
catch ( /* storage unavailable */_a) { /* storage unavailable */ }
|
|
4177
|
+
setCurrentUser(user);
|
|
4178
|
+
setAuthProviderInstance(oidcProviderSingleton);
|
|
4179
|
+
return user;
|
|
4180
|
+
}
|
|
4181
|
+
// Auth base + appId for the Turnkey-native inline OTP endpoints, resolved through the
|
|
4182
|
+
// same config the OIDC flow uses.
|
|
4183
|
+
async function inlineAuthEndpoint() {
|
|
4184
|
+
const cfg = await getConfig();
|
|
4185
|
+
return { base: await issuerBase(), appId: cfg.appId };
|
|
4186
|
+
}
|
|
4154
4187
|
async function exchangeCode(code, verifier, redirectUri) {
|
|
4155
4188
|
const cfg = await getConfig();
|
|
4156
4189
|
const base = await issuerBase();
|
|
@@ -4384,6 +4417,40 @@ function isWalletLoginEnabled(config) {
|
|
|
4384
4417
|
return true;
|
|
4385
4418
|
return false;
|
|
4386
4419
|
}
|
|
4420
|
+
/**
|
|
4421
|
+
* Whether the app explicitly turned the "connect wallet" lane OFF at init()
|
|
4422
|
+
* (`walletLogin: false`). This is the hard opt-OUT the unified widget honors: it
|
|
4423
|
+
* hides the wallet option entirely even when an injected wallet is detected and
|
|
4424
|
+
* even if a per-call `openBoundedWidget({ wallet: true })` asked for it.
|
|
4425
|
+
*/
|
|
4426
|
+
function isWalletLoginDisabled(config) {
|
|
4427
|
+
return config.walletLogin === false;
|
|
4428
|
+
}
|
|
4429
|
+
/**
|
|
4430
|
+
* Site policy: the app requires an email on file for every user (`requireEmail:
|
|
4431
|
+
* true`). Native wallet login carries no email, so the unified widget suppresses
|
|
4432
|
+
* the wallet lane when this is set - email/social (which yields an email) remain.
|
|
4433
|
+
* This lets a policy deny wallet-only accounts without touching per-call options.
|
|
4434
|
+
*/
|
|
4435
|
+
function isEmailRequired(config) {
|
|
4436
|
+
return config.requireEmail === true;
|
|
4437
|
+
}
|
|
4438
|
+
/** The config passed to the last `init()`, or null before init. Read-only view
|
|
4439
|
+
* for runtime surfaces (e.g. the unified login widget) that need wallet/rpc
|
|
4440
|
+
* settings without re-plumbing them through every call. */
|
|
4441
|
+
function getInitConfig() {
|
|
4442
|
+
return initConfig;
|
|
4443
|
+
}
|
|
4444
|
+
/**
|
|
4445
|
+
* Which LOGIN mode the app runs (default "bounded"). "turnkey" opts the app into
|
|
4446
|
+
* Turnkey-native email OTP: the unified widget runs the code entry INLINE (no OIDC
|
|
4447
|
+
* popup) via startTurnkeyEmailLogin(), and Bounded mints the session from Turnkey's
|
|
4448
|
+
* verification. The value comes from init() config (`authMode`); a `openBoundedWidget`
|
|
4449
|
+
* option can override it per call. Anything other than "turnkey" is "bounded".
|
|
4450
|
+
*/
|
|
4451
|
+
function resolveAuthMode(config) {
|
|
4452
|
+
return config.authMode === 'turnkey' ? 'turnkey' : 'bounded';
|
|
4453
|
+
}
|
|
4387
4454
|
/**
|
|
4388
4455
|
* Resolve the injected-wallet-config the provider should use, merging the
|
|
4389
4456
|
* `walletLogin` object form (if the app passed one) over `injectedWalletConfig`.
|
|
@@ -4443,7 +4510,7 @@ async function getAuthProvider(config) {
|
|
|
4443
4510
|
// manager as legacy email sessions. Human login should be started
|
|
4444
4511
|
// with loginWithRedirect(); this provider is kept as the restore and
|
|
4445
4512
|
// logout surface for those sessions.
|
|
4446
|
-
currentAuthProvider = new (await import('./email-auth-provider-
|
|
4513
|
+
currentAuthProvider = new (await import('./email-auth-provider-Bmah1GWS.mjs')).EmailAuthProvider();
|
|
4447
4514
|
break;
|
|
4448
4515
|
case "wallet":
|
|
4449
4516
|
case "phantom": {
|
|
@@ -4462,7 +4529,7 @@ async function getAuthProvider(config) {
|
|
|
4462
4529
|
if (!isWalletLoginEnabled(config)) {
|
|
4463
4530
|
throw walletLoginNotEnabledError(authMethod);
|
|
4464
4531
|
}
|
|
4465
|
-
const { InjectedWalletProvider } = await import('./injected-wallet-provider-
|
|
4532
|
+
const { InjectedWalletProvider } = await import('./injected-wallet-provider-CFnqSsHy.mjs');
|
|
4466
4533
|
currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
|
|
4467
4534
|
break;
|
|
4468
4535
|
}
|
|
@@ -4475,7 +4542,7 @@ async function getAuthProvider(config) {
|
|
|
4475
4542
|
// Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
|
|
4476
4543
|
// to point at a specific wallet or set chainId/domain.
|
|
4477
4544
|
const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
|
|
4478
|
-
const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-
|
|
4545
|
+
const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-DIWeWiAN.mjs');
|
|
4479
4546
|
currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
|
|
4480
4547
|
break;
|
|
4481
4548
|
}
|
|
@@ -4503,7 +4570,7 @@ async function getAuthProvider(config) {
|
|
|
4503
4570
|
break;
|
|
4504
4571
|
case "guest":
|
|
4505
4572
|
// Anonymous auth: a device-local ed25519 keypair signs the challenge.
|
|
4506
|
-
currentAuthProvider = new (await import('./guest-auth-provider-
|
|
4573
|
+
currentAuthProvider = new (await import('./guest-auth-provider-D6h4eAtx.mjs')).GuestAuthProvider();
|
|
4507
4574
|
break;
|
|
4508
4575
|
}
|
|
4509
4576
|
if (!currentAuthProvider) {
|
|
@@ -4559,7 +4626,7 @@ async function signInAnonymously() {
|
|
|
4559
4626
|
// Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
|
|
4560
4627
|
// the main bundle for data-only apps. Specifier is a static string literal
|
|
4561
4628
|
// so bundle-splitting can see it.
|
|
4562
|
-
const provider = new (await import('./guest-auth-provider-
|
|
4629
|
+
const provider = new (await import('./guest-auth-provider-D6h4eAtx.mjs')).GuestAuthProvider();
|
|
4563
4630
|
setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
|
|
4564
4631
|
const commit = (user) => {
|
|
4565
4632
|
// No await/interleaving inside this block: provider/method/config/marker
|
|
@@ -4689,6 +4756,44 @@ async function loginWithPrivy(options) {
|
|
|
4689
4756
|
const result = await provider.login();
|
|
4690
4757
|
return result ? Object.assign(Object.assign({}, result), { provider }) : null;
|
|
4691
4758
|
}
|
|
4759
|
+
/**
|
|
4760
|
+
* Log in with an injected Solana WALLET (Phantom / Solflare / Backpack / any
|
|
4761
|
+
* Wallet-Standard `window.solana`) as a co-equal RUNTIME route - selectable
|
|
4762
|
+
* alongside `login()` / hosted `loginWithPopup()` / `signInAnonymously()` WITHOUT
|
|
4763
|
+
* re-`init()`. This is the "Continue with wallet" choice the unified Bounded
|
|
4764
|
+
* login widget offers: it NEVER touches Better Auth - the user's real wallet
|
|
4765
|
+
* becomes `@user.address` and login is a SIWS signature.
|
|
4766
|
+
*
|
|
4767
|
+
* `getProvider` pins a specific discovered wallet (e.g. when the widget lists
|
|
4768
|
+
* several); omit it to use Phantom-first discovery. Must be called from a user
|
|
4769
|
+
* gesture so the wallet's connect prompt is allowed. `init()` must have run first.
|
|
4770
|
+
*/
|
|
4771
|
+
async function loginWithWallet(options) {
|
|
4772
|
+
assertBrowserAuthEnv('loginWithWallet');
|
|
4773
|
+
const coreConfig = await getConfig();
|
|
4774
|
+
const cfg = initConfig !== null && initConfig !== void 0 ? initConfig : {};
|
|
4775
|
+
const base = resolveInjectedWalletConfig(cfg);
|
|
4776
|
+
const injectedConfig = (options === null || options === void 0 ? void 0 : options.getProvider)
|
|
4777
|
+
? Object.assign(Object.assign({}, base), { getProvider: options.getProvider }) : base;
|
|
4778
|
+
const rpcUrl = normalizeSolanaRpcUrl(cfg.rpcUrl);
|
|
4779
|
+
const network = getConfiguredSolanaNetwork(cfg);
|
|
4780
|
+
const { InjectedWalletProvider } = await import('./injected-wallet-provider-CFnqSsHy.mjs');
|
|
4781
|
+
let provider = new InjectedWalletProvider(injectedConfig, rpcUrl, network);
|
|
4782
|
+
if (coreConfig.chain === 'offchain')
|
|
4783
|
+
provider = new OffchainAuthProvider(provider);
|
|
4784
|
+
// The provider stores the session + user + auth-method marker itself on success.
|
|
4785
|
+
const user = await provider.login();
|
|
4786
|
+
if (!user)
|
|
4787
|
+
return null;
|
|
4788
|
+
currentAuthProvider = provider;
|
|
4789
|
+
currentAuthMethod = 'phantom';
|
|
4790
|
+
currentAuthProviderConfigKey = authProviderConfigKey(Object.assign(Object.assign({}, cfg), { authMethod: 'phantom' }));
|
|
4791
|
+
coreConfig.authProvider = provider;
|
|
4792
|
+
coreConfig.authMethod = 'phantom';
|
|
4793
|
+
setAuthProviderInstance(provider);
|
|
4794
|
+
setStoredAuthMethod('phantom');
|
|
4795
|
+
return Object.assign(Object.assign({}, user), { provider });
|
|
4796
|
+
}
|
|
4692
4797
|
async function login$1(options) {
|
|
4693
4798
|
// Re-initialize provider if it was cleared by logout
|
|
4694
4799
|
if (!currentAuthProvider && initConfig) {
|
|
@@ -4825,6 +4930,27 @@ async function logout$1(options) {
|
|
|
4825
4930
|
}
|
|
4826
4931
|
}
|
|
4827
4932
|
|
|
4933
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
4934
|
+
__proto__: null,
|
|
4935
|
+
clearIncompatibleSession: clearIncompatibleSession,
|
|
4936
|
+
completeLoginFromRedirect: completeLoginFromRedirect,
|
|
4937
|
+
completeLoginInPopup: completeLoginInPopup,
|
|
4938
|
+
getAuthProvider: getAuthProvider,
|
|
4939
|
+
getCurrentAuthMethod: getCurrentAuthMethod,
|
|
4940
|
+
getInitConfig: getInitConfig,
|
|
4941
|
+
isEmailRequired: isEmailRequired,
|
|
4942
|
+
isWalletLoginDisabled: isWalletLoginDisabled,
|
|
4943
|
+
isWalletLoginEnabled: isWalletLoginEnabled,
|
|
4944
|
+
login: login$1,
|
|
4945
|
+
loginWithPopup: loginWithPopup,
|
|
4946
|
+
loginWithPrivy: loginWithPrivy,
|
|
4947
|
+
loginWithRedirect: loginWithRedirect,
|
|
4948
|
+
loginWithWallet: loginWithWallet,
|
|
4949
|
+
logout: logout$1,
|
|
4950
|
+
resolveAuthMode: resolveAuthMode,
|
|
4951
|
+
signInAnonymously: signInAnonymously
|
|
4952
|
+
});
|
|
4953
|
+
|
|
4828
4954
|
/**
|
|
4829
4955
|
* Read the active session's idToken synchronously from whichever store is live
|
|
4830
4956
|
* for this runtime (WebSessionManager on web, ReactNativeSessionManager on RN —
|
|
@@ -5003,17 +5129,1150 @@ function setAuthProviderInstance(provider) {
|
|
|
5003
5129
|
authProviderInstance = provider;
|
|
5004
5130
|
}
|
|
5005
5131
|
|
|
5006
|
-
|
|
5132
|
+
// bounded-widget-modal.ts - the shared in-app WIDGET shell for Bounded's login
|
|
5133
|
+
// and signing surfaces. One Shadow-DOM overlay + panel that matches the Bounded
|
|
5134
|
+
// widget (Seal) look and MOTION: a backdrop fade, a panel rise on desktop, a
|
|
5135
|
+
// bottom-sheet on mobile, a height tween as content swaps, and a clean close.
|
|
5136
|
+
//
|
|
5137
|
+
// It hosts EITHER:
|
|
5138
|
+
// - NATIVE content (the login card the SDK renders itself), or
|
|
5139
|
+
// - an IFRAME (the Bounded-owned signer page, which must run on the single
|
|
5140
|
+
// signer origin because a WebAuthn passkey is bound to one origin).
|
|
5141
|
+
//
|
|
5142
|
+
// The modal is a warm singleton: it stays mounted (hidden) between uses so a
|
|
5143
|
+
// second open - or a signature right after login - is instant and, when it is
|
|
5144
|
+
// already open, a clean in-place view swap rather than a reopen.
|
|
5145
|
+
function hasDOM$2() {
|
|
5146
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
5147
|
+
}
|
|
5148
|
+
const OVERLAY_CSS = `
|
|
5149
|
+
:host{all:initial}
|
|
5150
|
+
*{box-sizing:border-box}
|
|
5151
|
+
.bw-ov{position:fixed;inset:0;z-index:2147483647;display:none;
|
|
5152
|
+
font-family:"Schibsted Grotesk","Helvetica Neue",Arial,sans-serif}
|
|
5153
|
+
.bw-ov.mounted{display:block}
|
|
5154
|
+
.bw-back{position:fixed;inset:0;background:rgba(4,4,5,.62);opacity:0;
|
|
5155
|
+
transition:opacity .24s ease;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}
|
|
5156
|
+
.bw-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:20px}
|
|
5157
|
+
.bw-panel{position:relative;width:420px;max-width:calc(100vw - 28px);height:280px;
|
|
5158
|
+
border-radius:14px;overflow:hidden;background:#0e0f11;border:1px solid rgba(232,230,222,.08);
|
|
5159
|
+
box-shadow:0 1px 0 rgba(232,230,222,.03) inset,0 40px 90px -45px rgba(0,0,0,.85);
|
|
5160
|
+
opacity:0;transform:translateY(14px) scale(.98);will-change:transform,opacity,height;
|
|
5161
|
+
transition:opacity .28s cubic-bezier(.2,.8,.2,1),transform .28s cubic-bezier(.2,.8,.2,1),
|
|
5162
|
+
height .3s cubic-bezier(.2,.8,.2,1)}
|
|
5163
|
+
.bw-ov.show .bw-back{opacity:1}
|
|
5164
|
+
.bw-ov.show .bw-panel{opacity:1;transform:none}
|
|
5165
|
+
.bw-ov.closing .bw-back{opacity:0}
|
|
5166
|
+
.bw-ov.closing .bw-panel{opacity:0;transform:translateY(12px) scale(.99)}
|
|
5167
|
+
.bw-content{position:absolute;inset:0;overflow:hidden}
|
|
5168
|
+
.bw-frame{display:block;width:100%;height:100%;border:0;background:transparent;color-scheme:dark}
|
|
5169
|
+
/* editorial corner crop-marks, matching the reference frame */
|
|
5170
|
+
.bw-mk{position:absolute;width:13px;height:13px;pointer-events:none;z-index:2;
|
|
5171
|
+
border-color:rgba(232,230,222,.22);border-style:solid;border-width:0}
|
|
5172
|
+
.bw-mk.tl{top:11px;left:11px;border-top-width:1px;border-left-width:1px}
|
|
5173
|
+
.bw-mk.tr{top:11px;right:11px;border-top-width:1px;border-right-width:1px}
|
|
5174
|
+
.bw-mk.bl{bottom:11px;left:11px;border-bottom-width:1px;border-left-width:1px}
|
|
5175
|
+
.bw-mk.br{bottom:11px;right:11px;border-bottom-width:1px;border-right-width:1px}
|
|
5176
|
+
@media(max-width:560px){
|
|
5177
|
+
.bw-wrap{align-items:flex-end;padding:0}
|
|
5178
|
+
.bw-panel{width:100%;max-width:100%;border-radius:18px 18px 0 0;border-bottom:0}
|
|
5179
|
+
.bw-content{overflow-y:auto}
|
|
5180
|
+
.bw-ov.show .bw-panel{transform:none}
|
|
5181
|
+
.bw-ov:not(.show) .bw-panel,.bw-ov.closing .bw-panel{transform:translateY(18%)}
|
|
5182
|
+
}
|
|
5183
|
+
@media(prefers-reduced-motion:reduce){.bw-back,.bw-panel{transition:opacity .12s linear!important}}
|
|
5184
|
+
`;
|
|
5185
|
+
let modal = null;
|
|
5186
|
+
function getModal() {
|
|
5187
|
+
if (modal)
|
|
5188
|
+
return modal;
|
|
5189
|
+
const root = document.createElement("div");
|
|
5190
|
+
root.setAttribute("data-bounded-widget", "");
|
|
5191
|
+
const shadow = root.attachShadow({ mode: "open" });
|
|
5192
|
+
const style = document.createElement("style");
|
|
5193
|
+
style.textContent = OVERLAY_CSS;
|
|
5194
|
+
shadow.appendChild(style);
|
|
5195
|
+
const overlay = document.createElement("div");
|
|
5196
|
+
overlay.className = "bw-ov";
|
|
5197
|
+
const back = document.createElement("div");
|
|
5198
|
+
back.className = "bw-back";
|
|
5199
|
+
const wrap = document.createElement("div");
|
|
5200
|
+
wrap.className = "bw-wrap";
|
|
5201
|
+
const panel = document.createElement("div");
|
|
5202
|
+
panel.className = "bw-panel";
|
|
5203
|
+
const content = document.createElement("div");
|
|
5204
|
+
content.className = "bw-content";
|
|
5205
|
+
panel.appendChild(content);
|
|
5206
|
+
for (const corner of ["tl", "tr", "bl", "br"]) {
|
|
5207
|
+
const mk = document.createElement("span");
|
|
5208
|
+
mk.className = "bw-mk " + corner;
|
|
5209
|
+
panel.appendChild(mk);
|
|
5210
|
+
}
|
|
5211
|
+
wrap.appendChild(panel);
|
|
5212
|
+
overlay.appendChild(back);
|
|
5213
|
+
overlay.appendChild(wrap);
|
|
5214
|
+
shadow.appendChild(overlay);
|
|
5215
|
+
(document.body || document.documentElement).appendChild(root);
|
|
5216
|
+
const m = {
|
|
5217
|
+
root,
|
|
5218
|
+
overlay,
|
|
5219
|
+
panel,
|
|
5220
|
+
content,
|
|
5221
|
+
iframe: null,
|
|
5222
|
+
iframeOrigin: "",
|
|
5223
|
+
iframeReady: false,
|
|
5224
|
+
onBackdrop: null,
|
|
5225
|
+
onIframeMessage: null,
|
|
5226
|
+
chain: Promise.resolve(),
|
|
5227
|
+
fitObserver: null,
|
|
5228
|
+
};
|
|
5229
|
+
// The dismiss handler lives on `.bw-wrap`: it is the full-viewport flex layer
|
|
5230
|
+
// that sits ABOVE `.bw-back`, so it (not the backdrop element) receives clicks
|
|
5231
|
+
// outside the panel. Fire only when the click lands on the wrap itself, never
|
|
5232
|
+
// when it bubbles up from the panel or its content.
|
|
5233
|
+
wrap.addEventListener("click", (event) => {
|
|
5234
|
+
if (event.target === wrap && m.onBackdrop)
|
|
5235
|
+
m.onBackdrop();
|
|
5236
|
+
});
|
|
5237
|
+
window.addEventListener("message", (event) => {
|
|
5238
|
+
if (!m.iframe)
|
|
5239
|
+
return;
|
|
5240
|
+
if (event.origin !== m.iframeOrigin)
|
|
5241
|
+
return;
|
|
5242
|
+
if (event.source !== m.iframe.contentWindow)
|
|
5243
|
+
return;
|
|
5244
|
+
const d = event.data;
|
|
5245
|
+
if (!d || typeof d !== "object")
|
|
5246
|
+
return;
|
|
5247
|
+
if (m.onIframeMessage)
|
|
5248
|
+
m.onIframeMessage(d);
|
|
5249
|
+
});
|
|
5250
|
+
modal = m;
|
|
5251
|
+
return m;
|
|
5252
|
+
}
|
|
5253
|
+
function showModal(m) {
|
|
5254
|
+
m.overlay.classList.remove("closing");
|
|
5255
|
+
m.overlay.classList.add("mounted");
|
|
5256
|
+
// Force a reflow so the .show transition runs from the hidden state every open.
|
|
5257
|
+
void m.panel.offsetWidth;
|
|
5258
|
+
m.overlay.classList.add("show");
|
|
5259
|
+
}
|
|
5260
|
+
function hideModal(m, after) {
|
|
5261
|
+
m.overlay.classList.remove("show");
|
|
5262
|
+
m.overlay.classList.add("closing");
|
|
5263
|
+
window.setTimeout(() => {
|
|
5264
|
+
m.overlay.classList.remove("mounted", "closing");
|
|
5265
|
+
}, 260);
|
|
5266
|
+
}
|
|
5267
|
+
function setPanelHeight(m, px) {
|
|
5268
|
+
const h = Math.max(140, Math.min(680, Math.round(px)));
|
|
5269
|
+
m.panel.style.height = h + "px";
|
|
5270
|
+
}
|
|
5271
|
+
// True outer height of the mounted card: the max of its padding-box scroll height
|
|
5272
|
+
// (catches overflowing descendants) and its border-box height plus own vertical
|
|
5273
|
+
// margins (catches border + margins, which scrollHeight omits). Ceiled so a
|
|
5274
|
+
// sub-pixel row is never clipped under `overflow:hidden`.
|
|
5275
|
+
function measureOuterHeight(target) {
|
|
5276
|
+
var _a;
|
|
5277
|
+
const cs = (_a = target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.getComputedStyle(target);
|
|
5278
|
+
const marginY = cs ? parseFloat(cs.marginTop) + parseFloat(cs.marginBottom) : 0;
|
|
5279
|
+
const borderBox = target.offsetHeight + (Number.isFinite(marginY) ? marginY : 0);
|
|
5280
|
+
const rect = target.getBoundingClientRect().height;
|
|
5281
|
+
return Math.ceil(Math.max(target.scrollHeight, borderBox, rect));
|
|
5282
|
+
}
|
|
5283
|
+
/**
|
|
5284
|
+
* Measure the currently mounted native content and tween the panel to fit it.
|
|
5285
|
+
* Re-fits automatically when the card's intrinsic height changes - notably after
|
|
5286
|
+
* the Newsreader/Schibsted web fonts swap in (which grows line boxes a few px
|
|
5287
|
+
* after first paint) and on inline view swaps (email -> OTP code -> success).
|
|
5288
|
+
*/
|
|
5289
|
+
function fitPanelToContent(m) {
|
|
5290
|
+
var _a, _b;
|
|
5291
|
+
const first = m.content.firstElementChild;
|
|
5292
|
+
const target = first || m.content;
|
|
5293
|
+
const apply = () => {
|
|
5294
|
+
// Only fit while this exact node is still the mounted card.
|
|
5295
|
+
if (m.content.firstElementChild !== first && first)
|
|
5296
|
+
return;
|
|
5297
|
+
const h = measureOuterHeight(target);
|
|
5298
|
+
if (h > 0)
|
|
5299
|
+
setPanelHeight(m, h);
|
|
5300
|
+
};
|
|
5301
|
+
apply();
|
|
5302
|
+
const view = target.ownerDocument.defaultView;
|
|
5303
|
+
// Re-fit after fonts load (one-shot) and once the ResizeObserver-less browsers
|
|
5304
|
+
// have painted, so the height never lags a late reflow.
|
|
5305
|
+
(_a = view === null || view === void 0 ? void 0 : view.requestAnimationFrame) === null || _a === void 0 ? void 0 : _a.call(view, apply);
|
|
5306
|
+
const fonts = target.ownerDocument.fonts;
|
|
5307
|
+
if (fonts === null || fonts === void 0 ? void 0 : fonts.ready)
|
|
5308
|
+
fonts.ready.then(apply).catch(() => { });
|
|
5309
|
+
(_b = m.fitObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
5310
|
+
m.fitObserver = null;
|
|
5311
|
+
if (first && typeof (view === null || view === void 0 ? void 0 : view.ResizeObserver) === "function") {
|
|
5312
|
+
const ro = new view.ResizeObserver(() => apply());
|
|
5313
|
+
ro.observe(first);
|
|
5314
|
+
m.fitObserver = ro;
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
/** Switch the modal into NATIVE-content mode: detach any iframe, mount `node`. */
|
|
5318
|
+
function mountContent(m, node) {
|
|
5319
|
+
var _a;
|
|
5320
|
+
detachIframe(m);
|
|
5321
|
+
(_a = m.fitObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
5322
|
+
m.fitObserver = null;
|
|
5323
|
+
m.content.replaceChildren(node);
|
|
5324
|
+
m.content.style.display = "block";
|
|
5325
|
+
}
|
|
5326
|
+
/** Get (or lazily create) the warm iframe for signer/hosted-page mode. */
|
|
5327
|
+
function ensureIframe(m, url, origin) {
|
|
5328
|
+
var _a;
|
|
5329
|
+
if (m.iframe && m.iframeOrigin === origin && m.iframe.src === url)
|
|
5330
|
+
return m.iframe;
|
|
5331
|
+
detachIframe(m);
|
|
5332
|
+
(_a = m.fitObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
5333
|
+
m.fitObserver = null;
|
|
5334
|
+
m.content.style.display = "none";
|
|
5335
|
+
const iframe = document.createElement("iframe");
|
|
5336
|
+
iframe.className = "bw-frame";
|
|
5337
|
+
iframe.setAttribute("title", "Bounded");
|
|
5338
|
+
// Grant the framed origin WebAuthn get + create so passkey enroll/sign run
|
|
5339
|
+
// inside the frame from the user's click (transient activation stays in-frame).
|
|
5340
|
+
iframe.setAttribute("allow", "publickey-credentials-get; publickey-credentials-create; clipboard-write");
|
|
5341
|
+
iframe.src = url;
|
|
5342
|
+
m.iframeOrigin = origin;
|
|
5343
|
+
m.iframeReady = false;
|
|
5344
|
+
m.panel.appendChild(iframe);
|
|
5345
|
+
m.iframe = iframe;
|
|
5346
|
+
return iframe;
|
|
5347
|
+
}
|
|
5348
|
+
function detachIframe(m) {
|
|
5349
|
+
if (m.iframe) {
|
|
5350
|
+
try {
|
|
5351
|
+
m.iframe.remove();
|
|
5352
|
+
}
|
|
5353
|
+
catch (_a) {
|
|
5354
|
+
/* ignore */
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5357
|
+
m.iframe = null;
|
|
5358
|
+
m.iframeReady = false;
|
|
5359
|
+
m.iframeOrigin = "";
|
|
5360
|
+
m.onIframeMessage = null;
|
|
5361
|
+
}
|
|
5362
|
+
// Load the Bounded editorial typefaces (Newsreader display + Schibsted Grotesk body +
|
|
5363
|
+
// JetBrains Mono) once at the document level so they apply inside the shadow root. A
|
|
5364
|
+
// no-op if already present or off-DOM. Fonts loaded here match the hosted auth page.
|
|
5365
|
+
const FONTS_HREF = "https://fonts.googleapis.com/css2?family=Newsreader:opsz,wght@6..72,400;6..72,500&family=Schibsted+Grotesk:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap";
|
|
5366
|
+
function ensureFonts() {
|
|
5367
|
+
if (!hasDOM$2())
|
|
5368
|
+
return;
|
|
5369
|
+
if (document.querySelector('link[data-bounded-fonts="1"]'))
|
|
5370
|
+
return;
|
|
5371
|
+
const pre1 = document.createElement("link");
|
|
5372
|
+
pre1.rel = "preconnect";
|
|
5373
|
+
pre1.href = "https://fonts.googleapis.com";
|
|
5374
|
+
const pre2 = document.createElement("link");
|
|
5375
|
+
pre2.rel = "preconnect";
|
|
5376
|
+
pre2.href = "https://fonts.gstatic.com";
|
|
5377
|
+
pre2.crossOrigin = "anonymous";
|
|
5378
|
+
const link = document.createElement("link");
|
|
5379
|
+
link.rel = "stylesheet";
|
|
5380
|
+
link.href = FONTS_HREF;
|
|
5381
|
+
link.setAttribute("data-bounded-fonts", "1");
|
|
5382
|
+
document.head.append(pre1, pre2, link);
|
|
5383
|
+
}
|
|
5384
|
+
|
|
5385
|
+
var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
5386
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
5387
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
5388
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5389
|
+
};
|
|
5390
|
+
var __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
5391
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
5392
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5393
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5394
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
5395
|
+
};
|
|
5396
|
+
var _AppReadyEvent_detail;
|
|
5397
|
+
let wallets = undefined;
|
|
5398
|
+
const registeredWalletsSet = new Set();
|
|
5399
|
+
function addRegisteredWallet(wallet) {
|
|
5400
|
+
cachedWalletsArray = undefined;
|
|
5401
|
+
registeredWalletsSet.add(wallet);
|
|
5402
|
+
}
|
|
5403
|
+
function removeRegisteredWallet(wallet) {
|
|
5404
|
+
cachedWalletsArray = undefined;
|
|
5405
|
+
registeredWalletsSet.delete(wallet);
|
|
5406
|
+
}
|
|
5407
|
+
const listeners = {};
|
|
5408
|
+
/**
|
|
5409
|
+
* Get an API for {@link Wallets.get | getting}, {@link Wallets.on | listening for}, and
|
|
5410
|
+
* {@link Wallets.register | registering} {@link "@wallet-standard/base".Wallet | Wallets}.
|
|
5411
|
+
*
|
|
5412
|
+
* When called for the first time --
|
|
5413
|
+
*
|
|
5414
|
+
* This dispatches a {@link "@wallet-standard/base".WindowAppReadyEvent} to notify each Wallet that the app is ready
|
|
5415
|
+
* to register it.
|
|
5416
|
+
*
|
|
5417
|
+
* This also adds a listener for {@link "@wallet-standard/base".WindowRegisterWalletEvent} to listen for a notification
|
|
5418
|
+
* from each Wallet that the Wallet is ready to be registered by the app.
|
|
5419
|
+
*
|
|
5420
|
+
* This combination of event dispatch and listener guarantees that each Wallet will be registered synchronously as soon
|
|
5421
|
+
* as the app is ready whether the app loads before or after each Wallet.
|
|
5422
|
+
*
|
|
5423
|
+
* @return API for getting, listening for, and registering Wallets.
|
|
5424
|
+
*
|
|
5425
|
+
* @group App
|
|
5426
|
+
*/
|
|
5427
|
+
function getWallets() {
|
|
5428
|
+
if (wallets)
|
|
5429
|
+
return wallets;
|
|
5430
|
+
wallets = Object.freeze({ register, get: get$1, on });
|
|
5431
|
+
if (typeof window === 'undefined')
|
|
5432
|
+
return wallets;
|
|
5433
|
+
const api = Object.freeze({ register });
|
|
5434
|
+
try {
|
|
5435
|
+
window.addEventListener('wallet-standard:register-wallet', ({ detail: callback }) => callback(api));
|
|
5436
|
+
}
|
|
5437
|
+
catch (error) {
|
|
5438
|
+
console.error('wallet-standard:register-wallet event listener could not be added\n', error);
|
|
5439
|
+
}
|
|
5440
|
+
try {
|
|
5441
|
+
window.dispatchEvent(new AppReadyEvent(api));
|
|
5442
|
+
}
|
|
5443
|
+
catch (error) {
|
|
5444
|
+
console.error('wallet-standard:app-ready event could not be dispatched\n', error);
|
|
5445
|
+
}
|
|
5446
|
+
return wallets;
|
|
5447
|
+
}
|
|
5448
|
+
function register(...wallets) {
|
|
5449
|
+
// Filter out wallets that have already been registered.
|
|
5450
|
+
// This prevents the same wallet from being registered twice, but it also prevents wallets from being
|
|
5451
|
+
// unregistered by reusing a reference to the wallet to obtain the unregister function for it.
|
|
5452
|
+
wallets = wallets.filter((wallet) => !registeredWalletsSet.has(wallet));
|
|
5453
|
+
// If there are no new wallets to register, just return a no-op unregister function.
|
|
5454
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
5455
|
+
if (!wallets.length)
|
|
5456
|
+
return () => { };
|
|
5457
|
+
wallets.forEach((wallet) => addRegisteredWallet(wallet));
|
|
5458
|
+
listeners['register']?.forEach((listener) => guard(() => listener(...wallets)));
|
|
5459
|
+
// Return a function that unregisters the registered wallets.
|
|
5460
|
+
return function unregister() {
|
|
5461
|
+
wallets.forEach((wallet) => removeRegisteredWallet(wallet));
|
|
5462
|
+
listeners['unregister']?.forEach((listener) => guard(() => listener(...wallets)));
|
|
5463
|
+
};
|
|
5464
|
+
}
|
|
5465
|
+
let cachedWalletsArray;
|
|
5466
|
+
function get$1() {
|
|
5467
|
+
if (!cachedWalletsArray) {
|
|
5468
|
+
cachedWalletsArray = [...registeredWalletsSet];
|
|
5469
|
+
}
|
|
5470
|
+
return cachedWalletsArray;
|
|
5471
|
+
}
|
|
5472
|
+
function on(event, listener) {
|
|
5473
|
+
listeners[event]?.push(listener) || (listeners[event] = [listener]);
|
|
5474
|
+
// Return a function that removes the event listener.
|
|
5475
|
+
return function off() {
|
|
5476
|
+
listeners[event] = listeners[event]?.filter((existingListener) => listener !== existingListener);
|
|
5477
|
+
};
|
|
5478
|
+
}
|
|
5479
|
+
function guard(callback) {
|
|
5480
|
+
try {
|
|
5481
|
+
callback();
|
|
5482
|
+
}
|
|
5483
|
+
catch (error) {
|
|
5484
|
+
console.error(error);
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
class AppReadyEvent extends Event {
|
|
5488
|
+
get detail() {
|
|
5489
|
+
return __classPrivateFieldGet(this, _AppReadyEvent_detail, "f");
|
|
5490
|
+
}
|
|
5491
|
+
get type() {
|
|
5492
|
+
return 'wallet-standard:app-ready';
|
|
5493
|
+
}
|
|
5494
|
+
constructor(api) {
|
|
5495
|
+
super('wallet-standard:app-ready', {
|
|
5496
|
+
bubbles: false,
|
|
5497
|
+
cancelable: false,
|
|
5498
|
+
composed: false,
|
|
5499
|
+
});
|
|
5500
|
+
_AppReadyEvent_detail.set(this, void 0);
|
|
5501
|
+
__classPrivateFieldSet(this, _AppReadyEvent_detail, api, "f");
|
|
5502
|
+
}
|
|
5503
|
+
/** @deprecated */
|
|
5504
|
+
preventDefault() {
|
|
5505
|
+
throw new Error('preventDefault cannot be called');
|
|
5506
|
+
}
|
|
5507
|
+
/** @deprecated */
|
|
5508
|
+
stopImmediatePropagation() {
|
|
5509
|
+
throw new Error('stopImmediatePropagation cannot be called');
|
|
5510
|
+
}
|
|
5511
|
+
/** @deprecated */
|
|
5512
|
+
stopPropagation() {
|
|
5513
|
+
throw new Error('stopPropagation cannot be called');
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
_AppReadyEvent_detail = new WeakMap();
|
|
5517
|
+
|
|
5518
|
+
/** Name of the feature. */
|
|
5519
|
+
const SolanaSignAndSendTransaction = 'solana:signAndSendTransaction';
|
|
5520
|
+
|
|
5521
|
+
/** Name of the feature. */
|
|
5522
|
+
const SolanaSignMessage = 'solana:signMessage';
|
|
5523
|
+
|
|
5524
|
+
/** Name of the feature. */
|
|
5525
|
+
const SolanaSignTransaction = 'solana:signTransaction';
|
|
5526
|
+
|
|
5527
|
+
/** Name of the feature. */
|
|
5528
|
+
const StandardConnect = 'standard:connect';
|
|
5529
|
+
|
|
5530
|
+
/** Name of the feature. */
|
|
5531
|
+
const StandardDisconnect = 'standard:disconnect';
|
|
5532
|
+
|
|
5533
|
+
// wallet-standard-discovery.ts - PROPER native-wallet enumeration for the unified
|
|
5534
|
+
// login widget. We do NOT hand-maintain a list of wallet names (Phantom / Solflare
|
|
5535
|
+
// / Backpack / ...); instead we read the Solana Wallet Standard registry
|
|
5536
|
+
// (`@wallet-standard/app` getWallets(), fed by each wallet's
|
|
5537
|
+
// `wallet-standard:register-wallet` event) and surface every wallet that advertises
|
|
5538
|
+
// the Solana features we need. New wallets appear automatically, with their real
|
|
5539
|
+
// name + icon, and nothing is hardcoded.
|
|
5540
|
+
//
|
|
5541
|
+
// Each discovered wallet is adapted to the SDK's tiny `InjectedSolanaProvider`
|
|
5542
|
+
// shape so the existing wallet-login path (loginWithWallet -> InjectedWalletProvider,
|
|
5543
|
+
// SIWS) and the full app signing surface work unchanged. web3.js is only imported
|
|
5544
|
+
// lazily inside the transaction methods, so listing wallets and message-signing
|
|
5545
|
+
// login stay off the heavy chunk.
|
|
5546
|
+
function hasFeature(wallet, name) {
|
|
5547
|
+
return Object.prototype.hasOwnProperty.call(wallet.features, name);
|
|
5548
|
+
}
|
|
5549
|
+
function supportsSolana(wallet) {
|
|
5550
|
+
return wallet.chains.some((c) => c.startsWith("solana:"));
|
|
5551
|
+
}
|
|
5552
|
+
/** A wallet is usable for Bounded login when it can connect + sign a SIWS message
|
|
5553
|
+
* on a Solana chain. Transaction features are optional and checked lazily at
|
|
5554
|
+
* call time, so a message-signing-only wallet still qualifies for login. */
|
|
5555
|
+
function isLoginCapable(wallet) {
|
|
5556
|
+
return (supportsSolana(wallet) &&
|
|
5557
|
+
hasFeature(wallet, StandardConnect) &&
|
|
5558
|
+
hasFeature(wallet, SolanaSignMessage));
|
|
5559
|
+
}
|
|
5560
|
+
function feature(wallet, name) {
|
|
5561
|
+
return wallet.features[name];
|
|
5562
|
+
}
|
|
5563
|
+
function firstSolanaChain(wallet, account) {
|
|
5564
|
+
var _a;
|
|
5565
|
+
const fromAccount = account.chains.find((c) => c.startsWith("solana:"));
|
|
5566
|
+
const fromWallet = wallet.chains.find((c) => c.startsWith("solana:"));
|
|
5567
|
+
return ((_a = fromAccount !== null && fromAccount !== void 0 ? fromAccount : fromWallet) !== null && _a !== void 0 ? _a : "solana:mainnet");
|
|
5568
|
+
}
|
|
5569
|
+
/**
|
|
5570
|
+
* Adapt a Wallet Standard wallet to the slim `InjectedSolanaProvider` the SDK's
|
|
5571
|
+
* wallet-login + signing path consumes. Connect resolves (and caches) the active
|
|
5572
|
+
* account; sign* delegate to the wallet's own Solana features. Nothing is
|
|
5573
|
+
* wallet-name specific.
|
|
5574
|
+
*/
|
|
5575
|
+
function standardWalletToInjectedProvider(wallet) {
|
|
5576
|
+
let account = null;
|
|
5577
|
+
const requireAccount = () => {
|
|
5578
|
+
if (!account)
|
|
5579
|
+
throw new Error("Wallet not connected");
|
|
5580
|
+
return account;
|
|
5581
|
+
};
|
|
5582
|
+
return {
|
|
5583
|
+
get isConnected() {
|
|
5584
|
+
return account !== null;
|
|
5585
|
+
},
|
|
5586
|
+
get publicKey() {
|
|
5587
|
+
// Wallet Standard account.address is already the base58 Solana address.
|
|
5588
|
+
return account ? { toString: () => account.address } : null;
|
|
5589
|
+
},
|
|
5590
|
+
async connect() {
|
|
5591
|
+
var _a;
|
|
5592
|
+
const connect = feature(wallet, StandardConnect);
|
|
5593
|
+
if (!connect)
|
|
5594
|
+
throw new Error("Wallet does not support standard:connect");
|
|
5595
|
+
const res = await connect.connect();
|
|
5596
|
+
const acc = (_a = res.accounts.find((a) => a.chains.some((c) => c.startsWith("solana:")))) !== null && _a !== void 0 ? _a : res.accounts[0];
|
|
5597
|
+
if (!acc)
|
|
5598
|
+
throw new Error("Wallet returned no account on connect");
|
|
5599
|
+
account = acc;
|
|
5600
|
+
return { publicKey: { toString: () => acc.address } };
|
|
5601
|
+
},
|
|
5602
|
+
async disconnect() {
|
|
5603
|
+
const disconnect = feature(wallet, StandardDisconnect);
|
|
5604
|
+
if (disconnect)
|
|
5605
|
+
await disconnect.disconnect();
|
|
5606
|
+
account = null;
|
|
5607
|
+
},
|
|
5608
|
+
async signMessage(message) {
|
|
5609
|
+
const acc = requireAccount();
|
|
5610
|
+
const sign = feature(wallet, SolanaSignMessage);
|
|
5611
|
+
if (!sign)
|
|
5612
|
+
throw new Error("Wallet does not support solana:signMessage");
|
|
5613
|
+
const [out] = await sign.signMessage({ account: acc, message });
|
|
5614
|
+
return { signature: out.signature, publicKey: { toString: () => acc.address } };
|
|
5615
|
+
},
|
|
5616
|
+
async signTransaction(tx) {
|
|
5617
|
+
const acc = requireAccount();
|
|
5618
|
+
const sign = feature(wallet, SolanaSignTransaction);
|
|
5619
|
+
if (!sign)
|
|
5620
|
+
throw new Error("Wallet does not support solana:signTransaction");
|
|
5621
|
+
const { serializeUnsignedTx, deserializeSignedTx } = await import('./wallet-standard-tx-DBliTUWH.mjs');
|
|
5622
|
+
const [out] = await sign.signTransaction({ account: acc, transaction: serializeUnsignedTx(tx) });
|
|
5623
|
+
return deserializeSignedTx(out.signedTransaction);
|
|
5624
|
+
},
|
|
5625
|
+
async signAllTransactions(txs) {
|
|
5626
|
+
const acc = requireAccount();
|
|
5627
|
+
const sign = feature(wallet, SolanaSignTransaction);
|
|
5628
|
+
if (!sign)
|
|
5629
|
+
throw new Error("Wallet does not support solana:signTransaction");
|
|
5630
|
+
const { serializeUnsignedTx, deserializeSignedTx } = await import('./wallet-standard-tx-DBliTUWH.mjs');
|
|
5631
|
+
const outs = await sign.signTransaction(...txs.map((tx) => ({ account: acc, transaction: serializeUnsignedTx(tx) })));
|
|
5632
|
+
return outs.map((o) => deserializeSignedTx(o.signedTransaction));
|
|
5633
|
+
},
|
|
5634
|
+
async signAndSendTransaction(tx) {
|
|
5635
|
+
const acc = requireAccount();
|
|
5636
|
+
const sas = feature(wallet, SolanaSignAndSendTransaction);
|
|
5637
|
+
if (!sas)
|
|
5638
|
+
throw new Error("Wallet does not support solana:signAndSendTransaction");
|
|
5639
|
+
const { serializeUnsignedTx } = await import('./wallet-standard-tx-DBliTUWH.mjs');
|
|
5640
|
+
const [out] = await sas.signAndSendTransaction({
|
|
5641
|
+
account: acc,
|
|
5642
|
+
transaction: serializeUnsignedTx(tx),
|
|
5643
|
+
chain: firstSolanaChain(wallet, acc),
|
|
5644
|
+
});
|
|
5645
|
+
return { signature: bs58.encode(out.signature) };
|
|
5646
|
+
},
|
|
5647
|
+
};
|
|
5648
|
+
}
|
|
5649
|
+
/**
|
|
5650
|
+
* Enumerate every Solana-capable wallet currently registered with the Wallet
|
|
5651
|
+
* Standard, newest registrations included. Returns login-ready choices (name +
|
|
5652
|
+
* icon + provider factory). Empty array off-browser or when no wallet is present;
|
|
5653
|
+
* the widget then falls back to the legacy injected `window.solana` probe.
|
|
5654
|
+
*/
|
|
5655
|
+
function discoverStandardWallets() {
|
|
5656
|
+
if (typeof window === "undefined")
|
|
5657
|
+
return [];
|
|
5658
|
+
const out = [];
|
|
5659
|
+
const seen = new Set();
|
|
5660
|
+
for (const wallet of getWallets().get()) {
|
|
5661
|
+
if (!isLoginCapable(wallet) || seen.has(wallet.name))
|
|
5662
|
+
continue;
|
|
5663
|
+
seen.add(wallet.name);
|
|
5664
|
+
out.push({
|
|
5665
|
+
name: wallet.name,
|
|
5666
|
+
icon: wallet.icon,
|
|
5667
|
+
getProvider: () => standardWalletToInjectedProvider(wallet),
|
|
5668
|
+
});
|
|
5669
|
+
}
|
|
5670
|
+
return out;
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5673
|
+
function hasDOM$1() {
|
|
5674
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
5675
|
+
}
|
|
5676
|
+
async function signerOrigin() {
|
|
5677
|
+
const cfg = await getConfig();
|
|
5678
|
+
const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
5679
|
+
return { url: `${base}/wallet/turnkey/signer`, origin: new URL(base).origin };
|
|
5680
|
+
}
|
|
5681
|
+
let encryptSeq = 0;
|
|
5682
|
+
// Seal the OTP in a HIDDEN, offscreen signer iframe on the Bounded origin. The signer page
|
|
5683
|
+
// runs the vendored @turnkey/crypto encryptOtpCodeToBundle (which verifies the enclave's
|
|
5684
|
+
// signature on the target bundle first). Origin + source + requestId are all pinned.
|
|
5685
|
+
function sealOtpInIframe(code, otpEncryptionTargetBundle) {
|
|
5686
|
+
return new Promise((resolve, reject) => {
|
|
5687
|
+
signerOrigin().then(({ url, origin }) => {
|
|
5688
|
+
const requestId = `tkotp_${Date.now()}_${++encryptSeq}`;
|
|
5689
|
+
const frame = document.createElement('iframe');
|
|
5690
|
+
frame.setAttribute('aria-hidden', 'true');
|
|
5691
|
+
frame.setAttribute('allow', 'publickey-credentials-get; publickey-credentials-create; clipboard-write');
|
|
5692
|
+
frame.style.cssText =
|
|
5693
|
+
'position:fixed;width:1px;height:1px;left:-9999px;top:-9999px;border:0;opacity:0;pointer-events:none';
|
|
5694
|
+
let settled = false;
|
|
5695
|
+
let timer;
|
|
5696
|
+
const cleanup = () => {
|
|
5697
|
+
settled = true;
|
|
5698
|
+
window.removeEventListener('message', onMsg);
|
|
5699
|
+
if (timer)
|
|
5700
|
+
clearTimeout(timer);
|
|
5701
|
+
try {
|
|
5702
|
+
frame.remove();
|
|
5703
|
+
}
|
|
5704
|
+
catch ( /* already gone */_a) { /* already gone */ }
|
|
5705
|
+
};
|
|
5706
|
+
const onMsg = (event) => {
|
|
5707
|
+
var _a;
|
|
5708
|
+
if (event.origin !== origin || event.source !== frame.contentWindow)
|
|
5709
|
+
return;
|
|
5710
|
+
const d = event.data;
|
|
5711
|
+
if (!d || typeof d !== 'object')
|
|
5712
|
+
return;
|
|
5713
|
+
if (d.type === 'bounded:turnkey:ready') {
|
|
5714
|
+
try {
|
|
5715
|
+
(_a = frame.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage({ type: 'bounded:turnkey:encryptOtp', requestId, otpCode: code, otpEncryptionTargetBundle }, origin);
|
|
5716
|
+
}
|
|
5717
|
+
catch ( /* frame gone */_b) { /* frame gone */ }
|
|
5718
|
+
return;
|
|
5719
|
+
}
|
|
5720
|
+
if (d.type === 'bounded:turnkey:result' && d.requestId === requestId) {
|
|
5721
|
+
if (d.ok && typeof d.encryptedOtpBundle === 'string' && d.encryptedOtpBundle) {
|
|
5722
|
+
const bundle = d.encryptedOtpBundle;
|
|
5723
|
+
cleanup();
|
|
5724
|
+
resolve(bundle);
|
|
5725
|
+
}
|
|
5726
|
+
else {
|
|
5727
|
+
cleanup();
|
|
5728
|
+
reject(new Error(String(d.error || 'Could not secure the code. Please try again.')));
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
};
|
|
5732
|
+
window.addEventListener('message', onMsg);
|
|
5733
|
+
timer = setTimeout(() => {
|
|
5734
|
+
if (!settled) {
|
|
5735
|
+
cleanup();
|
|
5736
|
+
reject(new Error('Securing the code timed out. Please try again.'));
|
|
5737
|
+
}
|
|
5738
|
+
}, 30000);
|
|
5739
|
+
frame.src = url;
|
|
5740
|
+
document.body.appendChild(frame);
|
|
5741
|
+
}).catch(reject);
|
|
5742
|
+
});
|
|
5743
|
+
}
|
|
5744
|
+
/**
|
|
5745
|
+
* Begin authMode:"turnkey" login: ask Turnkey to email a one-time code to `email`.
|
|
5746
|
+
* Resolves with a handle whose `verify(code)` finishes the login. Web-only (the sealing
|
|
5747
|
+
* step needs the signer iframe); on React Native use loginWithRedirect() instead.
|
|
5748
|
+
*/
|
|
5749
|
+
async function startTurnkeyEmailLogin(email) {
|
|
5750
|
+
if (!hasDOM$1()) {
|
|
5751
|
+
throw new Error('Turnkey-native email login is web-only - on React Native use loginWithRedirect()');
|
|
5752
|
+
}
|
|
5753
|
+
const trimmed = email.trim();
|
|
5754
|
+
if (!trimmed)
|
|
5755
|
+
throw new Error('Enter your email to continue.');
|
|
5756
|
+
const { base, appId } = await inlineAuthEndpoint();
|
|
5757
|
+
if (!appId)
|
|
5758
|
+
throw new Error('appId is not configured (init the SDK with your appId)');
|
|
5759
|
+
const res = await fetch(`${base}/wallet/turnkey/auth/init`, {
|
|
5760
|
+
method: 'POST',
|
|
5761
|
+
headers: { 'content-type': 'application/json' },
|
|
5762
|
+
body: JSON.stringify({ email: trimmed, appId }),
|
|
5763
|
+
});
|
|
5764
|
+
const data = await res.json().catch(() => null);
|
|
5765
|
+
if (!res.ok || !data || typeof data.otpId !== 'string' || typeof data.otpEncryptionTargetBundle !== 'string') {
|
|
5766
|
+
throw new Error(String((data && data.error) || 'Could not send a code. Please try again.'));
|
|
5767
|
+
}
|
|
5768
|
+
const otpId = data.otpId;
|
|
5769
|
+
const otpEncryptionTargetBundle = data.otpEncryptionTargetBundle;
|
|
5770
|
+
return {
|
|
5771
|
+
email: trimmed,
|
|
5772
|
+
async verify(code) {
|
|
5773
|
+
const trimmedCode = (code || '').trim();
|
|
5774
|
+
if (!trimmedCode)
|
|
5775
|
+
throw new Error('Enter the code from your email.');
|
|
5776
|
+
const encryptedOtpBundle = await sealOtpInIframe(trimmedCode, otpEncryptionTargetBundle);
|
|
5777
|
+
const vres = await fetch(`${base}/wallet/turnkey/auth/verify`, {
|
|
5778
|
+
method: 'POST',
|
|
5779
|
+
headers: { 'content-type': 'application/json' },
|
|
5780
|
+
body: JSON.stringify({ email: trimmed, appId, otpId, encryptedOtpBundle }),
|
|
5781
|
+
});
|
|
5782
|
+
const vdata = await vres.json().catch(() => null);
|
|
5783
|
+
if (!vres.ok || !vdata) {
|
|
5784
|
+
throw new Error(String((vdata && vdata.error) || 'Invalid or expired code.'));
|
|
5785
|
+
}
|
|
5786
|
+
return establishInlineSession(vdata);
|
|
5787
|
+
},
|
|
5788
|
+
};
|
|
5789
|
+
}
|
|
5790
|
+
|
|
5791
|
+
// bounded-login-widget.ts - the UNIFIED Bounded login surface: one in-app card
|
|
5792
|
+
// (Shadow-DOM modal from bounded-widget-modal.ts, Bounded look + motion) that folds
|
|
5793
|
+
// together every login lane the SDK supports:
|
|
5794
|
+
// - Email / social (Google, ...) -> the hosted Better Auth OIDC flow, run in a
|
|
5795
|
+
// brief FIRST-PARTY popup (loginWithPopup). A cross-origin auth IFRAME cannot
|
|
5796
|
+
// hold the Better Auth session cookie (third-party-cookie blocking in Safari /
|
|
5797
|
+
// Chrome), so the session handshake pops; the card itself stays in-app.
|
|
5798
|
+
// - "Continue with wallet" -> native injected wallets (Phantom / Solflare /
|
|
5799
|
+
// Backpack) via loginWithWallet(), run ENTIRELY on the app page (injected
|
|
5800
|
+
// providers only exist there, never inside a cross-origin frame).
|
|
5801
|
+
//
|
|
5802
|
+
// Redirect stays available as the popup-blocked fallback (loginWithRedirect).
|
|
5803
|
+
// The same modal is reused for Turnkey passkey signing, so login -> first
|
|
5804
|
+
// signature is one continuous surface.
|
|
5805
|
+
// Palette + type ladder mirror the hosted auth page (login-page.ts): dark editorial
|
|
5806
|
+
// panel, Newsreader display serif for the title, Schibsted Grotesk body, a light
|
|
5807
|
+
// (paper) primary button and inset inputs. This is the same look/feel as
|
|
5808
|
+
// auth.bounded, brought inline into the in-app widget.
|
|
5809
|
+
const CARD_CSS = `
|
|
5810
|
+
:host,*{box-sizing:border-box}
|
|
5811
|
+
.c{--bg-inset:#060607;--bg-raise:#131416;
|
|
5812
|
+
--line:rgba(232,230,222,.08);--line-strong:rgba(232,230,222,.16);
|
|
5813
|
+
--text:#e8e6de;--dim:#9b9991;--faint:#85837c;
|
|
5814
|
+
--green:#58c98f;--green-bg:rgba(88,201,143,.09);--green-line:rgba(88,201,143,.28);--red:#ec8488;
|
|
5815
|
+
--disp:"Newsreader","Iowan Old Style",Georgia,serif;
|
|
5816
|
+
--body:"Schibsted Grotesk","Helvetica Neue",Arial,sans-serif;
|
|
5817
|
+
position:relative;padding:34px 32px 20px;color:var(--text);font-family:var(--body);
|
|
5818
|
+
-webkit-font-smoothing:antialiased}
|
|
5819
|
+
.x{position:absolute;top:20px;right:24px;border:0;background:transparent;color:var(--faint);
|
|
5820
|
+
font-family:var(--body);font-size:13px;line-height:1;cursor:pointer;padding:4px}
|
|
5821
|
+
.x:hover{color:var(--text)}
|
|
5822
|
+
h1{font-family:var(--disp);font-weight:400;font-size:27px;line-height:1.12;letter-spacing:-.01em;margin:0 0 6px;max-width:88%}
|
|
5823
|
+
.sub{margin:0 0 24px;color:var(--dim);font-size:13.5px}
|
|
5824
|
+
label{display:block;font-size:12px;color:var(--dim);margin:0 0 7px}
|
|
5825
|
+
input{width:100%;padding:11px 13px;border-radius:6px;border:1px solid var(--line-strong);
|
|
5826
|
+
background:var(--bg-inset);color:var(--text);font-size:15px;font-family:var(--body);outline:none;
|
|
5827
|
+
transition:border-color .15s,box-shadow .15s}
|
|
5828
|
+
input::placeholder{color:var(--faint)}
|
|
5829
|
+
input:focus{border-color:var(--green-line);box-shadow:0 0 0 3px var(--green-bg)}
|
|
5830
|
+
input.otp{font-family:var(--mono,ui-monospace,monospace);letter-spacing:8px;text-align:center;font-size:21px;padding-left:8px}
|
|
5831
|
+
.btn{width:100%;padding:12px 14px;border-radius:6px;font-size:14px;font-weight:600;
|
|
5832
|
+
cursor:pointer;font-family:var(--body);display:flex;align-items:center;justify-content:center;gap:9px;
|
|
5833
|
+
transition:opacity .15s ease,background .15s ease,border-color .15s ease,transform .04s}
|
|
5834
|
+
.btn:disabled{opacity:.45;cursor:default}
|
|
5835
|
+
.primary{border:0;background:var(--text);color:#0a0a0b;margin-top:16px}
|
|
5836
|
+
.primary:hover:not(:disabled){opacity:.9}
|
|
5837
|
+
.primary:active:not(:disabled){transform:translateY(.5px)}
|
|
5838
|
+
.ghost{border:1px solid var(--line-strong);background:var(--bg-raise);color:var(--text);margin-top:9px}
|
|
5839
|
+
.ghost:hover:not(:disabled){background:#17181b}
|
|
5840
|
+
.ghost svg{width:16px;height:16px;flex:none}
|
|
5841
|
+
.wicon{width:18px;height:18px;border-radius:4px;flex:none}
|
|
5842
|
+
.or{display:flex;align-items:center;gap:12px;margin:20px 0 14px;color:var(--faint);
|
|
5843
|
+
font-size:11px;letter-spacing:.08em;text-transform:uppercase}
|
|
5844
|
+
.or::before,.or::after{content:"";flex:1;height:1px;background:var(--line)}
|
|
5845
|
+
.status{margin-top:16px;min-height:16px;font-size:12.5px;color:var(--dim)}
|
|
5846
|
+
.status.err{color:var(--red)}
|
|
5847
|
+
.foot{margin-top:24px;padding-top:15px;border-top:1px solid var(--line);
|
|
5848
|
+
color:var(--faint);font-size:11px;text-align:center;letter-spacing:.02em}
|
|
5849
|
+
.foot a{color:var(--dim);text-decoration:none}
|
|
5850
|
+
.foot a:hover{color:var(--text)}
|
|
5851
|
+
.back{background:transparent;border:0;color:var(--dim);font-size:12.5px;cursor:pointer;padding:0;margin-bottom:16px}
|
|
5852
|
+
.back:hover{color:var(--text)}
|
|
5853
|
+
.spin{width:15px;height:15px;border-radius:50%;border:2px solid rgba(10,10,11,.3);
|
|
5854
|
+
border-top-color:#0a0a0b;animation:bwspin .7s linear infinite}
|
|
5855
|
+
.ghost .spin,.primary.wallet-busy .spin{border:2px solid var(--line-strong);border-top-color:var(--text)}
|
|
5856
|
+
@keyframes bwspin{to{transform:rotate(360deg)}}
|
|
5857
|
+
`;
|
|
5858
|
+
// Brand glyphs, built as SVG DOM (never innerHTML) so the buttons match the hosted page.
|
|
5859
|
+
function googleGlyph() {
|
|
5860
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
5861
|
+
svg.setAttribute("viewBox", "0 0 48 48");
|
|
5862
|
+
svg.setAttribute("aria-hidden", "true");
|
|
5863
|
+
const paths = [
|
|
5864
|
+
["#EA4335", "M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"],
|
|
5865
|
+
["#4285F4", "M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"],
|
|
5866
|
+
["#FBBC05", "M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"],
|
|
5867
|
+
["#34A853", "M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"],
|
|
5868
|
+
];
|
|
5869
|
+
for (const [fill, d] of paths) {
|
|
5870
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
5871
|
+
path.setAttribute("fill", fill);
|
|
5872
|
+
path.setAttribute("d", d);
|
|
5873
|
+
svg.appendChild(path);
|
|
5874
|
+
}
|
|
5875
|
+
return svg;
|
|
5876
|
+
}
|
|
5877
|
+
function walletGlyph() {
|
|
5878
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
5879
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
5880
|
+
svg.setAttribute("fill", "none");
|
|
5881
|
+
svg.setAttribute("aria-hidden", "true");
|
|
5882
|
+
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
5883
|
+
rect.setAttribute("x", "3");
|
|
5884
|
+
rect.setAttribute("y", "6");
|
|
5885
|
+
rect.setAttribute("width", "18");
|
|
5886
|
+
rect.setAttribute("height", "13");
|
|
5887
|
+
rect.setAttribute("rx", "2.5");
|
|
5888
|
+
rect.setAttribute("stroke", "currentColor");
|
|
5889
|
+
rect.setAttribute("stroke-width", "1.6");
|
|
5890
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
5891
|
+
dot.setAttribute("cx", "16.5");
|
|
5892
|
+
dot.setAttribute("cy", "12.5");
|
|
5893
|
+
dot.setAttribute("r", "1.4");
|
|
5894
|
+
dot.setAttribute("fill", "currentColor");
|
|
5895
|
+
svg.append(rect, dot);
|
|
5896
|
+
return svg;
|
|
5897
|
+
}
|
|
5898
|
+
function isInjectedProvider(v) {
|
|
5899
|
+
return (typeof v === "object" &&
|
|
5900
|
+
v !== null &&
|
|
5901
|
+
typeof v.signMessage === "function");
|
|
5902
|
+
}
|
|
5903
|
+
// Enumerate native wallets via the Solana Wallet Standard registry (NOT a
|
|
5904
|
+
// hardcoded name list) - every wallet that registered itself shows up with its
|
|
5905
|
+
// real name + icon. Only when the registry is empty (an older wallet that never
|
|
5906
|
+
// registers, exposing just `window.solana`) do we fall back to the injected probe.
|
|
5907
|
+
function discoverWallets() {
|
|
5908
|
+
if (typeof window === "undefined")
|
|
5909
|
+
return [];
|
|
5910
|
+
const standard = discoverStandardWallets();
|
|
5911
|
+
if (standard.length > 0) {
|
|
5912
|
+
return standard.map((w) => ({ name: w.name, icon: w.icon, get: () => w.getProvider() }));
|
|
5913
|
+
}
|
|
5914
|
+
const generic = window.solana;
|
|
5915
|
+
if (isInjectedProvider(generic))
|
|
5916
|
+
return [{ name: "Wallet", get: () => generic }];
|
|
5917
|
+
return [];
|
|
5918
|
+
}
|
|
5919
|
+
const METHOD_LABEL = {
|
|
5920
|
+
google: "Continue with Google",
|
|
5921
|
+
apple: "Continue with Apple",
|
|
5922
|
+
github: "Continue with GitHub",
|
|
5923
|
+
};
|
|
5924
|
+
function el(tag, cls, text) {
|
|
5925
|
+
const n = document.createElement(tag);
|
|
5926
|
+
if (cls)
|
|
5927
|
+
n.className = cls;
|
|
5928
|
+
if (text != null)
|
|
5929
|
+
n.textContent = text;
|
|
5930
|
+
return n;
|
|
5931
|
+
}
|
|
5932
|
+
/**
|
|
5933
|
+
* Open the unified Bounded login widget and resolve with the signed-in `User`.
|
|
5934
|
+
* Rejects with `Error("cancelled")` if the user dismisses it. `init()` must have
|
|
5935
|
+
* run first; call from a user gesture so social/wallet popups are allowed.
|
|
5936
|
+
*/
|
|
5937
|
+
async function openBoundedWidget(opts = {}) {
|
|
5938
|
+
var _a, _b, _c, _d;
|
|
5939
|
+
if (!hasDOM$2()) {
|
|
5940
|
+
throw new Error("openBoundedWidget is web-only - on React Native use loginWithRedirect()");
|
|
5941
|
+
}
|
|
5942
|
+
// Imported lazily to avoid a static cycle with the auth index barrel.
|
|
5943
|
+
const authApi = await Promise.resolve().then(function () { return index; });
|
|
5944
|
+
const initCfg = (_a = authApi.getInitConfig()) !== null && _a !== void 0 ? _a : {};
|
|
5945
|
+
const methods = ((_b = opts.methods) !== null && _b !== void 0 ? _b : ["email", "google"]).map(String);
|
|
5946
|
+
const hasEmail = methods.includes("email") || methods.includes("text");
|
|
5947
|
+
const social = methods.filter((m) => m !== "email" && m !== "text");
|
|
5948
|
+
const wallets = discoverWallets();
|
|
5949
|
+
// Policy first: an app whose site requires an email on file, or that explicitly
|
|
5950
|
+
// set `walletLogin: false`, hides the wallet lane entirely - this OVERRIDES a
|
|
5951
|
+
// per-call `opts.wallet` and any detected injected wallet.
|
|
5952
|
+
const walletHardOff = authApi.isWalletLoginDisabled(initCfg) || authApi.isEmailRequired(initCfg);
|
|
5953
|
+
const walletEnabled = walletHardOff
|
|
5954
|
+
? false
|
|
5955
|
+
: ((_c = opts.wallet) !== null && _c !== void 0 ? _c : (wallets.length > 0 || authApi.isWalletLoginEnabled(initCfg)));
|
|
5956
|
+
const authMode = (_d = opts.authMode) !== null && _d !== void 0 ? _d : authApi.resolveAuthMode(initCfg);
|
|
5957
|
+
ensureFonts();
|
|
5958
|
+
const m = getModal();
|
|
5959
|
+
return new Promise((resolve, reject) => {
|
|
5960
|
+
let settled = false;
|
|
5961
|
+
const done = (fn) => {
|
|
5962
|
+
if (settled)
|
|
5963
|
+
return;
|
|
5964
|
+
settled = true;
|
|
5965
|
+
m.onBackdrop = null;
|
|
5966
|
+
fn();
|
|
5967
|
+
};
|
|
5968
|
+
const succeed = (u) => {
|
|
5969
|
+
hideModal(m);
|
|
5970
|
+
done(() => resolve(u));
|
|
5971
|
+
};
|
|
5972
|
+
const cancel = () => {
|
|
5973
|
+
hideModal(m);
|
|
5974
|
+
done(() => reject(new Error("cancelled")));
|
|
5975
|
+
};
|
|
5976
|
+
m.onBackdrop = cancel;
|
|
5977
|
+
const style = el("style");
|
|
5978
|
+
style.textContent = CARD_CSS;
|
|
5979
|
+
const renderChoose = () => {
|
|
5980
|
+
var _a, _b;
|
|
5981
|
+
const card = el("div", "c");
|
|
5982
|
+
card.appendChild(style.cloneNode(true));
|
|
5983
|
+
const x = el("button", "x", "Close");
|
|
5984
|
+
x.type = "button";
|
|
5985
|
+
x.setAttribute("aria-label", "Close");
|
|
5986
|
+
x.addEventListener("click", cancel);
|
|
5987
|
+
card.appendChild(x);
|
|
5988
|
+
card.appendChild(el("h1", undefined, (_a = opts.title) !== null && _a !== void 0 ? _a : "Sign in"));
|
|
5989
|
+
if (opts.subtitle !== "") {
|
|
5990
|
+
card.appendChild(el("p", "sub", (_b = opts.subtitle) !== null && _b !== void 0 ? _b : "One account and wallet across every Bounded app."));
|
|
5991
|
+
}
|
|
5992
|
+
const status = el("div", "status");
|
|
5993
|
+
const setErr = (msg) => {
|
|
5994
|
+
status.textContent = msg;
|
|
5995
|
+
status.classList.add("err");
|
|
5996
|
+
};
|
|
5997
|
+
const setBusy = (btn, label) => {
|
|
5998
|
+
status.classList.remove("err");
|
|
5999
|
+
status.textContent = "";
|
|
6000
|
+
btn.disabled = true;
|
|
6001
|
+
btn.replaceChildren(el("span", "spin"), document.createTextNode(label));
|
|
6002
|
+
};
|
|
6003
|
+
if (hasEmail) {
|
|
6004
|
+
const form = el("form");
|
|
6005
|
+
const label = el("label");
|
|
6006
|
+
label.htmlFor = "bw-email";
|
|
6007
|
+
label.textContent = "Email";
|
|
6008
|
+
const input = el("input");
|
|
6009
|
+
input.id = "bw-email";
|
|
6010
|
+
input.type = "email";
|
|
6011
|
+
input.autocomplete = "email";
|
|
6012
|
+
input.placeholder = "you@example.com";
|
|
6013
|
+
input.required = true;
|
|
6014
|
+
const submit = el("button", "btn primary");
|
|
6015
|
+
submit.type = "submit";
|
|
6016
|
+
submit.textContent = "Continue";
|
|
6017
|
+
form.append(label, input, submit);
|
|
6018
|
+
form.addEventListener("submit", (e) => {
|
|
6019
|
+
e.preventDefault();
|
|
6020
|
+
if (!input.value)
|
|
6021
|
+
return;
|
|
6022
|
+
if (authMode === "turnkey") {
|
|
6023
|
+
// Turnkey-native: ask Turnkey to email a code, then collect it INLINE.
|
|
6024
|
+
setBusy(submit, "Sending code\u2026");
|
|
6025
|
+
startTurnkeyEmailLogin(input.value)
|
|
6026
|
+
.then((handle) => swap(renderOtp(handle)))
|
|
6027
|
+
.catch((err) => {
|
|
6028
|
+
submit.disabled = false;
|
|
6029
|
+
submit.textContent = "Continue";
|
|
6030
|
+
setErr(errText(err));
|
|
6031
|
+
});
|
|
6032
|
+
return;
|
|
6033
|
+
}
|
|
6034
|
+
setBusy(submit, "Opening secure sign-in\u2026");
|
|
6035
|
+
authApi
|
|
6036
|
+
.loginWithPopup({
|
|
6037
|
+
provider: "email",
|
|
6038
|
+
loginHint: input.value,
|
|
6039
|
+
redirectUri: opts.redirectUri,
|
|
6040
|
+
})
|
|
6041
|
+
.then(succeed)
|
|
6042
|
+
.catch((err) => {
|
|
6043
|
+
submit.disabled = false;
|
|
6044
|
+
submit.textContent = "Continue";
|
|
6045
|
+
setErr(errText(err));
|
|
6046
|
+
});
|
|
6047
|
+
});
|
|
6048
|
+
card.appendChild(form);
|
|
6049
|
+
}
|
|
6050
|
+
if (social.length || walletEnabled) {
|
|
6051
|
+
card.appendChild(el("div", "or", "OR"));
|
|
6052
|
+
}
|
|
6053
|
+
const labelFor = (method) => { var _a; return (_a = METHOD_LABEL[method]) !== null && _a !== void 0 ? _a : `Continue with ${cap(method)}`; };
|
|
6054
|
+
const glyphFor = (method) => method === "google" ? googleGlyph() : null;
|
|
6055
|
+
const setLabel = (b, method) => {
|
|
6056
|
+
const glyph = glyphFor(method);
|
|
6057
|
+
const text = document.createTextNode(labelFor(method));
|
|
6058
|
+
if (glyph)
|
|
6059
|
+
b.replaceChildren(glyph, text);
|
|
6060
|
+
else
|
|
6061
|
+
b.replaceChildren(text);
|
|
6062
|
+
};
|
|
6063
|
+
for (const method of social) {
|
|
6064
|
+
const b = el("button", "btn ghost");
|
|
6065
|
+
b.type = "button";
|
|
6066
|
+
setLabel(b, method);
|
|
6067
|
+
b.addEventListener("click", () => {
|
|
6068
|
+
setBusy(b, "Opening secure sign-in\u2026");
|
|
6069
|
+
authApi
|
|
6070
|
+
.loginWithPopup({ provider: method, redirectUri: opts.redirectUri })
|
|
6071
|
+
.then(succeed)
|
|
6072
|
+
.catch((err) => {
|
|
6073
|
+
b.disabled = false;
|
|
6074
|
+
setLabel(b, method);
|
|
6075
|
+
setErr(errText(err));
|
|
6076
|
+
});
|
|
6077
|
+
});
|
|
6078
|
+
card.appendChild(b);
|
|
6079
|
+
}
|
|
6080
|
+
if (walletEnabled) {
|
|
6081
|
+
const b = el("button", "btn ghost");
|
|
6082
|
+
b.type = "button";
|
|
6083
|
+
const walletLabel = () => b.replaceChildren(walletGlyph(), document.createTextNode("Continue with wallet"));
|
|
6084
|
+
walletLabel();
|
|
6085
|
+
b.addEventListener("click", () => {
|
|
6086
|
+
if (wallets.length <= 1) {
|
|
6087
|
+
connectWallet(wallets[0]);
|
|
6088
|
+
}
|
|
6089
|
+
else {
|
|
6090
|
+
swap(renderWallets());
|
|
6091
|
+
}
|
|
6092
|
+
});
|
|
6093
|
+
card.appendChild(b);
|
|
6094
|
+
const connectWallet = (choice) => {
|
|
6095
|
+
setBusy(b, "Approve in your wallet\u2026");
|
|
6096
|
+
authApi
|
|
6097
|
+
.loginWithWallet(choice ? { getProvider: choice.get } : undefined)
|
|
6098
|
+
.then((u) => {
|
|
6099
|
+
if (u)
|
|
6100
|
+
succeed(u);
|
|
6101
|
+
else {
|
|
6102
|
+
b.disabled = false;
|
|
6103
|
+
walletLabel();
|
|
6104
|
+
setErr("Wallet login was cancelled.");
|
|
6105
|
+
}
|
|
6106
|
+
})
|
|
6107
|
+
.catch((err) => {
|
|
6108
|
+
b.disabled = false;
|
|
6109
|
+
walletLabel();
|
|
6110
|
+
setErr(errText(err));
|
|
6111
|
+
});
|
|
6112
|
+
};
|
|
6113
|
+
}
|
|
6114
|
+
card.appendChild(status);
|
|
6115
|
+
const foot = el("div", "foot");
|
|
6116
|
+
const poweredBy = el("a", undefined, "Powered by Bounded");
|
|
6117
|
+
poweredBy.href = "https://bounded.sh";
|
|
6118
|
+
poweredBy.target = "_blank";
|
|
6119
|
+
poweredBy.rel = "noopener";
|
|
6120
|
+
foot.appendChild(poweredBy);
|
|
6121
|
+
card.appendChild(foot);
|
|
6122
|
+
return card;
|
|
6123
|
+
};
|
|
6124
|
+
const renderWallets = () => {
|
|
6125
|
+
const card = el("div", "c");
|
|
6126
|
+
card.appendChild(style.cloneNode(true));
|
|
6127
|
+
const back = el("button", "back", "\u2190 Back");
|
|
6128
|
+
back.type = "button";
|
|
6129
|
+
back.addEventListener("click", () => swap(renderChoose()));
|
|
6130
|
+
card.appendChild(back);
|
|
6131
|
+
card.appendChild(el("h1", undefined, "Connect a wallet"));
|
|
6132
|
+
card.appendChild(el("p", "sub", "Choose your Solana wallet."));
|
|
6133
|
+
const status = el("div", "status");
|
|
6134
|
+
// Icon (the wallet's own, from the Wallet Standard registry) + name.
|
|
6135
|
+
const setWalletLabel = (b, choice) => {
|
|
6136
|
+
const text = document.createTextNode(choice.name);
|
|
6137
|
+
if (choice.icon) {
|
|
6138
|
+
const img = el("img", "wicon");
|
|
6139
|
+
img.src = choice.icon;
|
|
6140
|
+
img.alt = "";
|
|
6141
|
+
b.replaceChildren(img, text);
|
|
6142
|
+
}
|
|
6143
|
+
else {
|
|
6144
|
+
b.replaceChildren(text);
|
|
6145
|
+
}
|
|
6146
|
+
};
|
|
6147
|
+
for (const choice of wallets) {
|
|
6148
|
+
const b = el("button", "btn ghost");
|
|
6149
|
+
b.type = "button";
|
|
6150
|
+
setWalletLabel(b, choice);
|
|
6151
|
+
b.addEventListener("click", () => {
|
|
6152
|
+
status.classList.remove("err");
|
|
6153
|
+
b.disabled = true;
|
|
6154
|
+
b.replaceChildren(el("span", "spin"), document.createTextNode("Approve in your wallet\u2026"));
|
|
6155
|
+
authApi
|
|
6156
|
+
.loginWithWallet({ getProvider: choice.get })
|
|
6157
|
+
.then((u) => {
|
|
6158
|
+
if (u)
|
|
6159
|
+
succeed(u);
|
|
6160
|
+
else {
|
|
6161
|
+
b.disabled = false;
|
|
6162
|
+
setWalletLabel(b, choice);
|
|
6163
|
+
}
|
|
6164
|
+
})
|
|
6165
|
+
.catch((err) => {
|
|
6166
|
+
b.disabled = false;
|
|
6167
|
+
setWalletLabel(b, choice);
|
|
6168
|
+
status.textContent = errText(err);
|
|
6169
|
+
status.classList.add("err");
|
|
6170
|
+
});
|
|
6171
|
+
});
|
|
6172
|
+
card.appendChild(b);
|
|
6173
|
+
}
|
|
6174
|
+
card.appendChild(status);
|
|
6175
|
+
return card;
|
|
6176
|
+
};
|
|
6177
|
+
// authMode:"turnkey" inline code entry. Same editorial card; the code is sealed
|
|
6178
|
+
// on the Bounded signer origin and verified server-side by Turnkey.
|
|
6179
|
+
const renderOtp = (handle) => {
|
|
6180
|
+
const card = el("div", "c");
|
|
6181
|
+
card.appendChild(style.cloneNode(true));
|
|
6182
|
+
const back = el("button", "back", "\u2190 Back");
|
|
6183
|
+
back.type = "button";
|
|
6184
|
+
back.addEventListener("click", () => swap(renderChoose()));
|
|
6185
|
+
card.appendChild(back);
|
|
6186
|
+
card.appendChild(el("h1", undefined, "Enter your code"));
|
|
6187
|
+
card.appendChild(el("p", "sub", `We sent a 6-digit code to ${handle.email}.`));
|
|
6188
|
+
const status = el("div", "status");
|
|
6189
|
+
const setErr = (msg) => { status.textContent = msg; status.classList.add("err"); };
|
|
6190
|
+
const form = el("form");
|
|
6191
|
+
const label = el("label");
|
|
6192
|
+
label.htmlFor = "bw-otp";
|
|
6193
|
+
label.textContent = "Verification code";
|
|
6194
|
+
const input = el("input", "otp");
|
|
6195
|
+
input.id = "bw-otp";
|
|
6196
|
+
input.type = "text";
|
|
6197
|
+
input.inputMode = "numeric";
|
|
6198
|
+
input.autocomplete = "one-time-code";
|
|
6199
|
+
input.maxLength = 6;
|
|
6200
|
+
input.placeholder = "000000";
|
|
6201
|
+
input.required = true;
|
|
6202
|
+
const submit = el("button", "btn primary");
|
|
6203
|
+
submit.type = "submit";
|
|
6204
|
+
submit.textContent = "Verify";
|
|
6205
|
+
form.append(label, input, submit);
|
|
6206
|
+
const verify = () => {
|
|
6207
|
+
const code = input.value.replace(/\D+/g, "");
|
|
6208
|
+
if (code.length < 6) {
|
|
6209
|
+
setErr("Enter the 6-digit code.");
|
|
6210
|
+
return;
|
|
6211
|
+
}
|
|
6212
|
+
status.classList.remove("err");
|
|
6213
|
+
status.textContent = "";
|
|
6214
|
+
submit.disabled = true;
|
|
6215
|
+
input.disabled = true;
|
|
6216
|
+
submit.replaceChildren(el("span", "spin"), document.createTextNode("Verifying\u2026"));
|
|
6217
|
+
handle
|
|
6218
|
+
.verify(code)
|
|
6219
|
+
.then(succeed)
|
|
6220
|
+
.catch((err) => {
|
|
6221
|
+
submit.disabled = false;
|
|
6222
|
+
input.disabled = false;
|
|
6223
|
+
submit.textContent = "Verify";
|
|
6224
|
+
input.value = "";
|
|
6225
|
+
input.focus();
|
|
6226
|
+
setErr(errText(err));
|
|
6227
|
+
});
|
|
6228
|
+
};
|
|
6229
|
+
form.addEventListener("submit", (e) => { e.preventDefault(); verify(); });
|
|
6230
|
+
// Auto-submit once six digits are in - the buttery one-shot paste/type path.
|
|
6231
|
+
input.addEventListener("input", () => {
|
|
6232
|
+
if (input.value.replace(/\D+/g, "").length === 6 && !submit.disabled)
|
|
6233
|
+
verify();
|
|
6234
|
+
});
|
|
6235
|
+
card.appendChild(form);
|
|
6236
|
+
card.appendChild(status);
|
|
6237
|
+
const foot = el("div", "foot");
|
|
6238
|
+
const poweredBy = el("a", undefined, "Powered by Bounded");
|
|
6239
|
+
poweredBy.href = "https://bounded.sh";
|
|
6240
|
+
poweredBy.target = "_blank";
|
|
6241
|
+
poweredBy.rel = "noopener";
|
|
6242
|
+
foot.appendChild(poweredBy);
|
|
6243
|
+
card.appendChild(foot);
|
|
6244
|
+
requestAnimationFrame(() => input.focus());
|
|
6245
|
+
return card;
|
|
6246
|
+
};
|
|
6247
|
+
const swap = (node) => {
|
|
6248
|
+
mountContent(m, node);
|
|
6249
|
+
requestAnimationFrame(() => fitPanelToContent(m));
|
|
6250
|
+
};
|
|
6251
|
+
swap(renderChoose());
|
|
6252
|
+
showModal(m);
|
|
6253
|
+
});
|
|
6254
|
+
}
|
|
6255
|
+
function cap(s) {
|
|
6256
|
+
return s ? s[0].toUpperCase() + s.slice(1) : s;
|
|
6257
|
+
}
|
|
6258
|
+
function errText(err) {
|
|
6259
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6260
|
+
if (/blocked/i.test(msg))
|
|
6261
|
+
return "Popup blocked - allow popups and try again.";
|
|
6262
|
+
return msg || "Something went wrong. Try again.";
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
var _a$2;
|
|
5007
6266
|
// Whether we're in a server-side rendering context (no hooks allowed).
|
|
5008
6267
|
// React Native has no `window` but DOES support hooks.
|
|
5009
6268
|
// We detect Node SSR specifically via process.versions.node to avoid
|
|
5010
6269
|
// false positives from Node 21+ which defines `navigator` globally.
|
|
5011
|
-
const isSSR$
|
|
6270
|
+
const isSSR$2 = typeof window === 'undefined'
|
|
5012
6271
|
&& typeof process !== 'undefined'
|
|
5013
|
-
&& !!((_a$
|
|
6272
|
+
&& !!((_a$2 = process.versions) === null || _a$2 === void 0 ? void 0 : _a$2.node);
|
|
5014
6273
|
function useAuth() {
|
|
5015
6274
|
// Provide a fallback so server render doesn't break
|
|
5016
|
-
if (isSSR$
|
|
6275
|
+
if (isSSR$2) {
|
|
5017
6276
|
return {
|
|
5018
6277
|
login: async (_options) => undefined,
|
|
5019
6278
|
logout: async () => undefined,
|
|
@@ -5075,6 +6334,72 @@ function useAuth() {
|
|
|
5075
6334
|
};
|
|
5076
6335
|
}
|
|
5077
6336
|
|
|
6337
|
+
var _a$1;
|
|
6338
|
+
// SSR guard (mirrors useAuth): no hooks/subscriptions during Node server render.
|
|
6339
|
+
// React Native has no `window` but DOES support hooks, so detect Node SSR
|
|
6340
|
+
// specifically via process.versions.node.
|
|
6341
|
+
const isSSR$1 = typeof window === 'undefined'
|
|
6342
|
+
&& typeof process !== 'undefined'
|
|
6343
|
+
&& !!((_a$1 = process.versions) === null || _a$1 === void 0 ? void 0 : _a$1.node);
|
|
6344
|
+
function useQuery(path, options) {
|
|
6345
|
+
if (isSSR$1) {
|
|
6346
|
+
return { data: undefined, loading: true, error: null };
|
|
6347
|
+
}
|
|
6348
|
+
const [data, setData] = React.useState(undefined);
|
|
6349
|
+
const [loading, setLoading] = React.useState(true);
|
|
6350
|
+
const [error, setError] = React.useState(null);
|
|
6351
|
+
// Re-subscribe only when the query actually changes (not on every render).
|
|
6352
|
+
const optionsKey = options ? JSON.stringify(options) : '';
|
|
6353
|
+
React.useEffect(() => {
|
|
6354
|
+
if (!path) {
|
|
6355
|
+
setData(undefined);
|
|
6356
|
+
setLoading(false);
|
|
6357
|
+
setError(null);
|
|
6358
|
+
return;
|
|
6359
|
+
}
|
|
6360
|
+
let active = true;
|
|
6361
|
+
let unsubscribe;
|
|
6362
|
+
setLoading(true);
|
|
6363
|
+
setError(null);
|
|
6364
|
+
subscribe(path, Object.assign(Object.assign({}, options), { onData: (d) => {
|
|
6365
|
+
if (!active)
|
|
6366
|
+
return;
|
|
6367
|
+
// A reconnect can legitimately surface a transport error before the
|
|
6368
|
+
// socket re-authenticates and sends a new authorized snapshot. That
|
|
6369
|
+
// fresh snapshot is the recovery boundary: keep the error visible
|
|
6370
|
+
// until data arrives, then clear it so React does not remain stuck on
|
|
6371
|
+
// a failure page after realtime has recovered.
|
|
6372
|
+
setError(null);
|
|
6373
|
+
setData(d);
|
|
6374
|
+
setLoading(false);
|
|
6375
|
+
}, onError: (e) => {
|
|
6376
|
+
if (!active)
|
|
6377
|
+
return;
|
|
6378
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
6379
|
+
setLoading(false);
|
|
6380
|
+
} }))
|
|
6381
|
+
.then((unsub) => {
|
|
6382
|
+
if (active)
|
|
6383
|
+
unsubscribe = unsub;
|
|
6384
|
+
else
|
|
6385
|
+
void unsub(); // unmounted before subscribe resolved
|
|
6386
|
+
})
|
|
6387
|
+
.catch((e) => {
|
|
6388
|
+
if (!active)
|
|
6389
|
+
return;
|
|
6390
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
6391
|
+
setLoading(false);
|
|
6392
|
+
});
|
|
6393
|
+
return () => {
|
|
6394
|
+
active = false;
|
|
6395
|
+
void (unsubscribe === null || unsubscribe === void 0 ? void 0 : unsubscribe());
|
|
6396
|
+
};
|
|
6397
|
+
// optionsKey captures the option object's contents; path is the other input.
|
|
6398
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
6399
|
+
}, [path, optionsKey]);
|
|
6400
|
+
return { data, loading, error };
|
|
6401
|
+
}
|
|
6402
|
+
|
|
5078
6403
|
// Wrapper for getIdToken - passes isServer=false for client-side usage
|
|
5079
6404
|
async function getIdToken() {
|
|
5080
6405
|
return getIdToken$1(false);
|
|
@@ -5355,9 +6680,11 @@ const BUILD_REVIEW_CAPABILITY_SCHEMA = 'bounded.build-review-capability.v1';
|
|
|
5355
6680
|
const BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA = 'bounded.build-review-capability-refresh.v1';
|
|
5356
6681
|
const BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA = 'bounded.build-delegated-review-capability.v1';
|
|
5357
6682
|
|
|
6683
|
+
const FAST_POLL_MS = 1000;
|
|
5358
6684
|
const NORMAL_POLL_MS = 2000;
|
|
5359
6685
|
const PARKED_POLL_MS = 5000;
|
|
5360
6686
|
const LONG_RUNNING_POLL_MS = 10000;
|
|
6687
|
+
const THIRTY_SECONDS_MS = 30000;
|
|
5361
6688
|
const TWO_MINUTES_MS = 2 * 60000;
|
|
5362
6689
|
const TEN_MINUTES_MS = 10 * 60000;
|
|
5363
6690
|
const terminalStates = new Set(TERMINAL_STATES);
|
|
@@ -5366,6 +6693,10 @@ function pollCadence(state, elapsedMs) {
|
|
|
5366
6693
|
return LONG_RUNNING_POLL_MS;
|
|
5367
6694
|
if ((state === null || state === void 0 ? void 0 : state.startsWith('parked')) || elapsedMs >= TWO_MINUTES_MS)
|
|
5368
6695
|
return PARKED_POLL_MS;
|
|
6696
|
+
// The first 30s is where the user is staring at "waiting for a build slot" /
|
|
6697
|
+
// "building" and every transition matters most; poll fast, then relax.
|
|
6698
|
+
if (elapsedMs < THIRTY_SECONDS_MS)
|
|
6699
|
+
return FAST_POLL_MS;
|
|
5369
6700
|
return NORMAL_POLL_MS;
|
|
5370
6701
|
}
|
|
5371
6702
|
function retryCadence(error, state, elapsedMs) {
|
|
@@ -5451,9 +6782,18 @@ function watch(appId, runId, onUpdate, options = {}) {
|
|
|
5451
6782
|
const poll = async () => {
|
|
5452
6783
|
if (!active)
|
|
5453
6784
|
return;
|
|
6785
|
+
// Fire the view and the events page concurrently: the events cursor (lastSeq)
|
|
6786
|
+
// does not depend on the run view, so there is no reason to pay two serial
|
|
6787
|
+
// round-trips per tick. Error semantics below are unchanged — the run result
|
|
6788
|
+
// still gates, and a failed events page still publishes the run with no rows.
|
|
6789
|
+
const runPromise = get(appId, runId);
|
|
6790
|
+
const eventsPromise = events(appId, runId, lastSeq);
|
|
6791
|
+
// Keep the in-flight events page from becoming an unhandled rejection if the
|
|
6792
|
+
// run result makes us bail before awaiting it; the real await below still sees it.
|
|
6793
|
+
eventsPromise.catch(() => { });
|
|
5454
6794
|
let run;
|
|
5455
6795
|
try {
|
|
5456
|
-
const view = await
|
|
6796
|
+
const view = await runPromise;
|
|
5457
6797
|
if (!active)
|
|
5458
6798
|
return;
|
|
5459
6799
|
run = view.run;
|
|
@@ -5471,7 +6811,7 @@ function watch(appId, runId, onUpdate, options = {}) {
|
|
|
5471
6811
|
return;
|
|
5472
6812
|
}
|
|
5473
6813
|
try {
|
|
5474
|
-
const response = await
|
|
6814
|
+
const response = await eventsPromise;
|
|
5475
6815
|
if (!active)
|
|
5476
6816
|
return;
|
|
5477
6817
|
const deduped = newEvents(Array.isArray(response.events) ? response.events : [], lastSeq, seenSeqs);
|
|
@@ -5680,6 +7020,302 @@ async function deserializeTransaction(tx) {
|
|
|
5680
7020
|
}
|
|
5681
7021
|
}
|
|
5682
7022
|
|
|
7023
|
+
// turnkey-wallet-widget.ts - runs one Turnkey signer exchange inside the shared
|
|
7024
|
+
// in-app WIDGET (bounded-widget-modal.ts). The Bounded-owned signer page is loaded
|
|
7025
|
+
// in a warm iframe behind the modal so signing feels native: an in-app card plus
|
|
7026
|
+
// the OS Face ID / Touch ID sheet, never a jarring popup.
|
|
7027
|
+
//
|
|
7028
|
+
// WHY AN IFRAME: a WebAuthn passkey is bound to ONE origin, so passkey create/sign
|
|
7029
|
+
// MUST run on the single Bounded signer origin. The iframe carries the
|
|
7030
|
+
// publickey-credentials-{get,create} permission policy and the user's click lands
|
|
7031
|
+
// INSIDE it, giving WebAuthn the transient activation it needs.
|
|
7032
|
+
//
|
|
7033
|
+
// SAFARI / restricted embedders: cross-origin iframe passkey CREATE is unreliable
|
|
7034
|
+
// in Safari. The signer detects that and posts `fallbackPopup`; we then rerun the
|
|
7035
|
+
// SAME request through `popupFallback` (a real window). Callers pass a popup
|
|
7036
|
+
// implementation and never see the difference.
|
|
7037
|
+
/**
|
|
7038
|
+
* Run one signer exchange through the in-app widget. Resolves when `onResult`
|
|
7039
|
+
* accepts a result; falls back to `popupFallback` if the frame cannot run WebAuthn
|
|
7040
|
+
* (Safari cross-origin create) or cannot be mounted.
|
|
7041
|
+
*/
|
|
7042
|
+
async function runWidget(url, signerOrigin, ex, popupFallback) {
|
|
7043
|
+
if (!hasDOM$2())
|
|
7044
|
+
return popupFallback();
|
|
7045
|
+
const m = getModal();
|
|
7046
|
+
const requestId = String(ex.request.requestId);
|
|
7047
|
+
// Serialize: WebAuthn is modal, and one warm frame handles one request at a time.
|
|
7048
|
+
const run = m.chain.then(() => new Promise((resolve, reject) => {
|
|
7049
|
+
let settled = false;
|
|
7050
|
+
let usedFallback = false;
|
|
7051
|
+
const iframe = ensureIframe(m, url, signerOrigin);
|
|
7052
|
+
const send = () => {
|
|
7053
|
+
if (!iframe.contentWindow)
|
|
7054
|
+
return;
|
|
7055
|
+
try {
|
|
7056
|
+
iframe.contentWindow.postMessage(ex.request, signerOrigin);
|
|
7057
|
+
}
|
|
7058
|
+
catch (_a) {
|
|
7059
|
+
/* frame gone */
|
|
7060
|
+
}
|
|
7061
|
+
};
|
|
7062
|
+
let loadTimer = 0;
|
|
7063
|
+
let ceilingTimer = 0;
|
|
7064
|
+
const finish = (fn) => {
|
|
7065
|
+
if (settled)
|
|
7066
|
+
return;
|
|
7067
|
+
settled = true;
|
|
7068
|
+
m.onIframeMessage = null;
|
|
7069
|
+
m.onBackdrop = null;
|
|
7070
|
+
window.clearTimeout(loadTimer);
|
|
7071
|
+
window.clearTimeout(ceilingTimer);
|
|
7072
|
+
fn();
|
|
7073
|
+
};
|
|
7074
|
+
const goFallback = () => {
|
|
7075
|
+
if (usedFallback || settled)
|
|
7076
|
+
return;
|
|
7077
|
+
usedFallback = true;
|
|
7078
|
+
hideModal(m);
|
|
7079
|
+
finish(() => popupFallback().then(resolve, reject));
|
|
7080
|
+
};
|
|
7081
|
+
m.onBackdrop = () => {
|
|
7082
|
+
hideModal(m);
|
|
7083
|
+
finish(() => reject(new Error("cancelled")));
|
|
7084
|
+
};
|
|
7085
|
+
m.onIframeMessage = (d) => {
|
|
7086
|
+
if (d.type === "bounded:turnkey:ready") {
|
|
7087
|
+
m.iframeReady = true;
|
|
7088
|
+
send();
|
|
7089
|
+
return;
|
|
7090
|
+
}
|
|
7091
|
+
if (d.type === "bounded:turnkey:resize" && typeof d.height === "number") {
|
|
7092
|
+
setPanelHeight(m, d.height);
|
|
7093
|
+
return;
|
|
7094
|
+
}
|
|
7095
|
+
if (d.type === "bounded:turnkey:fallbackPopup" && d.requestId === requestId) {
|
|
7096
|
+
goFallback();
|
|
7097
|
+
return;
|
|
7098
|
+
}
|
|
7099
|
+
if (d.type === "bounded:turnkey:result" && d.requestId === requestId) {
|
|
7100
|
+
const outcome = ex.onResult(d);
|
|
7101
|
+
if ("error" in outcome) {
|
|
7102
|
+
hideModal(m);
|
|
7103
|
+
finish(() => reject(new Error(outcome.error)));
|
|
7104
|
+
return;
|
|
7105
|
+
}
|
|
7106
|
+
if (outcome.done) {
|
|
7107
|
+
hideModal(m);
|
|
7108
|
+
finish(() => resolve(outcome.value));
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7111
|
+
};
|
|
7112
|
+
showModal(m);
|
|
7113
|
+
if (m.iframeReady)
|
|
7114
|
+
send();
|
|
7115
|
+
// If the frame never loads, degrade to the popup (best-effort - a post-load
|
|
7116
|
+
// popup may be blocked without a gesture, in which case the fallback rejects).
|
|
7117
|
+
loadTimer = window.setTimeout(() => {
|
|
7118
|
+
if (!m.iframeReady && !settled)
|
|
7119
|
+
goFallback();
|
|
7120
|
+
}, 8000);
|
|
7121
|
+
// Absolute ceiling so a stuck flow cannot hang forever.
|
|
7122
|
+
ceilingTimer = window.setTimeout(() => {
|
|
7123
|
+
if (!settled) {
|
|
7124
|
+
hideModal(m);
|
|
7125
|
+
finish(() => reject(new Error("Wallet approval timed out.")));
|
|
7126
|
+
}
|
|
7127
|
+
}, 5 * 60 * 1000);
|
|
7128
|
+
}));
|
|
7129
|
+
m.chain = run.then(() => undefined, () => undefined);
|
|
7130
|
+
return run;
|
|
7131
|
+
}
|
|
7132
|
+
|
|
7133
|
+
// turnkey-signer-bridge.ts - routes NON-CUSTODIAL Turnkey embedded-wallet signing
|
|
7134
|
+
// through the Bounded-owned signer page (auth.bounded.sh/wallet/turnkey/signer).
|
|
7135
|
+
//
|
|
7136
|
+
// TRANSPORT: by default the signer runs inside the in-app WIDGET (turnkey-wallet-
|
|
7137
|
+
// widget.ts) - an iframe behind a Shadow-DOM modal that matches the Bounded widget
|
|
7138
|
+
// look + motion, so signing feels native. Safari (unreliable at iframe passkey
|
|
7139
|
+
// CREATE) and any environment where the frame cannot mount fall back to a real
|
|
7140
|
+
// popup window; both transports speak the same postMessage protocol.
|
|
7141
|
+
//
|
|
7142
|
+
// Unlike the Crossmint bridge (crossmint-signer-bridge.ts), Turnkey wallets DO
|
|
7143
|
+
// produce raw detached signatures: the signer runs the Turnkey browser SDK on the
|
|
7144
|
+
// single Bounded origin, enrolls the user's passkey on first use (lazy - the
|
|
7145
|
+
// sub-org is created with the passkey + email attached at that moment), and stamps
|
|
7146
|
+
// an ed25519 signRawPayload with that passkey. So this bridge exposes a
|
|
7147
|
+
// `signSolanaMessage` primitive; the app builds + submits the transaction itself.
|
|
7148
|
+
//
|
|
7149
|
+
// ONE ORIGIN, ONE PASSKEY: a WebAuthn passkey is bound to a single origin, so ALL
|
|
7150
|
+
// Bounded apps (any subdomain / custom domain) sign through this one origin - giving
|
|
7151
|
+
// every user one wallet + one passkey that roams across every app.
|
|
7152
|
+
function hasDOM() {
|
|
7153
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
7154
|
+
}
|
|
7155
|
+
// Safari (incl. iOS WebViews) is unreliable at running a WebAuthn CREATE inside a
|
|
7156
|
+
// cross-origin iframe, so the widget's automatic in-iframe passkey enrollment can
|
|
7157
|
+
// fail - and a post-failure popup would no longer have a user gesture to open with.
|
|
7158
|
+
// There we pick the popup up front, from the original click, so enrollment is solid.
|
|
7159
|
+
// Everywhere else the inline widget is used (buttery in-app modal).
|
|
7160
|
+
function preferPopup() {
|
|
7161
|
+
if (!hasDOM() || typeof navigator === 'undefined')
|
|
7162
|
+
return true;
|
|
7163
|
+
const ua = navigator.userAgent || '';
|
|
7164
|
+
return /^((?!chrome|android|crios|fxios|edg|edga|opr).)*safari/i.test(ua);
|
|
7165
|
+
}
|
|
7166
|
+
// Dispatch one signer exchange through the in-app WIDGET, degrading to the POPUP
|
|
7167
|
+
// when we cannot embed (Safari enrollment, or the frame never loads).
|
|
7168
|
+
async function deliver(url, origin, ex) {
|
|
7169
|
+
const popup = () => runPopup(url, origin, ex);
|
|
7170
|
+
if (preferPopup())
|
|
7171
|
+
return popup();
|
|
7172
|
+
return runWidget(url, origin, { request: ex.request, onResult: ex.onResult }, popup);
|
|
7173
|
+
}
|
|
7174
|
+
async function turnkeySignerPage() {
|
|
7175
|
+
const cfg = await getConfig();
|
|
7176
|
+
const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
7177
|
+
return { url: `${base}/wallet/turnkey/signer`, origin: new URL(base).origin };
|
|
7178
|
+
}
|
|
7179
|
+
function toHex(bytes) {
|
|
7180
|
+
let h = '';
|
|
7181
|
+
for (const b of bytes)
|
|
7182
|
+
h += b.toString(16).padStart(2, '0');
|
|
7183
|
+
return h;
|
|
7184
|
+
}
|
|
7185
|
+
function fromHex(hex) {
|
|
7186
|
+
const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
7187
|
+
const out = new Uint8Array(clean.length / 2);
|
|
7188
|
+
for (let i = 0; i < out.length; i++)
|
|
7189
|
+
out[i] = parseInt(clean.substr(i * 2, 2), 16);
|
|
7190
|
+
return out;
|
|
7191
|
+
}
|
|
7192
|
+
let requestSeq = 0;
|
|
7193
|
+
// Shared popup driver: open the signer page, do the ready/postMessage handshake, and
|
|
7194
|
+
// resolve when `onResult` accepts a result message. Origin + source are pinned.
|
|
7195
|
+
async function runPopup(url, signerOrigin, ex) {
|
|
7196
|
+
var _a, _b;
|
|
7197
|
+
const w = (_a = ex.width) !== null && _a !== void 0 ? _a : 420, h = (_b = ex.height) !== null && _b !== void 0 ? _b : 620;
|
|
7198
|
+
const left = window.screenX + Math.max(0, (window.outerWidth - w) / 2);
|
|
7199
|
+
const top = window.screenY + Math.max(0, (window.outerHeight - h) / 2);
|
|
7200
|
+
const popup = window.open(url, 'bounded-turnkey-signer', `width=${w},height=${h},left=${left},top=${top}`);
|
|
7201
|
+
if (!popup) {
|
|
7202
|
+
throw new Error('Popup blocked - call this from a user gesture (e.g. a click handler) so the passkey approval window can open.');
|
|
7203
|
+
}
|
|
7204
|
+
return await new Promise((resolve, reject) => {
|
|
7205
|
+
let settled = false;
|
|
7206
|
+
let sentRequest = false;
|
|
7207
|
+
let closeTimer;
|
|
7208
|
+
const cleanup = () => { settled = true; window.removeEventListener('message', onMsg); if (closeTimer)
|
|
7209
|
+
clearInterval(closeTimer); };
|
|
7210
|
+
const sendRequest = () => { if (sentRequest)
|
|
7211
|
+
return; sentRequest = true; try {
|
|
7212
|
+
popup.postMessage(ex.request, signerOrigin);
|
|
7213
|
+
}
|
|
7214
|
+
catch ( /* popup gone */_a) { /* popup gone */ } };
|
|
7215
|
+
const onMsg = (event) => {
|
|
7216
|
+
if (event.origin !== signerOrigin)
|
|
7217
|
+
return;
|
|
7218
|
+
if (event.source !== popup)
|
|
7219
|
+
return;
|
|
7220
|
+
const d = event.data;
|
|
7221
|
+
if (!d || typeof d !== 'object')
|
|
7222
|
+
return;
|
|
7223
|
+
if (d.type === 'bounded:turnkey:ready') {
|
|
7224
|
+
sendRequest();
|
|
7225
|
+
return;
|
|
7226
|
+
}
|
|
7227
|
+
if (d.type === 'bounded:turnkey:result' && d.requestId === ex.request.requestId) {
|
|
7228
|
+
const outcome = ex.onResult(d);
|
|
7229
|
+
if ('error' in outcome) {
|
|
7230
|
+
cleanup();
|
|
7231
|
+
try {
|
|
7232
|
+
popup.close();
|
|
7233
|
+
}
|
|
7234
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
7235
|
+
reject(new Error(outcome.error));
|
|
7236
|
+
return;
|
|
7237
|
+
}
|
|
7238
|
+
if (outcome.done) {
|
|
7239
|
+
cleanup();
|
|
7240
|
+
try {
|
|
7241
|
+
popup.close();
|
|
7242
|
+
}
|
|
7243
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
7244
|
+
resolve(outcome.value);
|
|
7245
|
+
}
|
|
7246
|
+
}
|
|
7247
|
+
};
|
|
7248
|
+
window.addEventListener('message', onMsg);
|
|
7249
|
+
setTimeout(sendRequest, 1200);
|
|
7250
|
+
closeTimer = setInterval(() => {
|
|
7251
|
+
if (popup.closed) {
|
|
7252
|
+
clearInterval(closeTimer);
|
|
7253
|
+
setTimeout(() => { if (!settled) {
|
|
7254
|
+
cleanup();
|
|
7255
|
+
reject(new Error('Wallet approval window was closed before it completed.'));
|
|
7256
|
+
} }, 400);
|
|
7257
|
+
}
|
|
7258
|
+
}, 500);
|
|
7259
|
+
setTimeout(() => { if (!settled) {
|
|
7260
|
+
cleanup();
|
|
7261
|
+
try {
|
|
7262
|
+
popup.close();
|
|
7263
|
+
}
|
|
7264
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
7265
|
+
reject(new Error('Wallet approval timed out.'));
|
|
7266
|
+
} }, 5 * 60 * 1000);
|
|
7267
|
+
});
|
|
7268
|
+
}
|
|
7269
|
+
/**
|
|
7270
|
+
* Get-or-create the user's non-custodial Turnkey wallet, resolving with its stable
|
|
7271
|
+
* Solana address. On first use the popup enrolls a device passkey (Face ID / Touch ID)
|
|
7272
|
+
* and creates the wallet (lazy) with no email code; if the wallet already exists it
|
|
7273
|
+
* resolves immediately without a prompt. Call from a USER GESTURE.
|
|
7274
|
+
*
|
|
7275
|
+
* Use this to learn the fee-payer address BEFORE building a transaction to sign.
|
|
7276
|
+
*/
|
|
7277
|
+
async function getOrCreateTurnkeyWallet() {
|
|
7278
|
+
if (!hasDOM())
|
|
7279
|
+
throw new Error('Turnkey embedded wallets are only available in the browser (popup-based).');
|
|
7280
|
+
const { url, origin } = await turnkeySignerPage();
|
|
7281
|
+
const requestId = `tkcreate_${Date.now()}_${++requestSeq}`;
|
|
7282
|
+
return await deliver(url, origin, {
|
|
7283
|
+
request: { type: 'bounded:turnkey:createWallet', requestId },
|
|
7284
|
+
onResult: (d) => {
|
|
7285
|
+
if (d.ok && typeof d.address === 'string')
|
|
7286
|
+
return { done: true, value: { address: d.address, subOrgId: String(d.subOrgId || '') } };
|
|
7287
|
+
return { error: String(d.error || 'Wallet setup failed') };
|
|
7288
|
+
},
|
|
7289
|
+
});
|
|
7290
|
+
}
|
|
7291
|
+
/**
|
|
7292
|
+
* Open the Bounded Turnkey signer popup and sign `message` (raw bytes) with the
|
|
7293
|
+
* user's non-custodial Turnkey wallet, resolving with the ed25519 signature +
|
|
7294
|
+
* the wallet address. MUST be called from a USER GESTURE (click/tap) or the
|
|
7295
|
+
* browser blocks the popup.
|
|
7296
|
+
*
|
|
7297
|
+
* On the user's FIRST signature the popup transparently enrolls a device passkey
|
|
7298
|
+
* (Face ID / Touch ID) and creates their wallet - no email code. Subsequent
|
|
7299
|
+
* signatures are just a passkey approval.
|
|
7300
|
+
*/
|
|
7301
|
+
async function signSolanaMessageViaTurnkey(message) {
|
|
7302
|
+
if (!hasDOM()) {
|
|
7303
|
+
throw new Error('Turnkey embedded-wallet signing is only available in the browser (popup-based).');
|
|
7304
|
+
}
|
|
7305
|
+
const { url, origin } = await turnkeySignerPage();
|
|
7306
|
+
const requestId = `tksign_${Date.now()}_${++requestSeq}`;
|
|
7307
|
+
const messageHex = toHex(message);
|
|
7308
|
+
return await deliver(url, origin, {
|
|
7309
|
+
request: { type: 'bounded:turnkey:sign', requestId, messageHex },
|
|
7310
|
+
onResult: (d) => {
|
|
7311
|
+
if (d.ok && typeof d.signatureHex === 'string' && typeof d.address === 'string') {
|
|
7312
|
+
return { done: true, value: { signature: fromHex(d.signatureHex), address: d.address, subOrgId: String(d.subOrgId || '') } };
|
|
7313
|
+
}
|
|
7314
|
+
return { error: String(d.error || 'Turnkey signing failed') };
|
|
7315
|
+
},
|
|
7316
|
+
});
|
|
7317
|
+
}
|
|
7318
|
+
|
|
5683
7319
|
/**
|
|
5684
7320
|
* Extracts a human-readable error message from Solana transaction logs.
|
|
5685
7321
|
*/
|
|
@@ -6063,5 +7699,90 @@ class PrivyExpoProvider {
|
|
|
6063
7699
|
}
|
|
6064
7700
|
}
|
|
6065
7701
|
|
|
6066
|
-
|
|
6067
|
-
|
|
7702
|
+
/**
|
|
7703
|
+
* Default provider discovery: Phantom-first, then any Wallet-Standard
|
|
7704
|
+
* `window.solana`. Kept tiny + synchronous (no heavy imports) so it works inside
|
|
7705
|
+
* a user gesture and can be eagerly exported from the SDK entry.
|
|
7706
|
+
*/
|
|
7707
|
+
function defaultInjectedProvider() {
|
|
7708
|
+
var _a;
|
|
7709
|
+
if (typeof window === 'undefined')
|
|
7710
|
+
return null;
|
|
7711
|
+
const w = window;
|
|
7712
|
+
const phantom = (_a = w === null || w === void 0 ? void 0 : w.phantom) === null || _a === void 0 ? void 0 : _a.solana;
|
|
7713
|
+
if (phantom && typeof phantom.signMessage === 'function')
|
|
7714
|
+
return phantom;
|
|
7715
|
+
const generic = w === null || w === void 0 ? void 0 : w.solana;
|
|
7716
|
+
if (generic && typeof generic.signMessage === 'function')
|
|
7717
|
+
return generic;
|
|
7718
|
+
return null;
|
|
7719
|
+
}
|
|
7720
|
+
|
|
7721
|
+
// injected-evm-wallet-types.ts — tiny, dependency-free surface for the EVM
|
|
7722
|
+
// WALLET LOGIN path (BYO wallet via SIWE). The EVM twin of
|
|
7723
|
+
// injected-wallet-types.ts: kept separate from injected-evm-wallet-provider.ts
|
|
7724
|
+
// so the SDK entry can eagerly export the type surface + the provider-discovery
|
|
7725
|
+
// helper WITHOUT dragging the provider class into the default bundle. The
|
|
7726
|
+
// provider is lazy-loaded by getAuthProvider() when an app opts into
|
|
7727
|
+
// authMethod:'evm-wallet'.
|
|
7728
|
+
//
|
|
7729
|
+
// It rides the standard EIP-1193 injected provider a browser wallet exposes
|
|
7730
|
+
// (MetaMask / Rabby / Coinbase Wallet / any window.ethereum), discovered via
|
|
7731
|
+
// EIP-6963 (multi-wallet, collision-free) with a window.ethereum fallback. It
|
|
7732
|
+
// needs NO viem/ethers and NO React — EIP-1193 request() + hex strings suffice.
|
|
7733
|
+
// EIP-6963 is announcement-based: wallets add an `eip6963:requestProvider`
|
|
7734
|
+
// listener and synchronously dispatch `eip6963:announceProvider` in response. We
|
|
7735
|
+
// keep a persistent collector so announcements (including late ones) are
|
|
7736
|
+
// captured, and re-request on demand.
|
|
7737
|
+
const announcedProviders = [];
|
|
7738
|
+
let announceListenerAttached = false;
|
|
7739
|
+
function attachAnnounceListener() {
|
|
7740
|
+
if (announceListenerAttached || typeof window === 'undefined')
|
|
7741
|
+
return;
|
|
7742
|
+
announceListenerAttached = true;
|
|
7743
|
+
window.addEventListener('eip6963:announceProvider', (event) => {
|
|
7744
|
+
const detail = event === null || event === void 0 ? void 0 : event.detail;
|
|
7745
|
+
if (detail &&
|
|
7746
|
+
detail.provider &&
|
|
7747
|
+
typeof detail.provider.request === 'function' &&
|
|
7748
|
+
!announcedProviders.some((d) => { var _a, _b; return ((_a = d.info) === null || _a === void 0 ? void 0 : _a.uuid) === ((_b = detail.info) === null || _b === void 0 ? void 0 : _b.uuid); })) {
|
|
7749
|
+
announcedProviders.push(detail);
|
|
7750
|
+
}
|
|
7751
|
+
});
|
|
7752
|
+
}
|
|
7753
|
+
/**
|
|
7754
|
+
* Request + collect EIP-6963 providers. Dispatches `eip6963:requestProvider`
|
|
7755
|
+
* (synchronous — already-loaded wallets announce in the same tick) and returns
|
|
7756
|
+
* every wallet announced so far. Kept synchronous so it works inside a user
|
|
7757
|
+
* gesture and can be eagerly exported from the SDK entry.
|
|
7758
|
+
*/
|
|
7759
|
+
function requestEip6963Providers() {
|
|
7760
|
+
if (typeof window === 'undefined')
|
|
7761
|
+
return [];
|
|
7762
|
+
attachAnnounceListener();
|
|
7763
|
+
try {
|
|
7764
|
+
window.dispatchEvent(new Event('eip6963:requestProvider'));
|
|
7765
|
+
}
|
|
7766
|
+
catch (_a) {
|
|
7767
|
+
/* older browsers without Event constructor — fall through to window.ethereum */
|
|
7768
|
+
}
|
|
7769
|
+
return announcedProviders.slice();
|
|
7770
|
+
}
|
|
7771
|
+
/**
|
|
7772
|
+
* Default EVM provider discovery: EIP-6963 first (multi-wallet, collision-free),
|
|
7773
|
+
* then legacy `window.ethereum`. Returns null when no injected wallet is present.
|
|
7774
|
+
*/
|
|
7775
|
+
function defaultInjectedEvmProvider() {
|
|
7776
|
+
if (typeof window === 'undefined')
|
|
7777
|
+
return null;
|
|
7778
|
+
const details = requestEip6963Providers();
|
|
7779
|
+
if (details.length > 0)
|
|
7780
|
+
return details[0].provider;
|
|
7781
|
+
const eth = window.ethereum;
|
|
7782
|
+
if (eth && typeof eth.request === 'function')
|
|
7783
|
+
return eth;
|
|
7784
|
+
return null;
|
|
7785
|
+
}
|
|
7786
|
+
|
|
7787
|
+
export { BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA as $, completeLoginFromRedirect as A, loginWithPopup as B, completeLoginInPopup as C, openBoundedWidget as D, EMBEDDED_WALLET_UNSUPPORTED as E, useAuth as F, useQuery as G, useBuildRun as H, apps as I, deserializeTransaction as J, signSolanaMessageViaTurnkey as K, getOrCreateTurnkeyWallet as L, startTurnkeyEmailLogin as M, getIdToken as N, setPlatform as O, OffchainAuthProvider as P, PrivyExpoProvider as Q, requestEip6963Providers as R, builds as S, BuildsApiError as T, NON_TERMINAL_STATES as U, TERMINAL_STATES as V, RUN_STATES as W, PARK_REASONS as X, FAILURE_REASONS as Y, BUILD_REVIEW_CAPABILITY_SCHEMA as Z, BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA as _, hasEmbeddedEvmWallet as a, embeddedSignAndSubmit as b, setAuthLoading as c, defaultInjectedProvider as d, embeddedRunEvmTransaction as e, bufferExports as f, getPlatform as g, hasEmbeddedWallet as h, bs58 as i, confirmAndCheckTransaction as j, defaultInjectedEvmProvider as k, init as l, getCurrentUser as m, normalizeSolanaRpcUrl as n, onAuthStateChanged as o, onAuthLoadingChanged as p, getAuthLoading as q, resolveSolanaRpcUrl as r, setCurrentUser as s, login as t, logout as u, getAuthProvider as v, signInAnonymously as w, loginWithPrivy as x, loginWithWallet as y, loginWithRedirect as z };
|
|
7788
|
+
//# sourceMappingURL=index-C7rOe6B2.mjs.map
|