typo(packages): add types & update code

This commit is contained in:
子殊 2024-09-24 15:48:42 +08:00
parent 494db95614
commit e7799d2db1
2 changed files with 104 additions and 37 deletions

View File

@ -1,24 +1,12 @@
import { createAlova } from 'alova'; import { createAlova } from 'alova';
import VueHook from 'alova/vue'; import VueHook from 'alova/vue';
import adapterFetch from 'alova/fetch'; import adapterFetch from 'alova/fetch';
import { nanoid } from '@sa/utils';
import { createServerTokenAuthentication } from 'alova/client'; import { createServerTokenAuthentication } from 'alova/client';
import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant'; import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant';
import { isJSON } from './shared'; import { isJSON } from './shared';
import type { CustomAlovaConfig, RequestOptions } from './type';
export interface CustomAlovaConfig { export const createAlovaRequest = (customConfig: CustomAlovaConfig<any>, options: RequestOptions<any>) => {
baseURL: string;
headers?: Record<string, string>;
timeout?: number;
expiredTokenCodes: string[];
}
export interface RequestOptions {
refreshTokenHandler: () => Promise<boolean>;
onBackendFail: (response: Response) => Promise<Response | null>;
}
export const createAlovaRequest = (customConfig: CustomAlovaConfig, options: RequestOptions) => {
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication({ const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication({
refreshTokenOnSuccess: { refreshTokenOnSuccess: {
isExpired: async response => { isExpired: async response => {
@ -34,46 +22,60 @@ export const createAlovaRequest = (customConfig: CustomAlovaConfig, options: Req
return false; return false;
}, },
handler: async () => { handler: async () => {
await options.refreshTokenHandler(); if (options.refreshTokenHandler) {
await options.refreshTokenHandler();
}
} }
}, },
// 附加token refreshTokenOnError: {
assignToken: method => { isExpired: async response => {
const token = localStorage.getItem('SOY_token'); const contentType = response.headers.get('Content-Type');
method.config.headers.Authorization = token ? `Bearer ${JSON.parse(token)}` : null; if (isJSON(contentType ?? '')) {
const resp = response.clone();
const data = await resp.json();
const responseCode = String(data.code);
if (customConfig.expiredTokenCodes.includes(responseCode)) {
return true;
}
}
return false;
},
handler: async () => {
if (options.refreshTokenHandler) {
await options.refreshTokenHandler();
}
}
} }
}); });
const instance = createAlova({ const instance = createAlova({
baseURL: customConfig.baseURL, ...customConfig,
timeout: customConfig.timeout ?? 10 * 1000, timeout: customConfig.timeout ?? 10 * 1000,
requestAdapter: adapterFetch(), requestAdapter: customConfig.requestAdapter ?? adapterFetch(),
statesHook: VueHook, statesHook: VueHook,
beforeRequest: onAuthRequired(({ config }) => { beforeRequest: onAuthRequired(options.onRequest),
// 添加配置headers
config.headers = {
...config.headers,
...customConfig.headers
};
// set request id
const requestId = nanoid();
config.headers[REQUEST_ID_KEY] = requestId;
}),
responded: onResponseRefreshToken({ responded: onResponseRefreshToken({
onSuccess: async resp => { onSuccess: async resp => {
// check if http status is success
if (resp.ok || resp.status === 304) { if (resp.ok || resp.status === 304) {
if (isJSON(resp.headers.get('Content-Type') ?? '')) { if (
!isJSON(resp.headers.get('Content-Type') ?? '') ||
(options.isBackendSuccess && (await options.isBackendSuccess(resp)))
) {
return resp;
}
if (options.onBackendFail) {
const fail = await options.onBackendFail(resp); const fail = await options.onBackendFail(resp);
if (fail) { if (fail) {
return fail; return fail;
} }
return await resp.json();
} }
return resp; return options.transformBackendResponse ? await options.transformBackendResponse(resp) : resp;
} }
return Promise.reject(resp); throw new Error(resp.statusText);
} },
onComplete: options.onComplete,
onError: options.onError
}) })
}); });
@ -81,3 +83,4 @@ export const createAlovaRequest = (customConfig: CustomAlovaConfig, options: Req
}; };
export { BACKEND_ERROR_CODE, REQUEST_ID_KEY }; export { BACKEND_ERROR_CODE, REQUEST_ID_KEY };
export type * from './type';

View File

@ -0,0 +1,64 @@
import type {
AlovaGenerics,
AlovaOptions,
AlovaRequestAdapter,
Method,
ResponseCompleteHandler,
ResponseErrorHandler
} from 'alova';
export type CustomAlovaConfig<AG extends AlovaGenerics> = Omit<
AlovaOptions<AG>,
'statesHook' | 'beforeRequest' | 'responded' | 'requestAdapter'
> & {
/** expired token codes */
expiredTokenCodes: string[];
/** request adapter. all request of alova will be sent by it. */
requestAdapter?: AlovaRequestAdapter<AG['RequestConfig'], AG['Response'], AG['ResponseHeader']>;
};
export interface RequestOptions<AG extends AlovaGenerics> {
/**
* The hook before request
*
* For example: You can add header token in this hook
*
* @param method alova Method Instance
*/
onRequest?: (method: Method<AG>) => void | Promise<void>;
/**
* The hook to check backend response is success or not
*
* @param response Axios response
*/
isBackendSuccess?: (response: Response) => Promise<boolean>;
/** The hook to refresh token */
refreshTokenHandler?: () => Promise<void>;
/**
* The hook after backend request fail
*
* For example: You can handle the expired token in this hook
*
* @param response Axios response
* @param instance Axios instance
*/
onBackendFail?: (response: Response) => Promise<Response | null> | Promise<void>;
onComplete?: ResponseCompleteHandler<AG>;
/**
* The hook to handle error
*
* For example: You can show error message in this hook
*
* @param error
*/
onError?: ResponseErrorHandler<AG>;
/**
* transform backend response when the responseType is json
*
* @param response Axios response
*/
transformBackendResponse?: (response: AG['Response']) => any | Promise<any>;
}