@bounded-sh/client 0.0.98 → 0.0.100-performance.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/bounded-widget-modal.d.ts +7 -2
- package/dist/auth/oidc-auth.d.ts +1 -1
- package/dist/auth/session-provider.d.ts +1 -0
- package/dist/auth/turnkey-auth.d.ts +4 -1
- package/dist/auth/turnkey-signer-bridge.d.ts +8 -5
- package/dist/auth/turnkey-signer-frame.d.ts +4 -1
- package/dist/auth/turnkey-wallet-context.d.ts +24 -0
- package/dist/auth/turnkey-wallet-widget.d.ts +2 -0
- package/dist/builds/types.d.ts +31 -0
- package/dist/{email-auth-provider-DF7Urh8i.mjs → email-auth-provider-BGTfU1_i.mjs} +2 -2
- package/dist/{email-auth-provider-CiPAD3vz.mjs.map → email-auth-provider-BGTfU1_i.mjs.map} +1 -1
- package/dist/{email-auth-provider-DxB_pJKO.js → email-auth-provider-C6SFJd9L.js} +2 -2
- package/dist/{email-auth-provider-DxB_pJKO.js.map → email-auth-provider-C6SFJd9L.js.map} +1 -1
- package/dist/{email-auth-provider-CiPAD3vz.mjs → email-auth-provider-CIRbOSM3.mjs} +2 -2
- package/dist/{email-auth-provider-DF7Urh8i.mjs.map → email-auth-provider-CIRbOSM3.mjs.map} +1 -1
- package/dist/{email-auth-provider-BhYzRN0r.js → email-auth-provider-Cpj8hIFu.js} +2 -2
- package/dist/{email-auth-provider-BhYzRN0r.js.map → email-auth-provider-Cpj8hIFu.js.map} +1 -1
- package/dist/{guest-auth-provider-BiM35KHb.js → guest-auth-provider-0PjGFGDm.js} +2 -2
- package/dist/{guest-auth-provider-BiM35KHb.js.map → guest-auth-provider-0PjGFGDm.js.map} +1 -1
- package/dist/{guest-auth-provider-Dtsq4QaY.mjs → guest-auth-provider-C23vWrOT.mjs} +2 -2
- package/dist/{guest-auth-provider-DVw-tITC.mjs.map → guest-auth-provider-C23vWrOT.mjs.map} +1 -1
- package/dist/{guest-auth-provider-DVw-tITC.mjs → guest-auth-provider-DVxlea-z.mjs} +2 -2
- package/dist/{guest-auth-provider-Dtsq4QaY.mjs.map → guest-auth-provider-DVxlea-z.mjs.map} +1 -1
- package/dist/{guest-auth-provider-B9C3Iub_.js → guest-auth-provider-pC-AheTZ.js} +2 -2
- package/dist/{guest-auth-provider-B9C3Iub_.js.map → guest-auth-provider-pC-AheTZ.js.map} +1 -1
- package/dist/{index-CYdYsf8s.js → index-DUVRmF9-.js} +971 -606
- package/dist/index-DUVRmF9-.js.map +1 -0
- package/dist/{index-Cflemdaf.mjs → index-DmAXYXDi.mjs} +970 -607
- package/dist/index-DmAXYXDi.mjs.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{index.native-BZOWS_sR.js → index.native-CllNqdoy.js} +442 -81
- package/dist/index.native-CllNqdoy.js.map +1 -0
- package/dist/{index.native-gVbAsohv.mjs → index.native-r1I-9E8e.mjs} +442 -81
- package/dist/index.native-r1I-9E8e.mjs.map +1 -0
- package/dist/index.native.js +1 -1
- package/dist/index.native.mjs +1 -1
- package/dist/{injected-evm-wallet-provider-CMqUqqxN.mjs → injected-evm-wallet-provider-BPZMEE8I.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-CMqUqqxN.mjs.map → injected-evm-wallet-provider-BPZMEE8I.mjs.map} +1 -1
- package/dist/{injected-evm-wallet-provider-C3MwUTRd.js → injected-evm-wallet-provider-BXM8TfS3.js} +2 -2
- package/dist/{injected-evm-wallet-provider-C3MwUTRd.js.map → injected-evm-wallet-provider-BXM8TfS3.js.map} +1 -1
- package/dist/{injected-evm-wallet-provider-K0j697jt.mjs → injected-evm-wallet-provider-CkhN5u3d.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-K0j697jt.mjs.map → injected-evm-wallet-provider-CkhN5u3d.mjs.map} +1 -1
- package/dist/{injected-evm-wallet-provider-Bv8VLuB1.js → injected-evm-wallet-provider-Dq3FDV1M.js} +2 -2
- package/dist/{injected-evm-wallet-provider-Bv8VLuB1.js.map → injected-evm-wallet-provider-Dq3FDV1M.js.map} +1 -1
- package/dist/{injected-wallet-provider-BDt4CN0b.js → injected-wallet-provider-B3LLEpfp.js} +2 -2
- package/dist/injected-wallet-provider-B3LLEpfp.js.map +1 -0
- package/dist/{injected-wallet-provider-CG3oKdp_.js → injected-wallet-provider-Bi_mB_q3.js} +2 -2
- package/dist/injected-wallet-provider-Bi_mB_q3.js.map +1 -0
- package/dist/{injected-wallet-provider-DH8yLpyf.mjs → injected-wallet-provider-BlSdRbgq.mjs} +2 -2
- package/dist/injected-wallet-provider-BlSdRbgq.mjs.map +1 -0
- package/dist/{injected-wallet-provider-Dlo96aV9.mjs → injected-wallet-provider-CLLhLBAJ.mjs} +2 -2
- package/dist/injected-wallet-provider-CLLhLBAJ.mjs.map +1 -0
- package/package.json +7 -3
- package/dist/bounded-widget-modal-C0Z7gmxH.mjs +0 -31
- package/dist/bounded-widget-modal-C0Z7gmxH.mjs.map +0 -1
- package/dist/bounded-widget-modal-s0PL86Y-.js +0 -33
- package/dist/bounded-widget-modal-s0PL86Y-.js.map +0 -1
- package/dist/index-CYdYsf8s.js.map +0 -1
- package/dist/index-Cflemdaf.mjs.map +0 -1
- package/dist/index.native-BZOWS_sR.js.map +0 -1
- package/dist/index.native-gVbAsohv.mjs.map +0 -1
- package/dist/injected-wallet-provider-BDt4CN0b.js.map +0 -1
- package/dist/injected-wallet-provider-CG3oKdp_.js.map +0 -1
- package/dist/injected-wallet-provider-DH8yLpyf.mjs.map +0 -1
- package/dist/injected-wallet-provider-Dlo96aV9.mjs.map +0 -1
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
import { getConfig, getActiveSessionManager, WebSessionManager, ReactNativeSessionManager, revokeSession, init as init$1, reconnectWithNewAuth, onSessionCleared, deriveUserIdentityFromIdToken, subscribe, getFreshAuthToken, getSessionGeneration, getIdToken as getIdToken$1, genAuthNonce, genSolanaMessage, createSessionWithSignature } from '@bounded-sh/core';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
|
+
function walletContextFromClaims(claims, baseUrl, expectedAppId, tenantOrigin) {
|
|
5
|
+
const base = baseUrl.replace(/\/$/, '');
|
|
6
|
+
const scope = claims['custom:walletScope'];
|
|
7
|
+
const protocol = claims['custom:walletProtocol'];
|
|
8
|
+
if (scope !== undefined && scope !== 'legacy' && scope !== 'app')
|
|
9
|
+
throw new Error('wallet_scope_invalid');
|
|
10
|
+
if ((scope === 'app' && protocol !== 2) || (scope === 'legacy' && protocol !== 1)
|
|
11
|
+
|| (scope === undefined && protocol !== undefined))
|
|
12
|
+
throw new Error('wallet_protocol_invalid');
|
|
13
|
+
const appId = claims['custom:appId'];
|
|
14
|
+
const userId = claims['custom:userId'];
|
|
15
|
+
const address = claims['custom:walletAddress'];
|
|
16
|
+
if (scope === 'app' && (appId !== expectedAppId || typeof appId !== 'string'
|
|
17
|
+
|| !/^[a-f0-9]{24}$/.test(appId) || typeof userId !== 'string' || !userId
|
|
18
|
+
|| claims.iss !== base))
|
|
19
|
+
throw new Error('wallet_session_context_invalid');
|
|
20
|
+
const resolvedScope = scope === 'app' ? 'app' : 'legacy';
|
|
21
|
+
const route = resolvedScope === 'app' ? `/wallet/turnkey/app/${appId}` : '/wallet/turnkey';
|
|
22
|
+
return {
|
|
23
|
+
scope: resolvedScope, appId: String(appId || expectedAppId), userId: String(userId || ''),
|
|
24
|
+
address: typeof address === 'string' ? address : '', base, origin: new URL(base).origin,
|
|
25
|
+
url: `${base}${route}/signer${resolvedScope === 'app' ? `?origin=${encodeURIComponent(tenantOrigin)}` : ''}`,
|
|
26
|
+
key: JSON.stringify([base, appId || expectedAppId, userId || '', address || '', resolvedScope]),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let loginGeneration = 0;
|
|
30
|
+
function invalidatePendingWalletLogin() { ++loginGeneration; }
|
|
31
|
+
function walletOperationGuard() {
|
|
32
|
+
const generation = loginGeneration;
|
|
33
|
+
return () => {
|
|
34
|
+
if (generation !== loginGeneration)
|
|
35
|
+
throw new Error('wallet_session_changed');
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function walletLoginGuard(signal) {
|
|
39
|
+
const generation = ++loginGeneration;
|
|
40
|
+
return () => {
|
|
41
|
+
if ((signal === null || signal === void 0 ? void 0 : signal.aborted) || generation !== loginGeneration)
|
|
42
|
+
throw new Error('wallet_login_cancelled');
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// This realm owns one active SDK session and one retained signer frame.
|
|
46
|
+
let activeWalletState;
|
|
47
|
+
function setWalletSessionState(key, state) { activeWalletState = { key, state }; }
|
|
48
|
+
function walletSessionState(key) {
|
|
49
|
+
if ((activeWalletState === null || activeWalletState === void 0 ? void 0 : activeWalletState.key) !== key)
|
|
50
|
+
return { status: 'verification-required' };
|
|
51
|
+
const state = activeWalletState.state;
|
|
52
|
+
if (state.status === 'ready' && (!Number.isFinite(state.expiresAt) || state.expiresAt <= Date.now())) {
|
|
53
|
+
return { status: 'verification-required', reason: 'wallet_session_expired' };
|
|
54
|
+
}
|
|
55
|
+
return state;
|
|
56
|
+
}
|
|
57
|
+
|
|
4
58
|
/**
|
|
5
59
|
* Platform abstraction layer for React Native compatibility.
|
|
6
60
|
*
|
|
@@ -944,7 +998,10 @@ function registerAuthRegistrySync(fn) {
|
|
|
944
998
|
* Returns the normalized user published by this commit, or null when omitted.
|
|
945
999
|
*/
|
|
946
1000
|
async function adoptSessionProvider(opts) {
|
|
1001
|
+
var _a;
|
|
947
1002
|
const coreConfig = await getConfig();
|
|
1003
|
+
(_a = opts.beforeCommit) === null || _a === void 0 ? void 0 : _a.call(opts);
|
|
1004
|
+
invalidatePendingWalletLogin();
|
|
948
1005
|
coreConfig.authProvider = opts.provider;
|
|
949
1006
|
coreConfig.authMethod = opts.method;
|
|
950
1007
|
setAuthProviderInstance(opts.provider);
|
|
@@ -4178,145 +4235,562 @@ async function ensureSolanaMobileWalletRegistered(config, network) {
|
|
|
4178
4235
|
|
|
4179
4236
|
var bufferExports = requireBuffer();
|
|
4180
4237
|
|
|
4181
|
-
//
|
|
4182
|
-
//
|
|
4183
|
-
//
|
|
4184
|
-
//
|
|
4185
|
-
// (bounded:turnkey:clearSigningSession).
|
|
4238
|
+
// bounded-widget-modal.ts - the shared in-app WIDGET shell for Bounded's login
|
|
4239
|
+
// and signing surfaces. One Shadow-DOM overlay + panel that matches the Bounded
|
|
4240
|
+
// widget (Seal) look and MOTION: a backdrop fade, a panel rise on desktop, a
|
|
4241
|
+
// bottom-sheet on mobile, a height tween as content swaps, and a clean close.
|
|
4186
4242
|
//
|
|
4187
|
-
//
|
|
4188
|
-
//
|
|
4189
|
-
//
|
|
4190
|
-
//
|
|
4243
|
+
// It hosts EITHER:
|
|
4244
|
+
// - NATIVE content (the login card the SDK renders itself), or
|
|
4245
|
+
// - an IFRAME (the Bounded-owned signer page, which must run on the single
|
|
4246
|
+
// signer origin because the wallet's 24h signing session lives in that
|
|
4247
|
+
// origin's storage, out of every app's reach).
|
|
4248
|
+
//
|
|
4249
|
+
// The modal is a warm singleton: it stays mounted (hidden) between uses so a
|
|
4250
|
+
// second open - or a signature right after login - is instant and, when it is
|
|
4251
|
+
// already open, a clean in-place view swap rather than a reopen.
|
|
4191
4252
|
function hasDOM$5() {
|
|
4192
|
-
return typeof window !==
|
|
4253
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
4193
4254
|
}
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4255
|
+
const OVERLAY_CSS = `
|
|
4256
|
+
:host{all:initial}
|
|
4257
|
+
*{box-sizing:border-box}
|
|
4258
|
+
.bw-ov{position:fixed;inset:0;z-index:2147483647;display:none;
|
|
4259
|
+
font-family:"Schibsted Grotesk","Helvetica Neue",Arial,sans-serif}
|
|
4260
|
+
.bw-ov.mounted{display:block}
|
|
4261
|
+
.bw-back{position:fixed;inset:0;background:rgba(4,4,5,.62);opacity:0;
|
|
4262
|
+
transition:opacity .24s ease;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}
|
|
4263
|
+
.bw-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:20px}
|
|
4264
|
+
.bw-panel{position:relative;width:420px;max-width:calc(100vw - 28px);height:280px;
|
|
4265
|
+
border-radius:14px;overflow:hidden;background:#0e0f11;border:1px solid rgba(232,230,222,.08);
|
|
4266
|
+
box-shadow:0 1px 0 rgba(232,230,222,.03) inset,0 40px 90px -45px rgba(0,0,0,.85);
|
|
4267
|
+
opacity:0;transform:translateY(14px) scale(.98);will-change:transform,opacity,height;
|
|
4268
|
+
transition:opacity .28s cubic-bezier(.2,.8,.2,1),transform .28s cubic-bezier(.2,.8,.2,1),
|
|
4269
|
+
height .3s cubic-bezier(.2,.8,.2,1)}
|
|
4270
|
+
.bw-ov.show .bw-back{opacity:1}
|
|
4271
|
+
.bw-ov.show .bw-panel{opacity:1;transform:none}
|
|
4272
|
+
.bw-ov.closing .bw-back{opacity:0}
|
|
4273
|
+
.bw-ov.closing .bw-panel{opacity:0;transform:translateY(12px) scale(.99)}
|
|
4274
|
+
.bw-content{position:absolute;inset:0;overflow:hidden}
|
|
4275
|
+
.bw-frame{display:block;width:100%;height:100%;border:0;background:transparent;color-scheme:dark}
|
|
4276
|
+
/* editorial corner crop-marks, matching the reference frame */
|
|
4277
|
+
.bw-mk{position:absolute;width:13px;height:13px;pointer-events:none;z-index:2;
|
|
4278
|
+
border-color:rgba(232,230,222,.22);border-style:solid;border-width:0}
|
|
4279
|
+
.bw-mk.tl{top:11px;left:11px;border-top-width:1px;border-left-width:1px}
|
|
4280
|
+
.bw-mk.tr{top:11px;right:11px;border-top-width:1px;border-right-width:1px}
|
|
4281
|
+
.bw-mk.bl{bottom:11px;left:11px;border-bottom-width:1px;border-left-width:1px}
|
|
4282
|
+
.bw-mk.br{bottom:11px;right:11px;border-bottom-width:1px;border-right-width:1px}
|
|
4283
|
+
@media(max-width:560px){
|
|
4284
|
+
.bw-wrap{align-items:flex-end;padding:0}
|
|
4285
|
+
.bw-panel{width:100%;max-width:100%;border-radius:18px 18px 0 0;border-bottom:0}
|
|
4286
|
+
.bw-content{overflow-y:auto}
|
|
4287
|
+
.bw-ov.show .bw-panel{transform:none}
|
|
4288
|
+
.bw-ov:not(.show) .bw-panel,.bw-ov.closing .bw-panel{transform:translateY(18%)}
|
|
4198
4289
|
}
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
const
|
|
4212
|
-
|
|
4213
|
-
|
|
4290
|
+
@media(prefers-reduced-motion:reduce){.bw-back,.bw-panel{transition:opacity .12s linear!important}}
|
|
4291
|
+
`;
|
|
4292
|
+
let modal = null;
|
|
4293
|
+
function getModal() {
|
|
4294
|
+
if (modal)
|
|
4295
|
+
return modal;
|
|
4296
|
+
const root = document.createElement("div");
|
|
4297
|
+
root.setAttribute("data-bounded-widget", "");
|
|
4298
|
+
const shadow = root.attachShadow({ mode: "open" });
|
|
4299
|
+
const style = document.createElement("style");
|
|
4300
|
+
style.textContent = OVERLAY_CSS;
|
|
4301
|
+
shadow.appendChild(style);
|
|
4302
|
+
const overlay = document.createElement("div");
|
|
4303
|
+
overlay.className = "bw-ov";
|
|
4304
|
+
const back = document.createElement("div");
|
|
4305
|
+
back.className = "bw-back";
|
|
4306
|
+
const wrap = document.createElement("div");
|
|
4307
|
+
wrap.className = "bw-wrap";
|
|
4308
|
+
const panel = document.createElement("div");
|
|
4309
|
+
panel.className = "bw-panel";
|
|
4310
|
+
const content = document.createElement("div");
|
|
4311
|
+
content.className = "bw-content";
|
|
4312
|
+
panel.appendChild(content);
|
|
4313
|
+
for (const corner of ["tl", "tr", "bl", "br"]) {
|
|
4314
|
+
const mk = document.createElement("span");
|
|
4315
|
+
mk.className = "bw-mk " + corner;
|
|
4316
|
+
panel.appendChild(mk);
|
|
4317
|
+
}
|
|
4318
|
+
wrap.appendChild(panel);
|
|
4319
|
+
overlay.appendChild(back);
|
|
4320
|
+
overlay.appendChild(wrap);
|
|
4321
|
+
shadow.appendChild(overlay);
|
|
4322
|
+
(document.body || document.documentElement).appendChild(root);
|
|
4323
|
+
const m = {
|
|
4324
|
+
root,
|
|
4325
|
+
overlay,
|
|
4326
|
+
panel,
|
|
4327
|
+
content,
|
|
4328
|
+
iframe: null,
|
|
4329
|
+
iframeOrigin: "",
|
|
4330
|
+
iframeReady: false,
|
|
4331
|
+
iframeContext: "",
|
|
4332
|
+
onBackdrop: null,
|
|
4333
|
+
onIframeMessage: null,
|
|
4334
|
+
chain: Promise.resolve(),
|
|
4335
|
+
fitObserver: null,
|
|
4336
|
+
generation: 0,
|
|
4337
|
+
onSupersede: null,
|
|
4338
|
+
};
|
|
4339
|
+
// The dismiss handler lives on `.bw-wrap`: it is the full-viewport flex layer
|
|
4340
|
+
// that sits ABOVE `.bw-back`, so it (not the backdrop element) receives clicks
|
|
4341
|
+
// outside the panel. Fire only when the click lands on the wrap itself, never
|
|
4342
|
+
// when it bubbles up from the panel or its content.
|
|
4343
|
+
wrap.addEventListener("click", (event) => {
|
|
4344
|
+
if (event.target === wrap && m.onBackdrop)
|
|
4345
|
+
m.onBackdrop();
|
|
4346
|
+
});
|
|
4347
|
+
window.addEventListener("message", (event) => {
|
|
4348
|
+
if (!m.iframe)
|
|
4214
4349
|
return;
|
|
4215
|
-
|
|
4216
|
-
if (!d || typeof d !== 'object')
|
|
4350
|
+
if (event.origin !== m.iframeOrigin)
|
|
4217
4351
|
return;
|
|
4218
|
-
if (
|
|
4219
|
-
readyResolve === null || readyResolve === void 0 ? void 0 : readyResolve();
|
|
4352
|
+
if (event.source !== m.iframe.contentWindow)
|
|
4220
4353
|
return;
|
|
4221
|
-
|
|
4222
|
-
if (d
|
|
4223
|
-
const settle = pending.get(d.requestId);
|
|
4224
|
-
if (settle) {
|
|
4225
|
-
pending.delete(d.requestId);
|
|
4226
|
-
settle(d);
|
|
4227
|
-
}
|
|
4228
|
-
}
|
|
4229
|
-
};
|
|
4230
|
-
window.addEventListener('message', onMsg);
|
|
4231
|
-
frame.src = url;
|
|
4232
|
-
document.body.appendChild(frame);
|
|
4233
|
-
const dispose = () => {
|
|
4234
|
-
if (disposed)
|
|
4354
|
+
const d = event.data;
|
|
4355
|
+
if (!d || typeof d !== "object")
|
|
4235
4356
|
return;
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
return
|
|
4245
|
-
dispose,
|
|
4246
|
-
request(message, timeoutMs) {
|
|
4247
|
-
const requestId = String(message.requestId || '');
|
|
4248
|
-
if (!requestId)
|
|
4249
|
-
return Promise.reject(new Error('signer frame request needs a requestId'));
|
|
4250
|
-
if (disposed)
|
|
4251
|
-
return Promise.reject(new Error('signer frame disposed'));
|
|
4252
|
-
return new Promise((resolve, reject) => {
|
|
4253
|
-
let timer;
|
|
4254
|
-
pending.set(requestId, (d) => {
|
|
4255
|
-
if (timer)
|
|
4256
|
-
clearTimeout(timer);
|
|
4257
|
-
resolve(d);
|
|
4258
|
-
});
|
|
4259
|
-
timer = setTimeout(() => {
|
|
4260
|
-
pending.delete(requestId);
|
|
4261
|
-
reject(new Error('The Bounded signer did not respond in time.'));
|
|
4262
|
-
}, timeoutMs);
|
|
4263
|
-
ready.then(() => {
|
|
4264
|
-
var _a;
|
|
4265
|
-
if (disposed || !pending.has(requestId))
|
|
4266
|
-
return;
|
|
4267
|
-
try {
|
|
4268
|
-
(_a = frame.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage(message, origin);
|
|
4269
|
-
}
|
|
4270
|
-
catch ( /* frame gone; timeout rejects */_b) { /* frame gone; timeout rejects */ }
|
|
4271
|
-
});
|
|
4272
|
-
});
|
|
4273
|
-
},
|
|
4274
|
-
};
|
|
4357
|
+
// Readiness belongs to the document, even while login owns the modal and no
|
|
4358
|
+
// signing exchange is listening. The retained frame will not load again.
|
|
4359
|
+
if (d.type === "bounded:turnkey:ready")
|
|
4360
|
+
m.iframeReady = true;
|
|
4361
|
+
if (m.onIframeMessage)
|
|
4362
|
+
m.onIframeMessage(d);
|
|
4363
|
+
});
|
|
4364
|
+
modal = m;
|
|
4365
|
+
return m;
|
|
4275
4366
|
}
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
//
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
//
|
|
4287
|
-
//
|
|
4288
|
-
//
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4367
|
+
function showModal(m) {
|
|
4368
|
+
m.overlay.classList.remove("closing");
|
|
4369
|
+
m.overlay.classList.add("mounted");
|
|
4370
|
+
// Force a reflow so the .show transition runs from the hidden state every open.
|
|
4371
|
+
void m.panel.offsetWidth;
|
|
4372
|
+
m.overlay.classList.add("show");
|
|
4373
|
+
}
|
|
4374
|
+
function hideModal(m, after) {
|
|
4375
|
+
m.overlay.classList.remove("show");
|
|
4376
|
+
m.overlay.classList.add("closing");
|
|
4377
|
+
// Capture the generation at hide time. If a newer open re-shows the modal within
|
|
4378
|
+
// the close tween, this stale timer must NOT strip `.mounted`/`.closing` off the
|
|
4379
|
+
// freshly reopened widget - only tear down when we are still the same open.
|
|
4380
|
+
const gen = m.generation;
|
|
4381
|
+
window.setTimeout(() => {
|
|
4382
|
+
if (m.generation === gen)
|
|
4383
|
+
m.overlay.classList.remove("mounted", "closing");
|
|
4384
|
+
}, 260);
|
|
4385
|
+
}
|
|
4386
|
+
function setPanelHeight(m, px) {
|
|
4387
|
+
const h = Math.max(140, Math.min(680, Math.round(px)));
|
|
4388
|
+
m.panel.style.height = h + "px";
|
|
4389
|
+
}
|
|
4390
|
+
// True outer height of the mounted card: the max of its padding-box scroll height
|
|
4391
|
+
// (catches overflowing descendants) and its border-box height plus own vertical
|
|
4392
|
+
// margins (catches border + margins, which scrollHeight omits). Ceiled so a
|
|
4393
|
+
// sub-pixel row is never clipped under `overflow:hidden`.
|
|
4394
|
+
function measureOuterHeight(target) {
|
|
4395
|
+
var _a;
|
|
4396
|
+
const cs = (_a = target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.getComputedStyle(target);
|
|
4397
|
+
const marginY = cs ? parseFloat(cs.marginTop) + parseFloat(cs.marginBottom) : 0;
|
|
4398
|
+
const borderBox = target.offsetHeight + (Number.isFinite(marginY) ? marginY : 0);
|
|
4399
|
+
const rect = target.getBoundingClientRect().height;
|
|
4400
|
+
return Math.ceil(Math.max(target.scrollHeight, borderBox, rect));
|
|
4401
|
+
}
|
|
4402
|
+
/**
|
|
4403
|
+
* Measure the currently mounted native content and tween the panel to fit it.
|
|
4404
|
+
* Re-fits automatically when the card's intrinsic height changes - notably after
|
|
4405
|
+
* the Newsreader/Schibsted web fonts swap in (which grows line boxes a few px
|
|
4406
|
+
* after first paint) and on inline view swaps (email -> OTP code -> success).
|
|
4407
|
+
*/
|
|
4408
|
+
function fitPanelToContent(m) {
|
|
4409
|
+
var _a, _b;
|
|
4410
|
+
const first = m.content.firstElementChild;
|
|
4411
|
+
const target = first || m.content;
|
|
4412
|
+
const apply = () => {
|
|
4413
|
+
// Only fit while this exact node is still the mounted card.
|
|
4414
|
+
if (m.content.firstElementChild !== first && first)
|
|
4415
|
+
return;
|
|
4416
|
+
const h = measureOuterHeight(target);
|
|
4417
|
+
if (h > 0)
|
|
4418
|
+
setPanelHeight(m, h);
|
|
4419
|
+
};
|
|
4420
|
+
apply();
|
|
4421
|
+
const view = target.ownerDocument.defaultView;
|
|
4422
|
+
// Re-fit after fonts load (one-shot) and once the ResizeObserver-less browsers
|
|
4423
|
+
// have painted, so the height never lags a late reflow.
|
|
4424
|
+
(_a = view === null || view === void 0 ? void 0 : view.requestAnimationFrame) === null || _a === void 0 ? void 0 : _a.call(view, apply);
|
|
4425
|
+
const fonts = target.ownerDocument.fonts;
|
|
4426
|
+
if (fonts === null || fonts === void 0 ? void 0 : fonts.ready)
|
|
4427
|
+
fonts.ready.then(apply).catch(() => { });
|
|
4428
|
+
(_b = m.fitObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
4429
|
+
m.fitObserver = null;
|
|
4430
|
+
if (first && typeof (view === null || view === void 0 ? void 0 : view.ResizeObserver) === "function") {
|
|
4431
|
+
const ro = new view.ResizeObserver(() => apply());
|
|
4432
|
+
ro.observe(first);
|
|
4433
|
+
m.fitObserver = ro;
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
/** Switch the modal into NATIVE-content mode: detach any iframe, mount `node`. */
|
|
4437
|
+
function mountContent(m, node) {
|
|
4438
|
+
var _a;
|
|
4439
|
+
detachIframe(m);
|
|
4440
|
+
(_a = m.fitObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
4441
|
+
m.fitObserver = null;
|
|
4442
|
+
m.content.replaceChildren(node);
|
|
4443
|
+
m.content.style.display = "block";
|
|
4444
|
+
}
|
|
4445
|
+
/** Get (or lazily create) the warm iframe for signer/hosted-page mode. */
|
|
4446
|
+
function ensureIframe(m, url, origin, context = "") {
|
|
4447
|
+
var _a;
|
|
4448
|
+
if (m.iframe && m.iframeOrigin === origin && m.iframe.src === url && m.iframeContext === context)
|
|
4449
|
+
return m.iframe;
|
|
4450
|
+
detachIframe(m);
|
|
4451
|
+
(_a = m.fitObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
4452
|
+
m.fitObserver = null;
|
|
4453
|
+
m.content.style.display = "none";
|
|
4454
|
+
const iframe = document.createElement("iframe");
|
|
4455
|
+
iframe.className = "bw-frame";
|
|
4456
|
+
iframe.setAttribute("title", "Bounded");
|
|
4457
|
+
iframe.setAttribute("allow", "clipboard-write");
|
|
4458
|
+
iframe.src = url;
|
|
4459
|
+
m.iframeOrigin = origin;
|
|
4460
|
+
m.iframeContext = context;
|
|
4461
|
+
m.iframeReady = false;
|
|
4462
|
+
m.panel.appendChild(iframe);
|
|
4463
|
+
m.iframe = iframe;
|
|
4464
|
+
return iframe;
|
|
4465
|
+
}
|
|
4466
|
+
/**
|
|
4467
|
+
* Destroy the warm signer iframe if one is currently mounted for `origin`, and report
|
|
4468
|
+
* whether there was one. Logout needs this: a signing session can live in that document's
|
|
4469
|
+
* MEMORY (the signer keeps an in-memory mirror when the browser blocks its storage), and a
|
|
4470
|
+
* throwaway iframe opened by the sweep can only ever clear the shared store, never another
|
|
4471
|
+
* document's memory. Removing the iframe discards its browsing context, which takes the
|
|
4472
|
+
* mirror with it.
|
|
4473
|
+
*
|
|
4474
|
+
* Deliberately does NOT call getModal(): that would BUILD a modal just to tear it down, on
|
|
4475
|
+
* every logout of every app, most of which never mounted one.
|
|
4476
|
+
*/
|
|
4477
|
+
function detachWarmSignerIframe(origin) {
|
|
4478
|
+
if (!modal || !modal.iframe || (origin))
|
|
4479
|
+
return false;
|
|
4480
|
+
detachIframe(modal);
|
|
4481
|
+
return true;
|
|
4482
|
+
}
|
|
4483
|
+
/** A removed frame cannot still supply its in-memory signing session. */
|
|
4484
|
+
function hasWarmSignerFrame(context) {
|
|
4485
|
+
var _a;
|
|
4486
|
+
return Boolean(((_a = modal === null || modal === void 0 ? void 0 : modal.iframe) === null || _a === void 0 ? void 0 : _a.isConnected) && modal.iframeContext === context);
|
|
4487
|
+
}
|
|
4488
|
+
function detachIframe(m) {
|
|
4489
|
+
if (m.iframe) {
|
|
4490
|
+
try {
|
|
4491
|
+
m.iframe.remove();
|
|
4492
|
+
}
|
|
4493
|
+
catch (_a) {
|
|
4494
|
+
/* ignore */
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
m.iframe = null;
|
|
4498
|
+
m.iframeReady = false;
|
|
4499
|
+
m.iframeOrigin = "";
|
|
4500
|
+
m.iframeContext = "";
|
|
4501
|
+
m.onIframeMessage = null;
|
|
4502
|
+
}
|
|
4503
|
+
// Load the Bounded editorial typefaces (Newsreader display + Schibsted Grotesk body +
|
|
4504
|
+
// JetBrains Mono) once at the document level so they apply inside the shadow root. A
|
|
4505
|
+
// no-op if already present or off-DOM. Fonts loaded here match the hosted auth page.
|
|
4506
|
+
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";
|
|
4507
|
+
function ensureFonts() {
|
|
4508
|
+
if (!hasDOM$5())
|
|
4509
|
+
return;
|
|
4510
|
+
if (document.querySelector('link[data-bounded-fonts="1"]'))
|
|
4511
|
+
return;
|
|
4512
|
+
const pre1 = document.createElement("link");
|
|
4513
|
+
pre1.rel = "preconnect";
|
|
4514
|
+
pre1.href = "https://fonts.googleapis.com";
|
|
4515
|
+
const pre2 = document.createElement("link");
|
|
4516
|
+
pre2.rel = "preconnect";
|
|
4517
|
+
pre2.href = "https://fonts.gstatic.com";
|
|
4518
|
+
pre2.crossOrigin = "anonymous";
|
|
4519
|
+
const link = document.createElement("link");
|
|
4520
|
+
link.rel = "stylesheet";
|
|
4521
|
+
link.href = FONTS_HREF;
|
|
4522
|
+
link.setAttribute("data-bounded-fonts", "1");
|
|
4523
|
+
document.head.append(pre1, pre2, link);
|
|
4524
|
+
}
|
|
4525
|
+
/** Tag the already mounted login document without moving or navigating it. */
|
|
4526
|
+
function bindWarmSignerFrame(frame, context) {
|
|
4527
|
+
if ((modal === null || modal === void 0 ? void 0 : modal.iframe) === frame)
|
|
4528
|
+
modal.iframeContext = context;
|
|
4529
|
+
}
|
|
4530
|
+
|
|
4531
|
+
// turnkey-wallet-widget.ts - runs one Turnkey signer exchange inside the shared
|
|
4532
|
+
// in-app WIDGET (bounded-widget-modal.ts). The Bounded-owned signer page is loaded
|
|
4533
|
+
// in a warm iframe behind the modal so signing feels native: an in-app approve
|
|
4534
|
+
// card (with inline code entry when the 24h signing session must first be
|
|
4535
|
+
// established), never a jarring popup.
|
|
4536
|
+
//
|
|
4537
|
+
// WHY AN IFRAME: the wallet's signing session lives in the single Bounded signer
|
|
4538
|
+
// origin's storage, out of every app's reach, so signing MUST run on that origin;
|
|
4539
|
+
// the iframe brings it in-app.
|
|
4540
|
+
//
|
|
4541
|
+
// Restricted embedders: if the frame never loads, the SAME request reruns through
|
|
4542
|
+
// `popupFallback` (a real window). Callers pass a popup implementation and never
|
|
4543
|
+
// see the difference.
|
|
4544
|
+
/**
|
|
4545
|
+
* Run one signer exchange through the in-app widget. Resolves when `onResult`
|
|
4546
|
+
* accepts a result; falls back to `popupFallback` if the frame cannot be mounted
|
|
4547
|
+
* or never loads.
|
|
4548
|
+
*/
|
|
4549
|
+
async function runWidget(url, signerOrigin, ex, popupFallback) {
|
|
4550
|
+
if (!hasDOM$5())
|
|
4551
|
+
return popupFallback();
|
|
4552
|
+
const m = getModal();
|
|
4553
|
+
const requestId = String(ex.request.requestId);
|
|
4554
|
+
// Serialize: the approve card is modal, and one warm frame handles one request at a time.
|
|
4555
|
+
const run = m.chain.then(() => new Promise((resolve, reject) => {
|
|
4556
|
+
var _a;
|
|
4557
|
+
let settled = false;
|
|
4558
|
+
let usedFallback = false;
|
|
4559
|
+
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4560
|
+
const iframe = ensureIframe(m, url, signerOrigin, ex.context);
|
|
4561
|
+
const send = () => {
|
|
4562
|
+
var _a;
|
|
4563
|
+
if (!iframe.contentWindow)
|
|
4564
|
+
return;
|
|
4565
|
+
try {
|
|
4566
|
+
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4567
|
+
iframe.contentWindow.postMessage(ex.request, signerOrigin);
|
|
4568
|
+
}
|
|
4569
|
+
catch (_b) {
|
|
4570
|
+
finish(() => reject(new Error('wallet_session_changed')));
|
|
4571
|
+
}
|
|
4572
|
+
};
|
|
4573
|
+
let loadTimer = 0;
|
|
4574
|
+
let ceilingTimer = 0;
|
|
4575
|
+
const finish = (fn) => {
|
|
4576
|
+
if (settled)
|
|
4577
|
+
return;
|
|
4578
|
+
settled = true;
|
|
4579
|
+
m.onIframeMessage = null;
|
|
4580
|
+
m.onBackdrop = null;
|
|
4581
|
+
window.clearTimeout(loadTimer);
|
|
4582
|
+
window.clearTimeout(ceilingTimer);
|
|
4583
|
+
fn();
|
|
4584
|
+
};
|
|
4585
|
+
// The frame is WARM across exchanges, so an abandoned approval must be told to drop its
|
|
4586
|
+
// in-flight ceremony. Otherwise the signer keeps `approving` set for a card nobody is
|
|
4587
|
+
// looking at and refuses every later create/sign as `signer_request_superseded` (the
|
|
4588
|
+
// BSA-2026-012 guard) until the page reloads.
|
|
4589
|
+
const cancelInFrame = () => {
|
|
4590
|
+
if (!iframe.contentWindow)
|
|
4591
|
+
return;
|
|
4592
|
+
try {
|
|
4593
|
+
iframe.contentWindow.postMessage({ type: "bounded:turnkey:cancel", requestId }, signerOrigin);
|
|
4594
|
+
}
|
|
4595
|
+
catch (_a) {
|
|
4596
|
+
/* frame gone */
|
|
4597
|
+
}
|
|
4598
|
+
};
|
|
4599
|
+
const goFallback = () => {
|
|
4600
|
+
if (usedFallback || settled)
|
|
4601
|
+
return;
|
|
4602
|
+
usedFallback = true;
|
|
4603
|
+
hideModal(m);
|
|
4604
|
+
finish(() => popupFallback().then(resolve, reject));
|
|
4605
|
+
};
|
|
4606
|
+
m.onBackdrop = () => {
|
|
4607
|
+
cancelInFrame();
|
|
4608
|
+
hideModal(m);
|
|
4609
|
+
finish(() => reject(new Error("cancelled")));
|
|
4610
|
+
};
|
|
4611
|
+
m.onIframeMessage = (d) => {
|
|
4612
|
+
if (d.type === "bounded:turnkey:ready") {
|
|
4613
|
+
send();
|
|
4614
|
+
return;
|
|
4615
|
+
}
|
|
4616
|
+
if (d.type === "bounded:turnkey:resize" && typeof d.height === "number") {
|
|
4617
|
+
setPanelHeight(m, d.height);
|
|
4618
|
+
return;
|
|
4619
|
+
}
|
|
4620
|
+
if (d.type === "bounded:turnkey:result" && d.requestId === requestId) {
|
|
4621
|
+
const outcome = ex.onResult(d);
|
|
4622
|
+
if ("error" in outcome) {
|
|
4623
|
+
hideModal(m);
|
|
4624
|
+
finish(() => reject(new Error(outcome.error)));
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
if (outcome.done) {
|
|
4628
|
+
hideModal(m);
|
|
4629
|
+
finish(() => resolve(outcome.value));
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
};
|
|
4633
|
+
showModal(m);
|
|
4634
|
+
if (m.iframeReady)
|
|
4635
|
+
send();
|
|
4636
|
+
// If the frame never loads, degrade to the popup (best-effort - a post-load
|
|
4637
|
+
// popup may be blocked without a gesture, in which case the fallback rejects).
|
|
4638
|
+
loadTimer = window.setTimeout(() => {
|
|
4639
|
+
if (!m.iframeReady && !settled)
|
|
4640
|
+
goFallback();
|
|
4641
|
+
}, 8000);
|
|
4642
|
+
// Absolute ceiling so a stuck flow cannot hang forever.
|
|
4643
|
+
ceilingTimer = window.setTimeout(() => {
|
|
4644
|
+
if (!settled) {
|
|
4645
|
+
cancelInFrame();
|
|
4646
|
+
hideModal(m);
|
|
4647
|
+
finish(() => reject(new Error("Wallet approval timed out.")));
|
|
4648
|
+
}
|
|
4649
|
+
}, 5 * 60 * 1000);
|
|
4650
|
+
}));
|
|
4651
|
+
m.chain = run.then(() => undefined, () => undefined);
|
|
4652
|
+
return run;
|
|
4653
|
+
}
|
|
4654
|
+
|
|
4655
|
+
// turnkey-signer-frame.ts - a HIDDEN, offscreen iframe of the Bounded signer page
|
|
4656
|
+
// for UI-less signer exchanges: sealing a login code (bounded:turnkey:encryptOtp),
|
|
4657
|
+
// establishing the 24h signing session right after login
|
|
4658
|
+
// (bounded:turnkey:establishSigningSession), and destroying it on logout
|
|
4659
|
+
// (bounded:turnkey:clearSigningSession).
|
|
4660
|
+
//
|
|
4661
|
+
// One handle = one live frame. The frame is kept across requests so multi-step
|
|
4662
|
+
// exchanges (seal a code, then establish the session with the keypair the SEAL
|
|
4663
|
+
// left in that exact page instance) talk to the same page. Origin + source +
|
|
4664
|
+
// requestId are all pinned; dispose() tears everything down.
|
|
4665
|
+
function hasDOM$4() {
|
|
4666
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
4667
|
+
}
|
|
4668
|
+
async function signerPage() {
|
|
4669
|
+
const cfg = await getConfig();
|
|
4670
|
+
const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
4671
|
+
return { url: `${base}/wallet/turnkey/signer`, origin: new URL(base).origin };
|
|
4672
|
+
}
|
|
4673
|
+
/** Mount the hidden signer frame. Callers MUST dispose() when done. */
|
|
4674
|
+
async function openHiddenSignerFrame(page) {
|
|
4675
|
+
if (!hasDOM$4())
|
|
4676
|
+
throw new Error('The Bounded signer frame is only available in the browser.');
|
|
4677
|
+
const { url, origin } = page !== null && page !== void 0 ? page : await signerPage();
|
|
4678
|
+
const frame = document.createElement('iframe');
|
|
4679
|
+
frame.setAttribute('aria-hidden', 'true');
|
|
4680
|
+
frame.style.cssText =
|
|
4681
|
+
'position:fixed;width:1px;height:1px;left:-9999px;top:-9999px;border:0;opacity:0;pointer-events:none';
|
|
4682
|
+
let disposed = false;
|
|
4683
|
+
let readyResolve = null;
|
|
4684
|
+
const ready = new Promise((resolve) => { readyResolve = resolve; });
|
|
4685
|
+
const pending = new Map();
|
|
4686
|
+
const onMsg = (event) => {
|
|
4687
|
+
if (event.origin !== origin || event.source !== frame.contentWindow)
|
|
4688
|
+
return;
|
|
4689
|
+
const d = event.data;
|
|
4690
|
+
if (!d || typeof d !== 'object')
|
|
4691
|
+
return;
|
|
4692
|
+
if (d.type === 'bounded:turnkey:ready') {
|
|
4693
|
+
readyResolve === null || readyResolve === void 0 ? void 0 : readyResolve();
|
|
4694
|
+
return;
|
|
4695
|
+
}
|
|
4696
|
+
if (d.type === 'bounded:turnkey:result' && typeof d.requestId === 'string') {
|
|
4697
|
+
const settle = pending.get(d.requestId);
|
|
4698
|
+
if (settle) {
|
|
4699
|
+
pending.delete(d.requestId);
|
|
4700
|
+
settle(d);
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
};
|
|
4704
|
+
window.addEventListener('message', onMsg);
|
|
4705
|
+
frame.src = url;
|
|
4706
|
+
document.body.appendChild(frame);
|
|
4707
|
+
const dispose = () => {
|
|
4708
|
+
if (disposed)
|
|
4709
|
+
return;
|
|
4710
|
+
disposed = true;
|
|
4711
|
+
window.removeEventListener('message', onMsg);
|
|
4712
|
+
pending.clear();
|
|
4713
|
+
try {
|
|
4714
|
+
frame.remove();
|
|
4715
|
+
}
|
|
4716
|
+
catch ( /* already gone */_a) { /* already gone */ }
|
|
4717
|
+
};
|
|
4718
|
+
return {
|
|
4719
|
+
dispose,
|
|
4720
|
+
request(message, timeoutMs) {
|
|
4721
|
+
const requestId = String(message.requestId || '');
|
|
4722
|
+
if (!requestId)
|
|
4723
|
+
return Promise.reject(new Error('signer frame request needs a requestId'));
|
|
4724
|
+
if (disposed)
|
|
4725
|
+
return Promise.reject(new Error('signer frame disposed'));
|
|
4726
|
+
return new Promise((resolve, reject) => {
|
|
4727
|
+
let timer;
|
|
4728
|
+
pending.set(requestId, (d) => {
|
|
4729
|
+
if (timer)
|
|
4730
|
+
clearTimeout(timer);
|
|
4731
|
+
resolve(d);
|
|
4732
|
+
});
|
|
4733
|
+
timer = setTimeout(() => {
|
|
4734
|
+
pending.delete(requestId);
|
|
4735
|
+
reject(new Error('The Bounded signer did not respond in time.'));
|
|
4736
|
+
}, timeoutMs);
|
|
4737
|
+
ready.then(() => {
|
|
4738
|
+
var _a;
|
|
4739
|
+
if (disposed || !pending.has(requestId))
|
|
4740
|
+
return;
|
|
4741
|
+
try {
|
|
4742
|
+
(_a = frame.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage(message, origin);
|
|
4743
|
+
}
|
|
4744
|
+
catch ( /* frame gone; timeout rejects */_b) { /* frame gone; timeout rejects */ }
|
|
4745
|
+
});
|
|
4746
|
+
});
|
|
4747
|
+
},
|
|
4748
|
+
};
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
// turnkey-signing-capability.ts - the pure AS-02 (DS2-0112) signing-capability logic,
|
|
4752
|
+
// kept import-free (no DOM, no session, no @bounded-sh/core) so it is unit-testable.
|
|
4753
|
+
//
|
|
4754
|
+
// The hosted Turnkey signer refuses to sign unless the request carries an issuer-signed
|
|
4755
|
+
// SIGNING CAPABILITY bound to the app id, the requesting origin, and the EXACT payload
|
|
4756
|
+
// hash. `requestTurnkeySigningCapability` mints one for the given bytes and
|
|
4757
|
+
// `buildTurnkeySignRequest` assembles the signer request that carries it. The bridge
|
|
4758
|
+
// (turnkey-signer-bridge.ts) supplies the base url + id token and routes through both,
|
|
4759
|
+
// so a regression here (a dropped capability, a wrong route, a swallowed error) is
|
|
4760
|
+
// caught without a browser.
|
|
4761
|
+
// POST the Bounded id token + payload hash to the issuer's capability mint route and
|
|
4762
|
+
// return the issuer-signed capability for exactly these bytes. Credential-free: identity
|
|
4763
|
+
// is proven with the id token in the body (not an ambient cookie the cross-origin fetch
|
|
4764
|
+
// would not carry). Throws with the same stable messages the bridge relied on inline.
|
|
4765
|
+
async function requestTurnkeySigningCapability(opts) {
|
|
4766
|
+
const base = opts.baseUrl.replace(/\/$/, '');
|
|
4767
|
+
let res;
|
|
4768
|
+
try {
|
|
4769
|
+
res = await opts.fetchImpl(`${base}/wallet/turnkey/capability`, {
|
|
4770
|
+
method: 'POST',
|
|
4771
|
+
headers: { 'content-type': 'application/json' },
|
|
4772
|
+
body: JSON.stringify({ idToken: opts.idToken, messageHex: opts.messageHex, walletProtocol: 2 }),
|
|
4773
|
+
});
|
|
4774
|
+
}
|
|
4775
|
+
catch (_a) {
|
|
4776
|
+
throw new Error('Could not reach the Bounded signer to authorize this signature.');
|
|
4777
|
+
}
|
|
4778
|
+
if (!res.ok) {
|
|
4779
|
+
let error = 'signing_capability_denied';
|
|
4780
|
+
try {
|
|
4781
|
+
const d = (await res.json());
|
|
4782
|
+
if (d && typeof d.error === 'string')
|
|
4783
|
+
error = d.error;
|
|
4784
|
+
}
|
|
4785
|
+
catch (_b) {
|
|
4786
|
+
/* keep the default marker */
|
|
4787
|
+
}
|
|
4788
|
+
throw new Error(`Turnkey signing was not authorized (${error}).`);
|
|
4789
|
+
}
|
|
4790
|
+
const d = (await res.json());
|
|
4791
|
+
if (!d || typeof d.capability !== 'string' || !d.capability) {
|
|
4792
|
+
throw new Error('Turnkey signing capability was malformed.');
|
|
4793
|
+
}
|
|
4320
4794
|
return d.capability;
|
|
4321
4795
|
}
|
|
4322
4796
|
// Assemble the `bounded:turnkey:sign` postMessage request, carrying the issuer-signed
|
|
@@ -4347,7 +4821,7 @@ async function requestTurnkeyActionAuthorization(opts) {
|
|
|
4347
4821
|
res = await opts.fetchImpl(`${base}/wallet/turnkey/action-authorization`, {
|
|
4348
4822
|
method: 'POST',
|
|
4349
4823
|
headers: { 'content-type': 'application/json' },
|
|
4350
|
-
body: JSON.stringify({ idToken: opts.idToken, action: opts.action }),
|
|
4824
|
+
body: JSON.stringify({ idToken: opts.idToken, action: opts.action, walletProtocol: 2 }),
|
|
4351
4825
|
});
|
|
4352
4826
|
}
|
|
4353
4827
|
catch (_a) {
|
|
@@ -4399,26 +4873,46 @@ function buildTurnkeyActionRequest(opts) {
|
|
|
4399
4873
|
// code is the sole signing authority; within a live session every signature is one
|
|
4400
4874
|
// approval click). The app retains the transaction itself.
|
|
4401
4875
|
//
|
|
4402
|
-
//
|
|
4403
|
-
//
|
|
4404
|
-
|
|
4405
|
-
// across every app.
|
|
4406
|
-
function hasDOM$4() {
|
|
4876
|
+
// Signing keys remain on the trusted signer origin. New apps use independently
|
|
4877
|
+
// bound wallets and storage slots; legacy apps retain their shared wallet.
|
|
4878
|
+
function hasDOM$3() {
|
|
4407
4879
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
4408
4880
|
}
|
|
4409
|
-
//
|
|
4410
|
-
//
|
|
4411
|
-
//
|
|
4412
|
-
//
|
|
4413
|
-
//
|
|
4414
|
-
// never silently fall back to an iframe.
|
|
4881
|
+
// Dispatch one signer exchange through the in-app WIDGET, degrading to the POPUP
|
|
4882
|
+
// when we cannot embed (no DOM, or the frame never mounts/loads). Nothing in the
|
|
4883
|
+
// email-session flow needs a popup by preference: the code entry and the approval
|
|
4884
|
+
// click both run fine inside a cross-origin iframe on every engine (the old
|
|
4885
|
+
// Safari popup preference existed only for in-iframe WebAuthn CREATE).
|
|
4415
4886
|
async function deliver(url, origin, ex) {
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4887
|
+
var _a, _b;
|
|
4888
|
+
const popup = () => runPopup(url, origin, ex);
|
|
4889
|
+
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4890
|
+
const result = await runWidget(url, origin, { request: ex.request, onResult: ex.onResult, context: ex.context, assertCurrent: ex.assertCurrent }, popup);
|
|
4891
|
+
(_b = ex.assertCurrent) === null || _b === void 0 ? void 0 : _b.call(ex);
|
|
4892
|
+
return result;
|
|
4893
|
+
}
|
|
4894
|
+
async function turnkeySignerPage(token) {
|
|
4895
|
+
const assertGeneration = walletOperationGuard();
|
|
4896
|
+
const idToken = token === undefined ? getActiveSessionManager().getIdToken() : token;
|
|
4419
4897
|
const cfg = await getConfig();
|
|
4420
4898
|
const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
4421
|
-
|
|
4899
|
+
const context = walletContextFromClaims(idToken ? decodeJwt$1(idToken) : {}, base, cfg.appId, window.location.origin);
|
|
4900
|
+
return Object.assign(Object.assign({}, context), { idToken: idToken || '', assertCurrent() {
|
|
4901
|
+
assertGeneration();
|
|
4902
|
+
const current = getActiveSessionManager().getIdToken();
|
|
4903
|
+
if (!current)
|
|
4904
|
+
throw new Error('wallet_session_changed');
|
|
4905
|
+
const next = walletContextFromClaims(decodeJwt$1(current), base, cfg.appId, window.location.origin);
|
|
4906
|
+
if (next.key !== context.key)
|
|
4907
|
+
throw new Error('wallet_session_changed');
|
|
4908
|
+
} });
|
|
4909
|
+
}
|
|
4910
|
+
async function getTurnkeyWalletSessionState() {
|
|
4911
|
+
const context = await turnkeySignerPage();
|
|
4912
|
+
context.assertCurrent();
|
|
4913
|
+
if (!hasWarmSignerFrame(context.key))
|
|
4914
|
+
return { status: 'verification-required' };
|
|
4915
|
+
return walletSessionState(context.key);
|
|
4422
4916
|
}
|
|
4423
4917
|
function toHex(bytes) {
|
|
4424
4918
|
let h = '';
|
|
@@ -4474,7 +4968,7 @@ async function runPopup(url, signerOrigin, ex) {
|
|
|
4474
4968
|
const top = window.screenY + Math.max(0, (window.outerHeight - h) / 2);
|
|
4475
4969
|
const popup = window.open(url, 'bounded-turnkey-signer', `width=${w},height=${h},left=${left},top=${top}`);
|
|
4476
4970
|
if (!popup) {
|
|
4477
|
-
throw new Error('
|
|
4971
|
+
throw new Error('Popup blocked - call this from a user gesture (e.g. a click handler) so the wallet approval window can open.');
|
|
4478
4972
|
}
|
|
4479
4973
|
return await new Promise((resolve, reject) => {
|
|
4480
4974
|
let settled = false;
|
|
@@ -4490,11 +4984,12 @@ async function runPopup(url, signerOrigin, ex) {
|
|
|
4490
4984
|
let closeTimer;
|
|
4491
4985
|
const cleanup = () => { settled = true; window.removeEventListener('message', onMsg); if (closeTimer)
|
|
4492
4986
|
clearInterval(closeTimer); };
|
|
4493
|
-
const sendRequest = () => { if (settled || acked)
|
|
4987
|
+
const sendRequest = () => { var _a; if (settled || acked)
|
|
4494
4988
|
return; try {
|
|
4989
|
+
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4495
4990
|
popup.postMessage(ex.request, signerOrigin);
|
|
4496
4991
|
}
|
|
4497
|
-
catch ( /* popup gone */
|
|
4992
|
+
catch ( /* popup gone */_b) { /* popup gone */ } };
|
|
4498
4993
|
const onMsg = (event) => {
|
|
4499
4994
|
if (event.origin !== signerOrigin)
|
|
4500
4995
|
return;
|
|
@@ -4564,16 +5059,9 @@ async function runPopup(url, signerOrigin, ex) {
|
|
|
4564
5059
|
// identity with the Bounded id token WE minted (a body secret, RS256-verified server-side), not
|
|
4565
5060
|
// an ambient cookie - the issuer's session cookie is SameSite-scoped to its own origin and is
|
|
4566
5061
|
// not sent on this cross-origin fetch, so the token is what carries the session's wallet.
|
|
4567
|
-
async function fetchSigningCapability(messageHex) {
|
|
4568
|
-
|
|
4569
|
-
const base
|
|
4570
|
-
let idToken = null;
|
|
4571
|
-
try {
|
|
4572
|
-
idToken = getActiveSessionManager().getIdToken();
|
|
4573
|
-
}
|
|
4574
|
-
catch (_a) {
|
|
4575
|
-
idToken = null;
|
|
4576
|
-
}
|
|
5062
|
+
async function fetchSigningCapability(messageHex, context) {
|
|
5063
|
+
context.assertCurrent();
|
|
5064
|
+
const { base, idToken } = context;
|
|
4577
5065
|
if (!idToken) {
|
|
4578
5066
|
throw new Error('This session has no Turnkey wallet. Sign in again to provision one.');
|
|
4579
5067
|
}
|
|
@@ -4588,16 +5076,9 @@ async function fetchSigningCapability(messageHex) {
|
|
|
4588
5076
|
// requires an issuer-signed authorization bound to this app origin and exact action. Fetch it
|
|
4589
5077
|
// before posting to the signer. Walletless sessions are valid for createWallet because that is
|
|
4590
5078
|
// the operation that provisions their first wallet.
|
|
4591
|
-
async function fetchActionAuthorization(action) {
|
|
4592
|
-
|
|
4593
|
-
const base
|
|
4594
|
-
let idToken = null;
|
|
4595
|
-
try {
|
|
4596
|
-
idToken = getActiveSessionManager().getIdToken();
|
|
4597
|
-
}
|
|
4598
|
-
catch (_a) {
|
|
4599
|
-
idToken = null;
|
|
4600
|
-
}
|
|
5079
|
+
async function fetchActionAuthorization(action, context) {
|
|
5080
|
+
context.assertCurrent();
|
|
5081
|
+
const { base, idToken } = context;
|
|
4601
5082
|
if (!idToken) {
|
|
4602
5083
|
throw new Error('Sign in again to set up your Turnkey wallet.');
|
|
4603
5084
|
}
|
|
@@ -4617,31 +5098,23 @@ async function fetchActionAuthorization(action) {
|
|
|
4617
5098
|
* an unswept session is account-bound and unusable without a live authorization,
|
|
4618
5099
|
* it just costs its owner nothing and a stranger nothing.
|
|
4619
5100
|
*/
|
|
4620
|
-
async function clearTurnkeySigningSession() {
|
|
4621
|
-
if (!hasDOM$
|
|
5101
|
+
async function clearTurnkeySigningSession(idToken) {
|
|
5102
|
+
if (!hasDOM$3())
|
|
4622
5103
|
return;
|
|
4623
|
-
//
|
|
4624
|
-
//
|
|
4625
|
-
|
|
4626
|
-
//
|
|
4627
|
-
|
|
4628
|
-
// between signatures, so the mirror outlives logout. A throwaway frame cannot see
|
|
4629
|
-
// another document's memory - the only way to take it is to destroy that document.
|
|
4630
|
-
// Done first, and synchronously, so it lands even if the frame exchange below fails.
|
|
4631
|
-
try {
|
|
4632
|
-
const { origin } = await turnkeySignerPage();
|
|
4633
|
-
(await Promise.resolve().then(function () { return boundedWidgetModal; })).detachWarmSignerIframe(origin);
|
|
4634
|
-
}
|
|
4635
|
-
catch (_a) {
|
|
4636
|
-
/* no modal module / no warm frame: nothing to take */
|
|
4637
|
-
}
|
|
5104
|
+
// Destroy the trusted in-memory key even if configuration or token parsing fails.
|
|
5105
|
+
// Do it synchronously, before a different login can mount a replacement frame.
|
|
5106
|
+
detachWarmSignerIframe();
|
|
5107
|
+
// Capture the session before asynchronous teardown clears the SDK's token.
|
|
5108
|
+
const contextPromise = turnkeySignerPage(idToken);
|
|
4638
5109
|
let frame = null;
|
|
4639
5110
|
try {
|
|
4640
|
-
|
|
5111
|
+
const context = await contextPromise;
|
|
5112
|
+
setWalletSessionState(context.key, { status: 'verification-required', reason: 'logged_out' });
|
|
5113
|
+
frame = await openHiddenSignerFrame(context);
|
|
4641
5114
|
await frame.request({ type: 'bounded:turnkey:clearSigningSession', requestId: `tkclear_${Date.now()}_${++requestSeq}` }, 4000);
|
|
4642
5115
|
}
|
|
4643
|
-
catch (
|
|
4644
|
-
|
|
5116
|
+
catch (_a) {
|
|
5117
|
+
// The warm frame was destroyed; the bounded store sweep is best effort.
|
|
4645
5118
|
}
|
|
4646
5119
|
finally {
|
|
4647
5120
|
frame === null || frame === void 0 ? void 0 : frame.dispose();
|
|
@@ -4659,12 +5132,14 @@ function hasTurnkeyWallet() {
|
|
|
4659
5132
|
* Use this to learn the fee-payer address BEFORE building a transaction to sign.
|
|
4660
5133
|
*/
|
|
4661
5134
|
async function getOrCreateTurnkeyWallet() {
|
|
4662
|
-
if (!hasDOM$
|
|
4663
|
-
throw new Error('Turnkey embedded wallets are only available in the browser (
|
|
4664
|
-
const
|
|
5135
|
+
if (!hasDOM$3())
|
|
5136
|
+
throw new Error('Turnkey embedded wallets are only available in the browser (trusted wallet UI).');
|
|
5137
|
+
const context = await turnkeySignerPage();
|
|
5138
|
+
const { url, origin } = context;
|
|
4665
5139
|
const requestId = `tkcreate_${Date.now()}_${++requestSeq}`;
|
|
4666
|
-
const authorization = await fetchActionAuthorization('createWallet');
|
|
5140
|
+
const authorization = await fetchActionAuthorization('createWallet', context);
|
|
4667
5141
|
return await deliver(url, origin, {
|
|
5142
|
+
context: context.key, assertCurrent: context.assertCurrent,
|
|
4668
5143
|
request: buildTurnkeyActionRequest({ requestId, action: 'createWallet', authorization }),
|
|
4669
5144
|
onResult: (d) => {
|
|
4670
5145
|
if (d.ok && typeof d.address === 'string')
|
|
@@ -4684,7 +5159,7 @@ async function getOrCreateTurnkeyWallet() {
|
|
|
4684
5159
|
* that tab - it never transits Bounded.
|
|
4685
5160
|
*/
|
|
4686
5161
|
async function openTurnkeyKeyExport() {
|
|
4687
|
-
if (!hasDOM$
|
|
5162
|
+
if (!hasDOM$3())
|
|
4688
5163
|
throw new Error('Turnkey key export is only available in the browser.');
|
|
4689
5164
|
if (!hasTurnkeyWallet())
|
|
4690
5165
|
throw new Error('This session has no Turnkey wallet to export.');
|
|
@@ -4705,10 +5180,11 @@ async function openTurnkeyKeyExport() {
|
|
|
4705
5180
|
}
|
|
4706
5181
|
catch ( /* older engines: the tab still cannot read us cross-origin */_a) { /* older engines: the tab still cannot read us cross-origin */ }
|
|
4707
5182
|
try {
|
|
4708
|
-
const
|
|
4709
|
-
const
|
|
4710
|
-
|
|
4711
|
-
|
|
5183
|
+
const context = await turnkeySignerPage();
|
|
5184
|
+
const authorization = await fetchActionAuthorization('exportWallet', context);
|
|
5185
|
+
context.assertCurrent();
|
|
5186
|
+
const path = context.scope === 'app' ? `/wallet/turnkey/app/${context.appId}` : '/wallet/turnkey';
|
|
5187
|
+
handle.location.href = `${context.base}${path}/export#authorization=${encodeURIComponent(authorization)}`;
|
|
4712
5188
|
}
|
|
4713
5189
|
catch (error) {
|
|
4714
5190
|
// Never strand a blank tab on a failed mint.
|
|
@@ -4720,10 +5196,9 @@ async function openTurnkeyKeyExport() {
|
|
|
4720
5196
|
}
|
|
4721
5197
|
}
|
|
4722
5198
|
/**
|
|
4723
|
-
* Open the Bounded Turnkey
|
|
4724
|
-
* user's
|
|
4725
|
-
*
|
|
4726
|
-
* browser blocks the popup.
|
|
5199
|
+
* Open the Bounded Turnkey approval card and sign `message` (raw bytes) with the
|
|
5200
|
+
* user's Turnkey wallet, resolving with the ed25519 signature and wallet address.
|
|
5201
|
+
* Call from a user gesture so a deliberate browser fallback can open if needed.
|
|
4727
5202
|
*
|
|
4728
5203
|
* The signer shows an approve card (wallet address, requesting origin, Approve /
|
|
4729
5204
|
* Cancel). With a live 24-hour signing session (normally established by the login
|
|
@@ -4732,24 +5207,27 @@ async function openTurnkeyKeyExport() {
|
|
|
4732
5207
|
* completes the signature.
|
|
4733
5208
|
*/
|
|
4734
5209
|
async function signSolanaMessageViaTurnkey(message) {
|
|
4735
|
-
if (!hasDOM$
|
|
4736
|
-
throw new Error('Turnkey embedded-wallet signing is only available in the browser (
|
|
5210
|
+
if (!hasDOM$3()) {
|
|
5211
|
+
throw new Error('Turnkey embedded-wallet signing is only available in the browser (trusted wallet UI).');
|
|
4737
5212
|
}
|
|
4738
|
-
const
|
|
5213
|
+
const context = await turnkeySignerPage();
|
|
5214
|
+
const { url, origin } = context;
|
|
4739
5215
|
const requestId = `tksign_${Date.now()}_${++requestSeq}`;
|
|
4740
5216
|
const messageHex = toHex(message);
|
|
4741
5217
|
// AS-02: obtain the issuer-signed capability for THESE exact bytes first; the signer will
|
|
4742
5218
|
// refuse (capability_required) without it. Fetched per signature (each is single-payload).
|
|
4743
|
-
const capability = await fetchSigningCapability(messageHex);
|
|
5219
|
+
const capability = await fetchSigningCapability(messageHex, context);
|
|
4744
5220
|
// Inline (authMode:"turnkey") sessions plant no Better Auth cookie on the issuer, so the
|
|
4745
5221
|
// signer's same-origin status/create fetches need a bearer session proof: attach the
|
|
4746
5222
|
// createWallet action authorization (it covers the sign flow's get-or-create too). Minted
|
|
4747
5223
|
// from the same id token as the capability, so a session that minted one can mint both.
|
|
4748
|
-
const authorization = await fetchActionAuthorization('createWallet');
|
|
5224
|
+
const authorization = await fetchActionAuthorization('createWallet', context);
|
|
4749
5225
|
return await deliver(url, origin, {
|
|
5226
|
+
context: context.key, assertCurrent: context.assertCurrent,
|
|
4750
5227
|
request: buildTurnkeySignRequest({ requestId, messageHex, capability, authorization }),
|
|
4751
5228
|
onResult: (d) => {
|
|
4752
5229
|
if (d.ok && typeof d.signatureHex === 'string' && typeof d.address === 'string') {
|
|
5230
|
+
setWalletSessionState(context.key, { status: 'ready', expiresAt: Number(d.signingSessionExpiresAt) });
|
|
4753
5231
|
return { done: true, value: { signature: fromHex(d.signatureHex), address: d.address, subOrgId: String(d.subOrgId || '') } };
|
|
4754
5232
|
}
|
|
4755
5233
|
return { error: String(d.error || 'Turnkey signing failed') };
|
|
@@ -4859,11 +5337,21 @@ async function signAndSubmitTransactionWithTurnkey(transaction, feePayer) {
|
|
|
4859
5337
|
}
|
|
4860
5338
|
return signature;
|
|
4861
5339
|
}
|
|
5340
|
+
/** Save this trusted URL to recover the wallet even after the app is deleted. */
|
|
5341
|
+
async function getTurnkeyWalletRecoveryUrl() {
|
|
5342
|
+
const context = await turnkeySignerPage();
|
|
5343
|
+
context.assertCurrent();
|
|
5344
|
+
if (!context.address || !hasTurnkeyWallet())
|
|
5345
|
+
throw new Error('wallet_unavailable');
|
|
5346
|
+
return `${context.base}/wallet/turnkey/recover/${encodeURIComponent(context.address)}`;
|
|
5347
|
+
}
|
|
4862
5348
|
|
|
4863
5349
|
var turnkeySignerBridge = /*#__PURE__*/Object.freeze({
|
|
4864
5350
|
__proto__: null,
|
|
4865
5351
|
clearTurnkeySigningSession: clearTurnkeySigningSession,
|
|
4866
5352
|
getOrCreateTurnkeyWallet: getOrCreateTurnkeyWallet,
|
|
5353
|
+
getTurnkeyWalletRecoveryUrl: getTurnkeyWalletRecoveryUrl,
|
|
5354
|
+
getTurnkeyWalletSessionState: getTurnkeyWalletSessionState,
|
|
4867
5355
|
hasTurnkeyWallet: hasTurnkeyWallet,
|
|
4868
5356
|
openTurnkeyKeyExport: openTurnkeyKeyExport,
|
|
4869
5357
|
signAndSubmitTransactionWithTurnkey: signAndSubmitTransactionWithTurnkey,
|
|
@@ -4890,7 +5378,7 @@ const ISSUER_SESSION_KIND_KEY = 'bounded_issuer_session_kind';
|
|
|
4890
5378
|
// not by `typeof window` (truthy on RN). OIDC redirect/popup is web-only, but the
|
|
4891
5379
|
// session it mints is read back through this same selector everywhere.
|
|
4892
5380
|
const sessionManager = getActiveSessionManager;
|
|
4893
|
-
function hasDOM$
|
|
5381
|
+
function hasDOM$2() {
|
|
4894
5382
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
4895
5383
|
}
|
|
4896
5384
|
function decodeJwt(token) {
|
|
@@ -5041,7 +5529,7 @@ async function buildAuthorize(opts) {
|
|
|
5041
5529
|
throw new Error('appId is not configured (init the SDK with your appId)');
|
|
5042
5530
|
// Web: default to the current page so callers don't have to pass redirectUri. Native has no
|
|
5043
5531
|
// window — it MUST pass an https universal link (the system auth session needs a real URL).
|
|
5044
|
-
const redirectUri = opts.redirectUri || (hasDOM$
|
|
5532
|
+
const redirectUri = opts.redirectUri || (hasDOM$2() ? window.location.origin + window.location.pathname : undefined);
|
|
5045
5533
|
if (!redirectUri)
|
|
5046
5534
|
throw new Error('redirectUri is required on React Native — pass the https universal link the app owner registered.');
|
|
5047
5535
|
const { verifier, challenge } = await makePkce();
|
|
@@ -5149,7 +5637,7 @@ async function runNativeLogin(opts) {
|
|
|
5149
5637
|
async function loginWithRedirect(opts) {
|
|
5150
5638
|
if (isNative())
|
|
5151
5639
|
return runNativeLogin(opts);
|
|
5152
|
-
if (!hasDOM$
|
|
5640
|
+
if (!hasDOM$2())
|
|
5153
5641
|
throw new Error('loginWithRedirect requires a browser or a configured React Native platform');
|
|
5154
5642
|
const { url, pkce } = await buildAuthorize(opts);
|
|
5155
5643
|
// Stash the verifier + state so the redirect_uri page can finish the exchange. sessionStorage
|
|
@@ -5166,7 +5654,7 @@ async function loginWithRedirect(opts) {
|
|
|
5166
5654
|
* loginWithRedirect() (which resolves with the User), so there is no separate redirect
|
|
5167
5655
|
* page to finish from. Calling it on RN is harmless. */
|
|
5168
5656
|
async function completeLoginFromRedirect() {
|
|
5169
|
-
if (!hasDOM$
|
|
5657
|
+
if (!hasDOM$2())
|
|
5170
5658
|
return null;
|
|
5171
5659
|
const url = new URL(window.location.href);
|
|
5172
5660
|
const code = url.searchParams.get('code');
|
|
@@ -5214,7 +5702,7 @@ async function completeLoginFromRedirect() {
|
|
|
5214
5702
|
* redirect_uri page posts the code back. The redirect_uri page just calls the usual
|
|
5215
5703
|
* completeLoginFromRedirect() (which auto-detects the popup) — no separate call needed. */
|
|
5216
5704
|
async function loginWithPopup(opts) {
|
|
5217
|
-
if (!hasDOM$
|
|
5705
|
+
if (!hasDOM$2())
|
|
5218
5706
|
throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
|
|
5219
5707
|
// Open synchronously, before PKCE/config awaits consume the click's user-
|
|
5220
5708
|
// activation window. Opening only after buildAuthorize() intermittently
|
|
@@ -5374,7 +5862,7 @@ async function loginWithPopup(opts) {
|
|
|
5374
5862
|
/** Call this on the redirect_uri page when login was opened via loginWithPopup(): posts the
|
|
5375
5863
|
* code back to the opener and closes. (For the redirect variant use completeLoginFromRedirect.) */
|
|
5376
5864
|
function completeLoginInPopup(openerOrigin) {
|
|
5377
|
-
if (!hasDOM$
|
|
5865
|
+
if (!hasDOM$2() || !window.opener)
|
|
5378
5866
|
return;
|
|
5379
5867
|
const url = new URL(window.location.href);
|
|
5380
5868
|
const code = url.searchParams.get('code');
|
|
@@ -5395,19 +5883,27 @@ function completeLoginInPopup(openerOrigin) {
|
|
|
5395
5883
|
// Turnkey-native login path (authMode:"turnkey"), which mints the session server-side
|
|
5396
5884
|
// after Turnkey verifies the email OTP - there is no OIDC code to exchange. Wiring is
|
|
5397
5885
|
// identical to exchangeCode's tail so the realtime worker cannot tell them apart.
|
|
5398
|
-
async function establishInlineSession(data) {
|
|
5886
|
+
async function establishInlineSession(data, beforeCommit, beforeStore) {
|
|
5399
5887
|
const base = await issuerBase();
|
|
5888
|
+
beforeCommit === null || beforeCommit === void 0 ? void 0 : beforeCommit();
|
|
5400
5889
|
const idToken = tokenField(data, 'id_token', 'idToken');
|
|
5401
5890
|
if (!idToken)
|
|
5402
5891
|
throw new Error('No id_token in the inline session response');
|
|
5403
5892
|
const accessToken = tokenField(data, 'access_token', 'accessToken') || idToken;
|
|
5404
5893
|
const refreshToken = boundedRefreshTokenField(data);
|
|
5405
5894
|
const user = userFromIdToken(idToken);
|
|
5406
|
-
await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base);
|
|
5895
|
+
const rollback = await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base, () => { beforeCommit === null || beforeCommit === void 0 ? void 0 : beforeCommit(); beforeStore === null || beforeStore === void 0 ? void 0 : beforeStore(); });
|
|
5407
5896
|
// Adopt the OIDC provider on EVERY session surface - including core's signing
|
|
5408
5897
|
// pointer (getConfig().authProvider), which this path used to skip, leaving
|
|
5409
5898
|
// onchain writes routed through a previous (e.g. logged-out wallet) provider.
|
|
5410
|
-
|
|
5899
|
+
try {
|
|
5900
|
+
await adoptSessionProvider({ provider: oidcProviderSingleton, method: 'email', configKey: 'direct:oidc', user, beforeCommit });
|
|
5901
|
+
}
|
|
5902
|
+
catch (error) {
|
|
5903
|
+
if (typeof rollback === 'function')
|
|
5904
|
+
await rollback();
|
|
5905
|
+
throw error;
|
|
5906
|
+
}
|
|
5411
5907
|
// Turnkey-native OTP mints an app session directly. It does not establish a
|
|
5412
5908
|
// Better Auth browser cookie on the issuer, so logout must not navigate
|
|
5413
5909
|
// through the issuer's cookie-clearing /logout endpoint. Stamped AFTER the
|
|
@@ -5471,9 +5967,6 @@ async function exchangeCode(code, verifier, redirectUri) {
|
|
|
5471
5967
|
return user;
|
|
5472
5968
|
}
|
|
5473
5969
|
|
|
5474
|
-
// NOTE: Provider class re-exports have been moved to index.ts (web) and
|
|
5475
|
-
// index.native.ts (RN) to avoid eager resolution of web-only dependencies
|
|
5476
|
-
// (react-dom) when this module is imported on RN.
|
|
5477
5970
|
let currentAuthProvider = null;
|
|
5478
5971
|
let currentAuthMethod = null;
|
|
5479
5972
|
let currentAuthProviderConfigKey = null;
|
|
@@ -5828,7 +6321,7 @@ async function getAuthProvider(config) {
|
|
|
5828
6321
|
// manager as legacy email sessions. Human login should be started
|
|
5829
6322
|
// with loginWithRedirect(); this provider is kept as the restore and
|
|
5830
6323
|
// logout surface for those sessions.
|
|
5831
|
-
currentAuthProvider = new (await import('./email-auth-provider-
|
|
6324
|
+
currentAuthProvider = new (await import('./email-auth-provider-BGTfU1_i.mjs')).EmailAuthProvider();
|
|
5832
6325
|
break;
|
|
5833
6326
|
case "phantom": {
|
|
5834
6327
|
// First-class Solana WALLET LOGIN (the poof/TaroBase-style "connect
|
|
@@ -5870,7 +6363,7 @@ async function getAuthProvider(config) {
|
|
|
5870
6363
|
// (bounded-login-widget.ts), so registration never counts as
|
|
5871
6364
|
// evidence the user has a wallet.
|
|
5872
6365
|
await ensureSolanaMobileWalletRegistered(config, configuredNetwork);
|
|
5873
|
-
const { InjectedWalletProvider } = await import('./injected-wallet-provider-
|
|
6366
|
+
const { InjectedWalletProvider } = await import('./injected-wallet-provider-CLLhLBAJ.mjs');
|
|
5874
6367
|
currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
|
|
5875
6368
|
break;
|
|
5876
6369
|
}
|
|
@@ -5883,7 +6376,7 @@ async function getAuthProvider(config) {
|
|
|
5883
6376
|
// Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
|
|
5884
6377
|
// to point at a specific wallet or set chainId/domain.
|
|
5885
6378
|
const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
|
|
5886
|
-
const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-
|
|
6379
|
+
const { InjectedEvmWalletProvider } = await import('./injected-evm-wallet-provider-BPZMEE8I.mjs');
|
|
5887
6380
|
currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
|
|
5888
6381
|
break;
|
|
5889
6382
|
}
|
|
@@ -5908,7 +6401,7 @@ async function getAuthProvider(config) {
|
|
|
5908
6401
|
break;
|
|
5909
6402
|
case "guest":
|
|
5910
6403
|
// Anonymous auth: a device-local ed25519 keypair signs the challenge.
|
|
5911
|
-
currentAuthProvider = new (await import('./guest-auth-provider-
|
|
6404
|
+
currentAuthProvider = new (await import('./guest-auth-provider-DVxlea-z.mjs')).GuestAuthProvider();
|
|
5912
6405
|
break;
|
|
5913
6406
|
}
|
|
5914
6407
|
if (!currentAuthProvider) {
|
|
@@ -5970,7 +6463,7 @@ async function signInAnonymously() {
|
|
|
5970
6463
|
// Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
|
|
5971
6464
|
// the main bundle for data-only apps. Specifier is a static string literal
|
|
5972
6465
|
// so bundle-splitting can see it.
|
|
5973
|
-
const provider = new (await import('./guest-auth-provider-
|
|
6466
|
+
const provider = new (await import('./guest-auth-provider-DVxlea-z.mjs')).GuestAuthProvider();
|
|
5974
6467
|
setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
|
|
5975
6468
|
// adoptSessionProvider awaits only getConfig() (already resolved here) and then
|
|
5976
6469
|
// runs every write - provider/method/config/marker/user - in one synchronous
|
|
@@ -6141,7 +6634,7 @@ function ensureSharedWalletLoginReady() {
|
|
|
6141
6634
|
if (!sharedWalletReady) {
|
|
6142
6635
|
sharedWalletReady = (async () => {
|
|
6143
6636
|
await getConfig();
|
|
6144
|
-
await import('./injected-wallet-provider-
|
|
6637
|
+
await import('./injected-wallet-provider-CLLhLBAJ.mjs');
|
|
6145
6638
|
})().catch((error) => {
|
|
6146
6639
|
sharedWalletReady = null;
|
|
6147
6640
|
throw error;
|
|
@@ -6244,7 +6737,7 @@ async function loginWithWallet(options) {
|
|
|
6244
6737
|
await ensureSharedWalletLoginReady();
|
|
6245
6738
|
else
|
|
6246
6739
|
await ensureWalletLoginReady();
|
|
6247
|
-
const { InjectedWalletProvider } = await import('./injected-wallet-provider-
|
|
6740
|
+
const { InjectedWalletProvider } = await import('./injected-wallet-provider-CLLhLBAJ.mjs');
|
|
6248
6741
|
// Keep the RAW wallet provider: the offchain wrapper forwards only a fixed
|
|
6249
6742
|
// set of methods, so cleanup below must speak to the provider that actually
|
|
6250
6743
|
// holds the config rather than to whatever is wrapping it.
|
|
@@ -6406,13 +6899,14 @@ async function exchangeLogoutHint(issuerBase, idToken) {
|
|
|
6406
6899
|
// which can be blocked or offline), so its failures are the one thing logout may
|
|
6407
6900
|
// tolerate - never revokeSession's. Lazy import per this module's rule (the signer
|
|
6408
6901
|
// bridge is browser-only machinery). Never rejects.
|
|
6409
|
-
async function sweepTurnkeySigningSession() {
|
|
6902
|
+
async function sweepTurnkeySigningSession(idToken) {
|
|
6410
6903
|
try {
|
|
6411
|
-
await (await Promise.resolve().then(function () { return turnkeySignerBridge; })).clearTurnkeySigningSession();
|
|
6904
|
+
await (await Promise.resolve().then(function () { return turnkeySignerBridge; })).clearTurnkeySigningSession(idToken);
|
|
6412
6905
|
}
|
|
6413
6906
|
catch ( /* best-effort: never block or fail logout on the sweep */_a) { /* best-effort: never block or fail logout on the sweep */ }
|
|
6414
6907
|
}
|
|
6415
6908
|
async function logout$1(options) {
|
|
6909
|
+
invalidatePendingWalletLogin();
|
|
6416
6910
|
// Re-initialize the provider if it was cleared by a prior logout — the exact mirror
|
|
6417
6911
|
// of login()'s self-heal above. Every session-establishment path now adopts through
|
|
6418
6912
|
// session-provider.ts, so this is a REPAIR (the registry was cleared by a previous
|
|
@@ -6444,7 +6938,7 @@ async function logout$1(options) {
|
|
|
6444
6938
|
// sweep is a hidden signer-iframe exchange; start it now (after revocation is
|
|
6445
6939
|
// committed - a failed logout leaves the still-logged-in user their session) and
|
|
6446
6940
|
// let it overlap the provider teardown. sweepTurnkeySigningSession never rejects.
|
|
6447
|
-
const signingSessionSweep = sweepTurnkeySigningSession();
|
|
6941
|
+
const signingSessionSweep = sweepTurnkeySigningSession(idTokenHint);
|
|
6448
6942
|
// Only OIDC-hosted sessions own an issuer browser cookie. Turnkey-native
|
|
6449
6943
|
// email OTP is also an email auth method, but it mints the app session
|
|
6450
6944
|
// inline and has no issuer cookie to clear. Use the explicit session-kind
|
|
@@ -6658,386 +7152,116 @@ function init(newConfig) {
|
|
|
6658
7152
|
async function waitForAuthReady() {
|
|
6659
7153
|
const ready = authReadyPromise;
|
|
6660
7154
|
if (!ready) {
|
|
6661
|
-
throw new Error('SDK not initialized. Please call init() first.');
|
|
6662
|
-
}
|
|
6663
|
-
await ready;
|
|
6664
|
-
return currentUser;
|
|
6665
|
-
}
|
|
6666
|
-
function onAuthStateChanged(callback) {
|
|
6667
|
-
authStateListeners.push(callback);
|
|
6668
|
-
if (initCompleted || hasRunFirstTime) {
|
|
6669
|
-
// Call the callback immediately with the current user.
|
|
6670
|
-
// We check hasRunFirstTime in addition to initCompleted to handle the race
|
|
6671
|
-
// where the Phantom auto-login effect calls setCurrentUser() before init()
|
|
6672
|
-
// finishes. Without this, listeners registered between auto-login's
|
|
6673
|
-
// setCurrentUser and init()'s initCompleted would never fire (init's
|
|
6674
|
-
// setCurrentUser is a no-op for same-address and initCompleted is still false).
|
|
6675
|
-
callback(currentUser);
|
|
6676
|
-
}
|
|
6677
|
-
return () => {
|
|
6678
|
-
authStateListeners = authStateListeners.filter((listener) => listener !== callback);
|
|
6679
|
-
};
|
|
6680
|
-
}
|
|
6681
|
-
function onAuthLoadingChanged(callback) {
|
|
6682
|
-
authLoadingListeners.push(callback);
|
|
6683
|
-
// Call immediately with current loading state
|
|
6684
|
-
callback(isAuthLoading);
|
|
6685
|
-
return () => {
|
|
6686
|
-
authLoadingListeners = authLoadingListeners.filter((listener) => listener !== callback);
|
|
6687
|
-
};
|
|
6688
|
-
}
|
|
6689
|
-
function setAuthLoading(loading) {
|
|
6690
|
-
if (isAuthLoading !== loading) {
|
|
6691
|
-
isAuthLoading = loading;
|
|
6692
|
-
authLoadingListeners.forEach((callback) => callback(loading));
|
|
6693
|
-
}
|
|
6694
|
-
}
|
|
6695
|
-
function getAuthLoading() {
|
|
6696
|
-
return isAuthLoading;
|
|
6697
|
-
}
|
|
6698
|
-
async function login(options) {
|
|
6699
|
-
if (!authProviderInstance) {
|
|
6700
|
-
throw new Error('SDK not initialized. Please call init() first.');
|
|
6701
|
-
}
|
|
6702
|
-
const loggedInUser = await login$1(options);
|
|
6703
|
-
setCurrentUser(loggedInUser);
|
|
6704
|
-
return currentUser;
|
|
6705
|
-
}
|
|
6706
|
-
async function logout(options) {
|
|
6707
|
-
if (!authProviderInstance) {
|
|
6708
|
-
throw new Error('SDK not initialized. Please call init() first.');
|
|
6709
|
-
}
|
|
6710
|
-
await logout$1(options);
|
|
6711
|
-
setCurrentUser(null);
|
|
6712
|
-
}
|
|
6713
|
-
var hasRunFirstTime = false;
|
|
6714
|
-
function userPrincipalKey(user) {
|
|
6715
|
-
var _a, _b, _c;
|
|
6716
|
-
if (!user)
|
|
6717
|
-
return null;
|
|
6718
|
-
return (_c = (_b = (_a = user.id) !== null && _a !== void 0 ? _a : user.address) !== null && _b !== void 0 ? _b : user.email) !== null && _c !== void 0 ? _c : null;
|
|
6719
|
-
}
|
|
6720
|
-
// Update the currentUser and notify listeners
|
|
6721
|
-
function setCurrentUser(user) {
|
|
6722
|
-
// Enrich with the universal identity fields (id/email) derived from the
|
|
6723
|
-
// active session's idToken — central chokepoint so every provider's user
|
|
6724
|
-
// object gains { id, address, email } without each construction site
|
|
6725
|
-
// having to set them.
|
|
6726
|
-
user = withUserIdentity(user);
|
|
6727
|
-
const lastUser = currentUser;
|
|
6728
|
-
const wasInitializedBefore = hasRunFirstTime;
|
|
6729
|
-
const lastPrincipal = userPrincipalKey(currentUser);
|
|
6730
|
-
const nextPrincipal = userPrincipalKey(user);
|
|
6731
|
-
const shouldRunListeners = !hasRunFirstTime || lastPrincipal !== nextPrincipal;
|
|
6732
|
-
hasRunFirstTime = true;
|
|
6733
|
-
currentUser = user;
|
|
6734
|
-
if (shouldRunListeners) {
|
|
6735
|
-
// Isolate each listener: a consumer callback that throws (during logout in
|
|
6736
|
-
// particular) must not abort the notification loop OR skip the socket reopen
|
|
6737
|
-
// below. Before this, a single throwing listener propagated out of
|
|
6738
|
-
// setCurrentUser, so the realtime socket kept streaming as the signed-out
|
|
6739
|
-
// user and the other listeners were never told the principal changed.
|
|
6740
|
-
authStateListeners.forEach((callback) => {
|
|
6741
|
-
try {
|
|
6742
|
-
callback(user);
|
|
6743
|
-
}
|
|
6744
|
-
catch (error) {
|
|
6745
|
-
console.error('Error in onAuthStateChanged listener:', error);
|
|
6746
|
-
}
|
|
6747
|
-
});
|
|
6748
|
-
// Reconnect WebSocket connections with new auth when user changes.
|
|
6749
|
-
// Skip on the first setCurrentUser call during init() — there are no
|
|
6750
|
-
// existing connections to reconnect at that point.
|
|
6751
|
-
if (wasInitializedBefore && userPrincipalKey(lastUser) !== nextPrincipal) {
|
|
6752
|
-
reconnectWithNewAuth().catch((error) => {
|
|
6753
|
-
console.error('Error reconnecting WebSocket after auth change:', error);
|
|
6754
|
-
});
|
|
6755
|
-
}
|
|
6756
|
-
}
|
|
6757
|
-
// Return this commit's normalized snapshot. A listener can start another
|
|
6758
|
-
// login, so the mutable currentUser may already belong to a newer session.
|
|
6759
|
-
return user;
|
|
6760
|
-
}
|
|
6761
|
-
function getCurrentUser() {
|
|
6762
|
-
return currentUser;
|
|
6763
|
-
}
|
|
6764
|
-
/** @internal Snapshot/restore surface for transactional auth-provider switches. */
|
|
6765
|
-
function getAuthProviderInstance() {
|
|
6766
|
-
return authProviderInstance;
|
|
6767
|
-
}
|
|
6768
|
-
function setAuthProviderInstance(provider) {
|
|
6769
|
-
authProviderInstance = provider;
|
|
6770
|
-
}
|
|
6771
|
-
|
|
6772
|
-
// bounded-widget-modal.ts - the shared in-app WIDGET shell for Bounded's login
|
|
6773
|
-
// and signing surfaces. One Shadow-DOM overlay + panel that matches the Bounded
|
|
6774
|
-
// widget (Seal) look and MOTION: a backdrop fade, a panel rise on desktop, a
|
|
6775
|
-
// bottom-sheet on mobile, a height tween as content swaps, and a clean close.
|
|
6776
|
-
//
|
|
6777
|
-
// It hosts EITHER:
|
|
6778
|
-
// - NATIVE content (the login card the SDK renders itself), or
|
|
6779
|
-
// - an IFRAME (the Bounded-owned signer page, which must run on the single
|
|
6780
|
-
// signer origin because the wallet's 24h signing session lives in that
|
|
6781
|
-
// origin's storage, out of every app's reach).
|
|
6782
|
-
//
|
|
6783
|
-
// The modal is a warm singleton: it stays mounted (hidden) between uses so a
|
|
6784
|
-
// second open - or a signature right after login - is instant and, when it is
|
|
6785
|
-
// already open, a clean in-place view swap rather than a reopen.
|
|
6786
|
-
function hasDOM$2() {
|
|
6787
|
-
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
6788
|
-
}
|
|
6789
|
-
const OVERLAY_CSS = `
|
|
6790
|
-
:host{all:initial}
|
|
6791
|
-
*{box-sizing:border-box}
|
|
6792
|
-
.bw-ov{position:fixed;inset:0;z-index:2147483647;display:none;
|
|
6793
|
-
font-family:"Schibsted Grotesk","Helvetica Neue",Arial,sans-serif}
|
|
6794
|
-
.bw-ov.mounted{display:block}
|
|
6795
|
-
.bw-back{position:fixed;inset:0;background:rgba(4,4,5,.62);opacity:0;
|
|
6796
|
-
transition:opacity .24s ease;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}
|
|
6797
|
-
.bw-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:20px}
|
|
6798
|
-
.bw-panel{position:relative;width:420px;max-width:calc(100vw - 28px);height:280px;
|
|
6799
|
-
border-radius:14px;overflow:hidden;background:#0e0f11;border:1px solid rgba(232,230,222,.08);
|
|
6800
|
-
box-shadow:0 1px 0 rgba(232,230,222,.03) inset,0 40px 90px -45px rgba(0,0,0,.85);
|
|
6801
|
-
opacity:0;transform:translateY(14px) scale(.98);will-change:transform,opacity,height;
|
|
6802
|
-
transition:opacity .28s cubic-bezier(.2,.8,.2,1),transform .28s cubic-bezier(.2,.8,.2,1),
|
|
6803
|
-
height .3s cubic-bezier(.2,.8,.2,1)}
|
|
6804
|
-
.bw-ov.show .bw-back{opacity:1}
|
|
6805
|
-
.bw-ov.show .bw-panel{opacity:1;transform:none}
|
|
6806
|
-
.bw-ov.closing .bw-back{opacity:0}
|
|
6807
|
-
.bw-ov.closing .bw-panel{opacity:0;transform:translateY(12px) scale(.99)}
|
|
6808
|
-
.bw-content{position:absolute;inset:0;overflow:hidden}
|
|
6809
|
-
.bw-frame{display:block;width:100%;height:100%;border:0;background:transparent;color-scheme:dark}
|
|
6810
|
-
/* editorial corner crop-marks, matching the reference frame */
|
|
6811
|
-
.bw-mk{position:absolute;width:13px;height:13px;pointer-events:none;z-index:2;
|
|
6812
|
-
border-color:rgba(232,230,222,.22);border-style:solid;border-width:0}
|
|
6813
|
-
.bw-mk.tl{top:11px;left:11px;border-top-width:1px;border-left-width:1px}
|
|
6814
|
-
.bw-mk.tr{top:11px;right:11px;border-top-width:1px;border-right-width:1px}
|
|
6815
|
-
.bw-mk.bl{bottom:11px;left:11px;border-bottom-width:1px;border-left-width:1px}
|
|
6816
|
-
.bw-mk.br{bottom:11px;right:11px;border-bottom-width:1px;border-right-width:1px}
|
|
6817
|
-
@media(max-width:560px){
|
|
6818
|
-
.bw-wrap{align-items:flex-end;padding:0}
|
|
6819
|
-
.bw-panel{width:100%;max-width:100%;border-radius:18px 18px 0 0;border-bottom:0}
|
|
6820
|
-
.bw-content{overflow-y:auto}
|
|
6821
|
-
.bw-ov.show .bw-panel{transform:none}
|
|
6822
|
-
.bw-ov:not(.show) .bw-panel,.bw-ov.closing .bw-panel{transform:translateY(18%)}
|
|
6823
|
-
}
|
|
6824
|
-
@media(prefers-reduced-motion:reduce){.bw-back,.bw-panel{transition:opacity .12s linear!important}}
|
|
6825
|
-
`;
|
|
6826
|
-
let modal = null;
|
|
6827
|
-
function getModal() {
|
|
6828
|
-
if (modal)
|
|
6829
|
-
return modal;
|
|
6830
|
-
const root = document.createElement("div");
|
|
6831
|
-
root.setAttribute("data-bounded-widget", "");
|
|
6832
|
-
const shadow = root.attachShadow({ mode: "open" });
|
|
6833
|
-
const style = document.createElement("style");
|
|
6834
|
-
style.textContent = OVERLAY_CSS;
|
|
6835
|
-
shadow.appendChild(style);
|
|
6836
|
-
const overlay = document.createElement("div");
|
|
6837
|
-
overlay.className = "bw-ov";
|
|
6838
|
-
const back = document.createElement("div");
|
|
6839
|
-
back.className = "bw-back";
|
|
6840
|
-
const wrap = document.createElement("div");
|
|
6841
|
-
wrap.className = "bw-wrap";
|
|
6842
|
-
const panel = document.createElement("div");
|
|
6843
|
-
panel.className = "bw-panel";
|
|
6844
|
-
const content = document.createElement("div");
|
|
6845
|
-
content.className = "bw-content";
|
|
6846
|
-
panel.appendChild(content);
|
|
6847
|
-
for (const corner of ["tl", "tr", "bl", "br"]) {
|
|
6848
|
-
const mk = document.createElement("span");
|
|
6849
|
-
mk.className = "bw-mk " + corner;
|
|
6850
|
-
panel.appendChild(mk);
|
|
6851
|
-
}
|
|
6852
|
-
wrap.appendChild(panel);
|
|
6853
|
-
overlay.appendChild(back);
|
|
6854
|
-
overlay.appendChild(wrap);
|
|
6855
|
-
shadow.appendChild(overlay);
|
|
6856
|
-
(document.body || document.documentElement).appendChild(root);
|
|
6857
|
-
const m = {
|
|
6858
|
-
root,
|
|
6859
|
-
overlay,
|
|
6860
|
-
panel,
|
|
6861
|
-
content,
|
|
6862
|
-
iframe: null,
|
|
6863
|
-
iframeOrigin: "",
|
|
6864
|
-
iframeReady: false,
|
|
6865
|
-
onBackdrop: null,
|
|
6866
|
-
onIframeMessage: null,
|
|
6867
|
-
chain: Promise.resolve(),
|
|
6868
|
-
fitObserver: null,
|
|
6869
|
-
generation: 0,
|
|
6870
|
-
onSupersede: null,
|
|
6871
|
-
};
|
|
6872
|
-
// The dismiss handler lives on `.bw-wrap`: it is the full-viewport flex layer
|
|
6873
|
-
// that sits ABOVE `.bw-back`, so it (not the backdrop element) receives clicks
|
|
6874
|
-
// outside the panel. Fire only when the click lands on the wrap itself, never
|
|
6875
|
-
// when it bubbles up from the panel or its content.
|
|
6876
|
-
wrap.addEventListener("click", (event) => {
|
|
6877
|
-
if (event.target === wrap && m.onBackdrop)
|
|
6878
|
-
m.onBackdrop();
|
|
6879
|
-
});
|
|
6880
|
-
window.addEventListener("message", (event) => {
|
|
6881
|
-
if (!m.iframe)
|
|
6882
|
-
return;
|
|
6883
|
-
if (event.origin !== m.iframeOrigin)
|
|
6884
|
-
return;
|
|
6885
|
-
if (event.source !== m.iframe.contentWindow)
|
|
6886
|
-
return;
|
|
6887
|
-
const d = event.data;
|
|
6888
|
-
if (!d || typeof d !== "object")
|
|
6889
|
-
return;
|
|
6890
|
-
if (m.onIframeMessage)
|
|
6891
|
-
m.onIframeMessage(d);
|
|
6892
|
-
});
|
|
6893
|
-
modal = m;
|
|
6894
|
-
return m;
|
|
7155
|
+
throw new Error('SDK not initialized. Please call init() first.');
|
|
7156
|
+
}
|
|
7157
|
+
await ready;
|
|
7158
|
+
return currentUser;
|
|
6895
7159
|
}
|
|
6896
|
-
function
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
7160
|
+
function onAuthStateChanged(callback) {
|
|
7161
|
+
authStateListeners.push(callback);
|
|
7162
|
+
if (initCompleted || hasRunFirstTime) {
|
|
7163
|
+
// Call the callback immediately with the current user.
|
|
7164
|
+
// We check hasRunFirstTime in addition to initCompleted to handle the race
|
|
7165
|
+
// where the Phantom auto-login effect calls setCurrentUser() before init()
|
|
7166
|
+
// finishes. Without this, listeners registered between auto-login's
|
|
7167
|
+
// setCurrentUser and init()'s initCompleted would never fire (init's
|
|
7168
|
+
// setCurrentUser is a no-op for same-address and initCompleted is still false).
|
|
7169
|
+
callback(currentUser);
|
|
7170
|
+
}
|
|
7171
|
+
return () => {
|
|
7172
|
+
authStateListeners = authStateListeners.filter((listener) => listener !== callback);
|
|
7173
|
+
};
|
|
6902
7174
|
}
|
|
6903
|
-
function
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
window.setTimeout(() => {
|
|
6911
|
-
if (m.generation === gen)
|
|
6912
|
-
m.overlay.classList.remove("mounted", "closing");
|
|
6913
|
-
}, 260);
|
|
7175
|
+
function onAuthLoadingChanged(callback) {
|
|
7176
|
+
authLoadingListeners.push(callback);
|
|
7177
|
+
// Call immediately with current loading state
|
|
7178
|
+
callback(isAuthLoading);
|
|
7179
|
+
return () => {
|
|
7180
|
+
authLoadingListeners = authLoadingListeners.filter((listener) => listener !== callback);
|
|
7181
|
+
};
|
|
6914
7182
|
}
|
|
6915
|
-
function
|
|
6916
|
-
|
|
6917
|
-
|
|
7183
|
+
function setAuthLoading(loading) {
|
|
7184
|
+
if (isAuthLoading !== loading) {
|
|
7185
|
+
isAuthLoading = loading;
|
|
7186
|
+
authLoadingListeners.forEach((callback) => callback(loading));
|
|
7187
|
+
}
|
|
6918
7188
|
}
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
// margins (catches border + margins, which scrollHeight omits). Ceiled so a
|
|
6922
|
-
// sub-pixel row is never clipped under `overflow:hidden`.
|
|
6923
|
-
function measureOuterHeight(target) {
|
|
6924
|
-
var _a;
|
|
6925
|
-
const cs = (_a = target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.getComputedStyle(target);
|
|
6926
|
-
const marginY = cs ? parseFloat(cs.marginTop) + parseFloat(cs.marginBottom) : 0;
|
|
6927
|
-
const borderBox = target.offsetHeight + (Number.isFinite(marginY) ? marginY : 0);
|
|
6928
|
-
const rect = target.getBoundingClientRect().height;
|
|
6929
|
-
return Math.ceil(Math.max(target.scrollHeight, borderBox, rect));
|
|
7189
|
+
function getAuthLoading() {
|
|
7190
|
+
return isAuthLoading;
|
|
6930
7191
|
}
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
* the Newsreader/Schibsted web fonts swap in (which grows line boxes a few px
|
|
6935
|
-
* after first paint) and on inline view swaps (email -> OTP code -> success).
|
|
6936
|
-
*/
|
|
6937
|
-
function fitPanelToContent(m) {
|
|
6938
|
-
var _a, _b;
|
|
6939
|
-
const first = m.content.firstElementChild;
|
|
6940
|
-
const target = first || m.content;
|
|
6941
|
-
const apply = () => {
|
|
6942
|
-
// Only fit while this exact node is still the mounted card.
|
|
6943
|
-
if (m.content.firstElementChild !== first && first)
|
|
6944
|
-
return;
|
|
6945
|
-
const h = measureOuterHeight(target);
|
|
6946
|
-
if (h > 0)
|
|
6947
|
-
setPanelHeight(m, h);
|
|
6948
|
-
};
|
|
6949
|
-
apply();
|
|
6950
|
-
const view = target.ownerDocument.defaultView;
|
|
6951
|
-
// Re-fit after fonts load (one-shot) and once the ResizeObserver-less browsers
|
|
6952
|
-
// have painted, so the height never lags a late reflow.
|
|
6953
|
-
(_a = view === null || view === void 0 ? void 0 : view.requestAnimationFrame) === null || _a === void 0 ? void 0 : _a.call(view, apply);
|
|
6954
|
-
const fonts = target.ownerDocument.fonts;
|
|
6955
|
-
if (fonts === null || fonts === void 0 ? void 0 : fonts.ready)
|
|
6956
|
-
fonts.ready.then(apply).catch(() => { });
|
|
6957
|
-
(_b = m.fitObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
6958
|
-
m.fitObserver = null;
|
|
6959
|
-
if (first && typeof (view === null || view === void 0 ? void 0 : view.ResizeObserver) === "function") {
|
|
6960
|
-
const ro = new view.ResizeObserver(() => apply());
|
|
6961
|
-
ro.observe(first);
|
|
6962
|
-
m.fitObserver = ro;
|
|
7192
|
+
async function login(options) {
|
|
7193
|
+
if (!authProviderInstance) {
|
|
7194
|
+
throw new Error('SDK not initialized. Please call init() first.');
|
|
6963
7195
|
}
|
|
7196
|
+
const loggedInUser = await login$1(options);
|
|
7197
|
+
setCurrentUser(loggedInUser);
|
|
7198
|
+
return currentUser;
|
|
6964
7199
|
}
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
m.content.replaceChildren(node);
|
|
6972
|
-
m.content.style.display = "block";
|
|
7200
|
+
async function logout(options) {
|
|
7201
|
+
if (!authProviderInstance) {
|
|
7202
|
+
throw new Error('SDK not initialized. Please call init() first.');
|
|
7203
|
+
}
|
|
7204
|
+
await logout$1(options);
|
|
7205
|
+
setCurrentUser(null);
|
|
6973
7206
|
}
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
* mirror with it.
|
|
6981
|
-
*
|
|
6982
|
-
* Deliberately does NOT call getModal(): that would BUILD a modal just to tear it down, on
|
|
6983
|
-
* every logout of every app, most of which never mounted one.
|
|
6984
|
-
*/
|
|
6985
|
-
function detachWarmSignerIframe(origin) {
|
|
6986
|
-
if (!modal || !modal.iframe || modal.iframeOrigin !== origin)
|
|
6987
|
-
return false;
|
|
6988
|
-
detachIframe(modal);
|
|
6989
|
-
return true;
|
|
7207
|
+
var hasRunFirstTime = false;
|
|
7208
|
+
function userPrincipalKey(user) {
|
|
7209
|
+
var _a, _b, _c;
|
|
7210
|
+
if (!user)
|
|
7211
|
+
return null;
|
|
7212
|
+
return (_c = (_b = (_a = user.id) !== null && _a !== void 0 ? _a : user.address) !== null && _b !== void 0 ? _b : user.email) !== null && _c !== void 0 ? _c : null;
|
|
6990
7213
|
}
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
7214
|
+
// Update the currentUser and notify listeners
|
|
7215
|
+
function setCurrentUser(user) {
|
|
7216
|
+
// Enrich with the universal identity fields (id/email) derived from the
|
|
7217
|
+
// active session's idToken — central chokepoint so every provider's user
|
|
7218
|
+
// object gains { id, address, email } without each construction site
|
|
7219
|
+
// having to set them.
|
|
7220
|
+
user = withUserIdentity(user);
|
|
7221
|
+
const lastUser = currentUser;
|
|
7222
|
+
const wasInitializedBefore = hasRunFirstTime;
|
|
7223
|
+
const lastPrincipal = userPrincipalKey(currentUser);
|
|
7224
|
+
const nextPrincipal = userPrincipalKey(user);
|
|
7225
|
+
const shouldRunListeners = !hasRunFirstTime || lastPrincipal !== nextPrincipal;
|
|
7226
|
+
hasRunFirstTime = true;
|
|
7227
|
+
currentUser = user;
|
|
7228
|
+
if (shouldRunListeners) {
|
|
7229
|
+
// Isolate each listener: a consumer callback that throws (during logout in
|
|
7230
|
+
// particular) must not abort the notification loop OR skip the socket reopen
|
|
7231
|
+
// below. Before this, a single throwing listener propagated out of
|
|
7232
|
+
// setCurrentUser, so the realtime socket kept streaming as the signed-out
|
|
7233
|
+
// user and the other listeners were never told the principal changed.
|
|
7234
|
+
authStateListeners.forEach((callback) => {
|
|
7235
|
+
try {
|
|
7236
|
+
callback(user);
|
|
7237
|
+
}
|
|
7238
|
+
catch (error) {
|
|
7239
|
+
console.error('Error in onAuthStateChanged listener:', error);
|
|
7240
|
+
}
|
|
7241
|
+
});
|
|
7242
|
+
// Reconnect WebSocket connections with new auth when user changes.
|
|
7243
|
+
// Skip on the first setCurrentUser call during init() — there are no
|
|
7244
|
+
// existing connections to reconnect at that point.
|
|
7245
|
+
if (wasInitializedBefore && userPrincipalKey(lastUser) !== nextPrincipal) {
|
|
7246
|
+
reconnectWithNewAuth().catch((error) => {
|
|
7247
|
+
console.error('Error reconnecting WebSocket after auth change:', error);
|
|
7248
|
+
});
|
|
6998
7249
|
}
|
|
6999
7250
|
}
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
m.onIframeMessage = null;
|
|
7251
|
+
// Return this commit's normalized snapshot. A listener can start another
|
|
7252
|
+
// login, so the mutable currentUser may already belong to a newer session.
|
|
7253
|
+
return user;
|
|
7004
7254
|
}
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
function
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
const pre1 = document.createElement("link");
|
|
7015
|
-
pre1.rel = "preconnect";
|
|
7016
|
-
pre1.href = "https://fonts.googleapis.com";
|
|
7017
|
-
const pre2 = document.createElement("link");
|
|
7018
|
-
pre2.rel = "preconnect";
|
|
7019
|
-
pre2.href = "https://fonts.gstatic.com";
|
|
7020
|
-
pre2.crossOrigin = "anonymous";
|
|
7021
|
-
const link = document.createElement("link");
|
|
7022
|
-
link.rel = "stylesheet";
|
|
7023
|
-
link.href = FONTS_HREF;
|
|
7024
|
-
link.setAttribute("data-bounded-fonts", "1");
|
|
7025
|
-
document.head.append(pre1, pre2, link);
|
|
7255
|
+
function getCurrentUser() {
|
|
7256
|
+
return currentUser;
|
|
7257
|
+
}
|
|
7258
|
+
/** @internal Snapshot/restore surface for transactional auth-provider switches. */
|
|
7259
|
+
function getAuthProviderInstance() {
|
|
7260
|
+
return authProviderInstance;
|
|
7261
|
+
}
|
|
7262
|
+
function setAuthProviderInstance(provider) {
|
|
7263
|
+
authProviderInstance = provider;
|
|
7026
7264
|
}
|
|
7027
|
-
|
|
7028
|
-
var boundedWidgetModal = /*#__PURE__*/Object.freeze({
|
|
7029
|
-
__proto__: null,
|
|
7030
|
-
detachIframe: detachIframe,
|
|
7031
|
-
detachWarmSignerIframe: detachWarmSignerIframe,
|
|
7032
|
-
ensureFonts: ensureFonts,
|
|
7033
|
-
fitPanelToContent: fitPanelToContent,
|
|
7034
|
-
getModal: getModal,
|
|
7035
|
-
hasDOM: hasDOM$2,
|
|
7036
|
-
hideModal: hideModal,
|
|
7037
|
-
mountContent: mountContent,
|
|
7038
|
-
setPanelHeight: setPanelHeight,
|
|
7039
|
-
showModal: showModal
|
|
7040
|
-
});
|
|
7041
7265
|
|
|
7042
7266
|
// bounded-widget-ownership.ts - per-open ownership generation for the singleton
|
|
7043
7267
|
// login/signing modal (bounded-widget-modal.ts).
|
|
@@ -7498,7 +7722,7 @@ async function startTurnkeyEmailLogin(email) {
|
|
|
7498
7722
|
const res = await fetch(`${base}/wallet/turnkey/auth/init`, {
|
|
7499
7723
|
method: 'POST',
|
|
7500
7724
|
headers: { 'content-type': 'application/json' },
|
|
7501
|
-
body: JSON.stringify({ email: trimmed, appId }),
|
|
7725
|
+
body: JSON.stringify({ email: trimmed, appId, walletProtocol: 2 }),
|
|
7502
7726
|
});
|
|
7503
7727
|
const data = await res.json().catch(() => null);
|
|
7504
7728
|
if (!res.ok || !data || typeof data.otpId !== 'string' || typeof data.otpEncryptionTargetBundle !== 'string') {
|
|
@@ -7510,13 +7734,21 @@ async function startTurnkeyEmailLogin(email) {
|
|
|
7510
7734
|
// (there is nothing to bind the frame key to), which is the correct fail-closed posture.
|
|
7511
7735
|
const ceremonyId = typeof data.ceremonyId === 'string' ? data.ceremonyId : '';
|
|
7512
7736
|
const signerOrigin = new URL(base).origin;
|
|
7737
|
+
if (data.walletScope !== 'app' && data.walletScope !== 'legacy')
|
|
7738
|
+
throw new Error('wallet_scope_config_unavailable');
|
|
7739
|
+
if (data.walletProtocol !== (data.walletScope === 'app' ? 2 : 1))
|
|
7740
|
+
throw new Error('wallet_protocol_invalid');
|
|
7741
|
+
const frameUrl = data.walletScope === 'app'
|
|
7742
|
+
? `${base.replace(/\/$/, '')}/wallet/turnkey/app/${appId}/signer?origin=${encodeURIComponent(window.location.origin)}`
|
|
7743
|
+
: undefined;
|
|
7513
7744
|
return {
|
|
7514
|
-
email: trimmed,
|
|
7515
|
-
run(iframe) {
|
|
7745
|
+
email: trimmed, frameUrl, signerOrigin,
|
|
7746
|
+
run(iframe, signal) {
|
|
7516
7747
|
if (!ceremonyId) {
|
|
7517
7748
|
return Promise.reject(new Error('This app is not configured for in-frame wallet login. Please try again.'));
|
|
7518
7749
|
}
|
|
7519
|
-
|
|
7750
|
+
const run = { iframe, base, signerOrigin, appId, email: trimmed, otpId, otpEncryptionTargetBundle, ceremonyId };
|
|
7751
|
+
return frameUrl ? runScopedOtpFrame(run, frameUrl, signal) : runOtpFrame(run);
|
|
7520
7752
|
},
|
|
7521
7753
|
};
|
|
7522
7754
|
}
|
|
@@ -7621,6 +7853,104 @@ function runOtpFrame(run) {
|
|
|
7621
7853
|
run.iframe.src = url;
|
|
7622
7854
|
});
|
|
7623
7855
|
}
|
|
7856
|
+
// The scoped document owns both OTP and signing. Adopt only its sanitized app
|
|
7857
|
+
// session, acknowledge with a narrow grant, then wait for wallet readiness.
|
|
7858
|
+
function runScopedOtpFrame(run, frameUrl, signal) {
|
|
7859
|
+
const assertCurrent = walletLoginGuard(signal);
|
|
7860
|
+
const beforeStore = getActiveSessionManager().captureSessionEstablishment();
|
|
7861
|
+
return new Promise((resolve, reject) => {
|
|
7862
|
+
let settled = false, adopting = false, started = false;
|
|
7863
|
+
let user = null;
|
|
7864
|
+
let adoptedToken = '';
|
|
7865
|
+
let context = null;
|
|
7866
|
+
const post = (data) => { var _a; return (_a = run.iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage(data, run.signerOrigin); };
|
|
7867
|
+
const cleanup = () => {
|
|
7868
|
+
settled = true;
|
|
7869
|
+
clearTimeout(timer);
|
|
7870
|
+
window.removeEventListener('message', onMessage);
|
|
7871
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', abort);
|
|
7872
|
+
};
|
|
7873
|
+
const fail = (error) => { cleanup(); reject(error instanceof Error ? error : new Error(String(error))); };
|
|
7874
|
+
const abort = () => { post({ type: 'bounded:otp:cancel' }); fail(new Error('wallet_login_cancelled')); };
|
|
7875
|
+
const finish = (ready, reason, expiresAt) => {
|
|
7876
|
+
if (!user || !context || getActiveSessionManager().getIdToken() !== adoptedToken) {
|
|
7877
|
+
fail(new Error('wallet_session_changed'));
|
|
7878
|
+
return;
|
|
7879
|
+
}
|
|
7880
|
+
setWalletSessionState(context.key, ready && Number.isFinite(expiresAt) && expiresAt > Date.now()
|
|
7881
|
+
? { status: 'ready', expiresAt: expiresAt } : { status: 'verification-required', reason });
|
|
7882
|
+
cleanup();
|
|
7883
|
+
resolve(user);
|
|
7884
|
+
};
|
|
7885
|
+
const onMessage = async (event) => {
|
|
7886
|
+
if (settled || event.origin !== run.signerOrigin || event.source !== run.iframe.contentWindow)
|
|
7887
|
+
return;
|
|
7888
|
+
const d = event.data;
|
|
7889
|
+
if (!d || typeof d !== 'object')
|
|
7890
|
+
return;
|
|
7891
|
+
if (d.type === 'bounded:otp:ready' && !started) {
|
|
7892
|
+
started = true;
|
|
7893
|
+
post({ type: 'bounded:otp:start', email: run.email, ceremonyId: run.ceremonyId,
|
|
7894
|
+
otpId: run.otpId, otpEncryptionTargetBundle: run.otpEncryptionTargetBundle });
|
|
7895
|
+
}
|
|
7896
|
+
if (d.type === 'bounded:otp:wallet-ready' && user) {
|
|
7897
|
+
finish(d.ok === true, typeof d.error === 'string' ? d.error : 'wallet_session_unavailable', d.expiresAt);
|
|
7898
|
+
}
|
|
7899
|
+
if (d.type !== 'bounded:otp:result' || adopting)
|
|
7900
|
+
return;
|
|
7901
|
+
if (d.ok !== true || !d.session || typeof d.session.idToken !== 'string') {
|
|
7902
|
+
fail(new Error(String(d.error || 'wallet_login_failed')));
|
|
7903
|
+
return;
|
|
7904
|
+
}
|
|
7905
|
+
adopting = true;
|
|
7906
|
+
try {
|
|
7907
|
+
assertCurrent();
|
|
7908
|
+
const token = d.session.idToken;
|
|
7909
|
+
const claims = JSON.parse(getPlatform().atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
7910
|
+
context = walletContextFromClaims(claims, run.base, run.appId, window.location.origin);
|
|
7911
|
+
if (context.scope !== 'app')
|
|
7912
|
+
throw new Error('wallet_scope_mismatch');
|
|
7913
|
+
user = await establishInlineSession(d.session, () => { assertCurrent(); if (settled)
|
|
7914
|
+
throw new Error('wallet_login_cancelled'); }, beforeStore);
|
|
7915
|
+
adoptedToken = token;
|
|
7916
|
+
bindWarmSignerFrame(run.iframe, context.key);
|
|
7917
|
+
if (settled || (signal === null || signal === void 0 ? void 0 : signal.aborted) || getActiveSessionManager().getIdToken() !== token)
|
|
7918
|
+
throw new Error('wallet_session_changed');
|
|
7919
|
+
setWalletSessionState(context.key, { status: 'verification-required', reason: 'wallet_session_pending' });
|
|
7920
|
+
const authorization = await requestTurnkeyActionAuthorization({
|
|
7921
|
+
baseUrl: run.base, idToken: token, action: 'createWallet', fetchImpl: (u, i) => fetch(u, i),
|
|
7922
|
+
});
|
|
7923
|
+
if (settled || getActiveSessionManager().getIdToken() !== token)
|
|
7924
|
+
throw new Error('wallet_session_changed');
|
|
7925
|
+
post({ type: 'bounded:otp:authorization', authorization });
|
|
7926
|
+
}
|
|
7927
|
+
catch (error) {
|
|
7928
|
+
if (user && adoptedToken) {
|
|
7929
|
+
post({ type: 'bounded:otp:authorization', authorization: '' });
|
|
7930
|
+
finish(false, 'wallet_session_unavailable');
|
|
7931
|
+
}
|
|
7932
|
+
else
|
|
7933
|
+
fail(error);
|
|
7934
|
+
}
|
|
7935
|
+
};
|
|
7936
|
+
const timer = setTimeout(() => {
|
|
7937
|
+
if (user)
|
|
7938
|
+
finish(false, 'wallet_session_timeout');
|
|
7939
|
+
else
|
|
7940
|
+
fail(new Error('The sign-in code entry timed out. Please try again.'));
|
|
7941
|
+
}, 180000);
|
|
7942
|
+
window.addEventListener('message', onMessage);
|
|
7943
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', abort, { once: true });
|
|
7944
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
7945
|
+
abort();
|
|
7946
|
+
return;
|
|
7947
|
+
}
|
|
7948
|
+
if (run.iframe.src !== frameUrl)
|
|
7949
|
+
run.iframe.src = frameUrl;
|
|
7950
|
+
// Readiness can predate listener installation when reusing a mounted frame.
|
|
7951
|
+
post({ type: 'bounded:otp:hello' });
|
|
7952
|
+
});
|
|
7953
|
+
}
|
|
7624
7954
|
|
|
7625
7955
|
// bounded-login-widget.ts - the UNIFIED Bounded login surface: one in-app card
|
|
7626
7956
|
// (Shadow-DOM modal from bounded-widget-modal.ts, Bounded look + motion) that folds
|
|
@@ -7844,7 +8174,7 @@ function untilCancelled(work, pending) {
|
|
|
7844
8174
|
* run first; call from a user gesture so social/wallet popups are allowed.
|
|
7845
8175
|
*/
|
|
7846
8176
|
async function openBoundedWidget(opts = {}) {
|
|
7847
|
-
if (!hasDOM$
|
|
8177
|
+
if (!hasDOM$5()) {
|
|
7848
8178
|
throw new Error("openBoundedWidget is web-only - on React Native use loginWithRedirect()");
|
|
7849
8179
|
}
|
|
7850
8180
|
// Registered before the first await - see PendingOpen.
|
|
@@ -8006,6 +8336,7 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8006
8336
|
// the underlying promise SETTLES, whoever owns the card by then.
|
|
8007
8337
|
let attemptSeq = 0;
|
|
8008
8338
|
let activeAttempt = 0;
|
|
8339
|
+
let abortScopedLogin = null;
|
|
8009
8340
|
let abortGesture = null;
|
|
8010
8341
|
/** Reject a gesture promise already waiting on a view that is going away. */
|
|
8011
8342
|
const abortPendingGesture = () => {
|
|
@@ -8037,6 +8368,8 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8037
8368
|
/** Take the card back (Back, a lane switch, settling the modal). Deliberately
|
|
8038
8369
|
* NOT a release: the login underneath keeps running - see above. */
|
|
8039
8370
|
const invalidateAttempts = () => {
|
|
8371
|
+
abortScopedLogin === null || abortScopedLogin === void 0 ? void 0 : abortScopedLogin();
|
|
8372
|
+
abortScopedLogin = null;
|
|
8040
8373
|
activeAttempt = 0;
|
|
8041
8374
|
abortPendingGesture();
|
|
8042
8375
|
};
|
|
@@ -8090,7 +8423,7 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8090
8423
|
const style = el("style");
|
|
8091
8424
|
style.textContent = CARD_CSS;
|
|
8092
8425
|
let emailDraft = "";
|
|
8093
|
-
const renderChoose = () => {
|
|
8426
|
+
const renderChoose = (initialError) => {
|
|
8094
8427
|
const card = el("div", "c");
|
|
8095
8428
|
card.appendChild(style.cloneNode(true));
|
|
8096
8429
|
const x = el("button", "x", "Close");
|
|
@@ -8100,13 +8433,15 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8100
8433
|
card.appendChild(x);
|
|
8101
8434
|
card.appendChild(el("h1", undefined, title !== null && title !== void 0 ? title : "Sign in"));
|
|
8102
8435
|
if (subtitle !== "") {
|
|
8103
|
-
card.appendChild(el("p", "sub", subtitle !== null && subtitle !== void 0 ? subtitle : "
|
|
8436
|
+
card.appendChild(el("p", "sub", subtitle !== null && subtitle !== void 0 ? subtitle : "Sign in to continue to this app."));
|
|
8104
8437
|
}
|
|
8105
8438
|
const status = el("div", "status");
|
|
8106
8439
|
const setErr = (msg) => {
|
|
8107
8440
|
status.textContent = msg;
|
|
8108
8441
|
status.classList.add("err");
|
|
8109
8442
|
};
|
|
8443
|
+
if (initialError)
|
|
8444
|
+
setErr(initialError);
|
|
8110
8445
|
const setBusy = (btn, label) => {
|
|
8111
8446
|
status.classList.remove("err");
|
|
8112
8447
|
status.textContent = "";
|
|
@@ -8150,11 +8485,39 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8150
8485
|
// arrives; the code entry claims its own when the user verifies.
|
|
8151
8486
|
setBusy(submit, "Sending code\u2026");
|
|
8152
8487
|
startTurnkeyEmailLogin(input.value)
|
|
8153
|
-
.then((handle) => {
|
|
8154
|
-
|
|
8488
|
+
.then(async (handle) => {
|
|
8489
|
+
if (!attemptIsCurrent(attempt)) {
|
|
8490
|
+
settleAttempt(attempt);
|
|
8491
|
+
return;
|
|
8492
|
+
}
|
|
8493
|
+
if (!handle.frameUrl || !handle.signerOrigin) {
|
|
8494
|
+
if (settleAttempt(attempt))
|
|
8495
|
+
swap(renderOtpFrame(handle));
|
|
8496
|
+
return;
|
|
8497
|
+
}
|
|
8498
|
+
const controller = new AbortController();
|
|
8499
|
+
abortScopedLogin = () => controller.abort();
|
|
8500
|
+
// Mount once in the shared modal. Moving a live iframe would discard
|
|
8501
|
+
// the trusted document holding the private in-memory session key.
|
|
8502
|
+
detachIframe(m);
|
|
8503
|
+
const frame = ensureIframe(m, handle.frameUrl, handle.signerOrigin, `login:${attempt}`);
|
|
8504
|
+
setPanelHeight(m, 380);
|
|
8505
|
+
const completion = handle.run(frame, controller.signal);
|
|
8506
|
+
m.chain = completion.then(() => undefined, () => undefined);
|
|
8507
|
+
const user = await completion;
|
|
8508
|
+
abortScopedLogin = null;
|
|
8509
|
+
settleAttempt(attempt);
|
|
8510
|
+
succeed(user);
|
|
8511
|
+
})
|
|
8155
8512
|
.catch((err) => {
|
|
8156
8513
|
if (!settleAttempt(attempt))
|
|
8157
8514
|
return;
|
|
8515
|
+
if (abortScopedLogin) {
|
|
8516
|
+
abortScopedLogin = null;
|
|
8517
|
+
detachIframe(m);
|
|
8518
|
+
swap(renderChoose(errText(err)));
|
|
8519
|
+
return;
|
|
8520
|
+
}
|
|
8158
8521
|
submit.disabled = false;
|
|
8159
8522
|
submit.textContent = "Continue";
|
|
8160
8523
|
setErr(errText(err));
|
|
@@ -10119,5 +10482,5 @@ function defaultInjectedEvmProvider() {
|
|
|
10119
10482
|
return null;
|
|
10120
10483
|
}
|
|
10121
10484
|
|
|
10122
|
-
export {
|
|
10123
|
-
//# sourceMappingURL=index-
|
|
10485
|
+
export { onrampStatus as $, getAuthProvider as A, signInAnonymously as B, loginWithPrivy as C, loginWithWallet as D, ensureWalletLoginReady as E, ensureSharedWalletLoginReady as F, preloadWalletLogin as G, loginWithRedirect as H, completeLoginFromRedirect as I, loginWithPopup as J, completeLoginInPopup as K, openBoundedWidget as L, ensureSignedIn as M, useAuth as N, useQuery as O, useBuildRun as P, apps as Q, signSolanaMessageViaTurnkey as R, getOrCreateTurnkeyWallet as S, openTurnkeyKeyExport as T, getTurnkeyWalletSessionState as U, getTurnkeyWalletRecoveryUrl as V, WalletConfigError as W, startTurnkeyEmailLogin as X, getIdToken as Y, onramp as Z, onrampQuote as _, signMessageWithTurnkey as a, setPlatform as a0, OffchainAuthProvider as a1, PrivyExpoProvider as a2, requestEip6963Providers as a3, builds as a4, BuildsApiError as a5, NON_TERMINAL_STATES as a6, TERMINAL_STATES as a7, RUN_STATES as a8, PARK_REASONS as a9, FAILURE_REASONS as aa, BUILD_REVIEW_CAPABILITY_SCHEMA as ab, BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA as ac, BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA as ad, signTransactionWithTurnkey as b, signTransactionV1WithTurnkey as c, signAndSubmitTransactionWithTurnkey as d, bs58 as e, defaultInjectedProvider as f, getPlatform as g, hasTurnkeyWallet as h, firstStandardWalletProvider as i, setAuthLoading as j, bufferExports as k, setStoredAuthMethod as l, confirmAndCheckTransaction as m, normalizeSolanaRpcUrl as n, defaultInjectedEvmProvider as o, init as p, getCurrentUser as q, resolveSolanaRpcUrl as r, setCurrentUser as s, transactionHasSignature as t, onAuthStateChanged as u, onAuthLoadingChanged as v, getAuthLoading as w, waitForAuthReady as x, login as y, logout as z };
|
|
10486
|
+
//# sourceMappingURL=index-DmAXYXDi.mjs.map
|