@molecule/app-http 1.1.0 → 1.1.2
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 +192 -136
- package/dist/auth-gate.d.ts +56 -0
- package/dist/auth-gate.d.ts.map +1 -0
- package/dist/auth-gate.js +100 -0
- package/dist/auth-gate.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +5 -0
- package/dist/provider.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-16T22:03:15.467Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/app-http
|
|
@@ -49,29 +49,6 @@ npm install @molecule/app-http @molecule/app-bond @molecule/app-i18n @molecule/a
|
|
|
49
49
|
|
|
50
50
|
### Interfaces
|
|
51
51
|
|
|
52
|
-
#### `FullRequestConfig`
|
|
53
|
-
|
|
54
|
-
Full request configuration including method and URL.
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
interface FullRequestConfig extends RequestConfig {
|
|
58
|
-
/**
|
|
59
|
-
* HTTP method.
|
|
60
|
-
*/
|
|
61
|
-
method: HttpMethod
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Request URL (can be relative or absolute).
|
|
65
|
-
*/
|
|
66
|
-
url: string
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Request body data.
|
|
70
|
-
*/
|
|
71
|
-
data?: unknown
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
52
|
#### `HttpClient`
|
|
76
53
|
|
|
77
54
|
HTTP client interface.
|
|
@@ -192,39 +169,6 @@ interface HttpClientConfig {
|
|
|
192
169
|
}
|
|
193
170
|
```
|
|
194
171
|
|
|
195
|
-
#### `HttpResponse`
|
|
196
|
-
|
|
197
|
-
Parsed HTTP response with status code, headers, and typed body data.
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
interface HttpResponse<T = unknown> {
|
|
201
|
-
/**
|
|
202
|
-
* Response data.
|
|
203
|
-
*/
|
|
204
|
-
data: T
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* HTTP status code.
|
|
208
|
-
*/
|
|
209
|
-
status: number
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* HTTP status text.
|
|
213
|
-
*/
|
|
214
|
-
statusText: string
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Response headers.
|
|
218
|
-
*/
|
|
219
|
-
headers: Record<string, string>
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Original request config.
|
|
223
|
-
*/
|
|
224
|
-
config: FullRequestConfig
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
172
|
#### `RequestConfig`
|
|
229
173
|
|
|
230
174
|
HTTP request options (headers, query params, timeout, credentials, response type, abort signal).
|
|
@@ -273,25 +217,64 @@ interface RequestConfig {
|
|
|
273
217
|
}
|
|
274
218
|
```
|
|
275
219
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
#### `ErrorInterceptor`
|
|
220
|
+
#### `FullRequestConfig`
|
|
279
221
|
|
|
280
|
-
|
|
281
|
-
Throwing from this interceptor propagates the error to the caller.
|
|
222
|
+
Full request configuration including method and URL.
|
|
282
223
|
|
|
283
224
|
```typescript
|
|
284
|
-
|
|
225
|
+
interface FullRequestConfig extends RequestConfig {
|
|
226
|
+
/**
|
|
227
|
+
* HTTP method.
|
|
228
|
+
*/
|
|
229
|
+
method: HttpMethod
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Request URL (can be relative or absolute).
|
|
233
|
+
*/
|
|
234
|
+
url: string
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Request body data.
|
|
238
|
+
*/
|
|
239
|
+
data?: unknown
|
|
240
|
+
}
|
|
285
241
|
```
|
|
286
242
|
|
|
287
|
-
#### `
|
|
243
|
+
#### `HttpResponse`
|
|
288
244
|
|
|
289
|
-
HTTP
|
|
245
|
+
Parsed HTTP response with status code, headers, and typed body data.
|
|
290
246
|
|
|
291
247
|
```typescript
|
|
292
|
-
|
|
248
|
+
interface HttpResponse<T = unknown> {
|
|
249
|
+
/**
|
|
250
|
+
* Response data.
|
|
251
|
+
*/
|
|
252
|
+
data: T
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* HTTP status code.
|
|
256
|
+
*/
|
|
257
|
+
status: number
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* HTTP status text.
|
|
261
|
+
*/
|
|
262
|
+
statusText: string
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Response headers.
|
|
266
|
+
*/
|
|
267
|
+
headers: Record<string, string>
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Original request config.
|
|
271
|
+
*/
|
|
272
|
+
config: FullRequestConfig
|
|
273
|
+
}
|
|
293
274
|
```
|
|
294
275
|
|
|
276
|
+
### Types
|
|
277
|
+
|
|
295
278
|
#### `RequestInterceptor`
|
|
296
279
|
|
|
297
280
|
Intercepts outgoing requests to modify headers, URL, or body
|
|
@@ -303,6 +286,8 @@ type RequestInterceptor = (
|
|
|
303
286
|
) => FullRequestConfig | Promise<FullRequestConfig>
|
|
304
287
|
```
|
|
305
288
|
|
|
289
|
+
- `config` — The full request configuration to transform.
|
|
290
|
+
|
|
306
291
|
#### `ResponseInterceptor`
|
|
307
292
|
|
|
308
293
|
Intercepts incoming responses to transform data, check status,
|
|
@@ -314,14 +299,78 @@ type ResponseInterceptor<T = unknown> = (
|
|
|
314
299
|
) => HttpResponse<T> | Promise<HttpResponse<T>>
|
|
315
300
|
```
|
|
316
301
|
|
|
302
|
+
#### `ErrorInterceptor`
|
|
303
|
+
|
|
304
|
+
Intercepts HTTP errors to transform, retry, or rethrow them.
|
|
305
|
+
Throwing from this interceptor propagates the error to the caller.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
type ErrorInterceptor = (error: HttpError) => HttpError | Promise<HttpError> | never
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### `HttpMethod`
|
|
312
|
+
|
|
313
|
+
HTTP request method.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'
|
|
317
|
+
```
|
|
318
|
+
|
|
317
319
|
### Classes
|
|
318
320
|
|
|
319
321
|
#### `HttpError`
|
|
320
322
|
|
|
321
323
|
HTTP error with response details.
|
|
322
324
|
|
|
325
|
+
```typescript
|
|
326
|
+
class HttpError extends Error {
|
|
327
|
+
constructor(
|
|
328
|
+
message: string,
|
|
329
|
+
public status: number,
|
|
330
|
+
public response?: HttpResponse,
|
|
331
|
+
public config?: FullRequestConfig,
|
|
332
|
+
public errorKey?: string,
|
|
333
|
+
) {
|
|
334
|
+
super(message)
|
|
335
|
+
this.name = 'HttpError'
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
323
340
|
### Functions
|
|
324
341
|
|
|
342
|
+
#### `setAuthGate(next)`
|
|
343
|
+
|
|
344
|
+
Registers the gate every HTTP request awaits before it is sent, or clears it
|
|
345
|
+
with `null`.
|
|
346
|
+
|
|
347
|
+
Pass a function that resolves once the session restore has settled — with the
|
|
348
|
+
template auth that is `() => authClient.whenRestored()`, which resolves
|
|
349
|
+
immediately when no restore is in flight, so registering it never makes a
|
|
350
|
+
request wait longer than the restore it was about to race anyway.
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
function setAuthGate(next: (() => Promise<void>) | null): void
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- `next` — The gate, or `null` to clear a previously registered one.
|
|
357
|
+
|
|
358
|
+
#### `awaitAuthGate()`
|
|
359
|
+
|
|
360
|
+
Awaits the registered gate, if any. Every HTTP provider calls this before it
|
|
361
|
+
sends a request; callers never need to.
|
|
362
|
+
|
|
363
|
+
A gate that rejects (a restore that failed) is logged and cleared of consequence: the request
|
|
364
|
+
is sent rather than held forever, because a dead restore must not block every request for the
|
|
365
|
+
life of the page. The same holds for a gate that never settles — the wait is bounded by
|
|
366
|
+
{@link GATE_TIMEOUT_MS}.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
function awaitAuthGate(): Promise<void>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Returns:** A promise that settles when the gate has released the request.
|
|
373
|
+
|
|
325
374
|
#### `createFetchClient(config)`
|
|
326
375
|
|
|
327
376
|
Creates a fetch-based HTTP client using the native Fetch API.
|
|
@@ -338,31 +387,71 @@ function createFetchClient(config?: HttpClientConfig): HttpClient
|
|
|
338
387
|
|
|
339
388
|
**Returns:** A fully configured `HttpClient` instance.
|
|
340
389
|
|
|
341
|
-
#### `
|
|
390
|
+
#### `unwrapList(res)`
|
|
342
391
|
|
|
343
|
-
|
|
392
|
+
Normalize an unknown response body into a typed array.
|
|
393
|
+
|
|
394
|
+
Accepts:
|
|
395
|
+
|
|
396
|
+
- a bare array → returned as-is (cast)
|
|
397
|
+
- `{ data: T[] }` envelope → the inner array
|
|
398
|
+
- `HttpResponse<T[]>` (i.e. `{ data: T[], status, ... }`) → the inner array
|
|
399
|
+
- `HttpResponse<{ data: T[] }>` (the response of an envelope-returning
|
|
400
|
+
endpoint as it arrives from `@molecule/app-http`'s `HttpClient`) → the
|
|
401
|
+
doubly-nested inner array
|
|
402
|
+
- `HttpResponse<{ data: T[], total, limit, offset }>` (a RICH pagination
|
|
403
|
+
envelope from a pre-built `@molecule/api-resource-*` list endpoint) → the
|
|
404
|
+
inner `data` array (the numeric `total`/`limit`/`offset` make the shape
|
|
405
|
+
unambiguous, so callers can pass the whole HttpResponse and still get the rows)
|
|
406
|
+
- anything else → `[]`
|
|
407
|
+
|
|
408
|
+
Callers commonly pass either the raw JSON body (e.g. from `fetch().then(r =>
|
|
409
|
+
r.json())`) or the `HttpResponse` returned by `useHttpClient().get(...)`.
|
|
410
|
+
Both shapes are handled here so pages don't have to remember to call
|
|
411
|
+
`unwrapList(res.data)` vs `unwrapList(res)`.
|
|
344
412
|
|
|
345
413
|
```typescript
|
|
346
|
-
function
|
|
414
|
+
function unwrapList(res: unknown): T[]
|
|
347
415
|
```
|
|
348
416
|
|
|
349
|
-
- `
|
|
350
|
-
- `config` — Optional request configuration.
|
|
417
|
+
- `res` — Raw response body OR an `HttpResponse` envelope from `@molecule/app-http`.
|
|
351
418
|
|
|
352
|
-
**Returns:**
|
|
419
|
+
**Returns:** A typed array `T[]`; never `null`/`undefined`.
|
|
353
420
|
|
|
354
|
-
#### `
|
|
421
|
+
#### `unwrapSingle(res)`
|
|
355
422
|
|
|
356
|
-
|
|
423
|
+
Normalize an unknown response body into a single typed resource.
|
|
424
|
+
|
|
425
|
+
Accepts:
|
|
426
|
+
|
|
427
|
+
- a non-empty plain object → returned as-is (cast)
|
|
428
|
+
- `{ data: T }` envelope → the inner value
|
|
429
|
+
- `{ data: null }`, `{ data: undefined }`, `{ data: [] }`, or
|
|
430
|
+
`{ data: {} }` (mock-server's no-match shape) → `null`
|
|
431
|
+
- an empty object `{}` → `null`
|
|
432
|
+
- arrays, primitives, `null`, `undefined` → `null`
|
|
433
|
+
|
|
434
|
+
The "envelope contains an array → null" branch handles the case
|
|
435
|
+
where the mock server returns `[]` for unmatched endpoints but the
|
|
436
|
+
caller expects a single resource.
|
|
357
437
|
|
|
358
438
|
```typescript
|
|
359
|
-
function
|
|
439
|
+
function unwrapSingle(res: unknown): T | null
|
|
360
440
|
```
|
|
361
441
|
|
|
362
|
-
- `
|
|
363
|
-
- `config` — Optional request configuration (headers, params, timeout).
|
|
442
|
+
- `res` — Raw response body (e.g. `HttpResponse.data` from `@molecule/app-http`).
|
|
364
443
|
|
|
365
|
-
**Returns:** The
|
|
444
|
+
**Returns:** The typed resource `T`, or `null` when the response shape indicates "no resource" (including the various empty envelopes above).
|
|
445
|
+
|
|
446
|
+
#### `setClient(client)`
|
|
447
|
+
|
|
448
|
+
Registers an HTTP client as the active singleton.
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
function setClient(client: HttpClient): void
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
- `client` — The HTTP client implementation to bond.
|
|
366
455
|
|
|
367
456
|
#### `getClient()`
|
|
368
457
|
|
|
@@ -375,17 +464,16 @@ function getClient(): HttpClient
|
|
|
375
464
|
|
|
376
465
|
**Returns:** The active HTTP client instance.
|
|
377
466
|
|
|
378
|
-
#### `
|
|
467
|
+
#### `get(url, config)`
|
|
379
468
|
|
|
380
|
-
Makes a
|
|
469
|
+
Makes a GET request using the bonded HTTP client.
|
|
381
470
|
|
|
382
471
|
```typescript
|
|
383
|
-
function
|
|
472
|
+
function get(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
|
|
384
473
|
```
|
|
385
474
|
|
|
386
|
-
- `url` — The request URL.
|
|
387
|
-
- `
|
|
388
|
-
- `config` — Optional request configuration.
|
|
475
|
+
- `url` — The request URL (relative to baseURL if configured).
|
|
476
|
+
- `config` — Optional request configuration (headers, params, timeout).
|
|
389
477
|
|
|
390
478
|
**Returns:** The HTTP response with typed data.
|
|
391
479
|
|
|
@@ -417,71 +505,32 @@ function put(url: string, data?: unknown, config?: RequestConfig): Promise<HttpR
|
|
|
417
505
|
|
|
418
506
|
**Returns:** The HTTP response with typed data.
|
|
419
507
|
|
|
420
|
-
#### `
|
|
421
|
-
|
|
422
|
-
Registers an HTTP client as the active singleton.
|
|
423
|
-
|
|
424
|
-
```typescript
|
|
425
|
-
function setClient(client: HttpClient): void
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
- `client` — The HTTP client implementation to bond.
|
|
429
|
-
|
|
430
|
-
#### `unwrapList(res)`
|
|
431
|
-
|
|
432
|
-
Normalize an unknown response body into a typed array.
|
|
433
|
-
|
|
434
|
-
Accepts:
|
|
435
|
-
|
|
436
|
-
- a bare array → returned as-is (cast)
|
|
437
|
-
- `{ data: T[] }` envelope → the inner array
|
|
438
|
-
- `HttpResponse<T[]>` (i.e. `{ data: T[], status, ... }`) → the inner array
|
|
439
|
-
- `HttpResponse<{ data: T[] }>` (the response of an envelope-returning
|
|
440
|
-
endpoint as it arrives from `@molecule/app-http`'s `HttpClient`) → the
|
|
441
|
-
doubly-nested inner array
|
|
442
|
-
- `HttpResponse<{ data: T[], total, limit, offset }>` (a RICH pagination
|
|
443
|
-
envelope from a pre-built `@molecule/api-resource-*` list endpoint) → the
|
|
444
|
-
inner `data` array (the numeric `total`/`limit`/`offset` make the shape
|
|
445
|
-
unambiguous, so callers can pass the whole HttpResponse and still get the rows)
|
|
446
|
-
- anything else → `[]`
|
|
508
|
+
#### `patch(url, data, config)`
|
|
447
509
|
|
|
448
|
-
|
|
449
|
-
r.json())`) or the `HttpResponse` returned by `useHttpClient().get(...)`.
|
|
450
|
-
Both shapes are handled here so pages don't have to remember to call
|
|
451
|
-
`unwrapList(res.data)` vs `unwrapList(res)`.
|
|
510
|
+
Makes a PATCH request using the bonded HTTP client.
|
|
452
511
|
|
|
453
512
|
```typescript
|
|
454
|
-
function
|
|
513
|
+
function patch(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>
|
|
455
514
|
```
|
|
456
515
|
|
|
457
|
-
- `
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
#### `unwrapSingle(res)`
|
|
462
|
-
|
|
463
|
-
Normalize an unknown response body into a single typed resource.
|
|
516
|
+
- `url` — The request URL.
|
|
517
|
+
- `data` — The request body (partial update).
|
|
518
|
+
- `config` — Optional request configuration.
|
|
464
519
|
|
|
465
|
-
|
|
520
|
+
**Returns:** The HTTP response with typed data.
|
|
466
521
|
|
|
467
|
-
|
|
468
|
-
- `{ data: T }` envelope → the inner value
|
|
469
|
-
- `{ data: null }`, `{ data: undefined }`, `{ data: [] }`, or
|
|
470
|
-
`{ data: {} }` (mock-server's no-match shape) → `null`
|
|
471
|
-
- an empty object `{}` → `null`
|
|
472
|
-
- arrays, primitives, `null`, `undefined` → `null`
|
|
522
|
+
#### `del(url, config)`
|
|
473
523
|
|
|
474
|
-
|
|
475
|
-
where the mock server returns `[]` for unmatched endpoints but the
|
|
476
|
-
caller expects a single resource.
|
|
524
|
+
Makes a DELETE request using the bonded HTTP client.
|
|
477
525
|
|
|
478
526
|
```typescript
|
|
479
|
-
function
|
|
527
|
+
function del(url: string, config?: RequestConfig): Promise<HttpResponse<T>>
|
|
480
528
|
```
|
|
481
529
|
|
|
482
|
-
- `
|
|
530
|
+
- `url` — The request URL.
|
|
531
|
+
- `config` — Optional request configuration.
|
|
483
532
|
|
|
484
|
-
**Returns:** The
|
|
533
|
+
**Returns:** The HTTP response with typed data.
|
|
485
534
|
|
|
486
535
|
### Constants
|
|
487
536
|
|
|
@@ -540,6 +589,13 @@ resource).
|
|
|
540
589
|
An API that wants another scheme than `Bearer` (`Authorization: Token <jwt>`) sets
|
|
541
590
|
`authScheme: 'Token'` in the client config; do not hand-write a fetch wrapper for it.
|
|
542
591
|
|
|
592
|
+
A request made while the session is still being restored WAITS for that restore, so a page
|
|
593
|
+
that fetches viewer-dependent data on mount (a profile's `following` flag, a feed, a
|
|
594
|
+
`favorited` state) cannot go out unauthenticated and answer for an anonymous viewer. The auth
|
|
595
|
+
bond registers that wait with `setAuthGate`; apps that bond auth through
|
|
596
|
+
`@molecule/app-bonds-default-react` get it without doing anything. An app with its own session
|
|
597
|
+
module registers its own: `setAuthGate(() => session.whenRestored())`.
|
|
598
|
+
|
|
543
599
|
## Translations
|
|
544
600
|
|
|
545
601
|
Translation strings are provided by `@molecule/app-locales-http`.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one gate every HTTP request awaits before it is sent.
|
|
3
|
+
*
|
|
4
|
+
* A page that fetches viewer-dependent data on mount (`GET /profiles/:user`,
|
|
5
|
+
* a feed, a "favorited" flag) can fire BEFORE the stored session has been
|
|
6
|
+
* restored, so the request leaves with no `Authorization` header and the server
|
|
7
|
+
* answers for an anonymous viewer — `following: false` on a user you follow.
|
|
8
|
+
* It is invisible on localhost, where the restore wins the race against the
|
|
9
|
+
* first effect, and appears over the network as soon as the restore takes one
|
|
10
|
+
* round trip: X1 xr-molecule-flash-m95 spent ~25 min chasing this as "database
|
|
11
|
+
* leftovers" on the deployed URL.
|
|
12
|
+
*
|
|
13
|
+
* So the auth bond registers a gate here (`setAuthGate`) and every request
|
|
14
|
+
* waits for it (`awaitAuthGate`) — the race is impossible to write by hand.
|
|
15
|
+
* `@molecule/app-bonds-default-react`'s auth helpers register the gate for
|
|
16
|
+
* apps that bond auth, so an app does not have to get it right itself.
|
|
17
|
+
*
|
|
18
|
+
* The gate is module-level, not per client, on purpose: the client an app ends
|
|
19
|
+
* up using is often created AFTER the auth bond ran (`setupAuthDefault()` then
|
|
20
|
+
* `setupHttpDefault()`), and a gate installed on the throwaway client the auth
|
|
21
|
+
* bond saw would be silently lost.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Registers the gate every HTTP request awaits before it is sent, or clears it
|
|
27
|
+
* with `null`.
|
|
28
|
+
*
|
|
29
|
+
* Pass a function that resolves once the session restore has settled — with the
|
|
30
|
+
* template auth that is `() => authClient.whenRestored()`, which resolves
|
|
31
|
+
* immediately when no restore is in flight, so registering it never makes a
|
|
32
|
+
* request wait longer than the restore it was about to race anyway.
|
|
33
|
+
*
|
|
34
|
+
* @param next - The gate, or `null` to clear a previously registered one.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { setAuthGate } from '@molecule/app-http'
|
|
39
|
+
*
|
|
40
|
+
* setAuthGate(() => authClient.whenRestored())
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare const setAuthGate: (next: (() => Promise<void>) | null) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Awaits the registered gate, if any. Every HTTP provider calls this before it
|
|
46
|
+
* sends a request; callers never need to.
|
|
47
|
+
*
|
|
48
|
+
* A gate that rejects (a restore that failed) is logged and cleared of consequence: the request
|
|
49
|
+
* is sent rather than held forever, because a dead restore must not block every request for the
|
|
50
|
+
* life of the page. The same holds for a gate that never settles — the wait is bounded by
|
|
51
|
+
* {@link GATE_TIMEOUT_MS}.
|
|
52
|
+
*
|
|
53
|
+
* @returns A promise that settles when the gate has released the request.
|
|
54
|
+
*/
|
|
55
|
+
export declare const awaitAuthGate: () => Promise<void>;
|
|
56
|
+
//# sourceMappingURL=auth-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-gate.d.ts","sourceRoot":"","sources":["../src/auth-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAoBH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAG,IAEhE,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,QAAa,OAAO,CAAC,IAAI,CA2BlD,CAAA"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one gate every HTTP request awaits before it is sent.
|
|
3
|
+
*
|
|
4
|
+
* A page that fetches viewer-dependent data on mount (`GET /profiles/:user`,
|
|
5
|
+
* a feed, a "favorited" flag) can fire BEFORE the stored session has been
|
|
6
|
+
* restored, so the request leaves with no `Authorization` header and the server
|
|
7
|
+
* answers for an anonymous viewer — `following: false` on a user you follow.
|
|
8
|
+
* It is invisible on localhost, where the restore wins the race against the
|
|
9
|
+
* first effect, and appears over the network as soon as the restore takes one
|
|
10
|
+
* round trip: X1 xr-molecule-flash-m95 spent ~25 min chasing this as "database
|
|
11
|
+
* leftovers" on the deployed URL.
|
|
12
|
+
*
|
|
13
|
+
* So the auth bond registers a gate here (`setAuthGate`) and every request
|
|
14
|
+
* waits for it (`awaitAuthGate`) — the race is impossible to write by hand.
|
|
15
|
+
* `@molecule/app-bonds-default-react`'s auth helpers register the gate for
|
|
16
|
+
* apps that bond auth, so an app does not have to get it right itself.
|
|
17
|
+
*
|
|
18
|
+
* The gate is module-level, not per client, on purpose: the client an app ends
|
|
19
|
+
* up using is often created AFTER the auth bond ran (`setupAuthDefault()` then
|
|
20
|
+
* `setupHttpDefault()`), and a gate installed on the throwaway client the auth
|
|
21
|
+
* bond saw would be silently lost.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import { getLogger } from '@molecule/app-logger';
|
|
26
|
+
const logger = getLogger('http');
|
|
27
|
+
/** The registered gate, or null when no auth bond wired one (anonymous apps). */
|
|
28
|
+
let gate = null;
|
|
29
|
+
/**
|
|
30
|
+
* How long a request may be held by the gate before it is sent anyway.
|
|
31
|
+
*
|
|
32
|
+
* The gate waits for the session restore, and a restore that never settles — a stalled
|
|
33
|
+
* connection, an endpoint that hangs without answering — must not hold back every request for
|
|
34
|
+
* the life of the page: an unauthenticated request that arrives is a better failure than an app
|
|
35
|
+
* that never loads. A settled restore is what the deployed race needs (one round trip, hundreds
|
|
36
|
+
* of milliseconds), so this bound is far above any wait it is meant to cause.
|
|
37
|
+
*/
|
|
38
|
+
const GATE_TIMEOUT_MS = 10_000;
|
|
39
|
+
/**
|
|
40
|
+
* Registers the gate every HTTP request awaits before it is sent, or clears it
|
|
41
|
+
* with `null`.
|
|
42
|
+
*
|
|
43
|
+
* Pass a function that resolves once the session restore has settled — with the
|
|
44
|
+
* template auth that is `() => authClient.whenRestored()`, which resolves
|
|
45
|
+
* immediately when no restore is in flight, so registering it never makes a
|
|
46
|
+
* request wait longer than the restore it was about to race anyway.
|
|
47
|
+
*
|
|
48
|
+
* @param next - The gate, or `null` to clear a previously registered one.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { setAuthGate } from '@molecule/app-http'
|
|
53
|
+
*
|
|
54
|
+
* setAuthGate(() => authClient.whenRestored())
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export const setAuthGate = (next) => {
|
|
58
|
+
gate = next;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Awaits the registered gate, if any. Every HTTP provider calls this before it
|
|
62
|
+
* sends a request; callers never need to.
|
|
63
|
+
*
|
|
64
|
+
* A gate that rejects (a restore that failed) is logged and cleared of consequence: the request
|
|
65
|
+
* is sent rather than held forever, because a dead restore must not block every request for the
|
|
66
|
+
* life of the page. The same holds for a gate that never settles — the wait is bounded by
|
|
67
|
+
* {@link GATE_TIMEOUT_MS}.
|
|
68
|
+
*
|
|
69
|
+
* @returns A promise that settles when the gate has released the request.
|
|
70
|
+
*/
|
|
71
|
+
export const awaitAuthGate = async () => {
|
|
72
|
+
const current = gate;
|
|
73
|
+
if (!current)
|
|
74
|
+
return;
|
|
75
|
+
let timer;
|
|
76
|
+
let timedOut = false;
|
|
77
|
+
try {
|
|
78
|
+
await Promise.race([
|
|
79
|
+
Promise.resolve(current()).catch((error) => {
|
|
80
|
+
logger.warn('The auth gate rejected; sending the request without waiting for it', {
|
|
81
|
+
error,
|
|
82
|
+
});
|
|
83
|
+
}),
|
|
84
|
+
new Promise((resolve) => {
|
|
85
|
+
timer = setTimeout(() => {
|
|
86
|
+
timedOut = true;
|
|
87
|
+
resolve();
|
|
88
|
+
}, GATE_TIMEOUT_MS);
|
|
89
|
+
}),
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
if (timer)
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
}
|
|
96
|
+
if (timedOut) {
|
|
97
|
+
logger.warn(`The auth gate has not settled after ${GATE_TIMEOUT_MS}ms; sending the request unauthenticated`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=auth-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-gate.js","sourceRoot":"","sources":["../src/auth-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAEhD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;AAEhC,iFAAiF;AACjF,IAAI,IAAI,GAAiC,IAAI,CAAA;AAE7C;;;;;;;;GAQG;AACH,MAAM,eAAe,GAAG,MAAM,CAAA;AAE9B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAkC,EAAQ,EAAE;IACtE,IAAI,GAAG,IAAI,CAAA;AACb,CAAC,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,IAAmB,EAAE;IACrD,MAAM,OAAO,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,OAAO;QAAE,OAAM;IACpB,IAAI,KAAgD,CAAA;IACpD,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,IAAI,CAAC;YACjB,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACzC,MAAM,CAAC,IAAI,CAAC,oEAAoE,EAAE;oBAChF,KAAK;iBACN,CAAC,CAAA;YACJ,CAAC,CAAC;YACF,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAC5B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBACtB,QAAQ,GAAG,IAAI,CAAA;oBACf,OAAO,EAAE,CAAA;gBACX,CAAC,EAAE,eAAe,CAAC,CAAA;YACrB,CAAC,CAAC;SACH,CAAC,CAAA;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;IACD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CACT,uCAAuC,eAAe,yCAAyC,CAChG,CAAA;IACH,CAAC;AACH,CAAC,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -44,8 +44,16 @@
|
|
|
44
44
|
* An API that wants another scheme than `Bearer` (`Authorization: Token <jwt>`) sets
|
|
45
45
|
* `authScheme: 'Token'` in the client config; do not hand-write a fetch wrapper for it.
|
|
46
46
|
*
|
|
47
|
+
* A request made while the session is still being restored WAITS for that restore, so a page
|
|
48
|
+
* that fetches viewer-dependent data on mount (a profile's `following` flag, a feed, a
|
|
49
|
+
* `favorited` state) cannot go out unauthenticated and answer for an anonymous viewer. The auth
|
|
50
|
+
* bond registers that wait with `setAuthGate`; apps that bond auth through
|
|
51
|
+
* `@molecule/app-bonds-default-react` get it without doing anything. An app with its own session
|
|
52
|
+
* module registers its own: `setAuthGate(() => session.whenRestored())`.
|
|
53
|
+
*
|
|
47
54
|
* @module
|
|
48
55
|
*/
|
|
56
|
+
export * from './auth-gate.js';
|
|
49
57
|
export * from './client.js';
|
|
50
58
|
export * from './interceptors.js';
|
|
51
59
|
export * from './provider.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -44,8 +44,16 @@
|
|
|
44
44
|
* An API that wants another scheme than `Bearer` (`Authorization: Token <jwt>`) sets
|
|
45
45
|
* `authScheme: 'Token'` in the client config; do not hand-write a fetch wrapper for it.
|
|
46
46
|
*
|
|
47
|
+
* A request made while the session is still being restored WAITS for that restore, so a page
|
|
48
|
+
* that fetches viewer-dependent data on mount (a profile's `following` flag, a feed, a
|
|
49
|
+
* `favorited` state) cannot go out unauthenticated and answer for an anonymous viewer. The auth
|
|
50
|
+
* bond registers that wait with `setAuthGate`; apps that bond auth through
|
|
51
|
+
* `@molecule/app-bonds-default-react` get it without doing anything. An app with its own session
|
|
52
|
+
* module registers its own: `setAuthGate(() => session.whenRestored())`.
|
|
53
|
+
*
|
|
47
54
|
* @module
|
|
48
55
|
*/
|
|
56
|
+
export * from './auth-gate.js';
|
|
49
57
|
export * from './client.js';
|
|
50
58
|
export * from './interceptors.js';
|
|
51
59
|
export * from './provider.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAK/D;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAQ,gBAAqB,KAAG,UA6VjE,CAAA"}
|
package/dist/provider.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { t } from '@molecule/app-i18n';
|
|
9
9
|
import { getLogger } from '@molecule/app-logger';
|
|
10
|
+
import { awaitAuthGate } from './auth-gate.js';
|
|
10
11
|
import { HttpError } from './types.js';
|
|
11
12
|
/**
|
|
12
13
|
* Creates a fetch-based HTTP client using the native Fetch API.
|
|
@@ -49,6 +50,10 @@ export const createFetchClient = (config = {}) => {
|
|
|
49
50
|
baseURL: config.baseURL || '',
|
|
50
51
|
defaultHeaders: config.defaultHeaders || {},
|
|
51
52
|
async request(requestConfig) {
|
|
53
|
+
// Wait for the session restore before anything else: the interceptors below
|
|
54
|
+
// read the auth token, and the request must not leave unauthenticated just
|
|
55
|
+
// because a page effect ran before the restore settled (see auth-gate.ts).
|
|
56
|
+
await awaitAuthGate();
|
|
52
57
|
// Apply request interceptors
|
|
53
58
|
let finalConfig = { ...requestConfig };
|
|
54
59
|
for (const interceptor of requestInterceptors) {
|
package/dist/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|