@bounded-sh/client 0.0.31 → 0.0.33
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/index.d.ts +10 -18
- package/dist/auth/providers/email-auth-provider.d.ts +6 -13
- package/dist/index.js +108 -560
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +109 -561
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +108 -560
- package/dist/index.native.js.map +1 -1
- package/dist/index.native.mjs +109 -561
- package/dist/index.native.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.native.js
CHANGED
|
@@ -3598,7 +3598,7 @@ class GuestAuthProvider {
|
|
|
3598
3598
|
// ReactNativeSessionManager. getActiveSessionManager() picks the right one by
|
|
3599
3599
|
// whether the RN manager was configured (NOT `typeof window`, which is truthy on
|
|
3600
3600
|
// RN too and would wrongly route to localStorage). Same selector the core
|
|
3601
|
-
// request path uses, so
|
|
3601
|
+
// request path uses, so hosted Bounded Auth sessions persist + authenticate on BOTH.
|
|
3602
3602
|
const sessionManager$1 = core.getActiveSessionManager;
|
|
3603
3603
|
const NO_WALLET = 'Email login is offchain — it has no Solana wallet and cannot sign/submit ' +
|
|
3604
3604
|
'transactions. Use a wallet provider for onchain actions, or link a wallet later.';
|
|
@@ -3627,191 +3627,13 @@ function userFromIdToken$1(idToken, fallbackEmail) {
|
|
|
3627
3627
|
// Email logins have no wallet — address is null (the User type allows it).
|
|
3628
3628
|
return { id, address: (c['custom:walletAddress'] || null), email, provider: emailProviderSingleton };
|
|
3629
3629
|
}
|
|
3630
|
-
async function humanAuthBase() {
|
|
3631
|
-
const cfg = await core.getConfig();
|
|
3632
|
-
const base = cfg.humanAuthApiUrl || 'https://auth.bounded.sh';
|
|
3633
|
-
return base.replace(/\/$/, '');
|
|
3634
|
-
}
|
|
3635
|
-
async function post(path, body) {
|
|
3636
|
-
const base = await humanAuthBase();
|
|
3637
|
-
let res;
|
|
3638
|
-
try {
|
|
3639
|
-
// Credential-free + no custom header: /email and /verify are open-CORS
|
|
3640
|
-
// (ACAO:*) public OTP routes, so this works inline from ANY app origin (no
|
|
3641
|
-
// popup, no redirect). The OTP is the body secret; the token returns in the
|
|
3642
|
-
// body. Adding credentials or a custom header would break the open-CORS
|
|
3643
|
-
// preflight — keep this a simple JSON POST.
|
|
3644
|
-
res = await fetch(base + path, {
|
|
3645
|
-
method: 'POST',
|
|
3646
|
-
headers: { 'content-type': 'application/json' },
|
|
3647
|
-
body: JSON.stringify(body),
|
|
3648
|
-
});
|
|
3649
|
-
}
|
|
3650
|
-
catch (err) {
|
|
3651
|
-
throw new Error(`Bounded auth request to ${path} failed: ${(err === null || err === void 0 ? void 0 : err.message) || err}`);
|
|
3652
|
-
}
|
|
3653
|
-
let data = null;
|
|
3654
|
-
try {
|
|
3655
|
-
data = await res.json();
|
|
3656
|
-
}
|
|
3657
|
-
catch ( /* non-JSON */_a) { /* non-JSON */ }
|
|
3658
|
-
if (!res.ok) {
|
|
3659
|
-
const msg = (data && (data.error || data.message)) || `HTTP ${res.status}`;
|
|
3660
|
-
throw new Error(String(msg));
|
|
3661
|
-
}
|
|
3662
|
-
return data || {};
|
|
3663
|
-
}
|
|
3664
|
-
// ---------------------------------------------------------------------------
|
|
3665
|
-
// headless primitives (used by the modal AND exported for custom UIs)
|
|
3666
|
-
// ---------------------------------------------------------------------------
|
|
3667
|
-
function normalizePhoneNumber(value) {
|
|
3668
|
-
const compact = String(value || '').trim().replace(/[()\s.-]/g, '');
|
|
3669
|
-
return compact.startsWith('00') ? `+${compact.slice(2)}` : compact;
|
|
3670
|
-
}
|
|
3671
|
-
function assertPhoneNumber(value) {
|
|
3672
|
-
const phoneNumber = normalizePhoneNumber(value);
|
|
3673
|
-
if (!/^\+[1-9]\d{7,14}$/.test(phoneNumber)) {
|
|
3674
|
-
throw new Error('phoneNumber must be in E.164 format, e.g. +14155550132');
|
|
3675
|
-
}
|
|
3676
|
-
return phoneNumber;
|
|
3677
|
-
}
|
|
3678
|
-
/** Send a login code to `email`. Step 1 of the inline email-OTP flow. */
|
|
3679
|
-
async function sendEmailOtp$1(email) {
|
|
3680
|
-
const cfg = await core.getConfig();
|
|
3681
|
-
const e = (email || '').trim().toLowerCase();
|
|
3682
|
-
if (!e)
|
|
3683
|
-
throw new Error('email is required');
|
|
3684
|
-
const data = await post('/email', { email: e, appId: cfg.appId });
|
|
3685
|
-
return { ok: true, expiresInSeconds: data === null || data === void 0 ? void 0 : data.expiresInSeconds };
|
|
3686
|
-
}
|
|
3687
|
-
/** Send a login code by text message. Requires the Bounded issuer to have text OTP explicitly enabled and SMS configured. */
|
|
3688
|
-
async function sendTextOtp$1(phoneNumber) {
|
|
3689
|
-
const cfg = await core.getConfig();
|
|
3690
|
-
const phone = assertPhoneNumber(phoneNumber);
|
|
3691
|
-
const data = await post('/text', { phoneNumber: phone, appId: cfg.appId });
|
|
3692
|
-
return { ok: true, expiresInSeconds: data === null || data === void 0 ? void 0 : data.expiresInSeconds };
|
|
3693
|
-
}
|
|
3694
|
-
/**
|
|
3695
|
-
* Verify the code for `email` and establish the session. Step 2 of the flow.
|
|
3696
|
-
* Stores the Bounded id token (the realtime bearer) and sets the current user.
|
|
3697
|
-
*/
|
|
3698
|
-
async function verifyEmailOtp$1(email, otp) {
|
|
3699
|
-
setAuthLoading(true);
|
|
3700
|
-
try {
|
|
3701
|
-
const cfg = await core.getConfig();
|
|
3702
|
-
const e = (email || '').trim().toLowerCase();
|
|
3703
|
-
const code = (otp || '').trim();
|
|
3704
|
-
if (!e || !code)
|
|
3705
|
-
throw new Error('email and code are required');
|
|
3706
|
-
const data = await post('/verify', { email: e, appId: cfg.appId, otp: code });
|
|
3707
|
-
const idToken = data === null || data === void 0 ? void 0 : data.idToken;
|
|
3708
|
-
if (!idToken)
|
|
3709
|
-
throw new Error('No token returned from verification.');
|
|
3710
|
-
const user = userFromIdToken$1(idToken, e);
|
|
3711
|
-
// Store the idToken as BOTH id + access (it is the JWT the realtime worker
|
|
3712
|
-
// verifies and carries `exp`, so the session-manager expiry check works).
|
|
3713
|
-
// address is "" for email (no wallet); the realtime bearer is the idToken.
|
|
3714
|
-
// Persist the rotating refresh token + the issuer base (humanAuthApiUrl) so the
|
|
3715
|
-
// session manager can silently renew via {issuer}/session/refresh — sessions now
|
|
3716
|
-
// live as long as the 30d refresh token instead of dying at the access TTL.
|
|
3717
|
-
const refreshToken = typeof (data === null || data === void 0 ? void 0 : data.refreshToken) === 'string' ? data.refreshToken : '';
|
|
3718
|
-
const issuer = (cfg.humanAuthApiUrl || '').replace(/\/$/, '');
|
|
3719
|
-
await sessionManager$1().storeSession(String(user.address || ''), idToken, idToken, refreshToken, issuer);
|
|
3720
|
-
setCurrentUser(user);
|
|
3721
|
-
return user;
|
|
3722
|
-
}
|
|
3723
|
-
finally {
|
|
3724
|
-
setAuthLoading(false);
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3727
|
-
/**
|
|
3728
|
-
* Verify the text-message code for `phoneNumber` and establish the session.
|
|
3729
|
-
* Phone-only users have `user.email === null`; use `user.id` for ownership.
|
|
3730
|
-
*/
|
|
3731
|
-
async function verifyTextOtp$1(phoneNumber, otp) {
|
|
3732
|
-
setAuthLoading(true);
|
|
3733
|
-
try {
|
|
3734
|
-
const cfg = await core.getConfig();
|
|
3735
|
-
const phone = assertPhoneNumber(phoneNumber);
|
|
3736
|
-
const code = (otp || '').trim();
|
|
3737
|
-
if (!code)
|
|
3738
|
-
throw new Error('code is required');
|
|
3739
|
-
const data = await post('/text/verify', { phoneNumber: phone, appId: cfg.appId, otp: code });
|
|
3740
|
-
const idToken = data === null || data === void 0 ? void 0 : data.idToken;
|
|
3741
|
-
if (!idToken)
|
|
3742
|
-
throw new Error('No token returned from verification.');
|
|
3743
|
-
const user = userFromIdToken$1(idToken);
|
|
3744
|
-
const refreshToken = typeof (data === null || data === void 0 ? void 0 : data.refreshToken) === 'string' ? data.refreshToken : '';
|
|
3745
|
-
const issuer = (cfg.humanAuthApiUrl || '').replace(/\/$/, '');
|
|
3746
|
-
await sessionManager$1().storeSession(String(user.address || ''), idToken, idToken, refreshToken, issuer);
|
|
3747
|
-
setCurrentUser(user);
|
|
3748
|
-
return user;
|
|
3749
|
-
}
|
|
3750
|
-
finally {
|
|
3751
|
-
setAuthLoading(false);
|
|
3752
|
-
}
|
|
3753
|
-
}
|
|
3754
|
-
/**
|
|
3755
|
-
* UPGRADE an anonymous (guest) session by LINKING an email credential — Firebase
|
|
3756
|
-
* `linkWithCredential` parity. The guest's `guestToken` is sent to the auth issuer
|
|
3757
|
-
* which, IF the email is brand-new, makes the new account ADOPT the guest's id so
|
|
3758
|
-
* `@user.id` is preserved and the guest's data carries over. If the email already
|
|
3759
|
-
* exists, the user just signs into it (stays separate; `linked:false`). On success
|
|
3760
|
-
* the upgraded session replaces the guest session and the user is no longer
|
|
3761
|
-
* anonymous. `guestToken` is captured by the caller BEFORE switching auth methods.
|
|
3762
|
-
*/
|
|
3763
|
-
async function linkEmailWithGuest(email, otp, guestToken) {
|
|
3764
|
-
var _a;
|
|
3765
|
-
setAuthLoading(true);
|
|
3766
|
-
try {
|
|
3767
|
-
const cfg = await core.getConfig();
|
|
3768
|
-
const e = (email || '').trim().toLowerCase();
|
|
3769
|
-
const code = (otp || '').trim();
|
|
3770
|
-
if (!e || !code)
|
|
3771
|
-
throw new Error('email and code are required');
|
|
3772
|
-
if (!guestToken)
|
|
3773
|
-
throw new Error('an active anonymous session is required to link an email');
|
|
3774
|
-
// Capture the OLD guest session's refresh token + issuer BEFORE it's overwritten,
|
|
3775
|
-
// so we can revoke the guest refresh family after the upgrade. Otherwise a stolen
|
|
3776
|
-
// guest refresh token would keep minting access to the now-upgraded identity.
|
|
3777
|
-
const oldRefreshToken = sessionManager$1().getRefreshToken();
|
|
3778
|
-
const oldIssuer = (_a = sessionManager$1().getIssuer()) !== null && _a !== void 0 ? _a : undefined;
|
|
3779
|
-
const data = await post('/link/email', { email: e, appId: cfg.appId, otp: code, guestToken });
|
|
3780
|
-
const idToken = data === null || data === void 0 ? void 0 : data.idToken;
|
|
3781
|
-
if (!idToken)
|
|
3782
|
-
throw new Error('No token returned from link.');
|
|
3783
|
-
const user = userFromIdToken$1(idToken, e);
|
|
3784
|
-
// Persist the REAL rotating refresh token + human issuer so the upgraded session
|
|
3785
|
-
// silently renews (was previously stored as '' -> upgraded users couldn't refresh).
|
|
3786
|
-
const refreshToken = typeof (data === null || data === void 0 ? void 0 : data.refreshToken) === 'string' ? data.refreshToken : '';
|
|
3787
|
-
const issuer = (cfg.humanAuthApiUrl || '').replace(/\/$/, '');
|
|
3788
|
-
await sessionManager$1().storeSession(String(user.address || ''), idToken, idToken, refreshToken, issuer);
|
|
3789
|
-
setCurrentUser(user);
|
|
3790
|
-
// Best-effort revoke of the old guest refresh family (upgrade is already committed;
|
|
3791
|
-
// a revoke failure must not break the new session).
|
|
3792
|
-
if (oldRefreshToken && oldRefreshToken !== refreshToken) {
|
|
3793
|
-
try {
|
|
3794
|
-
await core.revokeSession(oldRefreshToken, oldIssuer);
|
|
3795
|
-
}
|
|
3796
|
-
catch ( /* best-effort */_b) { /* best-effort */ }
|
|
3797
|
-
}
|
|
3798
|
-
return { user, linked: !!(data === null || data === void 0 ? void 0 : data.linked), isNewAccount: !!(data === null || data === void 0 ? void 0 : data.isNewAccount) };
|
|
3799
|
-
}
|
|
3800
|
-
finally {
|
|
3801
|
-
setAuthLoading(false);
|
|
3802
|
-
}
|
|
3803
|
-
}
|
|
3804
3630
|
// ---------------------------------------------------------------------------
|
|
3805
3631
|
// provider
|
|
3806
3632
|
// ---------------------------------------------------------------------------
|
|
3807
3633
|
class EmailAuthProvider {
|
|
3808
3634
|
async login() {
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
"sendEmailOtp(email) then verifyEmailOtp(email, code), or, when text OTP is enabled, " +
|
|
3812
|
-
"sendTextOtp(phone) then verifyTextOtp(phone, code). Render your own OTP inputs. The built-in modal is web-only.");
|
|
3813
|
-
}
|
|
3814
|
-
return openEmailModal();
|
|
3635
|
+
throw new Error('login() no longer opens inline email OTP. Use loginWithRedirect({ redirectUri }) ' +
|
|
3636
|
+
'or loginWithPopup({ redirectUri }) for hosted Bounded Auth.');
|
|
3815
3637
|
}
|
|
3816
3638
|
async restoreSession() {
|
|
3817
3639
|
const sm = sessionManager$1();
|
|
@@ -3836,283 +3658,6 @@ class EmailAuthProvider {
|
|
|
3836
3658
|
async getNativeMethods() { return {}; }
|
|
3837
3659
|
}
|
|
3838
3660
|
const emailProviderSingleton = new EmailAuthProvider();
|
|
3839
|
-
// ---------------------------------------------------------------------------
|
|
3840
|
-
// inline modal (web only) — mobile-first, no popup, no redirect
|
|
3841
|
-
// ---------------------------------------------------------------------------
|
|
3842
|
-
function openEmailModal() {
|
|
3843
|
-
return new Promise((resolve) => {
|
|
3844
|
-
var _a;
|
|
3845
|
-
const root = createEmailModalTree();
|
|
3846
|
-
const style = document.createElement('style');
|
|
3847
|
-
style.textContent = MODAL_CSS;
|
|
3848
|
-
root.appendChild(style);
|
|
3849
|
-
document.body.appendChild(root);
|
|
3850
|
-
const $ = (id) => root.querySelector('#' + id);
|
|
3851
|
-
const emailStep = $('ba-email-step');
|
|
3852
|
-
const otpStep = $('ba-otp-step');
|
|
3853
|
-
const emailInput = $('ba-email');
|
|
3854
|
-
const otpInput = $('ba-otp');
|
|
3855
|
-
const sentTo = $('ba-sent-to');
|
|
3856
|
-
const msg = $('ba-msg');
|
|
3857
|
-
const emailBtn = $('ba-email-btn');
|
|
3858
|
-
const otpBtn = $('ba-otp-btn');
|
|
3859
|
-
let email = '';
|
|
3860
|
-
let done = false;
|
|
3861
|
-
const setMsg = (t, err = false) => { msg.textContent = t; msg.className = 'ba-msg' + (err ? ' err' : ''); };
|
|
3862
|
-
const close = (user) => {
|
|
3863
|
-
var _a;
|
|
3864
|
-
if (done)
|
|
3865
|
-
return;
|
|
3866
|
-
done = true;
|
|
3867
|
-
document.removeEventListener('keydown', onEsc);
|
|
3868
|
-
(_a = root.querySelector('.ba-overlay')) === null || _a === void 0 ? void 0 : _a.classList.add('out');
|
|
3869
|
-
setTimeout(() => root.remove(), 160);
|
|
3870
|
-
resolve(user);
|
|
3871
|
-
};
|
|
3872
|
-
const onEsc = (e) => { if (e.key === 'Escape')
|
|
3873
|
-
close(null); };
|
|
3874
|
-
document.addEventListener('keydown', onEsc);
|
|
3875
|
-
(_a = root.querySelector('.ba-overlay')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', (e) => {
|
|
3876
|
-
if (e.target.classList.contains('ba-overlay'))
|
|
3877
|
-
close(null);
|
|
3878
|
-
});
|
|
3879
|
-
$('ba-close').addEventListener('click', () => close(null));
|
|
3880
|
-
requestAnimationFrame(() => emailInput.focus());
|
|
3881
|
-
emailBtn.addEventListener('click', async () => {
|
|
3882
|
-
email = emailInput.value.trim();
|
|
3883
|
-
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
|
|
3884
|
-
setMsg('Enter a valid email.', true);
|
|
3885
|
-
return;
|
|
3886
|
-
}
|
|
3887
|
-
emailBtn.disabled = true;
|
|
3888
|
-
setMsg('Sending code…');
|
|
3889
|
-
try {
|
|
3890
|
-
await sendEmailOtp$1(email);
|
|
3891
|
-
sentTo.textContent = email;
|
|
3892
|
-
emailStep.classList.add('hidden');
|
|
3893
|
-
otpStep.classList.remove('hidden');
|
|
3894
|
-
setMsg('');
|
|
3895
|
-
otpInput.focus();
|
|
3896
|
-
}
|
|
3897
|
-
catch (err) {
|
|
3898
|
-
setMsg((err === null || err === void 0 ? void 0 : err.message) || 'Could not send the code.', true);
|
|
3899
|
-
}
|
|
3900
|
-
finally {
|
|
3901
|
-
emailBtn.disabled = false;
|
|
3902
|
-
}
|
|
3903
|
-
});
|
|
3904
|
-
emailInput.addEventListener('keydown', (e) => { if (e.key === 'Enter')
|
|
3905
|
-
emailBtn.click(); });
|
|
3906
|
-
otpBtn.addEventListener('click', async () => {
|
|
3907
|
-
const code = otpInput.value.trim();
|
|
3908
|
-
if (!/^\d{4,8}$/.test(code)) {
|
|
3909
|
-
setMsg('Enter the code from your email.', true);
|
|
3910
|
-
return;
|
|
3911
|
-
}
|
|
3912
|
-
otpBtn.disabled = true;
|
|
3913
|
-
setMsg('Verifying…');
|
|
3914
|
-
try {
|
|
3915
|
-
const user = await verifyEmailOtp$1(email, code);
|
|
3916
|
-
close(user);
|
|
3917
|
-
}
|
|
3918
|
-
catch (err) {
|
|
3919
|
-
setMsg((err === null || err === void 0 ? void 0 : err.message) || 'That code did not work.', true);
|
|
3920
|
-
otpBtn.disabled = false;
|
|
3921
|
-
}
|
|
3922
|
-
});
|
|
3923
|
-
otpInput.addEventListener('keydown', (e) => { if (e.key === 'Enter')
|
|
3924
|
-
otpBtn.click(); });
|
|
3925
|
-
$('ba-back').addEventListener('click', () => {
|
|
3926
|
-
otpStep.classList.add('hidden');
|
|
3927
|
-
emailStep.classList.remove('hidden');
|
|
3928
|
-
setMsg('');
|
|
3929
|
-
emailInput.focus();
|
|
3930
|
-
});
|
|
3931
|
-
});
|
|
3932
|
-
}
|
|
3933
|
-
function createEmailModalTree() {
|
|
3934
|
-
const root = document.createElement('div');
|
|
3935
|
-
root.id = 'bounded-auth-modal';
|
|
3936
|
-
const overlay = document.createElement('div');
|
|
3937
|
-
overlay.className = 'ba-overlay';
|
|
3938
|
-
root.appendChild(overlay);
|
|
3939
|
-
const card = document.createElement('div');
|
|
3940
|
-
card.className = 'ba-card';
|
|
3941
|
-
card.setAttribute('role', 'dialog');
|
|
3942
|
-
card.setAttribute('aria-modal', 'true');
|
|
3943
|
-
card.setAttribute('aria-label', 'Sign in');
|
|
3944
|
-
overlay.appendChild(card);
|
|
3945
|
-
const close = document.createElement('button');
|
|
3946
|
-
close.id = 'ba-close';
|
|
3947
|
-
close.className = 'ba-x';
|
|
3948
|
-
close.setAttribute('aria-label', 'Close');
|
|
3949
|
-
close.type = 'button';
|
|
3950
|
-
close.textContent = '×';
|
|
3951
|
-
card.appendChild(close);
|
|
3952
|
-
// Brand row — the real Bounded mark (same geometry as bounded.sh) + wordmark.
|
|
3953
|
-
const brand = document.createElement('div');
|
|
3954
|
-
brand.className = 'ba-brand';
|
|
3955
|
-
const mark = document.createElement('span');
|
|
3956
|
-
mark.className = 'ba-mark';
|
|
3957
|
-
mark.setAttribute('aria-hidden', 'true');
|
|
3958
|
-
const svgNs = 'http://www.w3.org/2000/svg';
|
|
3959
|
-
const markSvg = document.createElementNS(svgNs, 'svg');
|
|
3960
|
-
markSvg.setAttribute('width', '24');
|
|
3961
|
-
markSvg.setAttribute('height', '24');
|
|
3962
|
-
markSvg.setAttribute('viewBox', '0 0 32 32');
|
|
3963
|
-
markSvg.setAttribute('fill', 'none');
|
|
3964
|
-
for (const [className, d] of [
|
|
3965
|
-
['ba-c ba-c1', 'M16 5 H5 V16'],
|
|
3966
|
-
['ba-c ba-c2', 'M16 5 H27 V16'],
|
|
3967
|
-
['ba-c ba-c3', 'M5 16 V27 H16'],
|
|
3968
|
-
['ba-c ba-c4', 'M27 16 V27 H16'],
|
|
3969
|
-
]) {
|
|
3970
|
-
const path = document.createElementNS(svgNs, 'path');
|
|
3971
|
-
path.setAttribute('class', className);
|
|
3972
|
-
path.setAttribute('d', d);
|
|
3973
|
-
markSvg.appendChild(path);
|
|
3974
|
-
}
|
|
3975
|
-
mark.appendChild(markSvg);
|
|
3976
|
-
brand.appendChild(mark);
|
|
3977
|
-
const word = document.createElement('span');
|
|
3978
|
-
word.className = 'ba-word';
|
|
3979
|
-
word.textContent = 'bounded';
|
|
3980
|
-
brand.appendChild(word);
|
|
3981
|
-
card.appendChild(brand);
|
|
3982
|
-
const title = document.createElement('h1');
|
|
3983
|
-
title.className = 'ba-title';
|
|
3984
|
-
title.textContent = 'Sign in';
|
|
3985
|
-
card.appendChild(title);
|
|
3986
|
-
const sub = document.createElement('p');
|
|
3987
|
-
sub.className = 'ba-subtitle';
|
|
3988
|
-
sub.textContent = 'Continue with your email — no wallet needed.';
|
|
3989
|
-
card.appendChild(sub);
|
|
3990
|
-
const emailStep = document.createElement('div');
|
|
3991
|
-
emailStep.id = 'ba-email-step';
|
|
3992
|
-
card.appendChild(emailStep);
|
|
3993
|
-
const emailLabel = document.createElement('label');
|
|
3994
|
-
emailLabel.className = 'ba-label';
|
|
3995
|
-
emailLabel.htmlFor = 'ba-email';
|
|
3996
|
-
emailLabel.textContent = 'Email';
|
|
3997
|
-
emailStep.appendChild(emailLabel);
|
|
3998
|
-
const emailInput = document.createElement('input');
|
|
3999
|
-
emailInput.id = 'ba-email';
|
|
4000
|
-
emailInput.className = 'ba-input';
|
|
4001
|
-
emailInput.type = 'email';
|
|
4002
|
-
emailInput.inputMode = 'email';
|
|
4003
|
-
emailInput.autocomplete = 'email';
|
|
4004
|
-
emailInput.placeholder = 'you@example.com';
|
|
4005
|
-
emailStep.appendChild(emailInput);
|
|
4006
|
-
const emailButton = document.createElement('button');
|
|
4007
|
-
emailButton.id = 'ba-email-btn';
|
|
4008
|
-
emailButton.className = 'ba-btn';
|
|
4009
|
-
emailButton.type = 'button';
|
|
4010
|
-
emailButton.textContent = 'Continue with email';
|
|
4011
|
-
emailStep.appendChild(emailButton);
|
|
4012
|
-
const otpStep = document.createElement('div');
|
|
4013
|
-
otpStep.id = 'ba-otp-step';
|
|
4014
|
-
otpStep.className = 'hidden';
|
|
4015
|
-
card.appendChild(otpStep);
|
|
4016
|
-
const otpLabel = document.createElement('label');
|
|
4017
|
-
otpLabel.className = 'ba-label';
|
|
4018
|
-
otpLabel.htmlFor = 'ba-otp';
|
|
4019
|
-
otpLabel.appendChild(document.createTextNode('Enter the code sent to '));
|
|
4020
|
-
const sentTo = document.createElement('span');
|
|
4021
|
-
sentTo.id = 'ba-sent-to';
|
|
4022
|
-
sentTo.className = 'ba-em';
|
|
4023
|
-
otpLabel.appendChild(sentTo);
|
|
4024
|
-
otpStep.appendChild(otpLabel);
|
|
4025
|
-
const otpInput = document.createElement('input');
|
|
4026
|
-
otpInput.id = 'ba-otp';
|
|
4027
|
-
otpInput.className = 'ba-input ba-code';
|
|
4028
|
-
otpInput.inputMode = 'numeric';
|
|
4029
|
-
otpInput.autocomplete = 'one-time-code';
|
|
4030
|
-
otpInput.maxLength = 8;
|
|
4031
|
-
otpInput.placeholder = '······';
|
|
4032
|
-
otpStep.appendChild(otpInput);
|
|
4033
|
-
const otpButton = document.createElement('button');
|
|
4034
|
-
otpButton.id = 'ba-otp-btn';
|
|
4035
|
-
otpButton.className = 'ba-btn';
|
|
4036
|
-
otpButton.type = 'button';
|
|
4037
|
-
otpButton.textContent = 'Verify & continue';
|
|
4038
|
-
otpStep.appendChild(otpButton);
|
|
4039
|
-
const backButton = document.createElement('button');
|
|
4040
|
-
backButton.id = 'ba-back';
|
|
4041
|
-
backButton.className = 'ba-link';
|
|
4042
|
-
backButton.type = 'button';
|
|
4043
|
-
backButton.textContent = 'Use a different email';
|
|
4044
|
-
otpStep.appendChild(backButton);
|
|
4045
|
-
const msg = document.createElement('div');
|
|
4046
|
-
msg.id = 'ba-msg';
|
|
4047
|
-
msg.className = 'ba-msg';
|
|
4048
|
-
card.appendChild(msg);
|
|
4049
|
-
const foot = document.createElement('div');
|
|
4050
|
-
foot.className = 'ba-foot';
|
|
4051
|
-
foot.textContent = 'Secured by Bounded';
|
|
4052
|
-
card.appendChild(foot);
|
|
4053
|
-
return root;
|
|
4054
|
-
}
|
|
4055
|
-
// Design language matches the hosted Bounded login (auth.bounded.sh) and bounded.sh:
|
|
4056
|
-
// dark editorial panel, green accent, Schibsted Grotesk / Newsreader / JetBrains Mono,
|
|
4057
|
-
// the Bounded mark drawing itself in. Fonts load best-effort (scoped @import); every
|
|
4058
|
-
// rule has a system fallback so the modal looks right even if Google Fonts is blocked.
|
|
4059
|
-
// All selectors are scoped under #bounded-auth-modal so we never leak into the host page.
|
|
4060
|
-
const MODAL_CSS = `
|
|
4061
|
-
@import url('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');
|
|
4062
|
-
#bounded-auth-modal{--ba-bg:#0a0a0b;--ba-panel:#0e0f11;--ba-inset:#060607;--ba-raise:#131416;
|
|
4063
|
-
--ba-line:rgba(232,230,222,.08);--ba-line-strong:rgba(232,230,222,.16);
|
|
4064
|
-
--ba-text:#e8e6de;--ba-dim:#9b9991;--ba-faint:#85837c;--ba-green:#58c98f;
|
|
4065
|
-
--ba-green-bg:rgba(88,201,143,.09);--ba-green-line:rgba(88,201,143,.28);--ba-red:#ec8488;
|
|
4066
|
-
--ba-body:'Schibsted Grotesk',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
|
|
4067
|
-
--ba-display:'Newsreader','Iowan Old Style',Georgia,serif;
|
|
4068
|
-
--ba-mono:'JetBrains Mono',ui-monospace,'SF Mono',Menlo,monospace;}
|
|
4069
|
-
#bounded-auth-modal .ba-overlay{position:fixed;inset:0;background:rgba(6,6,7,.66);backdrop-filter:blur(4px);
|
|
4070
|
-
display:flex;align-items:center;justify-content:center;z-index:2147483000;opacity:1;transition:opacity .16s ease;
|
|
4071
|
-
font-family:var(--ba-body);padding:16px;}
|
|
4072
|
-
#bounded-auth-modal .ba-overlay.out{opacity:0;}
|
|
4073
|
-
#bounded-auth-modal *{box-sizing:border-box;}
|
|
4074
|
-
#bounded-auth-modal .ba-card{width:100%;max-width:372px;background:var(--ba-panel);color:var(--ba-text);
|
|
4075
|
-
border:1px solid var(--ba-line);border-radius:12px;padding:28px 28px 22px;position:relative;
|
|
4076
|
-
box-shadow:0 1px 0 rgba(232,230,222,.03) inset,0 30px 80px -40px rgba(0,0,0,.8);animation:ba-in .18s ease;}
|
|
4077
|
-
@keyframes ba-in{from{transform:translateY(8px);opacity:.5}to{transform:translateY(0);opacity:1}}
|
|
4078
|
-
#bounded-auth-modal .ba-x{position:absolute;top:14px;right:14px;width:28px;height:28px;border:1px solid var(--ba-line);
|
|
4079
|
-
background:var(--ba-raise);color:var(--ba-faint);border-radius:7px;font-size:18px;line-height:1;cursor:pointer;
|
|
4080
|
-
transition:color .15s,border-color .15s;}
|
|
4081
|
-
#bounded-auth-modal .ba-x:hover{color:var(--ba-text);border-color:var(--ba-faint);}
|
|
4082
|
-
#bounded-auth-modal .ba-brand{display:flex;align-items:center;gap:10px;margin:0 0 24px;}
|
|
4083
|
-
#bounded-auth-modal .ba-mark{display:flex;color:var(--ba-text);}
|
|
4084
|
-
#bounded-auth-modal .ba-mark svg{overflow:visible;display:block;}
|
|
4085
|
-
#bounded-auth-modal .ba-c{stroke:currentColor;stroke-width:2.4;fill:none;}
|
|
4086
|
-
@media (prefers-reduced-motion:no-preference){
|
|
4087
|
-
#bounded-auth-modal .ba-c{stroke-dasharray:23;stroke-dashoffset:23;animation:ba-draw .4s cubic-bezier(.45,0,.2,1) forwards;}
|
|
4088
|
-
#bounded-auth-modal .ba-c1{animation-delay:.1s}#bounded-auth-modal .ba-c2{animation-delay:.38s}
|
|
4089
|
-
#bounded-auth-modal .ba-c3{animation-delay:.66s}#bounded-auth-modal .ba-c4{animation-delay:.94s}}
|
|
4090
|
-
@keyframes ba-draw{to{stroke-dashoffset:0}}
|
|
4091
|
-
#bounded-auth-modal .ba-word{font-family:var(--ba-body);font-weight:500;font-size:15px;letter-spacing:.2px;color:var(--ba-text);}
|
|
4092
|
-
#bounded-auth-modal .ba-title{font-family:var(--ba-display);font-weight:400;font-size:25px;letter-spacing:-.01em;margin:0 0 5px;}
|
|
4093
|
-
#bounded-auth-modal .ba-subtitle{color:var(--ba-dim);font-size:14px;margin:0 0 22px;}
|
|
4094
|
-
#bounded-auth-modal .ba-label{display:block;font-size:12px;color:var(--ba-dim);margin:0 0 7px;}
|
|
4095
|
-
#bounded-auth-modal .ba-em{color:var(--ba-text);font-weight:600;}
|
|
4096
|
-
#bounded-auth-modal .ba-input{width:100%;background:var(--ba-inset);border:1px solid var(--ba-line-strong);color:var(--ba-text);
|
|
4097
|
-
border-radius:6px;padding:11px 13px;font-family:var(--ba-body);font-size:15px;outline:none;transition:border-color .15s,box-shadow .15s;}
|
|
4098
|
-
#bounded-auth-modal .ba-input::placeholder{color:var(--ba-faint);}
|
|
4099
|
-
#bounded-auth-modal .ba-input:focus{border-color:var(--ba-green-line);box-shadow:0 0 0 3px var(--ba-green-bg);}
|
|
4100
|
-
#bounded-auth-modal .ba-code{font-family:var(--ba-mono);letter-spacing:8px;text-align:center;font-size:21px;padding-left:8px;}
|
|
4101
|
-
#bounded-auth-modal .ba-btn{width:100%;margin-top:16px;background:var(--ba-text);color:var(--ba-bg);
|
|
4102
|
-
border:0;border-radius:6px;padding:12px;font-family:var(--ba-body);font-size:14px;font-weight:600;cursor:pointer;
|
|
4103
|
-
transition:opacity .15s,transform .04s;}
|
|
4104
|
-
#bounded-auth-modal .ba-btn:hover{opacity:.9;}
|
|
4105
|
-
#bounded-auth-modal .ba-btn:active{transform:translateY(.5px);}
|
|
4106
|
-
#bounded-auth-modal .ba-btn:disabled{opacity:.45;cursor:default;}
|
|
4107
|
-
#bounded-auth-modal .ba-link{width:100%;margin-top:14px;background:none;border:none;color:var(--ba-dim);
|
|
4108
|
-
font-family:var(--ba-body);font-size:12.5px;cursor:pointer;}
|
|
4109
|
-
#bounded-auth-modal .ba-link:hover{color:var(--ba-text);}
|
|
4110
|
-
#bounded-auth-modal .ba-msg{min-height:16px;margin-top:16px;font-size:12.5px;color:var(--ba-dim);text-align:center;}
|
|
4111
|
-
#bounded-auth-modal .ba-msg.err{color:var(--ba-red);}
|
|
4112
|
-
#bounded-auth-modal .ba-foot{margin-top:26px;padding-top:16px;border-top:1px solid var(--ba-line);
|
|
4113
|
-
text-align:center;font-size:11px;color:var(--ba-faint);}
|
|
4114
|
-
#bounded-auth-modal .hidden{display:none;}
|
|
4115
|
-
@media (max-width:480px){#bounded-auth-modal .ba-card{max-width:none;}}`;
|
|
4116
3661
|
|
|
4117
3662
|
const SURFNET_RPC_URL = "https://surfpool.fly.dev";
|
|
4118
3663
|
const SUPPORTED_SOLANA_NETWORKS = new Set([
|
|
@@ -4263,14 +3808,35 @@ async function buildAuthorize(opts, guestHint) {
|
|
|
4263
3808
|
code_challenge: challenge, code_challenge_method: 'S256', state,
|
|
4264
3809
|
});
|
|
4265
3810
|
appendHostedLoginHints(params, opts);
|
|
4266
|
-
// Anonymous→real UPGRADE (linkWithRedirect):
|
|
4267
|
-
//
|
|
4268
|
-
//
|
|
4269
|
-
//
|
|
3811
|
+
// Anonymous→real UPGRADE (linkWithRedirect): the issuer needs the guest identity bound to
|
|
3812
|
+
// this request's PKCE code_challenge so the link completes single-use at the code→token
|
|
3813
|
+
// exchange. We do NOT put the raw guest id-token in the URL (it would land in server logs /
|
|
3814
|
+
// browser history — a replayable identity-capture primitive). Instead we exchange it for an
|
|
3815
|
+
// OPAQUE single-use handle server-side and carry only that.
|
|
4270
3816
|
if (guestHint)
|
|
4271
|
-
params.set('bounded_guest_hint', guestHint);
|
|
3817
|
+
params.set('bounded_guest_hint', await guestHintFor(guestHint));
|
|
4272
3818
|
return { url: `${base}/api/auth/oauth2/authorize?${params.toString()}`, pkce: { verifier, state, redirectUri } };
|
|
4273
3819
|
}
|
|
3820
|
+
/** Exchange a guest id-token for an opaque single-use handle via the issuer, so the raw token
|
|
3821
|
+
* never rides the authorize URL. Falls back to the raw token if the mint endpoint is
|
|
3822
|
+
* unavailable — the issuer accepts both, so an upgrade never breaks during rollout. */
|
|
3823
|
+
async function guestHintFor(guestToken) {
|
|
3824
|
+
try {
|
|
3825
|
+
const base = await issuerBase();
|
|
3826
|
+
const res = await fetch(`${base}/link/guest-handle`, {
|
|
3827
|
+
method: 'POST',
|
|
3828
|
+
headers: { 'content-type': 'application/json' },
|
|
3829
|
+
body: JSON.stringify({ guestToken }),
|
|
3830
|
+
});
|
|
3831
|
+
if (res.ok) {
|
|
3832
|
+
const data = await res.json().catch(() => null);
|
|
3833
|
+
if (data && typeof data.handle === 'string' && data.handle)
|
|
3834
|
+
return data.handle;
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
catch ( /* fall through to the raw-token fallback */_a) { /* fall through to the raw-token fallback */ }
|
|
3838
|
+
return guestToken;
|
|
3839
|
+
}
|
|
4274
3840
|
// Are we on React Native (no DOM)? OIDC dispatch uses this to choose the native
|
|
4275
3841
|
// expo-web-browser driver vs. the web full-page-redirect/popup driver.
|
|
4276
3842
|
function isNative() {
|
|
@@ -4372,7 +3938,8 @@ async function loginWithRedirect(opts) {
|
|
|
4372
3938
|
// Stash the verifier + state so the redirect_uri page can finish the exchange. sessionStorage
|
|
4373
3939
|
// is per-tab and cleared on close — appropriate for a short-lived auth handshake.
|
|
4374
3940
|
sessionStorage.setItem(PKCE_STORE_KEY, JSON.stringify(pkce));
|
|
4375
|
-
|
|
3941
|
+
setAuthLoading(true); // reflect "signing in…" right up to navigation; the return page's
|
|
3942
|
+
window.location.href = url; // completeLoginFromRedirect() keeps it true until the user resolves.
|
|
4376
3943
|
}
|
|
4377
3944
|
/** UPGRADE the current anonymous (guest) session by linking a SOCIAL/redirect login
|
|
4378
3945
|
* (e.g. Google) — Firebase `linkWithPopup`/redirect parity. Carries the guest's signed
|
|
@@ -4465,51 +4032,57 @@ async function loginWithPopup(opts) {
|
|
|
4465
4032
|
// Use the RESOLVED redirect_uri (buildAuthorize defaulted it to the current page on web),
|
|
4466
4033
|
// not opts.redirectUri which may be undefined.
|
|
4467
4034
|
const expectedOrigin = new URL(pkce.redirectUri).origin;
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
if (!raw)
|
|
4482
|
-
throw new Error('Missing PKCE state');
|
|
4483
|
-
const pkce = JSON.parse(raw);
|
|
4484
|
-
if (d.state !== pkce.state)
|
|
4485
|
-
throw new Error('OAuth state mismatch');
|
|
4486
|
-
const user = await exchangeCode(d.code, pkce.verifier, pkce.redirectUri);
|
|
4487
|
-
sessionStorage.removeItem(PKCE_STORE_KEY);
|
|
4035
|
+
setAuthLoading(true); // reflect "signing in…" while the popup is open
|
|
4036
|
+
try {
|
|
4037
|
+
return await new Promise((resolve, reject) => {
|
|
4038
|
+
let settled = false;
|
|
4039
|
+
let timer;
|
|
4040
|
+
const cleanup = () => { settled = true; clearInterval(timer); window.removeEventListener('message', onMsg); };
|
|
4041
|
+
const onMsg = async (ev) => {
|
|
4042
|
+
if (ev.origin !== expectedOrigin)
|
|
4043
|
+
return; // only trust the redirect_uri origin
|
|
4044
|
+
const d = ev.data;
|
|
4045
|
+
if (!d || d.type !== 'bounded-oidc-code' || typeof d.code !== 'string')
|
|
4046
|
+
return;
|
|
4047
|
+
cleanup();
|
|
4488
4048
|
try {
|
|
4489
|
-
|
|
4049
|
+
const raw = sessionStorage.getItem(PKCE_STORE_KEY);
|
|
4050
|
+
if (!raw)
|
|
4051
|
+
throw new Error('Missing PKCE state');
|
|
4052
|
+
const pkce = JSON.parse(raw);
|
|
4053
|
+
if (d.state !== pkce.state)
|
|
4054
|
+
throw new Error('OAuth state mismatch');
|
|
4055
|
+
const user = await exchangeCode(d.code, pkce.verifier, pkce.redirectUri);
|
|
4056
|
+
sessionStorage.removeItem(PKCE_STORE_KEY);
|
|
4057
|
+
try {
|
|
4058
|
+
popup.close();
|
|
4059
|
+
}
|
|
4060
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
4061
|
+
resolve(user);
|
|
4490
4062
|
}
|
|
4491
|
-
catch (
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4063
|
+
catch (e) {
|
|
4064
|
+
reject(e);
|
|
4065
|
+
}
|
|
4066
|
+
};
|
|
4067
|
+
window.addEventListener('message', onMsg);
|
|
4068
|
+
// The popup posts its code and then closes ITSELF, so a naive "popup.closed -> reject"
|
|
4069
|
+
// races the just-posted message and would report a spurious "Login window closed" on a
|
|
4070
|
+
// SUCCESSFUL login. When we see it closed, wait a beat for that pending message to settle
|
|
4071
|
+
// before declaring the window closed.
|
|
4072
|
+
timer = setInterval(() => {
|
|
4073
|
+
if (popup.closed) {
|
|
4074
|
+
clearInterval(timer);
|
|
4075
|
+
setTimeout(() => { if (!settled) {
|
|
4076
|
+
cleanup();
|
|
4077
|
+
reject(new Error('Login window closed'));
|
|
4078
|
+
} }, 500);
|
|
4079
|
+
}
|
|
4080
|
+
}, 500);
|
|
4081
|
+
});
|
|
4082
|
+
}
|
|
4083
|
+
finally {
|
|
4084
|
+
setAuthLoading(false);
|
|
4085
|
+
}
|
|
4513
4086
|
}
|
|
4514
4087
|
/** Call this on the redirect_uri page when login was opened via loginWithPopup(): posts the
|
|
4515
4088
|
* code back to the opener and closes. (For the redirect variant use completeLoginFromRedirect.) */
|
|
@@ -4768,6 +4341,12 @@ async function getAuthProvider(config) {
|
|
|
4768
4341
|
currentAuthProviderConfigKey = nextProviderConfigKey;
|
|
4769
4342
|
return currentAuthProvider;
|
|
4770
4343
|
}
|
|
4344
|
+
function retiredInlineAuthError(method) {
|
|
4345
|
+
return new Error(`bounded-sh: ${method} is retired. Human login must use the hosted OAuth2 + PKCE flow: ` +
|
|
4346
|
+
`loginWithRedirect({ redirectUri }) or loginWithPopup({ redirectUri }). ` +
|
|
4347
|
+
`For guests use signInAnonymously(); for guest-to-real-account data continuity, ` +
|
|
4348
|
+
`transfer ownership in app data after hosted login.`);
|
|
4349
|
+
}
|
|
4771
4350
|
/**
|
|
4772
4351
|
* Browser/React-Native-only auth flows (guest, email) persist their session via
|
|
4773
4352
|
* WebSessionManager, which is gated to environments that have `window` /
|
|
@@ -4801,7 +4380,13 @@ async function signInAnonymously() {
|
|
|
4801
4380
|
coreConfig.authMethod = 'guest';
|
|
4802
4381
|
setAuthProviderInstance(provider);
|
|
4803
4382
|
setStoredAuthMethod('guest');
|
|
4804
|
-
|
|
4383
|
+
setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
|
|
4384
|
+
try {
|
|
4385
|
+
return await provider.login();
|
|
4386
|
+
}
|
|
4387
|
+
finally {
|
|
4388
|
+
setAuthLoading(false);
|
|
4389
|
+
}
|
|
4805
4390
|
}
|
|
4806
4391
|
/**
|
|
4807
4392
|
* Log in with Privy as a co-equal route — selectable at runtime alongside
|
|
@@ -4845,77 +4430,40 @@ async function loginWithPrivy(options) {
|
|
|
4845
4430
|
const result = await provider.login();
|
|
4846
4431
|
return result ? Object.assign(Object.assign({}, result), { provider }) : null;
|
|
4847
4432
|
}
|
|
4848
|
-
// Activate the inline email provider (method=email so reload-restore + logout work),
|
|
4849
|
-
// mirroring signInAnonymously. Used by verifyEmailOtp/verifyTextOtp/linkEmail.
|
|
4850
|
-
async function activateEmailProvider() {
|
|
4851
|
-
const provider = new EmailAuthProvider();
|
|
4852
|
-
currentAuthProvider = provider;
|
|
4853
|
-
currentAuthMethod = 'email';
|
|
4854
|
-
currentAuthProviderConfigKey = 'direct:email';
|
|
4855
|
-
const coreConfig = await core.getConfig();
|
|
4856
|
-
coreConfig.authProvider = provider;
|
|
4857
|
-
coreConfig.authMethod = 'email';
|
|
4858
|
-
setAuthProviderInstance(provider);
|
|
4859
|
-
setStoredAuthMethod('email');
|
|
4860
|
-
return provider;
|
|
4861
|
-
}
|
|
4862
4433
|
/**
|
|
4863
|
-
*
|
|
4864
|
-
*
|
|
4865
|
-
* look, or just call `login()` for the built-in modal. Works on web AND React
|
|
4866
|
-
* Native (no DOM needed). Prefer `loginWithRedirect()`/`loginWithPopup()` if you
|
|
4867
|
-
* want the credential entered on Bounded's hosted page instead.
|
|
4434
|
+
* Retired. Use `loginWithRedirect({ redirectUri, methods: ['email'] })` or
|
|
4435
|
+
* `loginWithPopup(...)`; app-origin OTP helpers fail closed.
|
|
4868
4436
|
*/
|
|
4869
4437
|
async function sendEmailOtp(email) {
|
|
4870
|
-
|
|
4871
|
-
return sendEmailOtp$1(email);
|
|
4438
|
+
throw retiredInlineAuthError('sendEmailOtp');
|
|
4872
4439
|
}
|
|
4873
4440
|
/**
|
|
4874
|
-
*
|
|
4875
|
-
*
|
|
4876
|
-
* Works on web AND React Native.
|
|
4441
|
+
* Retired. Use hosted login with `methods: ['text']` or `provider: 'text'`
|
|
4442
|
+
* when text OTP is enabled for the app.
|
|
4877
4443
|
*/
|
|
4878
4444
|
async function sendTextOtp(phoneNumber) {
|
|
4879
|
-
|
|
4880
|
-
return sendTextOtp$1(phoneNumber);
|
|
4445
|
+
throw retiredInlineAuthError('sendTextOtp');
|
|
4881
4446
|
}
|
|
4882
4447
|
/**
|
|
4883
|
-
*
|
|
4884
|
-
*
|
|
4448
|
+
* Retired. Complete hosted login on the callback page with
|
|
4449
|
+
* `completeLoginFromRedirect()`.
|
|
4885
4450
|
*/
|
|
4886
4451
|
async function verifyEmailOtp(email, code) {
|
|
4887
|
-
|
|
4888
|
-
await activateEmailProvider();
|
|
4889
|
-
return verifyEmailOtp$1(email, code);
|
|
4452
|
+
throw retiredInlineAuthError('verifyEmailOtp');
|
|
4890
4453
|
}
|
|
4891
4454
|
/**
|
|
4892
|
-
*
|
|
4893
|
-
*
|
|
4455
|
+
* Retired. Complete hosted login on the callback page with
|
|
4456
|
+
* `completeLoginFromRedirect()`.
|
|
4894
4457
|
*/
|
|
4895
4458
|
async function verifyTextOtp(phoneNumber, code) {
|
|
4896
|
-
|
|
4897
|
-
await activateEmailProvider();
|
|
4898
|
-
return verifyTextOtp$1(phoneNumber, code);
|
|
4459
|
+
throw retiredInlineAuthError('verifyTextOtp');
|
|
4899
4460
|
}
|
|
4900
4461
|
/**
|
|
4901
|
-
*
|
|
4902
|
-
*
|
|
4903
|
-
* then call `linkEmail(email, code)`. If the email is brand-new the guest's
|
|
4904
|
-
* `@user.id` (and its data) is preserved; if the email already exists the user
|
|
4905
|
-
* just signs into it (no merge). Requires an active guest session, and the issuer
|
|
4906
|
-
* refuses if the wallet is already linked. Browser/React-Native only.
|
|
4462
|
+
* Retired. Guest upgrade is no longer an inline id-preserving auth operation;
|
|
4463
|
+
* send the user through hosted login and transfer data explicitly in app state.
|
|
4907
4464
|
*/
|
|
4908
4465
|
async function linkEmail(email, code) {
|
|
4909
|
-
|
|
4910
|
-
// Capture the GUEST token BEFORE switching auth methods — the issuer needs it
|
|
4911
|
-
// to prove guest ownership and adopt its id into the new email account.
|
|
4912
|
-
const guestToken = core.getActiveSessionManager().getIdToken();
|
|
4913
|
-
if (!guestToken) {
|
|
4914
|
-
throw new Error('linkEmail requires an active anonymous session — call signInAnonymously() first.');
|
|
4915
|
-
}
|
|
4916
|
-
await activateEmailProvider(); // method=email so the upgraded user is no longer anonymous
|
|
4917
|
-
const { user } = await linkEmailWithGuest(email, code, guestToken);
|
|
4918
|
-
return user;
|
|
4466
|
+
throw retiredInlineAuthError('linkEmail');
|
|
4919
4467
|
}
|
|
4920
4468
|
async function login$1(options) {
|
|
4921
4469
|
// Re-initialize provider if it was cleared by logout
|