@molecule/app-http 1.1.6 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -21
- package/dist/auth-gate.d.ts +36 -22
- package/dist/auth-gate.d.ts.map +1 -1
- package/dist/auth-gate.js +118 -35
- package/dist/auth-gate.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +11 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utilities.d.ts +32 -6
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +62 -9
- package/dist/utilities.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-18T17:46:02.937Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/app-http
|
|
@@ -219,11 +219,15 @@ interface RequestConfig {
|
|
|
219
219
|
/**
|
|
220
220
|
* Send this request WITHOUT waiting for the session restore (the auth gate).
|
|
221
221
|
*
|
|
222
|
-
* A request the gate
|
|
223
|
-
*
|
|
224
|
-
* client
|
|
225
|
-
*
|
|
226
|
-
*
|
|
222
|
+
* A request the gate's restore issues from a LATER continuation must set this. The gate
|
|
223
|
+
* recognizes a request the settle function issues while it runs and never holds it — this
|
|
224
|
+
* package's client awaits the gate before anything else, so a `restoreSession()` that calls
|
|
225
|
+
* it directly does not deadlock. But a restore that awaits something else first
|
|
226
|
+
* (`await storage.get()`), then calls the client, reaches `awaitAuthGate` after that window:
|
|
227
|
+
* the gate then waits on itself, and every request on the page stalls until the gate times
|
|
228
|
+
* out (3 s) — so pass `skipAuthGate: true` on that request. A provider that awaits the gate
|
|
229
|
+
* inside a scheduled interceptor (the Axios bond) also reaches it after the window, so its
|
|
230
|
+
* restore's request needs the opt-out.
|
|
227
231
|
*
|
|
228
232
|
* ```typescript
|
|
229
233
|
* // the request the gate's restore makes, through the same client the gate holds back
|
|
@@ -232,7 +236,8 @@ interface RequestConfig {
|
|
|
232
236
|
*
|
|
233
237
|
* `@molecule/app-auth`'s own restore does not need it — it sends its request with
|
|
234
238
|
* `fetch`, not through this client. An app with its own session store that registers a
|
|
235
|
-
* gate with `setAuthGate` does
|
|
239
|
+
* gate with `setAuthGate` does not need it either when its restore calls the client
|
|
240
|
+
* directly.
|
|
236
241
|
*/
|
|
237
242
|
skipAuthGate?: boolean
|
|
238
243
|
|
|
@@ -423,10 +428,20 @@ template auth that is `() => authClient.whenRestored()`, which resolves
|
|
|
423
428
|
immediately when no restore is in flight, so registering it never makes a
|
|
424
429
|
request wait longer than the restore it was about to race anyway.
|
|
425
430
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
(
|
|
429
|
-
|
|
431
|
+
Waiting for the restore does NOT attach its token: a separately created HTTP
|
|
432
|
+
client needs `createDefaultHttpClientWithAuthBearer({ baseURL,
|
|
433
|
+
getToken: () => authClient.getAccessToken(), authScheme })` from
|
|
434
|
+
`@molecule/app-bonds-default-react` (or the auth client built with
|
|
435
|
+
`createDefaultAuthClientWithHttpSync`, which mirrors the token into the
|
|
436
|
+
bonded client) plus `setAuthGate(() => authClient.whenRestored())`.
|
|
437
|
+
|
|
438
|
+
The restore's own requests are never held: a request the settle function issues while it
|
|
439
|
+
runs goes out immediately, so `setAuthGate(() => restoreSession())` cannot deadlock even
|
|
440
|
+
when `restoreSession` calls the gated client. A restore that awaits something else first
|
|
441
|
+
(a storage read, a `Promise.resolve()`) issues its request from a later continuation, which
|
|
442
|
+
the gate cannot recognize — that request needs `skipAuthGate: true` (see
|
|
443
|
+
`RequestConfig.skipAuthGate`). `@molecule/app-auth`'s own restore needs neither because it
|
|
444
|
+
uses `fetch` rather than the bonded client.
|
|
430
445
|
|
|
431
446
|
```typescript
|
|
432
447
|
function setAuthGate(next: (() => Promise<void>) | null): void
|
|
@@ -525,10 +540,30 @@ function unwrapSingle(res: unknown): T | null
|
|
|
525
540
|
|
|
526
541
|
**Returns:** The typed resource `T`, or `null` when the response shape indicates "no resource" (including the various empty envelopes above).
|
|
527
542
|
|
|
543
|
+
#### `setAuthTokenSource(source)`
|
|
544
|
+
|
|
545
|
+
Registers the token source a newly bonded client is hydrated from, or clears
|
|
546
|
+
it with `null`.
|
|
547
|
+
|
|
548
|
+
The source is read once per bond: the client bonded AFTER this call (and the
|
|
549
|
+
one bonded now, if any) receives `source()` through `setAuthToken`. That is
|
|
550
|
+
what carries a restored session's token into a client the auth setup ran
|
|
551
|
+
before — the setup does not need to hold a client reference at all.
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
function setAuthTokenSource(source: (() => string | null | undefined) | null): void
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
- `source` — Returns the current access token (null when signed out), or `null` to clear.
|
|
558
|
+
|
|
528
559
|
#### `setClient(client)`
|
|
529
560
|
|
|
530
561
|
Registers an HTTP client as the active singleton.
|
|
531
562
|
|
|
563
|
+
A registered `setAuthTokenSource` token source is applied to the client
|
|
564
|
+
here, so a client bonded after the auth setup still carries the restored
|
|
565
|
+
session's token.
|
|
566
|
+
|
|
532
567
|
```typescript
|
|
533
568
|
function setClient(client: HttpClient): void
|
|
534
569
|
```
|
|
@@ -537,8 +572,8 @@ function setClient(client: HttpClient): void
|
|
|
537
572
|
|
|
538
573
|
#### `getClient()`
|
|
539
574
|
|
|
540
|
-
Retrieves the bonded HTTP client. If none is bonded,
|
|
541
|
-
|
|
575
|
+
Retrieves the bonded HTTP client. If none is bonded, returns a module-level
|
|
576
|
+
fetch-based fallback — it is NOT bonded.
|
|
542
577
|
|
|
543
578
|
The fallback has NO `baseURL` — a core cannot know an app's API prefix. A relative
|
|
544
579
|
call then resolves against the page origin, where a dev server answers its own HTML
|
|
@@ -546,11 +581,15 @@ shell with a 200. It warns once so the misconfiguration is named instead of show
|
|
|
546
581
|
as a crash three components later; a scaffold bonds the shared client with its API
|
|
547
582
|
prefix, so the fallback only runs in an app that never bonded one.
|
|
548
583
|
|
|
584
|
+
Returning it WITHOUT bonding is what keeps a later `setClient()` authoritative: a
|
|
585
|
+
caller that captured this fallback early does not make it the app's client, so the
|
|
586
|
+
scaffold's client (and its token source) still takes effect for every later call.
|
|
587
|
+
|
|
549
588
|
```typescript
|
|
550
589
|
function getClient(): HttpClient
|
|
551
590
|
```
|
|
552
591
|
|
|
553
|
-
**Returns:** The active HTTP client
|
|
592
|
+
**Returns:** The active HTTP client, or the unbonded fallback when none is bonded.
|
|
554
593
|
|
|
555
594
|
#### `get(url, config)`
|
|
556
595
|
|
|
@@ -702,13 +741,15 @@ authScheme })` from `@molecule/app-bonds-default-react` (or an auth client built
|
|
|
702
741
|
`createDefaultAuthClientWithHttpSync`, which mirrors the token into the bonded client), because
|
|
703
742
|
`setAuthGate` alone sends no `Authorization` header.
|
|
704
743
|
|
|
705
|
-
**
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
`http.get(currentUserEndpoint, {
|
|
711
|
-
|
|
744
|
+
**The gate never holds its own restore's request.** A request the settle function issues
|
|
745
|
+
while it runs (its call reaches this client before the function awaits anything) is part of
|
|
746
|
+
the restore it is waiting for, so it goes out immediately — `setAuthGate(() =>
|
|
747
|
+
restoreSession())` does not deadlock even when `restoreSession` uses this client. A restore
|
|
748
|
+
that awaits something else first and only then calls the client reaches it after that
|
|
749
|
+
window, and that request must opt out of the gate: `http.get(currentUserEndpoint, {
|
|
750
|
+
skipAuthGate: true })`. Without the opt-out the gate waits on itself, the restore never
|
|
751
|
+
settles, and every request the gate holds stalls until it times out (3 s).
|
|
752
|
+
`@molecule/app-auth`'s own restore does not need it (it calls `fetch`, not this client).
|
|
712
753
|
|
|
713
754
|
## Translations
|
|
714
755
|
|
package/dist/auth-gate.d.ts
CHANGED
|
@@ -27,24 +27,28 @@
|
|
|
27
27
|
* `setupHttpDefault()`), and a gate installed on the throwaway client the auth
|
|
28
28
|
* bond saw would be silently lost.
|
|
29
29
|
*
|
|
30
|
-
* ##
|
|
31
|
-
*
|
|
32
|
-
* `awaitAuthGate` holds every request while the gate is pending —
|
|
33
|
-
*
|
|
34
|
-
* the
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* and
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
30
|
+
* ## The gate's own restore requests are never held
|
|
31
|
+
*
|
|
32
|
+
* `awaitAuthGate` holds every request while the gate is pending — except the ones the gate's
|
|
33
|
+
* settle function issues itself. Those are part of the restore it is waiting for; holding them
|
|
34
|
+
* makes the gate wait on itself, and nothing but the timeout releases anything (X1
|
|
35
|
+
* xr-molecule-flash-m97 and xr-molecule-flash-m173 both lost minutes to this: an app
|
|
36
|
+
* registering `setAuthGate(() => restoreSession())` over a restore that fetched through the
|
|
37
|
+
* same client, every page held for the full bound, and 917 `GET /api/user` requests named by
|
|
38
|
+
* one console line). So `setAuthGate(() => restoreSession())` works as written when
|
|
39
|
+
* `restoreSession` calls the gated client.
|
|
40
|
+
*
|
|
41
|
+
* "Issued by the gate" means issued while the settle function is running: this package's
|
|
42
|
+
* fetch client awaits the gate before anything else, so a restore's call is made in the settle
|
|
43
|
+
* function's own synchronous execution. A restore that awaits something else first (a storage
|
|
44
|
+
* read, a `Promise.resolve()`) and only then calls the client issues its request from a later
|
|
45
|
+
* continuation, which nothing can attribute to the gate — browsers ship no async-context API
|
|
46
|
+
* (`AsyncContext`/`AsyncLocalStorage`) to ask, and releasing requests merely because the gate is
|
|
47
|
+
* pending would drop exactly the first-paint requests the gate exists to hold (m95). A provider
|
|
48
|
+
* whose request pipeline awaits the gate in a scheduled interceptor (the Axios bond) reaches it
|
|
49
|
+
* a microtask after the settle function returns, outside the window, for the same reason. Such a
|
|
50
|
+
* request must pass `skipAuthGate: true` (`RequestConfig.skipAuthGate`), and a timeout with
|
|
51
|
+
* requests held is reported at error level naming it.
|
|
48
52
|
*
|
|
49
53
|
* @module
|
|
50
54
|
*/
|
|
@@ -58,10 +62,20 @@ import type { AuthGateRequest } from './types.js';
|
|
|
58
62
|
* immediately when no restore is in flight, so registering it never makes a
|
|
59
63
|
* request wait longer than the restore it was about to race anyway.
|
|
60
64
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* (
|
|
64
|
-
*
|
|
65
|
+
* Waiting for the restore does NOT attach its token: a separately created HTTP
|
|
66
|
+
* client needs `createDefaultHttpClientWithAuthBearer({ baseURL,
|
|
67
|
+
* getToken: () => authClient.getAccessToken(), authScheme })` from
|
|
68
|
+
* `@molecule/app-bonds-default-react` (or the auth client built with
|
|
69
|
+
* `createDefaultAuthClientWithHttpSync`, which mirrors the token into the
|
|
70
|
+
* bonded client) plus `setAuthGate(() => authClient.whenRestored())`.
|
|
71
|
+
*
|
|
72
|
+
* The restore's own requests are never held: a request the settle function issues while it
|
|
73
|
+
* runs goes out immediately, so `setAuthGate(() => restoreSession())` cannot deadlock even
|
|
74
|
+
* when `restoreSession` calls the gated client. A restore that awaits something else first
|
|
75
|
+
* (a storage read, a `Promise.resolve()`) issues its request from a later continuation, which
|
|
76
|
+
* the gate cannot recognize — that request needs `skipAuthGate: true` (see
|
|
77
|
+
* `RequestConfig.skipAuthGate`). `@molecule/app-auth`'s own restore needs neither because it
|
|
78
|
+
* uses `fetch` rather than the bonded client.
|
|
65
79
|
*
|
|
66
80
|
* @param next - The gate, or `null` to clear a previously registered one.
|
|
67
81
|
*
|
package/dist/auth-gate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-gate.d.ts","sourceRoot":"","sources":["../src/auth-gate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth-gate.d.ts","sourceRoot":"","sources":["../src/auth-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAkEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAG,IAIhE,CAAA;AA8ED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAU,UAAU,eAAe,KAAG,OAAO,CAAC,IAAI,CAwC3E,CAAA"}
|
package/dist/auth-gate.js
CHANGED
|
@@ -27,24 +27,28 @@
|
|
|
27
27
|
* `setupHttpDefault()`), and a gate installed on the throwaway client the auth
|
|
28
28
|
* bond saw would be silently lost.
|
|
29
29
|
*
|
|
30
|
-
* ##
|
|
31
|
-
*
|
|
32
|
-
* `awaitAuthGate` holds every request while the gate is pending —
|
|
33
|
-
*
|
|
34
|
-
* the
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* and
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
30
|
+
* ## The gate's own restore requests are never held
|
|
31
|
+
*
|
|
32
|
+
* `awaitAuthGate` holds every request while the gate is pending — except the ones the gate's
|
|
33
|
+
* settle function issues itself. Those are part of the restore it is waiting for; holding them
|
|
34
|
+
* makes the gate wait on itself, and nothing but the timeout releases anything (X1
|
|
35
|
+
* xr-molecule-flash-m97 and xr-molecule-flash-m173 both lost minutes to this: an app
|
|
36
|
+
* registering `setAuthGate(() => restoreSession())` over a restore that fetched through the
|
|
37
|
+
* same client, every page held for the full bound, and 917 `GET /api/user` requests named by
|
|
38
|
+
* one console line). So `setAuthGate(() => restoreSession())` works as written when
|
|
39
|
+
* `restoreSession` calls the gated client.
|
|
40
|
+
*
|
|
41
|
+
* "Issued by the gate" means issued while the settle function is running: this package's
|
|
42
|
+
* fetch client awaits the gate before anything else, so a restore's call is made in the settle
|
|
43
|
+
* function's own synchronous execution. A restore that awaits something else first (a storage
|
|
44
|
+
* read, a `Promise.resolve()`) and only then calls the client issues its request from a later
|
|
45
|
+
* continuation, which nothing can attribute to the gate — browsers ship no async-context API
|
|
46
|
+
* (`AsyncContext`/`AsyncLocalStorage`) to ask, and releasing requests merely because the gate is
|
|
47
|
+
* pending would drop exactly the first-paint requests the gate exists to hold (m95). A provider
|
|
48
|
+
* whose request pipeline awaits the gate in a scheduled interceptor (the Axios bond) reaches it
|
|
49
|
+
* a microtask after the settle function returns, outside the window, for the same reason. Such a
|
|
50
|
+
* request must pass `skipAuthGate: true` (`RequestConfig.skipAuthGate`), and a timeout with
|
|
51
|
+
* requests held is reported at error level naming it.
|
|
48
52
|
*
|
|
49
53
|
* @module
|
|
50
54
|
*/
|
|
@@ -66,6 +70,25 @@ const heldRequests = new Map();
|
|
|
66
70
|
* gates. Cleared when the same gate later releases a request normally.
|
|
67
71
|
*/
|
|
68
72
|
let reportedStall = null;
|
|
73
|
+
/**
|
|
74
|
+
* The settle-function invocation requests are currently waiting on, shared by every request
|
|
75
|
+
* that arrives while the gate is pending.
|
|
76
|
+
*
|
|
77
|
+
* Without it each held request re-invokes the gate, and a gate that issues a request on every
|
|
78
|
+
* call floods the network: the m97 shape measured 855 `fetch` calls for one page (`task-auth-
|
|
79
|
+
* gate-deadlock.report.md`). `null` when no invocation is in flight; `setAuthGate` clears it
|
|
80
|
+
* so a re-registered gate is never shared with the old one's waiters.
|
|
81
|
+
*/
|
|
82
|
+
let inFlightGate = null;
|
|
83
|
+
/**
|
|
84
|
+
* True only while the gate's settle function is executing synchronously.
|
|
85
|
+
*
|
|
86
|
+
* A request that reaches `awaitAuthGate` in this window was issued BY the restore — the fetch
|
|
87
|
+
* client's `request()` awaits the gate before anything else, so the call is made in the settle
|
|
88
|
+
* function's own execution. Holding it would make the gate wait on itself (see the module
|
|
89
|
+
* doc); requests issued after the function awaits something else are held as usual.
|
|
90
|
+
*/
|
|
91
|
+
let gateSettling = false;
|
|
69
92
|
/**
|
|
70
93
|
* How long a request may be held by the gate before it is sent anyway.
|
|
71
94
|
*
|
|
@@ -96,10 +119,20 @@ const GATE_TIMEOUT_MS = 3_000;
|
|
|
96
119
|
* immediately when no restore is in flight, so registering it never makes a
|
|
97
120
|
* request wait longer than the restore it was about to race anyway.
|
|
98
121
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* (
|
|
102
|
-
*
|
|
122
|
+
* Waiting for the restore does NOT attach its token: a separately created HTTP
|
|
123
|
+
* client needs `createDefaultHttpClientWithAuthBearer({ baseURL,
|
|
124
|
+
* getToken: () => authClient.getAccessToken(), authScheme })` from
|
|
125
|
+
* `@molecule/app-bonds-default-react` (or the auth client built with
|
|
126
|
+
* `createDefaultAuthClientWithHttpSync`, which mirrors the token into the
|
|
127
|
+
* bonded client) plus `setAuthGate(() => authClient.whenRestored())`.
|
|
128
|
+
*
|
|
129
|
+
* The restore's own requests are never held: a request the settle function issues while it
|
|
130
|
+
* runs goes out immediately, so `setAuthGate(() => restoreSession())` cannot deadlock even
|
|
131
|
+
* when `restoreSession` calls the gated client. A restore that awaits something else first
|
|
132
|
+
* (a storage read, a `Promise.resolve()`) issues its request from a later continuation, which
|
|
133
|
+
* the gate cannot recognize — that request needs `skipAuthGate: true` (see
|
|
134
|
+
* `RequestConfig.skipAuthGate`). `@molecule/app-auth`'s own restore needs neither because it
|
|
135
|
+
* uses `fetch` rather than the bonded client.
|
|
103
136
|
*
|
|
104
137
|
* @param next - The gate, or `null` to clear a previously registered one.
|
|
105
138
|
*
|
|
@@ -112,6 +145,7 @@ const GATE_TIMEOUT_MS = 3_000;
|
|
|
112
145
|
*/
|
|
113
146
|
export const setAuthGate = (next) => {
|
|
114
147
|
gate = next;
|
|
148
|
+
inFlightGate = null;
|
|
115
149
|
reportedStall = null;
|
|
116
150
|
};
|
|
117
151
|
/**
|
|
@@ -121,21 +155,69 @@ export const setAuthGate = (next) => {
|
|
|
121
155
|
const describeRequest = (request) => request?.method || request?.url
|
|
122
156
|
? `${request?.method ?? 'GET'} ${request?.url ?? ''}`.trim()
|
|
123
157
|
: 'a request';
|
|
158
|
+
/**
|
|
159
|
+
* How many held request names the stalled-gate report prints before summarizing the rest.
|
|
160
|
+
*
|
|
161
|
+
* The report is console output, and every reader of it truncates: molecule-dev's route walk
|
|
162
|
+
* keeps the first 200 characters of a console error, so the sentence naming the fix has to
|
|
163
|
+
* come first — and the request list is capped so the message stays readable when a deadlocked
|
|
164
|
+
* gate has held hundreds of requests (xr-molecule-flash-m173: 917 `GET /api/user`).
|
|
165
|
+
*/
|
|
166
|
+
const MAX_NAMED_HELD_REQUESTS = 5;
|
|
124
167
|
/**
|
|
125
168
|
* Reports a gate that timed out while holding requests: once per stuck gate, at error level
|
|
126
169
|
* so it reaches the page's error console and any error transport — a console warning on a
|
|
127
170
|
* deployed URL reads as "the site is slow", which is what the gate itself was added for.
|
|
171
|
+
*
|
|
172
|
+
* Leads with the cause and the fix, then names at most `MAX_NAMED_HELD_REQUESTS` distinct
|
|
173
|
+
* requests and counts the rest: an unbounded join buried the fix clause behind 900 names in
|
|
174
|
+
* xr-molecule-flash-m173, and no reader of the line ever reached it.
|
|
128
175
|
*/
|
|
129
176
|
const reportStalledGate = (stuck) => {
|
|
130
177
|
if (reportedStall === stuck)
|
|
131
178
|
return;
|
|
132
179
|
reportedStall = stuck;
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
`
|
|
138
|
-
|
|
180
|
+
const names = [...new Set(heldRequests.values())];
|
|
181
|
+
const shown = names.slice(0, MAX_NAMED_HELD_REQUESTS);
|
|
182
|
+
const more = heldRequests.size - shown.length;
|
|
183
|
+
logger.error(`The auth gate is waiting on itself: its restore's request is held by it. Send that ` +
|
|
184
|
+
`request with \`skipAuthGate: true\`, or clear the gate with \`setAuthGate(null)\` if ` +
|
|
185
|
+
`nothing restores a session. Held ${heldRequests.size} request(s) for ` +
|
|
186
|
+
`${GATE_TIMEOUT_MS}ms${shown.length ? `: ${shown.join(', ')}` : ''}` +
|
|
187
|
+
`${more > 0 ? ` (and ${more} more)` : ''}.`);
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Starts the gate's settle function — once, shared by every request that arrives while it is
|
|
191
|
+
* pending — turning a rejection (or a synchronous throw) into a logged warning so a failed
|
|
192
|
+
* restore releases its waiters instead of rejecting them.
|
|
193
|
+
*
|
|
194
|
+
* `gateSettling` is true only for the function's synchronous execution: the IIFE runs
|
|
195
|
+
* `current()` up to its first `await` before this call returns, and any request the restore
|
|
196
|
+
* issues there (the gated client's `request()` awaits the gate before anything else) bypasses
|
|
197
|
+
* the gate instead of waiting on the invocation it belongs to.
|
|
198
|
+
*/
|
|
199
|
+
const invokeGate = (current) => {
|
|
200
|
+
if (inFlightGate)
|
|
201
|
+
return inFlightGate;
|
|
202
|
+
let invocation;
|
|
203
|
+
gateSettling = true;
|
|
204
|
+
try {
|
|
205
|
+
invocation = (async () => current())();
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
gateSettling = false;
|
|
209
|
+
}
|
|
210
|
+
const shared = invocation.catch((error) => {
|
|
211
|
+
logger.warn('The auth gate rejected; sending the request without waiting for it', {
|
|
212
|
+
error,
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
inFlightGate = shared;
|
|
216
|
+
void shared.then(() => {
|
|
217
|
+
if (inFlightGate === shared)
|
|
218
|
+
inFlightGate = null;
|
|
219
|
+
});
|
|
220
|
+
return shared;
|
|
139
221
|
};
|
|
140
222
|
/**
|
|
141
223
|
* Awaits the registered gate, if any. Every HTTP provider calls this before it
|
|
@@ -160,19 +242,20 @@ export const awaitAuthGate = async (request) => {
|
|
|
160
242
|
const current = gate;
|
|
161
243
|
if (!current)
|
|
162
244
|
return;
|
|
245
|
+
// Issued while the gate's settle function is running: part of the restore it is settling,
|
|
246
|
+
// never held by it. See `invokeGate` and the module doc.
|
|
247
|
+
if (gateSettling)
|
|
248
|
+
return;
|
|
163
249
|
const token = Symbol('held-request');
|
|
164
250
|
heldRequests.set(token, describeRequest(request));
|
|
165
251
|
let timer;
|
|
166
252
|
let timedOut = false;
|
|
167
253
|
try {
|
|
168
254
|
await Promise.race([
|
|
169
|
-
//
|
|
170
|
-
// a gate is someone else's code, and no shape of it may reject the
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
error,
|
|
174
|
-
});
|
|
175
|
-
}),
|
|
255
|
+
// One invocation, shared: `invokeGate` also turns a gate that throws SYNCHRONOUSLY into
|
|
256
|
+
// a rejection — a gate is someone else's code, and no shape of it may reject the
|
|
257
|
+
// request it was holding.
|
|
258
|
+
invokeGate(current),
|
|
176
259
|
new Promise((resolve) => {
|
|
177
260
|
timer = setTimeout(() => {
|
|
178
261
|
timedOut = true;
|
package/dist/auth-gate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-gate.js","sourceRoot":"","sources":["../src/auth-gate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth-gate.js","sourceRoot":"","sources":["../src/auth-gate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -78,13 +78,15 @@
|
|
|
78
78
|
* `createDefaultAuthClientWithHttpSync`, which mirrors the token into the bonded client), because
|
|
79
79
|
* `setAuthGate` alone sends no `Authorization` header.
|
|
80
80
|
*
|
|
81
|
-
* **
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* `http.get(currentUserEndpoint, {
|
|
87
|
-
*
|
|
81
|
+
* **The gate never holds its own restore's request.** A request the settle function issues
|
|
82
|
+
* while it runs (its call reaches this client before the function awaits anything) is part of
|
|
83
|
+
* the restore it is waiting for, so it goes out immediately — `setAuthGate(() =>
|
|
84
|
+
* restoreSession())` does not deadlock even when `restoreSession` uses this client. A restore
|
|
85
|
+
* that awaits something else first and only then calls the client reaches it after that
|
|
86
|
+
* window, and that request must opt out of the gate: `http.get(currentUserEndpoint, {
|
|
87
|
+
* skipAuthGate: true })`. Without the opt-out the gate waits on itself, the restore never
|
|
88
|
+
* settles, and every request the gate holds stalls until it times out (3 s).
|
|
89
|
+
* `@molecule/app-auth`'s own restore does not need it (it calls `fetch`, not this client).
|
|
88
90
|
*
|
|
89
91
|
* @module
|
|
90
92
|
*/
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;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
|
@@ -78,13 +78,15 @@
|
|
|
78
78
|
* `createDefaultAuthClientWithHttpSync`, which mirrors the token into the bonded client), because
|
|
79
79
|
* `setAuthGate` alone sends no `Authorization` header.
|
|
80
80
|
*
|
|
81
|
-
* **
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* `http.get(currentUserEndpoint, {
|
|
87
|
-
*
|
|
81
|
+
* **The gate never holds its own restore's request.** A request the settle function issues
|
|
82
|
+
* while it runs (its call reaches this client before the function awaits anything) is part of
|
|
83
|
+
* the restore it is waiting for, so it goes out immediately — `setAuthGate(() =>
|
|
84
|
+
* restoreSession())` does not deadlock even when `restoreSession` uses this client. A restore
|
|
85
|
+
* that awaits something else first and only then calls the client reaches it after that
|
|
86
|
+
* window, and that request must opt out of the gate: `http.get(currentUserEndpoint, {
|
|
87
|
+
* skipAuthGate: true })`. Without the opt-out the gate waits on itself, the restore never
|
|
88
|
+
* settles, and every request the gate holds stalls until it times out (3 s).
|
|
89
|
+
* `@molecule/app-auth`'s own restore does not need it (it calls `fetch`, not this client).
|
|
88
90
|
*
|
|
89
91
|
* @module
|
|
90
92
|
*/
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;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/types.d.ts
CHANGED
|
@@ -30,11 +30,15 @@ export interface RequestConfig {
|
|
|
30
30
|
/**
|
|
31
31
|
* Send this request WITHOUT waiting for the session restore (the auth gate).
|
|
32
32
|
*
|
|
33
|
-
* A request the gate
|
|
34
|
-
*
|
|
35
|
-
* client
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* A request the gate's restore issues from a LATER continuation must set this. The gate
|
|
34
|
+
* recognizes a request the settle function issues while it runs and never holds it — this
|
|
35
|
+
* package's client awaits the gate before anything else, so a `restoreSession()` that calls
|
|
36
|
+
* it directly does not deadlock. But a restore that awaits something else first
|
|
37
|
+
* (`await storage.get()`), then calls the client, reaches `awaitAuthGate` after that window:
|
|
38
|
+
* the gate then waits on itself, and every request on the page stalls until the gate times
|
|
39
|
+
* out (3 s) — so pass `skipAuthGate: true` on that request. A provider that awaits the gate
|
|
40
|
+
* inside a scheduled interceptor (the Axios bond) also reaches it after the window, so its
|
|
41
|
+
* restore's request needs the opt-out.
|
|
38
42
|
*
|
|
39
43
|
* ```typescript
|
|
40
44
|
* // the request the gate's restore makes, through the same client the gate holds back
|
|
@@ -43,7 +47,8 @@ export interface RequestConfig {
|
|
|
43
47
|
*
|
|
44
48
|
* `@molecule/app-auth`'s own restore does not need it — it sends its request with
|
|
45
49
|
* `fetch`, not through this client. An app with its own session store that registers a
|
|
46
|
-
* gate with `setAuthGate` does
|
|
50
|
+
* gate with `setAuthGate` does not need it either when its restore calls the client
|
|
51
|
+
* directly.
|
|
47
52
|
*/
|
|
48
53
|
skipAuthGate?: boolean;
|
|
49
54
|
/**
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;AAEzF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;IAE9D;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;AAEzF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;IAE9D;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAA;IAEvD;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IAEpB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD;;OAEG;IACH,MAAM,EAAE,UAAU,CAAA;IAElB;;OAEG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;OAEG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC;;OAEG;IACH,IAAI,EAAE,CAAC,CAAA;IAEP;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IAEd;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE/B;;OAEG;IACH,MAAM,EAAE,iBAAiB,CAAA;CAC1B;AAED;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAGzB,MAAM,EAAE,MAAM;IACd,QAAQ,CAAC,EAAE,YAAY;IACvB,MAAM,CAAC,EAAE,iBAAiB;IAC1B,QAAQ,CAAC,EAAE,MAAM;gBAJxB,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,YAAY,YAAA,EACvB,MAAM,CAAC,EAAE,iBAAiB,YAAA,EAC1B,QAAQ,CAAC,EAAE,MAAM,YAAA;CAK3B"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6JH;;GAEG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAGzB;IACA;IACA;IACA;IALT,YACE,OAAe,EACR,MAAc,EACd,QAAuB,EACvB,MAA0B,EAC1B,QAAiB;QAExB,KAAK,CAAC,OAAO,CAAC,CAAA;QALP,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAe;QACvB,WAAM,GAAN,MAAM,CAAoB;QAC1B,aAAQ,GAAR,QAAQ,CAAS;QAGxB,IAAI,CAAC,IAAI,GAAG,WAAW,CAAA;IACzB,CAAC;CACF"}
|
package/dist/utilities.d.ts
CHANGED
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP bond accessor and convenience request functions.
|
|
3
3
|
*
|
|
4
|
-
* If no custom client is bonded, a fetch-based client is
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* If no custom client is bonded, a fetch-based client is returned — it is NOT
|
|
5
|
+
* bonded, so a client bonded later is what every subsequent call sees.
|
|
6
|
+
* Provides `get`, `post`, `put`, `patch`, and `del` shorthand functions that
|
|
7
|
+
* delegate to the bonded client.
|
|
7
8
|
*
|
|
8
9
|
* @module
|
|
9
10
|
*/
|
|
10
11
|
import type { HttpClient } from './client.js';
|
|
11
12
|
import type { HttpResponse, RequestConfig } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Registers the token source a newly bonded client is hydrated from, or clears
|
|
15
|
+
* it with `null`.
|
|
16
|
+
*
|
|
17
|
+
* The source is read once per bond: the client bonded AFTER this call (and the
|
|
18
|
+
* one bonded now, if any) receives `source()` through `setAuthToken`. That is
|
|
19
|
+
* what carries a restored session's token into a client the auth setup ran
|
|
20
|
+
* before — the setup does not need to hold a client reference at all.
|
|
21
|
+
*
|
|
22
|
+
* @param source - Returns the current access token (null when signed out), or `null` to clear.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* setAuthTokenSource(() => authClient.getAccessToken())
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const setAuthTokenSource: (source: (() => string | null | undefined) | null) => void;
|
|
12
30
|
/**
|
|
13
31
|
* Registers an HTTP client as the active singleton.
|
|
14
32
|
*
|
|
33
|
+
* A registered `setAuthTokenSource` token source is applied to the client
|
|
34
|
+
* here, so a client bonded after the auth setup still carries the restored
|
|
35
|
+
* session's token.
|
|
36
|
+
*
|
|
15
37
|
* @param client - The HTTP client implementation to bond.
|
|
16
38
|
*
|
|
17
39
|
* @example
|
|
@@ -22,8 +44,8 @@ import type { HttpResponse, RequestConfig } from './types.js';
|
|
|
22
44
|
*/
|
|
23
45
|
export declare const setClient: (client: HttpClient) => void;
|
|
24
46
|
/**
|
|
25
|
-
* Retrieves the bonded HTTP client. If none is bonded,
|
|
26
|
-
*
|
|
47
|
+
* Retrieves the bonded HTTP client. If none is bonded, returns a module-level
|
|
48
|
+
* fetch-based fallback — it is NOT bonded.
|
|
27
49
|
*
|
|
28
50
|
* The fallback has NO `baseURL` — a core cannot know an app's API prefix. A relative
|
|
29
51
|
* call then resolves against the page origin, where a dev server answers its own HTML
|
|
@@ -31,7 +53,11 @@ export declare const setClient: (client: HttpClient) => void;
|
|
|
31
53
|
* as a crash three components later; a scaffold bonds the shared client with its API
|
|
32
54
|
* prefix, so the fallback only runs in an app that never bonded one.
|
|
33
55
|
*
|
|
34
|
-
*
|
|
56
|
+
* Returning it WITHOUT bonding is what keeps a later `setClient()` authoritative: a
|
|
57
|
+
* caller that captured this fallback early does not make it the app's client, so the
|
|
58
|
+
* scaffold's client (and its token source) still takes effect for every later call.
|
|
59
|
+
*
|
|
60
|
+
* @returns The active HTTP client, or the unbonded fallback when none is bonded.
|
|
35
61
|
*/
|
|
36
62
|
export declare const getClient: () => HttpClient;
|
|
37
63
|
/**
|
package/dist/utilities.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AA6B7D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,IAAI,KAAG,IAIrF,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GAAI,QAAQ,UAAU,KAAG,IAG9C,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,SAAS,QAAO,UAY5B,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,GAAG,OAAO,EAAE,KAAK,MAAM,EAAE,SAAS,aAAa,KAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAC7D,CAAA;AAEjC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,GAAG,OAAO,EAC9B,KAAK,MAAM,EACX,OAAO,OAAO,EACd,SAAS,aAAa,KACrB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAA2C,CAAA;AAErE;;;;;;;;;GASG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,GAAG,OAAO,EAC7B,KAAK,MAAM,EACX,OAAO,OAAO,EACd,SAAS,aAAa,KACrB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAA0C,CAAA;AAEpE;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,GAAG,OAAO,EAC/B,KAAK,MAAM,EACX,OAAO,OAAO,EACd,SAAS,aAAa,KACrB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAA4C,CAAA;AAEtE;;;;;;GAMG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,GAAG,OAAO,EAAE,KAAK,MAAM,EAAE,SAAS,aAAa,KAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAC1D,CAAA;AAEpC;;;GAGG;AACH,eAAO,MAAM,WAAW,mBAA4D,CAAA"}
|
package/dist/utilities.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP bond accessor and convenience request functions.
|
|
3
3
|
*
|
|
4
|
-
* If no custom client is bonded, a fetch-based client is
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* If no custom client is bonded, a fetch-based client is returned — it is NOT
|
|
5
|
+
* bonded, so a client bonded later is what every subsequent call sees.
|
|
6
|
+
* Provides `get`, `post`, `put`, `patch`, and `del` shorthand functions that
|
|
7
|
+
* delegate to the bonded client.
|
|
7
8
|
*
|
|
8
9
|
* @module
|
|
9
10
|
*/
|
|
@@ -13,9 +14,55 @@ import { createFetchClient } from './provider.js';
|
|
|
13
14
|
const BOND_TYPE = 'http-client';
|
|
14
15
|
/** One warning per page load is enough to name a missing bond. */
|
|
15
16
|
let warnedAboutFallback = false;
|
|
17
|
+
/**
|
|
18
|
+
* The client `getClient()` returns while nothing is bonded.
|
|
19
|
+
*
|
|
20
|
+
* Module-level and deliberately NEVER bonded: bonding it made the fallback the
|
|
21
|
+
* page's shared client, so a caller that read `getClient()` before
|
|
22
|
+
* `setClient()` captured a no-baseURL client that every later `setClient()`
|
|
23
|
+
* could not reach — the app's real client was bonded and the caller's token
|
|
24
|
+
* sync kept writing into the orphan, so every request went out bare
|
|
25
|
+
* (xr-molecule-flash-m156, 401 `token is missing` after a reload).
|
|
26
|
+
*/
|
|
27
|
+
let unbondedFallback = null;
|
|
28
|
+
/**
|
|
29
|
+
* The auth client whose token every HTTP client starts with, or null.
|
|
30
|
+
*
|
|
31
|
+
* `setClient` applies it at bond time, which is the only moment that can reach a
|
|
32
|
+
* client created AFTER the auth setup ran — the scaffold wires auth before http,
|
|
33
|
+
* so the auth bond's initial token had no client to land on
|
|
34
|
+
* (`createDefaultAuthClientWithHttpSync` in `@molecule/app-bonds-default-react`).
|
|
35
|
+
*/
|
|
36
|
+
let authTokenSource = null;
|
|
37
|
+
/**
|
|
38
|
+
* Registers the token source a newly bonded client is hydrated from, or clears
|
|
39
|
+
* it with `null`.
|
|
40
|
+
*
|
|
41
|
+
* The source is read once per bond: the client bonded AFTER this call (and the
|
|
42
|
+
* one bonded now, if any) receives `source()` through `setAuthToken`. That is
|
|
43
|
+
* what carries a restored session's token into a client the auth setup ran
|
|
44
|
+
* before — the setup does not need to hold a client reference at all.
|
|
45
|
+
*
|
|
46
|
+
* @param source - Returns the current access token (null when signed out), or `null` to clear.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* setAuthTokenSource(() => authClient.getAccessToken())
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export const setAuthTokenSource = (source) => {
|
|
54
|
+
authTokenSource = source;
|
|
55
|
+
const bonded = bondGet(BOND_TYPE);
|
|
56
|
+
if (bonded && source)
|
|
57
|
+
bonded.setAuthToken(source() ?? null);
|
|
58
|
+
};
|
|
16
59
|
/**
|
|
17
60
|
* Registers an HTTP client as the active singleton.
|
|
18
61
|
*
|
|
62
|
+
* A registered `setAuthTokenSource` token source is applied to the client
|
|
63
|
+
* here, so a client bonded after the auth setup still carries the restored
|
|
64
|
+
* session's token.
|
|
65
|
+
*
|
|
19
66
|
* @param client - The HTTP client implementation to bond.
|
|
20
67
|
*
|
|
21
68
|
* @example
|
|
@@ -26,10 +73,12 @@ let warnedAboutFallback = false;
|
|
|
26
73
|
*/
|
|
27
74
|
export const setClient = (client) => {
|
|
28
75
|
bond(BOND_TYPE, client);
|
|
76
|
+
if (authTokenSource)
|
|
77
|
+
client.setAuthToken(authTokenSource() ?? null);
|
|
29
78
|
};
|
|
30
79
|
/**
|
|
31
|
-
* Retrieves the bonded HTTP client. If none is bonded,
|
|
32
|
-
*
|
|
80
|
+
* Retrieves the bonded HTTP client. If none is bonded, returns a module-level
|
|
81
|
+
* fetch-based fallback — it is NOT bonded.
|
|
33
82
|
*
|
|
34
83
|
* The fallback has NO `baseURL` — a core cannot know an app's API prefix. A relative
|
|
35
84
|
* call then resolves against the page origin, where a dev server answers its own HTML
|
|
@@ -37,20 +86,24 @@ export const setClient = (client) => {
|
|
|
37
86
|
* as a crash three components later; a scaffold bonds the shared client with its API
|
|
38
87
|
* prefix, so the fallback only runs in an app that never bonded one.
|
|
39
88
|
*
|
|
40
|
-
*
|
|
89
|
+
* Returning it WITHOUT bonding is what keeps a later `setClient()` authoritative: a
|
|
90
|
+
* caller that captured this fallback early does not make it the app's client, so the
|
|
91
|
+
* scaffold's client (and its token source) still takes effect for every later call.
|
|
92
|
+
*
|
|
93
|
+
* @returns The active HTTP client, or the unbonded fallback when none is bonded.
|
|
41
94
|
*/
|
|
42
95
|
export const getClient = () => {
|
|
43
96
|
const existing = bondGet(BOND_TYPE);
|
|
44
97
|
if (existing)
|
|
45
98
|
return existing;
|
|
46
|
-
|
|
47
|
-
|
|
99
|
+
if (!unbondedFallback)
|
|
100
|
+
unbondedFallback = createFetchClient();
|
|
48
101
|
if (!warnedAboutFallback) {
|
|
49
102
|
warnedAboutFallback = true;
|
|
50
103
|
getLogger('http').warn('@molecule/app-http: no client is bonded — requests go to the page origin with no baseURL. ' +
|
|
51
104
|
"Bond one: setClient(createFetchClient({ baseURL: '/api' })).");
|
|
52
105
|
}
|
|
53
|
-
return
|
|
106
|
+
return unbondedFallback;
|
|
54
107
|
};
|
|
55
108
|
/**
|
|
56
109
|
* Makes a GET request using the bonded HTTP client.
|
package/dist/utilities.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|