refactor: create separate shared store for auth token and refresh action

This commit is contained in:
Ilia Mashkov
2026-03-24 09:26:10 +03:00
parent fd5b50a6f2
commit 6a2a826a11
27 changed files with 221 additions and 173 deletions

View File

@@ -0,0 +1,6 @@
export const BASE_URL =
import.meta.env.VITE_API_BASE_URL || "https://localhost:3001";
export const UNEXPECTED_ERROR_MESSAGE = "An unexpected error occured";
export const MOCK_TOKEN = "mock.access.token";
export const MOCK_FRESH_TOKEN = "mock.fresh.access.token";

View File

@@ -0,0 +1,3 @@
export * from "./types";
export * from "./stores/tokenStore";
export * from "./const";

View File

@@ -0,0 +1,45 @@
import { setupServer } from "msw/node";
import { MOCK_TOKEN } from "../const";
import { defaultStoreState, useTokenStore } from "./tokenStore";
import { refreshMock } from "shared/api/calls";
const server = setupServer(refreshMock);
describe("tokenStore", () => {
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => {
useTokenStore.getState().reset();
server.resetHandlers();
});
afterAll(() => server.close());
describe("reset", () => {
it("should reset the store to default state", () => {
useTokenStore.getState().reset();
expect(useTokenStore.getState()).toMatchObject(defaultStoreState);
});
});
describe("refreshToken", () => {
it("should update access token after successful refresh", async () => {
useTokenStore.setState({ accessToken: MOCK_TOKEN });
await useTokenStore.getState().refreshToken();
const { accessToken, error } = useTokenStore.getState();
expect(accessToken).toBeDefined();
expect(error).toBeNull();
});
it("should set error if refresh fails", async () => {
useTokenStore.setState({ accessToken: "old_token" });
await useTokenStore.getState().refreshToken();
const { error } = useTokenStore.getState();
expect(error).toBeDefined();
});
});
});

View File

@@ -0,0 +1,39 @@
import { create } from "zustand";
import type { TokenStore, TokenStoreState } from "../types";
import { callApi } from "shared/utils";
import { refresh } from "../../calls/refresh";
import { UNEXPECTED_ERROR_MESSAGE } from "../const";
export const defaultStoreState: TokenStoreState = {
accessToken: null,
error: null,
};
export const useTokenStore = create<TokenStore>()((set, get) => ({
...defaultStoreState,
reset: () => set(defaultStoreState),
refreshToken: async () => {
try {
const currentToken = get().accessToken;
if (!currentToken) {
return;
}
const [refreshResponse, refreshError] = await callApi(() =>
refresh(currentToken),
);
if (refreshError) {
set({ error: refreshError });
return;
}
set({ accessToken: refreshResponse?.accessToken });
} catch (error) {
console.error(error);
set({
error: new Error(UNEXPECTED_ERROR_MESSAGE),
});
}
},
}));

View File

@@ -0,0 +1,17 @@
import type { ApiError } from "shared/utils";
export interface TokenStoreState {
accessToken: string | null;
error: ApiError | Error | null;
}
export interface TokenStoreActions {
reset: () => void;
refreshToken: () => Promise<void>;
}
export type TokenStore = TokenStoreState & TokenStoreActions;
export interface RefreshTokenResponse {
accessToken: string;
}