@bounded-sh/client 0.0.29 → 0.0.31

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.
@@ -4250,7 +4250,11 @@ async function buildAuthorize(opts, guestHint) {
4250
4250
  const cfg = await core.getConfig();
4251
4251
  if (!cfg.appId)
4252
4252
  throw new Error('appId is not configured (init the SDK with your appId)');
4253
- const redirectUri = opts.redirectUri;
4253
+ // Web: default to the current page so callers don't have to pass redirectUri. Native has no
4254
+ // window — it MUST pass an https universal link (the system auth session needs a real URL).
4255
+ const redirectUri = opts.redirectUri || (hasDOM() ? window.location.origin + window.location.pathname : undefined);
4256
+ if (!redirectUri)
4257
+ throw new Error('redirectUri is required on React Native — pass the https universal link the app owner registered.');
4254
4258
  const { verifier, challenge } = await makePkce();
4255
4259
  const state = randomState();
4256
4260
  const base = await issuerBase();
@@ -4409,6 +4413,21 @@ async function completeLoginFromRedirect() {
4409
4413
  const returnedState = url.searchParams.get('state');
4410
4414
  if (!code)
4411
4415
  return null;
4416
+ // POPUP path: if we're the redirect_uri page running INSIDE the loginWithPopup() window,
4417
+ // hand the code to the opener and close — no separate completeLoginInPopup() call needed.
4418
+ // The opener is the same app/origin, so post to our own origin (the opener's message
4419
+ // handler checks ev.origin === redirect_uri origin). One call now finishes BOTH flows.
4420
+ if (window.opener && window.opener !== window) {
4421
+ try {
4422
+ window.opener.postMessage({ type: 'bounded-oidc-code', code, state: returnedState }, window.location.origin);
4423
+ }
4424
+ catch ( /* ignore */_a) { /* ignore */ }
4425
+ try {
4426
+ window.close();
4427
+ }
4428
+ catch ( /* ignore */_b) { /* ignore */ }
4429
+ return null;
4430
+ }
4412
4431
  setAuthLoading(true);
4413
4432
  try {
4414
4433
  const raw = sessionStorage.getItem(PKCE_STORE_KEY);
@@ -4430,7 +4449,8 @@ async function completeLoginFromRedirect() {
4430
4449
  }
4431
4450
  }
4432
4451
  /** Popup variant: open the hosted login in a popup; resolves with the User once the popup's
4433
- * redirect_uri page posts the code back. The redirect_uri page must call completeLoginInPopup(). */
4452
+ * redirect_uri page posts the code back. The redirect_uri page just calls the usual
4453
+ * completeLoginFromRedirect() (which auto-detects the popup) — no separate call needed. */
4434
4454
  async function loginWithPopup(opts) {
4435
4455
  if (!hasDOM())
4436
4456
  throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
@@ -4442,15 +4462,20 @@ async function loginWithPopup(opts) {
4442
4462
  const popup = window.open(url, 'bounded-oidc', `width=${w},height=${h},left=${left},top=${top}`);
4443
4463
  if (!popup)
4444
4464
  throw new Error('Popup blocked — use loginWithRedirect() instead');
4445
- const expectedOrigin = new URL(opts.redirectUri).origin;
4465
+ // Use the RESOLVED redirect_uri (buildAuthorize defaulted it to the current page on web),
4466
+ // not opts.redirectUri which may be undefined.
4467
+ const expectedOrigin = new URL(pkce.redirectUri).origin;
4446
4468
  return await new Promise((resolve, reject) => {
4469
+ let settled = false;
4470
+ let timer;
4471
+ const cleanup = () => { settled = true; clearInterval(timer); window.removeEventListener('message', onMsg); };
4447
4472
  const onMsg = async (ev) => {
4448
4473
  if (ev.origin !== expectedOrigin)
4449
4474
  return; // only trust the redirect_uri origin
4450
4475
  const d = ev.data;
4451
4476
  if (!d || d.type !== 'bounded-oidc-code' || typeof d.code !== 'string')
4452
4477
  return;
4453
- window.removeEventListener('message', onMsg);
4478
+ cleanup();
4454
4479
  try {
4455
4480
  const raw = sessionStorage.getItem(PKCE_STORE_KEY);
4456
4481
  if (!raw)
@@ -4471,11 +4496,17 @@ async function loginWithPopup(opts) {
4471
4496
  }
4472
4497
  };
4473
4498
  window.addEventListener('message', onMsg);
4474
- const timer = setInterval(() => {
4499
+ // The popup posts its code and then closes ITSELF, so a naive "popup.closed -> reject"
4500
+ // races the just-posted message and would report a spurious "Login window closed" on a
4501
+ // SUCCESSFUL login. When we see it closed, wait a beat for that pending message to settle
4502
+ // before declaring the window closed.
4503
+ timer = setInterval(() => {
4475
4504
  if (popup.closed) {
4476
4505
  clearInterval(timer);
4477
- window.removeEventListener('message', onMsg);
4478
- reject(new Error('Login window closed'));
4506
+ setTimeout(() => { if (!settled) {
4507
+ cleanup();
4508
+ reject(new Error('Login window closed'));
4509
+ } }, 500);
4479
4510
  }
4480
4511
  }, 500);
4481
4512
  });