@bounded-sh/client 0.0.41 → 0.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/REACT_NATIVE.md +12 -14
- package/dist/index.js +85 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +85 -7
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +79 -7
- package/dist/index.native.js.map +1 -1
- package/dist/index.native.mjs +79 -7
- package/dist/index.native.mjs.map +1 -1
- package/package.json +128 -117
package/dist/index.mjs
CHANGED
|
@@ -4510,23 +4510,74 @@ async function completeLoginFromRedirect() {
|
|
|
4510
4510
|
async function loginWithPopup(opts) {
|
|
4511
4511
|
if (!hasDOM())
|
|
4512
4512
|
throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
|
|
4513
|
-
|
|
4514
|
-
|
|
4513
|
+
// Open synchronously, before PKCE/config awaits consume the click's user-
|
|
4514
|
+
// activation window. Opening only after buildAuthorize() intermittently
|
|
4515
|
+
// left Chrome with a real named window stranded at about:blank (or blocked
|
|
4516
|
+
// it entirely), while callers waited forever for an OAuth message.
|
|
4515
4517
|
const w = opts.width || 480, h = opts.height || 720;
|
|
4516
4518
|
const left = window.screenX + (window.outerWidth - w) / 2;
|
|
4517
4519
|
const top = window.screenY + (window.outerHeight - h) / 2;
|
|
4518
|
-
const popup = window.open(
|
|
4520
|
+
const popup = window.open('', 'bounded-oidc', `width=${w},height=${h},left=${left},top=${top}`);
|
|
4519
4521
|
if (!popup)
|
|
4520
4522
|
throw new Error('Popup blocked — use loginWithRedirect() instead');
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4523
|
+
let authorization;
|
|
4524
|
+
try {
|
|
4525
|
+
authorization = await buildAuthorize(opts);
|
|
4526
|
+
}
|
|
4527
|
+
catch (error) {
|
|
4528
|
+
try {
|
|
4529
|
+
popup.close();
|
|
4530
|
+
}
|
|
4531
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
4532
|
+
throw error;
|
|
4533
|
+
}
|
|
4534
|
+
const { url, pkce } = authorization;
|
|
4535
|
+
// Validate every remaining synchronous setup step before navigating. Both
|
|
4536
|
+
// URL parsing and sessionStorage can throw (malformed caller input, blocked
|
|
4537
|
+
// storage, quota/privacy mode); because the popup is already open to retain
|
|
4538
|
+
// user activation, close it on either failure instead of orphaning a blank
|
|
4539
|
+
// window with no listener or watchdog attached.
|
|
4540
|
+
let expectedOrigin;
|
|
4541
|
+
try {
|
|
4542
|
+
expectedOrigin = new URL(pkce.redirectUri).origin;
|
|
4543
|
+
sessionStorage.setItem(PKCE_STORE_KEY, JSON.stringify(pkce));
|
|
4544
|
+
}
|
|
4545
|
+
catch (error) {
|
|
4546
|
+
try {
|
|
4547
|
+
popup.close();
|
|
4548
|
+
}
|
|
4549
|
+
catch ( /* ignore */_b) { /* ignore */ }
|
|
4550
|
+
throw error;
|
|
4551
|
+
}
|
|
4552
|
+
try {
|
|
4553
|
+
popup.location.replace(url);
|
|
4554
|
+
}
|
|
4555
|
+
catch (_c) {
|
|
4556
|
+
try {
|
|
4557
|
+
popup.location.href = url;
|
|
4558
|
+
}
|
|
4559
|
+
catch (_d) {
|
|
4560
|
+
try {
|
|
4561
|
+
popup.close();
|
|
4562
|
+
}
|
|
4563
|
+
catch ( /* ignore */_e) { /* ignore */ }
|
|
4564
|
+
throw new Error('Popup failed to navigate — use loginWithRedirect() instead');
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
// expectedOrigin came from the RESOLVED redirect_uri (buildAuthorize defaulted it to the
|
|
4568
|
+
// current page on web), not opts.redirectUri which may be undefined.
|
|
4524
4569
|
setAuthLoading(true); // reflect "signing in…" while the popup is open
|
|
4525
4570
|
try {
|
|
4526
4571
|
return await new Promise((resolve, reject) => {
|
|
4527
4572
|
let settled = false;
|
|
4528
4573
|
let timer;
|
|
4529
|
-
|
|
4574
|
+
let navigationTimer;
|
|
4575
|
+
const cleanup = () => {
|
|
4576
|
+
settled = true;
|
|
4577
|
+
clearInterval(timer);
|
|
4578
|
+
clearTimeout(navigationTimer);
|
|
4579
|
+
window.removeEventListener('message', onMsg);
|
|
4580
|
+
};
|
|
4530
4581
|
const onMsg = async (ev) => {
|
|
4531
4582
|
if (ev.origin !== expectedOrigin)
|
|
4532
4583
|
return; // only trust the redirect_uri origin
|
|
@@ -4554,6 +4605,27 @@ async function loginWithPopup(opts) {
|
|
|
4554
4605
|
}
|
|
4555
4606
|
};
|
|
4556
4607
|
window.addEventListener('message', onMsg);
|
|
4608
|
+
// Some browser/extension combinations can return a real popup handle but
|
|
4609
|
+
// strand the window at about:blank instead of navigating to the hosted
|
|
4610
|
+
// authorize URL. The old code then waited forever because the popup was
|
|
4611
|
+
// open and no message could ever arrive. Once the popup becomes
|
|
4612
|
+
// cross-origin, reading location throws — that is positive evidence that
|
|
4613
|
+
// navigation succeeded, so only a still-readable about:blank is rejected.
|
|
4614
|
+
navigationTimer = setTimeout(() => {
|
|
4615
|
+
if (settled || popup.closed)
|
|
4616
|
+
return;
|
|
4617
|
+
try {
|
|
4618
|
+
if (popup.location.href === 'about:blank') {
|
|
4619
|
+
cleanup();
|
|
4620
|
+
try {
|
|
4621
|
+
popup.close();
|
|
4622
|
+
}
|
|
4623
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
4624
|
+
reject(new Error('Popup stalled at about:blank — use loginWithRedirect() instead'));
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
catch ( /* cross-origin hosted auth loaded normally */_b) { /* cross-origin hosted auth loaded normally */ }
|
|
4628
|
+
}, 4000);
|
|
4557
4629
|
// The popup posts its code and then closes ITSELF, so a naive "popup.closed -> reject"
|
|
4558
4630
|
// races the just-posted message and would report a spurious "Login window closed" on a
|
|
4559
4631
|
// SUCCESSFUL login. When we see it closed, wait a beat for that pending message to settle
|
|
@@ -5325,6 +5397,12 @@ function useQuery(path, options) {
|
|
|
5325
5397
|
subscribe(path, Object.assign(Object.assign({}, options), { onData: (d) => {
|
|
5326
5398
|
if (!active)
|
|
5327
5399
|
return;
|
|
5400
|
+
// A reconnect can legitimately surface a transport error before the
|
|
5401
|
+
// socket re-authenticates and sends a new authorized snapshot. That
|
|
5402
|
+
// fresh snapshot is the recovery boundary: keep the error visible
|
|
5403
|
+
// until data arrives, then clear it so React does not remain stuck on
|
|
5404
|
+
// a failure page after realtime has recovered.
|
|
5405
|
+
setError(null);
|
|
5328
5406
|
setData(d);
|
|
5329
5407
|
setLoading(false);
|
|
5330
5408
|
}, onError: (e) => {
|