@civitai/blocks-react 0.39.0 → 0.41.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 +132 -5
- package/dist/hooks/useBlockContext.d.ts +6 -0
- package/dist/hooks/useBlockContext.d.ts.map +1 -1
- package/dist/hooks/useBlockContext.js +6 -0
- package/dist/hooks/useBlockContext.js.map +1 -1
- package/dist/hooks/useBlockTheme.d.ts +39 -0
- package/dist/hooks/useBlockTheme.d.ts.map +1 -0
- package/dist/hooks/useBlockTheme.js +41 -0
- package/dist/hooks/useBlockTheme.js.map +1 -0
- package/dist/hooks/useBuzzWorkflow.d.ts +13 -1
- package/dist/hooks/useBuzzWorkflow.d.ts.map +1 -1
- package/dist/hooks/useBuzzWorkflow.js +13 -1
- package/dist/hooks/useBuzzWorkflow.js.map +1 -1
- package/dist/hooks/useConsentUnavailable.d.ts +92 -0
- package/dist/hooks/useConsentUnavailable.d.ts.map +1 -0
- package/dist/hooks/useConsentUnavailable.js +93 -0
- package/dist/hooks/useConsentUnavailable.js.map +1 -0
- package/dist/hooks/useRequestConsent.d.ts +21 -3
- package/dist/hooks/useRequestConsent.d.ts.map +1 -1
- package/dist/hooks/useRequestConsent.js +30 -4
- package/dist/hooks/useRequestConsent.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/consent.d.ts +71 -0
- package/dist/internal/consent.d.ts.map +1 -0
- package/dist/internal/consent.js +88 -0
- package/dist/internal/consent.js.map +1 -0
- package/dist/internal/consentRefusalLatch.d.ts +19 -0
- package/dist/internal/consentRefusalLatch.d.ts.map +1 -0
- package/dist/internal/consentRefusalLatch.js +38 -0
- package/dist/internal/consentRefusalLatch.js.map +1 -0
- package/dist/internal/iframeTransport.d.ts +25 -0
- package/dist/internal/iframeTransport.d.ts.map +1 -1
- package/dist/internal/iframeTransport.js +47 -0
- package/dist/internal/iframeTransport.js.map +1 -1
- package/dist/internal/liveHost.d.ts +8 -2
- package/dist/internal/liveHost.d.ts.map +1 -1
- package/dist/internal/liveHost.js +100 -6
- package/dist/internal/liveHost.js.map +1 -1
- package/dist/internal/mockHost.d.ts +57 -13
- package/dist/internal/mockHost.d.ts.map +1 -1
- package/dist/internal/mockHost.js +188 -29
- package/dist/internal/mockHost.js.map +1 -1
- package/dist/internal/transport.d.ts +58 -1
- package/dist/internal/transport.d.ts.map +1 -1
- package/dist/internal/transport.js +62 -0
- package/dist/internal/transport.js.map +1 -1
- package/dist/internal/validate.d.ts +73 -3
- package/dist/internal/validate.d.ts.map +1 -1
- package/dist/internal/validate.js +135 -3
- package/dist/internal/validate.js.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +41 -2
- package/dist/testing.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ your block app and the SDK share a single React tree.
|
|
|
27
27
|
import { useRef } from 'react';
|
|
28
28
|
import { useBlockContext, useBlockResize, useBuzzWorkflow } from '@civitai/blocks-react';
|
|
29
29
|
import { Button } from '@civitai/blocks-react/ui';
|
|
30
|
-
import
|
|
30
|
+
import { isModelSlotContext } from '@civitai/app-sdk/blocks';
|
|
31
31
|
|
|
32
32
|
export function App() {
|
|
33
33
|
const { ready, context, viewer, theme } = useBlockContext();
|
|
@@ -36,21 +36,22 @@ export function App() {
|
|
|
36
36
|
useBlockResize(rootRef); // host fits the iframe to content
|
|
37
37
|
|
|
38
38
|
if (!ready) return <div ref={rootRef}>Loading…</div>;
|
|
39
|
-
|
|
39
|
+
// `context` is a union keyed on slotId — narrow with the guard, not a cast.
|
|
40
|
+
if (!isModelSlotContext(context)) return <div ref={rootRef}>Wrong slot.</div>;
|
|
40
41
|
|
|
41
42
|
return (
|
|
42
43
|
// GOTCHA #60: set data-theme on YOUR OWN root — the host can't reach into
|
|
43
44
|
// the iframe to set it. Without this any [data-theme="dark"] CSS is dormant.
|
|
44
45
|
<div ref={rootRef} data-theme={theme}>
|
|
45
|
-
<p>Block for model {
|
|
46
|
+
<p>Block for model {context.modelName} ({viewer ? 'signed in' : 'anon'})</p>
|
|
46
47
|
{/* `/ui` Button — themed by the data-theme above; `loading` disables + shows a spinner */}
|
|
47
48
|
<Button
|
|
48
49
|
loading={status === 'submitting' || status === 'polling'}
|
|
49
50
|
onClick={() =>
|
|
50
51
|
submit({
|
|
51
52
|
kind: 'textToImage',
|
|
52
|
-
modelId:
|
|
53
|
-
modelVersionId:
|
|
53
|
+
modelId: context.modelId,
|
|
54
|
+
modelVersionId: context.modelVersionId,
|
|
54
55
|
params: { prompt: 'a cat' },
|
|
55
56
|
})
|
|
56
57
|
}
|
|
@@ -112,8 +113,35 @@ const { ready, context, viewer, theme, settings, blockId, blockInstanceId, appId
|
|
|
112
113
|
model-page slots.
|
|
113
114
|
- `viewer` — `ViewerInfo | null` (`null` = anonymous).
|
|
114
115
|
- `theme` — `'light' | 'dark'`. **Set `data-theme={theme}` on your root** (gotcha #60).
|
|
116
|
+
LIVE: it starts at the `BLOCK_INIT` value and then tracks the host's
|
|
117
|
+
`THEME_CHANGE` push when the viewer toggles dark mode mid-session — see
|
|
118
|
+
[`useBlockTheme()`](#useblocktheme).
|
|
115
119
|
- `settings` — `{ publisherSettings, userSettings }`.
|
|
116
120
|
|
|
121
|
+
### `useBlockTheme()`
|
|
122
|
+
|
|
123
|
+
The host's CURRENT site theme, and nothing else. Same value as
|
|
124
|
+
`useBlockContext().theme` — reach for this when theme is all you need.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
function ThemedRoot() {
|
|
128
|
+
const theme = useBlockTheme(); // 'light' | 'dark'
|
|
129
|
+
return <div data-theme={theme}>…</div>;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The viewer can toggle light/dark **while your block is mounted**. The host pushes
|
|
134
|
+
a `THEME_CHANGE` message and this hook re-renders. You get that for free as long
|
|
135
|
+
as you *read* the theme on every render — a block that copies it into state once
|
|
136
|
+
at mount, or writes `data-theme` imperatively in a mount-only effect, will stay
|
|
137
|
+
stuck on the old theme.
|
|
138
|
+
|
|
139
|
+
Against a host that predates `THEME_CHANGE` the value simply never moves (the
|
|
140
|
+
old behaviour). Nothing awaits the message, so there is no hang either way.
|
|
141
|
+
|
|
142
|
+
Exercise it locally: `createMockHost(...).setTheme('light')` (and the same on the
|
|
143
|
+
`dev:live` host) pushes the real message.
|
|
144
|
+
|
|
117
145
|
### `useBlockResize(ref)`
|
|
118
146
|
|
|
119
147
|
Attach to your root element. Observes its height and posts `RESIZE_IFRAME` so the
|
|
@@ -474,10 +502,109 @@ needs `ai:write:budgeted` but the viewer hasn't granted it). Fire-and-forget —
|
|
|
474
502
|
on grant the host pushes a new token; observe `useBlockToken().scopes` and retry.
|
|
475
503
|
|
|
476
504
|
```tsx
|
|
505
|
+
import { useRequestConsent } from '@civitai/blocks-react';
|
|
506
|
+
|
|
477
507
|
const { requestConsent } = useRequestConsent();
|
|
478
508
|
requestConsent({ scopes: ['ai:write:budgeted', 'buzz:read:self'] });
|
|
479
509
|
```
|
|
480
510
|
|
|
511
|
+
🔴 **Always pass `scopes`, with a real scope name in it — it is optional in the
|
|
512
|
+
signature but a precondition for the refusal path below.** The host grants the
|
|
513
|
+
missing set it computed at mint, so a bare `requestConsent()` still opens the
|
|
514
|
+
consent dialog. But `CONSENT_UNAVAILABLE` is computed *from the hint*: with no
|
|
515
|
+
explicit scope proven un-grantable, the host cannot tell "can never be granted"
|
|
516
|
+
from "the viewer hasn't confirmed yet", so it stays silent rather than guess.
|
|
517
|
+
|
|
518
|
+
The bar is an array holding **at least one non-empty string** — not merely "an
|
|
519
|
+
array is present". `undefined`, a non-array, `[]`, `['']` and `[1, 2]` all
|
|
520
|
+
produce silence, in `pnpm dev` and in production alike, so
|
|
521
|
+
`requestConsent({ scopes: [] })` follows the instruction and still receives
|
|
522
|
+
nothing. To its author that reads as a broken message rather than a thin
|
|
523
|
+
argument.
|
|
524
|
+
|
|
525
|
+
### `useConsentUnavailable()`
|
|
526
|
+
|
|
527
|
+
Some environments withhold a scope at mint (a dev-tunnel preview token, a surface
|
|
528
|
+
that carries no money scope), so no consent round-trip can ever add it. The host
|
|
529
|
+
then pushes an uncorrelated `CONSENT_UNAVAILABLE` — *not* a reply, because
|
|
530
|
+
`REQUEST_CONSENT` carries no `requestId`. Consume it and stop telling the user to
|
|
531
|
+
retry something that can't succeed:
|
|
532
|
+
|
|
533
|
+
```tsx
|
|
534
|
+
import { useConsentUnavailable, useRequestConsent } from '@civitai/blocks-react';
|
|
535
|
+
|
|
536
|
+
function ConsentAwareGenerate() {
|
|
537
|
+
const { requestConsent } = useRequestConsent();
|
|
538
|
+
const { refusal, reset } = useConsentUnavailable();
|
|
539
|
+
|
|
540
|
+
// 🔴 Branch on `refusal !== null`, NEVER on `refusal.scopes.length`. The host
|
|
541
|
+
// refuses on its own unfiltered set but names only scopes in the public
|
|
542
|
+
// vocabulary, so `scopes: []` is a legitimate refusal — gating on the length
|
|
543
|
+
// silently drops the very message you subscribed for. Use the names for copy.
|
|
544
|
+
if (refusal) {
|
|
545
|
+
return (
|
|
546
|
+
<div>
|
|
547
|
+
<p>Generating isn't available on this page.</p>
|
|
548
|
+
<button onClick={reset}>Try again</button>
|
|
549
|
+
</div>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
// 🔴 `scopes` is REQUIRED for a refusal to ever arrive — see above.
|
|
553
|
+
return <button onClick={() => requestConsent({ scopes: ['ai:write:budgeted'] })}>Generate</button>;
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
`refusal` holds the latest `ConsentUnavailablePayload` (`{ reason, scopes }`) or
|
|
558
|
+
`null`; `reset()` clears it, since a refusal is scoped to the scopes that were
|
|
559
|
+
asked for and shouldn't latch for the life of the block. Against a host that
|
|
560
|
+
never sends the message the hook simply stays `null` — nothing awaits it, so
|
|
561
|
+
there is no timeout to hit.
|
|
562
|
+
|
|
563
|
+
🔴 **The push is UNCORRELATED, and that is the permanent shape of this API.**
|
|
564
|
+
`REQUEST_CONSENT` carries no `requestId`, so a refusal cannot be matched to the
|
|
565
|
+
request that provoked it. Two consequences to design around:
|
|
566
|
+
|
|
567
|
+
- **Every mounted `useConsentUnavailable()` sees every refusal.** There is no
|
|
568
|
+
reliable filter: `scopes` is advisory and may legitimately be `[]`, so it
|
|
569
|
+
cannot serve as a correlation key. If two independent parts of your block
|
|
570
|
+
request different scopes, both will see both refusals. Keep a request and its
|
|
571
|
+
refusal UI in one component, or track the outstanding request yourself.
|
|
572
|
+
- **A refusal is buffered across mounts, so one that arrives while the consumer
|
|
573
|
+
is unmounted is not lost.** The transport hands an unsolicited push only to
|
|
574
|
+
handlers registered at the instant it arrives, so without this a refusal that
|
|
575
|
+
landed before the consumer mounted — the requester and the consumer being
|
|
576
|
+
different components, or the consumer being conditionally rendered — vanished,
|
|
577
|
+
and the block went back to showing "click Generate again" beside the host's
|
|
578
|
+
"unavailable". `requestConsent()` arms the buffer as it sends. It keeps only
|
|
579
|
+
the latest refusal, is dropped when the block token changes (a refusal is a
|
|
580
|
+
claim about *that* token's scopes, and the grant path re-mints), and is cleared
|
|
581
|
+
by `reset()` — so the "Try again" button above genuinely resets, rather than
|
|
582
|
+
having the refusal reappear on the next mount. A `REQUEST_CONSENT` you post
|
|
583
|
+
through the raw transport instead of the hook does not arm it.
|
|
584
|
+
|
|
585
|
+
Without the hook (a non-React consumer, or one wiring the transport directly),
|
|
586
|
+
the same push is available untyped — note the explicit type import, which the
|
|
587
|
+
cast needs and which the hook makes unnecessary:
|
|
588
|
+
|
|
589
|
+
```tsx
|
|
590
|
+
import { getTransport } from '@civitai/blocks-react';
|
|
591
|
+
import type { ConsentUnavailablePayload } from '@civitai/app-sdk/blocks';
|
|
592
|
+
|
|
593
|
+
const unsubscribe = getTransport().onMessage('CONSENT_UNAVAILABLE', (payload) => {
|
|
594
|
+
// `onMessage` hands you `unknown`; this cast is UNCHECKED, which is why
|
|
595
|
+
// `useConsentUnavailable()` is the preferred path.
|
|
596
|
+
const { reason, scopes } = payload as ConsentUnavailablePayload;
|
|
597
|
+
console.info('permission unavailable', reason, scopes);
|
|
598
|
+
});
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
To exercise the refusal locally, run the mock host with
|
|
602
|
+
`createMockHost({ consentGrantable: false })`, flip it live with
|
|
603
|
+
`host.setScenario({ consentGrantable: false })`, or append `?consent=ungrantable`
|
|
604
|
+
to the dev harness URL — the `<Harness>` chrome then reads `consent=ungrantable`
|
|
605
|
+
rather than `withheld`. `dev:live` emits it too: live mode can grant nothing, so
|
|
606
|
+
any request for a scope your dev token lacks produces one.
|
|
607
|
+
|
|
481
608
|
### `useDomainMaturity()`
|
|
482
609
|
|
|
483
610
|
Read the surrounding color-domain's maturity ceiling (civitai #2670) so a block
|
|
@@ -18,6 +18,12 @@ declare function useTransportSnapshot(): BlockSnapshot;
|
|
|
18
18
|
* set `data-theme={theme}` on your root, gotcha #60), `blockId`,
|
|
19
19
|
* `blockInstanceId`, and `appId`.
|
|
20
20
|
*
|
|
21
|
+
* `theme` is LIVE: it starts at the `BLOCK_INIT` (or URL-fragment) value and
|
|
22
|
+
* then tracks the host's `THEME_CHANGE` push when the viewer toggles light/dark
|
|
23
|
+
* mid-session. Reading it here is enough — {@link useBlockTheme} is the same
|
|
24
|
+
* value, narrower. Against a host that never pushes it, the value simply never
|
|
25
|
+
* moves (today's behaviour).
|
|
26
|
+
*
|
|
21
27
|
* @example
|
|
22
28
|
* const { ready, context, viewer, theme, settings } = useBlockContext();
|
|
23
29
|
* if (!ready) return <div>Loading…</div>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBlockContext.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockContext.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;GAGG;AACH,iBAAS,oBAAoB,IAAI,aAAa,CAS7C;AAED
|
|
1
|
+
{"version":3,"file":"useBlockContext.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockContext.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;GAGG;AACH,iBAAS,oBAAoB,IAAI,aAAa,CAS7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,IAAI,IAAI,CACrC,aAAa,EACX,OAAO,GACP,YAAY,GACZ,SAAS,GACT,OAAO,GACP,UAAU,GACV,QAAQ,GACR,OAAO,GACP,SAAS,GACT,iBAAiB,GACjB,OAAO,CACV,CAcA;AAED,6EAA6E;AAC7E,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -25,6 +25,12 @@ function useTransportSnapshot() {
|
|
|
25
25
|
* set `data-theme={theme}` on your root, gotcha #60), `blockId`,
|
|
26
26
|
* `blockInstanceId`, and `appId`.
|
|
27
27
|
*
|
|
28
|
+
* `theme` is LIVE: it starts at the `BLOCK_INIT` (or URL-fragment) value and
|
|
29
|
+
* then tracks the host's `THEME_CHANGE` push when the viewer toggles light/dark
|
|
30
|
+
* mid-session. Reading it here is enough — {@link useBlockTheme} is the same
|
|
31
|
+
* value, narrower. Against a host that never pushes it, the value simply never
|
|
32
|
+
* moves (today's behaviour).
|
|
33
|
+
*
|
|
28
34
|
* @example
|
|
29
35
|
* const { ready, context, viewer, theme, settings } = useBlockContext();
|
|
30
36
|
* if (!ready) return <div>Loading…</div>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBlockContext.js","sourceRoot":"","sources":["../../src/hooks/useBlockContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD;;;GAGG;AACH,SAAS,oBAAoB;IAC3B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,OAAO,oBAAoB,CACzB,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,EAC/B,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE;IAC7B,iEAAiE;IACjE,mEAAmE;IACnE,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,CAC9B,CAAC;AACJ,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"useBlockContext.js","sourceRoot":"","sources":["../../src/hooks/useBlockContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD;;;GAGG;AACH,SAAS,oBAAoB;IAC3B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,OAAO,oBAAoB,CACzB,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,EAC/B,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE;IAC7B,iEAAiE;IACjE,mEAAmE;IACnE,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,CAC9B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,eAAe;IAa7B,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Theme } from '@civitai/app-sdk/blocks';
|
|
2
|
+
/**
|
|
3
|
+
* The host's CURRENT site theme (`'light' | 'dark'`), kept live for the whole
|
|
4
|
+
* life of the block ON THE IFRAME TRANSPORT.
|
|
5
|
+
*
|
|
6
|
+
* Reads the SAME singleton transport snapshot {@link useBlockContext} does, so
|
|
7
|
+
* it re-renders when the value changes. Three things can set it, in increasing
|
|
8
|
+
* order of authority:
|
|
9
|
+
*
|
|
10
|
+
* 1. the iframe URL fragment fast path (`#civitai-block=v1&theme=…`), read
|
|
11
|
+
* synchronously at construction, BEFORE any message — this is why a block
|
|
12
|
+
* can paint its first frame in the right theme;
|
|
13
|
+
* 2. `BLOCK_INIT` (authoritative — replaces the whole snapshot);
|
|
14
|
+
* 3. `THEME_CHANGE`, the host's push when the viewer toggles light/dark WHILE
|
|
15
|
+
* the block is mounted. Without it a mounted block kept its mount-time
|
|
16
|
+
* theme until reloaded: `BLOCK_INIT` is deduped by the transport and the
|
|
17
|
+
* URL fragment is frozen at mount, so neither can carry a later value.
|
|
18
|
+
*
|
|
19
|
+
* BEFORE `BLOCK_INIT` (and with no fragment) this returns the snapshot's
|
|
20
|
+
* `'light'` sentinel, exactly like `useBlockContext().theme`. Gate first paint
|
|
21
|
+
* on `useBlockContext().ready` if that matters to you.
|
|
22
|
+
*
|
|
23
|
+
* 🔴 OLD HOST: a host that never sends `THEME_CHANGE` simply never moves the
|
|
24
|
+
* value — the hook degrades to today's mount-time-constant behaviour. Nothing
|
|
25
|
+
* here awaits a message, so there is no hang and no timeout.
|
|
26
|
+
*
|
|
27
|
+
* 🔴 INLINE TRANSPORT: the value is FROZEN at the bootstrap theme. v1 inline
|
|
28
|
+
* mode receives no host pushes at all (`InlineTransport.onMessage` is a stub and
|
|
29
|
+
* `subscribe` is a no-op, so nothing can emit), exactly the way
|
|
30
|
+
* {@link useBlockResize} is a no-op there. Same degradation as an old host —
|
|
31
|
+
* correct first paint, no live toggle — and it lifts when v2 inline mode lands.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // The host cannot reach into your iframe's DOM — put the theme on YOUR root.
|
|
35
|
+
* const theme = useBlockTheme();
|
|
36
|
+
* return <div data-theme={theme}>…</div>;
|
|
37
|
+
*/
|
|
38
|
+
export declare function useBlockTheme(): Theme;
|
|
39
|
+
//# sourceMappingURL=useBlockTheme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBlockTheme.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockTheme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAIrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,aAAa,IAAI,KAAK,CAErC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useTransportSnapshot } from './useBlockContext.js';
|
|
2
|
+
/**
|
|
3
|
+
* The host's CURRENT site theme (`'light' | 'dark'`), kept live for the whole
|
|
4
|
+
* life of the block ON THE IFRAME TRANSPORT.
|
|
5
|
+
*
|
|
6
|
+
* Reads the SAME singleton transport snapshot {@link useBlockContext} does, so
|
|
7
|
+
* it re-renders when the value changes. Three things can set it, in increasing
|
|
8
|
+
* order of authority:
|
|
9
|
+
*
|
|
10
|
+
* 1. the iframe URL fragment fast path (`#civitai-block=v1&theme=…`), read
|
|
11
|
+
* synchronously at construction, BEFORE any message — this is why a block
|
|
12
|
+
* can paint its first frame in the right theme;
|
|
13
|
+
* 2. `BLOCK_INIT` (authoritative — replaces the whole snapshot);
|
|
14
|
+
* 3. `THEME_CHANGE`, the host's push when the viewer toggles light/dark WHILE
|
|
15
|
+
* the block is mounted. Without it a mounted block kept its mount-time
|
|
16
|
+
* theme until reloaded: `BLOCK_INIT` is deduped by the transport and the
|
|
17
|
+
* URL fragment is frozen at mount, so neither can carry a later value.
|
|
18
|
+
*
|
|
19
|
+
* BEFORE `BLOCK_INIT` (and with no fragment) this returns the snapshot's
|
|
20
|
+
* `'light'` sentinel, exactly like `useBlockContext().theme`. Gate first paint
|
|
21
|
+
* on `useBlockContext().ready` if that matters to you.
|
|
22
|
+
*
|
|
23
|
+
* 🔴 OLD HOST: a host that never sends `THEME_CHANGE` simply never moves the
|
|
24
|
+
* value — the hook degrades to today's mount-time-constant behaviour. Nothing
|
|
25
|
+
* here awaits a message, so there is no hang and no timeout.
|
|
26
|
+
*
|
|
27
|
+
* 🔴 INLINE TRANSPORT: the value is FROZEN at the bootstrap theme. v1 inline
|
|
28
|
+
* mode receives no host pushes at all (`InlineTransport.onMessage` is a stub and
|
|
29
|
+
* `subscribe` is a no-op, so nothing can emit), exactly the way
|
|
30
|
+
* {@link useBlockResize} is a no-op there. Same degradation as an old host —
|
|
31
|
+
* correct first paint, no live toggle — and it lifts when v2 inline mode lands.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // The host cannot reach into your iframe's DOM — put the theme on YOUR root.
|
|
35
|
+
* const theme = useBlockTheme();
|
|
36
|
+
* return <div data-theme={theme}>…</div>;
|
|
37
|
+
*/
|
|
38
|
+
export function useBlockTheme() {
|
|
39
|
+
return useTransportSnapshot().theme;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=useBlockTheme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBlockTheme.js","sourceRoot":"","sources":["../../src/hooks/useBlockTheme.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,oBAAoB,EAAE,CAAC,KAAK,CAAC;AACtC,CAAC"}
|
|
@@ -150,12 +150,24 @@ interface UseBuzzWorkflowReturn {
|
|
|
150
150
|
* `estimate`/`submit` take a full {@link WorkflowBody} — the discriminated
|
|
151
151
|
* union keyed by `kind`, with THREE members as of `@civitai/app-sdk@0.30.0`:
|
|
152
152
|
* a `textToImage` body (`{ kind, modelId, modelVersionId, params }`), a
|
|
153
|
-
* `customComfy`
|
|
153
|
+
* `customComfy` body (`kind: 'customComfy'`), or a `step` body
|
|
154
154
|
* (`{ kind: 'step', step, params }` — a server-registered orchestrator step
|
|
155
155
|
* such as `'chat-completion'`), never a bare `{ prompt }`. The hook forwards
|
|
156
156
|
* the body to the host verbatim and never reads variant-specific fields, so
|
|
157
157
|
* every member flows through unchanged, including any member added later.
|
|
158
158
|
*
|
|
159
|
+
* 🔴 `customComfy` IS ITSELF A UNION, on `mode` — an app CAN ship its own
|
|
160
|
+
* ComfyUI graph. `WorkflowBodyCustomComfyRecipe` (`mode` omitted or `'recipe'`)
|
|
161
|
+
* names a server-registered recipe; `WorkflowBodyCustomComfyInline`
|
|
162
|
+
* (`mode: 'inline'`) carries the graph itself, plus its declared AIR
|
|
163
|
+
* `resources` and a `maxBuzz` bound. The inline arm is LIVE in production
|
|
164
|
+
* (developer-only) and this comment used to describe `customComfy` as a
|
|
165
|
+
* recipe-only `{ kind, recipe, params }` shape — written when that was true and
|
|
166
|
+
* never revisited once the arm shipped. A developer working against the live
|
|
167
|
+
* feature read the equivalent claim on the type, believed it over their own
|
|
168
|
+
* instinct, and concluded the capability did not exist. `@civitai/app-sdk`
|
|
169
|
+
* 0.30.0 predates the inline arm; the union above is otherwise unchanged.
|
|
170
|
+
*
|
|
159
171
|
* @returns `{ estimate, submit, poll, watch, cancel, status, result, error }`.
|
|
160
172
|
*
|
|
161
173
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuzzWorkflow.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AA8BnG;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAW7C,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACrD;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyBD,oCAAoC;AACpC,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjE,MAAM,EAAE,CACN,IAAI,EAAE,YAAY,EAClB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC;;;OAGG;IACH,IAAI,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,EAAE,CACL,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/D,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACrC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED
|
|
1
|
+
{"version":3,"file":"useBuzzWorkflow.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AA8BnG;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAW7C,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACrD;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyBD,oCAAoC;AACpC,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjE,MAAM,EAAE,CACN,IAAI,EAAE,YAAY,EAClB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC;;;OAGG;IACH,IAAI,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,EAAE,CACL,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/D,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACrC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,eAAe,IAAI,qBAAqB,CA+MvD"}
|
|
@@ -94,12 +94,24 @@ function sleep(ms, signal) {
|
|
|
94
94
|
* `estimate`/`submit` take a full {@link WorkflowBody} — the discriminated
|
|
95
95
|
* union keyed by `kind`, with THREE members as of `@civitai/app-sdk@0.30.0`:
|
|
96
96
|
* a `textToImage` body (`{ kind, modelId, modelVersionId, params }`), a
|
|
97
|
-
* `customComfy`
|
|
97
|
+
* `customComfy` body (`kind: 'customComfy'`), or a `step` body
|
|
98
98
|
* (`{ kind: 'step', step, params }` — a server-registered orchestrator step
|
|
99
99
|
* such as `'chat-completion'`), never a bare `{ prompt }`. The hook forwards
|
|
100
100
|
* the body to the host verbatim and never reads variant-specific fields, so
|
|
101
101
|
* every member flows through unchanged, including any member added later.
|
|
102
102
|
*
|
|
103
|
+
* 🔴 `customComfy` IS ITSELF A UNION, on `mode` — an app CAN ship its own
|
|
104
|
+
* ComfyUI graph. `WorkflowBodyCustomComfyRecipe` (`mode` omitted or `'recipe'`)
|
|
105
|
+
* names a server-registered recipe; `WorkflowBodyCustomComfyInline`
|
|
106
|
+
* (`mode: 'inline'`) carries the graph itself, plus its declared AIR
|
|
107
|
+
* `resources` and a `maxBuzz` bound. The inline arm is LIVE in production
|
|
108
|
+
* (developer-only) and this comment used to describe `customComfy` as a
|
|
109
|
+
* recipe-only `{ kind, recipe, params }` shape — written when that was true and
|
|
110
|
+
* never revisited once the arm shipped. A developer working against the live
|
|
111
|
+
* feature read the equivalent claim on the type, believed it over their own
|
|
112
|
+
* instinct, and concluded the capability did not exist. `@civitai/app-sdk`
|
|
113
|
+
* 0.30.0 predates the inline arm; the union above is otherwise unchanged.
|
|
114
|
+
*
|
|
103
115
|
* @returns `{ estimate, submit, poll, watch, cancel, status, result, error }`.
|
|
104
116
|
*
|
|
105
117
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuzzWorkflow.js","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAI9C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEpF;;;;GAIG;AACH,MAAM,iBAAiB,GAAiD,IAAI,GAAG,CAAC;IAC9E,WAAW;IACX,QAAQ;IACR,UAAU;IACV,SAAS;CACV,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,MAAM,2BAA2B,GAAG,OAAO,CAAC;AAE5C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C,qFAAqF;AACrF,MAAM,yBAAyB,GAAG,KAAK,CAAC;AAExC,6EAA6E;AAC7E,MAAM,wBAAwB,GAAG,EAAE,GAAG,MAAM,CAAC;AAE7C,gFAAgF;AAChF,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAwDpC;;;;;;;;;GASG;AACH,SAAS,KAAK,CAAC,EAAU,EAAE,MAAoB;IAC7C,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9C,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC3C,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACnC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAmED
|
|
1
|
+
{"version":3,"file":"useBuzzWorkflow.js","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ConsentUnavailablePayload } from '@civitai/app-sdk/blocks';
|
|
2
|
+
export type { ConsentUnavailablePayload };
|
|
3
|
+
/** What {@link useConsentUnavailable} returns. */
|
|
4
|
+
export interface UseConsentUnavailable {
|
|
5
|
+
/**
|
|
6
|
+
* The most recent `CONSENT_UNAVAILABLE` push, or `null` if the host has not
|
|
7
|
+
* refused (the normal case — nothing to render differently).
|
|
8
|
+
*
|
|
9
|
+
* 🔴 `refusal.scopes` CAN BE EMPTY and that is still a refusal. The host
|
|
10
|
+
* decides to refuse on its own UNFILTERED un-grantable set, then filters the
|
|
11
|
+
* names it puts on the wire down to the public block-scope vocabulary — so a
|
|
12
|
+
* request naming nothing the platform recognises refuses with `scopes: []`.
|
|
13
|
+
* Branch on `refusal !== null`; use `refusal.scopes` only to word the copy.
|
|
14
|
+
*/
|
|
15
|
+
refusal: ConsentUnavailablePayload | null;
|
|
16
|
+
/**
|
|
17
|
+
* Clear the stored refusal — this hook's state AND the buffered copy.
|
|
18
|
+
*
|
|
19
|
+
* A refusal is scoped to the scopes that were asked for, not to the block, so
|
|
20
|
+
* it must not latch forever: a block that is refused `ai:write:budgeted` may
|
|
21
|
+
* still legitimately request something else later, and a long-lived block
|
|
22
|
+
* whose host re-inits (sign-in, a new token) can find itself grantable again.
|
|
23
|
+
* Call this when you move on from the refused action.
|
|
24
|
+
*
|
|
25
|
+
* Clearing the BUFFER too is load-bearing, not tidiness: the refusal is
|
|
26
|
+
* retained across mounts (see the hook docblock), so a `reset()` that cleared
|
|
27
|
+
* only local state would be silently undone the next time this hook mounted —
|
|
28
|
+
* the documented "Try again" button would put the refusal straight back.
|
|
29
|
+
*/
|
|
30
|
+
reset: () => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Subscribe to the host's `CONSENT_UNAVAILABLE` push — the signal that a
|
|
34
|
+
* `REQUEST_CONSENT` this block sent can **never** be granted in this
|
|
35
|
+
* environment, because the scope was clamped or withheld at mint. Distinct from
|
|
36
|
+
* "the viewer hasn't confirmed the dialog yet", which produces no message at
|
|
37
|
+
* all.
|
|
38
|
+
*
|
|
39
|
+
* This is the whole point of the message: without it a block keeps telling the
|
|
40
|
+
* user *"Confirm in the Civitai dialog. If you dismissed it, click Generate
|
|
41
|
+
* again"* while the host says the permission is unavailable — two contradictory
|
|
42
|
+
* messages on one screen, and the misleading one is the block's.
|
|
43
|
+
*
|
|
44
|
+
* 🔴 **PRECONDITION — pass `scopes` to `requestConsent()`, with a real scope
|
|
45
|
+
* name in it.** The refusal is computed from the request's `scopes` hint, and
|
|
46
|
+
* both dev hosts and the real host (`resolveUngrantableConsentNotice`) return
|
|
47
|
+
* "no notice" unless that hint is an array holding AT LEAST ONE NON-EMPTY
|
|
48
|
+
* STRING. Measured on this package's implementation: `undefined`, a non-array,
|
|
49
|
+
* `[]`, `['']` and `[1, 2]` ALL yield `notify: false`, and the real host applies
|
|
50
|
+
* the same `requested.length === 0` guard. So `requestConsent({ scopes: [] })`
|
|
51
|
+
* follows the letter of "always pass scopes" and still produces silence — no
|
|
52
|
+
* grant, no refusal, no error — and this hook sits at `null` forever. The reason
|
|
53
|
+
* is that without a named scope proven un-grantable there is no way to
|
|
54
|
+
* distinguish "never" from "not confirmed yet", and guessing is what produced
|
|
55
|
+
* the contradictory screen. Call `requestConsent({ scopes: ['ai:write:budgeted'] })`.
|
|
56
|
+
*
|
|
57
|
+
* 🔴 **UNCORRELATED, AND THAT IS THE SHAPE OF THE PUBLIC API — not an oversight
|
|
58
|
+
* that can be fixed later.** `REQUEST_CONSENT` carries no `requestId`, so
|
|
59
|
+
* `CONSENT_UNAVAILABLE` is a fire-and-forget push and not a reply. Two
|
|
60
|
+
* consequences a block author has to design around: EVERY mounted
|
|
61
|
+
* `useConsentUnavailable()` observes EVERY refusal, and there is no reliable way
|
|
62
|
+
* to filter one out — `scopes` cannot serve as a correlation key because it may
|
|
63
|
+
* legitimately be `[]` and is in any case only advisory. If two independent
|
|
64
|
+
* parts of your block request different scopes, both will see both refusals;
|
|
65
|
+
* keep the request and its refusal UI in one component, or track which request
|
|
66
|
+
* is outstanding yourself. Nothing here awaits anything — against a host that
|
|
67
|
+
* never sends the message the hook simply stays `null`, which is today's
|
|
68
|
+
* behaviour.
|
|
69
|
+
*
|
|
70
|
+
* 🔴 **A refusal is BUFFERED, so one that arrives while this hook is unmounted
|
|
71
|
+
* is not lost.** The transport delivers an unsolicited push only to handlers
|
|
72
|
+
* registered at the moment it arrives, so without a buffer a refusal that landed
|
|
73
|
+
* before mount (or between unmount and remount) vanished — and a dropped refusal
|
|
74
|
+
* puts back the two-message screen above. `requestConsent()` arms the buffer as
|
|
75
|
+
* it sends, and a mounting hook seeds from it. The buffer holds at most the
|
|
76
|
+
* LATEST refusal, is discarded when the block token changes (its premise is that
|
|
77
|
+
* token's scopes), and is cleared by `reset()`. See
|
|
78
|
+
* `internal/consentRefusalLatch.ts`.
|
|
79
|
+
*
|
|
80
|
+
* The subscription goes through the transport, so the handler runs only after
|
|
81
|
+
* the message has cleared the origin allowlist AND the payload validator; the
|
|
82
|
+
* hook never adds its own `window` listener behind that boundary.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* const { requestConsent } = useRequestConsent();
|
|
86
|
+
* const { refusal } = useConsentUnavailable();
|
|
87
|
+
*
|
|
88
|
+
* if (refusal) return <p>Generating isn't available on this page.</p>;
|
|
89
|
+
* return <button onClick={() => requestConsent({ scopes: ['ai:write:budgeted'] })}>Generate</button>;
|
|
90
|
+
*/
|
|
91
|
+
export declare function useConsentUnavailable(): UseConsentUnavailable;
|
|
92
|
+
//# sourceMappingURL=useConsentUnavailable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useConsentUnavailable.d.ts","sourceRoot":"","sources":["../../src/hooks/useConsentUnavailable.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAUzE,YAAY,EAAE,yBAAyB,EAAE,CAAC;AAE1C,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;OASG;IACH,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC1C;;;;;;;;;;;;;OAaG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,wBAAgB,qBAAqB,IAAI,qBAAqB,CA+B7D"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { armConsentRefusalLatch, clearConsentRefusalLatch, readConsentRefusalLatch, } from '../internal/consentRefusalLatch.js';
|
|
3
|
+
import { getTransport } from '../internal/singleton.js';
|
|
4
|
+
import { subscribeTyped } from '../internal/transport.js';
|
|
5
|
+
/**
|
|
6
|
+
* Subscribe to the host's `CONSENT_UNAVAILABLE` push — the signal that a
|
|
7
|
+
* `REQUEST_CONSENT` this block sent can **never** be granted in this
|
|
8
|
+
* environment, because the scope was clamped or withheld at mint. Distinct from
|
|
9
|
+
* "the viewer hasn't confirmed the dialog yet", which produces no message at
|
|
10
|
+
* all.
|
|
11
|
+
*
|
|
12
|
+
* This is the whole point of the message: without it a block keeps telling the
|
|
13
|
+
* user *"Confirm in the Civitai dialog. If you dismissed it, click Generate
|
|
14
|
+
* again"* while the host says the permission is unavailable — two contradictory
|
|
15
|
+
* messages on one screen, and the misleading one is the block's.
|
|
16
|
+
*
|
|
17
|
+
* 🔴 **PRECONDITION — pass `scopes` to `requestConsent()`, with a real scope
|
|
18
|
+
* name in it.** The refusal is computed from the request's `scopes` hint, and
|
|
19
|
+
* both dev hosts and the real host (`resolveUngrantableConsentNotice`) return
|
|
20
|
+
* "no notice" unless that hint is an array holding AT LEAST ONE NON-EMPTY
|
|
21
|
+
* STRING. Measured on this package's implementation: `undefined`, a non-array,
|
|
22
|
+
* `[]`, `['']` and `[1, 2]` ALL yield `notify: false`, and the real host applies
|
|
23
|
+
* the same `requested.length === 0` guard. So `requestConsent({ scopes: [] })`
|
|
24
|
+
* follows the letter of "always pass scopes" and still produces silence — no
|
|
25
|
+
* grant, no refusal, no error — and this hook sits at `null` forever. The reason
|
|
26
|
+
* is that without a named scope proven un-grantable there is no way to
|
|
27
|
+
* distinguish "never" from "not confirmed yet", and guessing is what produced
|
|
28
|
+
* the contradictory screen. Call `requestConsent({ scopes: ['ai:write:budgeted'] })`.
|
|
29
|
+
*
|
|
30
|
+
* 🔴 **UNCORRELATED, AND THAT IS THE SHAPE OF THE PUBLIC API — not an oversight
|
|
31
|
+
* that can be fixed later.** `REQUEST_CONSENT` carries no `requestId`, so
|
|
32
|
+
* `CONSENT_UNAVAILABLE` is a fire-and-forget push and not a reply. Two
|
|
33
|
+
* consequences a block author has to design around: EVERY mounted
|
|
34
|
+
* `useConsentUnavailable()` observes EVERY refusal, and there is no reliable way
|
|
35
|
+
* to filter one out — `scopes` cannot serve as a correlation key because it may
|
|
36
|
+
* legitimately be `[]` and is in any case only advisory. If two independent
|
|
37
|
+
* parts of your block request different scopes, both will see both refusals;
|
|
38
|
+
* keep the request and its refusal UI in one component, or track which request
|
|
39
|
+
* is outstanding yourself. Nothing here awaits anything — against a host that
|
|
40
|
+
* never sends the message the hook simply stays `null`, which is today's
|
|
41
|
+
* behaviour.
|
|
42
|
+
*
|
|
43
|
+
* 🔴 **A refusal is BUFFERED, so one that arrives while this hook is unmounted
|
|
44
|
+
* is not lost.** The transport delivers an unsolicited push only to handlers
|
|
45
|
+
* registered at the moment it arrives, so without a buffer a refusal that landed
|
|
46
|
+
* before mount (or between unmount and remount) vanished — and a dropped refusal
|
|
47
|
+
* puts back the two-message screen above. `requestConsent()` arms the buffer as
|
|
48
|
+
* it sends, and a mounting hook seeds from it. The buffer holds at most the
|
|
49
|
+
* LATEST refusal, is discarded when the block token changes (its premise is that
|
|
50
|
+
* token's scopes), and is cleared by `reset()`. See
|
|
51
|
+
* `internal/consentRefusalLatch.ts`.
|
|
52
|
+
*
|
|
53
|
+
* The subscription goes through the transport, so the handler runs only after
|
|
54
|
+
* the message has cleared the origin allowlist AND the payload validator; the
|
|
55
|
+
* hook never adds its own `window` listener behind that boundary.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const { requestConsent } = useRequestConsent();
|
|
59
|
+
* const { refusal } = useConsentUnavailable();
|
|
60
|
+
*
|
|
61
|
+
* if (refusal) return <p>Generating isn't available on this page.</p>;
|
|
62
|
+
* return <button onClick={() => requestConsent({ scopes: ['ai:write:budgeted'] })}>Generate</button>;
|
|
63
|
+
*/
|
|
64
|
+
export function useConsentUnavailable() {
|
|
65
|
+
const [refusal, setRefusal] = useState(null);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const transport = getTransport();
|
|
68
|
+
// Keep recording even while nothing is mounted, and seed from anything
|
|
69
|
+
// recorded before this mount. Seeding in the effect (not lazy `useState`)
|
|
70
|
+
// is deliberate: the latch is armed here too, so the read has to happen
|
|
71
|
+
// after the arm, and an effect is the only place both can be ordered.
|
|
72
|
+
armConsentRefusalLatch(transport);
|
|
73
|
+
const buffered = readConsentRefusalLatch(transport);
|
|
74
|
+
if (buffered)
|
|
75
|
+
setRefusal(buffered);
|
|
76
|
+
// `subscribeTyped` (not the raw `onMessage`) is what makes `payload` a
|
|
77
|
+
// `ConsentUnavailablePayload` here instead of `unknown` + a cast at the call
|
|
78
|
+
// site — the cast is unchecked, so a payload shape change would compile
|
|
79
|
+
// straight through it in every consuming block.
|
|
80
|
+
return subscribeTyped(transport, 'CONSENT_UNAVAILABLE', (payload) => {
|
|
81
|
+
// Store the payload UNCONDITIONALLY. No `payload.scopes.length` gate: an
|
|
82
|
+
// empty `scopes` is a legitimate refusal (see `UseConsentUnavailable`),
|
|
83
|
+
// and gating on it would drop the message this hook exists to deliver.
|
|
84
|
+
setRefusal(payload);
|
|
85
|
+
});
|
|
86
|
+
}, []);
|
|
87
|
+
const reset = useCallback(() => {
|
|
88
|
+
clearConsentRefusalLatch();
|
|
89
|
+
setRefusal(null);
|
|
90
|
+
}, []);
|
|
91
|
+
return { refusal, reset };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=useConsentUnavailable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useConsentUnavailable.js","sourceRoot":"","sources":["../../src/hooks/useConsentUnavailable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAIzD,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAkC1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAmC,IAAI,CAAC,CAAC;IAE/E,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;QACjC,uEAAuE;QACvE,0EAA0E;QAC1E,wEAAwE;QACxE,sEAAsE;QACtE,sBAAsB,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,QAAQ;YAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;QAEnC,uEAAuE;QACvE,6EAA6E;QAC7E,wEAAwE;QACxE,gDAAgD;QAChD,OAAO,cAAc,CAAC,SAAS,EAAE,qBAAqB,EAAE,CAAC,OAAO,EAAE,EAAE;YAClE,yEAAyE;YACzE,wEAAwE;YACxE,uEAAuE;YACvE,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,wBAAwB,EAAE,CAAC;QAC3B,UAAU,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC"}
|
|
@@ -7,9 +7,27 @@
|
|
|
7
7
|
* the message like every inbound one (origin + `event.source` pinned, only
|
|
8
8
|
* honored after BLOCK_READY) and opens its consent UI.
|
|
9
9
|
*
|
|
10
|
-
* `scopes` is an
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* `scopes` is an advisory hint of which scopes the action needs. It is optional
|
|
11
|
+
* in the signature, and for the GRANT path it genuinely is: the host
|
|
12
|
+
* independently grants the missing set it computed at mint, so a bare
|
|
13
|
+
* `requestConsent()` still opens the consent dialog.
|
|
14
|
+
*
|
|
15
|
+
* 🔴 BUT OMITTING IT DISABLES THE REFUSAL PATH ENTIRELY — pass `scopes`, AND
|
|
16
|
+
* PUT A REAL SCOPE NAME IN IT. The host's `CONSENT_UNAVAILABLE` push (see
|
|
17
|
+
* `useConsentUnavailable`) is computed FROM this hint, and the bar is higher
|
|
18
|
+
* than "an array is present": `resolveUngrantableConsentNotice` returns "no
|
|
19
|
+
* notice" unless the hint is an array containing AT LEAST ONE NON-EMPTY STRING.
|
|
20
|
+
* `undefined`, a non-array, `[]`, `['']` and `[1, 2]` all produce silence —
|
|
21
|
+
* measured on this package's implementation, and the real host has the identical
|
|
22
|
+
* `requested.length === 0` guard, so `requestConsent({ scopes: [] })` obeys the
|
|
23
|
+
* instruction above and STILL gets nothing back. That is deliberate — without a
|
|
24
|
+
* named scope proven un-grantable there is no way to distinguish "can NEVER be
|
|
25
|
+
* granted here" from "the viewer hasn't confirmed the dialog yet", and guessing
|
|
26
|
+
* is what produced the contradictory two-message screen this path exists to
|
|
27
|
+
* remove. The consequence for a block author: call `requestConsent()` bare (or
|
|
28
|
+
* with an empty hint) on an un-grantable surface and you get SILENCE — no grant,
|
|
29
|
+
* no refusal, no error — which reads as a broken message rather than a thin
|
|
30
|
+
* argument.
|
|
13
31
|
*
|
|
14
32
|
* Fire-and-forget: the host doesn't reply. On grant the host re-mints the block
|
|
15
33
|
* token and pushes a TOKEN_REFRESH carrying the now-granted scopes — observe
|