@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 CHANGED
@@ -4121,7 +4121,7 @@ import {
4121
4121
  // package.json
4122
4122
  var package_default = {
4123
4123
  name: "@mehdad67/apitogo",
4124
- version: "0.1.30",
4124
+ version: "0.1.31",
4125
4125
  type: "module",
4126
4126
  sideEffects: [
4127
4127
  "**/*.css",
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mehdad67/apitogo",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -7,7 +7,11 @@ import type {
7
7
  AuthenticationProviderInitializer,
8
8
  } from "../authentication.js";
9
9
  import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
10
- import { type UserProfile, useAuthState } from "../state.js";
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.getState().setLoggedIn({
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 } = auth;
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
- {!isAuthenticated ? (
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
- {isAuthenticated ? (
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"