@mehdad67/apitogo 0.1.30 → 0.1.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.
- package/dist/cli/cli.js +1 -1
- package/dist/declarations/lib/authentication/providers/dev-portal.d.ts +3 -0
- package/dist/declarations/lib/authentication/state.d.ts +1 -0
- package/package.json +1 -1
- package/src/lib/authentication/providers/dev-portal.tsx +40 -4
- package/src/lib/authentication/state.ts +19 -0
- package/src/lib/components/Header.tsx +5 -2
- package/src/lib/components/MobileTopNavigation.tsx +4 -2
package/dist/cli/cli.js
CHANGED
|
@@ -20,6 +20,8 @@ export declare class DevPortalAuthenticationProvider extends CoreAuthenticationP
|
|
|
20
20
|
private readonly redirectToAfterSignOut;
|
|
21
21
|
private authMode;
|
|
22
22
|
private backendAvailable;
|
|
23
|
+
private refreshGeneration;
|
|
24
|
+
private refreshInFlight;
|
|
23
25
|
constructor({ apiBaseUrl, redirectToAfterSignIn, redirectToAfterSignUp, redirectToAfterSignOut, }: DevPortalAuthenticationConfig);
|
|
24
26
|
initialize(_context: ZudokuContext): Promise<void>;
|
|
25
27
|
onPageLoad: () => Promise<void>;
|
|
@@ -27,6 +29,7 @@ export declare class DevPortalAuthenticationProvider extends CoreAuthenticationP
|
|
|
27
29
|
private setPreviewState;
|
|
28
30
|
private ensureLiveBackend;
|
|
29
31
|
refreshUserProfile(): Promise<boolean>;
|
|
32
|
+
private refreshUserProfileInternal;
|
|
30
33
|
signIn(_: AuthActionContext, { redirectTo }?: AuthActionOptions): Promise<void>;
|
|
31
34
|
signUp(_: AuthActionContext, { redirectTo }?: AuthActionOptions): Promise<void>;
|
|
32
35
|
signOut(_: AuthActionContext): Promise<void>;
|
|
@@ -42,6 +42,7 @@ export declare const useAuthState: import("zustand").UseBoundStore<Omit<import("
|
|
|
42
42
|
getOptions: () => Partial<import("zustand/middleware").PersistOptions<AuthState, unknown, unknown>>;
|
|
43
43
|
};
|
|
44
44
|
}>;
|
|
45
|
+
export declare const waitForAuthStateHydration: () => Promise<void>;
|
|
45
46
|
export interface UserProfile {
|
|
46
47
|
sub: string;
|
|
47
48
|
email: string | undefined;
|
package/package.json
CHANGED
|
@@ -7,7 +7,11 @@ import type {
|
|
|
7
7
|
AuthenticationProviderInitializer,
|
|
8
8
|
} from "../authentication.js";
|
|
9
9
|
import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type UserProfile,
|
|
12
|
+
useAuthState,
|
|
13
|
+
waitForAuthStateHydration,
|
|
14
|
+
} from "../state.js";
|
|
11
15
|
import type {
|
|
12
16
|
DevPortalAuthMode,
|
|
13
17
|
EndUserMeResponse,
|
|
@@ -43,6 +47,8 @@ export class DevPortalAuthenticationProvider
|
|
|
43
47
|
private readonly redirectToAfterSignOut: string;
|
|
44
48
|
private authMode: DevPortalAuthMode = "preview";
|
|
45
49
|
private backendAvailable = false;
|
|
50
|
+
private refreshGeneration = 0;
|
|
51
|
+
private refreshInFlight: Promise<boolean> | null = null;
|
|
46
52
|
|
|
47
53
|
constructor({
|
|
48
54
|
apiBaseUrl,
|
|
@@ -63,6 +69,7 @@ export class DevPortalAuthenticationProvider
|
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
await waitForAuthStateHydration();
|
|
66
73
|
await this.syncBackendAvailability();
|
|
67
74
|
if (!this.backendAvailable) {
|
|
68
75
|
this.setPreviewState();
|
|
@@ -77,6 +84,7 @@ export class DevPortalAuthenticationProvider
|
|
|
77
84
|
return;
|
|
78
85
|
}
|
|
79
86
|
|
|
87
|
+
await waitForAuthStateHydration();
|
|
80
88
|
await this.syncBackendAvailability();
|
|
81
89
|
if (!this.backendAvailable) {
|
|
82
90
|
this.setPreviewState();
|
|
@@ -123,6 +131,18 @@ export class DevPortalAuthenticationProvider
|
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
async refreshUserProfile(): Promise<boolean> {
|
|
134
|
+
if (this.refreshInFlight) {
|
|
135
|
+
return this.refreshInFlight;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.refreshInFlight = this.refreshUserProfileInternal().finally(() => {
|
|
139
|
+
this.refreshInFlight = null;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return this.refreshInFlight;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async refreshUserProfileInternal(): Promise<boolean> {
|
|
126
146
|
if (!this.apiBaseUrl) {
|
|
127
147
|
this.setPreviewState();
|
|
128
148
|
return false;
|
|
@@ -133,12 +153,16 @@ export class DevPortalAuthenticationProvider
|
|
|
133
153
|
return false;
|
|
134
154
|
}
|
|
135
155
|
|
|
156
|
+
await waitForAuthStateHydration();
|
|
136
157
|
await this.syncBackendAvailability();
|
|
137
158
|
if (!this.backendAvailable) {
|
|
138
159
|
this.setPreviewState();
|
|
139
160
|
return false;
|
|
140
161
|
}
|
|
141
162
|
|
|
163
|
+
const generation = ++this.refreshGeneration;
|
|
164
|
+
useAuthState.setState({ isPending: true });
|
|
165
|
+
|
|
142
166
|
try {
|
|
143
167
|
const response = await fetch(`${this.apiBaseUrl}/api/v1/auth/me`, {
|
|
144
168
|
method: "GET",
|
|
@@ -147,6 +171,10 @@ export class DevPortalAuthenticationProvider
|
|
|
147
171
|
redirect: "manual",
|
|
148
172
|
});
|
|
149
173
|
|
|
174
|
+
if (generation !== this.refreshGeneration) {
|
|
175
|
+
return useAuthState.getState().isAuthenticated;
|
|
176
|
+
}
|
|
177
|
+
|
|
150
178
|
// Backends may still respond with 302 to the IdP; treat as unauthenticated.
|
|
151
179
|
if (
|
|
152
180
|
response.type === "opaqueredirect" ||
|
|
@@ -174,22 +202,30 @@ export class DevPortalAuthenticationProvider
|
|
|
174
202
|
}
|
|
175
203
|
|
|
176
204
|
const me = (await response.json()) as EndUserMeResponse;
|
|
205
|
+
if (generation !== this.refreshGeneration) {
|
|
206
|
+
return useAuthState.getState().isAuthenticated;
|
|
207
|
+
}
|
|
208
|
+
|
|
177
209
|
const profile: UserProfile = mapEndUserMeToProfile(me);
|
|
178
210
|
|
|
179
|
-
useAuthState.
|
|
211
|
+
useAuthState.setState({
|
|
212
|
+
isAuthenticated: true,
|
|
213
|
+
isPending: false,
|
|
180
214
|
profile,
|
|
181
215
|
providerData: {
|
|
182
216
|
type: "dev-portal",
|
|
183
217
|
apiBaseUrl: this.apiBaseUrl,
|
|
184
218
|
authMode: "live",
|
|
185
219
|
},
|
|
186
|
-
});
|
|
187
|
-
useAuthState.setState({
|
|
188
220
|
isBackendAvailable: true,
|
|
189
221
|
authMode: "live",
|
|
190
222
|
});
|
|
191
223
|
return true;
|
|
192
224
|
} catch {
|
|
225
|
+
if (generation !== this.refreshGeneration) {
|
|
226
|
+
return useAuthState.getState().isAuthenticated;
|
|
227
|
+
}
|
|
228
|
+
|
|
193
229
|
this.setPreviewState();
|
|
194
230
|
return false;
|
|
195
231
|
}
|
|
@@ -106,6 +106,25 @@ syncZustandState(authState);
|
|
|
106
106
|
|
|
107
107
|
export const useAuthState = authState;
|
|
108
108
|
|
|
109
|
+
/** Wait until persisted auth metadata has rehydrated from localStorage. */
|
|
110
|
+
export const waitForAuthStateHydration = (): Promise<void> => {
|
|
111
|
+
if (typeof window === "undefined") {
|
|
112
|
+
return Promise.resolve();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const { persist } = useAuthState;
|
|
116
|
+
if (persist.hasHydrated()) {
|
|
117
|
+
return Promise.resolve();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const unsubscribe = persist.onFinishHydration(() => {
|
|
122
|
+
unsubscribe();
|
|
123
|
+
resolve();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
109
128
|
export interface UserProfile {
|
|
110
129
|
sub: string;
|
|
111
130
|
email: string | undefined;
|
|
@@ -73,13 +73,16 @@ const ProfileMenu = () => {
|
|
|
73
73
|
const context = useZudoku();
|
|
74
74
|
const profileItems = context.getProfileMenuItems();
|
|
75
75
|
const auth = useAuth();
|
|
76
|
-
const { isAuthEnabled, isAuthenticated, profile, isBackendAvailable } =
|
|
76
|
+
const { isAuthEnabled, isAuthenticated, isPending, profile, isBackendAvailable } =
|
|
77
|
+
auth;
|
|
77
78
|
|
|
78
79
|
if (!isAuthEnabled || !isBackendAvailable) return null;
|
|
79
80
|
|
|
80
81
|
return (
|
|
81
82
|
<ClientOnly fallback={<Skeleton className="rounded-sm h-5 w-24 mr-4" />}>
|
|
82
|
-
{
|
|
83
|
+
{isPending ? (
|
|
84
|
+
<Skeleton className="rounded-sm h-9 w-24" />
|
|
85
|
+
) : !isAuthenticated ? (
|
|
83
86
|
<Button size="lg" variant="ghost" onClick={() => auth.login()}>
|
|
84
87
|
Login
|
|
85
88
|
</Button>
|
|
@@ -130,7 +130,7 @@ export const MobileTopNavigation = () => {
|
|
|
130
130
|
getProfileMenuItems,
|
|
131
131
|
} = context;
|
|
132
132
|
const headerNavigation = header?.navigation ?? [];
|
|
133
|
-
const { isAuthenticated, profile, isAuthEnabled, isBackendAvailable } =
|
|
133
|
+
const { isAuthenticated, profile, isAuthEnabled, isBackendAvailable, isPending } =
|
|
134
134
|
authState;
|
|
135
135
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
136
136
|
|
|
@@ -237,7 +237,9 @@ export const MobileTopNavigation = () => {
|
|
|
237
237
|
<ClientOnly
|
|
238
238
|
fallback={<Skeleton className="rounded-sm h-8 w-16" />}
|
|
239
239
|
>
|
|
240
|
-
{
|
|
240
|
+
{isPending ? (
|
|
241
|
+
<Skeleton className="rounded-sm h-8 w-16" />
|
|
242
|
+
) : isAuthenticated ? (
|
|
241
243
|
<Button asChild variant="outline">
|
|
242
244
|
<Link
|
|
243
245
|
to="/signout"
|