@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.
@@ -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
- const { url, pkce } = await buildAuthorize(opts);
4514
- sessionStorage.setItem(PKCE_STORE_KEY, JSON.stringify(pkce));
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(url, 'bounded-oidc', `width=${w},height=${h},left=${left},top=${top}`);
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
- // Use the RESOLVED redirect_uri (buildAuthorize defaulted it to the current page on web),
4522
- // not opts.redirectUri which may be undefined.
4523
- const expectedOrigin = new URL(pkce.redirectUri).origin;
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
- const cleanup = () => { settled = true; clearInterval(timer); window.removeEventListener('message', onMsg); };
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