fix(customer): don't log customer out on transient session-refresh errors
This commit is contained in:
@@ -84,22 +84,37 @@ export const CustomerAuthProvider: React.FC<ProviderProps> = ({ children }) => {
|
|||||||
* mount-time sessionStorage cache and stays stale until a hard reload.
|
* mount-time sessionStorage cache and stays stale until a hard reload.
|
||||||
*/
|
*/
|
||||||
const refreshSession = React.useCallback(async () => {
|
const refreshSession = React.useCallback(async () => {
|
||||||
|
// Contract (see customerService.session()):
|
||||||
|
// - object → fresh data, store it.
|
||||||
|
// - null → server says we're explicitly logged out (401);
|
||||||
|
// clear local state.
|
||||||
|
// - throw → transient error (network blip, 5xx, timeout).
|
||||||
|
// KEEP whatever state we have — logging the user out
|
||||||
|
// on a flaky network call is the wrong default. The
|
||||||
|
// old code clobbered local state on any error, which
|
||||||
|
// caused mysterious "customer keeps getting kicked
|
||||||
|
// out" reports during unrelated admin saves and on
|
||||||
|
// brief connection drops.
|
||||||
|
let response: Awaited<ReturnType<typeof customerService.session>>;
|
||||||
try {
|
try {
|
||||||
const response = await customerService.session();
|
response = await customerService.session();
|
||||||
if (response?.customer) {
|
} catch (err) {
|
||||||
setCustomerState(response.customer);
|
// Transient. Don't touch state. The next focus/visibility tick
|
||||||
setFeatures(response.features);
|
// will retry; if the customer really is unauthenticated the
|
||||||
setBranding(response.branding);
|
// retry will see the 401 and clear properly.
|
||||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(response.customer));
|
// eslint-disable-next-line no-console
|
||||||
sessionStorage.setItem(FEATURES_KEY, JSON.stringify(response.features));
|
console.warn('[CustomerAuth] session refresh failed transiently, keeping current state', err);
|
||||||
sessionStorage.setItem(BRANDING_KEY, JSON.stringify(response.branding));
|
return;
|
||||||
} else {
|
}
|
||||||
setCustomerState(null);
|
if (response?.customer) {
|
||||||
sessionStorage.removeItem(STORAGE_KEY);
|
setCustomerState(response.customer);
|
||||||
sessionStorage.removeItem(FEATURES_KEY);
|
setFeatures(response.features);
|
||||||
sessionStorage.removeItem(BRANDING_KEY);
|
setBranding(response.branding);
|
||||||
}
|
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(response.customer));
|
||||||
} catch {
|
sessionStorage.setItem(FEATURES_KEY, JSON.stringify(response.features));
|
||||||
|
sessionStorage.setItem(BRANDING_KEY, JSON.stringify(response.branding));
|
||||||
|
} else {
|
||||||
|
// Explicit 401 — server says no.
|
||||||
setCustomerState(null);
|
setCustomerState(null);
|
||||||
sessionStorage.removeItem(STORAGE_KEY);
|
sessionStorage.removeItem(STORAGE_KEY);
|
||||||
sessionStorage.removeItem(FEATURES_KEY);
|
sessionStorage.removeItem(FEATURES_KEY);
|
||||||
|
|||||||
@@ -126,6 +126,23 @@ export const customerService = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the current customer session.
|
||||||
|
*
|
||||||
|
* Return contract:
|
||||||
|
* - object → fresh customer + features + branding from the server.
|
||||||
|
* - null → backend says we are NOT authenticated (401). The
|
||||||
|
* caller should clear local state and bounce to login.
|
||||||
|
* - throws → any other error (network blip, 5xx, timeout, 410
|
||||||
|
* from a feature-flag flip mid-flight). The caller
|
||||||
|
* should KEEP whatever state it has — punishing the
|
||||||
|
* user with a logout for a transient failure is the
|
||||||
|
* wrong default. Previously this catch swallowed
|
||||||
|
* everything and returned null, which logged the
|
||||||
|
* customer out on the slightest server hiccup
|
||||||
|
* (including the brief window while the admin saves
|
||||||
|
* an unrelated change like gallery assignments).
|
||||||
|
*/
|
||||||
async session(): Promise<{
|
async session(): Promise<{
|
||||||
customer: CustomerProfile;
|
customer: CustomerProfile;
|
||||||
features: { calendar: boolean; quotes: boolean; bills: boolean };
|
features: { calendar: boolean; quotes: boolean; bills: boolean };
|
||||||
@@ -142,8 +159,14 @@ export const customerService = {
|
|||||||
features: response.data.features || { calendar: false, quotes: false, bills: false },
|
features: response.data.features || { calendar: false, quotes: false, bills: false },
|
||||||
branding: response.data.branding || { showLogo: true, showCompanyName: true },
|
branding: response.data.branding || { showLogo: true, showCompanyName: true },
|
||||||
};
|
};
|
||||||
} catch {
|
} catch (error: any) {
|
||||||
return null;
|
// Only treat an explicit 401 as "session is gone". Anything else
|
||||||
|
// (network failure, server 500, etc.) is a transient problem
|
||||||
|
// and should not log the customer out.
|
||||||
|
if (error?.response?.status === 401) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user