taro-uno-ui 0.9.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/js/{index-DffLRSro.js → index-CDFsvu80.js} +15369 -10741
- package/dist/js/index-CDFsvu80.js.map +1 -0
- package/dist/js/index-DFdcksbe.js.map +1 -1
- package/dist/js/index-DXRIkWX1.js.map +1 -1
- package/dist/js/{index-6NJ3A1Dn.js → index-JffnTUrv.js} +15430 -10801
- package/dist/js/index-JffnTUrv.js.map +1 -0
- package/dist/utils/http/request.d.ts +280 -0
- package/package.json +14 -10
- package/src/components/basic/Button/Button.tsx +53 -13
- package/src/components/basic/Button/Button.types.ts +45 -9
- package/src/components/basic/Divider/Divider.tsx +60 -29
- package/src/components/basic/Icon/Icon.data.ts +474 -0
- package/src/components/basic/Icon/Icon.test.tsx +2 -2
- package/src/components/basic/Icon/Icon.tsx +48 -35
- package/src/components/basic/Icon/IconManager.ts +229 -0
- package/src/components/basic/Text/Text.styles.ts +3 -3
- package/src/components/basic/Text/Text.types.ts +14 -4
- package/src/components/basic/Typography/Typography.styles.ts +10 -9
- package/src/components/basic/Typography/Typography.tsx +15 -13
- package/src/components/basic/Typography/Typography.types.ts +41 -41
- package/src/components/basic/Typography/index.tsx +1 -1
- package/src/components/basic/Video/Video.styles.ts +777 -0
- package/src/components/basic/Video/Video.test.tsx +490 -0
- package/src/components/basic/Video/Video.tsx +1468 -0
- package/src/components/basic/Video/Video.types.ts +500 -0
- package/src/components/basic/Video/index.tsx +26 -0
- package/src/components/basic/index.tsx +13 -15
- package/src/components/common/ErrorBoundary.tsx +1 -1
- package/src/components/common/LazyComponent.tsx +9 -8
- package/src/components/common/SecurityProvider.tsx +2 -14
- package/src/components/common/ThemeProvider.tsx +43 -56
- package/src/components/common/VirtualList.tsx +187 -205
- package/src/components/common/index.tsx +25 -0
- package/src/components/display/Avatar/Avatar.styles.ts +1 -1
- package/src/components/display/Avatar/Avatar.tsx +6 -19
- package/src/components/display/Avatar/Avatar.types.ts +1 -1
- package/src/components/display/Avatar/index.ts +1 -1
- package/src/components/display/Badge/Badge.tsx +3 -16
- package/src/components/display/Badge/Badge.types.ts +1 -1
- package/src/components/display/Badge/index.ts +1 -1
- package/src/components/display/Calendar/Calendar.styles.ts +36 -36
- package/src/components/display/Calendar/Calendar.test.tsx +27 -15
- package/src/components/display/Calendar/Calendar.tsx +56 -35
- package/src/components/display/Calendar/Calendar.types.ts +1 -1
- package/src/components/display/Calendar/index.ts +1 -1
- package/src/components/display/Card/Card.styles.ts +2 -2
- package/src/components/display/Card/Card.test.tsx +6 -4
- package/src/components/display/Card/Card.tsx +1 -1
- package/src/components/display/Card/Card.types.ts +4 -4
- package/src/components/display/Card/index.ts +1 -1
- package/src/components/display/Carousel/Carousel.styles.ts +31 -31
- package/src/components/display/Carousel/Carousel.tsx +34 -39
- package/src/components/display/Carousel/Carousel.types.ts +1 -1
- package/src/components/display/Carousel/index.ts +1 -1
- package/src/components/display/List/List.styles.ts +3 -3
- package/src/components/display/List/List.tsx +0 -1
- package/src/components/display/List/index.ts +1 -1
- package/src/components/display/Rate/Rate.styles.ts +5 -17
- package/src/components/display/Rate/Rate.tsx +6 -14
- package/src/components/display/Rate/Rate.types.ts +4 -3
- package/src/components/display/Rate/index.ts +3 -11
- package/src/components/display/Table/Table.test.tsx +2 -0
- package/src/components/display/Table/Table.tsx +3 -7
- package/src/components/display/Table/Table.types.ts +3 -2
- package/src/components/display/Tag/Tag.styles.ts +31 -31
- package/src/components/display/Tag/Tag.tsx +9 -26
- package/src/components/display/Tag/Tag.types.ts +1 -1
- package/src/components/display/Tag/index.ts +1 -1
- package/src/components/display/Timeline/Timeline.styles.ts +32 -32
- package/src/components/display/Timeline/Timeline.tsx +23 -42
- package/src/components/display/Timeline/Timeline.types.ts +1 -1
- package/src/components/display/Timeline/index.ts +1 -1
- package/src/components/display/index.tsx +33 -29
- package/src/components/feedback/Loading/Loading.tsx +6 -1
- package/src/components/feedback/Loading/index.ts +2 -5
- package/src/components/feedback/Message/Message.styles.ts +3 -3
- package/src/components/feedback/Message/index.ts +2 -5
- package/src/components/feedback/Modal/Modal.styles.ts +1 -1
- package/src/components/feedback/Modal/Modal.tsx +9 -31
- package/src/components/feedback/Modal/Modal.types.ts +12 -2
- package/src/components/feedback/Notification/Notification.styles.ts +49 -39
- package/src/components/feedback/Notification/Notification.test.tsx +1 -1
- package/src/components/feedback/Notification/Notification.tsx +97 -120
- package/src/components/feedback/Notification/Notification.types.ts +11 -8
- package/src/components/feedback/Notification/NotificationManager.tsx +135 -106
- package/src/components/feedback/Notification/index.ts +10 -3
- package/src/components/feedback/Notification/index.tsx +16 -26
- package/src/components/feedback/Progress/Progress.styles.ts +23 -14
- package/src/components/feedback/Progress/Progress.tsx +93 -113
- package/src/components/feedback/Progress/Progress.types.ts +1 -1
- package/src/components/feedback/Progress/index.ts +1 -1
- package/src/components/feedback/Progress/utils/animation.ts +12 -23
- package/src/components/feedback/Progress/utils/index.ts +2 -2
- package/src/components/feedback/Progress/utils/progress-calculator.ts +14 -32
- package/src/components/feedback/Result/Result.styles.ts +29 -29
- package/src/components/feedback/Result/Result.tsx +8 -20
- package/src/components/feedback/Result/Result.types.ts +7 -7
- package/src/components/feedback/Result/index.tsx +1 -1
- package/src/components/feedback/Toast/Toast.styles.ts +1 -1
- package/src/components/feedback/Toast/Toast.tsx +25 -13
- package/src/components/feedback/Tooltip/Tooltip.examples.tsx +21 -44
- package/src/components/feedback/Tooltip/Tooltip.styles.ts +16 -22
- package/src/components/feedback/Tooltip/Tooltip.test.tsx +1 -1
- package/src/components/feedback/Tooltip/Tooltip.tsx +65 -46
- package/src/components/feedback/Tooltip/Tooltip.types.ts +14 -20
- package/src/components/feedback/Tooltip/index.ts +1 -1
- package/src/components/feedback/Tooltip/index.tsx +12 -24
- package/src/components/feedback/index.tsx +54 -42
- package/src/components/form/Cascader/Cascader.styles.ts +2 -2
- package/src/components/form/Cascader/Cascader.tsx +84 -88
- package/src/components/form/Cascader/Cascader.types.ts +49 -50
- package/src/components/form/Cascader/hooks/useCascaderFieldNames.ts +11 -8
- package/src/components/form/Cascader/hooks/useCascaderOptions.ts +73 -55
- package/src/components/form/Cascader/hooks/useCascaderState.ts +31 -25
- package/src/components/form/Cascader/index.ts +1 -1
- package/src/components/form/Cascader/utils/formatDisplayValue.ts +4 -4
- package/src/components/form/Checkbox/Checkbox.styles.ts +83 -84
- package/src/components/form/Checkbox/Checkbox.tsx +2 -9
- package/src/components/form/Checkbox/CheckboxGroup.tsx +7 -7
- package/src/components/form/DatePicker/DatePicker.test.tsx +1 -1
- package/src/components/form/DatePicker/DatePicker.tsx +91 -75
- package/src/components/form/DatePicker/DatePicker.types.ts +4 -1
- package/src/components/form/Form/Form.tsx +66 -504
- package/src/components/form/Form/Form.types.ts +16 -1
- package/src/components/form/Form/useFormLogic.ts +497 -0
- package/src/components/form/Input/Input.styles.ts +8 -1
- package/src/components/form/Input/Input.tsx +55 -291
- package/src/components/form/Input/Input.types.ts +13 -1
- package/src/components/form/Input/useInputLogic.test.ts +82 -0
- package/src/components/form/Input/useInputLogic.ts +260 -0
- package/src/components/form/InputNumber/InputNumber.styles.ts +76 -25
- package/src/components/form/InputNumber/InputNumber.tsx +53 -21
- package/src/components/form/InputNumber/InputNumber.types.ts +21 -3
- package/src/components/form/InputNumber/components/InputNumberClearButton.tsx +3 -11
- package/src/components/form/InputNumber/components/InputNumberControls.tsx +3 -12
- package/src/components/form/InputNumber/hooks/index.ts +1 -1
- package/src/components/form/InputNumber/hooks/useInputNumberState.ts +7 -9
- package/src/components/form/InputNumber/hooks/useInputNumberValidation.ts +18 -17
- package/src/components/form/InputNumber/index.ts +7 -7
- package/src/components/form/Radio/Radio.styles.ts +1 -8
- package/src/components/form/Radio/Radio.tsx +3 -9
- package/src/components/form/Radio/Radio.types.ts +5 -1
- package/src/components/form/Select/Select.styles.ts +5 -1
- package/src/components/form/Select/Select.tsx +15 -15
- package/src/components/form/Select/Select.types.ts +2 -1
- package/src/components/form/Slider/Slider.styles.ts +13 -13
- package/src/components/form/Slider/Slider.tsx +19 -33
- package/src/components/form/Slider/Slider.types.ts +14 -12
- package/src/components/form/Slider/index.tsx +2 -9
- package/src/components/form/Switch/Switch.styles.ts +1 -7
- package/src/components/form/Switch/Switch.tsx +7 -13
- package/src/components/form/Textarea/Textarea.styles.ts +4 -4
- package/src/components/form/Textarea/Textarea.tsx +7 -1
- package/src/components/form/Textarea/Textarea.types.ts +4 -1
- package/src/components/form/TimePicker/TimePicker.styles.ts +8 -12
- package/src/components/form/TimePicker/TimePicker.tsx +122 -100
- package/src/components/form/TimePicker/TimePicker.types.ts +2 -2
- package/src/components/form/TimePicker/index.ts +1 -1
- package/src/components/form/Transfer/Transfer.styles.ts +3 -15
- package/src/components/form/Transfer/Transfer.tsx +146 -134
- package/src/components/form/Transfer/Transfer.types.ts +34 -26
- package/src/components/form/Transfer/components/TransferItem.tsx +55 -62
- package/src/components/form/Transfer/components/TransferList.tsx +212 -199
- package/src/components/form/Transfer/components/TransferOperations.tsx +52 -55
- package/src/components/form/Transfer/components/TransferPagination.tsx +115 -111
- package/src/components/form/Transfer/components/TransferSearch.tsx +52 -55
- package/src/components/form/Transfer/hooks/useTransferData.ts +91 -81
- package/src/components/form/Transfer/hooks/useTransferState.ts +22 -16
- package/src/components/form/Transfer/index.ts +2 -8
- package/src/components/form/Upload/Upload.styles.ts +21 -21
- package/src/components/form/Upload/Upload.tsx +189 -142
- package/src/components/form/Upload/Upload.types.ts +31 -31
- package/src/components/form/Upload/index.tsx +1 -1
- package/src/components/form/index.tsx +60 -29
- package/src/components/index.tsx +0 -1
- package/src/components/layout/Affix/Affix.styles.ts +16 -11
- package/src/components/layout/Affix/Affix.tsx +67 -75
- package/src/components/layout/Affix/Affix.types.ts +18 -18
- package/src/components/layout/Affix/index.tsx +1 -1
- package/src/components/layout/Col/Col.styles.ts +17 -17
- package/src/components/layout/Col/Col.test.tsx +7 -5
- package/src/components/layout/Col/Col.tsx +3 -21
- package/src/components/layout/Col/Col.types.ts +1 -1
- package/src/components/layout/Container/Container.styles.ts +3 -1
- package/src/components/layout/Container/Container.tsx +2 -11
- package/src/components/layout/Grid/Grid.tsx +3 -53
- package/src/components/layout/Layout/Content.tsx +24 -32
- package/src/components/layout/Layout/Footer.tsx +24 -32
- package/src/components/layout/Layout/Header.tsx +24 -32
- package/src/components/layout/Layout/Layout.styles.ts +17 -17
- package/src/components/layout/Layout/Layout.tsx +14 -25
- package/src/components/layout/Layout/Layout.types.ts +29 -29
- package/src/components/layout/Layout/Sider.tsx +44 -56
- package/src/components/layout/Layout/index.tsx +16 -2
- package/src/components/layout/Row/Row.tsx +15 -43
- package/src/components/layout/Space/Space.tsx +3 -11
- package/src/components/layout/Space/Space.types.ts +1 -1
- package/src/components/layout/index.tsx +29 -19
- package/src/components/navigation/Menu/Menu.constants.ts +69 -0
- package/src/components/navigation/Menu/Menu.stories.tsx +107 -0
- package/src/components/navigation/Menu/Menu.styles.ts +25 -37
- package/src/components/navigation/Menu/Menu.tsx +8 -11
- package/src/components/navigation/Menu/Menu.types.ts +2 -2
- package/src/components/navigation/Menu/Menu.utils.ts +17 -17
- package/src/components/navigation/Menu/MenuItem.tsx +9 -11
- package/src/components/navigation/Menu/SubMenu.tsx +8 -6
- package/src/components/navigation/Menu/index.tsx +4 -69
- package/src/components/navigation/NavBar/NavBar.styles.ts +1 -1
- package/src/components/navigation/NavBar/NavBar.tsx +7 -10
- package/src/components/navigation/NavBar/NavBar.types.ts +3 -3
- package/src/components/navigation/NavBar/index.tsx +1 -1
- package/src/components/navigation/Pagination/Pagination.test.tsx +2 -3
- package/src/components/navigation/Pagination/Pagination.tsx +3 -3
- package/src/components/navigation/Pagination/Pagination.types.ts +3 -2
- package/src/components/navigation/Pagination/index.ts +9 -3
- package/src/components/navigation/Steps/Step.tsx +24 -44
- package/src/components/navigation/Steps/Steps.styles.ts +28 -13
- package/src/components/navigation/Steps/Steps.test.tsx +2 -0
- package/src/components/navigation/Steps/Steps.tsx +88 -89
- package/src/components/navigation/Steps/Steps.types.ts +30 -30
- package/src/components/navigation/Steps/index.tsx +1 -1
- package/src/components/navigation/Tabs/Tabs.test.tsx +3 -2
- package/src/components/navigation/Tabs/Tabs.types.ts +4 -3
- package/src/components/navigation/index.tsx +21 -16
- package/src/constants/index.ts +1 -1
- package/src/hooks/index.ts +52 -102
- package/src/hooks/types.ts +4 -5
- package/src/hooks/useAsync.ts +46 -47
- package/src/hooks/useClickOutside.ts +52 -0
- package/src/hooks/useCounter.ts +87 -0
- package/src/hooks/useDebounce.ts +150 -0
- package/src/hooks/useDeepCompareEffect.ts +88 -0
- package/src/hooks/useEventListener.ts +77 -0
- package/src/hooks/useMediaQuery.ts +75 -0
- package/src/hooks/useMutation.ts +233 -0
- package/src/hooks/usePerformance.ts +1 -64
- package/src/hooks/usePlatform.ts +3 -1
- package/src/hooks/usePrevious.ts +25 -0
- package/src/hooks/useRequest.ts +12 -7
- package/src/hooks/useStateManagement.ts +1 -1
- package/src/hooks/useStorage.ts +169 -0
- package/src/hooks/useStyle.ts +8 -2
- package/src/hooks/useToggle.ts +54 -0
- package/src/index.ts +34 -9
- package/src/theme/ThemeProvider.tsx +3 -7
- package/src/theme/ThemeProvider.types.ts +1 -1
- package/src/theme/defaults.ts +1 -1
- package/src/theme/design-system.ts +2 -2
- package/src/theme/design-tokens.ts +85 -99
- package/src/theme/generated/dark-theme.scss +1 -1
- package/src/theme/generated/tokens.scss +82 -18
- package/src/theme/index.ts +8 -29
- package/src/theme/responsive.tsx +36 -34
- package/src/theme/styles.ts +1 -1
- package/src/theme/useThemeUtils.ts +43 -43
- package/src/theme/utils.ts +32 -32
- package/src/theme/variables.ts +70 -51
- package/src/types/accessibility.ts +36 -37
- package/src/types/button.ts +25 -27
- package/src/types/component-props.ts +6 -1
- package/src/types/glob.d.ts +4 -0
- package/src/types/index.ts +2 -2
- package/src/types/standardized-components.ts +9 -3
- package/src/types/utils.ts +13 -23
- package/src/utils/__tests__/responsiveUtils.test.ts +5 -4
- package/src/utils/abort-controller.ts +48 -0
- package/src/utils/cache.ts +2 -6
- package/src/utils/createNamespace.ts +4 -4
- package/src/utils/environment.ts +26 -6
- package/src/utils/error-handler.ts +2 -2
- package/src/utils/errorLogger.ts +16 -20
- package/src/utils/formatUtils.ts +38 -70
- package/src/utils/http/error-codes.ts +314 -0
- package/src/utils/http/http-client.test.ts +63 -0
- package/src/utils/{network → http}/http-client.ts +45 -35
- package/src/utils/http/request-cache.ts +127 -0
- package/src/utils/http/request.ts +954 -0
- package/src/utils/http/taro-adapter.test.ts +74 -0
- package/src/utils/http/taro-adapter.ts +24 -0
- package/src/utils/http/types.ts +414 -0
- package/src/utils/http/web-adapter.ts +33 -0
- package/src/utils/index.ts +5 -8
- package/src/utils/inputValidator.ts +17 -14
- package/src/utils/performance/performance.ts +60 -71
- package/src/utils/responsiveUtils.ts +7 -16
- package/src/utils/rtl-support.ts +29 -19
- package/src/utils/security/api-security.ts +47 -39
- package/src/utils/securityHeaders.ts +61 -67
- package/src/utils/typeHelpers.ts +10 -10
- package/src/utils/types/dataProcessing.ts +93 -92
- package/src/utils/types/typeHelpers.ts +31 -21
- package/src/utils/xssProtection.ts +96 -48
- package/dist/js/index-6NJ3A1Dn.js.map +0 -1
- package/dist/js/index-DffLRSro.js.map +0 -1
- package/src/components/form/Input/Input.enhanced.tsx +0 -732
- package/src/components/navigation/Menu/__tests__/Menu.test.tsx +0 -687
- package/src/components/navigation/Tree/Tree.styles.ts +0 -553
- package/src/components/navigation/Tree/Tree.test.basic.tsx +0 -7
- package/src/components/navigation/Tree/Tree.test.functional.tsx +0 -496
- package/src/components/navigation/Tree/Tree.test.import.check.tsx +0 -6
- package/src/components/navigation/Tree/Tree.test.import.tsx +0 -6
- package/src/components/navigation/Tree/Tree.test.minimal.tsx +0 -5
- package/src/components/navigation/Tree/Tree.test.simple.tsx +0 -30
- package/src/components/navigation/Tree/Tree.test.tsx +0 -908
- package/src/components/navigation/Tree/Tree.test.working.tsx +0 -673
- package/src/components/navigation/Tree/Tree.tsx +0 -600
- package/src/components/navigation/Tree/Tree.types.ts +0 -909
- package/src/components/navigation/Tree/Tree.utils.ts +0 -452
- package/src/components/navigation/Tree/index.ts +0 -33
- package/src/components/navigation/Tree/index.tsx +0 -23
- package/src/utils/network/http-client.test.ts +0 -18
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Request Client
|
|
3
|
+
* Production-ready HTTP client with multi-platform support, caching, retry logic, and interceptors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ErrorHandlingManager } from '@/utils/error-handler';
|
|
7
|
+
import { createApiInterceptor, buildSecureHeaders, isSecureUrl } from '@/utils/security/api-security';
|
|
8
|
+
import { TaroAdapter } from './taro-adapter';
|
|
9
|
+
import { WebAdapter } from './web-adapter';
|
|
10
|
+
import { RequestCache } from './request-cache';
|
|
11
|
+
import type {
|
|
12
|
+
RequestConfig,
|
|
13
|
+
RequestOptions,
|
|
14
|
+
ResponseData,
|
|
15
|
+
IRequestAdapter,
|
|
16
|
+
RequestInterceptor,
|
|
17
|
+
ResponseInterceptor,
|
|
18
|
+
RetryConfig,
|
|
19
|
+
HttpMethod,
|
|
20
|
+
Platform,
|
|
21
|
+
InterceptorPriority,
|
|
22
|
+
InterceptorRegistration,
|
|
23
|
+
} from './types';
|
|
24
|
+
import { HttpError, NetworkError, TimeoutError, CancelError } from './types';
|
|
25
|
+
|
|
26
|
+
export interface RequestInstanceConfig {
|
|
27
|
+
/** Base URL for all requests */
|
|
28
|
+
baseURL?: string;
|
|
29
|
+
/** Default headers */
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
/** Default timeout in milliseconds */
|
|
32
|
+
timeout?: number;
|
|
33
|
+
/** Custom adapter */
|
|
34
|
+
adapter?: IRequestAdapter;
|
|
35
|
+
/** Enable request caching */
|
|
36
|
+
enableCache?: boolean;
|
|
37
|
+
/** Default cache TTL */
|
|
38
|
+
cacheTTL?: number;
|
|
39
|
+
/** Default retry configuration */
|
|
40
|
+
retry?: RetryConfig;
|
|
41
|
+
/** Enable security features */
|
|
42
|
+
enableSecurity?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Unified Request Client
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const client = new Request({
|
|
51
|
+
* baseURL: 'https://api.example.com',
|
|
52
|
+
* timeout: 10000,
|
|
53
|
+
* enableCache: true,
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* // GET request
|
|
57
|
+
* const data = await client.get<User>('/users/1');
|
|
58
|
+
*
|
|
59
|
+
* // POST request
|
|
60
|
+
* const created = await client.post<User>('/users', { name: 'John' });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export class Request {
|
|
64
|
+
private baseURL: string;
|
|
65
|
+
private defaultHeaders: Record<string, string>;
|
|
66
|
+
private defaultTimeout: number;
|
|
67
|
+
private adapter: IRequestAdapter;
|
|
68
|
+
private cache: RequestCache;
|
|
69
|
+
private errorManager: ReturnType<typeof ErrorHandlingManager.getInstance>;
|
|
70
|
+
private security: ReturnType<typeof createApiInterceptor>;
|
|
71
|
+
private requestInterceptors: Array<{ id: string } & RequestInterceptor> = [];
|
|
72
|
+
private responseInterceptors: Array<{ id: string } & ResponseInterceptor> = [];
|
|
73
|
+
private static globalRequestInterceptors: Array<{ id: string } & RequestInterceptor> = [];
|
|
74
|
+
private static globalResponseInterceptors: Array<{ id: string } & ResponseInterceptor> = [];
|
|
75
|
+
private defaultRetryConfig: RetryConfig;
|
|
76
|
+
private enableSecurity: boolean;
|
|
77
|
+
private enableCache: boolean;
|
|
78
|
+
private cacheTTL: number;
|
|
79
|
+
|
|
80
|
+
constructor(
|
|
81
|
+
config: RequestInstanceConfig & {
|
|
82
|
+
_testing?: {
|
|
83
|
+
cache?: RequestCache;
|
|
84
|
+
errorManager?: any;
|
|
85
|
+
security?: any;
|
|
86
|
+
};
|
|
87
|
+
} = {},
|
|
88
|
+
) {
|
|
89
|
+
this.baseURL = config.baseURL || '';
|
|
90
|
+
this.defaultHeaders = config.headers || {
|
|
91
|
+
'Content-Type': 'application/json',
|
|
92
|
+
};
|
|
93
|
+
this.defaultTimeout = config.timeout || 10000;
|
|
94
|
+
this.adapter = config.adapter || this.detectAdapter();
|
|
95
|
+
|
|
96
|
+
// Allow external dependencies injection for testing via _testing property
|
|
97
|
+
const testingConfig = config._testing || {};
|
|
98
|
+
this.cache = testingConfig.cache || new RequestCache();
|
|
99
|
+
this.errorManager = testingConfig.errorManager || ErrorHandlingManager.getInstance();
|
|
100
|
+
this.security = testingConfig.security || createApiInterceptor();
|
|
101
|
+
|
|
102
|
+
this.enableSecurity = config.enableSecurity !== false;
|
|
103
|
+
this.enableCache = config.enableCache || false;
|
|
104
|
+
this.cacheTTL = config.cacheTTL || 5 * 60 * 1000; // 5 minutes default
|
|
105
|
+
this.defaultRetryConfig = {
|
|
106
|
+
retries: 3,
|
|
107
|
+
retryDelay: 1000,
|
|
108
|
+
retryDelayStrategy: 'exponential',
|
|
109
|
+
maxRetryDelay: 10000,
|
|
110
|
+
shouldRetry: (error, _attempt) => {
|
|
111
|
+
// Retry on network errors and 5xx errors
|
|
112
|
+
if (error.name === 'NetworkError' || error.name === 'TimeoutError') return true;
|
|
113
|
+
if ((error as any).statusCode >= 500 && (error as any).statusCode < 600) return true;
|
|
114
|
+
return false;
|
|
115
|
+
},
|
|
116
|
+
...config.retry,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Detect appropriate adapter based on environment
|
|
122
|
+
*/
|
|
123
|
+
private detectAdapter(): IRequestAdapter {
|
|
124
|
+
// Check Taro environment
|
|
125
|
+
if (typeof process !== 'undefined' && process.env['TARO_ENV']) {
|
|
126
|
+
const taroEnv = process.env['TARO_ENV'];
|
|
127
|
+
if (taroEnv === 'h5' || taroEnv === 'web') {
|
|
128
|
+
return new WebAdapter();
|
|
129
|
+
}
|
|
130
|
+
return new TaroAdapter();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check for browser environment
|
|
134
|
+
if (typeof window !== 'undefined' && typeof window.fetch !== 'undefined') {
|
|
135
|
+
return new WebAdapter();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Default to Taro adapter
|
|
139
|
+
return new TaroAdapter();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Convert priority string to number
|
|
144
|
+
*/
|
|
145
|
+
private getPriorityValue(priority: InterceptorPriority): number {
|
|
146
|
+
if (typeof priority === 'number') {
|
|
147
|
+
return priority;
|
|
148
|
+
}
|
|
149
|
+
switch (priority) {
|
|
150
|
+
case 'high':
|
|
151
|
+
return 100;
|
|
152
|
+
case 'medium':
|
|
153
|
+
return 50;
|
|
154
|
+
case 'low':
|
|
155
|
+
default:
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Sort interceptors by priority
|
|
162
|
+
*/
|
|
163
|
+
private sortInterceptors<T extends { priority?: InterceptorPriority }>(interceptors: T[]): T[] {
|
|
164
|
+
return [...interceptors].sort((a, b) => {
|
|
165
|
+
const priorityA = this.getPriorityValue(a.priority || 'medium');
|
|
166
|
+
const priorityB = this.getPriorityValue(b.priority || 'medium');
|
|
167
|
+
return priorityB - priorityA; // Higher priority comes first
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Add request interceptor with enhanced features
|
|
173
|
+
*/
|
|
174
|
+
useRequestInterceptor(interceptor: RequestInterceptor): InterceptorRegistration {
|
|
175
|
+
const interceptorWithId = {
|
|
176
|
+
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
177
|
+
...interceptor,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
this.requestInterceptors.push(interceptorWithId);
|
|
181
|
+
|
|
182
|
+
// Return cleanup function with registration info
|
|
183
|
+
return {
|
|
184
|
+
id: interceptorWithId.id,
|
|
185
|
+
eject: () => {
|
|
186
|
+
const index = this.requestInterceptors.findIndex((i) => i.id === interceptorWithId.id);
|
|
187
|
+
if (index > -1) {
|
|
188
|
+
this.requestInterceptors.splice(index, 1);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Add response interceptor with enhanced features
|
|
196
|
+
*/
|
|
197
|
+
useResponseInterceptor(interceptor: ResponseInterceptor): InterceptorRegistration {
|
|
198
|
+
const interceptorWithId = {
|
|
199
|
+
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
200
|
+
...interceptor,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
this.responseInterceptors.push(interceptorWithId);
|
|
204
|
+
|
|
205
|
+
// Return cleanup function with registration info
|
|
206
|
+
return {
|
|
207
|
+
id: interceptorWithId.id,
|
|
208
|
+
eject: () => {
|
|
209
|
+
const index = this.responseInterceptors.findIndex((i) => i.id === interceptorWithId.id);
|
|
210
|
+
if (index > -1) {
|
|
211
|
+
this.responseInterceptors.splice(index, 1);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Add global request interceptor (shared across all instances)
|
|
219
|
+
*/
|
|
220
|
+
static useGlobalRequestInterceptor(interceptor: RequestInterceptor): InterceptorRegistration {
|
|
221
|
+
const interceptorWithId = {
|
|
222
|
+
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
223
|
+
...interceptor,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
Request.globalRequestInterceptors.push(interceptorWithId);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
id: interceptorWithId.id,
|
|
230
|
+
eject: () => {
|
|
231
|
+
const index = Request.globalRequestInterceptors.findIndex((i: { id: string }) => i.id === interceptorWithId.id);
|
|
232
|
+
if (index > -1) {
|
|
233
|
+
Request.globalRequestInterceptors.splice(index, 1);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Add global response interceptor (shared across all instances)
|
|
241
|
+
*/
|
|
242
|
+
static useGlobalResponseInterceptor(interceptor: ResponseInterceptor): InterceptorRegistration {
|
|
243
|
+
const interceptorWithId = {
|
|
244
|
+
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
245
|
+
...interceptor,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
Request.globalResponseInterceptors.push(interceptorWithId);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
id: interceptorWithId.id,
|
|
252
|
+
eject: () => {
|
|
253
|
+
const index = Request.globalResponseInterceptors.findIndex((i: { id: string }) => i.id === interceptorWithId.id);
|
|
254
|
+
if (index > -1) {
|
|
255
|
+
Request.globalResponseInterceptors.splice(index, 1);
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Clear all interceptors or by group
|
|
263
|
+
*/
|
|
264
|
+
clearInterceptors(group?: string): void {
|
|
265
|
+
if (group) {
|
|
266
|
+
this.requestInterceptors = this.requestInterceptors.filter((i) => i.group !== group);
|
|
267
|
+
this.responseInterceptors = this.responseInterceptors.filter((i) => i.group !== group);
|
|
268
|
+
} else {
|
|
269
|
+
this.requestInterceptors = [];
|
|
270
|
+
this.responseInterceptors = [];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Clear all global interceptors or by group
|
|
276
|
+
*/
|
|
277
|
+
static clearGlobalInterceptors(group?: string): void {
|
|
278
|
+
if (group) {
|
|
279
|
+
Request.globalRequestInterceptors = Request.globalRequestInterceptors.filter((i: { group?: string }) => i.group !== group);
|
|
280
|
+
Request.globalResponseInterceptors = Request.globalResponseInterceptors.filter((i: { group?: string }) => i.group !== group);
|
|
281
|
+
} else {
|
|
282
|
+
Request.globalRequestInterceptors = [];
|
|
283
|
+
Request.globalResponseInterceptors = [];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get all interceptors
|
|
289
|
+
*/
|
|
290
|
+
getInterceptors() {
|
|
291
|
+
return {
|
|
292
|
+
request: [...this.requestInterceptors],
|
|
293
|
+
response: [...this.responseInterceptors],
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get all global interceptors
|
|
299
|
+
*/
|
|
300
|
+
static getGlobalInterceptors() {
|
|
301
|
+
return {
|
|
302
|
+
request: [...Request.globalRequestInterceptors],
|
|
303
|
+
response: [...Request.globalResponseInterceptors],
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Build full URL from base and relative URL
|
|
309
|
+
*/
|
|
310
|
+
private buildURL(url: string, params?: Record<string, any>): string {
|
|
311
|
+
// If URL is absolute, use it directly
|
|
312
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
313
|
+
return this.appendParams(url, params);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Build URL with baseURL
|
|
317
|
+
const base = this.baseURL.replace(/\/$/, '');
|
|
318
|
+
const path = url.startsWith('/') ? url : `/${url}`;
|
|
319
|
+
const fullURL = `${base}${path}`;
|
|
320
|
+
|
|
321
|
+
return this.appendParams(fullURL, params);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Append query parameters to URL
|
|
326
|
+
*/
|
|
327
|
+
private appendParams(url: string, params?: Record<string, any>): string {
|
|
328
|
+
if (!params || Object.keys(params).length === 0) {
|
|
329
|
+
return url;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const searchParams = new URLSearchParams();
|
|
333
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
334
|
+
if (value === undefined || value === null) return;
|
|
335
|
+
|
|
336
|
+
if (Array.isArray(value)) {
|
|
337
|
+
value.forEach((item) => searchParams.append(key, String(item)));
|
|
338
|
+
} else {
|
|
339
|
+
searchParams.append(key, String(value));
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
344
|
+
return `${url}${separator}${searchParams.toString()}`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Calculate retry delay based on strategy
|
|
349
|
+
*/
|
|
350
|
+
private calculateRetryDelay(attempt: number, config: RetryConfig): number {
|
|
351
|
+
const { retryDelay = 1000, retryDelayStrategy = 'exponential', maxRetryDelay = 10000 } = config;
|
|
352
|
+
|
|
353
|
+
let delay: number;
|
|
354
|
+
switch (retryDelayStrategy) {
|
|
355
|
+
case 'exponential':
|
|
356
|
+
delay = retryDelay * Math.pow(2, attempt - 1);
|
|
357
|
+
break;
|
|
358
|
+
case 'linear':
|
|
359
|
+
delay = retryDelay * attempt;
|
|
360
|
+
break;
|
|
361
|
+
case 'fixed':
|
|
362
|
+
default:
|
|
363
|
+
delay = retryDelay;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return Math.min(delay, maxRetryDelay);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Execute request with retry logic
|
|
371
|
+
*/
|
|
372
|
+
private async executeWithRetry<T>(requestFn: () => Promise<T>, retryConfig: RetryConfig): Promise<T> {
|
|
373
|
+
const { retries = 0, shouldRetry } = retryConfig;
|
|
374
|
+
let lastError: Error;
|
|
375
|
+
|
|
376
|
+
for (let attempt = 1; attempt <= retries + 1; attempt++) {
|
|
377
|
+
try {
|
|
378
|
+
return await requestFn();
|
|
379
|
+
} catch (error) {
|
|
380
|
+
lastError = error as Error;
|
|
381
|
+
|
|
382
|
+
// Don't retry if it's the last attempt
|
|
383
|
+
if (attempt > retries) break;
|
|
384
|
+
|
|
385
|
+
// Check if we should retry this error
|
|
386
|
+
if (shouldRetry && !shouldRetry(lastError, attempt)) break;
|
|
387
|
+
|
|
388
|
+
// Wait before retrying
|
|
389
|
+
const delay = this.calculateRetryDelay(attempt, retryConfig);
|
|
390
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
throw lastError!;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Build request config from options
|
|
399
|
+
*/
|
|
400
|
+
private buildRequestConfig(options: RequestOptions, method?: HttpMethod): RequestConfig {
|
|
401
|
+
return {
|
|
402
|
+
method: method || options.method || 'GET',
|
|
403
|
+
url: this.buildURL(options.url, options.params),
|
|
404
|
+
headers: {
|
|
405
|
+
...this.defaultHeaders,
|
|
406
|
+
...options.headers,
|
|
407
|
+
},
|
|
408
|
+
data: options.data,
|
|
409
|
+
timeout: options.timeout || this.defaultTimeout,
|
|
410
|
+
responseType: options.responseType,
|
|
411
|
+
withCredentials: options.withCredentials,
|
|
412
|
+
meta: options.meta,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Perform security checks and add security headers
|
|
418
|
+
*/
|
|
419
|
+
private performSecurityCheck(config: RequestConfig, options: RequestOptions): void {
|
|
420
|
+
// Security check
|
|
421
|
+
if (this.enableSecurity && !isSecureUrl(config.url)) {
|
|
422
|
+
const error = this.errorManager.createAuthorizationError('insecure_url');
|
|
423
|
+
options.hooks?.onError?.(error);
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Add security headers
|
|
428
|
+
if (this.enableSecurity && config.headers) {
|
|
429
|
+
Object.assign(config.headers, buildSecureHeaders(config.method!, config.url, config.data));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Execute request interceptors with global + instance interceptors and priority support
|
|
435
|
+
*/
|
|
436
|
+
private async executeRequestInterceptors(config: RequestConfig, options: RequestOptions): Promise<RequestConfig> {
|
|
437
|
+
let processedConfig = config;
|
|
438
|
+
|
|
439
|
+
// Merge global and instance interceptors, then sort by priority
|
|
440
|
+
const allRequestInterceptors = this.sortInterceptors([
|
|
441
|
+
...Request.globalRequestInterceptors,
|
|
442
|
+
...this.requestInterceptors,
|
|
443
|
+
]);
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
// Security interceptor first
|
|
447
|
+
if (this.enableSecurity) {
|
|
448
|
+
processedConfig = this.security.request.execute(processedConfig);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Execute all request interceptors in order
|
|
452
|
+
for (const interceptor of allRequestInterceptors) {
|
|
453
|
+
if (interceptor.onRequest) {
|
|
454
|
+
processedConfig = await interceptor.onRequest(processedConfig);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Call before request hook
|
|
459
|
+
options.hooks?.beforeRequest?.(processedConfig);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
// Handle interceptor errors in reverse order
|
|
462
|
+
const reversedInterceptors = [...allRequestInterceptors].reverse();
|
|
463
|
+
for (const interceptor of reversedInterceptors) {
|
|
464
|
+
if (interceptor.onRequestError) {
|
|
465
|
+
try {
|
|
466
|
+
error = await interceptor.onRequestError(error as Error);
|
|
467
|
+
} catch (e) {
|
|
468
|
+
// If an error interceptor throws, continue with the original error
|
|
469
|
+
console.warn('Request interceptor error handler failed:', e);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
options.hooks?.onError?.(error as Error);
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
return processedConfig;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Execute response interceptors with global + instance interceptors and priority support
|
|
482
|
+
*/
|
|
483
|
+
private async executeResponseInterceptors<T>(
|
|
484
|
+
response: ResponseData<T>,
|
|
485
|
+
options: RequestOptions,
|
|
486
|
+
): Promise<ResponseData<T>> {
|
|
487
|
+
let processedResponse = response;
|
|
488
|
+
|
|
489
|
+
// Merge global and instance interceptors, then sort by priority
|
|
490
|
+
const allResponseInterceptors = this.sortInterceptors([
|
|
491
|
+
...Request.globalResponseInterceptors,
|
|
492
|
+
...this.responseInterceptors,
|
|
493
|
+
]);
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
// Security interceptor first
|
|
497
|
+
if (this.enableSecurity) {
|
|
498
|
+
processedResponse = this.security.response.execute(processedResponse);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Execute all response interceptors in order
|
|
502
|
+
for (const interceptor of allResponseInterceptors) {
|
|
503
|
+
if (interceptor.onResponse) {
|
|
504
|
+
processedResponse = await interceptor.onResponse(processedResponse);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
// Handle response interceptor errors in reverse order
|
|
509
|
+
const reversedInterceptors = [...allResponseInterceptors].reverse();
|
|
510
|
+
for (const interceptor of reversedInterceptors) {
|
|
511
|
+
if (interceptor.onResponseError) {
|
|
512
|
+
try {
|
|
513
|
+
error = await interceptor.onResponseError(error as Error);
|
|
514
|
+
} catch (e) {
|
|
515
|
+
// If an error interceptor throws, continue with the original error
|
|
516
|
+
console.warn('Response interceptor error handler failed:', e);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
options.hooks?.onError?.(error as Error);
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
return processedResponse;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Handle request error
|
|
529
|
+
*/
|
|
530
|
+
private handleRequestError(error: any, config: RequestConfig, options: RequestOptions, operation: string): never {
|
|
531
|
+
// Generate unique request ID for tracing
|
|
532
|
+
const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
533
|
+
|
|
534
|
+
// Get platform info
|
|
535
|
+
const platform = ((typeof process !== 'undefined' && process.env['TARO_ENV']) as Platform) || 'web';
|
|
536
|
+
|
|
537
|
+
// Transform to appropriate error type
|
|
538
|
+
if ((error as any).name === 'AbortError' || (error as any).name === 'CancelError') {
|
|
539
|
+
const cancelError = new CancelError(`${operation} cancelled`, config, { platform, requestId });
|
|
540
|
+
options.hooks?.onError?.(cancelError);
|
|
541
|
+
throw cancelError;
|
|
542
|
+
}
|
|
543
|
+
if ((error as any).name === 'TimeoutError' || (error as any).message?.includes('timeout')) {
|
|
544
|
+
const timeoutError = new TimeoutError(`${operation} timeout`, config.timeout, config, { platform, requestId });
|
|
545
|
+
options.hooks?.onError?.(timeoutError);
|
|
546
|
+
throw timeoutError;
|
|
547
|
+
}
|
|
548
|
+
const networkError = new NetworkError(`Network error during ${operation}`, config, { platform, requestId });
|
|
549
|
+
options.hooks?.onError?.(networkError);
|
|
550
|
+
throw networkError;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Handle response status
|
|
555
|
+
*/
|
|
556
|
+
private handleResponseStatus<T>(response: ResponseData<T>, config: RequestConfig, options: RequestOptions): void {
|
|
557
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
558
|
+
// Generate unique request ID for tracing
|
|
559
|
+
const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
560
|
+
|
|
561
|
+
// Get platform info
|
|
562
|
+
const platform = ((typeof process !== 'undefined' && process.env['TARO_ENV']) as Platform) || 'web';
|
|
563
|
+
|
|
564
|
+
const error = new HttpError(
|
|
565
|
+
response.errMsg || `HTTP ${response.statusCode}`,
|
|
566
|
+
response.statusCode,
|
|
567
|
+
response,
|
|
568
|
+
config,
|
|
569
|
+
{ platform, requestId },
|
|
570
|
+
);
|
|
571
|
+
this.errorManager.handleError(error);
|
|
572
|
+
options.hooks?.onError?.(error);
|
|
573
|
+
throw error;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Execute HTTP request
|
|
579
|
+
*/
|
|
580
|
+
async request<T = any>(options: RequestOptions): Promise<T> {
|
|
581
|
+
// Build request config
|
|
582
|
+
const config: RequestConfig = this.buildRequestConfig(options);
|
|
583
|
+
|
|
584
|
+
// Perform security checks
|
|
585
|
+
this.performSecurityCheck(config, options);
|
|
586
|
+
|
|
587
|
+
// Check cache for GET requests
|
|
588
|
+
const cacheEnabled = options.cache?.enabled !== false && this.enableCache;
|
|
589
|
+
const forceRefresh = options.cache?.forceRefresh || false;
|
|
590
|
+
|
|
591
|
+
if (cacheEnabled && config.method === 'GET' && !forceRefresh) {
|
|
592
|
+
const cacheKey = this.cache.generateKey(config.url, config.data);
|
|
593
|
+
|
|
594
|
+
// Check if there's a pending request for this key
|
|
595
|
+
if (this.cache.hasPendingRequest(cacheKey)) {
|
|
596
|
+
return this.cache.getPendingRequest<T>(cacheKey)!;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Check cache
|
|
600
|
+
const cached = this.cache.get<T>(cacheKey);
|
|
601
|
+
if (cached !== null) {
|
|
602
|
+
options.hooks?.afterResponse?.({
|
|
603
|
+
data: cached,
|
|
604
|
+
statusCode: 200,
|
|
605
|
+
header: {},
|
|
606
|
+
errMsg: 'Success',
|
|
607
|
+
cookies: [],
|
|
608
|
+
});
|
|
609
|
+
return cached;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Execute request with caching
|
|
613
|
+
const result = await this.executeRequestWithCommonLogic<T>(
|
|
614
|
+
options,
|
|
615
|
+
config.method as HttpMethod,
|
|
616
|
+
(processedConfig) => this.adapter.request<T>(processedConfig),
|
|
617
|
+
cacheKey,
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
// Cache successful GET responses
|
|
621
|
+
if (result !== null && result !== undefined) {
|
|
622
|
+
const cacheTTL = options.cache?.ttl || this.cacheTTL;
|
|
623
|
+
this.cache.set(cacheKey, result, cacheTTL);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Execute request without caching
|
|
630
|
+
return this.executeRequestWithCommonLogic<T>(options, config.method as HttpMethod, (processedConfig) =>
|
|
631
|
+
this.adapter.request<T>(processedConfig),
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* GET request
|
|
637
|
+
*/
|
|
638
|
+
get<T = any>(url: string, options: Omit<RequestOptions, 'url' | 'method' | 'data'> = {}): Promise<T> {
|
|
639
|
+
return this.request<T>({
|
|
640
|
+
...options,
|
|
641
|
+
url,
|
|
642
|
+
method: 'GET',
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* POST request
|
|
648
|
+
*/
|
|
649
|
+
post<T = any>(url: string, data?: any, options: Omit<RequestOptions, 'url' | 'method' | 'data'> = {}): Promise<T> {
|
|
650
|
+
return this.request<T>({
|
|
651
|
+
...options,
|
|
652
|
+
url,
|
|
653
|
+
method: 'POST',
|
|
654
|
+
data,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* PUT request
|
|
660
|
+
*/
|
|
661
|
+
put<T = any>(url: string, data?: any, options: Omit<RequestOptions, 'url' | 'method' | 'data'> = {}): Promise<T> {
|
|
662
|
+
return this.request<T>({
|
|
663
|
+
...options,
|
|
664
|
+
url,
|
|
665
|
+
method: 'PUT',
|
|
666
|
+
data,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* PATCH request
|
|
672
|
+
*/
|
|
673
|
+
patch<T = any>(url: string, data?: any, options: Omit<RequestOptions, 'url' | 'method' | 'data'> = {}): Promise<T> {
|
|
674
|
+
return this.request<T>({
|
|
675
|
+
...options,
|
|
676
|
+
url,
|
|
677
|
+
method: 'PATCH',
|
|
678
|
+
data,
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* DELETE request
|
|
684
|
+
*/
|
|
685
|
+
delete<T = any>(url: string, options: Omit<RequestOptions, 'url' | 'method' | 'data'> = {}): Promise<T> {
|
|
686
|
+
return this.request<T>({
|
|
687
|
+
...options,
|
|
688
|
+
url,
|
|
689
|
+
method: 'DELETE',
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Upload file
|
|
695
|
+
*/
|
|
696
|
+
async upload<T = any>(
|
|
697
|
+
options: RequestOptions & {
|
|
698
|
+
filePath: string | File;
|
|
699
|
+
name?: string;
|
|
700
|
+
formData?: Record<string, any>;
|
|
701
|
+
onProgress?: (progress: { progress: number; totalBytesSent: number; totalBytesExpectedToSend: number }) => void;
|
|
702
|
+
},
|
|
703
|
+
): Promise<T> {
|
|
704
|
+
return this.executeRequestWithCommonLogic<T>(options, 'POST', (processedConfig) => {
|
|
705
|
+
if (this.adapter.upload) {
|
|
706
|
+
return this.adapter.upload({
|
|
707
|
+
...processedConfig,
|
|
708
|
+
filePath: options.filePath,
|
|
709
|
+
name: options.name,
|
|
710
|
+
formData: options.formData,
|
|
711
|
+
onProgress: options.onProgress,
|
|
712
|
+
});
|
|
713
|
+
} else {
|
|
714
|
+
throw new Error('Upload adapter not supported');
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Download file
|
|
721
|
+
*/
|
|
722
|
+
async download<T = any>(
|
|
723
|
+
options: RequestOptions & {
|
|
724
|
+
filePath?: string;
|
|
725
|
+
onProgress?: (progress: {
|
|
726
|
+
progress: number;
|
|
727
|
+
totalBytesWritten: number;
|
|
728
|
+
totalBytesExpectedToWrite: number;
|
|
729
|
+
}) => void;
|
|
730
|
+
},
|
|
731
|
+
): Promise<T> {
|
|
732
|
+
return this.executeRequestWithCommonLogic<T>(options, 'GET', (processedConfig) => {
|
|
733
|
+
if (this.adapter.download) {
|
|
734
|
+
return this.adapter.download({
|
|
735
|
+
...processedConfig,
|
|
736
|
+
filePath: options.filePath,
|
|
737
|
+
onProgress: options.onProgress,
|
|
738
|
+
});
|
|
739
|
+
} else {
|
|
740
|
+
throw new Error('Download adapter not supported');
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Clear cache
|
|
747
|
+
*/
|
|
748
|
+
clearCache(url?: string, params?: any): void {
|
|
749
|
+
if (url) {
|
|
750
|
+
const key = this.cache.generateKey(url, params);
|
|
751
|
+
this.cache.clear(key);
|
|
752
|
+
} else {
|
|
753
|
+
this.cache.clearAll();
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Get cache statistics
|
|
759
|
+
*/
|
|
760
|
+
getCacheStats() {
|
|
761
|
+
return this.cache.getStats();
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Execute request with common logic
|
|
766
|
+
*/
|
|
767
|
+
private async executeRequestWithCommonLogic<T = any>(
|
|
768
|
+
options: RequestOptions,
|
|
769
|
+
method: HttpMethod,
|
|
770
|
+
requestFn: (config: RequestConfig) => Promise<ResponseData<T>>,
|
|
771
|
+
cacheKey?: string,
|
|
772
|
+
): Promise<T> {
|
|
773
|
+
// Build request config
|
|
774
|
+
const config: RequestConfig = this.buildRequestConfig(options, method);
|
|
775
|
+
|
|
776
|
+
// Perform security checks
|
|
777
|
+
this.performSecurityCheck(config, options);
|
|
778
|
+
|
|
779
|
+
// Execute request interceptors
|
|
780
|
+
const processedConfig = await this.executeRequestInterceptors(config, options);
|
|
781
|
+
|
|
782
|
+
// Create request function
|
|
783
|
+
const doRequest = async (): Promise<T> => {
|
|
784
|
+
let response: ResponseData<T>;
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
response = await requestFn(processedConfig);
|
|
788
|
+
} catch (error) {
|
|
789
|
+
this.handleRequestError(error, processedConfig, options, method.toLowerCase());
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Execute response interceptors
|
|
793
|
+
const processedResponse = await this.executeResponseInterceptors(response, options);
|
|
794
|
+
|
|
795
|
+
// Check response status
|
|
796
|
+
this.handleResponseStatus(processedResponse, processedConfig, options);
|
|
797
|
+
|
|
798
|
+
// Call after response hook
|
|
799
|
+
options.hooks?.afterResponse?.(processedResponse);
|
|
800
|
+
|
|
801
|
+
return processedResponse.data;
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
// Determine retry config
|
|
805
|
+
const retryConfig: RetryConfig = {
|
|
806
|
+
...this.defaultRetryConfig,
|
|
807
|
+
...options.retry,
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// Execute request with retry
|
|
811
|
+
const requestPromise = this.executeWithRetry(doRequest, retryConfig);
|
|
812
|
+
|
|
813
|
+
// Store pending request for de-duplication (not just for GET requests)
|
|
814
|
+
if (cacheKey) {
|
|
815
|
+
this.cache.setPendingRequest(cacheKey, requestPromise);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
try {
|
|
819
|
+
return await requestPromise;
|
|
820
|
+
} catch (error) {
|
|
821
|
+
// Call error hook
|
|
822
|
+
options.hooks?.onError?.(error as Error);
|
|
823
|
+
throw error;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Set default headers
|
|
829
|
+
*/
|
|
830
|
+
setDefaultHeaders(headers: Record<string, string>): this {
|
|
831
|
+
this.defaultHeaders = {
|
|
832
|
+
...this.defaultHeaders,
|
|
833
|
+
...headers,
|
|
834
|
+
};
|
|
835
|
+
return this;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Update default header
|
|
840
|
+
*/
|
|
841
|
+
updateDefaultHeader(key: string, value: string): this {
|
|
842
|
+
this.defaultHeaders[key] = value;
|
|
843
|
+
return this;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Remove default header
|
|
848
|
+
*/
|
|
849
|
+
removeDefaultHeader(key: string): this {
|
|
850
|
+
delete this.defaultHeaders[key];
|
|
851
|
+
return this;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Set default timeout
|
|
856
|
+
*/
|
|
857
|
+
setDefaultTimeout(timeout: number): this {
|
|
858
|
+
this.defaultTimeout = timeout;
|
|
859
|
+
return this;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Enable/disable security features
|
|
864
|
+
*/
|
|
865
|
+
setSecurityEnabled(enabled: boolean): this {
|
|
866
|
+
this.enableSecurity = enabled;
|
|
867
|
+
return this;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Enable/disable cache
|
|
872
|
+
*/
|
|
873
|
+
setCacheEnabled(enabled: boolean): this {
|
|
874
|
+
this.enableCache = enabled;
|
|
875
|
+
return this;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Set default cache TTL
|
|
880
|
+
*/
|
|
881
|
+
setDefaultCacheTTL(ttl: number): this {
|
|
882
|
+
this.cacheTTL = ttl;
|
|
883
|
+
return this;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Set authorization header with token
|
|
888
|
+
*/
|
|
889
|
+
setAuthToken(token: string, type: string = 'Bearer'): this {
|
|
890
|
+
this.defaultHeaders['Authorization'] = `${type} ${token}`;
|
|
891
|
+
return this;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Clear authorization header
|
|
896
|
+
*/
|
|
897
|
+
clearAuthToken(): this {
|
|
898
|
+
delete this.defaultHeaders['Authorization'];
|
|
899
|
+
return this;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Set base URL
|
|
904
|
+
*/
|
|
905
|
+
setBaseURL(baseURL: string): this {
|
|
906
|
+
this.baseURL = baseURL;
|
|
907
|
+
return this;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Create a new client instance with merged config
|
|
912
|
+
*/
|
|
913
|
+
createInstance(config: RequestInstanceConfig = {}): Request {
|
|
914
|
+
return new Request({
|
|
915
|
+
baseURL: config.baseURL || this.baseURL,
|
|
916
|
+
headers: { ...this.defaultHeaders, ...config.headers },
|
|
917
|
+
timeout: config.timeout || this.defaultTimeout,
|
|
918
|
+
adapter: config.adapter || this.adapter,
|
|
919
|
+
enableCache: config.enableCache !== undefined ? config.enableCache : this.enableCache,
|
|
920
|
+
cacheTTL: config.cacheTTL || this.cacheTTL,
|
|
921
|
+
retry: { ...this.defaultRetryConfig, ...config.retry },
|
|
922
|
+
enableSecurity: config.enableSecurity !== undefined ? config.enableSecurity : this.enableSecurity,
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Quick GET request with simplified API
|
|
928
|
+
*/
|
|
929
|
+
quickGet<T = any>(url: string, params?: Record<string, any>, headers?: Record<string, string>): Promise<T> {
|
|
930
|
+
return this.request<T>({
|
|
931
|
+
url,
|
|
932
|
+
method: 'GET',
|
|
933
|
+
params,
|
|
934
|
+
headers,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Quick POST request with simplified API
|
|
940
|
+
*/
|
|
941
|
+
quickPost<T = any>(url: string, data?: any, headers?: Record<string, string>): Promise<T> {
|
|
942
|
+
return this.request<T>({
|
|
943
|
+
url,
|
|
944
|
+
method: 'POST',
|
|
945
|
+
data,
|
|
946
|
+
headers,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Create default instance
|
|
952
|
+
export const request = new Request();
|
|
953
|
+
|
|
954
|
+
// Export for customization - removed default export to maintain naming consistency
|