mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 11:24:57 -05:00
fix(client): preserve stale session data on network or server errors (#8437)
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createAuthClient } from "./solid";
|
||||
import { testClientPlugin } from "./test-plugin";
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/8420
|
||||
*/
|
||||
describe("useAuthQuery - error handling", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should preserve stale data on network error (fetch throws)", async () => {
|
||||
let shouldFail = false;
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [testClientPlugin()],
|
||||
fetchOptions: {
|
||||
customFetchImpl: async (_url) => {
|
||||
if (shouldFail) {
|
||||
throw new TypeError("Failed to fetch");
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
session: { id: "session-1" },
|
||||
}),
|
||||
);
|
||||
},
|
||||
baseURL: "http://localhost:3000",
|
||||
},
|
||||
});
|
||||
|
||||
const session = client.useSession();
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
expect(session().data).toMatchObject({
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
});
|
||||
expect(session().error).toBeNull();
|
||||
|
||||
// Network failure on refetch
|
||||
shouldFail = true;
|
||||
await session().refetch();
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
// Stale data should be preserved
|
||||
expect(session().data).toMatchObject({
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
});
|
||||
});
|
||||
|
||||
it("should clear data on 401 unauthorized response", async () => {
|
||||
let returnUnauthorized = false;
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [testClientPlugin()],
|
||||
fetchOptions: {
|
||||
customFetchImpl: async (url) => {
|
||||
const urlStr = typeof url === "string" ? url : url.toString();
|
||||
if (returnUnauthorized && urlStr.includes("/get-session")) {
|
||||
return new Response(JSON.stringify({ message: "Unauthorized" }), {
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
session: { id: "session-1" },
|
||||
}),
|
||||
);
|
||||
},
|
||||
baseURL: "http://localhost:3000",
|
||||
},
|
||||
});
|
||||
|
||||
const session = client.useSession();
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
expect(session().data).toMatchObject({
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
});
|
||||
|
||||
// Refetch with 401
|
||||
returnUnauthorized = true;
|
||||
await session().refetch();
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
expect(session().data).toBeNull();
|
||||
});
|
||||
|
||||
it("should preserve stale data on 500 server error", async () => {
|
||||
let returnServerError = false;
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [testClientPlugin()],
|
||||
fetchOptions: {
|
||||
customFetchImpl: async (url) => {
|
||||
const urlStr = typeof url === "string" ? url : url.toString();
|
||||
if (returnServerError && urlStr.includes("/get-session")) {
|
||||
return new Response(
|
||||
JSON.stringify({ message: "Internal Server Error" }),
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
session: { id: "session-1" },
|
||||
}),
|
||||
);
|
||||
},
|
||||
baseURL: "http://localhost:3000",
|
||||
},
|
||||
});
|
||||
|
||||
const session = client.useSession();
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
expect(session().data).toMatchObject({
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
});
|
||||
|
||||
// Refetch with 500
|
||||
returnServerError = true;
|
||||
await session().refetch();
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
// Stale data should be preserved on 500
|
||||
expect(session().data).toMatchObject({
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -79,9 +79,12 @@ export const useAuthQuery = <T>(
|
||||
: request.retry?.attempts;
|
||||
const retryAttempt = request.retryAttempt || 0;
|
||||
if (retryAttempts && retryAttempt < retryAttempts) return;
|
||||
const isUnauthorized = context.error.status === 401;
|
||||
value.set({
|
||||
error: context.error,
|
||||
data: null,
|
||||
data: isUnauthorized
|
||||
? null // clear session on HTTP 401
|
||||
: value.get().data, // preserve stale data on other errors
|
||||
isPending: false,
|
||||
isRefetching: false,
|
||||
refetch: value.value.refetch,
|
||||
@@ -103,7 +106,7 @@ export const useAuthQuery = <T>(
|
||||
.catch((error) => {
|
||||
value.set({
|
||||
error,
|
||||
data: null,
|
||||
data: value.get().data,
|
||||
isPending: false,
|
||||
isRefetching: false,
|
||||
refetch: value.value.refetch,
|
||||
|
||||
Reference in New Issue
Block a user