@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.
@@ -4530,23 +4530,74 @@ async function completeLoginFromRedirect() {
4530
4530
  async function loginWithPopup(opts) {
4531
4531
  if (!hasDOM())
4532
4532
  throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
4533
- const { url, pkce } = await buildAuthorize(opts);
4534
- sessionStorage.setItem(PKCE_STORE_KEY, JSON.stringify(pkce));
4533
+ // Open synchronously, before PKCE/config awaits consume the click's user-
4534
+ // activation window. Opening only after buildAuthorize() intermittently
4535
+ // left Chrome with a real named window stranded at about:blank (or blocked
4536
+ // it entirely), while callers waited forever for an OAuth message.
4535
4537
  const w = opts.width || 480, h = opts.height || 720;
4536
4538
  const left = window.screenX + (window.outerWidth - w) / 2;
4537
4539
  const top = window.screenY + (window.outerHeight - h) / 2;
4538
- const popup = window.open(url, 'bounded-oidc', `width=${w},height=${h},left=${left},top=${top}`);
4540
+ const popup = window.open('', 'bounded-oidc', `width=${w},height=${h},left=${left},top=${top}`);
4539
4541
  if (!popup)
4540
4542
  throw new Error('Popup blocked — use loginWithRedirect() instead');
4541
- // Use the RESOLVED redirect_uri (buildAuthorize defaulted it to the current page on web),
4542
- // not opts.redirectUri which may be undefined.
4543
- const expectedOrigin = new URL(pkce.redirectUri).origin;
4543
+ let authorization;
4544
+ try {
4545
+ authorization = await buildAuthorize(opts);
4546
+ }
4547
+ catch (error) {
4548
+ try {
4549
+ popup.close();
4550
+ }
4551
+ catch ( /* ignore */_a) { /* ignore */ }
4552
+ throw error;
4553
+ }
4554
+ const { url, pkce } = authorization;
4555
+ // Validate every remaining synchronous setup step before navigating. Both
4556
+ // URL parsing and sessionStorage can throw (malformed caller input, blocked
4557
+ // storage, quota/privacy mode); because the popup is already open to retain
4558
+ // user activation, close it on either failure instead of orphaning a blank
4559
+ // window with no listener or watchdog attached.
4560
+ let expectedOrigin;
4561
+ try {
4562
+ expectedOrigin = new URL(pkce.redirectUri).origin;
4563
+ sessionStorage.setItem(PKCE_STORE_KEY, JSON.stringify(pkce));
4564
+ }
4565
+ catch (error) {
4566
+ try {
4567
+ popup.close();
4568
+ }
4569
+ catch ( /* ignore */_b) { /* ignore */ }
4570
+ throw error;
4571
+ }
4572
+ try {
4573
+ popup.location.replace(url);
4574
+ }
4575
+ catch (_c) {
4576
+ try {
4577
+ popup.location.href = url;
4578
+ }
4579
+ catch (_d) {
4580
+ try {
4581
+ popup.close();
4582
+ }
4583
+ catch ( /* ignore */_e) { /* ignore */ }
4584
+ throw new Error('Popup failed to navigate — use loginWithRedirect() instead');
4585
+ }
4586
+ }
4587
+ // expectedOrigin came from the RESOLVED redirect_uri (buildAuthorize defaulted it to the
4588
+ // current page on web), not opts.redirectUri which may be undefined.
4544
4589
  setAuthLoading(true); // reflect "signing in…" while the popup is open
4545
4590
  try {
4546
4591
  return await new Promise((resolve, reject) => {
4547
4592
  let settled = false;
4548
4593
  let timer;
4549
- const cleanup = () => { settled = true; clearInterval(timer); window.removeEventListener('message', onMsg); };
4594
+ let navigationTimer;
4595
+ const cleanup = () => {
4596
+ settled = true;
4597
+ clearInterval(timer);
4598
+ clearTimeout(navigationTimer);
4599
+ window.removeEventListener('message', onMsg);
4600
+ };
4550
4601
  const onMsg = async (ev) => {
4551
4602
  if (ev.origin !== expectedOrigin)
4552
4603
  return; // only trust the redirect_uri origin
@@ -4574,6 +4625,27 @@ async function loginWithPopup(opts) {
4574
4625
  }
4575
4626
  };
4576
4627
  window.addEventListener('message', onMsg);
4628
+ // Some browser/extension combinations can return a real popup handle but
4629
+ // strand the window at about:blank instead of navigating to the hosted
4630
+ // authorize URL. The old code then waited forever because the popup was
4631
+ // open and no message could ever arrive. Once the popup becomes
4632
+ // cross-origin, reading location throws — that is positive evidence that
4633
+ // navigation succeeded, so only a still-readable about:blank is rejected.
4634
+ navigationTimer = setTimeout(() => {
4635
+ if (settled || popup.closed)
4636
+ return;
4637
+ try {
4638
+ if (popup.location.href === 'about:blank') {
4639
+ cleanup();
4640
+ try {
4641
+ popup.close();
4642
+ }
4643
+ catch ( /* ignore */_a) { /* ignore */ }
4644
+ reject(new Error('Popup stalled at about:blank — use loginWithRedirect() instead'));
4645
+ }
4646
+ }
4647
+ catch ( /* cross-origin hosted auth loaded normally */_b) { /* cross-origin hosted auth loaded normally */ }
4648
+ }, 4000);
4577
4649
  // The popup posts its code and then closes ITSELF, so a naive "popup.closed -> reject"
4578
4650
  // races the just-posted message and would report a spurious "Login window closed" on a
4579
4651
  // SUCCESSFUL login. When we see it closed, wait a beat for that pending message to settle