@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.
Files changed (262) hide show
  1. package/README.md +34 -2
  2. package/dist/api/generationResources.d.ts +4 -3
  3. package/dist/hooks/returnTypeLedger.d.ts +2 -0
  4. package/dist/hooks/returnTypeLedger.js +2 -0
  5. package/dist/hooks/useAppStorage.js +2 -2
  6. package/dist/hooks/useAppWorkflows.d.ts +5 -2
  7. package/dist/hooks/useAppWorkflows.js +18 -7
  8. package/dist/hooks/useBlockAnalytics.d.ts +5 -3
  9. package/dist/hooks/useBlockAnalytics.js +1 -1
  10. package/dist/hooks/useBlockBreakpoint.d.ts +7 -1
  11. package/dist/hooks/useBlockContext.d.ts +13 -2
  12. package/dist/hooks/useBlockContext.js +1 -1
  13. package/dist/hooks/useBlockResize.d.ts +7 -1
  14. package/dist/hooks/useBlockResize.js +1 -1
  15. package/dist/hooks/useBlockSettings.d.ts +6 -1
  16. package/dist/hooks/useBlockTheme.d.ts +6 -1
  17. package/dist/hooks/useBlockToken.d.ts +8 -3
  18. package/dist/hooks/useBlockToken.js +2 -2
  19. package/dist/hooks/useBuzzAccounts.d.ts +3 -2
  20. package/dist/hooks/useBuzzAccounts.js +16 -15
  21. package/dist/hooks/useBuzzBalance.d.ts +3 -2
  22. package/dist/hooks/useBuzzBalance.js +16 -18
  23. package/dist/hooks/useBuzzPurchase.d.ts +8 -6
  24. package/dist/hooks/useBuzzPurchase.js +3 -3
  25. package/dist/hooks/useBuzzTransactions.d.ts +4 -2
  26. package/dist/hooks/useBuzzTransactions.js +16 -15
  27. package/dist/hooks/useBuzzWorkflow.d.ts +71 -28
  28. package/dist/hooks/useBuzzWorkflow.js +31 -17
  29. package/dist/hooks/useCheckpointPicker.d.ts +18 -16
  30. package/dist/hooks/useCheckpointPicker.js +3 -3
  31. package/dist/hooks/useCivitaiNavigate.d.ts +5 -3
  32. package/dist/hooks/useCivitaiNavigate.js +1 -1
  33. package/dist/hooks/useCollectionFollow.js +4 -4
  34. package/dist/hooks/useConsentUnavailable.js +2 -2
  35. package/dist/hooks/useCreatePostFromApp.js +4 -4
  36. package/dist/hooks/useDailyCompensation.d.ts +4 -2
  37. package/dist/hooks/useDailyCompensation.js +17 -15
  38. package/dist/hooks/useDirectLoad.d.ts +7 -1
  39. package/dist/hooks/useDirectLoad.js +1 -1
  40. package/dist/hooks/useDomainMaturity.d.ts +6 -2
  41. package/dist/hooks/useGatedImages.d.ts +8 -0
  42. package/dist/hooks/useGatedImages.js +10 -2
  43. package/dist/hooks/useGenerationResources.d.ts +5 -3
  44. package/dist/hooks/useHostOrigin.d.ts +7 -1
  45. package/dist/hooks/useHostOrigin.js +1 -1
  46. package/dist/hooks/useImageUpload.d.ts +44 -17
  47. package/dist/hooks/useImageUpload.js +24 -4
  48. package/dist/hooks/usePublishGenerationOutputs.js +4 -4
  49. package/dist/hooks/useRequestConsent.d.ts +7 -5
  50. package/dist/hooks/useRequestConsent.js +1 -1
  51. package/dist/hooks/useRequestSequencer.d.ts +71 -0
  52. package/dist/hooks/useRequestSequencer.js +28 -0
  53. package/dist/hooks/useRequestSignIn.d.ts +7 -5
  54. package/dist/hooks/useRequestSignIn.js +1 -1
  55. package/dist/hooks/useResourcePicker.d.ts +16 -14
  56. package/dist/hooks/useResourcePicker.js +3 -3
  57. package/dist/hooks/useSaveImage.js +2 -2
  58. package/dist/hooks/useSharedStorage.js +2 -2
  59. package/dist/hooks/useTip.js +1 -1
  60. package/dist/hooks/useTipAllowance.d.ts +13 -1
  61. package/dist/hooks/useTipAllowance.js +49 -11
  62. package/dist/hooks/useViewer.d.ts +3 -2
  63. package/dist/hooks/useViewer.js +16 -18
  64. package/dist/hooks/useWildcardPack.d.ts +3 -1
  65. package/dist/hooks/useWildcardPack.js +19 -14
  66. package/dist/index.d.ts +30 -14
  67. package/dist/index.js +7 -7
  68. package/dist/internal/consentRefusalLatch.d.ts +1 -1
  69. package/dist/internal/consentRefusalLatch.js +1 -1
  70. package/dist/internal/liveHost.d.ts +63 -11
  71. package/dist/internal/liveHost.js +240 -37
  72. package/dist/internal/mockHost.js +16 -15
  73. package/dist/internal/pickerOverlay.d.ts +52 -4
  74. package/dist/internal/pickerOverlay.js +14 -3
  75. package/dist/testing.d.ts +1 -1
  76. package/dist/testing.js +1 -1
  77. package/dist/{internal → transport}/iframeTransport.d.ts +43 -5
  78. package/dist/{internal → transport}/iframeTransport.js +142 -12
  79. package/dist/transport/originMatcher.d.ts +92 -0
  80. package/dist/transport/originMatcher.js +222 -0
  81. package/dist/transport/requestId.d.ts +113 -0
  82. package/dist/transport/requestId.js +117 -0
  83. package/dist/{internal → transport}/validate.d.ts +34 -0
  84. package/dist/{internal → transport}/validate.js +189 -48
  85. package/dist/ui/BlockGate.js +1 -1
  86. package/dist/ui/SettingsForm.d.ts +8 -2
  87. package/dist/ui/SettingsForm.js +49 -3
  88. package/package.json +7 -148
  89. package/dist/api/generationResources.d.ts.map +0 -1
  90. package/dist/api/generationResources.js.map +0 -1
  91. package/dist/hooks/SfwGate.d.ts.map +0 -1
  92. package/dist/hooks/SfwGate.js.map +0 -1
  93. package/dist/hooks/useAppStorage.d.ts.map +0 -1
  94. package/dist/hooks/useAppStorage.js.map +0 -1
  95. package/dist/hooks/useAppWorkflows.d.ts.map +0 -1
  96. package/dist/hooks/useAppWorkflows.js.map +0 -1
  97. package/dist/hooks/useBlockAnalytics.d.ts.map +0 -1
  98. package/dist/hooks/useBlockAnalytics.js.map +0 -1
  99. package/dist/hooks/useBlockBreakpoint.d.ts.map +0 -1
  100. package/dist/hooks/useBlockBreakpoint.js.map +0 -1
  101. package/dist/hooks/useBlockContext.d.ts.map +0 -1
  102. package/dist/hooks/useBlockContext.js.map +0 -1
  103. package/dist/hooks/useBlockResize.d.ts.map +0 -1
  104. package/dist/hooks/useBlockResize.js.map +0 -1
  105. package/dist/hooks/useBlockSettings.d.ts.map +0 -1
  106. package/dist/hooks/useBlockSettings.js.map +0 -1
  107. package/dist/hooks/useBlockTheme.d.ts.map +0 -1
  108. package/dist/hooks/useBlockTheme.js.map +0 -1
  109. package/dist/hooks/useBlockToken.d.ts.map +0 -1
  110. package/dist/hooks/useBlockToken.js.map +0 -1
  111. package/dist/hooks/useBuzzAccounts.d.ts.map +0 -1
  112. package/dist/hooks/useBuzzAccounts.js.map +0 -1
  113. package/dist/hooks/useBuzzBalance.d.ts.map +0 -1
  114. package/dist/hooks/useBuzzBalance.js.map +0 -1
  115. package/dist/hooks/useBuzzPurchase.d.ts.map +0 -1
  116. package/dist/hooks/useBuzzPurchase.js.map +0 -1
  117. package/dist/hooks/useBuzzTransactions.d.ts.map +0 -1
  118. package/dist/hooks/useBuzzTransactions.js.map +0 -1
  119. package/dist/hooks/useBuzzWorkflow.d.ts.map +0 -1
  120. package/dist/hooks/useBuzzWorkflow.js.map +0 -1
  121. package/dist/hooks/useCheckpointPicker.d.ts.map +0 -1
  122. package/dist/hooks/useCheckpointPicker.js.map +0 -1
  123. package/dist/hooks/useCivitaiNavigate.d.ts.map +0 -1
  124. package/dist/hooks/useCivitaiNavigate.js.map +0 -1
  125. package/dist/hooks/useCollectionFollow.d.ts.map +0 -1
  126. package/dist/hooks/useCollectionFollow.js.map +0 -1
  127. package/dist/hooks/useConsentUnavailable.d.ts.map +0 -1
  128. package/dist/hooks/useConsentUnavailable.js.map +0 -1
  129. package/dist/hooks/useCreatePostFromApp.d.ts.map +0 -1
  130. package/dist/hooks/useCreatePostFromApp.js.map +0 -1
  131. package/dist/hooks/useDailyCompensation.d.ts.map +0 -1
  132. package/dist/hooks/useDailyCompensation.js.map +0 -1
  133. package/dist/hooks/useDirectLoad.d.ts.map +0 -1
  134. package/dist/hooks/useDirectLoad.js.map +0 -1
  135. package/dist/hooks/useDomainMaturity.d.ts.map +0 -1
  136. package/dist/hooks/useDomainMaturity.js.map +0 -1
  137. package/dist/hooks/useGatedImages.d.ts.map +0 -1
  138. package/dist/hooks/useGatedImages.js.map +0 -1
  139. package/dist/hooks/useGenerationResources.d.ts.map +0 -1
  140. package/dist/hooks/useGenerationResources.js.map +0 -1
  141. package/dist/hooks/useHostOrigin.d.ts.map +0 -1
  142. package/dist/hooks/useHostOrigin.js.map +0 -1
  143. package/dist/hooks/useImageUpload.d.ts.map +0 -1
  144. package/dist/hooks/useImageUpload.js.map +0 -1
  145. package/dist/hooks/usePublishGenerationOutputs.d.ts.map +0 -1
  146. package/dist/hooks/usePublishGenerationOutputs.js.map +0 -1
  147. package/dist/hooks/useRequestConsent.d.ts.map +0 -1
  148. package/dist/hooks/useRequestConsent.js.map +0 -1
  149. package/dist/hooks/useRequestSignIn.d.ts.map +0 -1
  150. package/dist/hooks/useRequestSignIn.js.map +0 -1
  151. package/dist/hooks/useResourcePicker.d.ts.map +0 -1
  152. package/dist/hooks/useResourcePicker.js.map +0 -1
  153. package/dist/hooks/useSaveImage.d.ts.map +0 -1
  154. package/dist/hooks/useSaveImage.js.map +0 -1
  155. package/dist/hooks/useSharedStorage.d.ts.map +0 -1
  156. package/dist/hooks/useSharedStorage.js.map +0 -1
  157. package/dist/hooks/useTip.d.ts.map +0 -1
  158. package/dist/hooks/useTip.js.map +0 -1
  159. package/dist/hooks/useTipAllowance.d.ts.map +0 -1
  160. package/dist/hooks/useTipAllowance.js.map +0 -1
  161. package/dist/hooks/useViewer.d.ts.map +0 -1
  162. package/dist/hooks/useViewer.js.map +0 -1
  163. package/dist/hooks/useWildcardPack.d.ts.map +0 -1
  164. package/dist/hooks/useWildcardPack.js.map +0 -1
  165. package/dist/index.d.ts.map +0 -1
  166. package/dist/index.js.map +0 -1
  167. package/dist/internal/catalog.d.ts.map +0 -1
  168. package/dist/internal/catalog.js.map +0 -1
  169. package/dist/internal/consent.d.ts.map +0 -1
  170. package/dist/internal/consent.js.map +0 -1
  171. package/dist/internal/consentRefusalLatch.d.ts.map +0 -1
  172. package/dist/internal/consentRefusalLatch.js.map +0 -1
  173. package/dist/internal/detector.d.ts.map +0 -1
  174. package/dist/internal/detector.js.map +0 -1
  175. package/dist/internal/directLoad.d.ts.map +0 -1
  176. package/dist/internal/directLoad.js.map +0 -1
  177. package/dist/internal/iframeTransport.d.ts.map +0 -1
  178. package/dist/internal/iframeTransport.js.map +0 -1
  179. package/dist/internal/inlineTransport.d.ts.map +0 -1
  180. package/dist/internal/inlineTransport.js.map +0 -1
  181. package/dist/internal/liveHost.d.ts.map +0 -1
  182. package/dist/internal/liveHost.js.map +0 -1
  183. package/dist/internal/mockHost.d.ts.map +0 -1
  184. package/dist/internal/mockHost.js.map +0 -1
  185. package/dist/internal/originMatcher.d.ts +0 -28
  186. package/dist/internal/originMatcher.d.ts.map +0 -1
  187. package/dist/internal/originMatcher.js +0 -89
  188. package/dist/internal/originMatcher.js.map +0 -1
  189. package/dist/internal/pickerOverlay.d.ts.map +0 -1
  190. package/dist/internal/pickerOverlay.js.map +0 -1
  191. package/dist/internal/replyError.d.ts.map +0 -1
  192. package/dist/internal/replyError.js.map +0 -1
  193. package/dist/internal/requestTimeouts.d.ts.map +0 -1
  194. package/dist/internal/requestTimeouts.js.map +0 -1
  195. package/dist/internal/singleton.d.ts.map +0 -1
  196. package/dist/internal/singleton.js.map +0 -1
  197. package/dist/internal/transport.d.ts.map +0 -1
  198. package/dist/internal/transport.js.map +0 -1
  199. package/dist/internal/validate.d.ts.map +0 -1
  200. package/dist/internal/validate.js.map +0 -1
  201. package/dist/live.d.ts.map +0 -1
  202. package/dist/live.js.map +0 -1
  203. package/dist/testing.d.ts.map +0 -1
  204. package/dist/testing.js.map +0 -1
  205. package/dist/ui/Alert.d.ts.map +0 -1
  206. package/dist/ui/Alert.js.map +0 -1
  207. package/dist/ui/Badge.d.ts.map +0 -1
  208. package/dist/ui/Badge.js.map +0 -1
  209. package/dist/ui/BlockGate.d.ts.map +0 -1
  210. package/dist/ui/BlockGate.js.map +0 -1
  211. package/dist/ui/Button.d.ts.map +0 -1
  212. package/dist/ui/Button.js.map +0 -1
  213. package/dist/ui/Card.d.ts.map +0 -1
  214. package/dist/ui/Card.js.map +0 -1
  215. package/dist/ui/Collapse.d.ts.map +0 -1
  216. package/dist/ui/Collapse.js.map +0 -1
  217. package/dist/ui/FollowButton.d.ts.map +0 -1
  218. package/dist/ui/FollowButton.js.map +0 -1
  219. package/dist/ui/Group.d.ts.map +0 -1
  220. package/dist/ui/Group.js.map +0 -1
  221. package/dist/ui/Loader.d.ts.map +0 -1
  222. package/dist/ui/Loader.js.map +0 -1
  223. package/dist/ui/Modal.d.ts.map +0 -1
  224. package/dist/ui/Modal.js.map +0 -1
  225. package/dist/ui/NumberInput.d.ts.map +0 -1
  226. package/dist/ui/NumberInput.js.map +0 -1
  227. package/dist/ui/ReportButton.d.ts.map +0 -1
  228. package/dist/ui/ReportButton.js.map +0 -1
  229. package/dist/ui/ResourceCard.d.ts.map +0 -1
  230. package/dist/ui/ResourceCard.js.map +0 -1
  231. package/dist/ui/SegmentedControl.d.ts.map +0 -1
  232. package/dist/ui/SegmentedControl.js.map +0 -1
  233. package/dist/ui/Select.d.ts.map +0 -1
  234. package/dist/ui/Select.js.map +0 -1
  235. package/dist/ui/SettingsForm.d.ts.map +0 -1
  236. package/dist/ui/SettingsForm.js.map +0 -1
  237. package/dist/ui/Slider.d.ts.map +0 -1
  238. package/dist/ui/Slider.js.map +0 -1
  239. package/dist/ui/Stack.d.ts.map +0 -1
  240. package/dist/ui/Stack.js.map +0 -1
  241. package/dist/ui/TextInput.d.ts.map +0 -1
  242. package/dist/ui/TextInput.js.map +0 -1
  243. package/dist/ui/Textarea.d.ts.map +0 -1
  244. package/dist/ui/Textarea.js.map +0 -1
  245. package/dist/ui/TipButton.d.ts.map +0 -1
  246. package/dist/ui/TipButton.js.map +0 -1
  247. package/dist/ui/index.d.ts.map +0 -1
  248. package/dist/ui/index.js.map +0 -1
  249. package/dist/ui/styles.d.ts.map +0 -1
  250. package/dist/ui/styles.js.map +0 -1
  251. /package/dist/{internal → transport}/detector.d.ts +0 -0
  252. /package/dist/{internal → transport}/detector.js +0 -0
  253. /package/dist/{internal → transport}/directLoad.d.ts +0 -0
  254. /package/dist/{internal → transport}/directLoad.js +0 -0
  255. /package/dist/{internal → transport}/inlineTransport.d.ts +0 -0
  256. /package/dist/{internal → transport}/inlineTransport.js +0 -0
  257. /package/dist/{internal → transport}/requestTimeouts.d.ts +0 -0
  258. /package/dist/{internal → transport}/requestTimeouts.js +0 -0
  259. /package/dist/{internal → transport}/singleton.d.ts +0 -0
  260. /package/dist/{internal → transport}/singleton.js +0 -0
  261. /package/dist/{internal → transport}/transport.d.ts +0 -0
  262. /package/dist/{internal → transport}/transport.js +0 -0
@@ -206,11 +206,22 @@ export function openPickerOverlay(opts) {
206
206
  opts.onResolve(selection);
207
207
  };
208
208
  const selectCard = (card) => {
209
- if (opts.type === 'Checkpoint') {
210
- resolve({ kind: 'Checkpoint', selected: cardToCheckpoint(card) });
209
+ // 🔴 BRANCH ON THE REPLY CHANNEL, NOT ON `opts.type` (#391). The channel is
210
+ // what the block's consumers read the payload as; the requested type is
211
+ // only what the catalog was filtered by. Branching on the type sent a
212
+ // `cardToCheckpoint()` projection — no `modelType` — down
213
+ // `RESOURCE_PICKER_RESULT` for every `resourceType: 'Checkpoint'` request,
214
+ // and `BlockResourceInfo.modelType` is REQUIRED.
215
+ //
216
+ // `cardToResource` already resolves `modelType` correctly for both: it
217
+ // prefers the card's own REST-reported type and falls back to `opts.type`,
218
+ // so a Checkpoint asked for on the resource channel comes back
219
+ // `modelType: 'Checkpoint'`.
220
+ if (opts.resultChannel === 'CHECKPOINT_PICKER_RESULT') {
221
+ resolve({ channel: 'CHECKPOINT_PICKER_RESULT', selected: cardToCheckpoint(card) });
211
222
  }
212
223
  else {
213
- resolve({ kind: 'LORA', selected: cardToResource(card, opts.type) });
224
+ resolve({ channel: 'RESOURCE_PICKER_RESULT', selected: cardToResource(card, opts.type) });
214
225
  }
215
226
  };
216
227
  const handle = {
package/dist/testing.d.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  * README section, failing on growth and on shrinkage alike.
18
18
  */
19
19
  import { type ReactNode } from 'react';
20
- import { __resetTransport } from './internal/singleton.js';
20
+ import { __resetTransport } from './transport/singleton.js';
21
21
  import { type MockHostOptions } from './internal/mockHost.js';
22
22
  export { __resetTransport as resetTransport };
23
23
  export { createMockHost, readMockHostUrlOptions, type MockHost, type MockHostOptions, type MockHostFailMode, type MockHostScenarioPatch, type MockGenerationScenario, type MockBuzzScenario, type MockBuzzBalance, type MockBuzzHandle, type MockStorageScenario, type MockSharedScenario, type MockSharedSeed, type MockCannedImageScan, type CostSpec, type ImageSpec, type CannedPick, } from './internal/mockHost.js';
package/dist/testing.js CHANGED
@@ -18,7 +18,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
18
  * README section, failing on growth and on shrinkage alike.
19
19
  */
20
20
  import { useEffect, useRef, useState } from 'react';
21
- import { __resetTransport } from './internal/singleton.js';
21
+ import { __resetTransport } from './transport/singleton.js';
22
22
  import { createMockHost, readMockHostUrlOptions, } from './internal/mockHost.js';
23
23
  export { __resetTransport as resetTransport };
24
24
  export { createMockHost, readMockHostUrlOptions, } from './internal/mockHost.js';
@@ -4,7 +4,16 @@ export interface IframeTransportOptions {
4
4
  /**
5
5
  * Origins from which `BLOCK_INIT` (and any other inbound message) is
6
6
  * accepted. MUST contain at least one entry. Messages from any other
7
- * origin — including the local origin — are dropped silently.
7
+ * origin — including the local origin — are dropped; a drop that happens
8
+ * before init warns ONCE per distinct origin and is named in the
9
+ * init-timeout error (see {@link MAX_TRACKED_ORIGINS}).
10
+ *
11
+ * Entries are canonicalised by `OriginMatcher`, so a trailing slash, a
12
+ * mixed-case scheme/host and an explicit DEFAULT port all match the
13
+ * equivalent `event.origin` — while a non-default port, the scheme and the
14
+ * exact host stay significant. An entry that is not a bare origin THROWS
15
+ * here rather than being silently skipped. Read that file's docblock before
16
+ * changing anything about the comparison: it is a security boundary.
8
17
  *
9
18
  * Typically wired from `import.meta.env.VITE_BLOCK_ALLOWED_PARENT_ORIGINS`
10
19
  * (or the framework's equivalent) at block-app startup.
@@ -22,13 +31,34 @@ export interface IframeTransportOptions {
22
31
  export declare class IframeTransport implements BlockTransport {
23
32
  private readonly originMatcher;
24
33
  /**
25
- * The EXACT (non-wildcard) entries of `allowedParentOrigins`, usable as a
26
- * `postMessage` `targetOrigin`. A wildcard entry (`https://*.civitaic.com`)
27
- * is not a concrete origin and cannot be a target, so it is excluded here —
28
- * see {@link announceReady} for what happens when nothing exact remains.
34
+ * The EXACT (non-wildcard) entries of `allowedParentOrigins`, NORMALISED by
35
+ * `OriginMatcher` and usable as a `postMessage` `targetOrigin`. A wildcard
36
+ * entry (`https://*.civitaic.com`) is not a concrete origin and cannot be a
37
+ * target, so it is excluded there — see {@link announceReady} for what
38
+ * happens when nothing exact remains.
29
39
  */
30
40
  private readonly exactAllowedOrigins;
41
+ /**
42
+ * The allowlist AS CONFIGURED (trimmed, not normalised). Named in the
43
+ * init-timeout error so the operator sees the strings they actually wrote
44
+ * next to the origins that actually arrived.
45
+ */
46
+ private readonly configuredOrigins;
31
47
  private readonly window;
48
+ /**
49
+ * Origins observed BEFORE init resolved, split by what the allowlist gate did
50
+ * with them. Both bounded — see {@link MAX_TRACKED_ORIGINS}.
51
+ *
52
+ * 🔴 THE TWO BUCKETS ARE DIFFERENT DIAGNOSES AND AN EMPTY PAIR IS A THIRD.
53
+ * "rejected" says the host is talking and the allowlist is wrong; "accepted,
54
+ * but nothing was a valid BLOCK_INIT" says the allowlist is right and the
55
+ * payload or the message type is wrong; neither means the host frame never
56
+ * posted at all. Collapsing them would send the one person reading this error
57
+ * to the wrong half of the system — the failure this whole diagnostic exists
58
+ * to prevent.
59
+ */
60
+ private readonly acceptedOriginTally;
61
+ private readonly rejectedOriginTally;
32
62
  private snapshot;
33
63
  private readonly listeners;
34
64
  /** Origin of the parent — captured from the first valid `BLOCK_INIT`. */
@@ -71,6 +101,14 @@ export declare class IframeTransport implements BlockTransport {
71
101
  * chose to frame.
72
102
  */
73
103
  private announceReady;
104
+ /**
105
+ * One sentence naming the origins this transport actually heard from before
106
+ * init timed out — the single fact that turns "BLOCK_INIT never arrived" from
107
+ * a guess into a diagnosis.
108
+ *
109
+ * Three outcomes, deliberately worded apart (see {@link acceptedOriginTally}).
110
+ */
111
+ private describeObservedOrigins;
74
112
  getSnapshot(): BlockSnapshot;
75
113
  /**
76
114
  * The validated parent origin — `null` until `BLOCK_INIT` lands.
@@ -2,8 +2,52 @@ import { boundBlockToParentMessageType, isMessage, OTHER_MESSAGE_TYPE_LABEL, par
2
2
  import { EMPTY_SNAPSHOT, nextRequestId, RequestTimeoutError, snapshotFromInit, tokenFromWrapped, } from './transport.js';
3
3
  import { OriginMatcher } from './originMatcher.js';
4
4
  import { DEFAULT_REQUEST_TIMEOUT_MS } from './requestTimeouts.js';
5
- import { payloadValidatorFor } from './validate.js';
5
+ import { isRoutableRequestId } from './requestId.js';
6
+ import { payloadValidatorFor, projectInboundPayload } from './validate.js';
6
7
  const INIT_TIMEOUT_MS = 10_000;
8
+ /**
9
+ * How many DISTINCT inbound origins are remembered for the init-timeout
10
+ * diagnostic, per bucket (accepted / rejected).
11
+ *
12
+ * Bounded on purpose: a framing page — or any browser extension sharing the
13
+ * window — can postMessage from an unbounded number of origins, and the host
14
+ * itself re-sends `BLOCK_INIT` on a ~400ms tick, so an uncapped set would let a
15
+ * hostile or merely noisy page grow an error string without limit. Five is
16
+ * enough to name the misconfigured origin next to the configured allowlist,
17
+ * which is the whole diagnostic; past that the message says it is truncated
18
+ * rather than pretending the list is complete.
19
+ */
20
+ const MAX_TRACKED_ORIGINS = 5;
21
+ function newTally() {
22
+ return { seen: new Set(), truncated: false };
23
+ }
24
+ /**
25
+ * Remembers `origin` if there is room. Returns true only the FIRST time an
26
+ * origin is recorded, so callers can attach a one-shot log to it without
27
+ * turning the host's 400ms init retry into a console flood.
28
+ */
29
+ function recordOrigin(tally, origin) {
30
+ if (tally.seen.has(origin))
31
+ return false;
32
+ if (tally.seen.size >= MAX_TRACKED_ORIGINS) {
33
+ tally.truncated = true;
34
+ return false;
35
+ }
36
+ tally.seen.add(origin);
37
+ return true;
38
+ }
39
+ /** The quoted, comma-separated list of origins remembered on one bucket. */
40
+ function formatOrigins(tally) {
41
+ return [...tally.seen].map((o) => `"${o}"`).join(', ');
42
+ }
43
+ /**
44
+ * Leading clause for the bucket's parenthetical when it overflowed, so a capped
45
+ * list is never read as a complete one. Empty when nothing was dropped — the
46
+ * common case, and the one where the list IS the whole truth.
47
+ */
48
+ function truncationNote(tally) {
49
+ return tally.truncated ? `first ${MAX_TRACKED_ORIGINS} of more; ` : '';
50
+ }
7
51
  /**
8
52
  * Iframe-mode transport. Validates `event.origin` on every inbound message,
9
53
  * awaits `BLOCK_INIT` with a 10s timeout (after which `waitForInit` rejects
@@ -13,13 +57,34 @@ const INIT_TIMEOUT_MS = 10_000;
13
57
  export class IframeTransport {
14
58
  originMatcher;
15
59
  /**
16
- * The EXACT (non-wildcard) entries of `allowedParentOrigins`, usable as a
17
- * `postMessage` `targetOrigin`. A wildcard entry (`https://*.civitaic.com`)
18
- * is not a concrete origin and cannot be a target, so it is excluded here —
19
- * see {@link announceReady} for what happens when nothing exact remains.
60
+ * The EXACT (non-wildcard) entries of `allowedParentOrigins`, NORMALISED by
61
+ * `OriginMatcher` and usable as a `postMessage` `targetOrigin`. A wildcard
62
+ * entry (`https://*.civitaic.com`) is not a concrete origin and cannot be a
63
+ * target, so it is excluded there — see {@link announceReady} for what
64
+ * happens when nothing exact remains.
20
65
  */
21
66
  exactAllowedOrigins;
67
+ /**
68
+ * The allowlist AS CONFIGURED (trimmed, not normalised). Named in the
69
+ * init-timeout error so the operator sees the strings they actually wrote
70
+ * next to the origins that actually arrived.
71
+ */
72
+ configuredOrigins;
22
73
  window;
74
+ /**
75
+ * Origins observed BEFORE init resolved, split by what the allowlist gate did
76
+ * with them. Both bounded — see {@link MAX_TRACKED_ORIGINS}.
77
+ *
78
+ * 🔴 THE TWO BUCKETS ARE DIFFERENT DIAGNOSES AND AN EMPTY PAIR IS A THIRD.
79
+ * "rejected" says the host is talking and the allowlist is wrong; "accepted,
80
+ * but nothing was a valid BLOCK_INIT" says the allowlist is right and the
81
+ * payload or the message type is wrong; neither means the host frame never
82
+ * posted at all. Collapsing them would send the one person reading this error
83
+ * to the wrong half of the system — the failure this whole diagnostic exists
84
+ * to prevent.
85
+ */
86
+ acceptedOriginTally = newTally();
87
+ rejectedOriginTally = newTally();
23
88
  snapshot = EMPTY_SNAPSHOT;
24
89
  listeners = new Set();
25
90
  /** Origin of the parent — captured from the first valid `BLOCK_INIT`. */
@@ -48,9 +113,15 @@ export class IframeTransport {
48
113
  // `https://*.example.com` entries match any subdomain on a dot boundary
49
114
  // (mirrors the host-side CSP frame-ancestors convention).
50
115
  this.originMatcher = new OriginMatcher(opts.allowedParentOrigins);
51
- this.exactAllowedOrigins = opts.allowedParentOrigins
116
+ // From the matcher, NOT re-derived here: a second copy of "which entries are
117
+ // concrete origins, and how is one spelled" is a second place for the
118
+ // trailing-slash/case/default-port bug this class just fixed to come back —
119
+ // and it would come back specifically as a `postMessage` targetOrigin, where
120
+ // the failure is a silently dropped announce.
121
+ this.exactAllowedOrigins = this.originMatcher.exactOrigins;
122
+ this.configuredOrigins = opts.allowedParentOrigins
52
123
  .map((entry) => entry.trim())
53
- .filter((entry) => entry.length > 0 && !entry.includes('*'));
124
+ .filter((entry) => entry.length > 0);
54
125
  this.window = opts.window ?? globalThis.window;
55
126
  if (!this.window) {
56
127
  throw new Error('IframeTransport: no window available; cannot mount on the server.');
@@ -63,6 +134,8 @@ export class IframeTransport {
63
134
  if (!this.initResolved) {
64
135
  this.initResolved = true;
65
136
  this.rejectInit(new Error(`IframeTransport: timed out waiting for BLOCK_INIT after ${INIT_TIMEOUT_MS}ms. ` +
137
+ `${this.describeObservedOrigins()} ` +
138
+ `Configured allowedParentOrigins: ${this.configuredOrigins.map((o) => `"${o}"`).join(', ') || '(none)'}. ` +
66
139
  'Verify the host frame is sending the init message and that its origin is in allowedParentOrigins.'));
67
140
  }
68
141
  }, INIT_TIMEOUT_MS);
@@ -171,6 +244,28 @@ export class IframeTransport {
171
244
  }
172
245
  }
173
246
  }
247
+ /**
248
+ * One sentence naming the origins this transport actually heard from before
249
+ * init timed out — the single fact that turns "BLOCK_INIT never arrived" from
250
+ * a guess into a diagnosis.
251
+ *
252
+ * Three outcomes, deliberately worded apart (see {@link acceptedOriginTally}).
253
+ */
254
+ describeObservedOrigins() {
255
+ const parts = [];
256
+ if (this.rejectedOriginTally.seen.size > 0) {
257
+ parts.push(`rejected messages from ${formatOrigins(this.rejectedOriginTally)} ` +
258
+ `(${truncationNote(this.rejectedOriginTally)}no allowedParentOrigins entry matched)`);
259
+ }
260
+ if (this.acceptedOriginTally.seen.size > 0) {
261
+ parts.push(`accepted messages from ${formatOrigins(this.acceptedOriginTally)} ` +
262
+ `(${truncationNote(this.acceptedOriginTally)}none of them was a valid BLOCK_INIT)`);
263
+ }
264
+ if (parts.length === 0) {
265
+ return 'No inbound message was received from any origin.';
266
+ }
267
+ return `Origins seen: ${parts.join('; ')}.`;
268
+ }
174
269
  getSnapshot() {
175
270
  return this.snapshot;
176
271
  }
@@ -343,7 +438,7 @@ export class IframeTransport {
343
438
  return { label: OTHER_MESSAGE_TYPE_LABEL, hung: 'pushed' };
344
439
  }
345
440
  const requestId = payload?.requestId;
346
- if (typeof requestId === 'string') {
441
+ if (isRoutableRequestId(requestId)) {
347
442
  const pending = this.pending.get(requestId);
348
443
  // The same predicate `handleMessage` applies before it will SETTLE a reply.
349
444
  // An id matching a request awaiting a DIFFERENT reply type names nothing we
@@ -432,8 +527,24 @@ export class IframeTransport {
432
527
  this.window.parent.postMessage(msg, this.parentOrigin);
433
528
  }
434
529
  handleMessage(event) {
435
- if (!this.originMatcher.matches(event.origin))
530
+ if (!this.originMatcher.matches(event.origin)) {
531
+ // Still a DROP — nothing below this line runs. What changed is that the
532
+ // drop is no longer invisible: an origin rejected before init lands is the
533
+ // single fact that diagnoses a misspelled allowlist entry, and it is
534
+ // otherwise unobservable from inside the iframe. Bounded to
535
+ // MAX_TRACKED_ORIGINS distinct origins, and `recordOrigin` returns true
536
+ // only on the first sighting, so the host's ~400ms init retry warns ONCE
537
+ // rather than 25 times per ready window.
538
+ if (!this.initResolved && recordOrigin(this.rejectedOriginTally, event.origin)) {
539
+ // eslint-disable-next-line no-console -- developer-facing diagnostic at a trust boundary
540
+ console.warn(`IframeTransport: dropping a message from "${event.origin}" — no ` +
541
+ 'allowedParentOrigins entry matched. Configured: ' +
542
+ `${this.configuredOrigins.map((o) => `"${o}"`).join(', ') || '(none)'}.`);
543
+ }
436
544
  return;
545
+ }
546
+ if (!this.initResolved)
547
+ recordOrigin(this.acceptedOriginTally, event.origin);
437
548
  const data = event.data;
438
549
  if (data == null || typeof data !== 'object' || typeof data.type !== 'string')
439
550
  return;
@@ -513,10 +624,28 @@ export class IframeTransport {
513
624
  return;
514
625
  }
515
626
  // For request/response replies, look up the pending entry by `requestId`.
516
- const payload = data.payload;
627
+ //
628
+ // 🔴 PROJECTED, NOT RAW. This is the last point before an inbound payload
629
+ // crosses into block code — `pending.resolve` below and the push handlers
630
+ // further down are the only two deliveries, and BOTH read this binding.
631
+ // `projectInboundPayload` drops fields a consumer is not allowed to see
632
+ // (today: every key beyond `imageId`/`status` on a `hidden` gated image) and
633
+ // is identity for every other type. Validation said "deliver this message";
634
+ // this says "deliver these fields".
635
+ //
636
+ // The `TOKEN_REFRESH_RESPONSE` branch below still reads `data.payload` on
637
+ // purpose: it applies to the SNAPSHOT rather than handing anything to block
638
+ // code, and it needs the narrowing `isMessage` gave `data`. Every DELIVERY
639
+ // must read this binding instead — the raw object still carries whatever the
640
+ // host sent.
641
+ //
642
+ // It runs on the block's side of the boundary rather than in the hook
643
+ // because `getTransport` + `sendTypedRequest` are PUBLIC exports: a consumer
644
+ // that bypasses `useGatedImages()` still gets the projection.
645
+ const payload = projectInboundPayload(data.type, data.payload);
517
646
  let pending;
518
647
  let matchedRequestId = null;
519
- if (payload && typeof payload.requestId === 'string') {
648
+ if (payload && isRoutableRequestId(payload.requestId)) {
520
649
  const candidate = this.pending.get(payload.requestId);
521
650
  if (candidate && candidate.responseType === data.type) {
522
651
  pending = candidate;
@@ -549,8 +678,9 @@ export class IframeTransport {
549
678
  // listeners, so they fall through to the no-op tail below unchanged.
550
679
  const handlers = this.pushListeners.get(data.type);
551
680
  if (handlers && handlers.size > 0) {
681
+ // `payload`, not `data.payload`: the projected view — see the binding above.
552
682
  for (const handler of [...handlers])
553
- handler(data.payload);
683
+ handler(payload);
554
684
  return;
555
685
  }
556
686
  if (isMessage(data, 'SUSPEND')) {
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Origin allowlist matching for {@link IframeTransport}.
3
+ *
4
+ * Each `allowedParentOrigins` entry is either:
5
+ * - an EXACT origin (`https://civitai.com`) — matched by string equality
6
+ * against the NORMALISED entry, or
7
+ * - a SUFFIX-WILDCARD origin (`https://*.civitaic.com`) — matches any
8
+ * `https://<sub>.civitaic.com`, where `<sub>` is one or more labels
9
+ * (single-label `pr-9` or a full subtree `a.b`), but NOT the bare apex
10
+ * `https://civitaic.com` and NOT a different registrable domain.
11
+ *
12
+ * The wildcard form mirrors the host-side CSP `frame-ancestors` convention
13
+ * (`https://*.civitaic.com`) so a single block build can trust both prod
14
+ * (`civitai.com`, an exact entry) and dynamic preview subdomains
15
+ * (`pr-N.civitaic.com`, a wildcard entry).
16
+ *
17
+ * ## Normalisation — ENTRIES ONLY, NEVER THE CANDIDATE
18
+ *
19
+ * 🔴 SECURITY DIRECTION. Entries are canonicalised at construction; the
20
+ * candidate handed to {@link OriginMatcher.matches} is compared RAW. That
21
+ * asymmetry is deliberate and must not be "tidied up": a real `event.origin`
22
+ * is already the serialization of a canonical origin (lowercase scheme + host,
23
+ * no trailing slash, default port elided, IDN in punycode), so normalising it
24
+ * again can only ADD accepts — e.g. `new URL('https://civitai.com/evil').origin`
25
+ * is `https://civitai.com`, so a `.origin` round-trip on the candidate would
26
+ * make a path-bearing string match an apex entry. Normalising only the entries
27
+ * is value-preserving on the set of REAL origins accepted while closing the
28
+ * spellings an operator actually writes.
29
+ *
30
+ * Equivalences deliberately ACCEPTED (an entry spelled this way matches the
31
+ * equivalent `event.origin`), all of them delegated to the URL parser so the
32
+ * rules are the WHATWG ones rather than hand-rolled string surgery:
33
+ * - trailing slash — `https://civitai.com/` ≡ `https://civitai.com`
34
+ * - scheme/host case — `HTTPS://CIVITAI.COM` ≡ `https://civitai.com`
35
+ * (RFC 3986 §3.1/§3.2.2; path/query case is NOT touched — an origin has
36
+ * neither, and an entry carrying one is rejected outright, below)
37
+ * - default port — `https://civitai.com:443` ≡ `https://civitai.com`,
38
+ * `http://x:80` ≡ `http://x`
39
+ * - IDN — `https://пример.com` ≡ `https://xn--e1afmkfd.com`, which is the
40
+ * form a browser puts on `event.origin`
41
+ *
42
+ * Distinctions deliberately KEPT (these are NOT equivalences — an entry and a
43
+ * candidate differing this way must not match):
44
+ * - a NON-default port stays significant: `https://civitai.com:8443` does not
45
+ * match `https://civitai.com`
46
+ * - the scheme stays significant: `http://` never matches `https://`
47
+ * - a trailing-dot (fully-qualified) host is a DIFFERENT origin to the browser,
48
+ * so `https://civitai.com.` and `https://civitai.com` stay distinct here too
49
+ * - no suffix/prefix logic on exact entries: `https://civitai.com.evil.com`
50
+ * does not match `https://civitai.com`
51
+ *
52
+ * Spellings deliberately REJECTED — loudly, by throwing from the constructor.
53
+ * A malformed entry that is silently dropped is this file's own bug one level
54
+ * up: the allowlist then misses an origin the operator believes is on it, and
55
+ * nothing says so. An entry must denote an ORIGIN and nothing more:
56
+ * - no scheme (`civitai.com`) — unparseable
57
+ * - userinfo (`https://user:pass@civitai.com`) — `.origin` would silently
58
+ * discard the credentials the author wrote
59
+ * - a path, query or fragment (`https://civitai.com/embed`) — `.origin` would
60
+ * silently discard them, widening the entry to the whole origin
61
+ * - a scheme with no origin of its own (`foo://bar`, `file:///x`), whose
62
+ * `.origin` serialises to the literal string `"null"` — the same string a
63
+ * sandboxed opaque origin puts on `event.origin`, so accepting it would
64
+ * allowlist every opaque frame at once
65
+ *
66
+ * Security: matching is scheme-pinned and suffix-anchored on a DOT boundary,
67
+ * so `https://*.civitaic.com` does NOT match `https://civitaic.com.attacker.tld`
68
+ * (different suffix) nor `https://evilcivitaic.com` (no dot boundary). A
69
+ * bare `*` or empty wildcard is rejected at construction.
70
+ */
71
+ export declare class OriginMatcher {
72
+ private readonly exact;
73
+ private readonly wildcards;
74
+ /**
75
+ * The normalised EXACT origins, in first-seen order and de-duplicated.
76
+ *
77
+ * Exposed because `IframeTransport.announceReady` needs concrete
78
+ * `postMessage` targets and must derive them from the SAME normalisation as
79
+ * matching — one rule, one place. A wildcard is not a concrete origin and is
80
+ * absent here by construction.
81
+ */
82
+ readonly exactOrigins: readonly string[];
83
+ constructor(allowedParentOrigins: readonly string[]);
84
+ /**
85
+ * True when `origin` is allowed by an exact or wildcard allowlist entry.
86
+ *
87
+ * 🔴 `origin` is compared AS GIVEN — see the normalisation note in the module
88
+ * docblock for why it is never round-tripped through `new URL()` here.
89
+ */
90
+ matches(origin: string): boolean;
91
+ }
92
+ //# sourceMappingURL=originMatcher.d.ts.map
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Origin allowlist matching for {@link IframeTransport}.
3
+ *
4
+ * Each `allowedParentOrigins` entry is either:
5
+ * - an EXACT origin (`https://civitai.com`) — matched by string equality
6
+ * against the NORMALISED entry, or
7
+ * - a SUFFIX-WILDCARD origin (`https://*.civitaic.com`) — matches any
8
+ * `https://<sub>.civitaic.com`, where `<sub>` is one or more labels
9
+ * (single-label `pr-9` or a full subtree `a.b`), but NOT the bare apex
10
+ * `https://civitaic.com` and NOT a different registrable domain.
11
+ *
12
+ * The wildcard form mirrors the host-side CSP `frame-ancestors` convention
13
+ * (`https://*.civitaic.com`) so a single block build can trust both prod
14
+ * (`civitai.com`, an exact entry) and dynamic preview subdomains
15
+ * (`pr-N.civitaic.com`, a wildcard entry).
16
+ *
17
+ * ## Normalisation — ENTRIES ONLY, NEVER THE CANDIDATE
18
+ *
19
+ * 🔴 SECURITY DIRECTION. Entries are canonicalised at construction; the
20
+ * candidate handed to {@link OriginMatcher.matches} is compared RAW. That
21
+ * asymmetry is deliberate and must not be "tidied up": a real `event.origin`
22
+ * is already the serialization of a canonical origin (lowercase scheme + host,
23
+ * no trailing slash, default port elided, IDN in punycode), so normalising it
24
+ * again can only ADD accepts — e.g. `new URL('https://civitai.com/evil').origin`
25
+ * is `https://civitai.com`, so a `.origin` round-trip on the candidate would
26
+ * make a path-bearing string match an apex entry. Normalising only the entries
27
+ * is value-preserving on the set of REAL origins accepted while closing the
28
+ * spellings an operator actually writes.
29
+ *
30
+ * Equivalences deliberately ACCEPTED (an entry spelled this way matches the
31
+ * equivalent `event.origin`), all of them delegated to the URL parser so the
32
+ * rules are the WHATWG ones rather than hand-rolled string surgery:
33
+ * - trailing slash — `https://civitai.com/` ≡ `https://civitai.com`
34
+ * - scheme/host case — `HTTPS://CIVITAI.COM` ≡ `https://civitai.com`
35
+ * (RFC 3986 §3.1/§3.2.2; path/query case is NOT touched — an origin has
36
+ * neither, and an entry carrying one is rejected outright, below)
37
+ * - default port — `https://civitai.com:443` ≡ `https://civitai.com`,
38
+ * `http://x:80` ≡ `http://x`
39
+ * - IDN — `https://пример.com` ≡ `https://xn--e1afmkfd.com`, which is the
40
+ * form a browser puts on `event.origin`
41
+ *
42
+ * Distinctions deliberately KEPT (these are NOT equivalences — an entry and a
43
+ * candidate differing this way must not match):
44
+ * - a NON-default port stays significant: `https://civitai.com:8443` does not
45
+ * match `https://civitai.com`
46
+ * - the scheme stays significant: `http://` never matches `https://`
47
+ * - a trailing-dot (fully-qualified) host is a DIFFERENT origin to the browser,
48
+ * so `https://civitai.com.` and `https://civitai.com` stay distinct here too
49
+ * - no suffix/prefix logic on exact entries: `https://civitai.com.evil.com`
50
+ * does not match `https://civitai.com`
51
+ *
52
+ * Spellings deliberately REJECTED — loudly, by throwing from the constructor.
53
+ * A malformed entry that is silently dropped is this file's own bug one level
54
+ * up: the allowlist then misses an origin the operator believes is on it, and
55
+ * nothing says so. An entry must denote an ORIGIN and nothing more:
56
+ * - no scheme (`civitai.com`) — unparseable
57
+ * - userinfo (`https://user:pass@civitai.com`) — `.origin` would silently
58
+ * discard the credentials the author wrote
59
+ * - a path, query or fragment (`https://civitai.com/embed`) — `.origin` would
60
+ * silently discard them, widening the entry to the whole origin
61
+ * - a scheme with no origin of its own (`foo://bar`, `file:///x`), whose
62
+ * `.origin` serialises to the literal string `"null"` — the same string a
63
+ * sandboxed opaque origin puts on `event.origin`, so accepting it would
64
+ * allowlist every opaque frame at once
65
+ *
66
+ * Security: matching is scheme-pinned and suffix-anchored on a DOT boundary,
67
+ * so `https://*.civitaic.com` does NOT match `https://civitaic.com.attacker.tld`
68
+ * (different suffix) nor `https://evilcivitaic.com` (no dot boundary). A
69
+ * bare `*` or empty wildcard is rejected at construction.
70
+ */
71
+ /**
72
+ * Stand-in label substituted for `*` so a wildcard entry can be canonicalised
73
+ * by the SAME URL parse as an exact entry (see {@link parseWildcard}). It must
74
+ * be a valid DNS label that the URL parser leaves untouched, and distinctive
75
+ * enough that a real suffix cannot begin with it.
76
+ */
77
+ const WILDCARD_PROBE_LABEL = 'civitai-wildcard-probe';
78
+ export class OriginMatcher {
79
+ exact;
80
+ wildcards;
81
+ /**
82
+ * The normalised EXACT origins, in first-seen order and de-duplicated.
83
+ *
84
+ * Exposed because `IframeTransport.announceReady` needs concrete
85
+ * `postMessage` targets and must derive them from the SAME normalisation as
86
+ * matching — one rule, one place. A wildcard is not a concrete origin and is
87
+ * absent here by construction.
88
+ */
89
+ exactOrigins;
90
+ constructor(allowedParentOrigins) {
91
+ const exact = new Set();
92
+ const wildcards = [];
93
+ for (const raw of allowedParentOrigins) {
94
+ const entry = raw.trim();
95
+ if (!entry)
96
+ continue;
97
+ const wildcard = parseWildcard(entry);
98
+ if (wildcard) {
99
+ wildcards.push(wildcard);
100
+ }
101
+ else {
102
+ exact.add(normaliseOrigin(entry, entry));
103
+ }
104
+ }
105
+ this.exact = exact;
106
+ this.exactOrigins = [...exact];
107
+ this.wildcards = wildcards;
108
+ }
109
+ /**
110
+ * True when `origin` is allowed by an exact or wildcard allowlist entry.
111
+ *
112
+ * 🔴 `origin` is compared AS GIVEN — see the normalisation note in the module
113
+ * docblock for why it is never round-tripped through `new URL()` here.
114
+ */
115
+ matches(origin) {
116
+ if (this.exact.has(origin))
117
+ return true;
118
+ for (const wc of this.wildcards) {
119
+ if (!origin.startsWith(wc.scheme))
120
+ continue;
121
+ const host = origin.slice(wc.scheme.length);
122
+ // Reject anything with a path/query smuggled into the host span:
123
+ // a real `event.origin` is scheme + host (+ optional :port). We require
124
+ // an exact host-suffix match with at least one leading label, and no '/'.
125
+ if (host.includes('/'))
126
+ continue;
127
+ // The host must END with the dot-anchored suffix AND have at least one
128
+ // character of label before the leading dot (so the apex itself is excluded).
129
+ if (host.length > wc.suffix.length && host.endsWith(wc.suffix)) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
135
+ }
136
+ /**
137
+ * Canonicalises an allowlist entry to an origin serialization.
138
+ *
139
+ * `candidate` is what gets parsed; `entry` is what the operator wrote and is
140
+ * the only thing named in an error (for a wildcard the two differ — the probe
141
+ * label stands in for `*`). Throws on anything that is not, exactly, an origin.
142
+ */
143
+ function normaliseOrigin(candidate, entry) {
144
+ let url;
145
+ try {
146
+ url = new URL(candidate);
147
+ }
148
+ catch {
149
+ throw new Error(`IframeTransport: invalid allowed parent origin "${entry}". ` +
150
+ 'Entries must be absolute origins including a scheme, e.g. "https://civitai.com".');
151
+ }
152
+ const extras = [];
153
+ if (url.username || url.password)
154
+ extras.push('credentials');
155
+ // An absolute URL with a special scheme always has a pathname of at least
156
+ // "/", so only a LONGER path is a real path. `""` covers non-special schemes,
157
+ // which are rejected by the origin check below anyway.
158
+ if (url.pathname !== '' && url.pathname !== '/')
159
+ extras.push('a path');
160
+ if (url.search)
161
+ extras.push('a query string');
162
+ if (url.hash)
163
+ extras.push('a fragment');
164
+ if (extras.length > 0) {
165
+ throw new Error(`IframeTransport: invalid allowed parent origin "${entry}" — it carries ${extras.join(', ')}. An allowlist entry must be a bare origin (scheme + host + optional port), ` +
166
+ 'e.g. "https://civitai.com"; anything beyond that is not part of an origin and ' +
167
+ 'would be silently discarded.');
168
+ }
169
+ if (url.origin === 'null') {
170
+ throw new Error(`IframeTransport: invalid allowed parent origin "${entry}" — the "${url.protocol}" ` +
171
+ 'scheme has no origin of its own, so it serialises to the literal "null" that a ' +
172
+ 'sandboxed opaque frame also reports. Use an http(s) origin, e.g. "https://civitai.com".');
173
+ }
174
+ return url.origin;
175
+ }
176
+ /**
177
+ * Parses a `https://*.example.com`-style entry into `{scheme, suffix}`.
178
+ * Returns `null` for non-wildcard (exact) entries.
179
+ * Throws for malformed wildcards (`*` only, `https://*`, `https://*.`).
180
+ *
181
+ * Canonicalisation reuses {@link normaliseOrigin} by substituting a concrete
182
+ * placeholder label for `*` and stripping it back off the parsed origin. That
183
+ * is what makes a wildcard entry obey exactly the same case, default-port,
184
+ * trailing-slash and rejection rules as an exact one instead of a second,
185
+ * drifting copy of them — `https://*.CIVITAIC.COM:443/` and
186
+ * `https://*.civitaic.com` describe the same set, and a wildcard carrying a
187
+ * path is rejected rather than turning into an entry that can never match
188
+ * (`matches` refuses any host span containing `/`).
189
+ */
190
+ function parseWildcard(entry) {
191
+ const star = entry.indexOf('*');
192
+ if (star === -1)
193
+ return null;
194
+ // Wildcard must be of the exact form `<scheme>://*.<suffix>`.
195
+ const marker = '://*.';
196
+ const markerAt = entry.indexOf(marker);
197
+ if (markerAt === -1) {
198
+ throw new Error(`IframeTransport: invalid wildcard origin "${entry}". ` +
199
+ 'Wildcard entries must look like "https://*.example.com".');
200
+ }
201
+ const scheme = entry.slice(0, markerAt + 3); // include "://"
202
+ const bareSuffix = entry.slice(markerAt + marker.length); // after "://*."
203
+ if (!scheme || scheme === '://' || !bareSuffix || bareSuffix.includes('*')) {
204
+ throw new Error(`IframeTransport: invalid wildcard origin "${entry}". ` +
205
+ 'A wildcard needs a scheme and a non-empty domain suffix with exactly one "*", ' +
206
+ 'e.g. "https://*.example.com".');
207
+ }
208
+ const origin = normaliseOrigin(`${scheme}${WILDCARD_PROBE_LABEL}.${bareSuffix}`, entry);
209
+ const sep = origin.indexOf('://');
210
+ const normalisedScheme = origin.slice(0, sep + 3);
211
+ const host = origin.slice(sep + 3);
212
+ const probePrefix = `${WILDCARD_PROBE_LABEL}.`;
213
+ const normalisedSuffix = host.startsWith(probePrefix) ? host.slice(probePrefix.length) : '';
214
+ if (!normalisedSuffix) {
215
+ throw new Error(`IframeTransport: invalid wildcard origin "${entry}". ` +
216
+ 'A wildcard needs a scheme and a non-empty domain suffix with exactly one "*", ' +
217
+ 'e.g. "https://*.example.com".');
218
+ }
219
+ // Dot-anchor the suffix so `*.civitaic.com` only matches on a label boundary.
220
+ return { scheme: normalisedScheme, suffix: `.${normalisedSuffix}` };
221
+ }
222
+ //# sourceMappingURL=originMatcher.js.map