@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
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
* helpers. Do not open-code the test again: the two spellings coexisted in one
|
|
54
54
|
* file for a release precisely because it was inline at every call site.
|
|
55
55
|
*/
|
|
56
|
+
import { isRoutableRequestId, isWireRequestIdShape } from './requestId.js';
|
|
56
57
|
const isObject = (v) => v !== null && typeof v === 'object';
|
|
57
58
|
const isNonEmptyString = (v) => typeof v === 'string' && v.length > 0;
|
|
58
59
|
/** Accepts an ISO 8601 string that `new Date()` can parse to a finite timestamp. */
|
|
@@ -358,7 +359,7 @@ export function isValidTokenRefreshResponse(p) {
|
|
|
358
359
|
return false;
|
|
359
360
|
if (!isValidWrappedToken(p.token))
|
|
360
361
|
return false;
|
|
361
|
-
if (p.requestId
|
|
362
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
362
363
|
return false;
|
|
363
364
|
return true;
|
|
364
365
|
}
|
|
@@ -367,7 +368,7 @@ export function isValidWorkflowReply(p) {
|
|
|
367
368
|
return false;
|
|
368
369
|
if (!isValidWorkflowSnapshot(p.snapshot))
|
|
369
370
|
return false;
|
|
370
|
-
if (p.requestId
|
|
371
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
371
372
|
return false;
|
|
372
373
|
return true;
|
|
373
374
|
}
|
|
@@ -378,7 +379,7 @@ export function isValidBuzzPurchaseResult(p) {
|
|
|
378
379
|
return false;
|
|
379
380
|
if (p.newBalance !== undefined && typeof p.newBalance !== 'number')
|
|
380
381
|
return false;
|
|
381
|
-
if (p.requestId
|
|
382
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
382
383
|
return false;
|
|
383
384
|
return true;
|
|
384
385
|
}
|
|
@@ -392,7 +393,7 @@ const isFiniteNumber = (v) => typeof v === 'number' && Number.isFinite(v);
|
|
|
392
393
|
export function isValidBuzzBalanceResult(p) {
|
|
393
394
|
if (!isObject(p))
|
|
394
395
|
return false;
|
|
395
|
-
if (p.requestId
|
|
396
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
396
397
|
return false;
|
|
397
398
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
398
399
|
return false;
|
|
@@ -428,7 +429,7 @@ const VIEWER_STATUSES = new Set(['active', 'muted']);
|
|
|
428
429
|
export function isValidViewerResult(p) {
|
|
429
430
|
if (!isObject(p))
|
|
430
431
|
return false;
|
|
431
|
-
if (p.requestId
|
|
432
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
432
433
|
return false;
|
|
433
434
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
434
435
|
return false;
|
|
@@ -513,7 +514,7 @@ function isPendingUpload(s) {
|
|
|
513
514
|
export function isValidImageUploadResult(p) {
|
|
514
515
|
if (!isObject(p))
|
|
515
516
|
return false;
|
|
516
|
-
if (p.requestId
|
|
517
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
517
518
|
return false;
|
|
518
519
|
// `selected` absent → cancelled upload (valid). When present, it must match
|
|
519
520
|
// one of the purpose-keyed shapes (moderated / generationSource / pending).
|
|
@@ -541,7 +542,7 @@ export function isValidImageUploadResult(p) {
|
|
|
541
542
|
export function isValidImageScanResolved(p) {
|
|
542
543
|
if (!isObject(p))
|
|
543
544
|
return false;
|
|
544
|
-
if (!
|
|
545
|
+
if (!isRoutableRequestId(p.requestId))
|
|
545
546
|
return false;
|
|
546
547
|
if (typeof p.imageId !== 'number' || !Number.isInteger(p.imageId) || p.imageId <= 0)
|
|
547
548
|
return false;
|
|
@@ -569,7 +570,7 @@ export function isValidImageScanResolved(p) {
|
|
|
569
570
|
export function isValidSharedUpdateResult(p) {
|
|
570
571
|
if (!isObject(p))
|
|
571
572
|
return false;
|
|
572
|
-
if (p.requestId
|
|
573
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
573
574
|
return false;
|
|
574
575
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
575
576
|
return false;
|
|
@@ -604,7 +605,7 @@ function isDateLike(v) {
|
|
|
604
605
|
export function isValidBuzzTransactionsResult(p) {
|
|
605
606
|
if (!isObject(p))
|
|
606
607
|
return false;
|
|
607
|
-
if (p.requestId
|
|
608
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
608
609
|
return false;
|
|
609
610
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
610
611
|
return false;
|
|
@@ -644,7 +645,7 @@ export function isValidBuzzTransactionsResult(p) {
|
|
|
644
645
|
export function isValidBuzzAccountsResult(p) {
|
|
645
646
|
if (!isObject(p))
|
|
646
647
|
return false;
|
|
647
|
-
if (p.requestId
|
|
648
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
648
649
|
return false;
|
|
649
650
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
650
651
|
return false;
|
|
@@ -675,7 +676,7 @@ export function isValidBuzzAccountsResult(p) {
|
|
|
675
676
|
export function isValidDailyCompensationResult(p) {
|
|
676
677
|
if (!isObject(p))
|
|
677
678
|
return false;
|
|
678
|
-
if (p.requestId
|
|
679
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
679
680
|
return false;
|
|
680
681
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
681
682
|
return false;
|
|
@@ -711,7 +712,7 @@ const WILDCARD_PACK_ERROR_CODES = new Set([
|
|
|
711
712
|
export function isValidWildcardPackResult(p) {
|
|
712
713
|
if (!isObject(p))
|
|
713
714
|
return false;
|
|
714
|
-
if (p.requestId
|
|
715
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
715
716
|
return false;
|
|
716
717
|
if (p.error !== undefined) {
|
|
717
718
|
if (typeof p.error !== 'string' || !WILDCARD_PACK_ERROR_CODES.has(p.error))
|
|
@@ -806,7 +807,7 @@ function isValidAppWorkflow(w) {
|
|
|
806
807
|
export function isValidAppWorkflowsResult(p) {
|
|
807
808
|
if (!isObject(p))
|
|
808
809
|
return false;
|
|
809
|
-
if (p.requestId
|
|
810
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
810
811
|
return false;
|
|
811
812
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
812
813
|
return false;
|
|
@@ -835,7 +836,7 @@ export function isValidAppWorkflowsResult(p) {
|
|
|
835
836
|
export function isValidCancelAppWorkflowResult(p) {
|
|
836
837
|
if (!isObject(p))
|
|
837
838
|
return false;
|
|
838
|
-
if (p.requestId
|
|
839
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
839
840
|
return false;
|
|
840
841
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
841
842
|
return false;
|
|
@@ -867,7 +868,7 @@ export function isValidCancelAppWorkflowResult(p) {
|
|
|
867
868
|
export function isValidPublishResult(p) {
|
|
868
869
|
if (!isObject(p))
|
|
869
870
|
return false;
|
|
870
|
-
if (p.requestId
|
|
871
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
871
872
|
return false;
|
|
872
873
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
873
874
|
return false;
|
|
@@ -892,9 +893,18 @@ const GATED_IMAGE_STATUSES = new Set(['visible', 'hidden']);
|
|
|
892
893
|
* `imageId` is always a finite number and `status` one of `visible`/`hidden`.
|
|
893
894
|
* - `visible` → `url` a non-empty string and `width`/`height` each
|
|
894
895
|
* `number | null`, PLUS exactly one of two rating shapes (below).
|
|
895
|
-
* - `hidden` →
|
|
896
|
-
*
|
|
897
|
-
*
|
|
896
|
+
* - `hidden` → `imageId` + `status`, and a `url` on one is REJECTED — defense
|
|
897
|
+
* in depth against a buggy/hostile host leaking an unclamped url on an image
|
|
898
|
+
* the viewer isn't allowed to see.
|
|
899
|
+
*
|
|
900
|
+
* 🔴 THIS PREDICATE DOES **NOT** ENFORCE AN ALLOWLIST ON A `hidden` ENTRY, AND AN
|
|
901
|
+
* EARLIER VERSION OF THIS DOCBLOCK CLAIMED IT DID ("ONLY imageId + status").
|
|
902
|
+
* It bans exactly one SPELLING — the literal key `url`. `previewUrl`, `src`,
|
|
903
|
+
* `imageUrl` or any other key sails through here. The allowlist is real, but it
|
|
904
|
+
* lives one step later, in {@link projectGatedImage}: every `hidden` entry is
|
|
905
|
+
* narrowed to `{ imageId, status }` at the transport boundary, so no extra key
|
|
906
|
+
* can reach a consumer. See that function for why the split is PROJECTION for
|
|
907
|
+
* unknown keys and REJECTION for `url`.
|
|
898
908
|
*
|
|
899
909
|
* 🔴 THE TWO `visible` SHAPES ARE MUTUALLY EXCLUSIVE, AND THAT IS ENFORCED.
|
|
900
910
|
* A RATED entry carries `nsfwLevel` + `contentRating` and NO `ratingPending`; an
|
|
@@ -946,23 +956,72 @@ function isValidGatedImage(img) {
|
|
|
946
956
|
}
|
|
947
957
|
return true;
|
|
948
958
|
}
|
|
949
|
-
// status === 'hidden'
|
|
950
|
-
//
|
|
959
|
+
// status === 'hidden'. A leaked `url` is the exact cross-user moderation-
|
|
960
|
+
// boundary breach this guard exists to catch, and the contract forbids the key
|
|
961
|
+
// outright, so it is fatal here. Every OTHER key is left to
|
|
962
|
+
// `projectGatedImage`, which drops it — see that function's docblock for why
|
|
963
|
+
// the two cases are handled differently. This line is NOT an allowlist.
|
|
951
964
|
if ('url' in img)
|
|
952
965
|
return false;
|
|
953
966
|
return true;
|
|
954
967
|
}
|
|
968
|
+
/**
|
|
969
|
+
* Narrow ONE already-validated gated-image entry to the keys its `status`
|
|
970
|
+
* permits: a `hidden` entry becomes exactly `{ imageId, status }`; a `visible`
|
|
971
|
+
* entry is returned unchanged.
|
|
972
|
+
*
|
|
973
|
+
* 🔴 THIS IS WHERE THE `hidden` ALLOWLIST ACTUALLY LIVES. `isValidGatedImage`
|
|
974
|
+
* bans one spelling (`url`); this drops every key that is not `imageId` or
|
|
975
|
+
* `status`, so `previewUrl` / `src` / `imageUrl` / anything a future host adds
|
|
976
|
+
* cannot reach a consumer of `IMAGES_RESULT`.
|
|
977
|
+
*
|
|
978
|
+
* 🔴 WHY PROJECTION AND NOT REJECTION, for everything except `url`. A `false`
|
|
979
|
+
* from `isValidGatedImage` fails the WHOLE batch: `isValidImagesResult` returns
|
|
980
|
+
* false for one bad entry, `handleMessage` drops the entire `IMAGES_RESULT`
|
|
981
|
+
* before the pending-request lookup, and `useGatedImages().getImages()` then
|
|
982
|
+
* hangs to its transport timeout rather than rejecting. That failure mode is
|
|
983
|
+
* already documented in `useGatedImages.test.tsx` — it is what the
|
|
984
|
+
* `ratingPending` shape had to be admitted to avoid. The host is
|
|
985
|
+
* `civitai/civitai`, a separate repo on a separate release cadence, so an
|
|
986
|
+
* allowlist-by-rejection would convert ANY future host-side field addition into
|
|
987
|
+
* a block-wide hang. Dropping the key delivers the same moderation guarantee
|
|
988
|
+
* (nothing extra reaches a consumer) and costs a forward-compatible host
|
|
989
|
+
* nothing.
|
|
990
|
+
*
|
|
991
|
+
* `url` stays fatal because it is the one key the contract explicitly forbids on
|
|
992
|
+
* a `hidden` entry: a host can never legitimately add it, so it carries no
|
|
993
|
+
* forward-compat risk, and its presence is a live moderation breach worth
|
|
994
|
+
* surfacing loudly (`console.warn` + a `BLOCK_MESSAGE_REJECTED` report) rather
|
|
995
|
+
* than silently papering over.
|
|
996
|
+
*
|
|
997
|
+
* Identity-preserving: an entry that already satisfies the allowlist is returned
|
|
998
|
+
* as-is, so the common path allocates nothing.
|
|
999
|
+
*/
|
|
1000
|
+
function projectGatedImage(img) {
|
|
1001
|
+
if (!isObject(img))
|
|
1002
|
+
return img;
|
|
1003
|
+
if (img.status !== 'hidden')
|
|
1004
|
+
return img;
|
|
1005
|
+
const keys = Object.keys(img);
|
|
1006
|
+
if (keys.length === 2 && 'imageId' in img && 'status' in img)
|
|
1007
|
+
return img;
|
|
1008
|
+
return { imageId: img.imageId, status: img.status };
|
|
1009
|
+
}
|
|
955
1010
|
/**
|
|
956
1011
|
* Reply to a block-initiated `GET_IMAGES_BY_IDS`. A well-formed reply carries
|
|
957
1012
|
* EITHER a `result` ({ images: BlockGatedImage[] }) OR a free-text `error`; one
|
|
958
1013
|
* with neither is malformed and dropped. Each image is shape-checked via
|
|
959
1014
|
* {@link isValidGatedImage} — a `hidden` entry that carries a `url` DROPS the whole
|
|
960
1015
|
* reply (defense in depth against a leaked unclamped url).
|
|
1016
|
+
*
|
|
1017
|
+
* Validation only. The `hidden` ALLOWLIST is applied separately, by
|
|
1018
|
+
* {@link projectInboundPayload} → {@link projectGatedImage}, after this returns
|
|
1019
|
+
* true.
|
|
961
1020
|
*/
|
|
962
1021
|
export function isValidImagesResult(p) {
|
|
963
1022
|
if (!isObject(p))
|
|
964
1023
|
return false;
|
|
965
|
-
if (p.requestId
|
|
1024
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
966
1025
|
return false;
|
|
967
1026
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
968
1027
|
return false;
|
|
@@ -1003,7 +1062,7 @@ export function isValidImagesResult(p) {
|
|
|
1003
1062
|
export function isValidCollectionFollowResult(p) {
|
|
1004
1063
|
if (!isObject(p))
|
|
1005
1064
|
return false;
|
|
1006
|
-
if (p.requestId
|
|
1065
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1007
1066
|
return false;
|
|
1008
1067
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1009
1068
|
return false;
|
|
@@ -1047,7 +1106,7 @@ export function isValidCollectionFollowResult(p) {
|
|
|
1047
1106
|
export function isValidCreatePostResult(p) {
|
|
1048
1107
|
if (!isObject(p))
|
|
1049
1108
|
return false;
|
|
1050
|
-
if (p.requestId
|
|
1109
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1051
1110
|
return false;
|
|
1052
1111
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1053
1112
|
return false;
|
|
@@ -1094,7 +1153,7 @@ export function isValidCreatePostResult(p) {
|
|
|
1094
1153
|
export function isValidAppStorageGetResult(p) {
|
|
1095
1154
|
if (!isObject(p))
|
|
1096
1155
|
return false;
|
|
1097
|
-
if (p.requestId
|
|
1156
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1098
1157
|
return false;
|
|
1099
1158
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1100
1159
|
return false;
|
|
@@ -1111,7 +1170,7 @@ export function isValidAppStorageGetResult(p) {
|
|
|
1111
1170
|
export function isValidAppStorageSetResult(p) {
|
|
1112
1171
|
if (!isObject(p))
|
|
1113
1172
|
return false;
|
|
1114
|
-
if (p.requestId
|
|
1173
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1115
1174
|
return false;
|
|
1116
1175
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1117
1176
|
return false;
|
|
@@ -1130,7 +1189,7 @@ export function isValidAppStorageSetResult(p) {
|
|
|
1130
1189
|
export function isValidAppStorageDeleteResult(p) {
|
|
1131
1190
|
if (!isObject(p))
|
|
1132
1191
|
return false;
|
|
1133
|
-
if (p.requestId
|
|
1192
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1134
1193
|
return false;
|
|
1135
1194
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1136
1195
|
return false;
|
|
@@ -1150,7 +1209,7 @@ export function isValidAppStorageDeleteResult(p) {
|
|
|
1150
1209
|
export function isValidAppStorageListResult(p) {
|
|
1151
1210
|
if (!isObject(p))
|
|
1152
1211
|
return false;
|
|
1153
|
-
if (p.requestId
|
|
1212
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1154
1213
|
return false;
|
|
1155
1214
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1156
1215
|
return false;
|
|
@@ -1174,7 +1233,7 @@ export function isValidAppStorageListResult(p) {
|
|
|
1174
1233
|
export function isValidAppStorageQuotaResult(p) {
|
|
1175
1234
|
if (!isObject(p))
|
|
1176
1235
|
return false;
|
|
1177
|
-
if (p.requestId
|
|
1236
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1178
1237
|
return false;
|
|
1179
1238
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1180
1239
|
return false;
|
|
@@ -1221,7 +1280,7 @@ function isValidSharedValue(v) {
|
|
|
1221
1280
|
export function isValidSharedListResult(p) {
|
|
1222
1281
|
if (!isObject(p))
|
|
1223
1282
|
return false;
|
|
1224
|
-
if (p.requestId
|
|
1283
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1225
1284
|
return false;
|
|
1226
1285
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1227
1286
|
return false;
|
|
@@ -1282,7 +1341,7 @@ function isValidSharedItemWire(it) {
|
|
|
1282
1341
|
export function isValidSharedGetResult(p) {
|
|
1283
1342
|
if (!isObject(p))
|
|
1284
1343
|
return false;
|
|
1285
|
-
if (p.requestId
|
|
1344
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1286
1345
|
return false;
|
|
1287
1346
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1288
1347
|
return false;
|
|
@@ -1301,7 +1360,7 @@ export function isValidSharedGetResult(p) {
|
|
|
1301
1360
|
export function isValidSharedReportResult(p) {
|
|
1302
1361
|
if (!isObject(p))
|
|
1303
1362
|
return false;
|
|
1304
|
-
if (p.requestId
|
|
1363
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1305
1364
|
return false;
|
|
1306
1365
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1307
1366
|
return false;
|
|
@@ -1320,7 +1379,7 @@ export function isValidSharedReportResult(p) {
|
|
|
1320
1379
|
export function isValidSaveImageResult(p) {
|
|
1321
1380
|
if (!isObject(p))
|
|
1322
1381
|
return false;
|
|
1323
|
-
if (p.requestId
|
|
1382
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1324
1383
|
return false;
|
|
1325
1384
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1326
1385
|
return false;
|
|
@@ -1338,7 +1397,7 @@ export function isValidSaveImageResult(p) {
|
|
|
1338
1397
|
export function isValidSharedCountResult(p) {
|
|
1339
1398
|
if (!isObject(p))
|
|
1340
1399
|
return false;
|
|
1341
|
-
if (p.requestId
|
|
1400
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1342
1401
|
return false;
|
|
1343
1402
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1344
1403
|
return false;
|
|
@@ -1352,7 +1411,7 @@ export function isValidSharedCountResult(p) {
|
|
|
1352
1411
|
export function isValidSharedCountsResult(p) {
|
|
1353
1412
|
if (!isObject(p))
|
|
1354
1413
|
return false;
|
|
1355
|
-
if (p.requestId
|
|
1414
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1356
1415
|
return false;
|
|
1357
1416
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1358
1417
|
return false;
|
|
@@ -1370,7 +1429,7 @@ export function isValidSharedCountsResult(p) {
|
|
|
1370
1429
|
export function isValidSharedAppendResult(p) {
|
|
1371
1430
|
if (!isObject(p))
|
|
1372
1431
|
return false;
|
|
1373
|
-
if (p.requestId
|
|
1432
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1374
1433
|
return false;
|
|
1375
1434
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1376
1435
|
return false;
|
|
@@ -1387,7 +1446,7 @@ export function isValidSharedAppendResult(p) {
|
|
|
1387
1446
|
export function isValidSharedWithdrawResult(p) {
|
|
1388
1447
|
if (!isObject(p))
|
|
1389
1448
|
return false;
|
|
1390
|
-
if (p.requestId
|
|
1449
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1391
1450
|
return false;
|
|
1392
1451
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1393
1452
|
return false;
|
|
@@ -1435,7 +1494,7 @@ function isValidPickerResourceBase(s) {
|
|
|
1435
1494
|
export function isValidCheckpointPickerResult(p) {
|
|
1436
1495
|
if (!isObject(p))
|
|
1437
1496
|
return false;
|
|
1438
|
-
if (p.requestId
|
|
1497
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1439
1498
|
return false;
|
|
1440
1499
|
if (p.selected !== undefined) {
|
|
1441
1500
|
if (!isObject(p.selected))
|
|
@@ -1455,7 +1514,7 @@ export function isValidCheckpointPickerResult(p) {
|
|
|
1455
1514
|
export function isValidResourcePickerResult(p) {
|
|
1456
1515
|
if (!isObject(p))
|
|
1457
1516
|
return false;
|
|
1458
|
-
if (p.requestId
|
|
1517
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1459
1518
|
return false;
|
|
1460
1519
|
if (p.selected !== undefined) {
|
|
1461
1520
|
const s = p.selected;
|
|
@@ -1494,7 +1553,7 @@ export function isValidResourcePickerResult(p) {
|
|
|
1494
1553
|
export function isValidUserCheckpointSetResult(p) {
|
|
1495
1554
|
if (!isObject(p))
|
|
1496
1555
|
return false;
|
|
1497
|
-
if (p.requestId
|
|
1556
|
+
if (!isWireRequestIdShape(p.requestId))
|
|
1498
1557
|
return false;
|
|
1499
1558
|
if (p.error !== undefined && typeof p.error !== 'string')
|
|
1500
1559
|
return false;
|
|
@@ -1504,15 +1563,70 @@ export function isValidUserCheckpointSetResult(p) {
|
|
|
1504
1563
|
return false;
|
|
1505
1564
|
return true;
|
|
1506
1565
|
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Narrow an already-VALIDATED inbound payload to the fields a consumer is
|
|
1568
|
+
* allowed to see, immediately before `iframeTransport` hands it to a pending
|
|
1569
|
+
* request or a push listener.
|
|
1570
|
+
*
|
|
1571
|
+
* Validation answers "may this message be delivered at all"; projection answers
|
|
1572
|
+
* "which of its fields may cross". They are deliberately separate steps: a
|
|
1573
|
+
* predicate can only ever say yes/no about the WHOLE reply, so expressing an
|
|
1574
|
+
* allowlist as a predicate makes one unexpected key fatal to every entry in the
|
|
1575
|
+
* batch. See {@link projectGatedImage} for the concrete case and the hang it
|
|
1576
|
+
* avoids.
|
|
1577
|
+
*
|
|
1578
|
+
* 🔴 SCOPE, STATED HONESTLY: `IMAGES_RESULT` is the ONLY type projected today.
|
|
1579
|
+
* Every other type is returned by identity — this is not a general sanitizer and
|
|
1580
|
+
* must not be read as one. Add a case here (and say so in the type's validator
|
|
1581
|
+
* docblock) if another message ever needs one.
|
|
1582
|
+
*/
|
|
1583
|
+
export function projectInboundPayload(type, payload) {
|
|
1584
|
+
if (type !== 'IMAGES_RESULT')
|
|
1585
|
+
return payload;
|
|
1586
|
+
if (!isObject(payload))
|
|
1587
|
+
return payload;
|
|
1588
|
+
const result = payload.result;
|
|
1589
|
+
if (!isObject(result) || !Array.isArray(result.images))
|
|
1590
|
+
return payload;
|
|
1591
|
+
let changed = false;
|
|
1592
|
+
const images = result.images.map((img) => {
|
|
1593
|
+
const projected = projectGatedImage(img);
|
|
1594
|
+
if (projected !== img)
|
|
1595
|
+
changed = true;
|
|
1596
|
+
return projected;
|
|
1597
|
+
});
|
|
1598
|
+
// Identity when nothing was dropped, so the overwhelmingly common path neither
|
|
1599
|
+
// allocates nor perturbs object identity for anything downstream.
|
|
1600
|
+
if (!changed)
|
|
1601
|
+
return payload;
|
|
1602
|
+
return { ...payload, result: { ...result, images } };
|
|
1603
|
+
}
|
|
1507
1604
|
/**
|
|
1508
1605
|
* Returns the validator for an inbound message type, or `null` for types
|
|
1509
1606
|
* that don't carry a payload requiring shape checks (SUSPEND/RESUME).
|
|
1510
1607
|
*
|
|
1511
1608
|
* Falsy result from the validator means "drop the message"; `iframeTransport`
|
|
1512
1609
|
* pairs that with a `console.warn` carrying the type name.
|
|
1610
|
+
*
|
|
1611
|
+
* 🔴 EXHAUSTIVE OVER `ParentToBlockMessage` AT COMPILE TIME. The `default:` arm
|
|
1612
|
+
* binds the switch subject to `never`, so a union member with no `case` above
|
|
1613
|
+
* fails `tsc` with `Type '"NEW_TYPE"' is not assignable to type 'never'`. Adding
|
|
1614
|
+
* a message type to the protocol and forgetting its validator is therefore a
|
|
1615
|
+
* BUILD error, not a silent unvalidated path.
|
|
1616
|
+
*
|
|
1617
|
+
* ⚠️ The gate proves the type is MAPPED, not that it is mapped to the RIGHT
|
|
1618
|
+
* validator, and not that it is mapped to a validator at all rather than to
|
|
1619
|
+
* `null` — `case 'NEW_TYPE': return null;` satisfies it. `validate.test.ts`
|
|
1620
|
+
* covers that residual by asserting a validator (not `null`) for every
|
|
1621
|
+
* payload-carrying type, plus identity for the highest-stakes few.
|
|
1513
1622
|
*/
|
|
1514
1623
|
export function payloadValidatorFor(type) {
|
|
1515
|
-
|
|
1624
|
+
// Widened to `string` on the parameter so a JavaScript caller — and a block
|
|
1625
|
+
// running under a NEWER host than it was built against — reaches the
|
|
1626
|
+
// `default:` arm rather than bypassing the lookup. Narrowed here so the switch
|
|
1627
|
+
// has a union subject for the exhaustiveness check to bind.
|
|
1628
|
+
const messageType = type;
|
|
1629
|
+
switch (messageType) {
|
|
1516
1630
|
case 'BLOCK_INIT':
|
|
1517
1631
|
return isValidBlockInitPayload;
|
|
1518
1632
|
case 'TOKEN_REFRESH':
|
|
@@ -1552,11 +1666,10 @@ export function payloadValidatorFor(type) {
|
|
|
1552
1666
|
return isValidImagesResult;
|
|
1553
1667
|
case 'COLLECTION_FOLLOW_RESULT':
|
|
1554
1668
|
return isValidCollectionFollowResult;
|
|
1555
|
-
// 🔴
|
|
1556
|
-
// `
|
|
1557
|
-
//
|
|
1558
|
-
//
|
|
1559
|
-
// because that failure is otherwise invisible.
|
|
1669
|
+
// 🔴 PRESENCE of this entry IS compiler-enforced (the `never` bind in
|
|
1670
|
+
// `default:`); WHICH validator it names is not. This reply settles a PUBLIC
|
|
1671
|
+
// POST, so the identity is pinned by test as well — see the mapping pin in
|
|
1672
|
+
// `validate.test.ts`.
|
|
1560
1673
|
case 'CREATE_POST_RESULT':
|
|
1561
1674
|
return isValidCreatePostResult;
|
|
1562
1675
|
case 'IMAGE_UPLOAD_RESULT':
|
|
@@ -1602,10 +1715,38 @@ export function payloadValidatorFor(type) {
|
|
|
1602
1715
|
case 'SUSPEND':
|
|
1603
1716
|
case 'RESUME':
|
|
1604
1717
|
return null;
|
|
1605
|
-
default:
|
|
1606
|
-
//
|
|
1607
|
-
// `
|
|
1718
|
+
default: {
|
|
1719
|
+
// ── COMPILE-TIME TOTALITY GATE ─────────────────────────────────────────
|
|
1720
|
+
// Every `ParentToBlockMessage` member is handled above, so the subject
|
|
1721
|
+
// narrows to `never` here. Add a member to the union without a `case` and
|
|
1722
|
+
// this assignment fails:
|
|
1723
|
+
// Type '"NEW_TYPE"' is not assignable to type 'never'.
|
|
1724
|
+
// Do NOT "fix" that by widening this binding — the error IS the gate.
|
|
1725
|
+
const exhaustive = messageType;
|
|
1726
|
+
void exhaustive;
|
|
1727
|
+
// ── RUNTIME: a type the union does not declare ─────────────────────────
|
|
1728
|
+
// 🔴 THIS ARM IS A STRUCTURAL PASS, AND THAT IS A DELIBERATE CHOICE
|
|
1729
|
+
// RATHER THAN AN OVERSIGHT — an earlier comment here justified it with
|
|
1730
|
+
// "handleMessage's earlier `isMessage` branches won't match them anyway",
|
|
1731
|
+
// which is true but is only a third of the reason and reads as if the
|
|
1732
|
+
// other paths had been checked. They have been, so state it: a type
|
|
1733
|
+
// outside the union cannot reach `pending.resolve` (`sendRequest`'s
|
|
1734
|
+
// `responseType` is keyed to the union), cannot reach a push listener
|
|
1735
|
+
// (`onMessage`'s `type` is too), and matches none of the three `isMessage`
|
|
1736
|
+
// branches. It is a no-op in `handleMessage` either way, so returning a
|
|
1737
|
+
// rejecting validator would protect nothing and would cost real
|
|
1738
|
+
// forward-compatibility: a host that has shipped a new message type ahead
|
|
1739
|
+
// of this package would make every older block in the fleet emit a
|
|
1740
|
+
// `console.warn` reading "dropping malformed …" (it is not malformed, it
|
|
1741
|
+
// is unrecognised) plus an unbudgeted `BLOCK_MESSAGE_REJECTED` beacon
|
|
1742
|
+
// labelled `other` on every delivery.
|
|
1743
|
+
//
|
|
1744
|
+
// The residual hole this leaves is narrow and worth naming: an UNTYPED
|
|
1745
|
+
// JavaScript consumer that registers `onMessage('SOME_FUTURE_TYPE', …)`
|
|
1746
|
+
// receives that payload unvalidated. Nothing in this package can validate
|
|
1747
|
+
// a type it does not know; such a consumer must validate for itself.
|
|
1608
1748
|
return null;
|
|
1749
|
+
}
|
|
1609
1750
|
}
|
|
1610
1751
|
}
|
|
1611
1752
|
//# sourceMappingURL=validate.js.map
|
package/dist/ui/BlockGate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import { hostToRunUrl } from '../
|
|
3
|
+
import { hostToRunUrl } from '../transport/directLoad.js';
|
|
4
4
|
import { useDirectLoad } from '../hooks/useDirectLoad.js';
|
|
5
5
|
import { Card } from './Card.js';
|
|
6
6
|
import { Stack } from './Stack.js';
|
|
@@ -30,8 +30,14 @@ export interface SettingsFormProps {
|
|
|
30
30
|
/** Which slice of fields to render. */
|
|
31
31
|
forScope: SettingScope;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* Values keyed by field name. Missing keys fall back to the manifest
|
|
34
|
+
* field's `default`. Unknown keys are ignored.
|
|
35
|
+
*
|
|
36
|
+
* LIVE, not mount-only (#396): passing a different object after mount
|
|
37
|
+
* re-seeds every visible field the user has not yet edited, so values that
|
|
38
|
+
* arrive from an in-flight fetch land in the form instead of being dropped.
|
|
39
|
+
* Fields the user HAS edited keep the edit. The name is kept for API
|
|
40
|
+
* compatibility.
|
|
35
41
|
*/
|
|
36
42
|
initialValues: Record<string, unknown>;
|
|
37
43
|
/**
|
package/dist/ui/SettingsForm.js
CHANGED
|
@@ -26,7 +26,30 @@ export function isFieldVisible(field, forScope, declaredScopes) {
|
|
|
26
26
|
}
|
|
27
27
|
export function SettingsForm(props) {
|
|
28
28
|
const visibleFields = useMemo(() => Object.entries(props.manifestSettings).filter(([, def]) => isFieldVisible(def, props.forScope, props.declaredScopes)), [props.manifestSettings, props.forScope, props.declaredScopes]);
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The seed for the CURRENTLY visible fields: `initialValues` where present,
|
|
31
|
+
* otherwise the manifest `default`, otherwise `null`.
|
|
32
|
+
*
|
|
33
|
+
* 🔴 DERIVED, NOT SEEDED-ONCE STATE (#396). This used to be a `useState` lazy
|
|
34
|
+
* initializer, which runs exactly once — while `visibleFields` above is a
|
|
35
|
+
* `useMemo` that recomputes. The two drifted, in two ways that both reached
|
|
36
|
+
* the user:
|
|
37
|
+
*
|
|
38
|
+
* 1. `initialValues` that arrive ASYNCHRONOUSLY (the normal case: `{}`
|
|
39
|
+
* while a fetch is in flight, then the stored row) were ignored. The
|
|
40
|
+
* form rendered manifest defaults for ever, and Save wrote those
|
|
41
|
+
* defaults back OVER the user's stored value.
|
|
42
|
+
* 2. A `forScope` flip on a mounted form recomputed `visibleFields` to the
|
|
43
|
+
* new slice while `values` still held the OLD slice's keys — so the
|
|
44
|
+
* viewer-scope form rendered blank inputs and `onSubmit` posted the
|
|
45
|
+
* PUBLISHER slice's keys under a viewer-scope save.
|
|
46
|
+
*
|
|
47
|
+
* Recomputing instead of seeding closes both, and closes (2) STRUCTURALLY
|
|
48
|
+
* rather than by remembering to clear: `values` below is built by walking
|
|
49
|
+
* `seeded`'s keys, so a key outside the current `visibleFields` has no way
|
|
50
|
+
* into the submitted object at all.
|
|
51
|
+
*/
|
|
52
|
+
const seeded = useMemo(() => {
|
|
30
53
|
const seed = {};
|
|
31
54
|
for (const [key, def] of visibleFields) {
|
|
32
55
|
if (Object.prototype.hasOwnProperty.call(props.initialValues, key)) {
|
|
@@ -40,12 +63,35 @@ export function SettingsForm(props) {
|
|
|
40
63
|
}
|
|
41
64
|
}
|
|
42
65
|
return seed;
|
|
43
|
-
});
|
|
66
|
+
}, [visibleFields, props.initialValues]);
|
|
67
|
+
/**
|
|
68
|
+
* The user's own edits, keyed by field. Kept SEPARATE from the seed so that
|
|
69
|
+
* a later `initialValues`/`forScope` change re-seeds without discarding what
|
|
70
|
+
* the user typed, and so that an edit can never outlive its field's
|
|
71
|
+
* visibility (see `values`).
|
|
72
|
+
*/
|
|
73
|
+
const [edits, setEdits] = useState({});
|
|
74
|
+
/**
|
|
75
|
+
* Seed overlaid with edits — the rendered AND submitted value set.
|
|
76
|
+
*
|
|
77
|
+
* 🔴 The iteration order is load-bearing: this walks `seeded`'s keys, NOT
|
|
78
|
+
* `edits`'. An edit to a field that is no longer visible (a `forScope` flip,
|
|
79
|
+
* or a manifest change) is therefore simply not present here, so it cannot
|
|
80
|
+
* be rendered and cannot be submitted. Spreading `edits` in wholesale would
|
|
81
|
+
* reintroduce exactly the cross-scope leak this fix exists to close.
|
|
82
|
+
*/
|
|
83
|
+
const values = useMemo(() => {
|
|
84
|
+
const merged = {};
|
|
85
|
+
for (const key of Object.keys(seeded)) {
|
|
86
|
+
merged[key] = Object.prototype.hasOwnProperty.call(edits, key) ? edits[key] : seeded[key];
|
|
87
|
+
}
|
|
88
|
+
return merged;
|
|
89
|
+
}, [seeded, edits]);
|
|
44
90
|
const [errors, setErrors] = useState({});
|
|
45
91
|
const [submitting, setSubmitting] = useState(false);
|
|
46
92
|
const [submitError, setSubmitError] = useState(null);
|
|
47
93
|
const setFieldValue = useCallback((key, value) => {
|
|
48
|
-
|
|
94
|
+
setEdits((prev) => ({ ...prev, [key]: value }));
|
|
49
95
|
setErrors((prev) => {
|
|
50
96
|
if (!prev[key])
|
|
51
97
|
return prev;
|