@viibestack/ui 0.6.4 → 0.6.5
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 +1 -1
- package/src/auth.ts +38 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viibestack/ui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Dependency-free React UI kit -- icons, core components (Button, Card, Input, Modal, etc.), a real per-app data client (listRows/upsertRow/deleteRow/captureClientError), a real per-app end-user auth client (signUp/logIn/logOut/getCurrentUser/reportError), and a self-gating PoweredByBadge component -- backed by ViibeStack's own platform-managed data store.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
package/src/auth.ts
CHANGED
|
@@ -171,6 +171,39 @@ export async function logOut(): Promise<void> {
|
|
|
171
171
|
await callAppAuth("/logout", { method: "POST", body: JSON.stringify({ session_id: stored.sessionId }) });
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// Single-flight guard for the refresh call below -- without this, calling
|
|
175
|
+
// getCurrentUser() from more than one place on the same page load (a nav
|
|
176
|
+
// component AND a page component each checking auth in their own
|
|
177
|
+
// useEffect, a completely normal shape for a generated app) fires two
|
|
178
|
+
// concurrent /session/refresh calls with the SAME still-stored session_id.
|
|
179
|
+
// The auth service rotates that id on every refresh (see services/auth's
|
|
180
|
+
// own comment on that route), so only the first of the two UPDATEs
|
|
181
|
+
// actually matches a row -- the second gets back "session expired" even
|
|
182
|
+
// though the first one just succeeded, and its `saveStoredSession(null)`
|
|
183
|
+
// then wipes out the perfectly valid session the first call just saved,
|
|
184
|
+
// logging a real signed-in user out. Confirmed as the root cause of a
|
|
185
|
+
// real_accounts logout report (2026-07-19) -- funneling every concurrent
|
|
186
|
+
// caller through the SAME in-flight promise means only one refresh request
|
|
187
|
+
// is ever sent for a given expiring token, regardless of how many
|
|
188
|
+
// components independently call getCurrentUser() at once. Does not cover
|
|
189
|
+
// two separate browser TABS refreshing at once (each has its own module
|
|
190
|
+
// state) -- that's a rarer trigger and would need cross-tab coordination
|
|
191
|
+
// this fix doesn't attempt.
|
|
192
|
+
let refreshInFlight: Promise<AppUser | null> | null = null;
|
|
193
|
+
|
|
194
|
+
async function refreshCurrentSession(sessionId: string): Promise<AppUser | null> {
|
|
195
|
+
const result = await callAppAuth<SessionResponse>("/session/refresh", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
body: JSON.stringify({ session_id: sessionId }),
|
|
198
|
+
});
|
|
199
|
+
if (!result.ok) {
|
|
200
|
+
saveStoredSession(null);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
saveStoredSession(result.body);
|
|
204
|
+
return result.body.user;
|
|
205
|
+
}
|
|
206
|
+
|
|
174
207
|
// The one function generated app code is expected to call on every page
|
|
175
208
|
// load to find out "is anyone logged in" -- returns null if there's no
|
|
176
209
|
// session, or if the session has expired/been revoked server-side (in
|
|
@@ -183,16 +216,12 @@ export async function getCurrentUser(): Promise<AppUser | null> {
|
|
|
183
216
|
const needsRefresh = exp == null || exp - Math.floor(Date.now() / 1000) < 120;
|
|
184
217
|
if (!needsRefresh) return stored.user;
|
|
185
218
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (!result.ok) {
|
|
191
|
-
saveStoredSession(null);
|
|
192
|
-
return null;
|
|
219
|
+
if (!refreshInFlight) {
|
|
220
|
+
refreshInFlight = refreshCurrentSession(stored.sessionId).finally(() => {
|
|
221
|
+
refreshInFlight = null;
|
|
222
|
+
});
|
|
193
223
|
}
|
|
194
|
-
|
|
195
|
-
return result.body.user;
|
|
224
|
+
return refreshInFlight;
|
|
196
225
|
}
|
|
197
226
|
|
|
198
227
|
export async function requestPasswordReset(email: string): Promise<ActionResult> {
|