ropegeo-common 1.12.10 → 1.12.12
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 +3 -3
- package/dist/components/RopeGeoCursorPaginationHttpRequest.d.ts +18 -4
- package/dist/components/RopeGeoCursorPaginationHttpRequest.d.ts.map +1 -1
- package/dist/components/RopeGeoCursorPaginationHttpRequest.js +72 -15
- package/dist/components/RopeGeoHttpRequest.d.ts +14 -1
- package/dist/components/RopeGeoHttpRequest.d.ts.map +1 -1
- package/dist/components/RopeGeoHttpRequest.js +58 -5
- package/dist/components/RopeGeoPaginationHttpRequest.d.ts +14 -1
- package/dist/components/RopeGeoPaginationHttpRequest.d.ts.map +1 -1
- package/dist/components/RopeGeoPaginationHttpRequest.js +61 -9
- package/dist/helpers/network/index.d.ts +1 -1
- package/dist/helpers/network/index.d.ts.map +1 -1
- package/dist/helpers/network/index.js +3 -1
- package/dist/helpers/network/networkRequestPolicy.d.ts +10 -0
- package/dist/helpers/network/networkRequestPolicy.d.ts.map +1 -1
- package/dist/helpers/network/networkRequestPolicy.js +55 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -312,9 +312,9 @@ Abstract classes live under `minimap/abstract/`, concrete under `minimap/concret
|
|
|
312
312
|
|
|
313
313
|
| Name | Description | Import |
|
|
314
314
|
| --- | --- | --- |
|
|
315
|
-
| `RopeGeoHttpRequest` | Single GET/POST wrapper; parses `Result.fromResponseBody`; optional `timeoutAfterSeconds`
|
|
316
|
-
| `RopeGeoCursorPaginationHttpRequest` | Cursor-paginated fetch with `loadMore`;
|
|
317
|
-
| `RopeGeoPaginationHttpRequest<T>` | Page-based fetch;
|
|
315
|
+
| `RopeGeoHttpRequest` | Single GET/POST wrapper; parses `Result.fromResponseBody`; optional `timeoutAfterSeconds`, `isOnline`, `refreshOnReconnect`, `timeoutCountdown`, and `refreshing` (stale-while-revalidate) children args. | `import { RopeGeoHttpRequest, Method, Service } from 'ropegeo-common/components'` |
|
|
316
|
+
| `RopeGeoCursorPaginationHttpRequest` | Cursor-paginated fetch with `loadMore`; `data` is `T[] \| null` (null until first success); optional `timeoutAfterSeconds`, `isOnline`, `refreshOnReconnect`, `refreshing`, `timeoutCountdown`. | `import { RopeGeoCursorPaginationHttpRequest } from 'ropegeo-common/components'` |
|
|
317
|
+
| `RopeGeoPaginationHttpRequest<T>` | Page-based fetch; concatenates `results` into `data` (`T[]` when complete, otherwise `null` with `errors`); optional `isOnline`, `refreshOnReconnect`, `refreshing`, `timeoutCountdown`. | `import { RopeGeoPaginationHttpRequest } from 'ropegeo-common/components'` |
|
|
318
318
|
|
|
319
319
|
---
|
|
320
320
|
|
|
@@ -14,23 +14,37 @@ export type RopeGeoCursorPaginationHttpRequestProps<T = unknown> = {
|
|
|
14
14
|
timeoutAfterSeconds?: number;
|
|
15
15
|
/**
|
|
16
16
|
* When `false`, no HTTP requests run and children receive {@link NO_NETWORK_MESSAGE} as the error.
|
|
17
|
-
* Previously loaded `data` and cursor `params` are kept until the network returns
|
|
18
|
-
* behavior as {@link RopeGeoPaginationHttpRequest} / {@link RopeGeoHttpRequest} when offline).
|
|
17
|
+
* Previously loaded `data` and cursor `params` are kept until the network returns.
|
|
19
18
|
*/
|
|
20
19
|
isOnline?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* When `isOnline` goes from `false` to online and there is already successful data for the same
|
|
22
|
+
* request (only the soft {@link NO_NETWORK_MESSAGE} error), a new fetch runs only if this is
|
|
23
|
+
* `true`. Otherwise stale data stays visible and `errors` is cleared. When there is no
|
|
24
|
+
* successful data yet, or the last error was not the offline placeholder, a fetch always runs.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
refreshOnReconnect?: boolean;
|
|
21
28
|
/**
|
|
22
29
|
* Response body is parsed via CursorPaginationResults.fromResponseBody (must include resultType).
|
|
23
30
|
* Parsed shape is ValidatedCursorPaginationResponse; children receive result.results as data.
|
|
31
|
+
* `data` is `null` until the first successful response for the current request identity, then an
|
|
32
|
+
* array (possibly empty) for loaded pages.
|
|
24
33
|
*/
|
|
25
34
|
children: (args: {
|
|
26
35
|
loading: boolean;
|
|
27
36
|
loadingMore: boolean;
|
|
28
|
-
|
|
37
|
+
/**
|
|
38
|
+
* `true` while the initial request is in flight after at least one successful response for the
|
|
39
|
+
* current request identity (stale-while-revalidate). Not used for `loadMore` alone.
|
|
40
|
+
*/
|
|
41
|
+
refreshing: boolean;
|
|
42
|
+
data: T[] | null;
|
|
29
43
|
errors: Error | null;
|
|
30
44
|
loadMore: () => void;
|
|
31
45
|
hasMore: boolean;
|
|
32
46
|
timeoutCountdown: number | null;
|
|
33
47
|
}) => ReactNode;
|
|
34
48
|
};
|
|
35
|
-
export declare function RopeGeoCursorPaginationHttpRequest<T = unknown>({ service, method, path, pathParams, queryParams, timeoutAfterSeconds, isOnline, children, }: RopeGeoCursorPaginationHttpRequestProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
export declare function RopeGeoCursorPaginationHttpRequest<T = unknown>({ service, method, path, pathParams, queryParams, timeoutAfterSeconds, isOnline, refreshOnReconnect, children, }: RopeGeoCursorPaginationHttpRequestProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
36
50
|
//# sourceMappingURL=RopeGeoCursorPaginationHttpRequest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RopeGeoCursorPaginationHttpRequest.d.ts","sourceRoot":"","sources":["../../src/components/RopeGeoCursorPaginationHttpRequest.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"RopeGeoCursorPaginationHttpRequest.d.ts","sourceRoot":"","sources":["../../src/components/RopeGeoCursorPaginationHttpRequest.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAWvC,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAoB,MAAM,sBAAsB,CAAC;AAsCzE,MAAM,MAAM,uCAAuC,CAAC,CAAC,GAAG,OAAO,IAAI;IACjE,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,OAAO,MAAM,CAAC,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,WAAW,EAAE,sBAAsB,CAAC;IACpC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,OAAO,EAAE,OAAO,CAAC;QACjB,WAAW,EAAE,OAAO,CAAC;QACrB;;;WAGG;QACH,UAAU,EAAE,OAAO,CAAC;QACpB,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,MAAM,EAAE,KAAK,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;QACrB,OAAO,EAAE,OAAO,CAAC;QACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;KACjC,KAAK,SAAS,CAAC;CACjB,CAAC;AAEF,wBAAgB,kCAAkC,CAAC,CAAC,GAAG,OAAO,EAAE,EAC9D,OAAO,EACP,MAAmB,EACnB,IAAI,EACJ,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,QAAQ,EACR,kBAA0B,EAC1B,QAAQ,GACT,EAAE,uCAAuC,CAAC,CAAC,CAAC,2CAqV5C"}
|
|
@@ -30,16 +30,26 @@ function getResponseBody(raw) {
|
|
|
30
30
|
}
|
|
31
31
|
return raw;
|
|
32
32
|
}
|
|
33
|
-
function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.Method.GET, path, pathParams, queryParams, timeoutAfterSeconds, isOnline, children, }) {
|
|
33
|
+
function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.Method.GET, path, pathParams, queryParams, timeoutAfterSeconds, isOnline, refreshOnReconnect = false, children, }) {
|
|
34
34
|
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
35
35
|
const [loadingMore, setLoadingMore] = (0, react_1.useState)(false);
|
|
36
|
-
const [data, setData] = (0, react_1.useState)(
|
|
36
|
+
const [data, setData] = (0, react_1.useState)(null);
|
|
37
37
|
const [params, setParams] = (0, react_1.useState)(queryParams);
|
|
38
38
|
const [errors, setErrors] = (0, react_1.useState)(null);
|
|
39
39
|
const [timeoutCountdown, setTimeoutCountdown] = (0, react_1.useState)(null);
|
|
40
|
+
const [hasCommittedOnce, setHasCommittedOnce] = (0, react_1.useState)(false);
|
|
41
|
+
const errorsRef = (0, react_1.useRef)(errors);
|
|
42
|
+
const hasCommittedRef = (0, react_1.useRef)(hasCommittedOnce);
|
|
43
|
+
errorsRef.current = errors;
|
|
44
|
+
hasCommittedRef.current = hasCommittedOnce;
|
|
40
45
|
const loadingMoreRef = (0, react_1.useRef)(false);
|
|
41
46
|
const loadMoreAbortRef = (0, react_1.useRef)(null);
|
|
42
47
|
const hasMore = params.cursor != null;
|
|
48
|
+
const pathParamsKey = JSON.stringify(pathParams ?? null);
|
|
49
|
+
const queryKey = queryParams.toQueryString();
|
|
50
|
+
const requestKey = (0, react_1.useMemo)(() => `${service}|${method}|${path}|${pathParamsKey}|${queryKey}|${timeoutAfterSeconds ?? ""}`, [service, method, path, pathParamsKey, queryKey, timeoutAfterSeconds]);
|
|
51
|
+
const prevIsOnlineRef = (0, react_1.useRef)(undefined);
|
|
52
|
+
const lastRequestKeyRef = (0, react_1.useRef)("");
|
|
43
53
|
const buildUrl = (0, react_1.useCallback)((p) => {
|
|
44
54
|
const baseUrl = RopeGeoHttpRequest_1.SERVICE_BASE_URL[service];
|
|
45
55
|
const resolvedPath = resolvePath(path, pathParams);
|
|
@@ -48,26 +58,61 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
48
58
|
return new URL(fullPath, baseUrl).toString();
|
|
49
59
|
}, [service, path, pathParams]);
|
|
50
60
|
(0, react_1.useEffect)(() => {
|
|
51
|
-
|
|
61
|
+
const online = isOnline !== false;
|
|
62
|
+
const prevOnline = prevIsOnlineRef.current;
|
|
63
|
+
const reconnecting = prevOnline === false && online;
|
|
64
|
+
const keyChanged = lastRequestKeyRef.current !== requestKey;
|
|
65
|
+
if (!online) {
|
|
66
|
+
if (keyChanged) {
|
|
67
|
+
lastRequestKeyRef.current = requestKey;
|
|
68
|
+
setData(null);
|
|
69
|
+
setHasCommittedOnce(false);
|
|
70
|
+
setParams(queryParams);
|
|
71
|
+
setErrors(null);
|
|
72
|
+
}
|
|
52
73
|
loadMoreAbortRef.current?.abort();
|
|
53
74
|
loadMoreAbortRef.current = null;
|
|
54
75
|
loadingMoreRef.current = false;
|
|
55
76
|
setLoadingMore(false);
|
|
77
|
+
prevIsOnlineRef.current = false;
|
|
56
78
|
setLoading(false);
|
|
57
79
|
setErrors(new Error(network_1.NO_NETWORK_MESSAGE));
|
|
58
80
|
setTimeoutCountdown(null);
|
|
59
81
|
return;
|
|
60
82
|
}
|
|
83
|
+
if (keyChanged) {
|
|
84
|
+
lastRequestKeyRef.current = requestKey;
|
|
85
|
+
setHasCommittedOnce(false);
|
|
86
|
+
setData(null);
|
|
87
|
+
setParams(queryParams);
|
|
88
|
+
setErrors(null);
|
|
89
|
+
}
|
|
90
|
+
if (!keyChanged && reconnecting) {
|
|
91
|
+
const onlyNoNetwork = errorsRef.current?.message === network_1.NO_NETWORK_MESSAGE;
|
|
92
|
+
if (hasCommittedRef.current && onlyNoNetwork && !refreshOnReconnect) {
|
|
93
|
+
setErrors(null);
|
|
94
|
+
setLoading(false);
|
|
95
|
+
prevIsOnlineRef.current = true;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
prevIsOnlineRef.current = true;
|
|
61
100
|
let cancelled = false;
|
|
62
101
|
const abortController = new AbortController();
|
|
63
102
|
const timedOutRef = { current: false };
|
|
64
103
|
const requestStartedAt = Date.now();
|
|
65
104
|
const timeoutMs = (0, network_1.resolveRequestTimeoutMs)(timeoutAfterSeconds);
|
|
66
|
-
setData([]);
|
|
67
|
-
setParams(queryParams);
|
|
68
105
|
setLoading(true);
|
|
69
106
|
setErrors(null);
|
|
70
107
|
setTimeoutCountdown(null);
|
|
108
|
+
const keepStaleDuringFetch = reconnecting &&
|
|
109
|
+
hasCommittedRef.current &&
|
|
110
|
+
errorsRef.current?.message === network_1.NO_NETWORK_MESSAGE &&
|
|
111
|
+
refreshOnReconnect;
|
|
112
|
+
if (!keyChanged && !keepStaleDuringFetch) {
|
|
113
|
+
setData(null);
|
|
114
|
+
setParams(queryParams);
|
|
115
|
+
}
|
|
71
116
|
const policyDispose = timeoutMs == null
|
|
72
117
|
? () => { }
|
|
73
118
|
: (0, network_1.installNetworkRequestPolicyTimers)(requestStartedAt, timeoutMs, {
|
|
@@ -97,12 +142,16 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
97
142
|
return;
|
|
98
143
|
const text = await res.text();
|
|
99
144
|
if (!res.ok) {
|
|
100
|
-
setErrors(new Error(
|
|
101
|
-
setData(
|
|
145
|
+
setErrors(new Error((0, network_1.formatHttpStatusMessage)(res.status, text || res.statusText)));
|
|
146
|
+
setData(null);
|
|
147
|
+
setHasCommittedOnce(false);
|
|
102
148
|
return;
|
|
103
149
|
}
|
|
104
150
|
if (text.length === 0) {
|
|
105
151
|
setData([]);
|
|
152
|
+
setParams(queryParams.withCursor(null));
|
|
153
|
+
setErrors(null);
|
|
154
|
+
setHasCommittedOnce(true);
|
|
106
155
|
return;
|
|
107
156
|
}
|
|
108
157
|
try {
|
|
@@ -115,6 +164,7 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
115
164
|
setData(results);
|
|
116
165
|
setParams(queryParams.withCursor(nextCursor));
|
|
117
166
|
setErrors(null);
|
|
167
|
+
setHasCommittedOnce(true);
|
|
118
168
|
}
|
|
119
169
|
catch (parseError) {
|
|
120
170
|
if (!cancelled) {
|
|
@@ -125,7 +175,8 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
125
175
|
parseError: parseError instanceof Error ? parseError.message : String(parseError),
|
|
126
176
|
});
|
|
127
177
|
setErrors(new Error("Invalid JSON response"));
|
|
128
|
-
setData(
|
|
178
|
+
setData(null);
|
|
179
|
+
setHasCommittedOnce(false);
|
|
129
180
|
}
|
|
130
181
|
}
|
|
131
182
|
})
|
|
@@ -133,8 +184,9 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
133
184
|
if (cancelled)
|
|
134
185
|
return;
|
|
135
186
|
if (timedOutRef.current) {
|
|
136
|
-
setErrors(new Error(network_1.NETWORK_REQUEST_TIMED_OUT_MESSAGE));
|
|
137
|
-
setData(
|
|
187
|
+
setErrors(new Error((0, network_1.formatNetworkRequestErrorMessage)(new Error(network_1.NETWORK_REQUEST_TIMED_OUT_MESSAGE))));
|
|
188
|
+
setData(null);
|
|
189
|
+
setHasCommittedOnce(false);
|
|
138
190
|
return;
|
|
139
191
|
}
|
|
140
192
|
if ((0, network_1.isAbortError)(err))
|
|
@@ -143,8 +195,9 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
143
195
|
url,
|
|
144
196
|
error: err instanceof Error ? err.message : String(err),
|
|
145
197
|
});
|
|
146
|
-
setErrors(
|
|
147
|
-
setData(
|
|
198
|
+
setErrors(new Error((0, network_1.formatNetworkRequestErrorMessage)(err)));
|
|
199
|
+
setData(null);
|
|
200
|
+
setHasCommittedOnce(false);
|
|
148
201
|
})
|
|
149
202
|
.finally(() => {
|
|
150
203
|
policyDispose();
|
|
@@ -162,11 +215,13 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
162
215
|
service,
|
|
163
216
|
method,
|
|
164
217
|
path,
|
|
165
|
-
|
|
166
|
-
|
|
218
|
+
pathParamsKey,
|
|
219
|
+
queryKey,
|
|
167
220
|
buildUrl,
|
|
168
221
|
timeoutAfterSeconds,
|
|
169
222
|
isOnline,
|
|
223
|
+
refreshOnReconnect,
|
|
224
|
+
requestKey,
|
|
170
225
|
]);
|
|
171
226
|
(0, react_1.useEffect)(() => {
|
|
172
227
|
return () => {
|
|
@@ -224,7 +279,7 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
224
279
|
const body = getResponseBody(raw);
|
|
225
280
|
const result = models_1.CursorPaginationResults.fromResponseBody(body);
|
|
226
281
|
const { results, nextCursor } = result;
|
|
227
|
-
setData((prev) => [...prev, ...results]);
|
|
282
|
+
setData((prev) => [...(prev ?? []), ...results]);
|
|
228
283
|
setParams((p) => p.withCursor(nextCursor));
|
|
229
284
|
}
|
|
230
285
|
catch (parseError) {
|
|
@@ -260,9 +315,11 @@ function RopeGeoCursorPaginationHttpRequest({ service, method = RopeGeoHttpReque
|
|
|
260
315
|
setLoadingMore(false);
|
|
261
316
|
});
|
|
262
317
|
}, [params, method, buildUrl, timeoutAfterSeconds, isOnline]);
|
|
318
|
+
const refreshing = loading && hasCommittedOnce;
|
|
263
319
|
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children({
|
|
264
320
|
loading,
|
|
265
321
|
loadingMore,
|
|
322
|
+
refreshing,
|
|
266
323
|
data,
|
|
267
324
|
errors,
|
|
268
325
|
loadMore,
|
|
@@ -29,12 +29,25 @@ export type RopeGeoHttpRequestProps<T = unknown> = {
|
|
|
29
29
|
* not fired while offline.
|
|
30
30
|
*/
|
|
31
31
|
isOnline?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* When `isOnline` goes from `false` to online and there is already successful data for the same
|
|
34
|
+
* request (only the soft {@link NO_NETWORK_MESSAGE} error), a new fetch runs only if this is
|
|
35
|
+
* `true`. Otherwise stale data stays visible and `errors` is cleared. When there is no
|
|
36
|
+
* successful data yet, or the last error was not the offline placeholder, a fetch always runs.
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
refreshOnReconnect?: boolean;
|
|
32
40
|
/**
|
|
33
41
|
* Response body is parsed via Result.fromResponseBody (must include resultType and result).
|
|
34
42
|
* Children receive the validated result.result as data (typed by T).
|
|
35
43
|
*/
|
|
36
44
|
children: (args: {
|
|
37
45
|
loading: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* `true` while a request is in flight after at least one successful response for the current
|
|
48
|
+
* request identity (stale-while-revalidate).
|
|
49
|
+
*/
|
|
50
|
+
refreshing: boolean;
|
|
38
51
|
data: T | null;
|
|
39
52
|
errors: Error | null;
|
|
40
53
|
/**
|
|
@@ -45,5 +58,5 @@ export type RopeGeoHttpRequestProps<T = unknown> = {
|
|
|
45
58
|
timeoutCountdown: number | null;
|
|
46
59
|
}) => ReactNode;
|
|
47
60
|
};
|
|
48
|
-
export declare function RopeGeoHttpRequest<T = unknown>({ service, method, path, pathParams, queryParams, body, timeoutAfterSeconds, isOnline, children, }: RopeGeoHttpRequestProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
61
|
+
export declare function RopeGeoHttpRequest<T = unknown>({ service, method, path, pathParams, queryParams, body, timeoutAfterSeconds, isOnline, refreshOnReconnect, children, }: RopeGeoHttpRequestProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
49
62
|
//# sourceMappingURL=RopeGeoHttpRequest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RopeGeoHttpRequest.d.ts","sourceRoot":"","sources":["../../src/components/RopeGeoHttpRequest.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"RopeGeoHttpRequest.d.ts","sourceRoot":"","sources":["../../src/components/RopeGeoHttpRequest.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAavC,eAAO,MAAM,OAAO;;CAEV,CAAC;AACX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;AAE7D,eAAO,MAAM,MAAM;;;;;CAKT,CAAC;AACX,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,OAAO,MAAM,CAAC,CAAC;AAE1D,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAEpD,CAAC;AAmCF,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,OAAO,IAAI;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,OAAO,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,UAAU,EAAE,OAAO,CAAC;QACpB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;QACf,MAAM,EAAE,KAAK,GAAG,IAAI,CAAC;QACrB;;;;WAIG;QACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;KACjC,KAAK,SAAS,CAAC;CACjB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAAE,EAC9C,OAAO,EACP,MAAM,EACN,IAAI,EACJ,UAAU,EACV,WAAW,EACX,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,kBAA0B,EAC1B,QAAQ,GACT,EAAE,uBAAuB,CAAC,CAAC,CAAC,2CAkO5B"}
|
|
@@ -40,11 +40,16 @@ function buildUrl(baseUrl, path, pathParams, queryParams) {
|
|
|
40
40
|
}
|
|
41
41
|
return url.toString();
|
|
42
42
|
}
|
|
43
|
-
function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, body, timeoutAfterSeconds, isOnline, children, }) {
|
|
43
|
+
function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, body, timeoutAfterSeconds, isOnline, refreshOnReconnect = false, children, }) {
|
|
44
44
|
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
45
45
|
const [data, setData] = (0, react_1.useState)(null);
|
|
46
46
|
const [errors, setErrors] = (0, react_1.useState)(null);
|
|
47
47
|
const [timeoutCountdown, setTimeoutCountdown] = (0, react_1.useState)(null);
|
|
48
|
+
const [hasCommittedOnce, setHasCommittedOnce] = (0, react_1.useState)(false);
|
|
49
|
+
const errorsRef = (0, react_1.useRef)(errors);
|
|
50
|
+
const hasCommittedRef = (0, react_1.useRef)(hasCommittedOnce);
|
|
51
|
+
errorsRef.current = errors;
|
|
52
|
+
hasCommittedRef.current = hasCommittedOnce;
|
|
48
53
|
const pathParamsKey = JSON.stringify(pathParams ?? null);
|
|
49
54
|
const queryParamsKey = JSON.stringify(queryParams ?? null);
|
|
50
55
|
const bodyKey = body === undefined || body === null
|
|
@@ -52,13 +57,43 @@ function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, bo
|
|
|
52
57
|
: typeof body === "object"
|
|
53
58
|
? JSON.stringify(body)
|
|
54
59
|
: body;
|
|
60
|
+
const requestKey = (0, react_1.useMemo)(() => `${service}|${method}|${path}|${pathParamsKey}|${queryParamsKey}|${String(bodyKey)}|${timeoutAfterSeconds ?? ""}`, [service, method, path, pathParamsKey, queryParamsKey, bodyKey, timeoutAfterSeconds]);
|
|
61
|
+
const prevIsOnlineRef = (0, react_1.useRef)(undefined);
|
|
62
|
+
const lastRequestKeyRef = (0, react_1.useRef)("");
|
|
55
63
|
(0, react_1.useEffect)(() => {
|
|
56
|
-
|
|
64
|
+
const online = isOnline !== false;
|
|
65
|
+
const prevOnline = prevIsOnlineRef.current;
|
|
66
|
+
const reconnecting = prevOnline === false && online;
|
|
67
|
+
const keyChanged = lastRequestKeyRef.current !== requestKey;
|
|
68
|
+
if (!online) {
|
|
69
|
+
if (keyChanged) {
|
|
70
|
+
lastRequestKeyRef.current = requestKey;
|
|
71
|
+
setData(null);
|
|
72
|
+
setHasCommittedOnce(false);
|
|
73
|
+
setErrors(null);
|
|
74
|
+
}
|
|
75
|
+
prevIsOnlineRef.current = false;
|
|
57
76
|
setLoading(false);
|
|
58
77
|
setErrors(new Error(network_1.NO_NETWORK_MESSAGE));
|
|
59
78
|
setTimeoutCountdown(null);
|
|
60
79
|
return;
|
|
61
80
|
}
|
|
81
|
+
if (keyChanged) {
|
|
82
|
+
lastRequestKeyRef.current = requestKey;
|
|
83
|
+
setHasCommittedOnce(false);
|
|
84
|
+
setData(null);
|
|
85
|
+
setErrors(null);
|
|
86
|
+
}
|
|
87
|
+
if (!keyChanged && reconnecting) {
|
|
88
|
+
const onlyNoNetwork = errorsRef.current?.message === network_1.NO_NETWORK_MESSAGE;
|
|
89
|
+
if (hasCommittedRef.current && onlyNoNetwork && !refreshOnReconnect) {
|
|
90
|
+
setErrors(null);
|
|
91
|
+
setLoading(false);
|
|
92
|
+
prevIsOnlineRef.current = true;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
prevIsOnlineRef.current = true;
|
|
62
97
|
let cancelled = false;
|
|
63
98
|
const abortController = new AbortController();
|
|
64
99
|
const timedOutRef = { current: false };
|
|
@@ -67,6 +102,13 @@ function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, bo
|
|
|
67
102
|
setLoading(true);
|
|
68
103
|
setErrors(null);
|
|
69
104
|
setTimeoutCountdown(null);
|
|
105
|
+
const keepStaleDuringFetch = reconnecting &&
|
|
106
|
+
hasCommittedRef.current &&
|
|
107
|
+
errorsRef.current?.message === network_1.NO_NETWORK_MESSAGE &&
|
|
108
|
+
refreshOnReconnect;
|
|
109
|
+
if (!keyChanged && !keepStaleDuringFetch) {
|
|
110
|
+
setData(null);
|
|
111
|
+
}
|
|
70
112
|
const policyDispose = timeoutMs == null
|
|
71
113
|
? () => { }
|
|
72
114
|
: (0, network_1.installNetworkRequestPolicyTimers)(requestStartedAt, timeoutMs, {
|
|
@@ -102,12 +144,15 @@ function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, bo
|
|
|
102
144
|
return;
|
|
103
145
|
const text = await res.text();
|
|
104
146
|
if (!res.ok) {
|
|
105
|
-
setErrors(new Error(
|
|
147
|
+
setErrors(new Error((0, network_1.formatHttpStatusMessage)(res.status, text || res.statusText)));
|
|
106
148
|
setData(null);
|
|
149
|
+
setHasCommittedOnce(false);
|
|
107
150
|
return;
|
|
108
151
|
}
|
|
109
152
|
if (text.length === 0) {
|
|
110
153
|
setData(null);
|
|
154
|
+
setErrors(null);
|
|
155
|
+
setHasCommittedOnce(true);
|
|
111
156
|
return;
|
|
112
157
|
}
|
|
113
158
|
try {
|
|
@@ -116,6 +161,7 @@ function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, bo
|
|
|
116
161
|
if (!cancelled) {
|
|
117
162
|
setData(parsed.result);
|
|
118
163
|
setErrors(null);
|
|
164
|
+
setHasCommittedOnce(true);
|
|
119
165
|
}
|
|
120
166
|
}
|
|
121
167
|
catch (parseError) {
|
|
@@ -128,6 +174,7 @@ function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, bo
|
|
|
128
174
|
});
|
|
129
175
|
setErrors(parseError instanceof Error ? parseError : new Error("Invalid JSON response"));
|
|
130
176
|
setData(null);
|
|
177
|
+
setHasCommittedOnce(false);
|
|
131
178
|
}
|
|
132
179
|
}
|
|
133
180
|
})
|
|
@@ -135,8 +182,9 @@ function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, bo
|
|
|
135
182
|
if (cancelled)
|
|
136
183
|
return;
|
|
137
184
|
if (timedOutRef.current) {
|
|
138
|
-
setErrors(new Error(network_1.NETWORK_REQUEST_TIMED_OUT_MESSAGE));
|
|
185
|
+
setErrors(new Error((0, network_1.formatNetworkRequestErrorMessage)(new Error(network_1.NETWORK_REQUEST_TIMED_OUT_MESSAGE))));
|
|
139
186
|
setData(null);
|
|
187
|
+
setHasCommittedOnce(false);
|
|
140
188
|
return;
|
|
141
189
|
}
|
|
142
190
|
if ((0, network_1.isAbortError)(err))
|
|
@@ -145,8 +193,9 @@ function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, bo
|
|
|
145
193
|
url,
|
|
146
194
|
error: err instanceof Error ? err.message : String(err),
|
|
147
195
|
});
|
|
148
|
-
setErrors(
|
|
196
|
+
setErrors(new Error((0, network_1.formatNetworkRequestErrorMessage)(err)));
|
|
149
197
|
setData(null);
|
|
198
|
+
setHasCommittedOnce(false);
|
|
150
199
|
})
|
|
151
200
|
.finally(() => {
|
|
152
201
|
policyDispose();
|
|
@@ -169,9 +218,13 @@ function RopeGeoHttpRequest({ service, method, path, pathParams, queryParams, bo
|
|
|
169
218
|
bodyKey,
|
|
170
219
|
timeoutAfterSeconds,
|
|
171
220
|
isOnline,
|
|
221
|
+
refreshOnReconnect,
|
|
222
|
+
requestKey,
|
|
172
223
|
]);
|
|
224
|
+
const refreshing = loading && hasCommittedOnce;
|
|
173
225
|
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children({
|
|
174
226
|
loading,
|
|
227
|
+
refreshing,
|
|
175
228
|
data,
|
|
176
229
|
errors,
|
|
177
230
|
timeoutCountdown,
|
|
@@ -25,8 +25,21 @@ export type RopeGeoPaginationHttpRequestProps<T = unknown> = {
|
|
|
25
25
|
* {@link RopeGeoCursorPaginationHttpRequest}.
|
|
26
26
|
*/
|
|
27
27
|
isOnline?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* When `isOnline` goes from `false` to online and there is already successful data for the same
|
|
30
|
+
* request (only the soft {@link NO_NETWORK_MESSAGE} error), a new fetch runs only if this is
|
|
31
|
+
* `true`. Otherwise stale data stays visible and `errors` is cleared. When there is no
|
|
32
|
+
* successful data yet, or the last error was not the offline placeholder, a fetch always runs.
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
refreshOnReconnect?: boolean;
|
|
28
36
|
children: (args: {
|
|
29
37
|
loading: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* `true` while a full pagination pass is in flight after at least one successful completion for
|
|
40
|
+
* the current request identity (stale-while-revalidate).
|
|
41
|
+
*/
|
|
42
|
+
refreshing: boolean;
|
|
30
43
|
received: number;
|
|
31
44
|
total: number | null;
|
|
32
45
|
/**
|
|
@@ -46,5 +59,5 @@ export type RopeGeoPaginationHttpRequestProps<T = unknown> = {
|
|
|
46
59
|
* {@link PaginationResults.fromResponseBody}. Final `data` is pages concatenated in page order.
|
|
47
60
|
* In-flight requests use one {@link AbortController}: unmount or any failure aborts the rest.
|
|
48
61
|
*/
|
|
49
|
-
export declare function RopeGeoPaginationHttpRequest<T = unknown>({ service, method, path, pathParams, queryParams, batchSize, timeoutAfterSeconds, isOnline, children, }: RopeGeoPaginationHttpRequestProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
62
|
+
export declare function RopeGeoPaginationHttpRequest<T = unknown>({ service, method, path, pathParams, queryParams, batchSize, timeoutAfterSeconds, isOnline, refreshOnReconnect, children, }: RopeGeoPaginationHttpRequestProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
50
63
|
//# sourceMappingURL=RopeGeoPaginationHttpRequest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RopeGeoPaginationHttpRequest.d.ts","sourceRoot":"","sources":["../../src/components/RopeGeoPaginationHttpRequest.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"RopeGeoPaginationHttpRequest.d.ts","sourceRoot":"","sources":["../../src/components/RopeGeoPaginationHttpRequest.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAYvC,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAoB,MAAM,sBAAsB,CAAC;AA8DzE,MAAM,MAAM,iCAAiC,CAAC,CAAC,GAAG,OAAO,IAAI;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,OAAO,MAAM,CAAC,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,WAAW,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,OAAO,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,UAAU,EAAE,OAAO,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB;;;WAGG;QACH,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,+FAA+F;QAC/F,MAAM,EAAE,KAAK,GAAG,IAAI,CAAC;QACrB,qGAAqG;QACrG,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;KACjC,KAAK,SAAS,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,GAAG,OAAO,EAAE,EACxD,OAAO,EACP,MAAmB,EACnB,IAAI,EACJ,UAAU,EACV,WAAW,EACX,SAAc,EACd,mBAAmB,EACnB,QAAQ,EACR,kBAA0B,EAC1B,QAAQ,GACT,EAAE,iCAAiC,CAAC,CAAC,CAAC,2CAyTtC"}
|
|
@@ -57,33 +57,79 @@ function concatPaginationResultItemsSorted(pagesByNum) {
|
|
|
57
57
|
* {@link PaginationResults.fromResponseBody}. Final `data` is pages concatenated in page order.
|
|
58
58
|
* In-flight requests use one {@link AbortController}: unmount or any failure aborts the rest.
|
|
59
59
|
*/
|
|
60
|
-
function RopeGeoPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.Method.GET, path, pathParams, queryParams, batchSize = 10, timeoutAfterSeconds, isOnline, children, }) {
|
|
60
|
+
function RopeGeoPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.Method.GET, path, pathParams, queryParams, batchSize = 10, timeoutAfterSeconds, isOnline, refreshOnReconnect = false, children, }) {
|
|
61
61
|
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
62
62
|
const [received, setReceived] = (0, react_1.useState)(0);
|
|
63
63
|
const [total, setTotal] = (0, react_1.useState)(null);
|
|
64
64
|
const [data, setData] = (0, react_1.useState)(null);
|
|
65
65
|
const [errors, setErrors] = (0, react_1.useState)(null);
|
|
66
66
|
const [timeoutCountdown, setTimeoutCountdown] = (0, react_1.useState)(null);
|
|
67
|
+
const [hasCommittedOnce, setHasCommittedOnce] = (0, react_1.useState)(false);
|
|
68
|
+
const errorsRef = (0, react_1.useRef)(errors);
|
|
69
|
+
const hasCommittedRef = (0, react_1.useRef)(hasCommittedOnce);
|
|
70
|
+
errorsRef.current = errors;
|
|
71
|
+
hasCommittedRef.current = hasCommittedOnce;
|
|
67
72
|
const pathParamsKey = JSON.stringify(pathParams ?? null);
|
|
68
73
|
const queryParamsKey = queryParams.toQueryString();
|
|
69
74
|
const effectiveBatch = Math.max(1, Math.floor(batchSize));
|
|
75
|
+
const requestKey = (0, react_1.useMemo)(() => `${service}|${method}|${path}|${pathParamsKey}|${queryParamsKey}|${effectiveBatch}|${timeoutAfterSeconds ?? ""}`, [service, method, path, pathParamsKey, queryParamsKey, effectiveBatch, timeoutAfterSeconds]);
|
|
76
|
+
const prevIsOnlineRef = (0, react_1.useRef)(undefined);
|
|
77
|
+
const lastRequestKeyRef = (0, react_1.useRef)("");
|
|
70
78
|
(0, react_1.useEffect)(() => {
|
|
71
|
-
|
|
79
|
+
const online = isOnline !== false;
|
|
80
|
+
const prevOnline = prevIsOnlineRef.current;
|
|
81
|
+
const reconnecting = prevOnline === false && online;
|
|
82
|
+
const keyChanged = lastRequestKeyRef.current !== requestKey;
|
|
83
|
+
if (!online) {
|
|
84
|
+
if (keyChanged) {
|
|
85
|
+
lastRequestKeyRef.current = requestKey;
|
|
86
|
+
setHasCommittedOnce(false);
|
|
87
|
+
setReceived(0);
|
|
88
|
+
setTotal(null);
|
|
89
|
+
setData(null);
|
|
90
|
+
setErrors(null);
|
|
91
|
+
}
|
|
92
|
+
prevIsOnlineRef.current = false;
|
|
72
93
|
setLoading(false);
|
|
73
94
|
setErrors(new Error(network_1.NO_NETWORK_MESSAGE));
|
|
74
95
|
setTimeoutCountdown(null);
|
|
75
96
|
return;
|
|
76
97
|
}
|
|
98
|
+
if (keyChanged) {
|
|
99
|
+
lastRequestKeyRef.current = requestKey;
|
|
100
|
+
setHasCommittedOnce(false);
|
|
101
|
+
setReceived(0);
|
|
102
|
+
setTotal(null);
|
|
103
|
+
setData(null);
|
|
104
|
+
setErrors(null);
|
|
105
|
+
}
|
|
106
|
+
if (!keyChanged && reconnecting) {
|
|
107
|
+
const onlyNoNetwork = errorsRef.current?.message === network_1.NO_NETWORK_MESSAGE;
|
|
108
|
+
if (hasCommittedRef.current && onlyNoNetwork && !refreshOnReconnect) {
|
|
109
|
+
setErrors(null);
|
|
110
|
+
setLoading(false);
|
|
111
|
+
prevIsOnlineRef.current = true;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
prevIsOnlineRef.current = true;
|
|
77
116
|
let cancelled = false;
|
|
78
117
|
const abortController = new AbortController();
|
|
79
118
|
const { signal } = abortController;
|
|
80
119
|
const timeoutMs = (0, network_1.resolveRequestTimeoutMs)(timeoutAfterSeconds);
|
|
81
120
|
setLoading(true);
|
|
82
|
-
setReceived(0);
|
|
83
|
-
setTotal(null);
|
|
84
|
-
setData(null);
|
|
85
121
|
setErrors(null);
|
|
86
122
|
setTimeoutCountdown(null);
|
|
123
|
+
const keepStaleDuringFetch = reconnecting &&
|
|
124
|
+
hasCommittedRef.current &&
|
|
125
|
+
errorsRef.current?.message === network_1.NO_NETWORK_MESSAGE &&
|
|
126
|
+
refreshOnReconnect;
|
|
127
|
+
if (!keyChanged && !keepStaleDuringFetch) {
|
|
128
|
+
setReceived(0);
|
|
129
|
+
setTotal(null);
|
|
130
|
+
setData(null);
|
|
131
|
+
setHasCommittedOnce(false);
|
|
132
|
+
}
|
|
87
133
|
const baseUrl = RopeGeoHttpRequest_1.SERVICE_BASE_URL[service];
|
|
88
134
|
const resolvedPath = resolvePath(path, pathParams);
|
|
89
135
|
const baseInit = {
|
|
@@ -158,7 +204,7 @@ function RopeGeoPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.M
|
|
|
158
204
|
const text = await res.text();
|
|
159
205
|
if (!res.ok) {
|
|
160
206
|
abortController.abort();
|
|
161
|
-
throw new Error(
|
|
207
|
+
throw new Error((0, network_1.formatHttpStatusMessage)(res.status, text || res.statusText));
|
|
162
208
|
}
|
|
163
209
|
if (text.length === 0) {
|
|
164
210
|
abortController.abort();
|
|
@@ -192,7 +238,7 @@ function RopeGeoPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.M
|
|
|
192
238
|
catch (err) {
|
|
193
239
|
if (pageNum !== 1 && merged != null && merged.consumeDidTimeout()) {
|
|
194
240
|
abortController.abort();
|
|
195
|
-
throw new Error(network_1.NETWORK_REQUEST_TIMED_OUT_MESSAGE);
|
|
241
|
+
throw new Error((0, network_1.formatNetworkRequestErrorMessage)(new Error(network_1.NETWORK_REQUEST_TIMED_OUT_MESSAGE)));
|
|
196
242
|
}
|
|
197
243
|
throw err;
|
|
198
244
|
}
|
|
@@ -216,6 +262,7 @@ function RopeGeoPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.M
|
|
|
216
262
|
return;
|
|
217
263
|
setData(concatPaginationResultItemsSorted(pagesByNum));
|
|
218
264
|
setErrors(null);
|
|
265
|
+
setHasCommittedOnce(true);
|
|
219
266
|
return;
|
|
220
267
|
}
|
|
221
268
|
const lastPage = Math.max(1, Math.ceil(totalCount / limit));
|
|
@@ -246,6 +293,7 @@ function RopeGeoPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.M
|
|
|
246
293
|
return;
|
|
247
294
|
setData(concatPaginationResultItemsSorted(pagesByNum));
|
|
248
295
|
setErrors(null);
|
|
296
|
+
setHasCommittedOnce(true);
|
|
249
297
|
}
|
|
250
298
|
catch (err) {
|
|
251
299
|
if (cancelled || (0, network_1.isAbortError)(err))
|
|
@@ -253,8 +301,9 @@ function RopeGeoPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.M
|
|
|
253
301
|
console.error("[RopeGeoPaginationHttpRequest] Request failed", {
|
|
254
302
|
error: err instanceof Error ? err.message : String(err),
|
|
255
303
|
});
|
|
256
|
-
setErrors(
|
|
304
|
+
setErrors(new Error((0, network_1.formatNetworkRequestErrorMessage)(err)));
|
|
257
305
|
setData(null);
|
|
306
|
+
setHasCommittedOnce(false);
|
|
258
307
|
}
|
|
259
308
|
finally {
|
|
260
309
|
clearActivePolicy();
|
|
@@ -272,13 +321,16 @@ function RopeGeoPaginationHttpRequest({ service, method = RopeGeoHttpRequest_1.M
|
|
|
272
321
|
path,
|
|
273
322
|
pathParamsKey,
|
|
274
323
|
queryParamsKey,
|
|
275
|
-
queryParams,
|
|
276
324
|
effectiveBatch,
|
|
277
325
|
timeoutAfterSeconds,
|
|
278
326
|
isOnline,
|
|
327
|
+
refreshOnReconnect,
|
|
328
|
+
requestKey,
|
|
279
329
|
]);
|
|
330
|
+
const refreshing = loading && hasCommittedOnce;
|
|
280
331
|
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children({
|
|
281
332
|
loading,
|
|
333
|
+
refreshing,
|
|
282
334
|
received,
|
|
283
335
|
total,
|
|
284
336
|
data,
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* Published as `ropegeo-common/helpers/network`. Prefer this entry over `ropegeo-common/helpers` in
|
|
4
4
|
* Metro/RN bundles so the full helpers barrel (S3 folder upload, etc.) is not resolved.
|
|
5
5
|
*/
|
|
6
|
-
export { NETWORK_REQUEST_TIMED_OUT_MESSAGE, NO_NETWORK_MESSAGE, installNetworkRequestPolicyTimers, isAbortError, isNetworkRequestTimeoutError, mergeParentSignalWithDeadline, resolveRequestTimeoutMs, type MergedDeadlineHandles, type NetworkRequestPolicyTimerCallbacks, } from "./networkRequestPolicy";
|
|
6
|
+
export { formatHttpStatusMessage, formatNetworkRequestErrorMessage, NETWORK_REQUEST_TIMED_OUT_MESSAGE, NO_NETWORK_MESSAGE, installNetworkRequestPolicyTimers, isAbortError, isNetworkRequestTimeoutError, mergeParentSignalWithDeadline, resolveRequestTimeoutMs, type MergedDeadlineHandles, type NetworkRequestPolicyTimerCallbacks, } from "./networkRequestPolicy";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/helpers/network/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,iCAAiC,EACjC,kBAAkB,EAClB,iCAAiC,EACjC,YAAY,EACZ,4BAA4B,EAC5B,6BAA6B,EAC7B,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,kCAAkC,GACxC,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/helpers/network/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,uBAAuB,EACvB,gCAAgC,EAChC,iCAAiC,EACjC,kBAAkB,EAClB,iCAAiC,EACjC,YAAY,EACZ,4BAA4B,EAC5B,6BAA6B,EAC7B,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,kCAAkC,GACxC,MAAM,wBAAwB,CAAC"}
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* Metro/RN bundles so the full helpers barrel (S3 folder upload, etc.) is not resolved.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.resolveRequestTimeoutMs = exports.mergeParentSignalWithDeadline = exports.isNetworkRequestTimeoutError = exports.isAbortError = exports.installNetworkRequestPolicyTimers = exports.NO_NETWORK_MESSAGE = exports.NETWORK_REQUEST_TIMED_OUT_MESSAGE = void 0;
|
|
8
|
+
exports.resolveRequestTimeoutMs = exports.mergeParentSignalWithDeadline = exports.isNetworkRequestTimeoutError = exports.isAbortError = exports.installNetworkRequestPolicyTimers = exports.NO_NETWORK_MESSAGE = exports.NETWORK_REQUEST_TIMED_OUT_MESSAGE = exports.formatNetworkRequestErrorMessage = exports.formatHttpStatusMessage = void 0;
|
|
9
9
|
var networkRequestPolicy_1 = require("./networkRequestPolicy");
|
|
10
|
+
Object.defineProperty(exports, "formatHttpStatusMessage", { enumerable: true, get: function () { return networkRequestPolicy_1.formatHttpStatusMessage; } });
|
|
11
|
+
Object.defineProperty(exports, "formatNetworkRequestErrorMessage", { enumerable: true, get: function () { return networkRequestPolicy_1.formatNetworkRequestErrorMessage; } });
|
|
10
12
|
Object.defineProperty(exports, "NETWORK_REQUEST_TIMED_OUT_MESSAGE", { enumerable: true, get: function () { return networkRequestPolicy_1.NETWORK_REQUEST_TIMED_OUT_MESSAGE; } });
|
|
11
13
|
Object.defineProperty(exports, "NO_NETWORK_MESSAGE", { enumerable: true, get: function () { return networkRequestPolicy_1.NO_NETWORK_MESSAGE; } });
|
|
12
14
|
Object.defineProperty(exports, "installNetworkRequestPolicyTimers", { enumerable: true, get: function () { return networkRequestPolicy_1.installNetworkRequestPolicyTimers; } });
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
export declare const NETWORK_REQUEST_TIMED_OUT_MESSAGE = "Network request timed out";
|
|
4
4
|
/** Use this exact message for client-side offline gating and RN fetch failures treated as offline. */
|
|
5
5
|
export declare const NO_NETWORK_MESSAGE = "No network connection";
|
|
6
|
+
/**
|
|
7
|
+
* Formats HTTP response failures as user-facing copy (e.g. "500 Internal Server Error").
|
|
8
|
+
* `detail` can be a response body snippet or `statusText`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatHttpStatusMessage(status: number, detail?: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Normalizes raw network/request errors into stable user-facing copy.
|
|
13
|
+
* Keeps NO_NETWORK_MESSAGE unchanged for offline gating checks.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatNetworkRequestErrorMessage(error: unknown): string;
|
|
6
16
|
export declare function isNetworkRequestTimeoutError(e: unknown): boolean;
|
|
7
17
|
export declare function isAbortError(e: unknown): boolean;
|
|
8
18
|
/** Milliseconds for `timeoutAfterSeconds` on request components; `null` when timeout is disabled. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"networkRequestPolicy.d.ts","sourceRoot":"","sources":["../../../src/helpers/network/networkRequestPolicy.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,8EAA8E;AAC9E,eAAO,MAAM,iCAAiC,8BAA8B,CAAC;AAE7E,sGAAsG;AACtG,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"networkRequestPolicy.d.ts","sourceRoot":"","sources":["../../../src/helpers/network/networkRequestPolicy.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,8EAA8E;AAC9E,eAAO,MAAM,iCAAiC,8BAA8B,CAAC;AAE7E,sGAAsG;AACtG,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAoB1D;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAO/E;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAmBvE;AAED,wBAAgB,4BAA4B,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAEhE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAUhD;AAED,qGAAqG;AACrG,wBAAgB,uBAAuB,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASnF;AAED,MAAM,MAAM,kCAAkC,GAAG;IAC/C,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,kBAAkB,EAAE,CAAC,gBAAgB,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,uBAAuB,EAAE,MAAM,IAAI,CAAC;IACpC,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,kBAAkB,EAAE,MAAM,EAC1B,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,kCAAkC,GAC5C,MAAM,IAAI,CAuCZ;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,iBAAiB,EAAE,MAAM,OAAO,CAAC;CAClC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,YAAY,EAAE,WAAW,EACzB,UAAU,EAAE,MAAM,GACjB,qBAAqB,CAqCvB"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
/** Network helpers for optional request deadlines and timeout countdowns. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.NO_NETWORK_MESSAGE = exports.NETWORK_REQUEST_TIMED_OUT_MESSAGE = void 0;
|
|
5
|
+
exports.formatHttpStatusMessage = formatHttpStatusMessage;
|
|
6
|
+
exports.formatNetworkRequestErrorMessage = formatNetworkRequestErrorMessage;
|
|
5
7
|
exports.isNetworkRequestTimeoutError = isNetworkRequestTimeoutError;
|
|
6
8
|
exports.isAbortError = isAbortError;
|
|
7
9
|
exports.resolveRequestTimeoutMs = resolveRequestTimeoutMs;
|
|
@@ -11,6 +13,59 @@ exports.mergeParentSignalWithDeadline = mergeParentSignalWithDeadline;
|
|
|
11
13
|
exports.NETWORK_REQUEST_TIMED_OUT_MESSAGE = "Network request timed out";
|
|
12
14
|
/** Use this exact message for client-side offline gating and RN fetch failures treated as offline. */
|
|
13
15
|
exports.NO_NETWORK_MESSAGE = "No network connection";
|
|
16
|
+
const HTTP_STATUS_TEXT = {
|
|
17
|
+
400: "Bad Request",
|
|
18
|
+
401: "Unauthorized",
|
|
19
|
+
403: "Forbidden",
|
|
20
|
+
404: "Not Found",
|
|
21
|
+
408: "Request Timeout",
|
|
22
|
+
409: "Conflict",
|
|
23
|
+
429: "Too Many Requests",
|
|
24
|
+
500: "Internal Server Error",
|
|
25
|
+
502: "Bad Gateway",
|
|
26
|
+
503: "Service Unavailable",
|
|
27
|
+
504: "Gateway Timeout",
|
|
28
|
+
};
|
|
29
|
+
function firstLineOrEmpty(value) {
|
|
30
|
+
return (value.split("\n")[0] ?? "").trim();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Formats HTTP response failures as user-facing copy (e.g. "500 Internal Server Error").
|
|
34
|
+
* `detail` can be a response body snippet or `statusText`.
|
|
35
|
+
*/
|
|
36
|
+
function formatHttpStatusMessage(status, detail) {
|
|
37
|
+
const trimmed = (detail ?? "").trim();
|
|
38
|
+
if (trimmed !== "") {
|
|
39
|
+
const line = firstLineOrEmpty(trimmed);
|
|
40
|
+
return line === "" ? String(status) : `${status} ${line}`;
|
|
41
|
+
}
|
|
42
|
+
return `${status} ${HTTP_STATUS_TEXT[status] ?? "HTTP Error"}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Normalizes raw network/request errors into stable user-facing copy.
|
|
46
|
+
* Keeps NO_NETWORK_MESSAGE unchanged for offline gating checks.
|
|
47
|
+
*/
|
|
48
|
+
function formatNetworkRequestErrorMessage(error) {
|
|
49
|
+
const raw = error instanceof Error
|
|
50
|
+
? error.message
|
|
51
|
+
: typeof error === "string"
|
|
52
|
+
? error
|
|
53
|
+
: String(error ?? "");
|
|
54
|
+
const msg = raw.trim();
|
|
55
|
+
if (msg === "")
|
|
56
|
+
return "Request failed";
|
|
57
|
+
if (msg === exports.NO_NETWORK_MESSAGE)
|
|
58
|
+
return exports.NO_NETWORK_MESSAGE;
|
|
59
|
+
if (msg === exports.NETWORK_REQUEST_TIMED_OUT_MESSAGE)
|
|
60
|
+
return exports.NETWORK_REQUEST_TIMED_OUT_MESSAGE;
|
|
61
|
+
const http = /^HTTP\s+(\d{3})(?::\s*(.*))?$/i.exec(msg);
|
|
62
|
+
if (http != null) {
|
|
63
|
+
const code = Number(http[1]);
|
|
64
|
+
const detail = (http[2] ?? "").trim();
|
|
65
|
+
return formatHttpStatusMessage(code, detail);
|
|
66
|
+
}
|
|
67
|
+
return msg;
|
|
68
|
+
}
|
|
14
69
|
function isNetworkRequestTimeoutError(e) {
|
|
15
70
|
return e instanceof Error && e.message === exports.NETWORK_REQUEST_TIMED_OUT_MESSAGE;
|
|
16
71
|
}
|