109 行
2.8 KiB
TypeScript
109 行
2.8 KiB
TypeScript
import { apiRequestData } from "../api/http";
|
|
import { verifyEmailLoginCode } from "../api/auth";
|
|
import type { LoginRequest, LoginResponse, MeResponse } from "../api/types";
|
|
import { getDeviceFingerprint } from "./deviceFingerprint";
|
|
|
|
const JSON_HEADERS = {
|
|
"content-type": "application/json",
|
|
};
|
|
|
|
const REFRESH_SUCCESS_REPLAY_MS = 1000;
|
|
|
|
let refreshSessionPromise: Promise<LoginResponse> | null = null;
|
|
let refreshSessionReplay: { data: LoginResponse; expiresAt: number } | null = null;
|
|
let refreshSessionGeneration = 0;
|
|
|
|
export function resetRefreshSessionCache() {
|
|
refreshSessionGeneration += 1;
|
|
refreshSessionPromise = null;
|
|
refreshSessionReplay = null;
|
|
}
|
|
|
|
export async function loginWithPassword(payload: LoginRequest) {
|
|
resetRefreshSessionCache();
|
|
return apiRequestData<LoginResponse>("/api/auth/login", {
|
|
method: "POST",
|
|
auth: false,
|
|
retryOn401: false,
|
|
headers: {
|
|
...JSON_HEADERS,
|
|
"x-device-fingerprint": getDeviceFingerprint(),
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
export async function loginWithEmailCode(payload: { email: string; code: string }) {
|
|
resetRefreshSessionCache();
|
|
return verifyEmailLoginCode(payload);
|
|
}
|
|
|
|
async function requestRefreshSession() {
|
|
return apiRequestData<LoginResponse>("/api/auth/refresh", {
|
|
method: "POST",
|
|
auth: false,
|
|
retryOn401: false,
|
|
headers: {
|
|
"x-device-fingerprint": getDeviceFingerprint(),
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function refreshSession() {
|
|
const now = Date.now();
|
|
if (refreshSessionReplay && refreshSessionReplay.expiresAt > now) {
|
|
return refreshSessionReplay.data;
|
|
}
|
|
|
|
if (!refreshSessionPromise) {
|
|
const generation = refreshSessionGeneration;
|
|
const requestPromise = requestRefreshSession()
|
|
.then((data) => {
|
|
if (generation === refreshSessionGeneration) {
|
|
refreshSessionReplay = {
|
|
data,
|
|
expiresAt: Date.now() + REFRESH_SUCCESS_REPLAY_MS,
|
|
};
|
|
}
|
|
return data;
|
|
})
|
|
.catch((error: unknown) => {
|
|
if (generation === refreshSessionGeneration) {
|
|
refreshSessionReplay = null;
|
|
}
|
|
throw error;
|
|
});
|
|
|
|
refreshSessionPromise = requestPromise;
|
|
void requestPromise
|
|
.finally(() => {
|
|
if (refreshSessionPromise === requestPromise) {
|
|
refreshSessionPromise = null;
|
|
}
|
|
})
|
|
.catch(() => undefined);
|
|
}
|
|
|
|
return refreshSessionPromise;
|
|
}
|
|
|
|
export async function logoutSession() {
|
|
try {
|
|
return await apiRequestData<Record<string, never>>("/api/auth/logout", {
|
|
method: "POST",
|
|
auth: false,
|
|
retryOn401: false,
|
|
});
|
|
} finally {
|
|
resetRefreshSessionCache();
|
|
}
|
|
}
|
|
|
|
export async function fetchCurrentEmployee(accessToken: string) {
|
|
return apiRequestData<MeResponse>("/api/me", {
|
|
auth: true,
|
|
retryOn401: false,
|
|
accessTokenOverride: accessToken,
|
|
});
|
|
}
|