@xh/hoist 72.0.0-SNAPSHOT.1736826689435 → 72.0.0-SNAPSHOT.1736881018782

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.
@@ -142,6 +142,8 @@ export declare abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>
142
142
  * @param key - key for re-accessing this state, as round-tripped with redirect.
143
143
  */
144
144
  protected restoreRedirectState(key: string): void;
145
+ /** Call after requesting the provider library redirect the user away for auth. */
146
+ protected maskAfterRedirectAsync(): Promise<void>;
145
147
  protected fetchAllTokensAsync(useCache?: boolean): Promise<TokenMap>;
146
148
  protected getLocalStorage(key: string, defaultValue?: any): any;
147
149
  protected setLocalStorage(key: string, value: any): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "72.0.0-SNAPSHOT.1736826689435",
3
+ "version": "72.0.0-SNAPSHOT.1736881018782",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",
@@ -4,8 +4,11 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {br, fragment} from '@xh/hoist/cmp/layout';
7
8
  import {HoistBase, managed, XH} from '@xh/hoist/core';
9
+ import {Icon} from '@xh/hoist/icon';
8
10
  import {action, makeObservable} from '@xh/hoist/mobx';
11
+ import {never, wait} from '@xh/hoist/promise';
9
12
  import {Token, TokenMap} from '@xh/hoist/security/Token';
10
13
  import {Timer} from '@xh/hoist/utils/async';
11
14
  import {MINUTES, olderThan, ONE_MINUTE, SECONDS} from '@xh/hoist/utils/datetime';
@@ -285,6 +288,32 @@ export abstract class BaseOAuthClient<C extends BaseOAuthClientConfig<S>, S> ext
285
288
  window.history.replaceState(null, '', pathname + search);
286
289
  }
287
290
 
291
+ /** Call after requesting the provider library redirect the user away for auth. */
292
+ protected async maskAfterRedirectAsync() {
293
+ // We expect the tab to unload momentarily and be redirected to the provider's login page.
294
+ // Wait here to ensure we mask the app while the browser processes the redirect...
295
+ await wait(10 * SECONDS);
296
+
297
+ // ...but also handle an observed edge case where the browser decided to open a new tab
298
+ // instead of redirecting the current one (https://github.com/xh/hoist-react/issues/3899).
299
+ // Show message below if/when user swaps back to the original tab, vs. endless spinner.
300
+ await XH.alert({
301
+ title: 'Auth / Redirect Error',
302
+ message: fragment(
303
+ 'Authentication did not complete as expected / tab was not redirected.',
304
+ br(),
305
+ br(),
306
+ 'Please click below or refresh this tab in your browser to try again.'
307
+ ),
308
+ confirmProps: {text: 'Reload', icon: Icon.refresh(), intent: 'primary', minimal: false}
309
+ });
310
+
311
+ XH.reloadApp();
312
+
313
+ // Ensure stale init does not progress - this tab should *really* be on its way out now!
314
+ await never();
315
+ }
316
+
288
317
  protected async fetchAllTokensAsync(useCache = true): Promise<TokenMap> {
289
318
  const ret: TokenMap = {},
290
319
  {accessSpecs} = this;
@@ -7,7 +7,7 @@
7
7
  import type {Auth0ClientOptions} from '@auth0/auth0-spa-js';
8
8
  import {Auth0Client} from '@auth0/auth0-spa-js';
9
9
  import {XH} from '@xh/hoist/core';
10
- import {never, wait} from '@xh/hoist/promise';
10
+ import {wait} from '@xh/hoist/promise';
11
11
  import {Token, TokenMap} from '@xh/hoist/security/Token';
12
12
  import {SECONDS} from '@xh/hoist/utils/datetime';
13
13
  import {mergeDeep, throwIf} from '@xh/hoist/utils/js';
@@ -99,11 +99,13 @@ export class AuthZeroClient extends BaseOAuthClient<AuthZeroClientConfig, AuthZe
99
99
 
100
100
  protected override async doLoginRedirectAsync(): Promise<void> {
101
101
  const appState = this.captureRedirectState();
102
+
102
103
  await this.client.loginWithRedirect({
103
104
  appState,
104
105
  authorizationParams: {scope: this.loginScope}
105
106
  });
106
- await never();
107
+
108
+ await this.maskAfterRedirectAsync();
107
109
  }
108
110
 
109
111
  protected override async doLoginPopupAsync(): Promise<void> {
@@ -10,11 +10,9 @@ import {
10
10
  IPublicClientApplication,
11
11
  LogLevel,
12
12
  PopupRequest,
13
- RedirectRequest,
14
13
  SilentRequest
15
14
  } from '@azure/msal-browser';
16
15
  import {XH} from '@xh/hoist/core';
17
- import {never} from '@xh/hoist/promise';
18
16
  import {Token, TokenMap} from '@xh/hoist/security/Token';
19
17
  import {logDebug, logError, logInfo, logWarn, mergeDeep, throwIf} from '@xh/hoist/utils/js';
20
18
  import {flatMap, union, uniq} from 'lodash';
@@ -201,18 +199,18 @@ export class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTokenSpec>
201
199
  }
202
200
 
203
201
  protected override async doLoginRedirectAsync(): Promise<void> {
204
- const {client} = this,
205
- state = this.captureRedirectState(),
206
- opts: RedirectRequest = {
207
- state,
208
- loginHint: this.getSelectedUsername(),
209
- domainHint: this.config.domainHint,
210
- scopes: this.loginScopes,
211
- extraScopesToConsent: this.loginExtraScopesToConsent,
212
- redirectUri: this.redirectUrl
213
- };
214
- await client.acquireTokenRedirect(opts);
215
- await never();
202
+ const state = this.captureRedirectState();
203
+
204
+ await this.client.acquireTokenRedirect({
205
+ state,
206
+ loginHint: this.getSelectedUsername(),
207
+ domainHint: this.config.domainHint,
208
+ scopes: this.loginScopes,
209
+ extraScopesToConsent: this.loginExtraScopesToConsent,
210
+ redirectUri: this.redirectUrl
211
+ });
212
+
213
+ await this.maskAfterRedirectAsync();
216
214
  }
217
215
 
218
216
  protected override async fetchIdTokenAsync(useCache: boolean = true): Promise<Token> {