@civitai/blocks-react 0.57.4 → 0.58.0
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 +14 -2
- package/dist/hooks/useBlockToken.d.ts +35 -7
- package/dist/hooks/useBlockToken.js +32 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -211,13 +211,25 @@ const bp = useBlockBreakpoint();
|
|
|
211
211
|
### `useBlockToken()`
|
|
212
212
|
|
|
213
213
|
Current block-scoped JWT, auto-refreshing ~2 min before expiry. Returns the token
|
|
214
|
-
fields plus a `refresh()` for the 401-retry path
|
|
214
|
+
fields plus a `refresh()` for the 401-retry path, which **resolves with the new
|
|
215
|
+
token**.
|
|
215
216
|
|
|
216
217
|
```tsx
|
|
217
218
|
const { raw, scopes, expiresAt, buzzBudget, refresh } = useBlockToken();
|
|
218
|
-
|
|
219
|
+
|
|
220
|
+
let res = await fetch(url, { headers: { Authorization: `Bearer ${raw}` } });
|
|
221
|
+
if (res.status === 401) {
|
|
222
|
+
const fresh = await refresh(); // resolves WITH the new token
|
|
223
|
+
res = await fetch(url, { headers: { Authorization: `Bearer ${fresh.raw}` } });
|
|
224
|
+
}
|
|
219
225
|
```
|
|
220
226
|
|
|
227
|
+
> **Retry with the resolved token, not the `raw` you destructured.** That `raw` is
|
|
228
|
+
> a `const` from the render closure that ran *before* the refresh — awaiting
|
|
229
|
+
> `refresh()` re-renders the component but cannot reassign the binding your
|
|
230
|
+
> in-flight callback is already holding. A retry that re-reads the outer `raw`
|
|
231
|
+
> re-sends the stale JWT and 401s for exactly the reason the first call did.
|
|
232
|
+
|
|
221
233
|
### `useHostOrigin()`
|
|
222
234
|
|
|
223
235
|
The validated host origin to direct-fetch the App Blocks HTTP API against —
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { BlockToken } from '@civitai/app-sdk/blocks';
|
|
2
2
|
/**
|
|
3
3
|
* What {@link useBlockToken} returns: the live block token plus a manual
|
|
4
|
-
* `refresh
|
|
4
|
+
* `refresh` that resolves WITH the newly-minted token. See
|
|
5
|
+
* `./returnTypeLedger.js`.
|
|
5
6
|
*/
|
|
6
7
|
export type UseBlockToken = BlockToken & {
|
|
7
|
-
refresh: () => Promise<
|
|
8
|
+
refresh: () => Promise<BlockToken>;
|
|
8
9
|
};
|
|
9
10
|
/**
|
|
10
11
|
* Returns the current block-scoped JWT and keeps it fresh.
|
|
@@ -14,21 +15,48 @@ export type UseBlockToken = BlockToken & {
|
|
|
14
15
|
* snapshot, and the hook re-renders with the new token.
|
|
15
16
|
*
|
|
16
17
|
* Consumers also get a `refresh()` callable for the 401-retry path. Call it
|
|
17
|
-
* after any API request returns 401 — it forces an immediate token mint
|
|
18
|
-
*
|
|
18
|
+
* after any API request returns 401 — it forces an immediate token mint,
|
|
19
|
+
* applies the new token to the snapshot, and RESOLVES WITH that new
|
|
20
|
+
* {@link BlockToken}.
|
|
21
|
+
*
|
|
22
|
+
* 🔴 RETRY WITH THE RESOLVED TOKEN, NOT THE `raw` YOU DESTRUCTURED. The `raw`
|
|
23
|
+
* in scope was captured from the render closure that ran BEFORE the refresh;
|
|
24
|
+
* awaiting `refresh()` re-renders the component but cannot reassign a `const`
|
|
25
|
+
* binding inside an async callback that is already executing. So a retry that
|
|
26
|
+
* re-reads the outer `raw` re-sends the STALE token and 401s for exactly the
|
|
27
|
+
* reason the first call did. This is why `refresh()` resolves with the token:
|
|
28
|
+
* the resolved value is the only in-scope handle on the fresh JWT.
|
|
19
29
|
*
|
|
20
30
|
* @returns The {@link BlockToken} fields (`raw`, `scopes`, `expiresAt`,
|
|
21
|
-
* `buzzBudget`, …) plus a `refresh()` for the 401-retry path
|
|
31
|
+
* `buzzBudget`, …) plus a `refresh()` for the 401-retry path, which resolves
|
|
32
|
+
* with the new {@link BlockToken}.
|
|
22
33
|
*
|
|
23
34
|
* @example
|
|
24
35
|
* const { raw, scopes, expiresAt, buzzBudget, refresh } = useBlockToken();
|
|
25
|
-
*
|
|
36
|
+
* let res = await fetch(url, { headers: { Authorization: `Bearer ${raw}` } });
|
|
37
|
+
* if (res.status === 401) {
|
|
38
|
+
* const fresh = await refresh(); // resolves WITH the new token
|
|
39
|
+
* res = await fetch(url, { headers: { Authorization: `Bearer ${fresh.raw}` } });
|
|
40
|
+
* }
|
|
26
41
|
*/
|
|
27
42
|
export declare function useBlockToken(): UseBlockToken;
|
|
28
43
|
/**
|
|
29
44
|
* Mint/await a token refresh for one block instance, coalescing concurrent calls
|
|
30
45
|
* for the SAME `blockInstanceId`. Exported for the inline-mode multi-instance
|
|
31
46
|
* dedup test — hooks call it via `useBlockToken().refresh`.
|
|
47
|
+
*
|
|
48
|
+
* Resolves with the REFRESHED {@link BlockToken} so a 401-retry has an in-scope
|
|
49
|
+
* handle on the new JWT — the caller's destructured `raw` belongs to a closure
|
|
50
|
+
* that ran before the refresh and cannot be reassigned by it.
|
|
51
|
+
*
|
|
52
|
+
* The value is read back off the transport SNAPSHOT rather than re-parsed from
|
|
53
|
+
* the reply payload, and that is the ordering this depends on:
|
|
54
|
+
* `IframeTransport.handleMessage` calls `applyTokenRefresh` BEFORE
|
|
55
|
+
* `pending.resolve`, deliberately (see the comment there), so by the time this
|
|
56
|
+
* `.then` runs the snapshot already holds the new token. Reading the snapshot
|
|
57
|
+
* keeps ONE parse of the wire token — the resolved value is the very object the
|
|
58
|
+
* next render observes, not a second `tokenFromWrapped` of the same bytes that
|
|
59
|
+
* could drift from it.
|
|
32
60
|
*/
|
|
33
|
-
export declare function requestRefresh(blockInstanceId: string): Promise<
|
|
61
|
+
export declare function requestRefresh(blockInstanceId: string): Promise<BlockToken>;
|
|
34
62
|
//# sourceMappingURL=useBlockToken.d.ts.map
|
|
@@ -12,15 +12,29 @@ const REFRESH_LEAD_MS = 2 * 60 * 1000;
|
|
|
12
12
|
* snapshot, and the hook re-renders with the new token.
|
|
13
13
|
*
|
|
14
14
|
* Consumers also get a `refresh()` callable for the 401-retry path. Call it
|
|
15
|
-
* after any API request returns 401 — it forces an immediate token mint
|
|
16
|
-
*
|
|
15
|
+
* after any API request returns 401 — it forces an immediate token mint,
|
|
16
|
+
* applies the new token to the snapshot, and RESOLVES WITH that new
|
|
17
|
+
* {@link BlockToken}.
|
|
18
|
+
*
|
|
19
|
+
* 🔴 RETRY WITH THE RESOLVED TOKEN, NOT THE `raw` YOU DESTRUCTURED. The `raw`
|
|
20
|
+
* in scope was captured from the render closure that ran BEFORE the refresh;
|
|
21
|
+
* awaiting `refresh()` re-renders the component but cannot reassign a `const`
|
|
22
|
+
* binding inside an async callback that is already executing. So a retry that
|
|
23
|
+
* re-reads the outer `raw` re-sends the STALE token and 401s for exactly the
|
|
24
|
+
* reason the first call did. This is why `refresh()` resolves with the token:
|
|
25
|
+
* the resolved value is the only in-scope handle on the fresh JWT.
|
|
17
26
|
*
|
|
18
27
|
* @returns The {@link BlockToken} fields (`raw`, `scopes`, `expiresAt`,
|
|
19
|
-
* `buzzBudget`, …) plus a `refresh()` for the 401-retry path
|
|
28
|
+
* `buzzBudget`, …) plus a `refresh()` for the 401-retry path, which resolves
|
|
29
|
+
* with the new {@link BlockToken}.
|
|
20
30
|
*
|
|
21
31
|
* @example
|
|
22
32
|
* const { raw, scopes, expiresAt, buzzBudget, refresh } = useBlockToken();
|
|
23
|
-
*
|
|
33
|
+
* let res = await fetch(url, { headers: { Authorization: `Bearer ${raw}` } });
|
|
34
|
+
* if (res.status === 401) {
|
|
35
|
+
* const fresh = await refresh(); // resolves WITH the new token
|
|
36
|
+
* res = await fetch(url, { headers: { Authorization: `Bearer ${fresh.raw}` } });
|
|
37
|
+
* }
|
|
24
38
|
*/
|
|
25
39
|
export function useBlockToken() {
|
|
26
40
|
const snap = useTransportSnapshot();
|
|
@@ -64,6 +78,19 @@ const inFlightRefreshByInstance = new Map();
|
|
|
64
78
|
* Mint/await a token refresh for one block instance, coalescing concurrent calls
|
|
65
79
|
* for the SAME `blockInstanceId`. Exported for the inline-mode multi-instance
|
|
66
80
|
* dedup test — hooks call it via `useBlockToken().refresh`.
|
|
81
|
+
*
|
|
82
|
+
* Resolves with the REFRESHED {@link BlockToken} so a 401-retry has an in-scope
|
|
83
|
+
* handle on the new JWT — the caller's destructured `raw` belongs to a closure
|
|
84
|
+
* that ran before the refresh and cannot be reassigned by it.
|
|
85
|
+
*
|
|
86
|
+
* The value is read back off the transport SNAPSHOT rather than re-parsed from
|
|
87
|
+
* the reply payload, and that is the ordering this depends on:
|
|
88
|
+
* `IframeTransport.handleMessage` calls `applyTokenRefresh` BEFORE
|
|
89
|
+
* `pending.resolve`, deliberately (see the comment there), so by the time this
|
|
90
|
+
* `.then` runs the snapshot already holds the new token. Reading the snapshot
|
|
91
|
+
* keeps ONE parse of the wire token — the resolved value is the very object the
|
|
92
|
+
* next render observes, not a second `tokenFromWrapped` of the same bytes that
|
|
93
|
+
* could drift from it.
|
|
67
94
|
*/
|
|
68
95
|
export async function requestRefresh(blockInstanceId) {
|
|
69
96
|
const existing = inFlightRefreshByInstance.get(blockInstanceId);
|
|
@@ -71,7 +98,7 @@ export async function requestRefresh(blockInstanceId) {
|
|
|
71
98
|
return existing;
|
|
72
99
|
const transport = getTransport();
|
|
73
100
|
const refresh = sendTypedRequest(transport, { type: 'REQUEST_TOKEN', payload: { blockInstanceId } }, 'TOKEN_REFRESH_RESPONSE')
|
|
74
|
-
.then(() =>
|
|
101
|
+
.then(() => transport.getSnapshot().token)
|
|
75
102
|
.finally(() => {
|
|
76
103
|
inFlightRefreshByInstance.delete(blockInstanceId);
|
|
77
104
|
});
|