@sybilion/uilib 1.2.11 → 1.2.13
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/dist/esm/components/widgets/SignInPage/SignInPage.js +2 -2
- package/dist/esm/hooks/index.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/sybilion-auth/SybilionAuthProvider.js +33 -16
- package/dist/esm/types/src/hooks/index.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +1 -0
- package/dist/esm/types/src/sybilion-auth/SybilionAuthProvider.d.ts +1 -1
- package/package.json +6 -1
- package/src/components/widgets/SignInPage/SignInPage.tsx +3 -2
- package/src/hooks/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/sybilion-auth/SybilionAuthProvider.tsx +39 -17
|
@@ -7,7 +7,7 @@ import { SybilionSignInPanel } from '../SybilionSignInPanel/SybilionSignInPanel.
|
|
|
7
7
|
const DEFAULT_TITLE = 'Sign In';
|
|
8
8
|
const DEFAULT_SUBTITLE = 'To get access authenticate through google or your email.';
|
|
9
9
|
function SignInPage({ title = DEFAULT_TITLE, subtitle = DEFAULT_SUBTITLE, forgotPasswordTo = '/forgot-password', releasesTo = '/releases', versionLabel, primaryButtonLabel, connectingLabel, loginRedirectOptions, logoSize, containerClassName, }) {
|
|
10
|
-
const { error, loginWithRedirect, isLoading } = useSybilionAuth();
|
|
10
|
+
const { error, loginWithRedirect, isLoading, isAuthenticated } = useSybilionAuth();
|
|
11
11
|
const handleSignIn = () => loginWithRedirect({
|
|
12
12
|
...loginRedirectOptions,
|
|
13
13
|
authorizationParams: {
|
|
@@ -19,7 +19,7 @@ function SignInPage({ title = DEFAULT_TITLE, subtitle = DEFAULT_SUBTITLE, forgot
|
|
|
19
19
|
...loginRedirectOptions?.authorizationParams,
|
|
20
20
|
},
|
|
21
21
|
});
|
|
22
|
-
return (jsx(SybilionAuthLayout, { title: title, subtitle: subtitle, logoSize: logoSize, containerClassName: containerClassName, children: jsx(SybilionSignInPanel, { onSignIn: handleSignIn, isSigningIn: isLoading, error: error, forgotPasswordTo: forgotPasswordTo, releasesTo: releasesTo, versionLabel: versionLabel, primaryButtonLabel: primaryButtonLabel, connectingLabel: connectingLabel }) }));
|
|
22
|
+
return (jsx(SybilionAuthLayout, { title: title, subtitle: subtitle, logoSize: logoSize, containerClassName: containerClassName, children: jsx(SybilionSignInPanel, { onSignIn: handleSignIn, isSigningIn: isLoading && !isAuthenticated, error: error, forgotPasswordTo: forgotPasswordTo, releasesTo: releasesTo, versionLabel: versionLabel, primaryButtonLabel: primaryButtonLabel, connectingLabel: connectingLabel }) }));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export { SignInPage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useIsMobile } from './useIsMobile.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { useIsMobile } from './hooks/useIsMobile.js';
|
|
1
2
|
export { ThemeProvider, useTheme } from './contexts/theme-context.js';
|
|
2
3
|
export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme.js';
|
|
3
4
|
export { SybilionAuthProvider, createSybilionApiFetch, getSybilionApiOriginFromSdk, sybilionApiFetch, useSybilionApiFetch, useSybilionAuth } from './sybilion-auth/SybilionAuthProvider.js';
|
|
@@ -59,6 +59,16 @@ function useSybilionApiFetch() {
|
|
|
59
59
|
const { apiBaseUrl, getSybilionAccessToken } = useSybilionAuth();
|
|
60
60
|
return useMemo(() => createSybilionApiFetch(apiBaseUrl, getSybilionAccessToken), [apiBaseUrl, getSybilionAccessToken]);
|
|
61
61
|
}
|
|
62
|
+
function readTokenFromLs(key) {
|
|
63
|
+
if (typeof localStorage === 'undefined')
|
|
64
|
+
return null;
|
|
65
|
+
try {
|
|
66
|
+
return localStorage.getItem(key);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
62
72
|
function writeLs(key, value) {
|
|
63
73
|
if (typeof localStorage === 'undefined')
|
|
64
74
|
return;
|
|
@@ -76,7 +86,7 @@ function InnerSybilionSession({ children, sdk, storageKey, logoutReturnTo, useFe
|
|
|
76
86
|
const auth0 = useAuth0();
|
|
77
87
|
const auth0Ref = useRef(auth0);
|
|
78
88
|
auth0Ref.current = auth0;
|
|
79
|
-
const [sybilionToken, setSybilionToken] = useState(null);
|
|
89
|
+
const [sybilionToken, setSybilionToken] = useState(() => typeof window === 'undefined' ? null : readTokenFromLs(storageKey));
|
|
80
90
|
const [exchangeLoading, setExchangeLoading] = useState(false);
|
|
81
91
|
const [exchangeError, setExchangeError] = useState(null);
|
|
82
92
|
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
|
@@ -106,10 +116,15 @@ function InnerSybilionSession({ children, sdk, storageKey, logoutReturnTo, useFe
|
|
|
106
116
|
}, [auth0.isAuthenticated]);
|
|
107
117
|
const apiBaseUrl = useMemo(() => getSybilionApiOriginFromSdk(sdk), [sdk]);
|
|
108
118
|
useEffect(() => {
|
|
119
|
+
// While Auth0 restores from localStorage, `isAuthenticated` is false — do not
|
|
120
|
+
// wipe the Sybilion JWT (would break reload: sign-in flash / lost session).
|
|
121
|
+
if (auth0.isLoading) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
109
124
|
if (!auth0.isAuthenticated || !auth0.user?.sub) {
|
|
110
125
|
persistToken(null);
|
|
111
126
|
}
|
|
112
|
-
}, [auth0.isAuthenticated, auth0.user?.sub, persistToken]);
|
|
127
|
+
}, [auth0.isLoading, auth0.isAuthenticated, auth0.user?.sub, persistToken]);
|
|
113
128
|
useEffect(() => {
|
|
114
129
|
if (!auth0.isAuthenticated || !auth0.user?.sub) {
|
|
115
130
|
setExchangeLoading(false);
|
|
@@ -145,13 +160,7 @@ function InnerSybilionSession({ children, sdk, storageKey, logoutReturnTo, useFe
|
|
|
145
160
|
return () => {
|
|
146
161
|
cancelled = true;
|
|
147
162
|
};
|
|
148
|
-
}, [
|
|
149
|
-
sdk,
|
|
150
|
-
auth0.isAuthenticated,
|
|
151
|
-
auth0.user?.sub,
|
|
152
|
-
persistToken,
|
|
153
|
-
doLogout,
|
|
154
|
-
]);
|
|
163
|
+
}, [sdk, auth0.isAuthenticated, auth0.user?.sub, persistToken, doLogout]);
|
|
155
164
|
const getAuth0AccessToken = useCallback(async () => {
|
|
156
165
|
if (!auth0.isAuthenticated)
|
|
157
166
|
return undefined;
|
|
@@ -168,13 +177,21 @@ function InnerSybilionSession({ children, sdk, storageKey, logoutReturnTo, useFe
|
|
|
168
177
|
const logout = useCallback(() => {
|
|
169
178
|
doLogout();
|
|
170
179
|
}, [doLogout]);
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
const hasAuth0User = Boolean(auth0.isAuthenticated && auth0.user?.sub);
|
|
181
|
+
/** Auth0 session is enough for app routes; Sybilion JWT is filled by exchange. */
|
|
182
|
+
const isAuthenticated = Boolean(!exchangeError &&
|
|
183
|
+
!isLoggingOut &&
|
|
184
|
+
(hasAuth0User ||
|
|
185
|
+
(Boolean(sybilionToken) && (auth0.isAuthenticated || auth0.isLoading))));
|
|
186
|
+
const needsFirstSybilionToken = Boolean(auth0.isAuthenticated &&
|
|
187
|
+
auth0.user &&
|
|
188
|
+
sybilionToken === null &&
|
|
189
|
+
exchangeError === null &&
|
|
190
|
+
!isLoggingOut);
|
|
191
|
+
/** Omit Auth0/exchange "busy" once we already have a Sybilion JWT — avoids header skeleton flicker on refresh. */
|
|
192
|
+
const isLoading = (auth0.isLoading && sybilionToken === null) ||
|
|
193
|
+
(exchangeLoading && sybilionToken === null) ||
|
|
194
|
+
needsFirstSybilionToken;
|
|
178
195
|
const value = useMemo(() => ({
|
|
179
196
|
apiBaseUrl,
|
|
180
197
|
isLoading,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useIsMobile } from './useIsMobile';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RedirectLoginOptions } from '@auth0/auth0-react';
|
|
2
|
-
import type { SybilionSDK } from '@sybilion/sdk';
|
|
3
2
|
import type { JSX, ReactNode } from 'react';
|
|
3
|
+
import type { SybilionSDK } from '@sybilion/sdk';
|
|
4
4
|
/** Origin (`scheme://host:port`) for paths like `/api/v1/...`; derived from SDK URL layout. */
|
|
5
5
|
export declare function getSybilionApiOriginFromSdk(sdk: SybilionSDK): string;
|
|
6
6
|
export type SybilionAuthProviderProps = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sybilion/uilib",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.13",
|
|
4
4
|
"description": "Sybilion Design System — React UI components (Webpack + Stylus)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
"import": "./src/index.ts",
|
|
30
30
|
"default": "./src/index.ts"
|
|
31
31
|
},
|
|
32
|
+
"./hooks": {
|
|
33
|
+
"types": "./dist/esm/types/src/hooks/index.d.ts",
|
|
34
|
+
"import": "./dist/esm/hooks/index.js",
|
|
35
|
+
"default": "./dist/esm/hooks/index.js"
|
|
36
|
+
},
|
|
32
37
|
"./src/*": "./src/*",
|
|
33
38
|
"./vite-standalone-dev": {
|
|
34
39
|
"types": "./dist/standalone/vite-sybilion-standalone-dev.d.ts",
|
|
@@ -45,7 +45,8 @@ export function SignInPage({
|
|
|
45
45
|
logoSize,
|
|
46
46
|
containerClassName,
|
|
47
47
|
}: SignInPageProps) {
|
|
48
|
-
const { error, loginWithRedirect, isLoading } =
|
|
48
|
+
const { error, loginWithRedirect, isLoading, isAuthenticated } =
|
|
49
|
+
useSybilionAuth();
|
|
49
50
|
|
|
50
51
|
const handleSignIn = () =>
|
|
51
52
|
loginWithRedirect({
|
|
@@ -69,7 +70,7 @@ export function SignInPage({
|
|
|
69
70
|
>
|
|
70
71
|
<SybilionSignInPanel
|
|
71
72
|
onSignIn={handleSignIn}
|
|
72
|
-
isSigningIn={isLoading}
|
|
73
|
+
isSigningIn={isLoading && !isAuthenticated}
|
|
73
74
|
error={error}
|
|
74
75
|
forgotPasswordTo={forgotPasswordTo}
|
|
75
76
|
releasesTo={releasesTo}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useIsMobile } from './useIsMobile';
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
type RedirectLoginOptions,
|
|
4
4
|
useAuth0,
|
|
5
5
|
} from '@auth0/auth0-react';
|
|
6
|
-
import type { LoginTokenResponse, SybilionSDK } from '@sybilion/sdk';
|
|
7
6
|
import type { JSX, ReactNode } from 'react';
|
|
8
7
|
import {
|
|
9
8
|
createContext,
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
} from 'react';
|
|
17
16
|
|
|
18
17
|
import { normalizeApiBaseUrl } from '#uilib/sybilion-auth/authPaths';
|
|
18
|
+
import type { LoginTokenResponse, SybilionSDK } from '@sybilion/sdk';
|
|
19
19
|
|
|
20
20
|
const DEFAULT_TOKEN_KEY = 'sybilion.standalone.jwt';
|
|
21
21
|
|
|
@@ -138,6 +138,15 @@ export function useSybilionApiFetch(): (
|
|
|
138
138
|
);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
function readTokenFromLs(key: string): string | null {
|
|
142
|
+
if (typeof localStorage === 'undefined') return null;
|
|
143
|
+
try {
|
|
144
|
+
return localStorage.getItem(key);
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
141
150
|
function writeLs(key: string, value: string | null): void {
|
|
142
151
|
if (typeof localStorage === 'undefined') return;
|
|
143
152
|
try {
|
|
@@ -165,7 +174,9 @@ function InnerSybilionSession({
|
|
|
165
174
|
const auth0Ref = useRef(auth0);
|
|
166
175
|
auth0Ref.current = auth0;
|
|
167
176
|
|
|
168
|
-
const [sybilionToken, setSybilionToken] = useState<string | null>(
|
|
177
|
+
const [sybilionToken, setSybilionToken] = useState<string | null>(() =>
|
|
178
|
+
typeof window === 'undefined' ? null : readTokenFromLs(storageKey),
|
|
179
|
+
);
|
|
169
180
|
const [exchangeLoading, setExchangeLoading] = useState(false);
|
|
170
181
|
const [exchangeError, setExchangeError] = useState<string | null>(null);
|
|
171
182
|
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
|
@@ -203,10 +214,15 @@ function InnerSybilionSession({
|
|
|
203
214
|
const apiBaseUrl = useMemo(() => getSybilionApiOriginFromSdk(sdk), [sdk]);
|
|
204
215
|
|
|
205
216
|
useEffect(() => {
|
|
217
|
+
// While Auth0 restores from localStorage, `isAuthenticated` is false — do not
|
|
218
|
+
// wipe the Sybilion JWT (would break reload: sign-in flash / lost session).
|
|
219
|
+
if (auth0.isLoading) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
206
222
|
if (!auth0.isAuthenticated || !auth0.user?.sub) {
|
|
207
223
|
persistToken(null);
|
|
208
224
|
}
|
|
209
|
-
}, [auth0.isAuthenticated, auth0.user?.sub, persistToken]);
|
|
225
|
+
}, [auth0.isLoading, auth0.isAuthenticated, auth0.user?.sub, persistToken]);
|
|
210
226
|
|
|
211
227
|
useEffect(() => {
|
|
212
228
|
if (!auth0.isAuthenticated || !auth0.user?.sub) {
|
|
@@ -242,13 +258,7 @@ function InnerSybilionSession({
|
|
|
242
258
|
return () => {
|
|
243
259
|
cancelled = true;
|
|
244
260
|
};
|
|
245
|
-
}, [
|
|
246
|
-
sdk,
|
|
247
|
-
auth0.isAuthenticated,
|
|
248
|
-
auth0.user?.sub,
|
|
249
|
-
persistToken,
|
|
250
|
-
doLogout,
|
|
251
|
-
]);
|
|
261
|
+
}, [sdk, auth0.isAuthenticated, auth0.user?.sub, persistToken, doLogout]);
|
|
252
262
|
|
|
253
263
|
const getAuth0AccessToken = useCallback(async () => {
|
|
254
264
|
if (!auth0.isAuthenticated) return undefined;
|
|
@@ -269,17 +279,29 @@ function InnerSybilionSession({
|
|
|
269
279
|
doLogout();
|
|
270
280
|
}, [doLogout]);
|
|
271
281
|
|
|
282
|
+
const hasAuth0User = Boolean(auth0.isAuthenticated && auth0.user?.sub);
|
|
283
|
+
|
|
284
|
+
/** Auth0 session is enough for app routes; Sybilion JWT is filled by exchange. */
|
|
272
285
|
const isAuthenticated = Boolean(
|
|
273
|
-
|
|
286
|
+
!exchangeError &&
|
|
287
|
+
!isLoggingOut &&
|
|
288
|
+
(hasAuth0User ||
|
|
289
|
+
(Boolean(sybilionToken) && (auth0.isAuthenticated || auth0.isLoading))),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const needsFirstSybilionToken = Boolean(
|
|
293
|
+
auth0.isAuthenticated &&
|
|
294
|
+
auth0.user &&
|
|
295
|
+
sybilionToken === null &&
|
|
296
|
+
exchangeError === null &&
|
|
297
|
+
!isLoggingOut,
|
|
274
298
|
);
|
|
275
299
|
|
|
300
|
+
/** Omit Auth0/exchange "busy" once we already have a Sybilion JWT — avoids header skeleton flicker on refresh. */
|
|
276
301
|
const isLoading =
|
|
277
|
-
auth0.isLoading ||
|
|
278
|
-
exchangeLoading ||
|
|
279
|
-
|
|
280
|
-
sybilionToken === null &&
|
|
281
|
-
exchangeError === null &&
|
|
282
|
-
!isLoggingOut);
|
|
302
|
+
(auth0.isLoading && sybilionToken === null) ||
|
|
303
|
+
(exchangeLoading && sybilionToken === null) ||
|
|
304
|
+
needsFirstSybilionToken;
|
|
283
305
|
|
|
284
306
|
const value = useMemo(
|
|
285
307
|
(): SybilionAuthContextValue => ({
|