refactor(constants): 提取常量并优化国际化配置
- 创建API相关常量文件,包括响应码、HTTP状态码、请求头等 - 将认证相关常量从auth模块提取到独立的常量文件 - 在API客户端中使用新定义的常量替换硬编码值 - 更新认证API和服务中对常量的引用 - 在国际化配置中创建统一的文案常量以减少重复 - 将认证表单验证规则改为使用常量配置
This commit is contained in:
@@ -2,14 +2,19 @@ import ky, { HTTPError, type Options, TimeoutError } from 'ky'
|
||||
import {
|
||||
ACCESS_TOKEN_REFRESH_SKEW_MS,
|
||||
API_ERROR_MESSAGES,
|
||||
API_SUCCESS_CODE,
|
||||
AUTH_REFRESH_ATTEMPTED_CONTEXT_KEY,
|
||||
AUTH_REFRESH_ENDPOINT,
|
||||
AUTH_RELOGIN_REQUIRED_CODES,
|
||||
AUTH_SKIP_REFRESH_CONTEXT_KEY,
|
||||
AUTH_TOKEN_CACHE_SKEW_MS,
|
||||
AUTH_TOKEN_ENDPOINT,
|
||||
AUTHORIZATION_BEARER_PREFIX,
|
||||
CONTENT_TYPE_JSON,
|
||||
DEFAULT_REQUEST_ACCEPT_HEADER,
|
||||
DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
HTTP_STATUS,
|
||||
REQUEST_HEADERS,
|
||||
} from '@/constants'
|
||||
import type { AuthTokenDto } from '@/features/auth/api/types'
|
||||
import { getPreferredLanguage, isSupportedLanguage } from '@/i18n'
|
||||
@@ -65,13 +70,13 @@ function normalizeApiBaseUrl(baseUrl: string | undefined) {
|
||||
}
|
||||
|
||||
async function parseResponseBody(response: Response) {
|
||||
if (response.status === 204) {
|
||||
if (response.status === HTTP_STATUS.noContent) {
|
||||
return null
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') ?? ''
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
if (contentType.includes(CONTENT_TYPE_JSON)) {
|
||||
return response.json()
|
||||
}
|
||||
|
||||
@@ -110,7 +115,7 @@ async function toApiError(error: unknown) {
|
||||
if (error instanceof TimeoutError) {
|
||||
return new ApiError({
|
||||
message: API_ERROR_MESSAGES.timeout,
|
||||
status: 408,
|
||||
status: HTTP_STATUS.requestTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -143,14 +148,20 @@ const apiClient = ky.create({
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
({ request }) => {
|
||||
request.headers.set('Accept', DEFAULT_REQUEST_ACCEPT_HEADER)
|
||||
request.headers.set('lang', getRequestLanguage())
|
||||
request.headers.set(
|
||||
REQUEST_HEADERS.accept,
|
||||
DEFAULT_REQUEST_ACCEPT_HEADER,
|
||||
)
|
||||
request.headers.set(REQUEST_HEADERS.lang, getRequestLanguage())
|
||||
|
||||
const token = useAuthStore.getState().accessToken
|
||||
|
||||
if (token) {
|
||||
request.headers.set('Authorization', `Bearer ${token}`)
|
||||
request.headers.set('user-token', token)
|
||||
request.headers.set(
|
||||
REQUEST_HEADERS.authorization,
|
||||
`${AUTHORIZATION_BEARER_PREFIX}${token}`,
|
||||
)
|
||||
request.headers.set(REQUEST_HEADERS.userToken, token)
|
||||
}
|
||||
|
||||
if (shouldLogRequests) {
|
||||
@@ -222,14 +233,14 @@ function assertValidAuthEnvelope(data: unknown) {
|
||||
throw new ApiError({
|
||||
data,
|
||||
message: getApiEnvelopeMessage(data),
|
||||
status: 401,
|
||||
status: HTTP_STATUS.unauthorized,
|
||||
})
|
||||
}
|
||||
|
||||
function unwrapEnvelopeData<T>(response: ApiResponse<T>) {
|
||||
assertValidAuthEnvelope(response)
|
||||
|
||||
if (response.code === 1) {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -333,8 +344,8 @@ function createHeaders(headersInit?: Options['headers']) {
|
||||
async function buildRequestOptions(input: string, options?: Options) {
|
||||
const headers = createHeaders(options?.headers)
|
||||
|
||||
if (shouldAttachAuthToken(input) && !headers.has('auth-token')) {
|
||||
headers.set('auth-token', await fetchAuthToken())
|
||||
if (shouldAttachAuthToken(input) && !headers.has(REQUEST_HEADERS.authToken)) {
|
||||
headers.set(REQUEST_HEADERS.authToken, await fetchAuthToken())
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -361,7 +372,7 @@ async function request<TResponse>(input: string, options?: Options) {
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof HTTPError &&
|
||||
error.response.status === 401 &&
|
||||
error.response.status === HTTP_STATUS.unauthorized &&
|
||||
input !== AUTH_REFRESH_ENDPOINT &&
|
||||
options?.context?.[AUTH_SKIP_REFRESH_CONTEXT_KEY] !== true &&
|
||||
options?.context?.[AUTH_REFRESH_ATTEMPTED_CONTEXT_KEY] !== true
|
||||
@@ -379,7 +390,10 @@ async function request<TResponse>(input: string, options?: Options) {
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof HTTPError && error.response.status === 401) {
|
||||
if (
|
||||
error instanceof HTTPError &&
|
||||
error.response.status === HTTP_STATUS.unauthorized
|
||||
) {
|
||||
handleUnauthorizedSession()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LOGIN_PROMPT_DEDUP_MS } from '@/constants'
|
||||
import i18n from '@/i18n'
|
||||
import { notify } from '@/lib/notify'
|
||||
import { queryClient } from '@/lib/query/query-client'
|
||||
@@ -16,8 +17,6 @@ let authInitializationPromise: Promise<void> | null = null
|
||||
let refreshSessionPromise: Promise<boolean> | null = null
|
||||
let lastLoginPromptAt = 0
|
||||
|
||||
const LOGIN_PROMPT_DEDUP_MS = 1200
|
||||
|
||||
interface ClearAuthenticatedSessionOptions {
|
||||
clearBrowserStorage?: boolean
|
||||
clearQueryCache?: boolean
|
||||
|
||||
Reference in New Issue
Block a user