@anchrd/intel-ui 0.8.6 → 0.8.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-ui",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -65,3 +65,33 @@ export function createBrowserSignIn(loginPath: (returnTo?: string) => string): S
65
65
  loginPath,
66
66
  });
67
67
  }
68
+
69
+ /**
70
+ * What to do with a 401, across all of them at once (#130).
71
+ *
72
+ * ⚠️ The reason this is a guard and not a call to `attempt()` at each 401: a first load without a
73
+ * session fires several requests together — session, tree, flows — and every one of them answers
74
+ * 401. The first starts the redirect; the rest arrive while the browser is still on this page,
75
+ * find the marker the first one just wrote, and read it as "we already tried, this is a loop". It
76
+ * is not — it is this very sign-in, still in flight.
77
+ *
78
+ * Without `leaving`, the interface refuses itself before it can leave, and nobody reaches the
79
+ * login at all. That is worse than the loop this whole mechanism exists to prevent: a loop still
80
+ * eventually shows a login, a self-refusal never does.
81
+ */
82
+ export function createSignInGuard(signIn: SignIn, onRefused: () => void): () => void {
83
+ let leaving = false;
84
+ // Latches for its own reason: once the surface has turned down a freshly issued credential,
85
+ // later 401s say nothing new, and repainting for each would flicker the screen once per request
86
+ // the page had in flight.
87
+ let refused = false;
88
+ return () => {
89
+ if (leaving || refused) return;
90
+ if (signIn.attempt()) {
91
+ leaving = true;
92
+ return;
93
+ }
94
+ refused = true;
95
+ onRefused();
96
+ };
97
+ }
package/src/main.tsx CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  createIntelDataProvider,
7
7
  loginPath,
8
8
  } from "@/data/intel-data-provider/intel-data-provider.ts";
9
- import { createBrowserSignIn } from "@/data/sign-in/sign-in.ts";
9
+ import { createBrowserSignIn, createSignInGuard } from "@/data/sign-in/sign-in.ts";
10
10
  import { createI18n } from "@/i18n/i18n.ts";
11
11
  import { createIntelRouter } from "@/router/router.tsx";
12
12
  import { SignInRefused } from "@/sign-in-refused/sign-in-refused.tsx";
@@ -23,18 +23,15 @@ const signIn = createBrowserSignIn(loginPath);
23
23
  const mounted = createRoot(root);
24
24
  const queryClient = new QueryClient();
25
25
 
26
- // ⚠️ The shell is painted from a function because a refused sign-in has to replace it, and that
27
- // refusal arrives from inside a fetch rather than from React. `refused` latches: once the surface
28
- // has turned down a freshly issued credential, later 401s say nothing new, and re-painting for each
29
- // one would flicker the screen as many times as the page had requests in flight (#118).
26
+ // The shell is painted from a function because a refused sign-in has to replace it, and that
27
+ // refusal arrives from inside a fetch rather than from React.
30
28
  let refused = false;
31
29
 
32
30
  const data = createIntelDataProvider({
33
- onUnauthorized: () => {
34
- if (signIn.attempt() || refused) return;
31
+ onUnauthorized: createSignInGuard(signIn, () => {
35
32
  refused = true;
36
33
  paint();
37
- },
34
+ }),
38
35
  });
39
36
  const router = createIntelRouter({ data, i18n });
40
37