@explorins/pers-sdk-react-native 2.3.6 → 2.3.8
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/LICENSE +21 -0
- package/README.md +1266 -1244
- package/dist/__tests__/PersSDKProvider.test.js +33 -0
- package/dist/hooks/useBusiness.d.ts +2 -2
- package/dist/hooks/useBusiness.d.ts.map +1 -1
- package/dist/hooks/useBusiness.js +2 -2
- package/dist/hooks/useIPFSUrl.d.ts +3 -3
- package/dist/hooks/useIPFSUrl.d.ts.map +1 -1
- package/dist/hooks/useIPFSUrl.js +7 -7
- package/dist/hooks/useTransactions.d.ts.map +1 -1
- package/dist/hooks/useTransactions.js +13 -5
- package/dist/hooks/useWeb3.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2412 -492
- package/dist/index.js.map +1 -1
- package/dist/providers/PersSDKProvider.d.ts +4 -0
- package/dist/providers/PersSDKProvider.d.ts.map +1 -1
- package/dist/providers/PersSDKProvider.js +14 -0
- package/dist/providers/react-native-http-client.d.ts +2 -0
- package/dist/providers/react-native-http-client.d.ts.map +1 -1
- package/dist/providers/react-native-http-client.js +5 -2
- package/dist/storage/rn-secure-storage.d.ts.map +1 -1
- package/dist/storage/rn-secure-storage.js +21 -8
- package/package.json +3 -3
- package/src/__tests__/PersSDKProvider.test.tsx +484 -420
- package/src/__tests__/setup.ts +40 -40
- package/src/hooks/index.ts +60 -60
- package/src/hooks/useAnalytics.ts +327 -327
- package/src/hooks/useAuth.ts +265 -265
- package/src/hooks/useBookings.ts +191 -191
- package/src/hooks/useBusiness.ts +251 -251
- package/src/hooks/useCampaigns.ts +500 -500
- package/src/hooks/useDonations.ts +66 -66
- package/src/hooks/useEvents.ts +372 -372
- package/src/hooks/useFiles.ts +155 -155
- package/src/hooks/useIPFSUrl.ts +192 -192
- package/src/hooks/usePurchases.ts +65 -65
- package/src/hooks/useRedemptions.ts +467 -467
- package/src/hooks/useTenants.ts +72 -72
- package/src/hooks/useTokenBalances.ts +385 -385
- package/src/hooks/useTokens.ts +313 -313
- package/src/hooks/useTransactionSigner.ts +611 -611
- package/src/hooks/useTransactions.ts +299 -290
- package/src/hooks/useTriggerSources.ts +301 -301
- package/src/hooks/useUserStatus.ts +61 -61
- package/src/hooks/useUsers.ts +167 -167
- package/src/hooks/useWeb3.ts +355 -355
- package/src/index.ts +777 -774
- package/src/polyfills/index.ts +160 -160
- package/src/polyfills/web3-polyfills.ts +97 -97
- package/src/providers/PersSDKProvider.tsx +393 -374
- package/src/providers/react-native-auth-provider.ts +110 -110
- package/src/providers/react-native-http-client.ts +150 -145
- package/src/providers/rn-dpop-provider.ts +190 -190
- package/src/storage/async-storage-token-storage.ts +185 -185
- package/src/storage/rn-secure-storage.ts +182 -170
- package/src/types/react-native-globals.d.ts +45 -45
- package/src/utils/index.ts +8 -8
- package/src/utils/platform-detection.ts +86 -86
- package/dist/index-CzLxqePZ.js +0 -7717
- package/dist/index-CzLxqePZ.js.map +0 -1
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* React Native Unified Auth Provider
|
|
3
|
-
*
|
|
4
|
-
* Creates a platform-specific auth provider that automatically selects the appropriate storage:
|
|
5
|
-
* - Web: Uses IndexedDBTokenStorage for DPoP (CryptoKey persistence) or LocalStorage
|
|
6
|
-
* - Mobile: Uses AsyncStorage-based storage
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { Platform } from 'react-native';
|
|
10
|
-
import { DefaultAuthProvider, AccountOwnerType, LocalStorageTokenStorage, IndexedDBTokenStorage } from '@explorins/pers-sdk/core';
|
|
11
|
-
import { ReactNativeSecureStorage } from '../storage/rn-secure-storage';
|
|
12
|
-
import type { TokenStorage } from '@explorins/pers-sdk/core';
|
|
13
|
-
|
|
14
|
-
// Use React Native's built-in platform detection
|
|
15
|
-
const isWebPlatform = Platform.OS === 'web';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Check if IndexedDB is available (web environments only)
|
|
19
|
-
* Uses globalThis to avoid TypeScript errors in React Native context
|
|
20
|
-
*/
|
|
21
|
-
function hasIndexedDB(): boolean {
|
|
22
|
-
return typeof globalThis !== 'undefined' &&
|
|
23
|
-
typeof (globalThis as Record<string, unknown>).indexedDB !== 'undefined';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Configuration for React Native Auth Provider
|
|
28
|
-
*/
|
|
29
|
-
export interface ReactNativeAuthConfig {
|
|
30
|
-
/** Custom storage key prefix */
|
|
31
|
-
keyPrefix?: string;
|
|
32
|
-
/** Enable debug logging */
|
|
33
|
-
debug?: boolean;
|
|
34
|
-
/** Custom token storage implementation */
|
|
35
|
-
customStorage?: TokenStorage;
|
|
36
|
-
/** Authentication type (default: AccountOwnerType.USER) */
|
|
37
|
-
authType?: AccountOwnerType;
|
|
38
|
-
/** DPoP Configuration (optional) */
|
|
39
|
-
dpop?: {
|
|
40
|
-
enabled?: boolean;
|
|
41
|
-
cryptoProvider?: import('@explorins/pers-sdk/core').DPoPCryptoProvider;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Create a React Native auth provider with platform-appropriate storage
|
|
47
|
-
*
|
|
48
|
-
* Automatically selects storage implementation:
|
|
49
|
-
* - Web: Uses LocalStorageTokenStorage (from core SDK)
|
|
50
|
-
* - Mobile: Uses AsyncStorageTokenStorage (React Native specific)
|
|
51
|
-
*
|
|
52
|
-
* @param projectKey - PERS project key
|
|
53
|
-
* @param config - Configuration options
|
|
54
|
-
* @returns DefaultAuthProvider configured for the current platform
|
|
55
|
-
*/
|
|
56
|
-
export function createReactNativeAuthProvider(
|
|
57
|
-
projectKey: string,
|
|
58
|
-
config: ReactNativeAuthConfig = {}
|
|
59
|
-
): DefaultAuthProvider {
|
|
60
|
-
if (!projectKey || typeof projectKey !== 'string') {
|
|
61
|
-
throw new Error('createReactNativeAuthProvider: projectKey is required and must be a string');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const {
|
|
65
|
-
keyPrefix = `pers_${projectKey.slice(0, 8)}_`,
|
|
66
|
-
debug = false,
|
|
67
|
-
customStorage,
|
|
68
|
-
authType = AccountOwnerType.USER,
|
|
69
|
-
dpop
|
|
70
|
-
} = config;
|
|
71
|
-
|
|
72
|
-
// Platform-specific storage selection
|
|
73
|
-
// Web with DPoP: Use IndexedDB for CryptoKey persistence
|
|
74
|
-
// Web without DPoP or no IndexedDB: Use LocalStorage
|
|
75
|
-
// Mobile: Use ReactNativeSecureStorage
|
|
76
|
-
let tokenStorage: TokenStorage;
|
|
77
|
-
|
|
78
|
-
if (customStorage) {
|
|
79
|
-
tokenStorage = customStorage;
|
|
80
|
-
} else if (isWebPlatform) {
|
|
81
|
-
// On web, prefer IndexedDB when DPoP is enabled for proper CryptoKey persistence
|
|
82
|
-
const dpopEnabled = dpop?.enabled ?? true;
|
|
83
|
-
|
|
84
|
-
if (dpopEnabled && hasIndexedDB()) {
|
|
85
|
-
tokenStorage = new IndexedDBTokenStorage();
|
|
86
|
-
} else {
|
|
87
|
-
if (dpopEnabled && !hasIndexedDB()) {
|
|
88
|
-
console.warn('[ReactNativeAuthProvider] DPoP requested but IndexedDB not available. Falling back to LocalStorage (DPoP will be disabled by DefaultAuthProvider).');
|
|
89
|
-
}
|
|
90
|
-
tokenStorage = new LocalStorageTokenStorage();
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
tokenStorage = new ReactNativeSecureStorage(keyPrefix);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Prepare DPoP config ensuring enabled is boolean (default true)
|
|
97
|
-
const dpopConfig = dpop ? {
|
|
98
|
-
enabled: dpop.enabled ?? true,
|
|
99
|
-
cryptoProvider: dpop.cryptoProvider
|
|
100
|
-
} : undefined;
|
|
101
|
-
|
|
102
|
-
// Return DefaultAuthProvider configured with platform-appropriate storage
|
|
103
|
-
return new DefaultAuthProvider({
|
|
104
|
-
authType,
|
|
105
|
-
projectKey,
|
|
106
|
-
storage: tokenStorage,
|
|
107
|
-
dpop: dpopConfig
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
1
|
+
/**
|
|
2
|
+
* React Native Unified Auth Provider
|
|
3
|
+
*
|
|
4
|
+
* Creates a platform-specific auth provider that automatically selects the appropriate storage:
|
|
5
|
+
* - Web: Uses IndexedDBTokenStorage for DPoP (CryptoKey persistence) or LocalStorage
|
|
6
|
+
* - Mobile: Uses AsyncStorage-based storage
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Platform } from 'react-native';
|
|
10
|
+
import { DefaultAuthProvider, AccountOwnerType, LocalStorageTokenStorage, IndexedDBTokenStorage } from '@explorins/pers-sdk/core';
|
|
11
|
+
import { ReactNativeSecureStorage } from '../storage/rn-secure-storage';
|
|
12
|
+
import type { TokenStorage } from '@explorins/pers-sdk/core';
|
|
13
|
+
|
|
14
|
+
// Use React Native's built-in platform detection
|
|
15
|
+
const isWebPlatform = Platform.OS === 'web';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if IndexedDB is available (web environments only)
|
|
19
|
+
* Uses globalThis to avoid TypeScript errors in React Native context
|
|
20
|
+
*/
|
|
21
|
+
function hasIndexedDB(): boolean {
|
|
22
|
+
return typeof globalThis !== 'undefined' &&
|
|
23
|
+
typeof (globalThis as Record<string, unknown>).indexedDB !== 'undefined';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for React Native Auth Provider
|
|
28
|
+
*/
|
|
29
|
+
export interface ReactNativeAuthConfig {
|
|
30
|
+
/** Custom storage key prefix */
|
|
31
|
+
keyPrefix?: string;
|
|
32
|
+
/** Enable debug logging */
|
|
33
|
+
debug?: boolean;
|
|
34
|
+
/** Custom token storage implementation */
|
|
35
|
+
customStorage?: TokenStorage;
|
|
36
|
+
/** Authentication type (default: AccountOwnerType.USER) */
|
|
37
|
+
authType?: AccountOwnerType;
|
|
38
|
+
/** DPoP Configuration (optional) */
|
|
39
|
+
dpop?: {
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
cryptoProvider?: import('@explorins/pers-sdk/core').DPoPCryptoProvider;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a React Native auth provider with platform-appropriate storage
|
|
47
|
+
*
|
|
48
|
+
* Automatically selects storage implementation:
|
|
49
|
+
* - Web: Uses LocalStorageTokenStorage (from core SDK)
|
|
50
|
+
* - Mobile: Uses AsyncStorageTokenStorage (React Native specific)
|
|
51
|
+
*
|
|
52
|
+
* @param projectKey - PERS project key
|
|
53
|
+
* @param config - Configuration options
|
|
54
|
+
* @returns DefaultAuthProvider configured for the current platform
|
|
55
|
+
*/
|
|
56
|
+
export function createReactNativeAuthProvider(
|
|
57
|
+
projectKey: string,
|
|
58
|
+
config: ReactNativeAuthConfig = {}
|
|
59
|
+
): DefaultAuthProvider {
|
|
60
|
+
if (!projectKey || typeof projectKey !== 'string') {
|
|
61
|
+
throw new Error('createReactNativeAuthProvider: projectKey is required and must be a string');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const {
|
|
65
|
+
keyPrefix = `pers_${projectKey.slice(0, 8)}_`,
|
|
66
|
+
debug = false,
|
|
67
|
+
customStorage,
|
|
68
|
+
authType = AccountOwnerType.USER,
|
|
69
|
+
dpop
|
|
70
|
+
} = config;
|
|
71
|
+
|
|
72
|
+
// Platform-specific storage selection
|
|
73
|
+
// Web with DPoP: Use IndexedDB for CryptoKey persistence
|
|
74
|
+
// Web without DPoP or no IndexedDB: Use LocalStorage
|
|
75
|
+
// Mobile: Use ReactNativeSecureStorage
|
|
76
|
+
let tokenStorage: TokenStorage;
|
|
77
|
+
|
|
78
|
+
if (customStorage) {
|
|
79
|
+
tokenStorage = customStorage;
|
|
80
|
+
} else if (isWebPlatform) {
|
|
81
|
+
// On web, prefer IndexedDB when DPoP is enabled for proper CryptoKey persistence
|
|
82
|
+
const dpopEnabled = dpop?.enabled ?? true;
|
|
83
|
+
|
|
84
|
+
if (dpopEnabled && hasIndexedDB()) {
|
|
85
|
+
tokenStorage = new IndexedDBTokenStorage();
|
|
86
|
+
} else {
|
|
87
|
+
if (dpopEnabled && !hasIndexedDB()) {
|
|
88
|
+
console.warn('[ReactNativeAuthProvider] DPoP requested but IndexedDB not available. Falling back to LocalStorage (DPoP will be disabled by DefaultAuthProvider).');
|
|
89
|
+
}
|
|
90
|
+
tokenStorage = new LocalStorageTokenStorage();
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
tokenStorage = new ReactNativeSecureStorage(keyPrefix);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Prepare DPoP config ensuring enabled is boolean (default true)
|
|
97
|
+
const dpopConfig = dpop ? {
|
|
98
|
+
enabled: dpop.enabled ?? true,
|
|
99
|
+
cryptoProvider: dpop.cryptoProvider
|
|
100
|
+
} : undefined;
|
|
101
|
+
|
|
102
|
+
// Return DefaultAuthProvider configured with platform-appropriate storage
|
|
103
|
+
return new DefaultAuthProvider({
|
|
104
|
+
authType,
|
|
105
|
+
projectKey,
|
|
106
|
+
storage: tokenStorage,
|
|
107
|
+
dpop: dpopConfig
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
@@ -1,146 +1,151 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP Client implementation for React Native
|
|
3
|
-
*
|
|
4
|
-
* This implementation properly implements the core SDK's HttpClient interface
|
|
5
|
-
* and uses fetch API which is available in React Native.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Request options for HTTP client operations
|
|
10
|
-
*
|
|
11
|
-
* @interface RequestOptions
|
|
12
|
-
* @property {Record<string, string>} [headers] - HTTP headers to include
|
|
13
|
-
* @property {Record<string, string>} [params] - URL parameters
|
|
14
|
-
* @property {number} [timeout] - Request timeout in milliseconds
|
|
15
|
-
* @property {'json' | 'blob' | 'text' | 'arraybuffer'} [responseType] - Expected response type
|
|
16
|
-
*/
|
|
17
|
-
export interface RequestOptions {
|
|
18
|
-
headers?: Record<string, string>;
|
|
19
|
-
params?: Record<string, string>;
|
|
20
|
-
timeout?: number;
|
|
21
|
-
responseType?: 'json' | 'blob' | 'text' | 'arraybuffer';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* HTTP Client interface for React Native
|
|
26
|
-
*
|
|
27
|
-
* @interface HttpClient
|
|
28
|
-
* @template T - Response type
|
|
29
|
-
*/
|
|
30
|
-
export interface HttpClient {
|
|
31
|
-
get<T>(url: string, options?: RequestOptions): Promise<T>;
|
|
32
|
-
post<T>(url: string, body: any, options?: RequestOptions): Promise<T>;
|
|
33
|
-
put<T>(url: string, body: any, options?: RequestOptions): Promise<T>;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
url
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Object.
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Client implementation for React Native
|
|
3
|
+
*
|
|
4
|
+
* This implementation properly implements the core SDK's HttpClient interface
|
|
5
|
+
* and uses fetch API which is available in React Native.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Request options for HTTP client operations
|
|
10
|
+
*
|
|
11
|
+
* @interface RequestOptions
|
|
12
|
+
* @property {Record<string, string>} [headers] - HTTP headers to include
|
|
13
|
+
* @property {Record<string, string>} [params] - URL parameters
|
|
14
|
+
* @property {number} [timeout] - Request timeout in milliseconds
|
|
15
|
+
* @property {'json' | 'blob' | 'text' | 'arraybuffer'} [responseType] - Expected response type
|
|
16
|
+
*/
|
|
17
|
+
export interface RequestOptions {
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
params?: Record<string, string>;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
responseType?: 'json' | 'blob' | 'text' | 'arraybuffer';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* HTTP Client interface for React Native
|
|
26
|
+
*
|
|
27
|
+
* @interface HttpClient
|
|
28
|
+
* @template T - Response type
|
|
29
|
+
*/
|
|
30
|
+
export interface HttpClient {
|
|
31
|
+
get<T>(url: string, options?: RequestOptions): Promise<T>;
|
|
32
|
+
post<T>(url: string, body: any, options?: RequestOptions): Promise<T>;
|
|
33
|
+
put<T>(url: string, body: any, options?: RequestOptions): Promise<T>;
|
|
34
|
+
patch<T>(url: string, body: any, options?: RequestOptions): Promise<T>;
|
|
35
|
+
delete<T>(url: string, options?: RequestOptions): Promise<T>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class ReactNativeHttpClient implements HttpClient {
|
|
39
|
+
private baseURL?: string;
|
|
40
|
+
|
|
41
|
+
constructor(baseURL?: string) {
|
|
42
|
+
this.baseURL = baseURL;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async get<T = any>(url: string, options?: RequestOptions): Promise<T> {
|
|
46
|
+
return this.request<T>('GET', url, undefined, options);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async post<T = any>(url: string, body?: any, options?: RequestOptions): Promise<T> {
|
|
50
|
+
return this.request<T>('POST', url, body, options);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async put<T = any>(url: string, body?: any, options?: RequestOptions): Promise<T> {
|
|
54
|
+
return this.request<T>('PUT', url, body, options);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async patch<T = any>(url: string, body?: any, options?: RequestOptions): Promise<T> {
|
|
58
|
+
return this.request<T>('PATCH', url, body, options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async delete<T = any>(url: string, options?: RequestOptions): Promise<T> {
|
|
62
|
+
return this.request<T>('DELETE', url, undefined, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async request<T>(
|
|
66
|
+
method: string,
|
|
67
|
+
url: string,
|
|
68
|
+
body?: any,
|
|
69
|
+
options?: RequestOptions
|
|
70
|
+
): Promise<T> {
|
|
71
|
+
const fullUrl = this.buildUrl(url, options?.params);
|
|
72
|
+
|
|
73
|
+
const config: RequestInit = {
|
|
74
|
+
method,
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
...options?.headers,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Add timeout if specified
|
|
82
|
+
if (options?.timeout) {
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
config.signal = controller.signal;
|
|
85
|
+
setTimeout(() => controller.abort(), options.timeout);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Add body for POST/PUT/PATCH requests
|
|
89
|
+
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
90
|
+
config.body = JSON.stringify(body);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch(fullUrl, config);
|
|
95
|
+
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
// Get raw error response for ErrorUtils to handle
|
|
98
|
+
let errorText = '';
|
|
99
|
+
try {
|
|
100
|
+
errorText = await response.text();
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// Ignore errors when reading error body
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Create simple error with HTTP status and raw response
|
|
106
|
+
const httpError = new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
|
|
107
|
+
(httpError as any).status = response.status;
|
|
108
|
+
|
|
109
|
+
throw httpError;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handle different response types
|
|
113
|
+
switch (options?.responseType) {
|
|
114
|
+
case 'text':
|
|
115
|
+
return response.text() as Promise<T>;
|
|
116
|
+
case 'blob':
|
|
117
|
+
return response.blob() as Promise<T>;
|
|
118
|
+
case 'arraybuffer':
|
|
119
|
+
return response.arrayBuffer() as Promise<T>;
|
|
120
|
+
case 'json':
|
|
121
|
+
default:
|
|
122
|
+
// Check if response has content
|
|
123
|
+
const contentLength = response.headers.get('content-length');
|
|
124
|
+
if (contentLength === '0' || response.status === 204) {
|
|
125
|
+
return undefined as T;
|
|
126
|
+
}
|
|
127
|
+
return response.json();
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
131
|
+
throw new Error(`Request timeout after ${options?.timeout}ms`);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private buildUrl(url: string, params?: Record<string, string>): string {
|
|
138
|
+
const fullUrl = this.baseURL ? `${this.baseURL}${url}` : url;
|
|
139
|
+
|
|
140
|
+
if (!params || Object.keys(params).length === 0) {
|
|
141
|
+
return fullUrl;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const urlObj = new URL(fullUrl);
|
|
145
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
146
|
+
urlObj.searchParams.append(key, value);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return urlObj.toString();
|
|
150
|
+
}
|
|
146
151
|
}
|