@civitai/blocks-react 0.57.3 → 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 +25 -61
- package/dist/hooks/useBlockToken.d.ts +35 -7
- package/dist/hooks/useBlockToken.js +32 -5
- package/package.json +2 -2
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 —
|
|
@@ -1497,30 +1509,6 @@ does not publish — open an issue rather than reaching into `dist/internal/`.
|
|
|
1497
1509
|
> exists for one caller: a `pnpm dev:live` harness. **It must never appear in a
|
|
1498
1510
|
> test suite.** The free one is `createMockHost`, on `./testing`.
|
|
1499
1511
|
|
|
1500
|
-
### Why it has its own subpath
|
|
1501
|
-
|
|
1502
|
-
Until `0.55.0` this code was exported from `./testing`. The argument for moving
|
|
1503
|
-
it, in full, is that **a client which spends the caller's money should not be
|
|
1504
|
-
reachable through an import path named `testing`** — the import line is the one
|
|
1505
|
-
piece of context that travels with every call site, and `…/testing` actively
|
|
1506
|
-
asserts the opposite of what this module does. That is
|
|
1507
|
-
[#334](https://github.com/civitai/civitai-app-starters/issues/334)'s literal
|
|
1508
|
-
closing condition.
|
|
1509
|
-
|
|
1510
|
-
Two arguments that were made for this change and **do not hold** — recorded so
|
|
1511
|
-
they are not made again:
|
|
1512
|
-
|
|
1513
|
-
- **It does not shrink the install.** Measured: **+4,447 B**. See
|
|
1514
|
-
[What the host-simulation subpaths cost you](#what-the-host-simulation-subpaths-cost-you).
|
|
1515
|
-
- **It does not close a wrong-autocomplete hazard**, because there was none to
|
|
1516
|
-
close. `createMockHost(options: MockHostOptions = {})` is callable bare;
|
|
1517
|
-
`createLiveHost(options: LiveHostOptions)` takes a **required** argument whose
|
|
1518
|
-
`blockToken` is a **required** short-lived RS256 JWT that a human mints and
|
|
1519
|
-
pastes by hand. `createLiveHost()` and `createLiveHost({})` do not compile, so
|
|
1520
|
-
nobody reaches this module by picking the wrong completion. Earlier revisions
|
|
1521
|
-
of this file, of the changeset, and of #334 called the two signatures
|
|
1522
|
-
"near-identical"; none of them had read the signatures.
|
|
1523
|
-
|
|
1524
1512
|
### The whole surface
|
|
1525
1513
|
|
|
1526
1514
|
One value and one type. `test/subpathSurfaces.test.ts` pins the runtime export
|
|
@@ -1553,6 +1541,10 @@ The same terms as `./testing`: a normal subpath of a `0.x` package where a minor
|
|
|
1553
1541
|
may break it, with the runtime symbol set pinned by
|
|
1554
1542
|
`test/subpathSurfaces.test.ts` so it cannot change silently.
|
|
1555
1543
|
|
|
1544
|
+
It moved off `./testing` in `0.55.0`: an import path named `testing` asserts the
|
|
1545
|
+
opposite of what this module does, and the import line is the one piece of context
|
|
1546
|
+
that travels with every call site.
|
|
1547
|
+
|
|
1556
1548
|
🔴 One cost worth stating plainly: publishing and documenting this subpath makes
|
|
1557
1549
|
[#334](https://github.com/civitai/civitai-app-starters/issues/334) **item 3** —
|
|
1558
1550
|
getting the live-host code out of the tarball entirely — *harder*, not easier.
|
|
@@ -1562,42 +1554,14 @@ many on a subpath nobody was told to rely on.
|
|
|
1562
1554
|
|
|
1563
1555
|
## What the host-simulation subpaths cost you
|
|
1564
1556
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
```text
|
|
1570
|
-
118,590 dist/internal/mockHost.js ← ./testing (createMockHost)
|
|
1571
|
-
86,688 dist/internal/liveHost.js ← ./live (createLiveHost)
|
|
1572
|
-
29,508 dist/internal/pickerOverlay.js ← ./live (via liveHost)
|
|
1573
|
-
15,351 dist/internal/catalog.js ← ./live (via pickerOverlay)
|
|
1574
|
-
9,141 dist/testing.js
|
|
1575
|
-
4,697 dist/internal/consent.js ← BOTH hosts import it
|
|
1576
|
-
2,815 dist/live.js
|
|
1577
|
-
```
|
|
1578
|
-
|
|
1579
|
-
**They ship in every install**, production dependency trees included. They are
|
|
1580
|
-
tree-shaken out of application *bundles* — no block ships a mock host to a
|
|
1581
|
-
browser — so this is `node_modules` weight, not bundle weight.
|
|
1582
|
-
|
|
1583
|
-
🔴 **Splitting `createLiveHost` onto its own subpath removed none of this — it
|
|
1584
|
-
ADDS 4,447 B of code, and trimming the export list moved nothing either.**
|
|
1585
|
-
Measured with `pnpm pack` on both sides of the split, in a detached worktree so
|
|
1586
|
-
neither pack is contaminated by the other change in this release: **319 → 323
|
|
1587
|
-
entries, 1,590,099 B → 1,597,161 B uncompressed.** The only files that differ are
|
|
1588
|
-
|
|
1589
|
-
```text
|
|
1590
|
-
+6,048 dist/live.* (new: .js 2,815, .d.ts 2,839, + maps)
|
|
1591
|
-
-1,692 dist/testing.* (the re-export and its docblock leaving)
|
|
1592
|
-
+91 package.json (the new exports-map key)
|
|
1593
|
-
+2,615 README.md (this section)
|
|
1594
|
-
```
|
|
1557
|
+
The `dist/` modules reachable only from `./testing` and `./live`, and from nothing
|
|
1558
|
+
under `.` or `./ui`, **ship in every install**, production dependency trees
|
|
1559
|
+
included. They are tree-shaken out of application *bundles* — no block ships a
|
|
1560
|
+
mock host to a browser — so this is `node_modules` weight, not bundle weight.
|
|
1595
1561
|
|
|
1596
|
-
`
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
`exports` map has no bearing whatsoever on tarball contents; it decides only what
|
|
1600
|
-
a consumer can *name*.
|
|
1562
|
+
`files` is `["dist", "README.md"]` and `tsconfig` compiles all of `src/**/*`, so
|
|
1563
|
+
the `exports` map has no bearing whatsoever on tarball contents; it decides only
|
|
1564
|
+
what a consumer can *name*.
|
|
1601
1565
|
**The `/live` split buys safety, not size.** Moving these bytes needs the code
|
|
1602
1566
|
deleted or published as a second artifact; that is
|
|
1603
1567
|
[#334](https://github.com/civitai/civitai-app-starters/issues/334) item 3, and it
|
|
@@ -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
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civitai/blocks-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "React hooks and iframe transport for Civitai Apps. Pairs with @civitai/app-sdk/blocks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"node": ">=20"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@civitai/components": "^0.7.
|
|
40
|
+
"@civitai/components": "^0.7.1",
|
|
41
41
|
"@civitai/theme": "^0.4.0"
|
|
42
42
|
},
|
|
43
43
|
"comment-peerDependencies": "This floor is DERIVED, not chosen — the lowest published app-sdk that exports every symbol this package imports. Do not edit it without reading ./PEER_FLOOR.md (in-repo, unpublished) and tests/guards/blocks-react-peer-floor.test.mjs.",
|