@bounded-sh/client 0.0.98 → 0.0.99
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 +7 -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/client.d.ts +5 -2
- package/dist/builds/types.d.ts +108 -1
- package/dist/{email-auth-provider-DxB_pJKO.js → email-auth-provider-62OK5X30.js} +2 -2
- package/dist/{email-auth-provider-DxB_pJKO.js.map → email-auth-provider-62OK5X30.js.map} +1 -1
- package/dist/{email-auth-provider-DF7Urh8i.mjs → email-auth-provider-BKYVi3dl.mjs} +2 -2
- package/dist/{email-auth-provider-CiPAD3vz.mjs.map → email-auth-provider-BKYVi3dl.mjs.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-B9C3Iub_.js → guest-auth-provider-B6dJixRm.js} +2 -2
- package/dist/{guest-auth-provider-B9C3Iub_.js.map → guest-auth-provider-B6dJixRm.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-E7Fg7-E2.mjs} +2 -2
- package/dist/{guest-auth-provider-Dtsq4QaY.mjs.map → guest-auth-provider-E7Fg7-E2.mjs.map} +1 -1
- package/dist/{index-CYdYsf8s.js → index-BrU_tGd-.js} +1148 -597
- package/dist/index-BrU_tGd-.js.map +1 -0
- package/dist/{index-Cflemdaf.mjs → index-l5E4pjpt.mjs} +1145 -598
- package/dist/index-l5E4pjpt.mjs.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -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-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-CMqUqqxN.mjs → injected-evm-wallet-provider-BmDPRV5L.mjs} +2 -2
- package/dist/{injected-evm-wallet-provider-CMqUqqxN.mjs.map → injected-evm-wallet-provider-BmDPRV5L.mjs.map} +1 -1
- package/dist/{injected-evm-wallet-provider-Bv8VLuB1.js → injected-evm-wallet-provider-CO_aC4TY.js} +2 -2
- package/dist/{injected-evm-wallet-provider-Bv8VLuB1.js.map → injected-evm-wallet-provider-CO_aC4TY.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-wallet-provider-BDt4CN0b.js → injected-wallet-provider-B9eU7t8o.js} +2 -2
- package/dist/{injected-wallet-provider-BDt4CN0b.js.map → injected-wallet-provider-B9eU7t8o.js.map} +1 -1
- package/dist/{injected-wallet-provider-CG3oKdp_.js → injected-wallet-provider-Bi_mB_q3.js} +2 -2
- package/dist/{injected-wallet-provider-CG3oKdp_.js.map → injected-wallet-provider-Bi_mB_q3.js.map} +1 -1
- package/dist/{injected-wallet-provider-DH8yLpyf.mjs → injected-wallet-provider-BlSdRbgq.mjs} +2 -2
- package/dist/{injected-wallet-provider-DH8yLpyf.mjs.map → injected-wallet-provider-BlSdRbgq.mjs.map} +1 -1
- package/dist/{injected-wallet-provider-Dlo96aV9.mjs → injected-wallet-provider-yl51pLBa.mjs} +2 -2
- package/dist/{injected-wallet-provider-Dlo96aV9.mjs.map → injected-wallet-provider-yl51pLBa.mjs.map} +1 -1
- package/dist/services/broadcast.d.ts +28 -0
- package/dist/services/session.d.ts +28 -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
|
@@ -22,6 +22,53 @@ function _interopNamespaceDefault(e) {
|
|
|
22
22
|
|
|
23
23
|
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
24
24
|
|
|
25
|
+
function walletContextFromClaims(claims, baseUrl, expectedAppId, tenantOrigin) {
|
|
26
|
+
const base = baseUrl.replace(/\/$/, '');
|
|
27
|
+
const scope = claims['custom:walletScope'];
|
|
28
|
+
const protocol = claims['custom:walletProtocol'];
|
|
29
|
+
if (scope !== undefined && scope !== 'legacy' && scope !== 'app')
|
|
30
|
+
throw new Error('wallet_scope_invalid');
|
|
31
|
+
if ((scope === 'app' && protocol !== 2) || (scope === 'legacy' && protocol !== 1)
|
|
32
|
+
|| (scope === undefined && protocol !== undefined))
|
|
33
|
+
throw new Error('wallet_protocol_invalid');
|
|
34
|
+
const appId = claims['custom:appId'];
|
|
35
|
+
const userId = claims['custom:userId'];
|
|
36
|
+
const address = claims['custom:walletAddress'];
|
|
37
|
+
if (scope === 'app' && (appId !== expectedAppId || typeof appId !== 'string'
|
|
38
|
+
|| !/^[a-f0-9]{24}$/.test(appId) || typeof userId !== 'string' || !userId
|
|
39
|
+
|| claims.iss !== base))
|
|
40
|
+
throw new Error('wallet_session_context_invalid');
|
|
41
|
+
const resolvedScope = scope === 'app' ? 'app' : 'legacy';
|
|
42
|
+
const route = resolvedScope === 'app' ? `/wallet/turnkey/app/${appId}` : '/wallet/turnkey';
|
|
43
|
+
return {
|
|
44
|
+
scope: resolvedScope, appId: String(appId || expectedAppId), userId: String(userId || ''),
|
|
45
|
+
address: typeof address === 'string' ? address : '', base, origin: new URL(base).origin,
|
|
46
|
+
url: `${base}${route}/signer${resolvedScope === 'app' ? `?origin=${encodeURIComponent(tenantOrigin)}` : ''}`,
|
|
47
|
+
key: JSON.stringify([base, appId || expectedAppId, userId || '', address || '', resolvedScope]),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
let loginGeneration = 0;
|
|
51
|
+
function invalidatePendingWalletLogin() { ++loginGeneration; }
|
|
52
|
+
function walletOperationGuard() {
|
|
53
|
+
const generation = loginGeneration;
|
|
54
|
+
return () => {
|
|
55
|
+
if (generation !== loginGeneration)
|
|
56
|
+
throw new Error('wallet_session_changed');
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// This realm owns one active SDK session and one retained signer frame.
|
|
60
|
+
let activeWalletState;
|
|
61
|
+
function setWalletSessionState(key, state) { activeWalletState = { key, state }; }
|
|
62
|
+
function walletSessionState(key) {
|
|
63
|
+
if ((activeWalletState === null || activeWalletState === void 0 ? void 0 : activeWalletState.key) !== key)
|
|
64
|
+
return { status: 'verification-required' };
|
|
65
|
+
const state = activeWalletState.state;
|
|
66
|
+
if (state.status === 'ready' && (!Number.isFinite(state.expiresAt) || state.expiresAt <= Date.now())) {
|
|
67
|
+
return { status: 'verification-required', reason: 'wallet_session_expired' };
|
|
68
|
+
}
|
|
69
|
+
return state;
|
|
70
|
+
}
|
|
71
|
+
|
|
25
72
|
/**
|
|
26
73
|
* Platform abstraction layer for React Native compatibility.
|
|
27
74
|
*
|
|
@@ -965,7 +1012,10 @@ function registerAuthRegistrySync(fn) {
|
|
|
965
1012
|
* Returns the normalized user published by this commit, or null when omitted.
|
|
966
1013
|
*/
|
|
967
1014
|
async function adoptSessionProvider(opts) {
|
|
1015
|
+
var _a;
|
|
968
1016
|
const coreConfig = await core.getConfig();
|
|
1017
|
+
(_a = opts.beforeCommit) === null || _a === void 0 ? void 0 : _a.call(opts);
|
|
1018
|
+
invalidatePendingWalletLogin();
|
|
969
1019
|
coreConfig.authProvider = opts.provider;
|
|
970
1020
|
coreConfig.authMethod = opts.method;
|
|
971
1021
|
setAuthProviderInstance(opts.provider);
|
|
@@ -4199,148 +4249,560 @@ async function ensureSolanaMobileWalletRegistered(config, network) {
|
|
|
4199
4249
|
|
|
4200
4250
|
var bufferExports = requireBuffer();
|
|
4201
4251
|
|
|
4202
|
-
//
|
|
4203
|
-
//
|
|
4204
|
-
//
|
|
4205
|
-
//
|
|
4206
|
-
// (bounded:turnkey:clearSigningSession).
|
|
4252
|
+
// bounded-widget-modal.ts - the shared in-app WIDGET shell for Bounded's login
|
|
4253
|
+
// and signing surfaces. One Shadow-DOM overlay + panel that matches the Bounded
|
|
4254
|
+
// widget (Seal) look and MOTION: a backdrop fade, a panel rise on desktop, a
|
|
4255
|
+
// bottom-sheet on mobile, a height tween as content swaps, and a clean close.
|
|
4207
4256
|
//
|
|
4208
|
-
//
|
|
4209
|
-
//
|
|
4210
|
-
//
|
|
4211
|
-
//
|
|
4257
|
+
// It hosts EITHER:
|
|
4258
|
+
// - NATIVE content (the login card the SDK renders itself), or
|
|
4259
|
+
// - an IFRAME (the Bounded-owned signer page, which must run on the single
|
|
4260
|
+
// signer origin because the wallet's 24h signing session lives in that
|
|
4261
|
+
// origin's storage, out of every app's reach).
|
|
4262
|
+
//
|
|
4263
|
+
// The modal is a warm singleton: it stays mounted (hidden) between uses so a
|
|
4264
|
+
// second open - or a signature right after login - is instant and, when it is
|
|
4265
|
+
// already open, a clean in-place view swap rather than a reopen.
|
|
4212
4266
|
function hasDOM$5() {
|
|
4213
|
-
return typeof window !==
|
|
4267
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
4214
4268
|
}
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4269
|
+
const OVERLAY_CSS = `
|
|
4270
|
+
:host{all:initial}
|
|
4271
|
+
*{box-sizing:border-box}
|
|
4272
|
+
.bw-ov{position:fixed;inset:0;z-index:2147483647;display:none;
|
|
4273
|
+
font-family:"Schibsted Grotesk","Helvetica Neue",Arial,sans-serif}
|
|
4274
|
+
.bw-ov.mounted{display:block}
|
|
4275
|
+
.bw-back{position:fixed;inset:0;background:rgba(4,4,5,.62);opacity:0;
|
|
4276
|
+
transition:opacity .24s ease;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}
|
|
4277
|
+
.bw-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:20px}
|
|
4278
|
+
.bw-panel{position:relative;width:420px;max-width:calc(100vw - 28px);height:280px;
|
|
4279
|
+
border-radius:14px;overflow:hidden;background:#0e0f11;border:1px solid rgba(232,230,222,.08);
|
|
4280
|
+
box-shadow:0 1px 0 rgba(232,230,222,.03) inset,0 40px 90px -45px rgba(0,0,0,.85);
|
|
4281
|
+
opacity:0;transform:translateY(14px) scale(.98);will-change:transform,opacity,height;
|
|
4282
|
+
transition:opacity .28s cubic-bezier(.2,.8,.2,1),transform .28s cubic-bezier(.2,.8,.2,1),
|
|
4283
|
+
height .3s cubic-bezier(.2,.8,.2,1)}
|
|
4284
|
+
.bw-ov.show .bw-back{opacity:1}
|
|
4285
|
+
.bw-ov.show .bw-panel{opacity:1;transform:none}
|
|
4286
|
+
.bw-ov.closing .bw-back{opacity:0}
|
|
4287
|
+
.bw-ov.closing .bw-panel{opacity:0;transform:translateY(12px) scale(.99)}
|
|
4288
|
+
.bw-content{position:absolute;inset:0;overflow:hidden}
|
|
4289
|
+
.bw-frame{display:block;width:100%;height:100%;border:0;background:transparent;color-scheme:dark}
|
|
4290
|
+
/* editorial corner crop-marks, matching the reference frame */
|
|
4291
|
+
.bw-mk{position:absolute;width:13px;height:13px;pointer-events:none;z-index:2;
|
|
4292
|
+
border-color:rgba(232,230,222,.22);border-style:solid;border-width:0}
|
|
4293
|
+
.bw-mk.tl{top:11px;left:11px;border-top-width:1px;border-left-width:1px}
|
|
4294
|
+
.bw-mk.tr{top:11px;right:11px;border-top-width:1px;border-right-width:1px}
|
|
4295
|
+
.bw-mk.bl{bottom:11px;left:11px;border-bottom-width:1px;border-left-width:1px}
|
|
4296
|
+
.bw-mk.br{bottom:11px;right:11px;border-bottom-width:1px;border-right-width:1px}
|
|
4297
|
+
@media(max-width:560px){
|
|
4298
|
+
.bw-wrap{align-items:flex-end;padding:0}
|
|
4299
|
+
.bw-panel{width:100%;max-width:100%;border-radius:18px 18px 0 0;border-bottom:0}
|
|
4300
|
+
.bw-content{overflow-y:auto}
|
|
4301
|
+
.bw-ov.show .bw-panel{transform:none}
|
|
4302
|
+
.bw-ov:not(.show) .bw-panel,.bw-ov.closing .bw-panel{transform:translateY(18%)}
|
|
4219
4303
|
}
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
const
|
|
4233
|
-
|
|
4234
|
-
|
|
4304
|
+
@media(prefers-reduced-motion:reduce){.bw-back,.bw-panel{transition:opacity .12s linear!important}}
|
|
4305
|
+
`;
|
|
4306
|
+
let modal = null;
|
|
4307
|
+
function getModal() {
|
|
4308
|
+
if (modal)
|
|
4309
|
+
return modal;
|
|
4310
|
+
const root = document.createElement("div");
|
|
4311
|
+
root.setAttribute("data-bounded-widget", "");
|
|
4312
|
+
const shadow = root.attachShadow({ mode: "open" });
|
|
4313
|
+
const style = document.createElement("style");
|
|
4314
|
+
style.textContent = OVERLAY_CSS;
|
|
4315
|
+
shadow.appendChild(style);
|
|
4316
|
+
const overlay = document.createElement("div");
|
|
4317
|
+
overlay.className = "bw-ov";
|
|
4318
|
+
const back = document.createElement("div");
|
|
4319
|
+
back.className = "bw-back";
|
|
4320
|
+
const wrap = document.createElement("div");
|
|
4321
|
+
wrap.className = "bw-wrap";
|
|
4322
|
+
const panel = document.createElement("div");
|
|
4323
|
+
panel.className = "bw-panel";
|
|
4324
|
+
const content = document.createElement("div");
|
|
4325
|
+
content.className = "bw-content";
|
|
4326
|
+
panel.appendChild(content);
|
|
4327
|
+
for (const corner of ["tl", "tr", "bl", "br"]) {
|
|
4328
|
+
const mk = document.createElement("span");
|
|
4329
|
+
mk.className = "bw-mk " + corner;
|
|
4330
|
+
panel.appendChild(mk);
|
|
4331
|
+
}
|
|
4332
|
+
wrap.appendChild(panel);
|
|
4333
|
+
overlay.appendChild(back);
|
|
4334
|
+
overlay.appendChild(wrap);
|
|
4335
|
+
shadow.appendChild(overlay);
|
|
4336
|
+
(document.body || document.documentElement).appendChild(root);
|
|
4337
|
+
const m = {
|
|
4338
|
+
root,
|
|
4339
|
+
overlay,
|
|
4340
|
+
panel,
|
|
4341
|
+
content,
|
|
4342
|
+
iframe: null,
|
|
4343
|
+
iframeOrigin: "",
|
|
4344
|
+
iframeReady: false,
|
|
4345
|
+
iframeContext: "",
|
|
4346
|
+
onBackdrop: null,
|
|
4347
|
+
onIframeMessage: null,
|
|
4348
|
+
chain: Promise.resolve(),
|
|
4349
|
+
fitObserver: null,
|
|
4350
|
+
generation: 0,
|
|
4351
|
+
onSupersede: null,
|
|
4352
|
+
};
|
|
4353
|
+
// The dismiss handler lives on `.bw-wrap`: it is the full-viewport flex layer
|
|
4354
|
+
// that sits ABOVE `.bw-back`, so it (not the backdrop element) receives clicks
|
|
4355
|
+
// outside the panel. Fire only when the click lands on the wrap itself, never
|
|
4356
|
+
// when it bubbles up from the panel or its content.
|
|
4357
|
+
wrap.addEventListener("click", (event) => {
|
|
4358
|
+
if (event.target === wrap && m.onBackdrop)
|
|
4359
|
+
m.onBackdrop();
|
|
4360
|
+
});
|
|
4361
|
+
window.addEventListener("message", (event) => {
|
|
4362
|
+
if (!m.iframe)
|
|
4235
4363
|
return;
|
|
4236
|
-
|
|
4237
|
-
if (!d || typeof d !== 'object')
|
|
4364
|
+
if (event.origin !== m.iframeOrigin)
|
|
4238
4365
|
return;
|
|
4239
|
-
if (
|
|
4240
|
-
readyResolve === null || readyResolve === void 0 ? void 0 : readyResolve();
|
|
4366
|
+
if (event.source !== m.iframe.contentWindow)
|
|
4241
4367
|
return;
|
|
4242
|
-
|
|
4243
|
-
if (d
|
|
4244
|
-
const settle = pending.get(d.requestId);
|
|
4245
|
-
if (settle) {
|
|
4246
|
-
pending.delete(d.requestId);
|
|
4247
|
-
settle(d);
|
|
4248
|
-
}
|
|
4249
|
-
}
|
|
4250
|
-
};
|
|
4251
|
-
window.addEventListener('message', onMsg);
|
|
4252
|
-
frame.src = url;
|
|
4253
|
-
document.body.appendChild(frame);
|
|
4254
|
-
const dispose = () => {
|
|
4255
|
-
if (disposed)
|
|
4368
|
+
const d = event.data;
|
|
4369
|
+
if (!d || typeof d !== "object")
|
|
4256
4370
|
return;
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
return
|
|
4266
|
-
dispose,
|
|
4267
|
-
request(message, timeoutMs) {
|
|
4268
|
-
const requestId = String(message.requestId || '');
|
|
4269
|
-
if (!requestId)
|
|
4270
|
-
return Promise.reject(new Error('signer frame request needs a requestId'));
|
|
4271
|
-
if (disposed)
|
|
4272
|
-
return Promise.reject(new Error('signer frame disposed'));
|
|
4273
|
-
return new Promise((resolve, reject) => {
|
|
4274
|
-
let timer;
|
|
4275
|
-
pending.set(requestId, (d) => {
|
|
4276
|
-
if (timer)
|
|
4277
|
-
clearTimeout(timer);
|
|
4278
|
-
resolve(d);
|
|
4279
|
-
});
|
|
4280
|
-
timer = setTimeout(() => {
|
|
4281
|
-
pending.delete(requestId);
|
|
4282
|
-
reject(new Error('The Bounded signer did not respond in time.'));
|
|
4283
|
-
}, timeoutMs);
|
|
4284
|
-
ready.then(() => {
|
|
4285
|
-
var _a;
|
|
4286
|
-
if (disposed || !pending.has(requestId))
|
|
4287
|
-
return;
|
|
4288
|
-
try {
|
|
4289
|
-
(_a = frame.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage(message, origin);
|
|
4290
|
-
}
|
|
4291
|
-
catch ( /* frame gone; timeout rejects */_b) { /* frame gone; timeout rejects */ }
|
|
4292
|
-
});
|
|
4293
|
-
});
|
|
4294
|
-
},
|
|
4295
|
-
};
|
|
4296
|
-
}
|
|
4297
|
-
|
|
4298
|
-
// turnkey-signing-capability.ts - the pure AS-02 (DS2-0112) signing-capability logic,
|
|
4299
|
-
// kept import-free (no DOM, no session, no @bounded-sh/core) so it is unit-testable.
|
|
4300
|
-
//
|
|
4301
|
-
// The hosted Turnkey signer refuses to sign unless the request carries an issuer-signed
|
|
4302
|
-
// SIGNING CAPABILITY bound to the app id, the requesting origin, and the EXACT payload
|
|
4303
|
-
// hash. `requestTurnkeySigningCapability` mints one for the given bytes and
|
|
4304
|
-
// `buildTurnkeySignRequest` assembles the signer request that carries it. The bridge
|
|
4305
|
-
// (turnkey-signer-bridge.ts) supplies the base url + id token and routes through both,
|
|
4306
|
-
// so a regression here (a dropped capability, a wrong route, a swallowed error) is
|
|
4307
|
-
// caught without a browser.
|
|
4308
|
-
// POST the Bounded id token + payload hash to the issuer's capability mint route and
|
|
4309
|
-
// return the issuer-signed capability for exactly these bytes. Credential-free: identity
|
|
4310
|
-
// is proven with the id token in the body (not an ambient cookie the cross-origin fetch
|
|
4311
|
-
// would not carry). Throws with the same stable messages the bridge relied on inline.
|
|
4312
|
-
async function requestTurnkeySigningCapability(opts) {
|
|
4313
|
-
const base = opts.baseUrl.replace(/\/$/, '');
|
|
4314
|
-
let res;
|
|
4315
|
-
try {
|
|
4316
|
-
res = await opts.fetchImpl(`${base}/wallet/turnkey/capability`, {
|
|
4317
|
-
method: 'POST',
|
|
4318
|
-
headers: { 'content-type': 'application/json' },
|
|
4319
|
-
body: JSON.stringify({ idToken: opts.idToken, messageHex: opts.messageHex }),
|
|
4320
|
-
});
|
|
4321
|
-
}
|
|
4322
|
-
catch (_a) {
|
|
4323
|
-
throw new Error('Could not reach the Bounded signer to authorize this signature.');
|
|
4324
|
-
}
|
|
4325
|
-
if (!res.ok) {
|
|
4326
|
-
let error = 'signing_capability_denied';
|
|
4327
|
-
try {
|
|
4328
|
-
const d = (await res.json());
|
|
4329
|
-
if (d && typeof d.error === 'string')
|
|
4330
|
-
error = d.error;
|
|
4331
|
-
}
|
|
4332
|
-
catch (_b) {
|
|
4333
|
-
/* keep the default marker */
|
|
4334
|
-
}
|
|
4335
|
-
throw new Error(`Turnkey signing was not authorized (${error}).`);
|
|
4336
|
-
}
|
|
4337
|
-
const d = (await res.json());
|
|
4338
|
-
if (!d || typeof d.capability !== 'string' || !d.capability) {
|
|
4339
|
-
throw new Error('Turnkey signing capability was malformed.');
|
|
4340
|
-
}
|
|
4341
|
-
return d.capability;
|
|
4371
|
+
// Readiness belongs to the document, even while login owns the modal and no
|
|
4372
|
+
// signing exchange is listening. The retained frame will not load again.
|
|
4373
|
+
if (d.type === "bounded:turnkey:ready")
|
|
4374
|
+
m.iframeReady = true;
|
|
4375
|
+
if (m.onIframeMessage)
|
|
4376
|
+
m.onIframeMessage(d);
|
|
4377
|
+
});
|
|
4378
|
+
modal = m;
|
|
4379
|
+
return m;
|
|
4342
4380
|
}
|
|
4343
|
-
|
|
4381
|
+
function showModal(m) {
|
|
4382
|
+
m.overlay.classList.remove("closing");
|
|
4383
|
+
m.overlay.classList.add("mounted");
|
|
4384
|
+
// Force a reflow so the .show transition runs from the hidden state every open.
|
|
4385
|
+
void m.panel.offsetWidth;
|
|
4386
|
+
m.overlay.classList.add("show");
|
|
4387
|
+
}
|
|
4388
|
+
function hideModal(m, after) {
|
|
4389
|
+
m.overlay.classList.remove("show");
|
|
4390
|
+
m.overlay.classList.add("closing");
|
|
4391
|
+
// Capture the generation at hide time. If a newer open re-shows the modal within
|
|
4392
|
+
// the close tween, this stale timer must NOT strip `.mounted`/`.closing` off the
|
|
4393
|
+
// freshly reopened widget - only tear down when we are still the same open.
|
|
4394
|
+
const gen = m.generation;
|
|
4395
|
+
window.setTimeout(() => {
|
|
4396
|
+
if (m.generation === gen)
|
|
4397
|
+
m.overlay.classList.remove("mounted", "closing");
|
|
4398
|
+
}, 260);
|
|
4399
|
+
}
|
|
4400
|
+
function setPanelHeight(m, px) {
|
|
4401
|
+
const h = Math.max(140, Math.min(680, Math.round(px)));
|
|
4402
|
+
m.panel.style.height = h + "px";
|
|
4403
|
+
}
|
|
4404
|
+
// True outer height of the mounted card: the max of its padding-box scroll height
|
|
4405
|
+
// (catches overflowing descendants) and its border-box height plus own vertical
|
|
4406
|
+
// margins (catches border + margins, which scrollHeight omits). Ceiled so a
|
|
4407
|
+
// sub-pixel row is never clipped under `overflow:hidden`.
|
|
4408
|
+
function measureOuterHeight(target) {
|
|
4409
|
+
var _a;
|
|
4410
|
+
const cs = (_a = target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.getComputedStyle(target);
|
|
4411
|
+
const marginY = cs ? parseFloat(cs.marginTop) + parseFloat(cs.marginBottom) : 0;
|
|
4412
|
+
const borderBox = target.offsetHeight + (Number.isFinite(marginY) ? marginY : 0);
|
|
4413
|
+
const rect = target.getBoundingClientRect().height;
|
|
4414
|
+
return Math.ceil(Math.max(target.scrollHeight, borderBox, rect));
|
|
4415
|
+
}
|
|
4416
|
+
/**
|
|
4417
|
+
* Measure the currently mounted native content and tween the panel to fit it.
|
|
4418
|
+
* Re-fits automatically when the card's intrinsic height changes - notably after
|
|
4419
|
+
* the Newsreader/Schibsted web fonts swap in (which grows line boxes a few px
|
|
4420
|
+
* after first paint) and on inline view swaps (email -> OTP code -> success).
|
|
4421
|
+
*/
|
|
4422
|
+
function fitPanelToContent(m) {
|
|
4423
|
+
var _a, _b;
|
|
4424
|
+
const first = m.content.firstElementChild;
|
|
4425
|
+
const target = first || m.content;
|
|
4426
|
+
const apply = () => {
|
|
4427
|
+
// Only fit while this exact node is still the mounted card.
|
|
4428
|
+
if (m.content.firstElementChild !== first && first)
|
|
4429
|
+
return;
|
|
4430
|
+
const h = measureOuterHeight(target);
|
|
4431
|
+
if (h > 0)
|
|
4432
|
+
setPanelHeight(m, h);
|
|
4433
|
+
};
|
|
4434
|
+
apply();
|
|
4435
|
+
const view = target.ownerDocument.defaultView;
|
|
4436
|
+
// Re-fit after fonts load (one-shot) and once the ResizeObserver-less browsers
|
|
4437
|
+
// have painted, so the height never lags a late reflow.
|
|
4438
|
+
(_a = view === null || view === void 0 ? void 0 : view.requestAnimationFrame) === null || _a === void 0 ? void 0 : _a.call(view, apply);
|
|
4439
|
+
const fonts = target.ownerDocument.fonts;
|
|
4440
|
+
if (fonts === null || fonts === void 0 ? void 0 : fonts.ready)
|
|
4441
|
+
fonts.ready.then(apply).catch(() => { });
|
|
4442
|
+
(_b = m.fitObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
4443
|
+
m.fitObserver = null;
|
|
4444
|
+
if (first && typeof (view === null || view === void 0 ? void 0 : view.ResizeObserver) === "function") {
|
|
4445
|
+
const ro = new view.ResizeObserver(() => apply());
|
|
4446
|
+
ro.observe(first);
|
|
4447
|
+
m.fitObserver = ro;
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
/** Switch the modal into NATIVE-content mode: detach any iframe, mount `node`. */
|
|
4451
|
+
function mountContent(m, node) {
|
|
4452
|
+
var _a;
|
|
4453
|
+
detachIframe(m);
|
|
4454
|
+
(_a = m.fitObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
4455
|
+
m.fitObserver = null;
|
|
4456
|
+
m.content.replaceChildren(node);
|
|
4457
|
+
m.content.style.display = "block";
|
|
4458
|
+
}
|
|
4459
|
+
/** Get (or lazily create) the warm iframe for signer/hosted-page mode. */
|
|
4460
|
+
function ensureIframe(m, url, origin, context = "") {
|
|
4461
|
+
var _a;
|
|
4462
|
+
if (m.iframe && m.iframeOrigin === origin && m.iframe.src === url && m.iframeContext === context)
|
|
4463
|
+
return m.iframe;
|
|
4464
|
+
detachIframe(m);
|
|
4465
|
+
(_a = m.fitObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
4466
|
+
m.fitObserver = null;
|
|
4467
|
+
m.content.style.display = "none";
|
|
4468
|
+
const iframe = document.createElement("iframe");
|
|
4469
|
+
iframe.className = "bw-frame";
|
|
4470
|
+
iframe.setAttribute("title", "Bounded");
|
|
4471
|
+
iframe.setAttribute("allow", "clipboard-write");
|
|
4472
|
+
iframe.src = url;
|
|
4473
|
+
m.iframeOrigin = origin;
|
|
4474
|
+
m.iframeContext = context;
|
|
4475
|
+
m.iframeReady = false;
|
|
4476
|
+
m.panel.appendChild(iframe);
|
|
4477
|
+
m.iframe = iframe;
|
|
4478
|
+
return iframe;
|
|
4479
|
+
}
|
|
4480
|
+
/**
|
|
4481
|
+
* Destroy the warm signer iframe if one is currently mounted for `origin`, and report
|
|
4482
|
+
* whether there was one. Logout needs this: a signing session can live in that document's
|
|
4483
|
+
* MEMORY (the signer keeps an in-memory mirror when the browser blocks its storage), and a
|
|
4484
|
+
* throwaway iframe opened by the sweep can only ever clear the shared store, never another
|
|
4485
|
+
* document's memory. Removing the iframe discards its browsing context, which takes the
|
|
4486
|
+
* mirror with it.
|
|
4487
|
+
*
|
|
4488
|
+
* Deliberately does NOT call getModal(): that would BUILD a modal just to tear it down, on
|
|
4489
|
+
* every logout of every app, most of which never mounted one.
|
|
4490
|
+
*/
|
|
4491
|
+
function detachWarmSignerIframe(origin) {
|
|
4492
|
+
if (!modal || !modal.iframe || (origin))
|
|
4493
|
+
return false;
|
|
4494
|
+
detachIframe(modal);
|
|
4495
|
+
return true;
|
|
4496
|
+
}
|
|
4497
|
+
/** A removed frame cannot still supply its in-memory signing session. */
|
|
4498
|
+
function hasWarmSignerFrame(context) {
|
|
4499
|
+
var _a;
|
|
4500
|
+
return Boolean(((_a = modal === null || modal === void 0 ? void 0 : modal.iframe) === null || _a === void 0 ? void 0 : _a.isConnected) && modal.iframeContext === context);
|
|
4501
|
+
}
|
|
4502
|
+
function detachIframe(m) {
|
|
4503
|
+
if (m.iframe) {
|
|
4504
|
+
try {
|
|
4505
|
+
m.iframe.remove();
|
|
4506
|
+
}
|
|
4507
|
+
catch (_a) {
|
|
4508
|
+
/* ignore */
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
m.iframe = null;
|
|
4512
|
+
m.iframeReady = false;
|
|
4513
|
+
m.iframeOrigin = "";
|
|
4514
|
+
m.iframeContext = "";
|
|
4515
|
+
m.onIframeMessage = null;
|
|
4516
|
+
}
|
|
4517
|
+
// Load the Bounded editorial typefaces (Newsreader display + Schibsted Grotesk body +
|
|
4518
|
+
// JetBrains Mono) once at the document level so they apply inside the shadow root. A
|
|
4519
|
+
// no-op if already present or off-DOM. Fonts loaded here match the hosted auth page.
|
|
4520
|
+
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";
|
|
4521
|
+
function ensureFonts() {
|
|
4522
|
+
if (!hasDOM$5())
|
|
4523
|
+
return;
|
|
4524
|
+
if (document.querySelector('link[data-bounded-fonts="1"]'))
|
|
4525
|
+
return;
|
|
4526
|
+
const pre1 = document.createElement("link");
|
|
4527
|
+
pre1.rel = "preconnect";
|
|
4528
|
+
pre1.href = "https://fonts.googleapis.com";
|
|
4529
|
+
const pre2 = document.createElement("link");
|
|
4530
|
+
pre2.rel = "preconnect";
|
|
4531
|
+
pre2.href = "https://fonts.gstatic.com";
|
|
4532
|
+
pre2.crossOrigin = "anonymous";
|
|
4533
|
+
const link = document.createElement("link");
|
|
4534
|
+
link.rel = "stylesheet";
|
|
4535
|
+
link.href = FONTS_HREF;
|
|
4536
|
+
link.setAttribute("data-bounded-fonts", "1");
|
|
4537
|
+
document.head.append(pre1, pre2, link);
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
// turnkey-wallet-widget.ts - runs one Turnkey signer exchange inside the shared
|
|
4541
|
+
// in-app WIDGET (bounded-widget-modal.ts). The Bounded-owned signer page is loaded
|
|
4542
|
+
// in a warm iframe behind the modal so signing feels native: an in-app approve
|
|
4543
|
+
// card (with inline code entry when the 24h signing session must first be
|
|
4544
|
+
// established), never a jarring popup.
|
|
4545
|
+
//
|
|
4546
|
+
// WHY AN IFRAME: the wallet's signing session lives in the single Bounded signer
|
|
4547
|
+
// origin's storage, out of every app's reach, so signing MUST run on that origin;
|
|
4548
|
+
// the iframe brings it in-app.
|
|
4549
|
+
//
|
|
4550
|
+
// Restricted embedders: if the frame never loads, the SAME request reruns through
|
|
4551
|
+
// `popupFallback` (a real window). Callers pass a popup implementation and never
|
|
4552
|
+
// see the difference.
|
|
4553
|
+
/**
|
|
4554
|
+
* Run one signer exchange through the in-app widget. Resolves when `onResult`
|
|
4555
|
+
* accepts a result; falls back to `popupFallback` if the frame cannot be mounted
|
|
4556
|
+
* or never loads.
|
|
4557
|
+
*/
|
|
4558
|
+
async function runWidget(url, signerOrigin, ex, popupFallback) {
|
|
4559
|
+
if (!hasDOM$5())
|
|
4560
|
+
return popupFallback();
|
|
4561
|
+
const m = getModal();
|
|
4562
|
+
const requestId = String(ex.request.requestId);
|
|
4563
|
+
// Serialize: the approve card is modal, and one warm frame handles one request at a time.
|
|
4564
|
+
const run = m.chain.then(() => new Promise((resolve, reject) => {
|
|
4565
|
+
var _a;
|
|
4566
|
+
let settled = false;
|
|
4567
|
+
let usedFallback = false;
|
|
4568
|
+
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4569
|
+
const iframe = ensureIframe(m, url, signerOrigin, ex.context);
|
|
4570
|
+
const send = () => {
|
|
4571
|
+
var _a;
|
|
4572
|
+
if (!iframe.contentWindow)
|
|
4573
|
+
return;
|
|
4574
|
+
try {
|
|
4575
|
+
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4576
|
+
iframe.contentWindow.postMessage(ex.request, signerOrigin);
|
|
4577
|
+
}
|
|
4578
|
+
catch (_b) {
|
|
4579
|
+
finish(() => reject(new Error('wallet_session_changed')));
|
|
4580
|
+
}
|
|
4581
|
+
};
|
|
4582
|
+
let loadTimer = 0;
|
|
4583
|
+
let ceilingTimer = 0;
|
|
4584
|
+
const finish = (fn) => {
|
|
4585
|
+
if (settled)
|
|
4586
|
+
return;
|
|
4587
|
+
settled = true;
|
|
4588
|
+
m.onIframeMessage = null;
|
|
4589
|
+
m.onBackdrop = null;
|
|
4590
|
+
window.clearTimeout(loadTimer);
|
|
4591
|
+
window.clearTimeout(ceilingTimer);
|
|
4592
|
+
fn();
|
|
4593
|
+
};
|
|
4594
|
+
// The frame is WARM across exchanges, so an abandoned approval must be told to drop its
|
|
4595
|
+
// in-flight ceremony. Otherwise the signer keeps `approving` set for a card nobody is
|
|
4596
|
+
// looking at and refuses every later create/sign as `signer_request_superseded` (the
|
|
4597
|
+
// BSA-2026-012 guard) until the page reloads.
|
|
4598
|
+
const cancelInFrame = () => {
|
|
4599
|
+
if (!iframe.contentWindow)
|
|
4600
|
+
return;
|
|
4601
|
+
try {
|
|
4602
|
+
iframe.contentWindow.postMessage({ type: "bounded:turnkey:cancel", requestId }, signerOrigin);
|
|
4603
|
+
}
|
|
4604
|
+
catch (_a) {
|
|
4605
|
+
/* frame gone */
|
|
4606
|
+
}
|
|
4607
|
+
};
|
|
4608
|
+
const goFallback = () => {
|
|
4609
|
+
if (usedFallback || settled)
|
|
4610
|
+
return;
|
|
4611
|
+
usedFallback = true;
|
|
4612
|
+
hideModal(m);
|
|
4613
|
+
finish(() => popupFallback().then(resolve, reject));
|
|
4614
|
+
};
|
|
4615
|
+
m.onBackdrop = () => {
|
|
4616
|
+
cancelInFrame();
|
|
4617
|
+
hideModal(m);
|
|
4618
|
+
finish(() => reject(new Error("cancelled")));
|
|
4619
|
+
};
|
|
4620
|
+
m.onIframeMessage = (d) => {
|
|
4621
|
+
if (d.type === "bounded:turnkey:ready") {
|
|
4622
|
+
send();
|
|
4623
|
+
return;
|
|
4624
|
+
}
|
|
4625
|
+
if (d.type === "bounded:turnkey:resize" && typeof d.height === "number") {
|
|
4626
|
+
setPanelHeight(m, d.height);
|
|
4627
|
+
return;
|
|
4628
|
+
}
|
|
4629
|
+
if (d.type === "bounded:turnkey:result" && d.requestId === requestId) {
|
|
4630
|
+
const outcome = ex.onResult(d);
|
|
4631
|
+
if ("error" in outcome) {
|
|
4632
|
+
hideModal(m);
|
|
4633
|
+
finish(() => reject(new Error(outcome.error)));
|
|
4634
|
+
return;
|
|
4635
|
+
}
|
|
4636
|
+
if (outcome.done) {
|
|
4637
|
+
hideModal(m);
|
|
4638
|
+
finish(() => resolve(outcome.value));
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
};
|
|
4642
|
+
showModal(m);
|
|
4643
|
+
if (m.iframeReady)
|
|
4644
|
+
send();
|
|
4645
|
+
// If the frame never loads, degrade to the popup (best-effort - a post-load
|
|
4646
|
+
// popup may be blocked without a gesture, in which case the fallback rejects).
|
|
4647
|
+
loadTimer = window.setTimeout(() => {
|
|
4648
|
+
if (!m.iframeReady && !settled)
|
|
4649
|
+
goFallback();
|
|
4650
|
+
}, 8000);
|
|
4651
|
+
// Absolute ceiling so a stuck flow cannot hang forever.
|
|
4652
|
+
ceilingTimer = window.setTimeout(() => {
|
|
4653
|
+
if (!settled) {
|
|
4654
|
+
cancelInFrame();
|
|
4655
|
+
hideModal(m);
|
|
4656
|
+
finish(() => reject(new Error("Wallet approval timed out.")));
|
|
4657
|
+
}
|
|
4658
|
+
}, 5 * 60 * 1000);
|
|
4659
|
+
}));
|
|
4660
|
+
m.chain = run.then(() => undefined, () => undefined);
|
|
4661
|
+
return run;
|
|
4662
|
+
}
|
|
4663
|
+
|
|
4664
|
+
// turnkey-signer-frame.ts - a HIDDEN, offscreen iframe of the Bounded signer page
|
|
4665
|
+
// for UI-less signer exchanges: sealing a login code (bounded:turnkey:encryptOtp),
|
|
4666
|
+
// establishing the 24h signing session right after login
|
|
4667
|
+
// (bounded:turnkey:establishSigningSession), and destroying it on logout
|
|
4668
|
+
// (bounded:turnkey:clearSigningSession).
|
|
4669
|
+
//
|
|
4670
|
+
// One handle = one live frame. The frame is kept across requests so multi-step
|
|
4671
|
+
// exchanges (seal a code, then establish the session with the keypair the SEAL
|
|
4672
|
+
// left in that exact page instance) talk to the same page. Origin + source +
|
|
4673
|
+
// requestId are all pinned; dispose() tears everything down.
|
|
4674
|
+
function hasDOM$4() {
|
|
4675
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
4676
|
+
}
|
|
4677
|
+
async function signerPage() {
|
|
4678
|
+
const cfg = await core.getConfig();
|
|
4679
|
+
const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
4680
|
+
return { url: `${base}/wallet/turnkey/signer`, origin: new URL(base).origin };
|
|
4681
|
+
}
|
|
4682
|
+
/** Mount the hidden signer frame. Callers MUST dispose() when done. */
|
|
4683
|
+
async function openHiddenSignerFrame(page) {
|
|
4684
|
+
if (!hasDOM$4())
|
|
4685
|
+
throw new Error('The Bounded signer frame is only available in the browser.');
|
|
4686
|
+
const { url, origin } = page !== null && page !== void 0 ? page : await signerPage();
|
|
4687
|
+
const frame = document.createElement('iframe');
|
|
4688
|
+
frame.setAttribute('aria-hidden', 'true');
|
|
4689
|
+
frame.style.cssText =
|
|
4690
|
+
'position:fixed;width:1px;height:1px;left:-9999px;top:-9999px;border:0;opacity:0;pointer-events:none';
|
|
4691
|
+
let disposed = false;
|
|
4692
|
+
let readyResolve = null;
|
|
4693
|
+
const ready = new Promise((resolve) => { readyResolve = resolve; });
|
|
4694
|
+
const pending = new Map();
|
|
4695
|
+
const onMsg = (event) => {
|
|
4696
|
+
if (event.origin !== origin || event.source !== frame.contentWindow)
|
|
4697
|
+
return;
|
|
4698
|
+
const d = event.data;
|
|
4699
|
+
if (!d || typeof d !== 'object')
|
|
4700
|
+
return;
|
|
4701
|
+
if (d.type === 'bounded:turnkey:ready') {
|
|
4702
|
+
readyResolve === null || readyResolve === void 0 ? void 0 : readyResolve();
|
|
4703
|
+
return;
|
|
4704
|
+
}
|
|
4705
|
+
if (d.type === 'bounded:turnkey:result' && typeof d.requestId === 'string') {
|
|
4706
|
+
const settle = pending.get(d.requestId);
|
|
4707
|
+
if (settle) {
|
|
4708
|
+
pending.delete(d.requestId);
|
|
4709
|
+
settle(d);
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
};
|
|
4713
|
+
window.addEventListener('message', onMsg);
|
|
4714
|
+
frame.src = url;
|
|
4715
|
+
document.body.appendChild(frame);
|
|
4716
|
+
const dispose = () => {
|
|
4717
|
+
if (disposed)
|
|
4718
|
+
return;
|
|
4719
|
+
disposed = true;
|
|
4720
|
+
window.removeEventListener('message', onMsg);
|
|
4721
|
+
pending.clear();
|
|
4722
|
+
try {
|
|
4723
|
+
frame.remove();
|
|
4724
|
+
}
|
|
4725
|
+
catch ( /* already gone */_a) { /* already gone */ }
|
|
4726
|
+
};
|
|
4727
|
+
return {
|
|
4728
|
+
dispose,
|
|
4729
|
+
request(message, timeoutMs) {
|
|
4730
|
+
const requestId = String(message.requestId || '');
|
|
4731
|
+
if (!requestId)
|
|
4732
|
+
return Promise.reject(new Error('signer frame request needs a requestId'));
|
|
4733
|
+
if (disposed)
|
|
4734
|
+
return Promise.reject(new Error('signer frame disposed'));
|
|
4735
|
+
return new Promise((resolve, reject) => {
|
|
4736
|
+
let timer;
|
|
4737
|
+
pending.set(requestId, (d) => {
|
|
4738
|
+
if (timer)
|
|
4739
|
+
clearTimeout(timer);
|
|
4740
|
+
resolve(d);
|
|
4741
|
+
});
|
|
4742
|
+
timer = setTimeout(() => {
|
|
4743
|
+
pending.delete(requestId);
|
|
4744
|
+
reject(new Error('The Bounded signer did not respond in time.'));
|
|
4745
|
+
}, timeoutMs);
|
|
4746
|
+
ready.then(() => {
|
|
4747
|
+
var _a;
|
|
4748
|
+
if (disposed || !pending.has(requestId))
|
|
4749
|
+
return;
|
|
4750
|
+
try {
|
|
4751
|
+
(_a = frame.contentWindow) === null || _a === void 0 ? void 0 : _a.postMessage(message, origin);
|
|
4752
|
+
}
|
|
4753
|
+
catch ( /* frame gone; timeout rejects */_b) { /* frame gone; timeout rejects */ }
|
|
4754
|
+
});
|
|
4755
|
+
});
|
|
4756
|
+
},
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
|
|
4760
|
+
// turnkey-signing-capability.ts - the pure AS-02 (DS2-0112) signing-capability logic,
|
|
4761
|
+
// kept import-free (no DOM, no session, no @bounded-sh/core) so it is unit-testable.
|
|
4762
|
+
//
|
|
4763
|
+
// The hosted Turnkey signer refuses to sign unless the request carries an issuer-signed
|
|
4764
|
+
// SIGNING CAPABILITY bound to the app id, the requesting origin, and the EXACT payload
|
|
4765
|
+
// hash. `requestTurnkeySigningCapability` mints one for the given bytes and
|
|
4766
|
+
// `buildTurnkeySignRequest` assembles the signer request that carries it. The bridge
|
|
4767
|
+
// (turnkey-signer-bridge.ts) supplies the base url + id token and routes through both,
|
|
4768
|
+
// so a regression here (a dropped capability, a wrong route, a swallowed error) is
|
|
4769
|
+
// caught without a browser.
|
|
4770
|
+
// POST the Bounded id token + payload hash to the issuer's capability mint route and
|
|
4771
|
+
// return the issuer-signed capability for exactly these bytes. Credential-free: identity
|
|
4772
|
+
// is proven with the id token in the body (not an ambient cookie the cross-origin fetch
|
|
4773
|
+
// would not carry). Throws with the same stable messages the bridge relied on inline.
|
|
4774
|
+
async function requestTurnkeySigningCapability(opts) {
|
|
4775
|
+
const base = opts.baseUrl.replace(/\/$/, '');
|
|
4776
|
+
let res;
|
|
4777
|
+
try {
|
|
4778
|
+
res = await opts.fetchImpl(`${base}/wallet/turnkey/capability`, {
|
|
4779
|
+
method: 'POST',
|
|
4780
|
+
headers: { 'content-type': 'application/json' },
|
|
4781
|
+
body: JSON.stringify({ idToken: opts.idToken, messageHex: opts.messageHex, walletProtocol: 2 }),
|
|
4782
|
+
});
|
|
4783
|
+
}
|
|
4784
|
+
catch (_a) {
|
|
4785
|
+
throw new Error('Could not reach the Bounded signer to authorize this signature.');
|
|
4786
|
+
}
|
|
4787
|
+
if (!res.ok) {
|
|
4788
|
+
let error = 'signing_capability_denied';
|
|
4789
|
+
try {
|
|
4790
|
+
const d = (await res.json());
|
|
4791
|
+
if (d && typeof d.error === 'string')
|
|
4792
|
+
error = d.error;
|
|
4793
|
+
}
|
|
4794
|
+
catch (_b) {
|
|
4795
|
+
/* keep the default marker */
|
|
4796
|
+
}
|
|
4797
|
+
throw new Error(`Turnkey signing was not authorized (${error}).`);
|
|
4798
|
+
}
|
|
4799
|
+
const d = (await res.json());
|
|
4800
|
+
if (!d || typeof d.capability !== 'string' || !d.capability) {
|
|
4801
|
+
throw new Error('Turnkey signing capability was malformed.');
|
|
4802
|
+
}
|
|
4803
|
+
return d.capability;
|
|
4804
|
+
}
|
|
4805
|
+
// Assemble the `bounded:turnkey:sign` postMessage request, carrying the issuer-signed
|
|
4344
4806
|
// capability for the same bytes. Without the capability the hosted signer refuses
|
|
4345
4807
|
// (capability_required), so it is a required field of every sign request.
|
|
4346
4808
|
// `authorization` is the issuer-signed createWallet action authorization: inline
|
|
@@ -4368,7 +4830,7 @@ async function requestTurnkeyActionAuthorization(opts) {
|
|
|
4368
4830
|
res = await opts.fetchImpl(`${base}/wallet/turnkey/action-authorization`, {
|
|
4369
4831
|
method: 'POST',
|
|
4370
4832
|
headers: { 'content-type': 'application/json' },
|
|
4371
|
-
body: JSON.stringify({ idToken: opts.idToken, action: opts.action }),
|
|
4833
|
+
body: JSON.stringify({ idToken: opts.idToken, action: opts.action, walletProtocol: 2 }),
|
|
4372
4834
|
});
|
|
4373
4835
|
}
|
|
4374
4836
|
catch (_a) {
|
|
@@ -4420,26 +4882,46 @@ function buildTurnkeyActionRequest(opts) {
|
|
|
4420
4882
|
// code is the sole signing authority; within a live session every signature is one
|
|
4421
4883
|
// approval click). The app retains the transaction itself.
|
|
4422
4884
|
//
|
|
4423
|
-
//
|
|
4424
|
-
//
|
|
4425
|
-
|
|
4426
|
-
// across every app.
|
|
4427
|
-
function hasDOM$4() {
|
|
4885
|
+
// Signing keys remain on the trusted signer origin. New apps use independently
|
|
4886
|
+
// bound wallets and storage slots; legacy apps retain their shared wallet.
|
|
4887
|
+
function hasDOM$3() {
|
|
4428
4888
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
4429
4889
|
}
|
|
4430
|
-
//
|
|
4431
|
-
//
|
|
4432
|
-
//
|
|
4433
|
-
//
|
|
4434
|
-
//
|
|
4435
|
-
// never silently fall back to an iframe.
|
|
4890
|
+
// Dispatch one signer exchange through the in-app WIDGET, degrading to the POPUP
|
|
4891
|
+
// when we cannot embed (no DOM, or the frame never mounts/loads). Nothing in the
|
|
4892
|
+
// email-session flow needs a popup by preference: the code entry and the approval
|
|
4893
|
+
// click both run fine inside a cross-origin iframe on every engine (the old
|
|
4894
|
+
// Safari popup preference existed only for in-iframe WebAuthn CREATE).
|
|
4436
4895
|
async function deliver(url, origin, ex) {
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4896
|
+
var _a, _b;
|
|
4897
|
+
const popup = () => runPopup(url, origin, ex);
|
|
4898
|
+
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4899
|
+
const result = await runWidget(url, origin, { request: ex.request, onResult: ex.onResult, context: ex.context, assertCurrent: ex.assertCurrent }, popup);
|
|
4900
|
+
(_b = ex.assertCurrent) === null || _b === void 0 ? void 0 : _b.call(ex);
|
|
4901
|
+
return result;
|
|
4902
|
+
}
|
|
4903
|
+
async function turnkeySignerPage(token) {
|
|
4904
|
+
const assertGeneration = walletOperationGuard();
|
|
4905
|
+
const idToken = token === undefined ? core.getActiveSessionManager().getIdToken() : token;
|
|
4440
4906
|
const cfg = await core.getConfig();
|
|
4441
4907
|
const base = (cfg.humanAuthApiUrl || 'https://auth.bounded.sh').replace(/\/$/, '');
|
|
4442
|
-
|
|
4908
|
+
const context = walletContextFromClaims(idToken ? decodeJwt$1(idToken) : {}, base, cfg.appId, window.location.origin);
|
|
4909
|
+
return Object.assign(Object.assign({}, context), { idToken: idToken || '', assertCurrent() {
|
|
4910
|
+
assertGeneration();
|
|
4911
|
+
const current = core.getActiveSessionManager().getIdToken();
|
|
4912
|
+
if (!current)
|
|
4913
|
+
throw new Error('wallet_session_changed');
|
|
4914
|
+
const next = walletContextFromClaims(decodeJwt$1(current), base, cfg.appId, window.location.origin);
|
|
4915
|
+
if (next.key !== context.key)
|
|
4916
|
+
throw new Error('wallet_session_changed');
|
|
4917
|
+
} });
|
|
4918
|
+
}
|
|
4919
|
+
async function getTurnkeyWalletSessionState() {
|
|
4920
|
+
const context = await turnkeySignerPage();
|
|
4921
|
+
context.assertCurrent();
|
|
4922
|
+
if (!hasWarmSignerFrame(context.key))
|
|
4923
|
+
return { status: 'verification-required' };
|
|
4924
|
+
return walletSessionState(context.key);
|
|
4443
4925
|
}
|
|
4444
4926
|
function toHex(bytes) {
|
|
4445
4927
|
let h = '';
|
|
@@ -4495,7 +4977,7 @@ async function runPopup(url, signerOrigin, ex) {
|
|
|
4495
4977
|
const top = window.screenY + Math.max(0, (window.outerHeight - h) / 2);
|
|
4496
4978
|
const popup = window.open(url, 'bounded-turnkey-signer', `width=${w},height=${h},left=${left},top=${top}`);
|
|
4497
4979
|
if (!popup) {
|
|
4498
|
-
throw new Error('
|
|
4980
|
+
throw new Error('Popup blocked - call this from a user gesture (e.g. a click handler) so the wallet approval window can open.');
|
|
4499
4981
|
}
|
|
4500
4982
|
return await new Promise((resolve, reject) => {
|
|
4501
4983
|
let settled = false;
|
|
@@ -4511,11 +4993,12 @@ async function runPopup(url, signerOrigin, ex) {
|
|
|
4511
4993
|
let closeTimer;
|
|
4512
4994
|
const cleanup = () => { settled = true; window.removeEventListener('message', onMsg); if (closeTimer)
|
|
4513
4995
|
clearInterval(closeTimer); };
|
|
4514
|
-
const sendRequest = () => { if (settled || acked)
|
|
4996
|
+
const sendRequest = () => { var _a; if (settled || acked)
|
|
4515
4997
|
return; try {
|
|
4998
|
+
(_a = ex.assertCurrent) === null || _a === void 0 ? void 0 : _a.call(ex);
|
|
4516
4999
|
popup.postMessage(ex.request, signerOrigin);
|
|
4517
5000
|
}
|
|
4518
|
-
catch ( /* popup gone */
|
|
5001
|
+
catch ( /* popup gone */_b) { /* popup gone */ } };
|
|
4519
5002
|
const onMsg = (event) => {
|
|
4520
5003
|
if (event.origin !== signerOrigin)
|
|
4521
5004
|
return;
|
|
@@ -4585,16 +5068,9 @@ async function runPopup(url, signerOrigin, ex) {
|
|
|
4585
5068
|
// identity with the Bounded id token WE minted (a body secret, RS256-verified server-side), not
|
|
4586
5069
|
// an ambient cookie - the issuer's session cookie is SameSite-scoped to its own origin and is
|
|
4587
5070
|
// not sent on this cross-origin fetch, so the token is what carries the session's wallet.
|
|
4588
|
-
async function fetchSigningCapability(messageHex) {
|
|
4589
|
-
|
|
4590
|
-
const base
|
|
4591
|
-
let idToken = null;
|
|
4592
|
-
try {
|
|
4593
|
-
idToken = core.getActiveSessionManager().getIdToken();
|
|
4594
|
-
}
|
|
4595
|
-
catch (_a) {
|
|
4596
|
-
idToken = null;
|
|
4597
|
-
}
|
|
5071
|
+
async function fetchSigningCapability(messageHex, context) {
|
|
5072
|
+
context.assertCurrent();
|
|
5073
|
+
const { base, idToken } = context;
|
|
4598
5074
|
if (!idToken) {
|
|
4599
5075
|
throw new Error('This session has no Turnkey wallet. Sign in again to provision one.');
|
|
4600
5076
|
}
|
|
@@ -4609,16 +5085,9 @@ async function fetchSigningCapability(messageHex) {
|
|
|
4609
5085
|
// requires an issuer-signed authorization bound to this app origin and exact action. Fetch it
|
|
4610
5086
|
// before posting to the signer. Walletless sessions are valid for createWallet because that is
|
|
4611
5087
|
// the operation that provisions their first wallet.
|
|
4612
|
-
async function fetchActionAuthorization(action) {
|
|
4613
|
-
|
|
4614
|
-
const base
|
|
4615
|
-
let idToken = null;
|
|
4616
|
-
try {
|
|
4617
|
-
idToken = core.getActiveSessionManager().getIdToken();
|
|
4618
|
-
}
|
|
4619
|
-
catch (_a) {
|
|
4620
|
-
idToken = null;
|
|
4621
|
-
}
|
|
5088
|
+
async function fetchActionAuthorization(action, context) {
|
|
5089
|
+
context.assertCurrent();
|
|
5090
|
+
const { base, idToken } = context;
|
|
4622
5091
|
if (!idToken) {
|
|
4623
5092
|
throw new Error('Sign in again to set up your Turnkey wallet.');
|
|
4624
5093
|
}
|
|
@@ -4638,31 +5107,23 @@ async function fetchActionAuthorization(action) {
|
|
|
4638
5107
|
* an unswept session is account-bound and unusable without a live authorization,
|
|
4639
5108
|
* it just costs its owner nothing and a stranger nothing.
|
|
4640
5109
|
*/
|
|
4641
|
-
async function clearTurnkeySigningSession() {
|
|
4642
|
-
if (!hasDOM$
|
|
5110
|
+
async function clearTurnkeySigningSession(idToken) {
|
|
5111
|
+
if (!hasDOM$3())
|
|
4643
5112
|
return;
|
|
4644
|
-
//
|
|
4645
|
-
//
|
|
4646
|
-
|
|
4647
|
-
//
|
|
4648
|
-
|
|
4649
|
-
// between signatures, so the mirror outlives logout. A throwaway frame cannot see
|
|
4650
|
-
// another document's memory - the only way to take it is to destroy that document.
|
|
4651
|
-
// Done first, and synchronously, so it lands even if the frame exchange below fails.
|
|
4652
|
-
try {
|
|
4653
|
-
const { origin } = await turnkeySignerPage();
|
|
4654
|
-
(await Promise.resolve().then(function () { return boundedWidgetModal; })).detachWarmSignerIframe(origin);
|
|
4655
|
-
}
|
|
4656
|
-
catch (_a) {
|
|
4657
|
-
/* no modal module / no warm frame: nothing to take */
|
|
4658
|
-
}
|
|
5113
|
+
// Destroy the trusted in-memory key even if configuration or token parsing fails.
|
|
5114
|
+
// Do it synchronously, before a different login can mount a replacement frame.
|
|
5115
|
+
detachWarmSignerIframe();
|
|
5116
|
+
// Capture the session before asynchronous teardown clears the SDK's token.
|
|
5117
|
+
const contextPromise = turnkeySignerPage(idToken);
|
|
4659
5118
|
let frame = null;
|
|
4660
5119
|
try {
|
|
4661
|
-
|
|
5120
|
+
const context = await contextPromise;
|
|
5121
|
+
setWalletSessionState(context.key, { status: 'verification-required', reason: 'logged_out' });
|
|
5122
|
+
frame = await openHiddenSignerFrame(context);
|
|
4662
5123
|
await frame.request({ type: 'bounded:turnkey:clearSigningSession', requestId: `tkclear_${Date.now()}_${++requestSeq}` }, 4000);
|
|
4663
5124
|
}
|
|
4664
|
-
catch (
|
|
4665
|
-
|
|
5125
|
+
catch (_a) {
|
|
5126
|
+
// The warm frame was destroyed; the bounded store sweep is best effort.
|
|
4666
5127
|
}
|
|
4667
5128
|
finally {
|
|
4668
5129
|
frame === null || frame === void 0 ? void 0 : frame.dispose();
|
|
@@ -4680,12 +5141,14 @@ function hasTurnkeyWallet() {
|
|
|
4680
5141
|
* Use this to learn the fee-payer address BEFORE building a transaction to sign.
|
|
4681
5142
|
*/
|
|
4682
5143
|
async function getOrCreateTurnkeyWallet() {
|
|
4683
|
-
if (!hasDOM$
|
|
4684
|
-
throw new Error('Turnkey embedded wallets are only available in the browser (
|
|
4685
|
-
const
|
|
5144
|
+
if (!hasDOM$3())
|
|
5145
|
+
throw new Error('Turnkey embedded wallets are only available in the browser (trusted wallet UI).');
|
|
5146
|
+
const context = await turnkeySignerPage();
|
|
5147
|
+
const { url, origin } = context;
|
|
4686
5148
|
const requestId = `tkcreate_${Date.now()}_${++requestSeq}`;
|
|
4687
|
-
const authorization = await fetchActionAuthorization('createWallet');
|
|
5149
|
+
const authorization = await fetchActionAuthorization('createWallet', context);
|
|
4688
5150
|
return await deliver(url, origin, {
|
|
5151
|
+
context: context.key, assertCurrent: context.assertCurrent,
|
|
4689
5152
|
request: buildTurnkeyActionRequest({ requestId, action: 'createWallet', authorization }),
|
|
4690
5153
|
onResult: (d) => {
|
|
4691
5154
|
if (d.ok && typeof d.address === 'string')
|
|
@@ -4705,7 +5168,7 @@ async function getOrCreateTurnkeyWallet() {
|
|
|
4705
5168
|
* that tab - it never transits Bounded.
|
|
4706
5169
|
*/
|
|
4707
5170
|
async function openTurnkeyKeyExport() {
|
|
4708
|
-
if (!hasDOM$
|
|
5171
|
+
if (!hasDOM$3())
|
|
4709
5172
|
throw new Error('Turnkey key export is only available in the browser.');
|
|
4710
5173
|
if (!hasTurnkeyWallet())
|
|
4711
5174
|
throw new Error('This session has no Turnkey wallet to export.');
|
|
@@ -4726,10 +5189,11 @@ async function openTurnkeyKeyExport() {
|
|
|
4726
5189
|
}
|
|
4727
5190
|
catch ( /* older engines: the tab still cannot read us cross-origin */_a) { /* older engines: the tab still cannot read us cross-origin */ }
|
|
4728
5191
|
try {
|
|
4729
|
-
const
|
|
4730
|
-
const
|
|
4731
|
-
|
|
4732
|
-
|
|
5192
|
+
const context = await turnkeySignerPage();
|
|
5193
|
+
const authorization = await fetchActionAuthorization('exportWallet', context);
|
|
5194
|
+
context.assertCurrent();
|
|
5195
|
+
const path = context.scope === 'app' ? `/wallet/turnkey/app/${context.appId}` : '/wallet/turnkey';
|
|
5196
|
+
handle.location.href = `${context.base}${path}/export#authorization=${encodeURIComponent(authorization)}`;
|
|
4733
5197
|
}
|
|
4734
5198
|
catch (error) {
|
|
4735
5199
|
// Never strand a blank tab on a failed mint.
|
|
@@ -4741,10 +5205,9 @@ async function openTurnkeyKeyExport() {
|
|
|
4741
5205
|
}
|
|
4742
5206
|
}
|
|
4743
5207
|
/**
|
|
4744
|
-
* Open the Bounded Turnkey
|
|
4745
|
-
* user's
|
|
4746
|
-
*
|
|
4747
|
-
* browser blocks the popup.
|
|
5208
|
+
* Open the Bounded Turnkey approval card and sign `message` (raw bytes) with the
|
|
5209
|
+
* user's Turnkey wallet, resolving with the ed25519 signature and wallet address.
|
|
5210
|
+
* Call from a user gesture so a deliberate browser fallback can open if needed.
|
|
4748
5211
|
*
|
|
4749
5212
|
* The signer shows an approve card (wallet address, requesting origin, Approve /
|
|
4750
5213
|
* Cancel). With a live 24-hour signing session (normally established by the login
|
|
@@ -4753,24 +5216,27 @@ async function openTurnkeyKeyExport() {
|
|
|
4753
5216
|
* completes the signature.
|
|
4754
5217
|
*/
|
|
4755
5218
|
async function signSolanaMessageViaTurnkey(message) {
|
|
4756
|
-
if (!hasDOM$
|
|
4757
|
-
throw new Error('Turnkey embedded-wallet signing is only available in the browser (
|
|
5219
|
+
if (!hasDOM$3()) {
|
|
5220
|
+
throw new Error('Turnkey embedded-wallet signing is only available in the browser (trusted wallet UI).');
|
|
4758
5221
|
}
|
|
4759
|
-
const
|
|
5222
|
+
const context = await turnkeySignerPage();
|
|
5223
|
+
const { url, origin } = context;
|
|
4760
5224
|
const requestId = `tksign_${Date.now()}_${++requestSeq}`;
|
|
4761
5225
|
const messageHex = toHex(message);
|
|
4762
5226
|
// AS-02: obtain the issuer-signed capability for THESE exact bytes first; the signer will
|
|
4763
5227
|
// refuse (capability_required) without it. Fetched per signature (each is single-payload).
|
|
4764
|
-
const capability = await fetchSigningCapability(messageHex);
|
|
5228
|
+
const capability = await fetchSigningCapability(messageHex, context);
|
|
4765
5229
|
// Inline (authMode:"turnkey") sessions plant no Better Auth cookie on the issuer, so the
|
|
4766
5230
|
// signer's same-origin status/create fetches need a bearer session proof: attach the
|
|
4767
5231
|
// createWallet action authorization (it covers the sign flow's get-or-create too). Minted
|
|
4768
5232
|
// from the same id token as the capability, so a session that minted one can mint both.
|
|
4769
|
-
const authorization = await fetchActionAuthorization('createWallet');
|
|
5233
|
+
const authorization = await fetchActionAuthorization('createWallet', context);
|
|
4770
5234
|
return await deliver(url, origin, {
|
|
5235
|
+
context: context.key, assertCurrent: context.assertCurrent,
|
|
4771
5236
|
request: buildTurnkeySignRequest({ requestId, messageHex, capability, authorization }),
|
|
4772
5237
|
onResult: (d) => {
|
|
4773
5238
|
if (d.ok && typeof d.signatureHex === 'string' && typeof d.address === 'string') {
|
|
5239
|
+
setWalletSessionState(context.key, { status: 'ready', expiresAt: Number(d.signingSessionExpiresAt) });
|
|
4774
5240
|
return { done: true, value: { signature: fromHex(d.signatureHex), address: d.address, subOrgId: String(d.subOrgId || '') } };
|
|
4775
5241
|
}
|
|
4776
5242
|
return { error: String(d.error || 'Turnkey signing failed') };
|
|
@@ -4880,11 +5346,21 @@ async function signAndSubmitTransactionWithTurnkey(transaction, feePayer) {
|
|
|
4880
5346
|
}
|
|
4881
5347
|
return signature;
|
|
4882
5348
|
}
|
|
5349
|
+
/** Save this trusted URL to recover the wallet even after the app is deleted. */
|
|
5350
|
+
async function getTurnkeyWalletRecoveryUrl() {
|
|
5351
|
+
const context = await turnkeySignerPage();
|
|
5352
|
+
context.assertCurrent();
|
|
5353
|
+
if (!context.address || !hasTurnkeyWallet())
|
|
5354
|
+
throw new Error('wallet_unavailable');
|
|
5355
|
+
return `${context.base}/wallet/turnkey/recover/${encodeURIComponent(context.address)}`;
|
|
5356
|
+
}
|
|
4883
5357
|
|
|
4884
5358
|
var turnkeySignerBridge = /*#__PURE__*/Object.freeze({
|
|
4885
5359
|
__proto__: null,
|
|
4886
5360
|
clearTurnkeySigningSession: clearTurnkeySigningSession,
|
|
4887
5361
|
getOrCreateTurnkeyWallet: getOrCreateTurnkeyWallet,
|
|
5362
|
+
getTurnkeyWalletRecoveryUrl: getTurnkeyWalletRecoveryUrl,
|
|
5363
|
+
getTurnkeyWalletSessionState: getTurnkeyWalletSessionState,
|
|
4888
5364
|
hasTurnkeyWallet: hasTurnkeyWallet,
|
|
4889
5365
|
openTurnkeyKeyExport: openTurnkeyKeyExport,
|
|
4890
5366
|
signAndSubmitTransactionWithTurnkey: signAndSubmitTransactionWithTurnkey,
|
|
@@ -4911,7 +5387,7 @@ const ISSUER_SESSION_KIND_KEY = 'bounded_issuer_session_kind';
|
|
|
4911
5387
|
// not by `typeof window` (truthy on RN). OIDC redirect/popup is web-only, but the
|
|
4912
5388
|
// session it mints is read back through this same selector everywhere.
|
|
4913
5389
|
const sessionManager = core.getActiveSessionManager;
|
|
4914
|
-
function hasDOM$
|
|
5390
|
+
function hasDOM$2() {
|
|
4915
5391
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
4916
5392
|
}
|
|
4917
5393
|
function decodeJwt(token) {
|
|
@@ -5062,7 +5538,7 @@ async function buildAuthorize(opts) {
|
|
|
5062
5538
|
throw new Error('appId is not configured (init the SDK with your appId)');
|
|
5063
5539
|
// Web: default to the current page so callers don't have to pass redirectUri. Native has no
|
|
5064
5540
|
// window — it MUST pass an https universal link (the system auth session needs a real URL).
|
|
5065
|
-
const redirectUri = opts.redirectUri || (hasDOM$
|
|
5541
|
+
const redirectUri = opts.redirectUri || (hasDOM$2() ? window.location.origin + window.location.pathname : undefined);
|
|
5066
5542
|
if (!redirectUri)
|
|
5067
5543
|
throw new Error('redirectUri is required on React Native — pass the https universal link the app owner registered.');
|
|
5068
5544
|
const { verifier, challenge } = await makePkce();
|
|
@@ -5170,7 +5646,7 @@ async function runNativeLogin(opts) {
|
|
|
5170
5646
|
async function loginWithRedirect(opts) {
|
|
5171
5647
|
if (isNative())
|
|
5172
5648
|
return runNativeLogin(opts);
|
|
5173
|
-
if (!hasDOM$
|
|
5649
|
+
if (!hasDOM$2())
|
|
5174
5650
|
throw new Error('loginWithRedirect requires a browser or a configured React Native platform');
|
|
5175
5651
|
const { url, pkce } = await buildAuthorize(opts);
|
|
5176
5652
|
// Stash the verifier + state so the redirect_uri page can finish the exchange. sessionStorage
|
|
@@ -5187,7 +5663,7 @@ async function loginWithRedirect(opts) {
|
|
|
5187
5663
|
* loginWithRedirect() (which resolves with the User), so there is no separate redirect
|
|
5188
5664
|
* page to finish from. Calling it on RN is harmless. */
|
|
5189
5665
|
async function completeLoginFromRedirect() {
|
|
5190
|
-
if (!hasDOM$
|
|
5666
|
+
if (!hasDOM$2())
|
|
5191
5667
|
return null;
|
|
5192
5668
|
const url = new URL(window.location.href);
|
|
5193
5669
|
const code = url.searchParams.get('code');
|
|
@@ -5235,7 +5711,7 @@ async function completeLoginFromRedirect() {
|
|
|
5235
5711
|
* redirect_uri page posts the code back. The redirect_uri page just calls the usual
|
|
5236
5712
|
* completeLoginFromRedirect() (which auto-detects the popup) — no separate call needed. */
|
|
5237
5713
|
async function loginWithPopup(opts) {
|
|
5238
|
-
if (!hasDOM$
|
|
5714
|
+
if (!hasDOM$2())
|
|
5239
5715
|
throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
|
|
5240
5716
|
// Open synchronously, before PKCE/config awaits consume the click's user-
|
|
5241
5717
|
// activation window. Opening only after buildAuthorize() intermittently
|
|
@@ -5395,7 +5871,7 @@ async function loginWithPopup(opts) {
|
|
|
5395
5871
|
/** Call this on the redirect_uri page when login was opened via loginWithPopup(): posts the
|
|
5396
5872
|
* code back to the opener and closes. (For the redirect variant use completeLoginFromRedirect.) */
|
|
5397
5873
|
function completeLoginInPopup(openerOrigin) {
|
|
5398
|
-
if (!hasDOM$
|
|
5874
|
+
if (!hasDOM$2() || !window.opener)
|
|
5399
5875
|
return;
|
|
5400
5876
|
const url = new URL(window.location.href);
|
|
5401
5877
|
const code = url.searchParams.get('code');
|
|
@@ -5416,7 +5892,7 @@ function completeLoginInPopup(openerOrigin) {
|
|
|
5416
5892
|
// Turnkey-native login path (authMode:"turnkey"), which mints the session server-side
|
|
5417
5893
|
// after Turnkey verifies the email OTP - there is no OIDC code to exchange. Wiring is
|
|
5418
5894
|
// identical to exchangeCode's tail so the realtime worker cannot tell them apart.
|
|
5419
|
-
async function establishInlineSession(data) {
|
|
5895
|
+
async function establishInlineSession(data, beforeCommit, beforeStore) {
|
|
5420
5896
|
const base = await issuerBase();
|
|
5421
5897
|
const idToken = tokenField(data, 'id_token', 'idToken');
|
|
5422
5898
|
if (!idToken)
|
|
@@ -5424,11 +5900,18 @@ async function establishInlineSession(data) {
|
|
|
5424
5900
|
const accessToken = tokenField(data, 'access_token', 'accessToken') || idToken;
|
|
5425
5901
|
const refreshToken = boundedRefreshTokenField(data);
|
|
5426
5902
|
const user = userFromIdToken(idToken);
|
|
5427
|
-
await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base);
|
|
5903
|
+
const rollback = await sessionManager().storeSession(String(user.address || ''), accessToken, idToken, refreshToken, base, () => { });
|
|
5428
5904
|
// Adopt the OIDC provider on EVERY session surface - including core's signing
|
|
5429
5905
|
// pointer (getConfig().authProvider), which this path used to skip, leaving
|
|
5430
5906
|
// onchain writes routed through a previous (e.g. logged-out wallet) provider.
|
|
5431
|
-
|
|
5907
|
+
try {
|
|
5908
|
+
await adoptSessionProvider({ provider: oidcProviderSingleton, method: 'email', configKey: 'direct:oidc', user, beforeCommit });
|
|
5909
|
+
}
|
|
5910
|
+
catch (error) {
|
|
5911
|
+
if (typeof rollback === 'function')
|
|
5912
|
+
await rollback();
|
|
5913
|
+
throw error;
|
|
5914
|
+
}
|
|
5432
5915
|
// Turnkey-native OTP mints an app session directly. It does not establish a
|
|
5433
5916
|
// Better Auth browser cookie on the issuer, so logout must not navigate
|
|
5434
5917
|
// through the issuer's cookie-clearing /logout endpoint. Stamped AFTER the
|
|
@@ -5492,9 +5975,6 @@ async function exchangeCode(code, verifier, redirectUri) {
|
|
|
5492
5975
|
return user;
|
|
5493
5976
|
}
|
|
5494
5977
|
|
|
5495
|
-
// NOTE: Provider class re-exports have been moved to index.ts (web) and
|
|
5496
|
-
// index.native.ts (RN) to avoid eager resolution of web-only dependencies
|
|
5497
|
-
// (react-dom) when this module is imported on RN.
|
|
5498
5978
|
let currentAuthProvider = null;
|
|
5499
5979
|
let currentAuthMethod = null;
|
|
5500
5980
|
let currentAuthProviderConfigKey = null;
|
|
@@ -5849,7 +6329,7 @@ async function getAuthProvider(config) {
|
|
|
5849
6329
|
// manager as legacy email sessions. Human login should be started
|
|
5850
6330
|
// with loginWithRedirect(); this provider is kept as the restore and
|
|
5851
6331
|
// logout surface for those sessions.
|
|
5852
|
-
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-
|
|
6332
|
+
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-62OK5X30.js'); })).EmailAuthProvider();
|
|
5853
6333
|
break;
|
|
5854
6334
|
case "phantom": {
|
|
5855
6335
|
// First-class Solana WALLET LOGIN (the poof/TaroBase-style "connect
|
|
@@ -5891,7 +6371,7 @@ async function getAuthProvider(config) {
|
|
|
5891
6371
|
// (bounded-login-widget.ts), so registration never counts as
|
|
5892
6372
|
// evidence the user has a wallet.
|
|
5893
6373
|
await ensureSolanaMobileWalletRegistered(config, configuredNetwork);
|
|
5894
|
-
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-
|
|
6374
|
+
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-B9eU7t8o.js'); });
|
|
5895
6375
|
currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
|
|
5896
6376
|
break;
|
|
5897
6377
|
}
|
|
@@ -5904,7 +6384,7 @@ async function getAuthProvider(config) {
|
|
|
5904
6384
|
// Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
|
|
5905
6385
|
// to point at a specific wallet or set chainId/domain.
|
|
5906
6386
|
const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
|
|
5907
|
-
const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-
|
|
6387
|
+
const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-CO_aC4TY.js'); });
|
|
5908
6388
|
currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
|
|
5909
6389
|
break;
|
|
5910
6390
|
}
|
|
@@ -5929,7 +6409,7 @@ async function getAuthProvider(config) {
|
|
|
5929
6409
|
break;
|
|
5930
6410
|
case "guest":
|
|
5931
6411
|
// Anonymous auth: a device-local ed25519 keypair signs the challenge.
|
|
5932
|
-
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-
|
|
6412
|
+
currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-B6dJixRm.js'); })).GuestAuthProvider();
|
|
5933
6413
|
break;
|
|
5934
6414
|
}
|
|
5935
6415
|
if (!currentAuthProvider) {
|
|
@@ -5991,7 +6471,7 @@ async function signInAnonymously() {
|
|
|
5991
6471
|
// Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
|
|
5992
6472
|
// the main bundle for data-only apps. Specifier is a static string literal
|
|
5993
6473
|
// so bundle-splitting can see it.
|
|
5994
|
-
const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-
|
|
6474
|
+
const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-B6dJixRm.js'); })).GuestAuthProvider();
|
|
5995
6475
|
setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
|
|
5996
6476
|
// adoptSessionProvider awaits only getConfig() (already resolved here) and then
|
|
5997
6477
|
// runs every write - provider/method/config/marker/user - in one synchronous
|
|
@@ -6162,7 +6642,7 @@ function ensureSharedWalletLoginReady() {
|
|
|
6162
6642
|
if (!sharedWalletReady) {
|
|
6163
6643
|
sharedWalletReady = (async () => {
|
|
6164
6644
|
await core.getConfig();
|
|
6165
|
-
await Promise.resolve().then(function () { return require('./injected-wallet-provider-
|
|
6645
|
+
await Promise.resolve().then(function () { return require('./injected-wallet-provider-B9eU7t8o.js'); });
|
|
6166
6646
|
})().catch((error) => {
|
|
6167
6647
|
sharedWalletReady = null;
|
|
6168
6648
|
throw error;
|
|
@@ -6265,7 +6745,7 @@ async function loginWithWallet(options) {
|
|
|
6265
6745
|
await ensureSharedWalletLoginReady();
|
|
6266
6746
|
else
|
|
6267
6747
|
await ensureWalletLoginReady();
|
|
6268
|
-
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-
|
|
6748
|
+
const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-B9eU7t8o.js'); });
|
|
6269
6749
|
// Keep the RAW wallet provider: the offchain wrapper forwards only a fixed
|
|
6270
6750
|
// set of methods, so cleanup below must speak to the provider that actually
|
|
6271
6751
|
// holds the config rather than to whatever is wrapping it.
|
|
@@ -6427,13 +6907,14 @@ async function exchangeLogoutHint(issuerBase, idToken) {
|
|
|
6427
6907
|
// which can be blocked or offline), so its failures are the one thing logout may
|
|
6428
6908
|
// tolerate - never revokeSession's. Lazy import per this module's rule (the signer
|
|
6429
6909
|
// bridge is browser-only machinery). Never rejects.
|
|
6430
|
-
async function sweepTurnkeySigningSession() {
|
|
6910
|
+
async function sweepTurnkeySigningSession(idToken) {
|
|
6431
6911
|
try {
|
|
6432
|
-
await (await Promise.resolve().then(function () { return turnkeySignerBridge; })).clearTurnkeySigningSession();
|
|
6912
|
+
await (await Promise.resolve().then(function () { return turnkeySignerBridge; })).clearTurnkeySigningSession(idToken);
|
|
6433
6913
|
}
|
|
6434
6914
|
catch ( /* best-effort: never block or fail logout on the sweep */_a) { /* best-effort: never block or fail logout on the sweep */ }
|
|
6435
6915
|
}
|
|
6436
6916
|
async function logout$1(options) {
|
|
6917
|
+
invalidatePendingWalletLogin();
|
|
6437
6918
|
// Re-initialize the provider if it was cleared by a prior logout — the exact mirror
|
|
6438
6919
|
// of login()'s self-heal above. Every session-establishment path now adopts through
|
|
6439
6920
|
// session-provider.ts, so this is a REPAIR (the registry was cleared by a previous
|
|
@@ -6465,7 +6946,7 @@ async function logout$1(options) {
|
|
|
6465
6946
|
// sweep is a hidden signer-iframe exchange; start it now (after revocation is
|
|
6466
6947
|
// committed - a failed logout leaves the still-logged-in user their session) and
|
|
6467
6948
|
// let it overlap the provider teardown. sweepTurnkeySigningSession never rejects.
|
|
6468
|
-
const signingSessionSweep = sweepTurnkeySigningSession();
|
|
6949
|
+
const signingSessionSweep = sweepTurnkeySigningSession(idTokenHint);
|
|
6469
6950
|
// Only OIDC-hosted sessions own an issuer browser cookie. Turnkey-native
|
|
6470
6951
|
// email OTP is also an email auth method, but it mints the app session
|
|
6471
6952
|
// inline and has no issuer cookie to clear. Use the explicit session-kind
|
|
@@ -6694,371 +7175,101 @@ function onAuthStateChanged(callback) {
|
|
|
6694
7175
|
// setCurrentUser and init()'s initCompleted would never fire (init's
|
|
6695
7176
|
// setCurrentUser is a no-op for same-address and initCompleted is still false).
|
|
6696
7177
|
callback(currentUser);
|
|
6697
|
-
}
|
|
6698
|
-
return () => {
|
|
6699
|
-
authStateListeners = authStateListeners.filter((listener) => listener !== callback);
|
|
6700
|
-
};
|
|
6701
|
-
}
|
|
6702
|
-
function onAuthLoadingChanged(callback) {
|
|
6703
|
-
authLoadingListeners.push(callback);
|
|
6704
|
-
// Call immediately with current loading state
|
|
6705
|
-
callback(isAuthLoading);
|
|
6706
|
-
return () => {
|
|
6707
|
-
authLoadingListeners = authLoadingListeners.filter((listener) => listener !== callback);
|
|
6708
|
-
};
|
|
6709
|
-
}
|
|
6710
|
-
function setAuthLoading(loading) {
|
|
6711
|
-
if (isAuthLoading !== loading) {
|
|
6712
|
-
isAuthLoading = loading;
|
|
6713
|
-
authLoadingListeners.forEach((callback) => callback(loading));
|
|
6714
|
-
}
|
|
6715
|
-
}
|
|
6716
|
-
function getAuthLoading() {
|
|
6717
|
-
return isAuthLoading;
|
|
6718
|
-
}
|
|
6719
|
-
async function login(options) {
|
|
6720
|
-
if (!authProviderInstance) {
|
|
6721
|
-
throw new Error('SDK not initialized. Please call init() first.');
|
|
6722
|
-
}
|
|
6723
|
-
const loggedInUser = await login$1(options);
|
|
6724
|
-
setCurrentUser(loggedInUser);
|
|
6725
|
-
return currentUser;
|
|
6726
|
-
}
|
|
6727
|
-
async function logout(options) {
|
|
6728
|
-
if (!authProviderInstance) {
|
|
6729
|
-
throw new Error('SDK not initialized. Please call init() first.');
|
|
6730
|
-
}
|
|
6731
|
-
await logout$1(options);
|
|
6732
|
-
setCurrentUser(null);
|
|
6733
|
-
}
|
|
6734
|
-
var hasRunFirstTime = false;
|
|
6735
|
-
function userPrincipalKey(user) {
|
|
6736
|
-
var _a, _b, _c;
|
|
6737
|
-
if (!user)
|
|
6738
|
-
return null;
|
|
6739
|
-
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;
|
|
6740
|
-
}
|
|
6741
|
-
// Update the currentUser and notify listeners
|
|
6742
|
-
function setCurrentUser(user) {
|
|
6743
|
-
// Enrich with the universal identity fields (id/email) derived from the
|
|
6744
|
-
// active session's idToken — central chokepoint so every provider's user
|
|
6745
|
-
// object gains { id, address, email } without each construction site
|
|
6746
|
-
// having to set them.
|
|
6747
|
-
user = withUserIdentity(user);
|
|
6748
|
-
const lastUser = currentUser;
|
|
6749
|
-
const wasInitializedBefore = hasRunFirstTime;
|
|
6750
|
-
const lastPrincipal = userPrincipalKey(currentUser);
|
|
6751
|
-
const nextPrincipal = userPrincipalKey(user);
|
|
6752
|
-
const shouldRunListeners = !hasRunFirstTime || lastPrincipal !== nextPrincipal;
|
|
6753
|
-
hasRunFirstTime = true;
|
|
6754
|
-
currentUser = user;
|
|
6755
|
-
if (shouldRunListeners) {
|
|
6756
|
-
// Isolate each listener: a consumer callback that throws (during logout in
|
|
6757
|
-
// particular) must not abort the notification loop OR skip the socket reopen
|
|
6758
|
-
// below. Before this, a single throwing listener propagated out of
|
|
6759
|
-
// setCurrentUser, so the realtime socket kept streaming as the signed-out
|
|
6760
|
-
// user and the other listeners were never told the principal changed.
|
|
6761
|
-
authStateListeners.forEach((callback) => {
|
|
6762
|
-
try {
|
|
6763
|
-
callback(user);
|
|
6764
|
-
}
|
|
6765
|
-
catch (error) {
|
|
6766
|
-
console.error('Error in onAuthStateChanged listener:', error);
|
|
6767
|
-
}
|
|
6768
|
-
});
|
|
6769
|
-
// Reconnect WebSocket connections with new auth when user changes.
|
|
6770
|
-
// Skip on the first setCurrentUser call during init() — there are no
|
|
6771
|
-
// existing connections to reconnect at that point.
|
|
6772
|
-
if (wasInitializedBefore && userPrincipalKey(lastUser) !== nextPrincipal) {
|
|
6773
|
-
core.reconnectWithNewAuth().catch((error) => {
|
|
6774
|
-
console.error('Error reconnecting WebSocket after auth change:', error);
|
|
6775
|
-
});
|
|
6776
|
-
}
|
|
6777
|
-
}
|
|
6778
|
-
// Return this commit's normalized snapshot. A listener can start another
|
|
6779
|
-
// login, so the mutable currentUser may already belong to a newer session.
|
|
6780
|
-
return user;
|
|
6781
|
-
}
|
|
6782
|
-
function getCurrentUser() {
|
|
6783
|
-
return currentUser;
|
|
6784
|
-
}
|
|
6785
|
-
/** @internal Snapshot/restore surface for transactional auth-provider switches. */
|
|
6786
|
-
function getAuthProviderInstance() {
|
|
6787
|
-
return authProviderInstance;
|
|
6788
|
-
}
|
|
6789
|
-
function setAuthProviderInstance(provider) {
|
|
6790
|
-
authProviderInstance = provider;
|
|
6791
|
-
}
|
|
6792
|
-
|
|
6793
|
-
// bounded-widget-modal.ts - the shared in-app WIDGET shell for Bounded's login
|
|
6794
|
-
// and signing surfaces. One Shadow-DOM overlay + panel that matches the Bounded
|
|
6795
|
-
// widget (Seal) look and MOTION: a backdrop fade, a panel rise on desktop, a
|
|
6796
|
-
// bottom-sheet on mobile, a height tween as content swaps, and a clean close.
|
|
6797
|
-
//
|
|
6798
|
-
// It hosts EITHER:
|
|
6799
|
-
// - NATIVE content (the login card the SDK renders itself), or
|
|
6800
|
-
// - an IFRAME (the Bounded-owned signer page, which must run on the single
|
|
6801
|
-
// signer origin because the wallet's 24h signing session lives in that
|
|
6802
|
-
// origin's storage, out of every app's reach).
|
|
6803
|
-
//
|
|
6804
|
-
// The modal is a warm singleton: it stays mounted (hidden) between uses so a
|
|
6805
|
-
// second open - or a signature right after login - is instant and, when it is
|
|
6806
|
-
// already open, a clean in-place view swap rather than a reopen.
|
|
6807
|
-
function hasDOM$2() {
|
|
6808
|
-
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
6809
|
-
}
|
|
6810
|
-
const OVERLAY_CSS = `
|
|
6811
|
-
:host{all:initial}
|
|
6812
|
-
*{box-sizing:border-box}
|
|
6813
|
-
.bw-ov{position:fixed;inset:0;z-index:2147483647;display:none;
|
|
6814
|
-
font-family:"Schibsted Grotesk","Helvetica Neue",Arial,sans-serif}
|
|
6815
|
-
.bw-ov.mounted{display:block}
|
|
6816
|
-
.bw-back{position:fixed;inset:0;background:rgba(4,4,5,.62);opacity:0;
|
|
6817
|
-
transition:opacity .24s ease;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}
|
|
6818
|
-
.bw-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:20px}
|
|
6819
|
-
.bw-panel{position:relative;width:420px;max-width:calc(100vw - 28px);height:280px;
|
|
6820
|
-
border-radius:14px;overflow:hidden;background:#0e0f11;border:1px solid rgba(232,230,222,.08);
|
|
6821
|
-
box-shadow:0 1px 0 rgba(232,230,222,.03) inset,0 40px 90px -45px rgba(0,0,0,.85);
|
|
6822
|
-
opacity:0;transform:translateY(14px) scale(.98);will-change:transform,opacity,height;
|
|
6823
|
-
transition:opacity .28s cubic-bezier(.2,.8,.2,1),transform .28s cubic-bezier(.2,.8,.2,1),
|
|
6824
|
-
height .3s cubic-bezier(.2,.8,.2,1)}
|
|
6825
|
-
.bw-ov.show .bw-back{opacity:1}
|
|
6826
|
-
.bw-ov.show .bw-panel{opacity:1;transform:none}
|
|
6827
|
-
.bw-ov.closing .bw-back{opacity:0}
|
|
6828
|
-
.bw-ov.closing .bw-panel{opacity:0;transform:translateY(12px) scale(.99)}
|
|
6829
|
-
.bw-content{position:absolute;inset:0;overflow:hidden}
|
|
6830
|
-
.bw-frame{display:block;width:100%;height:100%;border:0;background:transparent;color-scheme:dark}
|
|
6831
|
-
/* editorial corner crop-marks, matching the reference frame */
|
|
6832
|
-
.bw-mk{position:absolute;width:13px;height:13px;pointer-events:none;z-index:2;
|
|
6833
|
-
border-color:rgba(232,230,222,.22);border-style:solid;border-width:0}
|
|
6834
|
-
.bw-mk.tl{top:11px;left:11px;border-top-width:1px;border-left-width:1px}
|
|
6835
|
-
.bw-mk.tr{top:11px;right:11px;border-top-width:1px;border-right-width:1px}
|
|
6836
|
-
.bw-mk.bl{bottom:11px;left:11px;border-bottom-width:1px;border-left-width:1px}
|
|
6837
|
-
.bw-mk.br{bottom:11px;right:11px;border-bottom-width:1px;border-right-width:1px}
|
|
6838
|
-
@media(max-width:560px){
|
|
6839
|
-
.bw-wrap{align-items:flex-end;padding:0}
|
|
6840
|
-
.bw-panel{width:100%;max-width:100%;border-radius:18px 18px 0 0;border-bottom:0}
|
|
6841
|
-
.bw-content{overflow-y:auto}
|
|
6842
|
-
.bw-ov.show .bw-panel{transform:none}
|
|
6843
|
-
.bw-ov:not(.show) .bw-panel,.bw-ov.closing .bw-panel{transform:translateY(18%)}
|
|
6844
|
-
}
|
|
6845
|
-
@media(prefers-reduced-motion:reduce){.bw-back,.bw-panel{transition:opacity .12s linear!important}}
|
|
6846
|
-
`;
|
|
6847
|
-
let modal = null;
|
|
6848
|
-
function getModal() {
|
|
6849
|
-
if (modal)
|
|
6850
|
-
return modal;
|
|
6851
|
-
const root = document.createElement("div");
|
|
6852
|
-
root.setAttribute("data-bounded-widget", "");
|
|
6853
|
-
const shadow = root.attachShadow({ mode: "open" });
|
|
6854
|
-
const style = document.createElement("style");
|
|
6855
|
-
style.textContent = OVERLAY_CSS;
|
|
6856
|
-
shadow.appendChild(style);
|
|
6857
|
-
const overlay = document.createElement("div");
|
|
6858
|
-
overlay.className = "bw-ov";
|
|
6859
|
-
const back = document.createElement("div");
|
|
6860
|
-
back.className = "bw-back";
|
|
6861
|
-
const wrap = document.createElement("div");
|
|
6862
|
-
wrap.className = "bw-wrap";
|
|
6863
|
-
const panel = document.createElement("div");
|
|
6864
|
-
panel.className = "bw-panel";
|
|
6865
|
-
const content = document.createElement("div");
|
|
6866
|
-
content.className = "bw-content";
|
|
6867
|
-
panel.appendChild(content);
|
|
6868
|
-
for (const corner of ["tl", "tr", "bl", "br"]) {
|
|
6869
|
-
const mk = document.createElement("span");
|
|
6870
|
-
mk.className = "bw-mk " + corner;
|
|
6871
|
-
panel.appendChild(mk);
|
|
6872
|
-
}
|
|
6873
|
-
wrap.appendChild(panel);
|
|
6874
|
-
overlay.appendChild(back);
|
|
6875
|
-
overlay.appendChild(wrap);
|
|
6876
|
-
shadow.appendChild(overlay);
|
|
6877
|
-
(document.body || document.documentElement).appendChild(root);
|
|
6878
|
-
const m = {
|
|
6879
|
-
root,
|
|
6880
|
-
overlay,
|
|
6881
|
-
panel,
|
|
6882
|
-
content,
|
|
6883
|
-
iframe: null,
|
|
6884
|
-
iframeOrigin: "",
|
|
6885
|
-
iframeReady: false,
|
|
6886
|
-
onBackdrop: null,
|
|
6887
|
-
onIframeMessage: null,
|
|
6888
|
-
chain: Promise.resolve(),
|
|
6889
|
-
fitObserver: null,
|
|
6890
|
-
generation: 0,
|
|
6891
|
-
onSupersede: null,
|
|
6892
|
-
};
|
|
6893
|
-
// The dismiss handler lives on `.bw-wrap`: it is the full-viewport flex layer
|
|
6894
|
-
// that sits ABOVE `.bw-back`, so it (not the backdrop element) receives clicks
|
|
6895
|
-
// outside the panel. Fire only when the click lands on the wrap itself, never
|
|
6896
|
-
// when it bubbles up from the panel or its content.
|
|
6897
|
-
wrap.addEventListener("click", (event) => {
|
|
6898
|
-
if (event.target === wrap && m.onBackdrop)
|
|
6899
|
-
m.onBackdrop();
|
|
6900
|
-
});
|
|
6901
|
-
window.addEventListener("message", (event) => {
|
|
6902
|
-
if (!m.iframe)
|
|
6903
|
-
return;
|
|
6904
|
-
if (event.origin !== m.iframeOrigin)
|
|
6905
|
-
return;
|
|
6906
|
-
if (event.source !== m.iframe.contentWindow)
|
|
6907
|
-
return;
|
|
6908
|
-
const d = event.data;
|
|
6909
|
-
if (!d || typeof d !== "object")
|
|
6910
|
-
return;
|
|
6911
|
-
if (m.onIframeMessage)
|
|
6912
|
-
m.onIframeMessage(d);
|
|
6913
|
-
});
|
|
6914
|
-
modal = m;
|
|
6915
|
-
return m;
|
|
6916
|
-
}
|
|
6917
|
-
function showModal(m) {
|
|
6918
|
-
m.overlay.classList.remove("closing");
|
|
6919
|
-
m.overlay.classList.add("mounted");
|
|
6920
|
-
// Force a reflow so the .show transition runs from the hidden state every open.
|
|
6921
|
-
void m.panel.offsetWidth;
|
|
6922
|
-
m.overlay.classList.add("show");
|
|
6923
|
-
}
|
|
6924
|
-
function hideModal(m, after) {
|
|
6925
|
-
m.overlay.classList.remove("show");
|
|
6926
|
-
m.overlay.classList.add("closing");
|
|
6927
|
-
// Capture the generation at hide time. If a newer open re-shows the modal within
|
|
6928
|
-
// the close tween, this stale timer must NOT strip `.mounted`/`.closing` off the
|
|
6929
|
-
// freshly reopened widget - only tear down when we are still the same open.
|
|
6930
|
-
const gen = m.generation;
|
|
6931
|
-
window.setTimeout(() => {
|
|
6932
|
-
if (m.generation === gen)
|
|
6933
|
-
m.overlay.classList.remove("mounted", "closing");
|
|
6934
|
-
}, 260);
|
|
6935
|
-
}
|
|
6936
|
-
function setPanelHeight(m, px) {
|
|
6937
|
-
const h = Math.max(140, Math.min(680, Math.round(px)));
|
|
6938
|
-
m.panel.style.height = h + "px";
|
|
6939
|
-
}
|
|
6940
|
-
// True outer height of the mounted card: the max of its padding-box scroll height
|
|
6941
|
-
// (catches overflowing descendants) and its border-box height plus own vertical
|
|
6942
|
-
// margins (catches border + margins, which scrollHeight omits). Ceiled so a
|
|
6943
|
-
// sub-pixel row is never clipped under `overflow:hidden`.
|
|
6944
|
-
function measureOuterHeight(target) {
|
|
6945
|
-
var _a;
|
|
6946
|
-
const cs = (_a = target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.getComputedStyle(target);
|
|
6947
|
-
const marginY = cs ? parseFloat(cs.marginTop) + parseFloat(cs.marginBottom) : 0;
|
|
6948
|
-
const borderBox = target.offsetHeight + (Number.isFinite(marginY) ? marginY : 0);
|
|
6949
|
-
const rect = target.getBoundingClientRect().height;
|
|
6950
|
-
return Math.ceil(Math.max(target.scrollHeight, borderBox, rect));
|
|
7178
|
+
}
|
|
7179
|
+
return () => {
|
|
7180
|
+
authStateListeners = authStateListeners.filter((listener) => listener !== callback);
|
|
7181
|
+
};
|
|
6951
7182
|
}
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
function fitPanelToContent(m) {
|
|
6959
|
-
var _a, _b;
|
|
6960
|
-
const first = m.content.firstElementChild;
|
|
6961
|
-
const target = first || m.content;
|
|
6962
|
-
const apply = () => {
|
|
6963
|
-
// Only fit while this exact node is still the mounted card.
|
|
6964
|
-
if (m.content.firstElementChild !== first && first)
|
|
6965
|
-
return;
|
|
6966
|
-
const h = measureOuterHeight(target);
|
|
6967
|
-
if (h > 0)
|
|
6968
|
-
setPanelHeight(m, h);
|
|
7183
|
+
function onAuthLoadingChanged(callback) {
|
|
7184
|
+
authLoadingListeners.push(callback);
|
|
7185
|
+
// Call immediately with current loading state
|
|
7186
|
+
callback(isAuthLoading);
|
|
7187
|
+
return () => {
|
|
7188
|
+
authLoadingListeners = authLoadingListeners.filter((listener) => listener !== callback);
|
|
6969
7189
|
};
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
const fonts = target.ownerDocument.fonts;
|
|
6976
|
-
if (fonts === null || fonts === void 0 ? void 0 : fonts.ready)
|
|
6977
|
-
fonts.ready.then(apply).catch(() => { });
|
|
6978
|
-
(_b = m.fitObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
6979
|
-
m.fitObserver = null;
|
|
6980
|
-
if (first && typeof (view === null || view === void 0 ? void 0 : view.ResizeObserver) === "function") {
|
|
6981
|
-
const ro = new view.ResizeObserver(() => apply());
|
|
6982
|
-
ro.observe(first);
|
|
6983
|
-
m.fitObserver = ro;
|
|
7190
|
+
}
|
|
7191
|
+
function setAuthLoading(loading) {
|
|
7192
|
+
if (isAuthLoading !== loading) {
|
|
7193
|
+
isAuthLoading = loading;
|
|
7194
|
+
authLoadingListeners.forEach((callback) => callback(loading));
|
|
6984
7195
|
}
|
|
6985
7196
|
}
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
var _a;
|
|
6989
|
-
detachIframe(m);
|
|
6990
|
-
(_a = m.fitObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
6991
|
-
m.fitObserver = null;
|
|
6992
|
-
m.content.replaceChildren(node);
|
|
6993
|
-
m.content.style.display = "block";
|
|
7197
|
+
function getAuthLoading() {
|
|
7198
|
+
return isAuthLoading;
|
|
6994
7199
|
}
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
*
|
|
7003
|
-
* Deliberately does NOT call getModal(): that would BUILD a modal just to tear it down, on
|
|
7004
|
-
* every logout of every app, most of which never mounted one.
|
|
7005
|
-
*/
|
|
7006
|
-
function detachWarmSignerIframe(origin) {
|
|
7007
|
-
if (!modal || !modal.iframe || modal.iframeOrigin !== origin)
|
|
7008
|
-
return false;
|
|
7009
|
-
detachIframe(modal);
|
|
7010
|
-
return true;
|
|
7200
|
+
async function login(options) {
|
|
7201
|
+
if (!authProviderInstance) {
|
|
7202
|
+
throw new Error('SDK not initialized. Please call init() first.');
|
|
7203
|
+
}
|
|
7204
|
+
const loggedInUser = await login$1(options);
|
|
7205
|
+
setCurrentUser(loggedInUser);
|
|
7206
|
+
return currentUser;
|
|
7011
7207
|
}
|
|
7012
|
-
function
|
|
7013
|
-
if (
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7208
|
+
async function logout(options) {
|
|
7209
|
+
if (!authProviderInstance) {
|
|
7210
|
+
throw new Error('SDK not initialized. Please call init() first.');
|
|
7211
|
+
}
|
|
7212
|
+
await logout$1(options);
|
|
7213
|
+
setCurrentUser(null);
|
|
7214
|
+
}
|
|
7215
|
+
var hasRunFirstTime = false;
|
|
7216
|
+
function userPrincipalKey(user) {
|
|
7217
|
+
var _a, _b, _c;
|
|
7218
|
+
if (!user)
|
|
7219
|
+
return null;
|
|
7220
|
+
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;
|
|
7221
|
+
}
|
|
7222
|
+
// Update the currentUser and notify listeners
|
|
7223
|
+
function setCurrentUser(user) {
|
|
7224
|
+
// Enrich with the universal identity fields (id/email) derived from the
|
|
7225
|
+
// active session's idToken — central chokepoint so every provider's user
|
|
7226
|
+
// object gains { id, address, email } without each construction site
|
|
7227
|
+
// having to set them.
|
|
7228
|
+
user = withUserIdentity(user);
|
|
7229
|
+
const lastUser = currentUser;
|
|
7230
|
+
const wasInitializedBefore = hasRunFirstTime;
|
|
7231
|
+
const lastPrincipal = userPrincipalKey(currentUser);
|
|
7232
|
+
const nextPrincipal = userPrincipalKey(user);
|
|
7233
|
+
const shouldRunListeners = !hasRunFirstTime || lastPrincipal !== nextPrincipal;
|
|
7234
|
+
hasRunFirstTime = true;
|
|
7235
|
+
currentUser = user;
|
|
7236
|
+
if (shouldRunListeners) {
|
|
7237
|
+
// Isolate each listener: a consumer callback that throws (during logout in
|
|
7238
|
+
// particular) must not abort the notification loop OR skip the socket reopen
|
|
7239
|
+
// below. Before this, a single throwing listener propagated out of
|
|
7240
|
+
// setCurrentUser, so the realtime socket kept streaming as the signed-out
|
|
7241
|
+
// user and the other listeners were never told the principal changed.
|
|
7242
|
+
authStateListeners.forEach((callback) => {
|
|
7243
|
+
try {
|
|
7244
|
+
callback(user);
|
|
7245
|
+
}
|
|
7246
|
+
catch (error) {
|
|
7247
|
+
console.error('Error in onAuthStateChanged listener:', error);
|
|
7248
|
+
}
|
|
7249
|
+
});
|
|
7250
|
+
// Reconnect WebSocket connections with new auth when user changes.
|
|
7251
|
+
// Skip on the first setCurrentUser call during init() — there are no
|
|
7252
|
+
// existing connections to reconnect at that point.
|
|
7253
|
+
if (wasInitializedBefore && userPrincipalKey(lastUser) !== nextPrincipal) {
|
|
7254
|
+
core.reconnectWithNewAuth().catch((error) => {
|
|
7255
|
+
console.error('Error reconnecting WebSocket after auth change:', error);
|
|
7256
|
+
});
|
|
7019
7257
|
}
|
|
7020
7258
|
}
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
m.onIframeMessage = null;
|
|
7259
|
+
// Return this commit's normalized snapshot. A listener can start another
|
|
7260
|
+
// login, so the mutable currentUser may already belong to a newer session.
|
|
7261
|
+
return user;
|
|
7025
7262
|
}
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
function
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
const pre1 = document.createElement("link");
|
|
7036
|
-
pre1.rel = "preconnect";
|
|
7037
|
-
pre1.href = "https://fonts.googleapis.com";
|
|
7038
|
-
const pre2 = document.createElement("link");
|
|
7039
|
-
pre2.rel = "preconnect";
|
|
7040
|
-
pre2.href = "https://fonts.gstatic.com";
|
|
7041
|
-
pre2.crossOrigin = "anonymous";
|
|
7042
|
-
const link = document.createElement("link");
|
|
7043
|
-
link.rel = "stylesheet";
|
|
7044
|
-
link.href = FONTS_HREF;
|
|
7045
|
-
link.setAttribute("data-bounded-fonts", "1");
|
|
7046
|
-
document.head.append(pre1, pre2, link);
|
|
7263
|
+
function getCurrentUser() {
|
|
7264
|
+
return currentUser;
|
|
7265
|
+
}
|
|
7266
|
+
/** @internal Snapshot/restore surface for transactional auth-provider switches. */
|
|
7267
|
+
function getAuthProviderInstance() {
|
|
7268
|
+
return authProviderInstance;
|
|
7269
|
+
}
|
|
7270
|
+
function setAuthProviderInstance(provider) {
|
|
7271
|
+
authProviderInstance = provider;
|
|
7047
7272
|
}
|
|
7048
|
-
|
|
7049
|
-
var boundedWidgetModal = /*#__PURE__*/Object.freeze({
|
|
7050
|
-
__proto__: null,
|
|
7051
|
-
detachIframe: detachIframe,
|
|
7052
|
-
detachWarmSignerIframe: detachWarmSignerIframe,
|
|
7053
|
-
ensureFonts: ensureFonts,
|
|
7054
|
-
fitPanelToContent: fitPanelToContent,
|
|
7055
|
-
getModal: getModal,
|
|
7056
|
-
hasDOM: hasDOM$2,
|
|
7057
|
-
hideModal: hideModal,
|
|
7058
|
-
mountContent: mountContent,
|
|
7059
|
-
setPanelHeight: setPanelHeight,
|
|
7060
|
-
showModal: showModal
|
|
7061
|
-
});
|
|
7062
7273
|
|
|
7063
7274
|
// bounded-widget-ownership.ts - per-open ownership generation for the singleton
|
|
7064
7275
|
// login/signing modal (bounded-widget-modal.ts).
|
|
@@ -7519,7 +7730,7 @@ async function startTurnkeyEmailLogin(email) {
|
|
|
7519
7730
|
const res = await fetch(`${base}/wallet/turnkey/auth/init`, {
|
|
7520
7731
|
method: 'POST',
|
|
7521
7732
|
headers: { 'content-type': 'application/json' },
|
|
7522
|
-
body: JSON.stringify({ email: trimmed, appId }),
|
|
7733
|
+
body: JSON.stringify({ email: trimmed, appId, walletProtocol: 2 }),
|
|
7523
7734
|
});
|
|
7524
7735
|
const data = await res.json().catch(() => null);
|
|
7525
7736
|
if (!res.ok || !data || typeof data.otpId !== 'string' || typeof data.otpEncryptionTargetBundle !== 'string') {
|
|
@@ -7531,13 +7742,23 @@ async function startTurnkeyEmailLogin(email) {
|
|
|
7531
7742
|
// (there is nothing to bind the frame key to), which is the correct fail-closed posture.
|
|
7532
7743
|
const ceremonyId = typeof data.ceremonyId === 'string' ? data.ceremonyId : '';
|
|
7533
7744
|
const signerOrigin = new URL(base).origin;
|
|
7745
|
+
if (data.walletScope !== 'app' && data.walletScope !== 'legacy')
|
|
7746
|
+
throw new Error('wallet_scope_config_unavailable');
|
|
7747
|
+
if (data.walletProtocol !== (data.walletScope === 'app' ? 2 : 1))
|
|
7748
|
+
throw new Error('wallet_protocol_invalid');
|
|
7749
|
+
const frameUrl = data.walletScope === 'app'
|
|
7750
|
+
? `${base.replace(/\/$/, '')}/wallet/turnkey/app/${appId}/signer?origin=${encodeURIComponent(window.location.origin)}`
|
|
7751
|
+
: undefined;
|
|
7534
7752
|
return {
|
|
7535
|
-
email: trimmed,
|
|
7536
|
-
run(iframe) {
|
|
7753
|
+
email: trimmed, frameUrl, signerOrigin,
|
|
7754
|
+
run(iframe, _signal) {
|
|
7537
7755
|
if (!ceremonyId) {
|
|
7538
7756
|
return Promise.reject(new Error('This app is not configured for in-frame wallet login. Please try again.'));
|
|
7539
7757
|
}
|
|
7540
|
-
|
|
7758
|
+
const run = { iframe, base, signerOrigin, appId, email: trimmed, otpId, otpEncryptionTargetBundle, ceremonyId };
|
|
7759
|
+
// BSA-2026-004: the login code is typed only in /wallet/turnkey/otp-frame.
|
|
7760
|
+
// The scoped signer URL is for post-login signing, not OTP entry.
|
|
7761
|
+
return runOtpFrame(run);
|
|
7541
7762
|
},
|
|
7542
7763
|
};
|
|
7543
7764
|
}
|
|
@@ -7865,7 +8086,7 @@ function untilCancelled(work, pending) {
|
|
|
7865
8086
|
* run first; call from a user gesture so social/wallet popups are allowed.
|
|
7866
8087
|
*/
|
|
7867
8088
|
async function openBoundedWidget(opts = {}) {
|
|
7868
|
-
if (!hasDOM$
|
|
8089
|
+
if (!hasDOM$5()) {
|
|
7869
8090
|
throw new Error("openBoundedWidget is web-only - on React Native use loginWithRedirect()");
|
|
7870
8091
|
}
|
|
7871
8092
|
// Registered before the first await - see PendingOpen.
|
|
@@ -8027,6 +8248,7 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8027
8248
|
// the underlying promise SETTLES, whoever owns the card by then.
|
|
8028
8249
|
let attemptSeq = 0;
|
|
8029
8250
|
let activeAttempt = 0;
|
|
8251
|
+
let abortScopedLogin = null;
|
|
8030
8252
|
let abortGesture = null;
|
|
8031
8253
|
/** Reject a gesture promise already waiting on a view that is going away. */
|
|
8032
8254
|
const abortPendingGesture = () => {
|
|
@@ -8058,6 +8280,8 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8058
8280
|
/** Take the card back (Back, a lane switch, settling the modal). Deliberately
|
|
8059
8281
|
* NOT a release: the login underneath keeps running - see above. */
|
|
8060
8282
|
const invalidateAttempts = () => {
|
|
8283
|
+
abortScopedLogin === null || abortScopedLogin === void 0 ? void 0 : abortScopedLogin();
|
|
8284
|
+
abortScopedLogin = null;
|
|
8061
8285
|
activeAttempt = 0;
|
|
8062
8286
|
abortPendingGesture();
|
|
8063
8287
|
};
|
|
@@ -8111,7 +8335,7 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8111
8335
|
const style = el("style");
|
|
8112
8336
|
style.textContent = CARD_CSS;
|
|
8113
8337
|
let emailDraft = "";
|
|
8114
|
-
const renderChoose = () => {
|
|
8338
|
+
const renderChoose = (initialError) => {
|
|
8115
8339
|
const card = el("div", "c");
|
|
8116
8340
|
card.appendChild(style.cloneNode(true));
|
|
8117
8341
|
const x = el("button", "x", "Close");
|
|
@@ -8121,13 +8345,15 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8121
8345
|
card.appendChild(x);
|
|
8122
8346
|
card.appendChild(el("h1", undefined, title !== null && title !== void 0 ? title : "Sign in"));
|
|
8123
8347
|
if (subtitle !== "") {
|
|
8124
|
-
card.appendChild(el("p", "sub", subtitle !== null && subtitle !== void 0 ? subtitle : "
|
|
8348
|
+
card.appendChild(el("p", "sub", subtitle !== null && subtitle !== void 0 ? subtitle : "Sign in to continue to this app."));
|
|
8125
8349
|
}
|
|
8126
8350
|
const status = el("div", "status");
|
|
8127
8351
|
const setErr = (msg) => {
|
|
8128
8352
|
status.textContent = msg;
|
|
8129
8353
|
status.classList.add("err");
|
|
8130
8354
|
};
|
|
8355
|
+
if (initialError)
|
|
8356
|
+
setErr(initialError);
|
|
8131
8357
|
const setBusy = (btn, label) => {
|
|
8132
8358
|
status.classList.remove("err");
|
|
8133
8359
|
status.textContent = "";
|
|
@@ -8171,11 +8397,25 @@ async function prepareAndOpen(opts, pending) {
|
|
|
8171
8397
|
// arrives; the code entry claims its own when the user verifies.
|
|
8172
8398
|
setBusy(submit, "Sending code\u2026");
|
|
8173
8399
|
startTurnkeyEmailLogin(input.value)
|
|
8174
|
-
.then((handle) => {
|
|
8175
|
-
|
|
8400
|
+
.then(async (handle) => {
|
|
8401
|
+
if (!attemptIsCurrent(attempt)) {
|
|
8402
|
+
settleAttempt(attempt);
|
|
8403
|
+
return;
|
|
8404
|
+
}
|
|
8405
|
+
// BSA-2026-004: collect the code in the Bounded OTP frame, never in
|
|
8406
|
+
// this tenant DOM and never in the unframeable signer document.
|
|
8407
|
+
if (settleAttempt(attempt))
|
|
8408
|
+
swap(renderOtpFrame(handle));
|
|
8409
|
+
})
|
|
8176
8410
|
.catch((err) => {
|
|
8177
8411
|
if (!settleAttempt(attempt))
|
|
8178
8412
|
return;
|
|
8413
|
+
if (abortScopedLogin) {
|
|
8414
|
+
abortScopedLogin = null;
|
|
8415
|
+
detachIframe(m);
|
|
8416
|
+
swap(renderChoose(errText(err)));
|
|
8417
|
+
return;
|
|
8418
|
+
}
|
|
8179
8419
|
submit.disabled = false;
|
|
8180
8420
|
submit.textContent = "Continue";
|
|
8181
8421
|
setErr(errText(err));
|
|
@@ -10140,6 +10380,313 @@ function defaultInjectedEvmProvider() {
|
|
|
10140
10380
|
return null;
|
|
10141
10381
|
}
|
|
10142
10382
|
|
|
10383
|
+
/** Admission comes from an app-authenticated function invoking the Cloudflare
|
|
10384
|
+
* broadcast connector. No application membership is managed by this helper. */
|
|
10385
|
+
async function connectBroadcast(ticket, options) {
|
|
10386
|
+
var _a, _b, _c, _d;
|
|
10387
|
+
const url = new URL(ticket.url);
|
|
10388
|
+
if (url.username || url.password || url.search || url.hash || (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))))
|
|
10389
|
+
throw new Error("Invalid broadcast URL");
|
|
10390
|
+
if (!/^[a-f0-9]{64}$/.test(ticket.token) || ticket.transport !== "webrtc-broadcast" || !Number.isFinite(ticket.expiresAt) || ticket.expiresAt <= Date.now()
|
|
10391
|
+
|| !Number.isInteger(ticket.maxSeconds) || ticket.maxSeconds < 15 || ticket.maxSeconds > 300
|
|
10392
|
+
|| !["publisher", "listener"].includes(ticket.role) || ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted))
|
|
10393
|
+
throw new Error("Invalid or expired broadcast ticket");
|
|
10394
|
+
const publisher = ticket.role === "publisher";
|
|
10395
|
+
const tracks = publisher ? [(_b = options.microphone) === null || _b === void 0 ? void 0 : _b.getAudioTracks()[0], (_c = options.assistant) === null || _c === void 0 ? void 0 : _c.getAudioTracks()[0]] : [];
|
|
10396
|
+
if (publisher && tracks.some(track => !track || track.readyState !== "live"))
|
|
10397
|
+
throw new Error("Microphone and assistant audio tracks are required");
|
|
10398
|
+
if (!publisher && !options.audio)
|
|
10399
|
+
throw new Error("Two playback elements are required");
|
|
10400
|
+
const peer = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.cloudflare.com:3478" }] });
|
|
10401
|
+
let stopped = false, stopPromise;
|
|
10402
|
+
let resolveClosed;
|
|
10403
|
+
const closed = new Promise(resolve => { resolveClosed = resolve; });
|
|
10404
|
+
let disconnectTimer;
|
|
10405
|
+
const request = async (method, body) => {
|
|
10406
|
+
const response = await fetch(ticket.url, Object.assign(Object.assign({ method, headers: { Authorization: `Bearer ${ticket.token}`, "Content-Type": "application/json" } }, (body === undefined ? {} : { body: JSON.stringify(body) })), { signal: AbortSignal.timeout(15000), redirect: "error", cache: "no-store" }));
|
|
10407
|
+
if (!response.ok)
|
|
10408
|
+
throw new Error(`Broadcast request failed (${response.status})`);
|
|
10409
|
+
return response.json();
|
|
10410
|
+
};
|
|
10411
|
+
const connection = {
|
|
10412
|
+
closed,
|
|
10413
|
+
status: () => request("GET"),
|
|
10414
|
+
close() {
|
|
10415
|
+
var _a, _b;
|
|
10416
|
+
if (stopPromise)
|
|
10417
|
+
return stopPromise;
|
|
10418
|
+
stopped = true;
|
|
10419
|
+
clearTimeout(deadline);
|
|
10420
|
+
clearTimeout(disconnectTimer);
|
|
10421
|
+
peer.close();
|
|
10422
|
+
(_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", abort);
|
|
10423
|
+
tracks.forEach(t => t === null || t === void 0 ? void 0 : t.removeEventListener("ended", abort));
|
|
10424
|
+
(_b = options.audio) === null || _b === void 0 ? void 0 : _b.forEach(audio => { audio.pause(); audio.srcObject = null; });
|
|
10425
|
+
resolveClosed();
|
|
10426
|
+
// These tracks also belong to the primary voice connection. Closing the
|
|
10427
|
+
// broadcast must not stop the microphone or assistant's direct playback.
|
|
10428
|
+
stopPromise = (async () => {
|
|
10429
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
10430
|
+
try {
|
|
10431
|
+
await request("DELETE");
|
|
10432
|
+
return;
|
|
10433
|
+
}
|
|
10434
|
+
catch (error) {
|
|
10435
|
+
if (attempt === 2)
|
|
10436
|
+
throw error;
|
|
10437
|
+
await new Promise(resolve => setTimeout(resolve, 250 * (attempt + 1)));
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
})();
|
|
10441
|
+
return stopPromise;
|
|
10442
|
+
},
|
|
10443
|
+
};
|
|
10444
|
+
const abort = () => { void connection.close().catch(() => { }); };
|
|
10445
|
+
const deadline = setTimeout(abort, ticket.maxSeconds * 1000);
|
|
10446
|
+
(_d = options.signal) === null || _d === void 0 ? void 0 : _d.addEventListener("abort", abort, { once: true });
|
|
10447
|
+
tracks.forEach(track => track === null || track === void 0 ? void 0 : track.addEventListener("ended", abort, { once: true }));
|
|
10448
|
+
let audioMids = [];
|
|
10449
|
+
peer.addEventListener("track", event => {
|
|
10450
|
+
var _a;
|
|
10451
|
+
const audio = (_a = options.audio) === null || _a === void 0 ? void 0 : _a[audioMids.indexOf(event.transceiver.mid || "")];
|
|
10452
|
+
if (!audio) {
|
|
10453
|
+
abort();
|
|
10454
|
+
return;
|
|
10455
|
+
}
|
|
10456
|
+
audio.srcObject = new MediaStream([event.track]);
|
|
10457
|
+
void audio.play().catch(abort);
|
|
10458
|
+
});
|
|
10459
|
+
peer.addEventListener("connectionstatechange", () => {
|
|
10460
|
+
clearTimeout(disconnectTimer);
|
|
10461
|
+
if (peer.connectionState === "failed")
|
|
10462
|
+
abort();
|
|
10463
|
+
else if (peer.connectionState === "disconnected")
|
|
10464
|
+
disconnectTimer = setTimeout(abort, 5000);
|
|
10465
|
+
});
|
|
10466
|
+
try {
|
|
10467
|
+
let result;
|
|
10468
|
+
if (publisher) {
|
|
10469
|
+
for (const track of tracks)
|
|
10470
|
+
peer.addTransceiver(track, { direction: "sendonly", sendEncodings: [{ maxBitrate: 32000 }] });
|
|
10471
|
+
await peer.setLocalDescription(await peer.createOffer());
|
|
10472
|
+
result = await request("POST", { sdp: peer.localDescription.sdp });
|
|
10473
|
+
if (stopped)
|
|
10474
|
+
throw new Error("Broadcast cancelled");
|
|
10475
|
+
await peer.setRemoteDescription(result.sessionDescription);
|
|
10476
|
+
}
|
|
10477
|
+
else {
|
|
10478
|
+
result = await request("POST", {});
|
|
10479
|
+
if (stopped)
|
|
10480
|
+
throw new Error("Broadcast cancelled");
|
|
10481
|
+
audioMids = ["microphone", "assistant"].map(name => { var _a, _b; return ((_b = (_a = result.tracks) === null || _a === void 0 ? void 0 : _a.find(track => track.trackName === name)) === null || _b === void 0 ? void 0 : _b.mid) || ""; });
|
|
10482
|
+
if (audioMids.some(mid => !mid) || new Set(audioMids).size !== 2)
|
|
10483
|
+
throw new Error("Invalid broadcast audio tracks");
|
|
10484
|
+
await peer.setRemoteDescription(result.sessionDescription);
|
|
10485
|
+
await peer.setLocalDescription(await peer.createAnswer());
|
|
10486
|
+
await request("PUT", { sdp: peer.localDescription.sdp });
|
|
10487
|
+
}
|
|
10488
|
+
await new Promise((resolve, reject) => {
|
|
10489
|
+
var _a;
|
|
10490
|
+
const timer = setTimeout(() => done(new Error("Broadcast connection timed out")), 15000);
|
|
10491
|
+
const check = () => {
|
|
10492
|
+
if (stopped || ["failed", "closed"].includes(peer.connectionState))
|
|
10493
|
+
done(new Error("Broadcast connection ended"));
|
|
10494
|
+
else if (peer.connectionState === "connected")
|
|
10495
|
+
done();
|
|
10496
|
+
};
|
|
10497
|
+
const done = (error) => { var _a; clearTimeout(timer); peer.removeEventListener("connectionstatechange", check); (_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", check); error ? reject(error) : resolve(); };
|
|
10498
|
+
peer.addEventListener("connectionstatechange", check);
|
|
10499
|
+
(_a = options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener("abort", check, { once: true });
|
|
10500
|
+
check();
|
|
10501
|
+
});
|
|
10502
|
+
if (stopped)
|
|
10503
|
+
throw new Error("Broadcast cancelled");
|
|
10504
|
+
return connection;
|
|
10505
|
+
}
|
|
10506
|
+
catch (error) {
|
|
10507
|
+
await connection.close().catch(() => { });
|
|
10508
|
+
throw error;
|
|
10509
|
+
}
|
|
10510
|
+
}
|
|
10511
|
+
|
|
10512
|
+
/** Attach browser media directly to the connector's provider. Bounded observes
|
|
10513
|
+
* usage independently; data-channel messages never count as billing receipts. */
|
|
10514
|
+
async function connectServiceSession(ticket, options) {
|
|
10515
|
+
var _a, _b, _c;
|
|
10516
|
+
const url = new URL(ticket.url);
|
|
10517
|
+
if (url.username || url.password || url.search || url.hash || (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))))
|
|
10518
|
+
throw new Error("Invalid service session URL");
|
|
10519
|
+
if (!/^[a-f0-9]{64}$/.test(ticket.token) || ticket.transport !== "webrtc-session" || !Number.isFinite(ticket.expiresAt) || ticket.expiresAt <= Date.now())
|
|
10520
|
+
throw new Error("Invalid or expired service session ticket");
|
|
10521
|
+
if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted)
|
|
10522
|
+
throw new Error("Service session aborted");
|
|
10523
|
+
const peer = new RTCPeerConnection();
|
|
10524
|
+
const audioStream = new MediaStream();
|
|
10525
|
+
const channel = peer.createDataChannel("oai-events");
|
|
10526
|
+
let ready = false, finished = false, closing = false;
|
|
10527
|
+
let resolveClosed, rejectClosed;
|
|
10528
|
+
let resolveReady, rejectReady;
|
|
10529
|
+
let pollTimer;
|
|
10530
|
+
const closed = new Promise((resolve, reject) => { resolveClosed = resolve; rejectClosed = reject; });
|
|
10531
|
+
void closed.catch(() => { });
|
|
10532
|
+
const connected = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
|
|
10533
|
+
void connected.catch(() => { });
|
|
10534
|
+
const request = async (method, body) => {
|
|
10535
|
+
const response = await fetch(ticket.url, Object.assign(Object.assign({ method, headers: Object.assign({ Authorization: `Bearer ${ticket.token}` }, (body === undefined ? {} : { "Content-Type": "application/json" })) }, (body === undefined ? {} : { body: JSON.stringify(body) })), { signal: AbortSignal.timeout(15000), cache: "no-store", redirect: "error" }));
|
|
10536
|
+
if (!response.ok)
|
|
10537
|
+
throw new Error(`Service session request failed (${response.status})`);
|
|
10538
|
+
return response.json();
|
|
10539
|
+
};
|
|
10540
|
+
const cleanup = () => {
|
|
10541
|
+
var _a;
|
|
10542
|
+
clearTimeout(startTimer);
|
|
10543
|
+
clearTimeout(pollTimer);
|
|
10544
|
+
(_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", abort);
|
|
10545
|
+
options.stream.getTracks().forEach(track => track.stop());
|
|
10546
|
+
options.audio.pause();
|
|
10547
|
+
options.audio.srcObject = null;
|
|
10548
|
+
channel.close();
|
|
10549
|
+
peer.close();
|
|
10550
|
+
};
|
|
10551
|
+
const fail = (error) => {
|
|
10552
|
+
if (finished)
|
|
10553
|
+
return;
|
|
10554
|
+
finished = true;
|
|
10555
|
+
rejectReady(error);
|
|
10556
|
+
rejectClosed(error);
|
|
10557
|
+
cleanup();
|
|
10558
|
+
};
|
|
10559
|
+
const poll = async (until) => {
|
|
10560
|
+
if (finished)
|
|
10561
|
+
return;
|
|
10562
|
+
try {
|
|
10563
|
+
const state = await request("GET");
|
|
10564
|
+
if (state.state === "closed" && state.finalUsage === true && Number.isFinite(state.seconds) && state.seconds >= 0 && Number.isSafeInteger(state.chargedMicroUsd) && state.chargedMicroUsd >= 0) {
|
|
10565
|
+
finished = true;
|
|
10566
|
+
resolveClosed({ seconds: state.seconds, chargedMicroUsd: state.chargedMicroUsd, finalUsage: true });
|
|
10567
|
+
if (!ready)
|
|
10568
|
+
rejectReady(new Error("Service session ended before startup"));
|
|
10569
|
+
cleanup();
|
|
10570
|
+
return;
|
|
10571
|
+
}
|
|
10572
|
+
if (["waiting", "creating", "active"].includes(state.state))
|
|
10573
|
+
await request("DELETE");
|
|
10574
|
+
if (state.state === "attention") {
|
|
10575
|
+
fail(new Error("Service session requires reconciliation"));
|
|
10576
|
+
return;
|
|
10577
|
+
}
|
|
10578
|
+
}
|
|
10579
|
+
catch ( /* bounded retries; never invent a receipt after a network failure */_a) { /* bounded retries; never invent a receipt after a network failure */ }
|
|
10580
|
+
if (Date.now() >= until) {
|
|
10581
|
+
fail(new Error("Service session finalization unconfirmed; check session status"));
|
|
10582
|
+
return;
|
|
10583
|
+
}
|
|
10584
|
+
pollTimer = setTimeout(() => { void poll(until); }, 500);
|
|
10585
|
+
};
|
|
10586
|
+
const session = {
|
|
10587
|
+
audioStream,
|
|
10588
|
+
closed,
|
|
10589
|
+
send(event) {
|
|
10590
|
+
if (!ready || closing || finished || channel.readyState !== "open")
|
|
10591
|
+
throw new Error("Service session is not accepting messages");
|
|
10592
|
+
const json = JSON.stringify(event);
|
|
10593
|
+
if (new TextEncoder().encode(json).byteLength > 65536 || channel.bufferedAmount > 256000)
|
|
10594
|
+
throw new Error("Service session backpressure limit");
|
|
10595
|
+
channel.send(json);
|
|
10596
|
+
},
|
|
10597
|
+
close() {
|
|
10598
|
+
if (!closing && !finished) {
|
|
10599
|
+
closing = true;
|
|
10600
|
+
options.stream.getTracks().forEach(track => track.stop());
|
|
10601
|
+
options.audio.pause();
|
|
10602
|
+
options.audio.srcObject = null;
|
|
10603
|
+
// Keep WebRTC alive while the server collects final usage. HTTP close
|
|
10604
|
+
// also works when the provider data channel is already gone.
|
|
10605
|
+
void request("DELETE").catch(() => { }).then(() => poll(Date.now() + 45000));
|
|
10606
|
+
}
|
|
10607
|
+
return closed;
|
|
10608
|
+
},
|
|
10609
|
+
};
|
|
10610
|
+
const abort = () => { void session.close(); };
|
|
10611
|
+
const startTimer = setTimeout(() => { void request("DELETE").catch(() => { }); fail(new Error("Service session startup timed out")); }, 30000);
|
|
10612
|
+
(_b = options.signal) === null || _b === void 0 ? void 0 : _b.addEventListener("abort", abort, { once: true });
|
|
10613
|
+
peer.addEventListener("track", ({ track }) => { if (closing || finished)
|
|
10614
|
+
return; audioStream.addTrack(track); options.audio.srcObject = audioStream; void options.audio.play().catch(() => { void session.close(); fail(new Error("Audio playback was blocked")); }); });
|
|
10615
|
+
channel.addEventListener("message", ({ data }) => {
|
|
10616
|
+
var _a;
|
|
10617
|
+
if (finished || closing)
|
|
10618
|
+
return;
|
|
10619
|
+
let event;
|
|
10620
|
+
try {
|
|
10621
|
+
event = JSON.parse(String(data));
|
|
10622
|
+
}
|
|
10623
|
+
catch (_b) {
|
|
10624
|
+
return;
|
|
10625
|
+
}
|
|
10626
|
+
if ((event === null || event === void 0 ? void 0 : event.type) === "session.started" && !ready) {
|
|
10627
|
+
ready = true;
|
|
10628
|
+
clearTimeout(startTimer);
|
|
10629
|
+
resolveReady();
|
|
10630
|
+
}
|
|
10631
|
+
if ((event === null || event === void 0 ? void 0 : event.type) === "session.closed")
|
|
10632
|
+
void session.close();
|
|
10633
|
+
try {
|
|
10634
|
+
(_a = options.onEvent) === null || _a === void 0 ? void 0 : _a.call(options, event);
|
|
10635
|
+
}
|
|
10636
|
+
catch ( /* UI callback failure cannot own cleanup */_c) { /* UI callback failure cannot own cleanup */ }
|
|
10637
|
+
});
|
|
10638
|
+
peer.addEventListener("connectionstatechange", () => { if (["failed", "closed"].includes(peer.connectionState) && !finished)
|
|
10639
|
+
void session.close(); });
|
|
10640
|
+
channel.addEventListener("close", () => { if (!finished)
|
|
10641
|
+
void session.close(); });
|
|
10642
|
+
try {
|
|
10643
|
+
for (const track of options.stream.getAudioTracks())
|
|
10644
|
+
peer.addTrack(track, options.stream);
|
|
10645
|
+
if (!options.stream.getAudioTracks().length)
|
|
10646
|
+
throw new Error("An audio track is required");
|
|
10647
|
+
await peer.setLocalDescription(await peer.createOffer());
|
|
10648
|
+
if (peer.iceGatheringState !== "complete")
|
|
10649
|
+
await new Promise((resolve, reject) => {
|
|
10650
|
+
var _a;
|
|
10651
|
+
const timer = setTimeout(() => done(new Error("Service session ICE gathering timed out")), 10000);
|
|
10652
|
+
const done = (error) => {
|
|
10653
|
+
var _a;
|
|
10654
|
+
clearTimeout(timer);
|
|
10655
|
+
peer.removeEventListener("icegatheringstatechange", check);
|
|
10656
|
+
peer.removeEventListener("connectionstatechange", check);
|
|
10657
|
+
(_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", check);
|
|
10658
|
+
error ? reject(error) : resolve();
|
|
10659
|
+
};
|
|
10660
|
+
const check = () => {
|
|
10661
|
+
var _a;
|
|
10662
|
+
if (closing || finished || ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) || peer.connectionState === "closed")
|
|
10663
|
+
done(new Error("Service session aborted during ICE gathering"));
|
|
10664
|
+
else if (peer.iceGatheringState === "complete")
|
|
10665
|
+
done();
|
|
10666
|
+
};
|
|
10667
|
+
peer.addEventListener("icegatheringstatechange", check);
|
|
10668
|
+
peer.addEventListener("connectionstatechange", check);
|
|
10669
|
+
(_a = options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener("abort", check, { once: true });
|
|
10670
|
+
check();
|
|
10671
|
+
});
|
|
10672
|
+
if (closing || finished)
|
|
10673
|
+
throw new Error("Service session aborted before admission");
|
|
10674
|
+
if (!((_c = peer.localDescription) === null || _c === void 0 ? void 0 : _c.sdp.includes("\r\na=candidate:")))
|
|
10675
|
+
throw new Error("Service session has no usable ICE candidates");
|
|
10676
|
+
const answer = await request("POST", { sdp: peer.localDescription.sdp });
|
|
10677
|
+
if (closing || finished)
|
|
10678
|
+
throw new Error("Service session aborted during admission");
|
|
10679
|
+
await peer.setRemoteDescription({ type: "answer", sdp: answer.sdp });
|
|
10680
|
+
await connected;
|
|
10681
|
+
return session;
|
|
10682
|
+
}
|
|
10683
|
+
catch (error) {
|
|
10684
|
+
void request("DELETE").catch(() => { });
|
|
10685
|
+
fail(error instanceof Error ? error : new Error("Service session startup failed"));
|
|
10686
|
+
throw error;
|
|
10687
|
+
}
|
|
10688
|
+
}
|
|
10689
|
+
|
|
10143
10690
|
exports.BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA = BUILD_DELEGATED_REVIEW_CAPABILITY_SCHEMA;
|
|
10144
10691
|
exports.BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA = BUILD_REVIEW_CAPABILITY_REFRESH_SCHEMA;
|
|
10145
10692
|
exports.BUILD_REVIEW_CAPABILITY_SCHEMA = BUILD_REVIEW_CAPABILITY_SCHEMA;
|
|
@@ -10159,6 +10706,8 @@ exports.builds = builds;
|
|
|
10159
10706
|
exports.completeLoginFromRedirect = completeLoginFromRedirect;
|
|
10160
10707
|
exports.completeLoginInPopup = completeLoginInPopup;
|
|
10161
10708
|
exports.confirmAndCheckTransaction = confirmAndCheckTransaction;
|
|
10709
|
+
exports.connectBroadcast = connectBroadcast;
|
|
10710
|
+
exports.connectServiceSession = connectServiceSession;
|
|
10162
10711
|
exports.defaultInjectedEvmProvider = defaultInjectedEvmProvider;
|
|
10163
10712
|
exports.defaultInjectedProvider = defaultInjectedProvider;
|
|
10164
10713
|
exports.ensureSharedWalletLoginReady = ensureSharedWalletLoginReady;
|
|
@@ -10171,6 +10720,8 @@ exports.getCurrentUser = getCurrentUser;
|
|
|
10171
10720
|
exports.getIdToken = getIdToken;
|
|
10172
10721
|
exports.getOrCreateTurnkeyWallet = getOrCreateTurnkeyWallet;
|
|
10173
10722
|
exports.getPlatform = getPlatform;
|
|
10723
|
+
exports.getTurnkeyWalletRecoveryUrl = getTurnkeyWalletRecoveryUrl;
|
|
10724
|
+
exports.getTurnkeyWalletSessionState = getTurnkeyWalletSessionState;
|
|
10174
10725
|
exports.hasTurnkeyWallet = hasTurnkeyWallet;
|
|
10175
10726
|
exports.init = init;
|
|
10176
10727
|
exports.login = login;
|
|
@@ -10206,4 +10757,4 @@ exports.useAuth = useAuth;
|
|
|
10206
10757
|
exports.useBuildRun = useBuildRun;
|
|
10207
10758
|
exports.useQuery = useQuery;
|
|
10208
10759
|
exports.waitForAuthReady = waitForAuthReady;
|
|
10209
|
-
//# sourceMappingURL=index-
|
|
10760
|
+
//# sourceMappingURL=index-BrU_tGd-.js.map
|