diff --git a/package.json b/package.json index cbe33f67..1f8f414c 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "dependencies": { "@better-scroll/core": "2.5.1", "@iconify/vue": "4.1.2", + "@sa/alova": "workspace:*", "@sa/axios": "workspace:*", "@sa/color": "workspace:*", "@sa/hooks": "workspace:*", diff --git a/packages/alova/package.json b/packages/alova/package.json index ac20adc0..6cbdf975 100644 --- a/packages/alova/package.json +++ b/packages/alova/package.json @@ -2,7 +2,8 @@ "name": "@sa/alova", "version": "0.1.0", "exports": { - ".": "./src/index.ts" + ".": "./src/index.ts", + "./client": "./src/client.ts" }, "typesVersions": { "*": { @@ -11,6 +12,6 @@ }, "dependencies": { "@sa/utils": "workspace:*", - "alova": "^3.0.16" + "alova": "^3.0.19" } } diff --git a/packages/alova/src/client.ts b/packages/alova/src/client.ts new file mode 100644 index 00000000..0d76ebb9 --- /dev/null +++ b/packages/alova/src/client.ts @@ -0,0 +1 @@ +export * from 'alova/client'; diff --git a/packages/alova/src/constant.ts b/packages/alova/src/constant.ts index d6c5a338..be5c43ca 100644 --- a/packages/alova/src/constant.ts +++ b/packages/alova/src/constant.ts @@ -1,5 +1,2 @@ -/** request id key */ -export const REQUEST_ID_KEY = 'X-Request-Id'; - /** the backend error code key */ export const BACKEND_ERROR_CODE = 'BACKEND_ERROR'; diff --git a/packages/alova/src/index.ts b/packages/alova/src/index.ts index 1d17ef1e..8d871492 100644 --- a/packages/alova/src/index.ts +++ b/packages/alova/src/index.ts @@ -1,86 +1,76 @@ +import type { AlovaDefaultCacheAdapter, AlovaGenerics, AlovaGlobalCacheAdapter, AlovaRequestAdapter } from 'alova'; import { createAlova } from 'alova'; import VueHook from 'alova/vue'; +import type { FetchRequestInit } from 'alova/fetch'; import adapterFetch from 'alova/fetch'; import { createServerTokenAuthentication } from 'alova/client'; -import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant'; -import { isJSON } from './shared'; +import { BACKEND_ERROR_CODE } from './constant'; import type { CustomAlovaConfig, RequestOptions } from './type'; -export const createAlovaRequest = (customConfig: CustomAlovaConfig, options: RequestOptions) => { - const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication({ +export const createAlovaRequest = < + RequestConfig = FetchRequestInit, + ResponseType = Response, + ResponseHeader = Headers, + L1Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter, + L2Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter +>( + customConfig: CustomAlovaConfig< + AlovaGenerics + >, + options: RequestOptions> +) => { + const { tokenRefresher } = options; + const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication< + typeof VueHook, + AlovaRequestAdapter + >({ refreshTokenOnSuccess: { - isExpired: async response => { - const contentType = response.headers.get('Content-Type'); - 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(); - } - } + isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false, + handler: async (response, method) => tokenRefresher?.handler(response, method) }, refreshTokenOnError: { - isExpired: async response => { - const contentType = response.headers.get('Content-Type'); - 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(); - } - } + isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false, + handler: async (response, method) => tokenRefresher?.handler(response, method) } }); const instance = createAlova({ ...customConfig, timeout: customConfig.timeout ?? 10 * 1000, - requestAdapter: customConfig.requestAdapter ?? adapterFetch(), + requestAdapter: (customConfig.requestAdapter as any) ?? adapterFetch(), statesHook: VueHook, - beforeRequest: onAuthRequired(options.onRequest), + beforeRequest: onAuthRequired(options.onRequest as any), responded: onResponseRefreshToken({ - onSuccess: async resp => { + onSuccess: async (response, method) => { // check if http status is success - if (resp.ok || resp.status === 304) { - if ( - !isJSON(resp.headers.get('Content-Type') ?? '') || - (options.isBackendSuccess && (await options.isBackendSuccess(resp))) - ) { - return resp; + let error: any = null; + let transformedData: any = null; + try { + if (await options.isBackendSuccess(response)) { + transformedData = await options.transformBackendResponse(response); + } else { + error = new Error('the backend request error'); + error.code = BACKEND_ERROR_CODE; } - if (options.onBackendFail) { - const fail = await options.onBackendFail(resp); - if (fail) { - return fail; - } - } - return options.transformBackendResponse ? await options.transformBackendResponse(resp) : resp; + } catch (err) { + error = err; } - throw new Error(resp.statusText); + + if (error) { + await options.onError?.(error, response, method); + throw error; + } + + return transformedData; }, onComplete: options.onComplete, - onError: options.onError + onError: (error, method) => options.onError?.(error, null, method) }) }); return instance; }; -export { BACKEND_ERROR_CODE, REQUEST_ID_KEY }; +export { BACKEND_ERROR_CODE }; export type * from './type'; +export type * from 'alova'; diff --git a/packages/alova/src/shared.ts b/packages/alova/src/shared.ts deleted file mode 100644 index 70e628ef..00000000 --- a/packages/alova/src/shared.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isJSON(contentType: string) { - return contentType.includes('application/json'); -} diff --git a/packages/alova/src/type.ts b/packages/alova/src/type.ts index 335b8804..f7b5593a 100644 --- a/packages/alova/src/type.ts +++ b/packages/alova/src/type.ts @@ -1,18 +1,9 @@ -import type { - AlovaGenerics, - AlovaOptions, - AlovaRequestAdapter, - Method, - ResponseCompleteHandler, - ResponseErrorHandler -} from 'alova'; +import type { AlovaGenerics, AlovaOptions, AlovaRequestAdapter, Method, ResponseCompleteHandler } from 'alova'; export type CustomAlovaConfig = Omit< AlovaOptions, 'statesHook' | 'beforeRequest' | 'responded' | 'requestAdapter' > & { - /** expired token codes */ - expiredTokenCodes: string[]; /** request adapter. all request of alova will be sent by it. */ requestAdapter?: AlovaRequestAdapter; }; @@ -25,26 +16,23 @@ export interface RequestOptions { * * @param method alova Method Instance */ - onRequest?: (method: Method) => void | Promise; + onRequest?: AlovaOptions['beforeRequest']; /** * The hook to check backend response is success or not * - * @param response Axios response + * @param response alova response */ - isBackendSuccess?: (response: Response) => Promise; + isBackendSuccess: (response: AG['Response']) => Promise; - /** The hook to refresh token */ - refreshTokenHandler?: () => Promise; - /** - * 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 | Promise; + /** The config to refresh token */ + tokenRefresher?: { + /** detect the token is expired */ + isExpired(response: AG['Response'], Method: Method): Promise | boolean; + /** refhresh token handler */ + handler(response: AG['Response'], Method: Method): Promise; + }; + /** The hook after backend request complete */ onComplete?: ResponseCompleteHandler; /** @@ -54,11 +42,11 @@ export interface RequestOptions { * * @param error */ - onError?: ResponseErrorHandler; + onError?: (error: any, response: AG['Response'] | null, methodInstance: Method) => any | Promise; /** * transform backend response when the responseType is json * - * @param response Axios response + * @param response alova response */ - transformBackendResponse?: (response: AG['Response']) => any | Promise; + transformBackendResponse: (response: AG['Response']) => any; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5b0d016..516a0e02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@iconify/vue': specifier: 4.1.2 version: 4.1.2(vue@3.5.3(typescript@5.5.4)) + '@sa/alova': + specifier: workspace:* + version: link:packages/alova '@sa/axios': specifier: workspace:* version: link:packages/axios @@ -160,6 +163,15 @@ importers: specifier: 2.1.6 version: 2.1.6(typescript@5.5.4) + packages/alova: + dependencies: + '@sa/utils': + specifier: workspace:* + version: link:../utils + alova: + specifier: ^3.0.19 + version: 3.0.20 + packages/axios: dependencies: '@sa/utils': @@ -275,6 +287,9 @@ importers: packages: + '@alova/shared@1.0.5': + resolution: {integrity: sha512-/a2Qm+xebQJ1OlIgpslK+UL1J7yhkt1/Mqdq58a22+fSVdANukmUcF4j4w1DF3lxZ04SrqP+2oJprJ8UOvM+9Q==} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -1300,6 +1315,10 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + alova@3.0.20: + resolution: {integrity: sha512-jNrHzktwFXDItoD15LQeptIlYBThmnAoQPSpi/BNW9vqw2h1EMEDXmVIoStKhwg4M6GwnN0oQiTTsbd3y1a4Xw==} + engines: {node: '>= 18.0.0'} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -3329,6 +3348,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + rate-limiter-flexible@5.0.3: + resolution: {integrity: sha512-lWx2y8NBVlTOLPyqs+6y7dxfEpT6YFqKy3MzWbCy95sTTOhOuxufP2QvRyOHpfXpB9OUJPbVLybw3z3AVAS5fA==} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -4143,6 +4165,8 @@ packages: snapshots: + '@alova/shared@1.0.5': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -5210,6 +5234,11 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + alova@3.0.20: + dependencies: + '@alova/shared': 1.0.5 + rate-limiter-flexible: 5.0.3 + ansi-colors@4.1.3: {} ansi-escapes@7.0.0: @@ -7460,6 +7489,8 @@ snapshots: queue-microtask@1.2.3: {} + rate-limiter-flexible@5.0.3: {} + rc9@2.1.2: dependencies: defu: 6.1.4