react-native-timacare 3.3.36 → 3.3.38
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/lib/commonjs/screens/home/Store.js +1 -1
- package/lib/commonjs/screens/home/Store.js.flow +3 -3
- package/lib/commonjs/screens/home/Store.js.map +1 -1
- package/lib/commonjs/screens/home/index.js +1 -1
- package/lib/commonjs/screens/home/index.js.flow +1 -1
- package/lib/commonjs/screens/home/index.js.map +1 -1
- package/lib/commonjs/screens/quick-submit/store.js +1 -1
- package/lib/commonjs/screens/quick-submit/store.js.flow +1 -1
- package/lib/commonjs/screens/quick-submit/store.js.map +1 -1
- package/lib/commonjs/screens/sign-tima/ViewContract.js +1 -1
- package/lib/commonjs/screens/sign-tima/ViewContract.js.flow +1 -0
- package/lib/commonjs/screens/sign-tima/ViewContract.js.map +1 -1
- package/lib/commonjs/screens/toan-trinh-so/store.js +1 -1
- package/lib/commonjs/screens/toan-trinh-so/store.js.flow +270 -263
- package/lib/commonjs/screens/toan-trinh-so/store.js.map +1 -1
- package/lib/commonjs/sdkConfig.js +1 -1
- package/lib/commonjs/sdkConfig.js.flow +35 -14
- package/lib/commonjs/sdkConfig.js.map +1 -1
- package/lib/commonjs/services/api/api-problem.js +1 -1
- package/lib/commonjs/services/api/api-problem.js.flow +32 -31
- package/lib/commonjs/services/api/api-problem.js.map +1 -1
- package/lib/commonjs/services/api/api.js +1 -1
- package/lib/commonjs/services/api/api.js.flow +238 -0
- package/lib/commonjs/services/api/api.js.map +1 -1
- package/lib/module/screens/home/Store.js +1 -1
- package/lib/module/screens/home/Store.js.map +1 -1
- package/lib/module/screens/home/index.js +1 -1
- package/lib/module/screens/home/index.js.map +1 -1
- package/lib/module/screens/quick-submit/store.js +1 -1
- package/lib/module/screens/quick-submit/store.js.map +1 -1
- package/lib/module/screens/sign-tima/ViewContract.js +1 -1
- package/lib/module/screens/sign-tima/ViewContract.js.map +1 -1
- package/lib/module/screens/toan-trinh-so/store.js +1 -1
- package/lib/module/screens/toan-trinh-so/store.js.map +1 -1
- package/lib/module/sdkConfig.js +1 -1
- package/lib/module/sdkConfig.js.map +1 -1
- package/lib/module/services/api/api-problem.js +1 -1
- package/lib/module/services/api/api-problem.js.map +1 -1
- package/lib/module/services/api/api.js +1 -1
- package/lib/module/services/api/api.js.map +1 -1
- package/lib/typescript/screens/sign-tima/ViewContract.d.ts.map +1 -1
- package/lib/typescript/screens/toan-trinh-so/store.d.ts.map +1 -1
- package/lib/typescript/sdkConfig.d.ts +23 -0
- package/lib/typescript/sdkConfig.d.ts.map +1 -1
- package/lib/typescript/services/api/api-problem.d.ts +10 -10
- package/lib/typescript/services/api/api-problem.d.ts.map +1 -1
- package/lib/typescript/services/api/api.d.ts +43 -0
- package/lib/typescript/services/api/api.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/screens/home/Store.tsx +3 -3
- package/src/screens/home/index.tsx +1 -1
- package/src/screens/quick-submit/store.tsx +1 -1
- package/src/screens/sign-tima/ViewContract.tsx +1 -0
- package/src/screens/toan-trinh-so/store.ts +270 -263
- package/src/sdkConfig.ts +35 -14
- package/src/services/api/api-problem.ts +32 -31
- package/src/services/api/api.ts +238 -0
|
@@ -7,6 +7,43 @@ import appStore from '../../AppStore';
|
|
|
7
7
|
import axios from 'axios';
|
|
8
8
|
import { load } from '../../utils/storage';
|
|
9
9
|
import { getSDKConfig, SDKConfig } from '../../sdkConfig';
|
|
10
|
+
import { Alert } from 'react-native';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Retry tuning for weak / unstable networks.
|
|
14
|
+
* Only transient failures on idempotent requests are retried.
|
|
15
|
+
*/
|
|
16
|
+
const MAX_RETRIES = 3;
|
|
17
|
+
const RETRY_BASE_DELAY = 1000; // ms, grows exponentially: 1s, 2s, 4s (+ jitter)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default timeout for idempotent GET requests. Kept short so reads fail fast
|
|
21
|
+
* on a weak network and let the retry interceptor take over, instead of
|
|
22
|
+
* hanging for the much longer upload/write timeout. Overridable via
|
|
23
|
+
* SDKConfig.getTimeout.
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_GET_TIMEOUT = 30000; // 30s
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A failure is transient (worth retrying) when there is no response
|
|
29
|
+
* (timeout / connection drop) or the server is overloaded (5xx / 429).
|
|
30
|
+
*/
|
|
31
|
+
const isRetryableError = (error: any): boolean => {
|
|
32
|
+
if (!error || error.config?.__skipRetry) return false;
|
|
33
|
+
// No response object => network error or timeout (ECONNABORTED)
|
|
34
|
+
if (!error.response) return true;
|
|
35
|
+
const status = error.response.status;
|
|
36
|
+
return status >= 500 || status === 429;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* GET/HEAD/OPTIONS are safe to replay. POST/PUT/etc. are NOT retried
|
|
41
|
+
* automatically to avoid duplicate side effects (double loan submit, double OTP).
|
|
42
|
+
*/
|
|
43
|
+
const isIdempotent = (method?: string): boolean => {
|
|
44
|
+
const m = (method || 'get').toLowerCase();
|
|
45
|
+
return m === 'get' || m === 'head' || m === 'options';
|
|
46
|
+
};
|
|
10
47
|
|
|
11
48
|
/**
|
|
12
49
|
* Manages all requests to the API.
|
|
@@ -16,6 +53,24 @@ export class Api {
|
|
|
16
53
|
apisauce: ApisauceInstance;
|
|
17
54
|
apisauceFormData: ApisauceInstance;
|
|
18
55
|
config: SDKConfig;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Flag to prevent multiple 401 logout calls at the same time.
|
|
59
|
+
* When multiple API calls return 401 simultaneously, only the first
|
|
60
|
+
* one will trigger logout.
|
|
61
|
+
*/
|
|
62
|
+
private isLoggingOut: boolean = false;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Last known network connectivity, driven by the injected NetInfo module.
|
|
66
|
+
* Defaults to true (optimistic) so requests are never blocked when NetInfo
|
|
67
|
+
* is not provided or its first event hasn't arrived yet.
|
|
68
|
+
*/
|
|
69
|
+
private isConnected: boolean = true;
|
|
70
|
+
|
|
71
|
+
/** Unsubscribe handle for the NetInfo listener, cleared on re-setup. */
|
|
72
|
+
private netInfoUnsubscribe?: () => void;
|
|
73
|
+
|
|
19
74
|
/**
|
|
20
75
|
* Configurable options.
|
|
21
76
|
*/
|
|
@@ -47,7 +102,53 @@ export class Api {
|
|
|
47
102
|
return Api.instance;
|
|
48
103
|
}
|
|
49
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Handles 401 Unauthorized responses.
|
|
107
|
+
* Uses isLoggingOut flag to ensure Alert + logout is only triggered once
|
|
108
|
+
* even when multiple API calls return 401 simultaneously.
|
|
109
|
+
*/
|
|
110
|
+
private handleUnauthorized = () => {
|
|
111
|
+
if (this.isLoggingOut) {
|
|
112
|
+
myLog('[API] 401 detected but logout already in progress, skipping...');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.isLoggingOut = true;
|
|
117
|
+
myLog('[API] 401 Unauthorized - triggering logout');
|
|
118
|
+
|
|
119
|
+
Alert.alert(
|
|
120
|
+
'Thông báo',
|
|
121
|
+
'Phiên đăng nhập hết hạn. Vui lòng đăng nhập lại',
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
text: 'Đồng ý',
|
|
125
|
+
onPress: () => {
|
|
126
|
+
try {
|
|
127
|
+
if (this.config.logout && typeof this.config.logout === 'function') {
|
|
128
|
+
this.config.logout();
|
|
129
|
+
} else {
|
|
130
|
+
myLog('[API] Warning: logout function not configured in SDKConfig');
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
myLog('[API] Error during logout:', error);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
]
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Resets the logging out flag. Call this when the user logs in again.
|
|
143
|
+
*/
|
|
144
|
+
public resetLogoutState() {
|
|
145
|
+
this.isLoggingOut = false;
|
|
146
|
+
}
|
|
147
|
+
|
|
50
148
|
async setup() {
|
|
149
|
+
// Reset logout state on setup (e.g. when token changes after re-login)
|
|
150
|
+
this.isLoggingOut = false;
|
|
151
|
+
|
|
51
152
|
// construct the apisauce instance
|
|
52
153
|
// const token = await load("TOKEN_TIMACARE")
|
|
53
154
|
this.apisauce = create({
|
|
@@ -71,6 +172,143 @@ export class Api {
|
|
|
71
172
|
'Authorization': 'Bearer ' + this.config.token,
|
|
72
173
|
},
|
|
73
174
|
});
|
|
175
|
+
|
|
176
|
+
// Add 401 response interceptor to both apisauce instances
|
|
177
|
+
const unauthorizedMonitor = (response: ApiResponse<any>) => {
|
|
178
|
+
if (response.status === 401 || response.data?.meta?.errorCode === 401) {
|
|
179
|
+
this.handleUnauthorized();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
this.apisauce.addMonitor(unauthorizedMonitor);
|
|
184
|
+
this.apisauceFormData.addMonitor(unauthorizedMonitor);
|
|
185
|
+
|
|
186
|
+
// Subscribe to connectivity changes (no-op if NetInfo isn't injected).
|
|
187
|
+
this.subscribeNetInfo();
|
|
188
|
+
|
|
189
|
+
// Request-side guards: fast-fail when offline + shorten GET timeouts.
|
|
190
|
+
// GET-timeout splitting only applies to the main instance; the form-data
|
|
191
|
+
// instance is used for uploads which legitimately need the long timeout.
|
|
192
|
+
this.attachRequestGuards(this.apisauce, true);
|
|
193
|
+
this.attachRequestGuards(this.apisauceFormData, false);
|
|
194
|
+
|
|
195
|
+
// Auto-retry transient failures on weak networks (timeouts, dropped
|
|
196
|
+
// connections, 5xx, 429). Applied to the underlying axios instance so
|
|
197
|
+
// apisauce still sees the final (successful or exhausted) response.
|
|
198
|
+
this.attachRetryInterceptor(this.apisauce);
|
|
199
|
+
this.attachRetryInterceptor(this.apisauceFormData);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Subscribes to the injected NetInfo module (if any) to track connectivity.
|
|
204
|
+
* Safe to call repeatedly — the previous listener is torn down first.
|
|
205
|
+
*/
|
|
206
|
+
private subscribeNetInfo() {
|
|
207
|
+
if (this.netInfoUnsubscribe) {
|
|
208
|
+
try {
|
|
209
|
+
this.netInfoUnsubscribe();
|
|
210
|
+
} catch (e) {
|
|
211
|
+
myLog('[API] NetInfo unsubscribe error:', e);
|
|
212
|
+
}
|
|
213
|
+
this.netInfoUnsubscribe = undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const netInfo = this.config.netInfo;
|
|
217
|
+
if (!netInfo) {
|
|
218
|
+
this.isConnected = true; // no NetInfo => never block on connectivity
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Seed current state, then listen for changes.
|
|
223
|
+
netInfo
|
|
224
|
+
.fetch()
|
|
225
|
+
.then(state => {
|
|
226
|
+
this.isConnected = state.isConnected !== false;
|
|
227
|
+
})
|
|
228
|
+
.catch(() => {
|
|
229
|
+
this.isConnected = true;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
this.netInfoUnsubscribe = netInfo.addEventListener(state => {
|
|
233
|
+
this.isConnected = state.isConnected !== false;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Attaches a request interceptor that:
|
|
239
|
+
* 1. Fast-fails when the device is known to be offline (avoids waiting for
|
|
240
|
+
* a long timeout before the user sees an error).
|
|
241
|
+
* 2. Shortens the timeout for idempotent GETs on the main instance so reads
|
|
242
|
+
* fail fast and hand off to the retry interceptor.
|
|
243
|
+
*/
|
|
244
|
+
private attachRequestGuards(instance: ApisauceInstance, splitGetTimeout: boolean) {
|
|
245
|
+
const getTimeout = this.config.getTimeout || DEFAULT_GET_TIMEOUT;
|
|
246
|
+
|
|
247
|
+
instance.axiosInstance.interceptors.request.use((config: any) => {
|
|
248
|
+
// Offline fast-fail. apisauce converts this rejection into a normal
|
|
249
|
+
// CANNOT_CONNECT / NETWORK_ERROR problem, same as a timeout but instant.
|
|
250
|
+
if (this.config.netInfo && this.isConnected === false) {
|
|
251
|
+
myLog(`[API] Offline - skipping request: ${config.url}`);
|
|
252
|
+
return Promise.reject({
|
|
253
|
+
config,
|
|
254
|
+
message: 'Network offline',
|
|
255
|
+
__offline: true,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Shorten GET timeouts unless the caller passed an explicit override.
|
|
260
|
+
if (
|
|
261
|
+
splitGetTimeout &&
|
|
262
|
+
isIdempotent(config.method) &&
|
|
263
|
+
config.timeout === this.config.timeout
|
|
264
|
+
) {
|
|
265
|
+
config.timeout = getTimeout;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return config;
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Attaches an exponential-backoff retry interceptor to an apisauce
|
|
274
|
+
* instance's axios layer. Only idempotent requests with transient
|
|
275
|
+
* errors are replayed, up to MAX_RETRIES times.
|
|
276
|
+
*/
|
|
277
|
+
private attachRetryInterceptor(instance: ApisauceInstance) {
|
|
278
|
+
instance.axiosInstance.interceptors.response.use(
|
|
279
|
+
response => response,
|
|
280
|
+
async (error: any) => {
|
|
281
|
+
const config = error?.config;
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
!config ||
|
|
285
|
+
!isIdempotent(config.method) ||
|
|
286
|
+
!isRetryableError(error)
|
|
287
|
+
) {
|
|
288
|
+
return Promise.reject(error);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
config.__retryCount = config.__retryCount || 0;
|
|
292
|
+
if (config.__retryCount >= MAX_RETRIES) {
|
|
293
|
+
myLog(`[API] Retry exhausted (${MAX_RETRIES}) for ${config.url}`);
|
|
294
|
+
return Promise.reject(error);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
config.__retryCount += 1;
|
|
298
|
+
|
|
299
|
+
// Exponential backoff with jitter: 1s, 2s, 4s (+ up to 300ms)
|
|
300
|
+
const delay =
|
|
301
|
+
RETRY_BASE_DELAY * Math.pow(2, config.__retryCount - 1) +
|
|
302
|
+
Math.floor(Math.random() * 300);
|
|
303
|
+
|
|
304
|
+
myLog(
|
|
305
|
+
`[API] Retry ${config.__retryCount}/${MAX_RETRIES} in ${delay}ms: ${config.url}`
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
309
|
+
return instance.axiosInstance(config);
|
|
310
|
+
}
|
|
311
|
+
);
|
|
74
312
|
}
|
|
75
313
|
|
|
76
314
|
/**
|