mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-23 15:42:09 -05:00
fix(client): broadcast session updates to other tabs on sign-out and user update (#8177)
Co-authored-by: Abhinav-kodes <183825080+Abhinav-kodes@users.noreply.github.com>
This commit is contained in:
@@ -61,7 +61,10 @@ export const getClientConfig = (
|
||||
...pluginsFetchPlugins,
|
||||
],
|
||||
});
|
||||
const { $sessionSignal, session } = getSessionAtom($fetch, options);
|
||||
const { $sessionSignal, session, broadcastSessionUpdate } = getSessionAtom(
|
||||
$fetch,
|
||||
options,
|
||||
);
|
||||
const plugins = options?.plugins || [];
|
||||
let pluginsActions = {} as Record<string, any>;
|
||||
const pluginsAtoms = {
|
||||
@@ -92,6 +95,13 @@ export const getClientConfig = (
|
||||
|
||||
return matchesCommonPaths;
|
||||
},
|
||||
callback(path) {
|
||||
if (path === "/sign-out") {
|
||||
broadcastSessionUpdate("signout");
|
||||
} else if (path === "/update-user" || path === "/update-session") {
|
||||
broadcastSessionUpdate("updateUser");
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -118,6 +118,8 @@ export function createDynamicPathProxy<T extends Record<string, any>>(
|
||||
//@ts-expect-error
|
||||
signal.set(!val);
|
||||
}, 10);
|
||||
// we also call the callback if it exists
|
||||
match.callback?.(routePath);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,10 @@ export function getSessionAtom(
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
let broadcastSessionUpdate: (
|
||||
trigger: "signout" | "getSession" | "updateUser",
|
||||
) => void = () => {};
|
||||
|
||||
onMount(session, () => {
|
||||
const refreshManager = createSessionRefreshManager({
|
||||
sessionAtom: session,
|
||||
@@ -32,6 +36,7 @@ export function getSessionAtom(
|
||||
});
|
||||
|
||||
refreshManager.init();
|
||||
broadcastSessionUpdate = refreshManager.broadcastSessionUpdate;
|
||||
|
||||
return () => {
|
||||
refreshManager.cleanup();
|
||||
@@ -41,5 +46,8 @@ export function getSessionAtom(
|
||||
return {
|
||||
session,
|
||||
$sessionSignal: $signal,
|
||||
broadcastSessionUpdate: (
|
||||
trigger: "signout" | "getSession" | "updateUser",
|
||||
) => broadcastSessionUpdate(trigger),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { atom } from "nanostores";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { getGlobalBroadcastChannel } from "./broadcast-channel";
|
||||
import { getGlobalOnlineManager } from "./online-manager";
|
||||
import type { SessionAtom } from "./session-atom";
|
||||
import { createSessionRefreshManager } from "./session-refresh";
|
||||
@@ -15,6 +16,8 @@ describe("session-refresh", () => {
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
delete (globalThis as any)[Symbol.for("better-auth:broadcast-channel")];
|
||||
});
|
||||
|
||||
it("should trigger network fetch and update session when refetchInterval fires", async () => {
|
||||
@@ -618,4 +621,68 @@ describe("session-refresh", () => {
|
||||
manager.cleanup();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("should broadcast session update when broadcastSessionUpdate is called with signout", () => {
|
||||
const channel = getGlobalBroadcastChannel();
|
||||
const postSpy = vi.spyOn(channel, "post");
|
||||
|
||||
const sessionAtom: SessionAtom = atom({
|
||||
data: {
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
session: { id: "session-1" },
|
||||
},
|
||||
error: null,
|
||||
isPending: false,
|
||||
});
|
||||
const sessionSignal = atom(false);
|
||||
const mockFetch = vi.fn(async () => ({ data: null, error: null }));
|
||||
|
||||
const manager = createSessionRefreshManager({
|
||||
sessionAtom,
|
||||
sessionSignal,
|
||||
$fetch: mockFetch as any,
|
||||
options: {},
|
||||
});
|
||||
|
||||
manager.init();
|
||||
manager.broadcastSessionUpdate("signout");
|
||||
|
||||
expect(postSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ data: { trigger: "signout" } }),
|
||||
);
|
||||
|
||||
manager.cleanup();
|
||||
});
|
||||
|
||||
it("should broadcast session update when broadcastSessionUpdate is called with updateUser", () => {
|
||||
const channel = getGlobalBroadcastChannel();
|
||||
const postSpy = vi.spyOn(channel, "post");
|
||||
|
||||
const sessionAtom: SessionAtom = atom({
|
||||
data: {
|
||||
user: { id: "1", email: "test@test.com" },
|
||||
session: { id: "session-1" },
|
||||
},
|
||||
error: null,
|
||||
isPending: false,
|
||||
});
|
||||
const sessionSignal = atom(false);
|
||||
const mockFetch = vi.fn(async () => ({ data: null, error: null }));
|
||||
|
||||
const manager = createSessionRefreshManager({
|
||||
sessionAtom,
|
||||
sessionSignal,
|
||||
$fetch: mockFetch as any,
|
||||
options: {},
|
||||
});
|
||||
|
||||
manager.init();
|
||||
manager.broadcastSessionUpdate("updateUser");
|
||||
|
||||
expect(postSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ data: { trigger: "updateUser" } }),
|
||||
);
|
||||
|
||||
manager.cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface ClientStore {
|
||||
export type ClientAtomListener = {
|
||||
matcher: (path: string) => boolean;
|
||||
signal: "$sessionSignal" | Omit<string, "$sessionSignal">;
|
||||
callback?: (path: string) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user