@civitai/blocks-react 0.56.0 → 0.57.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 +34 -2
- package/dist/api/generationResources.d.ts +4 -3
- package/dist/hooks/returnTypeLedger.d.ts +2 -0
- package/dist/hooks/returnTypeLedger.js +2 -0
- package/dist/hooks/useAppStorage.js +2 -2
- package/dist/hooks/useAppWorkflows.d.ts +5 -2
- package/dist/hooks/useAppWorkflows.js +18 -7
- package/dist/hooks/useBlockAnalytics.d.ts +5 -3
- package/dist/hooks/useBlockAnalytics.js +1 -1
- package/dist/hooks/useBlockBreakpoint.d.ts +7 -1
- package/dist/hooks/useBlockContext.d.ts +13 -2
- package/dist/hooks/useBlockContext.js +1 -1
- package/dist/hooks/useBlockResize.d.ts +7 -1
- package/dist/hooks/useBlockResize.js +1 -1
- package/dist/hooks/useBlockSettings.d.ts +6 -1
- package/dist/hooks/useBlockTheme.d.ts +6 -1
- package/dist/hooks/useBlockToken.d.ts +8 -3
- package/dist/hooks/useBlockToken.js +2 -2
- package/dist/hooks/useBuzzAccounts.d.ts +3 -2
- package/dist/hooks/useBuzzAccounts.js +16 -15
- package/dist/hooks/useBuzzBalance.d.ts +3 -2
- package/dist/hooks/useBuzzBalance.js +16 -18
- package/dist/hooks/useBuzzPurchase.d.ts +8 -6
- package/dist/hooks/useBuzzPurchase.js +3 -3
- package/dist/hooks/useBuzzTransactions.d.ts +4 -2
- package/dist/hooks/useBuzzTransactions.js +16 -15
- package/dist/hooks/useBuzzWorkflow.d.ts +71 -28
- package/dist/hooks/useBuzzWorkflow.js +31 -17
- package/dist/hooks/useCheckpointPicker.d.ts +18 -16
- package/dist/hooks/useCheckpointPicker.js +3 -3
- package/dist/hooks/useCivitaiNavigate.d.ts +5 -3
- package/dist/hooks/useCivitaiNavigate.js +1 -1
- package/dist/hooks/useCollectionFollow.js +4 -4
- package/dist/hooks/useConsentUnavailable.js +2 -2
- package/dist/hooks/useCreatePostFromApp.js +4 -4
- package/dist/hooks/useDailyCompensation.d.ts +4 -2
- package/dist/hooks/useDailyCompensation.js +17 -15
- package/dist/hooks/useDirectLoad.d.ts +7 -1
- package/dist/hooks/useDirectLoad.js +1 -1
- package/dist/hooks/useDomainMaturity.d.ts +6 -2
- package/dist/hooks/useGatedImages.d.ts +8 -0
- package/dist/hooks/useGatedImages.js +10 -2
- package/dist/hooks/useGenerationResources.d.ts +5 -3
- package/dist/hooks/useHostOrigin.d.ts +7 -1
- package/dist/hooks/useHostOrigin.js +1 -1
- package/dist/hooks/useImageUpload.d.ts +44 -17
- package/dist/hooks/useImageUpload.js +24 -4
- package/dist/hooks/usePublishGenerationOutputs.js +4 -4
- package/dist/hooks/useRequestConsent.d.ts +7 -5
- package/dist/hooks/useRequestConsent.js +1 -1
- package/dist/hooks/useRequestSequencer.d.ts +71 -0
- package/dist/hooks/useRequestSequencer.js +28 -0
- package/dist/hooks/useRequestSignIn.d.ts +7 -5
- package/dist/hooks/useRequestSignIn.js +1 -1
- package/dist/hooks/useResourcePicker.d.ts +16 -14
- package/dist/hooks/useResourcePicker.js +3 -3
- package/dist/hooks/useSaveImage.js +2 -2
- package/dist/hooks/useSharedStorage.js +2 -2
- package/dist/hooks/useTip.js +1 -1
- package/dist/hooks/useTipAllowance.d.ts +13 -1
- package/dist/hooks/useTipAllowance.js +49 -11
- package/dist/hooks/useViewer.d.ts +3 -2
- package/dist/hooks/useViewer.js +16 -18
- package/dist/hooks/useWildcardPack.d.ts +3 -1
- package/dist/hooks/useWildcardPack.js +19 -14
- package/dist/index.d.ts +30 -14
- package/dist/index.js +7 -7
- package/dist/internal/consentRefusalLatch.d.ts +1 -1
- package/dist/internal/consentRefusalLatch.js +1 -1
- package/dist/internal/liveHost.d.ts +63 -11
- package/dist/internal/liveHost.js +240 -37
- package/dist/internal/mockHost.js +16 -15
- package/dist/internal/pickerOverlay.d.ts +52 -4
- package/dist/internal/pickerOverlay.js +14 -3
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/{internal → transport}/iframeTransport.d.ts +43 -5
- package/dist/{internal → transport}/iframeTransport.js +142 -12
- package/dist/transport/originMatcher.d.ts +92 -0
- package/dist/transport/originMatcher.js +222 -0
- package/dist/transport/requestId.d.ts +113 -0
- package/dist/transport/requestId.js +117 -0
- package/dist/{internal → transport}/validate.d.ts +34 -0
- package/dist/{internal → transport}/validate.js +189 -48
- package/dist/ui/BlockGate.js +1 -1
- package/dist/ui/SettingsForm.d.ts +8 -2
- package/dist/ui/SettingsForm.js +49 -3
- package/package.json +7 -148
- package/dist/api/generationResources.d.ts.map +0 -1
- package/dist/api/generationResources.js.map +0 -1
- package/dist/hooks/SfwGate.d.ts.map +0 -1
- package/dist/hooks/SfwGate.js.map +0 -1
- package/dist/hooks/useAppStorage.d.ts.map +0 -1
- package/dist/hooks/useAppStorage.js.map +0 -1
- package/dist/hooks/useAppWorkflows.d.ts.map +0 -1
- package/dist/hooks/useAppWorkflows.js.map +0 -1
- package/dist/hooks/useBlockAnalytics.d.ts.map +0 -1
- package/dist/hooks/useBlockAnalytics.js.map +0 -1
- package/dist/hooks/useBlockBreakpoint.d.ts.map +0 -1
- package/dist/hooks/useBlockBreakpoint.js.map +0 -1
- package/dist/hooks/useBlockContext.d.ts.map +0 -1
- package/dist/hooks/useBlockContext.js.map +0 -1
- package/dist/hooks/useBlockResize.d.ts.map +0 -1
- package/dist/hooks/useBlockResize.js.map +0 -1
- package/dist/hooks/useBlockSettings.d.ts.map +0 -1
- package/dist/hooks/useBlockSettings.js.map +0 -1
- package/dist/hooks/useBlockTheme.d.ts.map +0 -1
- package/dist/hooks/useBlockTheme.js.map +0 -1
- package/dist/hooks/useBlockToken.d.ts.map +0 -1
- package/dist/hooks/useBlockToken.js.map +0 -1
- package/dist/hooks/useBuzzAccounts.d.ts.map +0 -1
- package/dist/hooks/useBuzzAccounts.js.map +0 -1
- package/dist/hooks/useBuzzBalance.d.ts.map +0 -1
- package/dist/hooks/useBuzzBalance.js.map +0 -1
- package/dist/hooks/useBuzzPurchase.d.ts.map +0 -1
- package/dist/hooks/useBuzzPurchase.js.map +0 -1
- package/dist/hooks/useBuzzTransactions.d.ts.map +0 -1
- package/dist/hooks/useBuzzTransactions.js.map +0 -1
- package/dist/hooks/useBuzzWorkflow.d.ts.map +0 -1
- package/dist/hooks/useBuzzWorkflow.js.map +0 -1
- package/dist/hooks/useCheckpointPicker.d.ts.map +0 -1
- package/dist/hooks/useCheckpointPicker.js.map +0 -1
- package/dist/hooks/useCivitaiNavigate.d.ts.map +0 -1
- package/dist/hooks/useCivitaiNavigate.js.map +0 -1
- package/dist/hooks/useCollectionFollow.d.ts.map +0 -1
- package/dist/hooks/useCollectionFollow.js.map +0 -1
- package/dist/hooks/useConsentUnavailable.d.ts.map +0 -1
- package/dist/hooks/useConsentUnavailable.js.map +0 -1
- package/dist/hooks/useCreatePostFromApp.d.ts.map +0 -1
- package/dist/hooks/useCreatePostFromApp.js.map +0 -1
- package/dist/hooks/useDailyCompensation.d.ts.map +0 -1
- package/dist/hooks/useDailyCompensation.js.map +0 -1
- package/dist/hooks/useDirectLoad.d.ts.map +0 -1
- package/dist/hooks/useDirectLoad.js.map +0 -1
- package/dist/hooks/useDomainMaturity.d.ts.map +0 -1
- package/dist/hooks/useDomainMaturity.js.map +0 -1
- package/dist/hooks/useGatedImages.d.ts.map +0 -1
- package/dist/hooks/useGatedImages.js.map +0 -1
- package/dist/hooks/useGenerationResources.d.ts.map +0 -1
- package/dist/hooks/useGenerationResources.js.map +0 -1
- package/dist/hooks/useHostOrigin.d.ts.map +0 -1
- package/dist/hooks/useHostOrigin.js.map +0 -1
- package/dist/hooks/useImageUpload.d.ts.map +0 -1
- package/dist/hooks/useImageUpload.js.map +0 -1
- package/dist/hooks/usePublishGenerationOutputs.d.ts.map +0 -1
- package/dist/hooks/usePublishGenerationOutputs.js.map +0 -1
- package/dist/hooks/useRequestConsent.d.ts.map +0 -1
- package/dist/hooks/useRequestConsent.js.map +0 -1
- package/dist/hooks/useRequestSignIn.d.ts.map +0 -1
- package/dist/hooks/useRequestSignIn.js.map +0 -1
- package/dist/hooks/useResourcePicker.d.ts.map +0 -1
- package/dist/hooks/useResourcePicker.js.map +0 -1
- package/dist/hooks/useSaveImage.d.ts.map +0 -1
- package/dist/hooks/useSaveImage.js.map +0 -1
- package/dist/hooks/useSharedStorage.d.ts.map +0 -1
- package/dist/hooks/useSharedStorage.js.map +0 -1
- package/dist/hooks/useTip.d.ts.map +0 -1
- package/dist/hooks/useTip.js.map +0 -1
- package/dist/hooks/useTipAllowance.d.ts.map +0 -1
- package/dist/hooks/useTipAllowance.js.map +0 -1
- package/dist/hooks/useViewer.d.ts.map +0 -1
- package/dist/hooks/useViewer.js.map +0 -1
- package/dist/hooks/useWildcardPack.d.ts.map +0 -1
- package/dist/hooks/useWildcardPack.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal/catalog.d.ts.map +0 -1
- package/dist/internal/catalog.js.map +0 -1
- package/dist/internal/consent.d.ts.map +0 -1
- package/dist/internal/consent.js.map +0 -1
- package/dist/internal/consentRefusalLatch.d.ts.map +0 -1
- package/dist/internal/consentRefusalLatch.js.map +0 -1
- package/dist/internal/detector.d.ts.map +0 -1
- package/dist/internal/detector.js.map +0 -1
- package/dist/internal/directLoad.d.ts.map +0 -1
- package/dist/internal/directLoad.js.map +0 -1
- package/dist/internal/iframeTransport.d.ts.map +0 -1
- package/dist/internal/iframeTransport.js.map +0 -1
- package/dist/internal/inlineTransport.d.ts.map +0 -1
- package/dist/internal/inlineTransport.js.map +0 -1
- package/dist/internal/liveHost.d.ts.map +0 -1
- package/dist/internal/liveHost.js.map +0 -1
- package/dist/internal/mockHost.d.ts.map +0 -1
- package/dist/internal/mockHost.js.map +0 -1
- package/dist/internal/originMatcher.d.ts +0 -28
- package/dist/internal/originMatcher.d.ts.map +0 -1
- package/dist/internal/originMatcher.js +0 -89
- package/dist/internal/originMatcher.js.map +0 -1
- package/dist/internal/pickerOverlay.d.ts.map +0 -1
- package/dist/internal/pickerOverlay.js.map +0 -1
- package/dist/internal/replyError.d.ts.map +0 -1
- package/dist/internal/replyError.js.map +0 -1
- package/dist/internal/requestTimeouts.d.ts.map +0 -1
- package/dist/internal/requestTimeouts.js.map +0 -1
- package/dist/internal/singleton.d.ts.map +0 -1
- package/dist/internal/singleton.js.map +0 -1
- package/dist/internal/transport.d.ts.map +0 -1
- package/dist/internal/transport.js.map +0 -1
- package/dist/internal/validate.d.ts.map +0 -1
- package/dist/internal/validate.js.map +0 -1
- package/dist/live.d.ts.map +0 -1
- package/dist/live.js.map +0 -1
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js.map +0 -1
- package/dist/ui/Alert.d.ts.map +0 -1
- package/dist/ui/Alert.js.map +0 -1
- package/dist/ui/Badge.d.ts.map +0 -1
- package/dist/ui/Badge.js.map +0 -1
- package/dist/ui/BlockGate.d.ts.map +0 -1
- package/dist/ui/BlockGate.js.map +0 -1
- package/dist/ui/Button.d.ts.map +0 -1
- package/dist/ui/Button.js.map +0 -1
- package/dist/ui/Card.d.ts.map +0 -1
- package/dist/ui/Card.js.map +0 -1
- package/dist/ui/Collapse.d.ts.map +0 -1
- package/dist/ui/Collapse.js.map +0 -1
- package/dist/ui/FollowButton.d.ts.map +0 -1
- package/dist/ui/FollowButton.js.map +0 -1
- package/dist/ui/Group.d.ts.map +0 -1
- package/dist/ui/Group.js.map +0 -1
- package/dist/ui/Loader.d.ts.map +0 -1
- package/dist/ui/Loader.js.map +0 -1
- package/dist/ui/Modal.d.ts.map +0 -1
- package/dist/ui/Modal.js.map +0 -1
- package/dist/ui/NumberInput.d.ts.map +0 -1
- package/dist/ui/NumberInput.js.map +0 -1
- package/dist/ui/ReportButton.d.ts.map +0 -1
- package/dist/ui/ReportButton.js.map +0 -1
- package/dist/ui/ResourceCard.d.ts.map +0 -1
- package/dist/ui/ResourceCard.js.map +0 -1
- package/dist/ui/SegmentedControl.d.ts.map +0 -1
- package/dist/ui/SegmentedControl.js.map +0 -1
- package/dist/ui/Select.d.ts.map +0 -1
- package/dist/ui/Select.js.map +0 -1
- package/dist/ui/SettingsForm.d.ts.map +0 -1
- package/dist/ui/SettingsForm.js.map +0 -1
- package/dist/ui/Slider.d.ts.map +0 -1
- package/dist/ui/Slider.js.map +0 -1
- package/dist/ui/Stack.d.ts.map +0 -1
- package/dist/ui/Stack.js.map +0 -1
- package/dist/ui/TextInput.d.ts.map +0 -1
- package/dist/ui/TextInput.js.map +0 -1
- package/dist/ui/Textarea.d.ts.map +0 -1
- package/dist/ui/Textarea.js.map +0 -1
- package/dist/ui/TipButton.d.ts.map +0 -1
- package/dist/ui/TipButton.js.map +0 -1
- package/dist/ui/index.d.ts.map +0 -1
- package/dist/ui/index.js.map +0 -1
- package/dist/ui/styles.d.ts.map +0 -1
- package/dist/ui/styles.js.map +0 -1
- /package/dist/{internal → transport}/detector.d.ts +0 -0
- /package/dist/{internal → transport}/detector.js +0 -0
- /package/dist/{internal → transport}/directLoad.d.ts +0 -0
- /package/dist/{internal → transport}/directLoad.js +0 -0
- /package/dist/{internal → transport}/inlineTransport.d.ts +0 -0
- /package/dist/{internal → transport}/inlineTransport.js +0 -0
- /package/dist/{internal → transport}/requestTimeouts.d.ts +0 -0
- /package/dist/{internal → transport}/requestTimeouts.js +0 -0
- /package/dist/{internal → transport}/singleton.d.ts +0 -0
- /package/dist/{internal → transport}/singleton.js +0 -0
- /package/dist/{internal → transport}/transport.d.ts +0 -0
- /package/dist/{internal → transport}/transport.js +0 -0
|
@@ -24,12 +24,31 @@
|
|
|
24
24
|
* viewer, decodes the token JWT payload for the block identity / scopes /
|
|
25
25
|
* budget / maturity, and dispatches `BLOCK_INIT`.
|
|
26
26
|
*
|
|
27
|
-
* REAL NETWORK, REAL MONEY: unlike the mock host, this calls `fetch`.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* real compute. The token's per-call
|
|
32
|
-
* server-side bounds (scope doc §4).
|
|
27
|
+
* REAL NETWORK, REAL MONEY: unlike the mock host, this calls `fetch`. Every
|
|
28
|
+
* call is Bearer-authed with the dev token and leaves this file through one of
|
|
29
|
+
* exactly two chokepoints — {@link rawTrpcCall} (every tRPC procedure) and the
|
|
30
|
+
* `/api/v1/blocks/me` viewer read in `resolveViewer`. A successful submit
|
|
31
|
+
* SPENDS the dev's OWN real Buzz against real compute. The token's per-call
|
|
32
|
+
* budget + the per-user daily cap are the server-side bounds (scope doc §4).
|
|
33
|
+
* (The picker overlay does its own catalog fetch — that lives in
|
|
34
|
+
* `pickerOverlay.ts`, not here; see PICKERS below.)
|
|
35
|
+
*
|
|
36
|
+
* 🔴 THE BLOCK BELOW IS DERIVED FROM THIS FILE. DO NOT HAND-EDIT IT — and do
|
|
37
|
+
* not re-introduce a typed "the only X is …" list anywhere in this header.
|
|
38
|
+
* This file grew from 5 network calls to 30 and from 1 refusal to 7 while the
|
|
39
|
+
* header still said "the only network it does is … the four `blocks.*Workflow`
|
|
40
|
+
* mutations" and "the ONE capability live mode cannot SERVE" (#387, #14). An
|
|
41
|
+
* exhaustive count typed into a 2,100-line file that grows every release is a
|
|
42
|
+
* claim nobody re-measures. So it is measured:
|
|
43
|
+
* `tests/guards/livehost-header-enumerations.test.mjs` regenerates the block
|
|
44
|
+
* from the code and fails with the exact replacement text when it drifts.
|
|
45
|
+
*
|
|
46
|
+
* --- BEGIN DERIVED ---
|
|
47
|
+
* fetch chokepoints ..... 2
|
|
48
|
+
* REST endpoints ........ GET /api/v1/blocks/me
|
|
49
|
+
* tRPC procedures ....... 29 = blocks.* (14) + apps.shared.* (10) + apps.storage.* (5)
|
|
50
|
+
* switch case labels .... 45, of which 7 REFUSE (enumerated under SCOPE below)
|
|
51
|
+
* --- END DERIVED ---
|
|
33
52
|
*
|
|
34
53
|
* PICKERS (Phase 1 of "make dev:live a faithful local host"): the live host
|
|
35
54
|
* SERVES the resource pickers locally. On `OPEN_CHECKPOINT_PICKER` /
|
|
@@ -57,14 +76,48 @@
|
|
|
57
76
|
* honest error "block token lacks modelId context"; that real outcome is
|
|
58
77
|
* surfaced to the block. With a model-slot token it persists for real.
|
|
59
78
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
79
|
+
* SHARED STORAGE (#386): the live host SERVES all TEN `SHARED_*` bridges —
|
|
80
|
+
* `apps.shared.{list,get,getCount,getCounts,append,update,vote,unvote,withdraw,
|
|
81
|
+
* report}` — on the same block-token convention as APP_STORAGE. `SHARED_GET`
|
|
82
|
+
* and `SHARED_REPORT` were the last two missing; until #386 they fell through
|
|
83
|
+
* the switch's `default: return` with no reply at all, so `useSharedStorage()`
|
|
84
|
+
* hung to the 30s protocol timeout in dev:live for code that worked in dev:mock.
|
|
85
|
+
*
|
|
86
|
+
* 🔴 SCOPE — the capabilities live mode still cannot SERVE. Each REFUSES on its
|
|
87
|
+
* own reply channel — `logOnce` (or `console.info`) plus an honest error, never
|
|
88
|
+
* a fabricated success and never silence. Two guards stand behind this list:
|
|
89
|
+
* `tests/guards/livehost-message-coverage.test.mjs` asserts that no block→parent
|
|
90
|
+
* type is left without a case at all (the failure mode a refusal is NOT), and
|
|
91
|
+
* `tests/guards/livehost-header-enumerations.test.mjs` derives the refusal set
|
|
92
|
+
* from the handlers below and asserts these bullets are exactly it — so the
|
|
93
|
+
* list cannot silently go stale again the way it did through #14/#387. Add a
|
|
94
|
+
* refusal handler without a bullet, or leave a bullet whose handler now serves,
|
|
95
|
+
* and that guard fails naming the difference.
|
|
96
|
+
*
|
|
97
|
+
* • OPEN_BUZZ_PURCHASE — no headless / block-token Buzz-purchase path (buying
|
|
98
|
+
* Buzz strictly requires the interactive Stripe/Paddle host chrome). Deep-
|
|
99
|
+
* links the real purchase page and replies `purchased: false`.
|
|
100
|
+
* • SET_COLLECTION_FOLLOW — needs the session-authed follow procedures plus
|
|
101
|
+
* host-chrome consent. Replies `collection-unavailable`.
|
|
102
|
+
* • CREATE_POST_FROM_APP — needs the server-resolved preview plus host-chrome
|
|
103
|
+
* confirm before a PUBLIC post is written. Replies with a refusal.
|
|
104
|
+
* • GET_WILDCARD_PACK — needs the session-authed resolve plus the in-tab
|
|
105
|
+
* zip/yaml parse that lives in civitai, not this SDK. Replies `parse-failed`.
|
|
106
|
+
* • OPEN_IMAGE_UPLOAD — needs the host's native modal + session-authed byte
|
|
107
|
+
* pipeline. Replies DISMISSED (the hook resolves `null`).
|
|
108
|
+
* • SAVE_IMAGE — the download bridge is the production host's unsandboxed top
|
|
109
|
+
* frame plus its CDN origin allowlist / gated per-viewer read. Replies with
|
|
110
|
+
* a refusal (#386).
|
|
111
|
+
* • REQUEST_CONSENT — live mode cannot grant a scope the token does not carry.
|
|
112
|
+
* Pushes `CONSENT_UNAVAILABLE` when the ask is genuinely ungrantable.
|
|
113
|
+
*
|
|
114
|
+
* In every case REFUSING IS THE POINT: serving a weakened local imitation would
|
|
115
|
+
* let a block prove out a flow production does not have, and ship having never
|
|
116
|
+
* handled the refusal. Use dev:mock for those paths. See the handlers below.
|
|
65
117
|
*/
|
|
66
118
|
import { consentUnavailablePayload, resolveUngrantableConsentNotice } from './consent.js';
|
|
67
|
-
import { hostContextWithTheme } from '
|
|
119
|
+
import { hostContextWithTheme } from '../transport/transport.js';
|
|
120
|
+
import { isRoutableRequestId } from '../transport/requestId.js';
|
|
68
121
|
import { openPickerOverlay, } from './pickerOverlay.js';
|
|
69
122
|
/** Default civitai backend the live host forwards to. */
|
|
70
123
|
const DEFAULT_BACKEND_BASE_URL = 'https://civitai.com';
|
|
@@ -165,6 +218,35 @@ function isTransientHttpStatus(status) {
|
|
|
165
218
|
* block's own poll loop tries again.
|
|
166
219
|
*/
|
|
167
220
|
const POLL_RETRY_BACKOFF_MS = [250, 500, 1000];
|
|
221
|
+
/**
|
|
222
|
+
* Map ONE raw `apps.shared.*` row → the `SharedStorageItemWire` shape the
|
|
223
|
+
* protocol puts on `SHARED_LIST_RESULT.items` and `SHARED_GET_RESULT.item`.
|
|
224
|
+
*
|
|
225
|
+
* ONE mapper, not one per case: the two reads return the same row and the
|
|
226
|
+
* fields are lenient-by-default (a missing `authorUserId`/`count` becomes `0`,
|
|
227
|
+
* dates are ISO-normalized whether the transport handed back a `Date` or a
|
|
228
|
+
* string). A second open-coded copy is how the two reads drift.
|
|
229
|
+
*
|
|
230
|
+
* `viewerVoted` is forwarded ONLY when the server actually sent a boolean —
|
|
231
|
+
* the field is OPTIONAL and additive, and the block-side hook defaults a
|
|
232
|
+
* missing value to `false`. Omitting it when the server omits it keeps a host
|
|
233
|
+
* that predates the field indistinguishable from today's behaviour; forwarding
|
|
234
|
+
* it when the server sends it is what lets a `?g=<key>` deep-link hydrate its
|
|
235
|
+
* vote button instead of guessing.
|
|
236
|
+
*/
|
|
237
|
+
function sharedItemWireFrom(raw) {
|
|
238
|
+
const e = raw;
|
|
239
|
+
const iso = (d) => (d instanceof Date ? d.toISOString() : String(d));
|
|
240
|
+
return {
|
|
241
|
+
key: String(e.key),
|
|
242
|
+
authorUserId: typeof e.authorUserId === 'number' ? e.authorUserId : 0,
|
|
243
|
+
value: e.value,
|
|
244
|
+
count: typeof e.count === 'number' ? e.count : 0,
|
|
245
|
+
createdAt: iso(e.createdAt),
|
|
246
|
+
updatedAt: iso(e.updatedAt),
|
|
247
|
+
...(typeof e.viewerVoted === 'boolean' ? { viewerVoted: e.viewerVoted } : {}),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
168
250
|
/**
|
|
169
251
|
* Create a LIVE host that forwards the App-Block postMessage protocol to the
|
|
170
252
|
* real Civitai backend. Returns the same {@link MockHost} interface as
|
|
@@ -472,6 +554,14 @@ export function createLiveHost(options) {
|
|
|
472
554
|
const openPicker = (params, resultType, requestId) => {
|
|
473
555
|
const handle = openPickerOverlay({
|
|
474
556
|
type: params.type,
|
|
557
|
+
// 🔴 THE REPLY CHANNEL TRAVELS WITH THE REQUEST (#391). It used to stop
|
|
558
|
+
// here: the overlay only received `params.type` and branched its
|
|
559
|
+
// converter on THAT, so an `OPEN_RESOURCE_PICKER` asking for a
|
|
560
|
+
// Checkpoint replied on `RESOURCE_PICKER_RESULT` with the CHECKPOINT
|
|
561
|
+
// projection — no `modelType`, which `BlockResourceInfo` requires.
|
|
562
|
+
// Two independent facts (what was asked for, what channel replies) were
|
|
563
|
+
// conflated into one; passing the channel down is what separates them.
|
|
564
|
+
resultChannel: resultType,
|
|
475
565
|
baseUrl,
|
|
476
566
|
token: rawToken,
|
|
477
567
|
fetchImpl,
|
|
@@ -522,7 +612,7 @@ export function createLiveHost(options) {
|
|
|
522
612
|
dispatchToBlock({
|
|
523
613
|
type: 'TOKEN_REFRESH_RESPONSE',
|
|
524
614
|
payload: {
|
|
525
|
-
...(requestId ? { requestId } : {}),
|
|
615
|
+
...(isRoutableRequestId(requestId) ? { requestId } : {}),
|
|
526
616
|
token: wrappedTokenFrom(rawToken ?? '', decoded),
|
|
527
617
|
},
|
|
528
618
|
});
|
|
@@ -655,7 +745,7 @@ export function createLiveHost(options) {
|
|
|
655
745
|
// Mirrors the `APP_STORAGE_SET` value-or-error convention. Drop a
|
|
656
746
|
// request with no `requestId` — the block correlates the reply by it,
|
|
657
747
|
// so a reply without one is unroutable.
|
|
658
|
-
if (
|
|
748
|
+
if (!isRoutableRequestId(requestId))
|
|
659
749
|
return;
|
|
660
750
|
void callTrpcData('blocks.getMyBuzzBalance', { blockToken: rawToken }, 'POST').then((r) => {
|
|
661
751
|
dispatchToBlock({
|
|
@@ -676,7 +766,7 @@ export function createLiveHost(options) {
|
|
|
676
766
|
// TEXT error on failure (anon / banned viewer). Drop a request with
|
|
677
767
|
// no `requestId` — the block correlates the reply by it, so a reply
|
|
678
768
|
// without one is unroutable.
|
|
679
|
-
if (
|
|
769
|
+
if (!isRoutableRequestId(requestId))
|
|
680
770
|
return;
|
|
681
771
|
void callTrpcData('blocks.getMyViewer', { blockToken: rawToken }, 'POST').then((r) => {
|
|
682
772
|
dispatchToBlock({
|
|
@@ -693,7 +783,7 @@ export function createLiveHost(options) {
|
|
|
693
783
|
// MUTATION (POST). params are spread FIRST so the host-authoritative
|
|
694
784
|
// `blockToken` (spread LAST) can never be overridden — mirrors the
|
|
695
785
|
// real host. FREE-TEXT error on failure. Unroutable without requestId.
|
|
696
|
-
if (
|
|
786
|
+
if (!isRoutableRequestId(requestId))
|
|
697
787
|
return;
|
|
698
788
|
void callTrpcData('blocks.getMyBuzzTransactions', { ...(typed.payload?.params ?? {}), blockToken: rawToken }, 'POST').then((r) => {
|
|
699
789
|
dispatchToBlock({
|
|
@@ -706,7 +796,7 @@ export function createLiveHost(options) {
|
|
|
706
796
|
return;
|
|
707
797
|
}
|
|
708
798
|
case 'GET_BUZZ_ACCOUNTS': {
|
|
709
|
-
if (
|
|
799
|
+
if (!isRoutableRequestId(requestId))
|
|
710
800
|
return;
|
|
711
801
|
void callTrpcData('blocks.getMyBuzzAccounts', { blockToken: rawToken }, 'POST').then((r) => {
|
|
712
802
|
dispatchToBlock({
|
|
@@ -719,7 +809,7 @@ export function createLiveHost(options) {
|
|
|
719
809
|
return;
|
|
720
810
|
}
|
|
721
811
|
case 'GET_DAILY_COMPENSATION': {
|
|
722
|
-
if (
|
|
812
|
+
if (!isRoutableRequestId(requestId))
|
|
723
813
|
return;
|
|
724
814
|
void callTrpcData('blocks.getMyDailyCompensation', { ...(typed.payload?.params ?? {}), blockToken: rawToken }, 'POST').then((r) => {
|
|
725
815
|
dispatchToBlock({
|
|
@@ -753,7 +843,7 @@ export function createLiveHost(options) {
|
|
|
753
843
|
// loop against a harness that has no sign-in. Use dev:mock (its
|
|
754
844
|
// `collectionFollowError` knob covers `declined` and the rest) to
|
|
755
845
|
// exercise the real refusal set.
|
|
756
|
-
if (
|
|
846
|
+
if (!isRoutableRequestId(requestId))
|
|
757
847
|
return;
|
|
758
848
|
logOnce('collection-follow', 'SET_COLLECTION_FOLLOW is not supported in dev:live (it needs the session-authed ' +
|
|
759
849
|
'follow procedures plus host-chrome consent). Replying `collection-unavailable`. ' +
|
|
@@ -786,7 +876,7 @@ export function createLiveHost(options) {
|
|
|
786
876
|
// surfaces it as `.code === undefined`, i.e. a message worth showing.
|
|
787
877
|
// Use dev:mock (its `createPostError` knob covers `declined` and the
|
|
788
878
|
// rest) to exercise the refusal set.
|
|
789
|
-
if (
|
|
879
|
+
if (!isRoutableRequestId(requestId))
|
|
790
880
|
return;
|
|
791
881
|
logOnce('create-post', 'CREATE_POST_FROM_APP is not supported in dev:live (it needs the server-resolved ' +
|
|
792
882
|
'preview plus host-chrome consent). Replying with a refusal. Use dev:mock to ' +
|
|
@@ -809,7 +899,7 @@ export function createLiveHost(options) {
|
|
|
809
899
|
// honest `parse-failed` (never a fabricated pack) so the block's hook
|
|
810
900
|
// surfaces a typed error rather than hanging. Use dev:mock for the
|
|
811
901
|
// full wildcard-import path.
|
|
812
|
-
if (
|
|
902
|
+
if (!isRoutableRequestId(requestId))
|
|
813
903
|
return;
|
|
814
904
|
logOnce('wildcard-pack', 'GET_WILDCARD_PACK is not supported in dev:live (it needs the session-authed ' +
|
|
815
905
|
'resolve + in-tab zip parse). Replying `parse-failed`. Use dev:mock to exercise ' +
|
|
@@ -828,7 +918,7 @@ export function createLiveHost(options) {
|
|
|
828
918
|
// per-app tag filter server-side (the input carries no `tags`). It
|
|
829
919
|
// returns a plain `{ workflows, cursor }`, unwrapped with `callTrpcData`.
|
|
830
920
|
// FREE-TEXT error on failure. Unroutable without requestId.
|
|
831
|
-
if (
|
|
921
|
+
if (!isRoutableRequestId(requestId))
|
|
832
922
|
return;
|
|
833
923
|
void callTrpcData('blocks.queryAppWorkflows', { ...(typed.payload?.params ?? {}), blockToken: rawToken }, 'POST').then((r) => {
|
|
834
924
|
dispatchToBlock({
|
|
@@ -847,7 +937,7 @@ export function createLiveHost(options) {
|
|
|
847
937
|
// terminal projection), unwrapped with `callTrpcData`. FREE-TEXT error
|
|
848
938
|
// on failure (FORBIDDEN / transport). Drop a request with no requestId
|
|
849
939
|
// or a missing/empty workflowId (mirrors the real host).
|
|
850
|
-
if (
|
|
940
|
+
if (!isRoutableRequestId(requestId))
|
|
851
941
|
return;
|
|
852
942
|
const cancelId = typed.payload?.workflowId;
|
|
853
943
|
if (typeof cancelId !== 'string' || cancelId.length === 0)
|
|
@@ -870,7 +960,7 @@ export function createLiveHost(options) {
|
|
|
870
960
|
// re-derives ownership, re-uploads + FULL-scans server-side. Returns a
|
|
871
961
|
// plain `{ imageIds }`, unwrapped with `callTrpcData`. FREE-TEXT error
|
|
872
962
|
// on failure. Unroutable without requestId.
|
|
873
|
-
if (
|
|
963
|
+
if (!isRoutableRequestId(requestId))
|
|
874
964
|
return;
|
|
875
965
|
void callTrpcData('blocks.publishGenerationOutputs', {
|
|
876
966
|
blockToken: rawToken,
|
|
@@ -895,7 +985,7 @@ export function createLiveHost(options) {
|
|
|
895
985
|
// browsing-level clamp server-side and returns a plain `{ images }`
|
|
896
986
|
// (`BlockGatedImage[]` — visible/hidden), unwrapped with `callTrpcData`.
|
|
897
987
|
// FREE-TEXT error on failure. Unroutable without requestId.
|
|
898
|
-
if (
|
|
988
|
+
if (!isRoutableRequestId(requestId))
|
|
899
989
|
return;
|
|
900
990
|
void callTrpcData('blocks.getImagesByIds', { blockToken: rawToken, imageIds: typed.payload?.imageIds ?? [] }, 'POST').then((r) => {
|
|
901
991
|
dispatchToBlock({
|
|
@@ -1173,18 +1263,7 @@ export function createLiveHost(options) {
|
|
|
1173
1263
|
return;
|
|
1174
1264
|
}
|
|
1175
1265
|
const rawItems = r.data?.items;
|
|
1176
|
-
const items = (Array.isArray(rawItems) ? rawItems : []).map(
|
|
1177
|
-
const e = it;
|
|
1178
|
-
const iso = (d) => d instanceof Date ? d.toISOString() : String(d);
|
|
1179
|
-
return {
|
|
1180
|
-
key: String(e.key),
|
|
1181
|
-
authorUserId: typeof e.authorUserId === 'number' ? e.authorUserId : 0,
|
|
1182
|
-
value: e.value,
|
|
1183
|
-
count: typeof e.count === 'number' ? e.count : 0,
|
|
1184
|
-
createdAt: iso(e.createdAt),
|
|
1185
|
-
updatedAt: iso(e.updatedAt),
|
|
1186
|
-
};
|
|
1187
|
-
});
|
|
1266
|
+
const items = (Array.isArray(rawItems) ? rawItems : []).map(sharedItemWireFrom);
|
|
1188
1267
|
const nextCursor = r.data?.nextCursor;
|
|
1189
1268
|
dispatchToBlock({
|
|
1190
1269
|
type: 'SHARED_LIST_RESULT',
|
|
@@ -1238,6 +1317,56 @@ export function createLiveHost(options) {
|
|
|
1238
1317
|
});
|
|
1239
1318
|
return;
|
|
1240
1319
|
}
|
|
1320
|
+
case 'SHARED_GET': {
|
|
1321
|
+
// SERVED, not refused (#386): this is the single-row companion to
|
|
1322
|
+
// SHARED_LIST's paged read and forwards to the SAME block-token
|
|
1323
|
+
// procedure family the other eight `SHARED_*` bridges already use.
|
|
1324
|
+
// Nothing about it needs host chrome or a session — so refusing it
|
|
1325
|
+
// would be a limitation this host does not actually have.
|
|
1326
|
+
//
|
|
1327
|
+
// Before this case existed the message fell through `default:
|
|
1328
|
+
// return` with NO reply, so `useSharedStorage().get(key)` sat for
|
|
1329
|
+
// the full 30s protocol timeout and then threw a generic
|
|
1330
|
+
// RequestTimeoutError — while the identical call resolved under
|
|
1331
|
+
// dev:mock.
|
|
1332
|
+
//
|
|
1333
|
+
// `item: null` on a miss (never an error): the protocol makes a
|
|
1334
|
+
// missing / hidden / withdrawn row resolve cleanly to "not found"
|
|
1335
|
+
// so a `?g=` deep-link to a moderated row cannot distinguish it
|
|
1336
|
+
// from an absent one. Only a HOST-SIDE failure sets `error`.
|
|
1337
|
+
const key = typed.payload?.key ?? '';
|
|
1338
|
+
void callTrpcData('apps.shared.get', { blockToken: rawToken, key }, 'GET').then((r) => {
|
|
1339
|
+
if (r.error) {
|
|
1340
|
+
dispatchToBlock({
|
|
1341
|
+
type: 'SHARED_GET_RESULT',
|
|
1342
|
+
payload: { requestId: requestId ?? '', item: null, error: r.error },
|
|
1343
|
+
});
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
// ENVELOPE-LENIENT. The sibling reads are not consistent about
|
|
1347
|
+
// this — `getCount` returns `{ count }` and `list` returns
|
|
1348
|
+
// `{ items }`, but `withdraw` returns the bare `{ deleted }` —
|
|
1349
|
+
// and an envelope guess that is wrong would turn every hit into
|
|
1350
|
+
// a silent `null`, i.e. "the row does not exist" for a row that
|
|
1351
|
+
// does. So accept `{ item: <row> }` OR the bare row, identified
|
|
1352
|
+
// by carrying a `key`. Anything else is a genuine miss.
|
|
1353
|
+
const data = r.data;
|
|
1354
|
+
const envelope = data?.item;
|
|
1355
|
+
const rawItem = envelope && typeof envelope === 'object'
|
|
1356
|
+
? envelope
|
|
1357
|
+
: data && typeof data.key === 'string'
|
|
1358
|
+
? data
|
|
1359
|
+
: null;
|
|
1360
|
+
dispatchToBlock({
|
|
1361
|
+
type: 'SHARED_GET_RESULT',
|
|
1362
|
+
payload: {
|
|
1363
|
+
requestId: requestId ?? '',
|
|
1364
|
+
item: rawItem ? sharedItemWireFrom(rawItem) : null,
|
|
1365
|
+
},
|
|
1366
|
+
});
|
|
1367
|
+
});
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1241
1370
|
case 'SHARED_APPEND': {
|
|
1242
1371
|
const value = typed.payload?.value;
|
|
1243
1372
|
void callTrpcData('apps.shared.append', { blockToken: rawToken, value }, 'POST').then((r) => {
|
|
@@ -1331,6 +1460,80 @@ export function createLiveHost(options) {
|
|
|
1331
1460
|
});
|
|
1332
1461
|
return;
|
|
1333
1462
|
}
|
|
1463
|
+
case 'SHARED_REPORT': {
|
|
1464
|
+
// SERVED, not refused (#386): same block-token procedure family as
|
|
1465
|
+
// the other `SHARED_*` bridges — the trust gate and the rate limit
|
|
1466
|
+
// are SERVER-side, so forwarding here exercises the real bounds
|
|
1467
|
+
// rather than a local imitation of them. Nothing needs host chrome.
|
|
1468
|
+
//
|
|
1469
|
+
// Before this case existed the message fell through `default:
|
|
1470
|
+
// return` with NO reply, so `useSharedStorage().report(key, reason)`
|
|
1471
|
+
// sat for the full 30s protocol timeout.
|
|
1472
|
+
//
|
|
1473
|
+
// `reason` is OPTIONAL free text — forwarded only when the block
|
|
1474
|
+
// actually sent a string, so an omitted reason stays omitted rather
|
|
1475
|
+
// than becoming `''` (the server bounds it either way).
|
|
1476
|
+
const key = typed.payload?.key ?? '';
|
|
1477
|
+
const reportInput = { blockToken: rawToken, key };
|
|
1478
|
+
if (typeof typed.payload?.reason === 'string') {
|
|
1479
|
+
reportInput.reason = typed.payload.reason;
|
|
1480
|
+
}
|
|
1481
|
+
void callTrpcData('apps.shared.report', reportInput, 'POST').then((r) => {
|
|
1482
|
+
dispatchToBlock({
|
|
1483
|
+
type: 'SHARED_REPORT_RESULT',
|
|
1484
|
+
payload: r.error
|
|
1485
|
+
? { requestId: requestId ?? '', ok: false, error: r.error }
|
|
1486
|
+
: { requestId: requestId ?? '', ok: true },
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
case 'SAVE_IMAGE': {
|
|
1492
|
+
// 🔴 REFUSED, and refusing is the POINT — the same reasoning as
|
|
1493
|
+
// CREATE_POST_FROM_APP above, one step stronger.
|
|
1494
|
+
//
|
|
1495
|
+
// The real bridge is a SECURITY boundary, not a convenience: the
|
|
1496
|
+
// host fetches the blob in its UNSANDBOXED top frame, and the two
|
|
1497
|
+
// request variants are gated differently.
|
|
1498
|
+
// • `url` — the host ALLOWLISTS the origin to the civitai
|
|
1499
|
+
// image/blob CDN and REFUSES an arbitrary host, because an
|
|
1500
|
+
// unverified block's `url` is untrusted input to a host-side
|
|
1501
|
+
// fetch.
|
|
1502
|
+
// • `imageId` — the host resolves it through the SAME per-viewer
|
|
1503
|
+
// gated read that backs GET_IMAGES_BY_IDS, so a withheld or
|
|
1504
|
+
// above-ceiling image can never be coerced into a download.
|
|
1505
|
+
//
|
|
1506
|
+
// This harness has NEITHER gate. The allowlist is the production
|
|
1507
|
+
// host's, not this SDK's — there is no origin set here to check
|
|
1508
|
+
// against — so a dev-side "download it anyway" would accept URLs
|
|
1509
|
+
// production refuses and let a block ship having never once handled
|
|
1510
|
+
// the refusal. Inventing a divergent allowlist is worse than having
|
|
1511
|
+
// none: it would be a security posture nobody reviewed, only ever
|
|
1512
|
+
// exercised in dev.
|
|
1513
|
+
//
|
|
1514
|
+
// So: an honest, IMMEDIATE refusal on the type's own channel rather
|
|
1515
|
+
// than the silent 30s hang this case replaces (#386). FREE TEXT,
|
|
1516
|
+
// not a host code — `SAVE_IMAGE_RESULT.error` is an open string and
|
|
1517
|
+
// `useSaveImage()` surfaces it verbatim; none of the host's real
|
|
1518
|
+
// failure strings ("disallowed origin", "hidden") would be true
|
|
1519
|
+
// here. Use dev:mock to exercise the resolve path, and the real
|
|
1520
|
+
// site to exercise the gates.
|
|
1521
|
+
if (!isRoutableRequestId(requestId))
|
|
1522
|
+
return;
|
|
1523
|
+
logOnce('save-image', 'SAVE_IMAGE is not supported in dev:live (the download bridge is the production ' +
|
|
1524
|
+
"host's unsandboxed top frame plus its CDN origin allowlist / gated per-viewer " +
|
|
1525
|
+
'read — neither of which this harness has). Replying with a refusal. Use dev:mock ' +
|
|
1526
|
+
'to exercise the save path.');
|
|
1527
|
+
dispatchToBlock({
|
|
1528
|
+
type: 'SAVE_IMAGE_RESULT',
|
|
1529
|
+
payload: {
|
|
1530
|
+
requestId,
|
|
1531
|
+
ok: false,
|
|
1532
|
+
error: 'saving images is not supported in dev:live — use dev:mock',
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1334
1537
|
case 'NAVIGATE': {
|
|
1335
1538
|
const path = typed.payload?.path ?? '';
|
|
1336
1539
|
const target = typed.payload?.target ?? 'current';
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
*/
|
|
40
40
|
import { APP_STORAGE_ERROR_REQUEST_FAILED, APP_STORAGE_ERROR_USER_QUOTA_EXCEEDED, APP_STORAGE_ERROR_USER_ROW_LIMIT, APP_STORAGE_ERROR_VALUE_TOO_LARGE, APP_STORAGE_MAX_BYTES, APP_STORAGE_MAX_ROWS, APP_STORAGE_MAX_VALUE_BYTES, BrowsingLevel, SFW_LEVELS, } from '@civitai/app-sdk/blocks';
|
|
41
41
|
import { consentUnavailablePayload, resolveUngrantableConsentNotice } from './consent.js';
|
|
42
|
-
import { hostContextWithTheme } from '
|
|
42
|
+
import { hostContextWithTheme } from '../transport/transport.js';
|
|
43
|
+
import { isRoutableRequestId } from '../transport/requestId.js';
|
|
43
44
|
/**
|
|
44
45
|
* The block's preferred Buzz pool. On a `textToImage` {@link WorkflowBody} it's
|
|
45
46
|
* the top-level `accountType`; on a `customComfy` RECIPE body it lives under
|
|
@@ -891,7 +892,7 @@ export function createMockHost(options = {}) {
|
|
|
891
892
|
case 'REQUEST_TOKEN':
|
|
892
893
|
dispatchToBlock({
|
|
893
894
|
type: 'TOKEN_REFRESH_RESPONSE',
|
|
894
|
-
payload: { ...(requestId ? { requestId } : {}), token: nextToken() },
|
|
895
|
+
payload: { ...(isRoutableRequestId(requestId) ? { requestId } : {}), token: nextToken() },
|
|
895
896
|
});
|
|
896
897
|
return;
|
|
897
898
|
case 'REQUEST_CONSENT': {
|
|
@@ -1215,7 +1216,7 @@ export function createMockHost(options = {}) {
|
|
|
1215
1216
|
// exactly. Drop a request with no requestId — the block correlates
|
|
1216
1217
|
// the reply by it, so a reply without one is unroutable (matches
|
|
1217
1218
|
// the sibling request cases + createLiveHost).
|
|
1218
|
-
if (
|
|
1219
|
+
if (!isRoutableRequestId(requestId))
|
|
1219
1220
|
return;
|
|
1220
1221
|
// Simulated read failure: reply with the error shape
|
|
1221
1222
|
// (`{ requestId, error }`, no `balance`) — byte-for-byte
|
|
@@ -1239,7 +1240,7 @@ export function createMockHost(options = {}) {
|
|
|
1239
1240
|
// with no requestId — the block correlates the reply by it, so a
|
|
1240
1241
|
// reply without one is unroutable (matches the sibling request cases
|
|
1241
1242
|
// + createLiveHost).
|
|
1242
|
-
if (
|
|
1243
|
+
if (!isRoutableRequestId(requestId))
|
|
1243
1244
|
return;
|
|
1244
1245
|
// Simulated read failure: reply with the error shape
|
|
1245
1246
|
// (`{ requestId, error }`, no `viewer`) — byte-for-byte
|
|
@@ -1261,7 +1262,7 @@ export function createMockHost(options = {}) {
|
|
|
1261
1262
|
// Buzz-dashboard ledger read. Drop a request with no requestId
|
|
1262
1263
|
// (unroutable). A forced read error replies with the FREE-TEXT error
|
|
1263
1264
|
// variant (mirrors the real host forwarding err.message).
|
|
1264
|
-
if (
|
|
1265
|
+
if (!isRoutableRequestId(requestId))
|
|
1265
1266
|
return;
|
|
1266
1267
|
if (buzzReadError !== undefined) {
|
|
1267
1268
|
dispatchToBlock({
|
|
@@ -1285,7 +1286,7 @@ export function createMockHost(options = {}) {
|
|
|
1285
1286
|
return;
|
|
1286
1287
|
}
|
|
1287
1288
|
case 'GET_BUZZ_ACCOUNTS': {
|
|
1288
|
-
if (
|
|
1289
|
+
if (!isRoutableRequestId(requestId))
|
|
1289
1290
|
return;
|
|
1290
1291
|
if (buzzReadError !== undefined) {
|
|
1291
1292
|
dispatchToBlock({
|
|
@@ -1301,7 +1302,7 @@ export function createMockHost(options = {}) {
|
|
|
1301
1302
|
return;
|
|
1302
1303
|
}
|
|
1303
1304
|
case 'GET_DAILY_COMPENSATION': {
|
|
1304
|
-
if (
|
|
1305
|
+
if (!isRoutableRequestId(requestId))
|
|
1305
1306
|
return;
|
|
1306
1307
|
if (buzzReadError !== undefined) {
|
|
1307
1308
|
dispatchToBlock({
|
|
@@ -1325,7 +1326,7 @@ export function createMockHost(options = {}) {
|
|
|
1325
1326
|
case 'GET_WILDCARD_PACK': {
|
|
1326
1327
|
// Token-INDEPENDENT import. A forced error replies with the
|
|
1327
1328
|
// DISCRIMINATED enum code (NOT free-text) — mirrors the real host.
|
|
1328
|
-
if (
|
|
1329
|
+
if (!isRoutableRequestId(requestId))
|
|
1329
1330
|
return;
|
|
1330
1331
|
if (wildcardPackError !== undefined) {
|
|
1331
1332
|
dispatchToBlock({
|
|
@@ -1344,7 +1345,7 @@ export function createMockHost(options = {}) {
|
|
|
1344
1345
|
// App generator SUBQUEUE read. Drop a request with no requestId
|
|
1345
1346
|
// (unroutable). A forced error replies with the FREE-TEXT error
|
|
1346
1347
|
// variant (mirrors the real host forwarding err.message).
|
|
1347
|
-
if (
|
|
1348
|
+
if (!isRoutableRequestId(requestId))
|
|
1348
1349
|
return;
|
|
1349
1350
|
if (appWorkflowsError !== undefined) {
|
|
1350
1351
|
dispatchToBlock({
|
|
@@ -1367,7 +1368,7 @@ export function createMockHost(options = {}) {
|
|
|
1367
1368
|
// requestId or a missing/empty workflowId (mirrors the real host
|
|
1368
1369
|
// dropping those without a reply). A forced error replies with the
|
|
1369
1370
|
// FREE-TEXT error variant (mirrors a FORBIDDEN / transport failure).
|
|
1370
|
-
if (
|
|
1371
|
+
if (!isRoutableRequestId(requestId))
|
|
1371
1372
|
return;
|
|
1372
1373
|
const cancelId = typed.payload?.workflowId;
|
|
1373
1374
|
if (typeof cancelId !== 'string' || cancelId.length === 0)
|
|
@@ -1400,7 +1401,7 @@ export function createMockHost(options = {}) {
|
|
|
1400
1401
|
case 'SET_COLLECTION_FOLLOW': {
|
|
1401
1402
|
// Follow / unfollow a collection for the viewer. Drop a request with
|
|
1402
1403
|
// no requestId (unroutable) — same as every REQUEST-style handler.
|
|
1403
|
-
if (
|
|
1404
|
+
if (!isRoutableRequestId(requestId))
|
|
1404
1405
|
return;
|
|
1405
1406
|
if (collectionFollowError !== undefined) {
|
|
1406
1407
|
dispatchToBlock({
|
|
@@ -1437,7 +1438,7 @@ export function createMockHost(options = {}) {
|
|
|
1437
1438
|
// outputs. Drop a request with no requestId (unroutable) — same as
|
|
1438
1439
|
// every REQUEST-style handler, and the ONLY safe drop: after the id
|
|
1439
1440
|
// is known, every path must reply or the block hangs TEN MINUTES.
|
|
1440
|
-
if (
|
|
1441
|
+
if (!isRoutableRequestId(requestId))
|
|
1441
1442
|
return;
|
|
1442
1443
|
if (createPostError !== undefined) {
|
|
1443
1444
|
dispatchToBlock({
|
|
@@ -1470,7 +1471,7 @@ export function createMockHost(options = {}) {
|
|
|
1470
1471
|
// real-scanned public Image rows. Drop a request with no requestId
|
|
1471
1472
|
// (unroutable). A forced error replies with the FREE-TEXT error variant
|
|
1472
1473
|
// (mirrors the real host forwarding err.message).
|
|
1473
|
-
if (
|
|
1474
|
+
if (!isRoutableRequestId(requestId))
|
|
1474
1475
|
return;
|
|
1475
1476
|
if (publishError !== undefined) {
|
|
1476
1477
|
dispatchToBlock({
|
|
@@ -1491,7 +1492,7 @@ export function createMockHost(options = {}) {
|
|
|
1491
1492
|
// (mirrors the real host forwarding err.message). The canned images
|
|
1492
1493
|
// include at least one `hidden` (no-url) entry so the block's
|
|
1493
1494
|
// blurred/placeholder path is exercised.
|
|
1494
|
-
if (
|
|
1495
|
+
if (!isRoutableRequestId(requestId))
|
|
1495
1496
|
return;
|
|
1496
1497
|
if (gatedImagesError !== undefined) {
|
|
1497
1498
|
dispatchToBlock({
|
|
@@ -2024,7 +2025,7 @@ export function createMockHost(options = {}) {
|
|
|
2024
2025
|
// explicit null (clear); anything else is a bad-input NACK, same as
|
|
2025
2026
|
// the real IframeHost. Without this reply `useCheckpointPicker().
|
|
2026
2027
|
// persist()` hung to its 30s timeout under the mock host.
|
|
2027
|
-
if (
|
|
2028
|
+
if (!isRoutableRequestId(requestId))
|
|
2028
2029
|
return;
|
|
2029
2030
|
const rawVersionId = typed.payload?.versionId;
|
|
2030
2031
|
const versionId = rawVersionId === null
|
|
@@ -28,17 +28,65 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { type CatalogCard, type CatalogModelType } from './catalog.js';
|
|
30
30
|
import type { BlockCheckpointInfo, BlockResourceInfo } from '@civitai/app-sdk/blocks';
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The inbound message type a picker's result is dispatched on. TWO CHANNELS,
|
|
33
|
+
* TWO CONTRACTS — `CHECKPOINT_PICKER_RESULT.selected` is a
|
|
34
|
+
* {@link BlockCheckpointInfo}, `RESOURCE_PICKER_RESULT.selected` is a
|
|
35
|
+
* {@link BlockResourceInfo} (which additionally REQUIRES `modelType`).
|
|
36
|
+
*/
|
|
37
|
+
export type PickerResultChannel = 'CHECKPOINT_PICKER_RESULT' | 'RESOURCE_PICKER_RESULT';
|
|
38
|
+
/**
|
|
39
|
+
* What the overlay resolves with — the production picker's `selected` shape,
|
|
40
|
+
* KEYED TO THE REPLY CHANNEL.
|
|
41
|
+
*
|
|
42
|
+
* 🔴 DISCRIMINATED BY `channel`, NOT BY THE REQUESTED MODEL TYPE (#391). Those
|
|
43
|
+
* are two independent facts and conflating them was the bug: a block asking for
|
|
44
|
+
* a `Checkpoint`-typed resource on `OPEN_RESOURCE_PICKER` got a
|
|
45
|
+
* `cardToCheckpoint()` projection — five fields, no `modelType` — dispatched on
|
|
46
|
+
* `RESOURCE_PICKER_RESULT`, where every consumer reads `resource.modelType` and
|
|
47
|
+
* found `undefined`. `tsc` could not see it: the branch was a runtime string
|
|
48
|
+
* comparison on `opts.type` and the two converters return different declared
|
|
49
|
+
* types, so no call site was ever checked against the channel it replies on.
|
|
50
|
+
*
|
|
51
|
+
* Keying the union to the channel is what turns THAT pairing into a COMPILE
|
|
52
|
+
* error: `{ channel: 'RESOURCE_PICKER_RESULT', selected: cardToCheckpoint(…) }`
|
|
53
|
+
* now fails `tsc` with "Property 'modelType' is missing in type
|
|
54
|
+
* 'BlockCheckpointInfo' but required in type 'BlockResourceInfo'". Measured by
|
|
55
|
+
* reapplying the original branch.
|
|
56
|
+
*
|
|
57
|
+
* 🔴 ONE DIRECTION ONLY, and the docblock said otherwise until it was checked.
|
|
58
|
+
* The MIRROR pairing — a `BlockResourceInfo` on `CHECKPOINT_PICKER_RESULT` — is
|
|
59
|
+
* structurally assignable (`BlockResourceInfo` has every `BlockCheckpointInfo`
|
|
60
|
+
* field plus more, and the value is not a fresh object literal, so no
|
|
61
|
+
* excess-property check applies). Measured: `tsc` reports ZERO errors for it.
|
|
62
|
+
* That half is pinned by a runtime test in `liveHost.test.tsx` asserting the
|
|
63
|
+
* checkpoint-channel payload carries exactly its five keys — do not read this
|
|
64
|
+
* union as covering it.
|
|
65
|
+
*/
|
|
32
66
|
export type PickerSelection = {
|
|
33
|
-
|
|
67
|
+
channel: 'CHECKPOINT_PICKER_RESULT';
|
|
34
68
|
selected: BlockCheckpointInfo;
|
|
35
69
|
} | {
|
|
36
|
-
|
|
70
|
+
channel: 'RESOURCE_PICKER_RESULT';
|
|
37
71
|
selected: BlockResourceInfo;
|
|
38
72
|
};
|
|
39
73
|
export interface OpenPickerOptions {
|
|
40
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Which model type the picker is FILTERED to — a catalog query parameter, and
|
|
76
|
+
* the `modelType` fallback when a card's own REST type is blank.
|
|
77
|
+
*
|
|
78
|
+
* 🔴 NOT the thing that decides the reply shape. See {@link resultChannel}.
|
|
79
|
+
*/
|
|
41
80
|
type: CatalogModelType;
|
|
81
|
+
/**
|
|
82
|
+
* Which message the host will dispatch this pick on — REQUIRED, because it is
|
|
83
|
+
* the only fact that determines the `selected` shape the block's consumers
|
|
84
|
+
* expect (#391). A `Checkpoint`-typed request can arrive on EITHER channel:
|
|
85
|
+
* `OPEN_CHECKPOINT_PICKER` (→ `BlockCheckpointInfo`) and
|
|
86
|
+
* `OPEN_RESOURCE_PICKER` with `resourceType: 'Checkpoint'`
|
|
87
|
+
* (→ `BlockResourceInfo`, `modelType` included).
|
|
88
|
+
*/
|
|
89
|
+
resultChannel: PickerResultChannel;
|
|
42
90
|
/** Backend origin the catalog resolves against (live host's `baseUrl`). */
|
|
43
91
|
baseUrl: string;
|
|
44
92
|
/** The dev block token — authoritative `/blocks/models` read (page token can read it). */
|