refactor: create separate shared store for auth token and refresh action
This commit is contained in:
6
src/shared/api/model/const/index.ts
Normal file
6
src/shared/api/model/const/index.ts
Normal 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";
|
||||
3
src/shared/api/model/index.ts
Normal file
3
src/shared/api/model/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./types";
|
||||
export * from "./stores/tokenStore";
|
||||
export * from "./const";
|
||||
45
src/shared/api/model/stores/tokenStore.spec.ts
Normal file
45
src/shared/api/model/stores/tokenStore.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
39
src/shared/api/model/stores/tokenStore.ts
Normal file
39
src/shared/api/model/stores/tokenStore.ts
Normal 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),
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
17
src/shared/api/model/types/index.ts
Normal file
17
src/shared/api/model/types/index.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user