@sonordev/site-kit 6.3.1 → 6.3.4

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/agent-manifest.json +1 -1
  2. package/dist/{AnalyticsProvider-G46LCH3V.js → AnalyticsProvider-4F7VWRAJ.js} +10 -10
  3. package/dist/{AnalyticsProvider-G46LCH3V.js.map → AnalyticsProvider-4F7VWRAJ.js.map} +1 -1
  4. package/dist/{AnalyticsProvider-IYGKHMVM.mjs → AnalyticsProvider-DQQLEE3B.mjs} +6 -6
  5. package/dist/{AnalyticsProvider-IYGKHMVM.mjs.map → AnalyticsProvider-DQQLEE3B.mjs.map} +1 -1
  6. package/dist/{ArticleViewTracker-7GEI37VK.mjs → ArticleViewTracker-3QRIQZOL.mjs} +5 -5
  7. package/dist/{ArticleViewTracker-7GEI37VK.mjs.map → ArticleViewTracker-3QRIQZOL.mjs.map} +1 -1
  8. package/dist/{ArticleViewTracker-3USIHB4O.js → ArticleViewTracker-B4J2IKWS.js} +6 -6
  9. package/dist/{ArticleViewTracker-3USIHB4O.js.map → ArticleViewTracker-B4J2IKWS.js.map} +1 -1
  10. package/dist/{ChatWidget-VP3QQJA6.js → ChatWidget-NGZLBGGZ.js} +11 -11
  11. package/dist/{ChatWidget-VP3QQJA6.js.map → ChatWidget-NGZLBGGZ.js.map} +1 -1
  12. package/dist/ChatWidget-S2IEUKSM.mjs +17 -0
  13. package/dist/{ChatWidget-36IDUB53.mjs.map → ChatWidget-S2IEUKSM.mjs.map} +1 -1
  14. package/dist/EngageWidget-DKXCD6VU.js +21 -0
  15. package/dist/{EngageWidget-YHMSB4YE.js.map → EngageWidget-DKXCD6VU.js.map} +1 -1
  16. package/dist/EngageWidget-XAG3OUTN.mjs +12 -0
  17. package/dist/{EngageWidget-ILRTLGBB.mjs.map → EngageWidget-XAG3OUTN.mjs.map} +1 -1
  18. package/dist/{FileField-LMSLNNRX.js → FileField-QSQS6WRY.js} +6 -6
  19. package/dist/FileField-QSQS6WRY.js.map +1 -0
  20. package/dist/{FileField-DXGPA4OB.mjs → FileField-QYAHRTRI.mjs} +5 -5
  21. package/dist/FileField-QYAHRTRI.mjs.map +1 -0
  22. package/dist/{FormSpotlight-SEJ4NJHG.mjs → FormSpotlight-OK2F4YEI.mjs} +4 -4
  23. package/dist/{FormSpotlight-SEJ4NJHG.mjs.map → FormSpotlight-OK2F4YEI.mjs.map} +1 -1
  24. package/dist/{FormSpotlight-5BQCAXIO.js → FormSpotlight-TNG5KXWM.js} +12 -12
  25. package/dist/{FormSpotlight-5BQCAXIO.js.map → FormSpotlight-TNG5KXWM.js.map} +1 -1
  26. package/dist/{FormStage-ZRXENSYN.js → FormStage-7QKWC76Z.js} +11 -11
  27. package/dist/{FormStage-ZRXENSYN.js.map → FormStage-7QKWC76Z.js.map} +1 -1
  28. package/dist/{FormStage-6OLDPLYU.mjs → FormStage-EKTMBUHT.mjs} +4 -4
  29. package/dist/{FormStage-6OLDPLYU.mjs.map → FormStage-EKTMBUHT.mjs.map} +1 -1
  30. package/dist/ManagedForm-2LGVJVNO.mjs +16 -0
  31. package/dist/{ManagedForm-AIPXF5QY.mjs.map → ManagedForm-2LGVJVNO.mjs.map} +1 -1
  32. package/dist/ManagedForm-LQK3KJUH.js +25 -0
  33. package/dist/{ManagedForm-T464AA5B.js.map → ManagedForm-LQK3KJUH.js.map} +1 -1
  34. package/dist/{ManagedNewsletterForm-UGDUBYR3.mjs → ManagedNewsletterForm-7EUEAMU5.mjs} +7 -7
  35. package/dist/{ManagedNewsletterForm-UGDUBYR3.mjs.map → ManagedNewsletterForm-7EUEAMU5.mjs.map} +1 -1
  36. package/dist/{ManagedNewsletterForm-HKNXIKDV.js → ManagedNewsletterForm-DNSCX7Y5.js} +8 -8
  37. package/dist/{ManagedNewsletterForm-HKNXIKDV.js.map → ManagedNewsletterForm-DNSCX7Y5.js.map} +1 -1
  38. package/dist/{SignalCore-O3GEJZCN.mjs → SignalCore-OPQXRYKI.mjs} +5 -5
  39. package/dist/{SignalCore-O3GEJZCN.mjs.map → SignalCore-OPQXRYKI.mjs.map} +1 -1
  40. package/dist/{SignalCore-OOQHKIUZ.js → SignalCore-SVJYWIM6.js} +11 -11
  41. package/dist/{SignalCore-OOQHKIUZ.js.map → SignalCore-SVJYWIM6.js.map} +1 -1
  42. package/dist/SitemapSync-JQFUZMGW.js +18 -0
  43. package/dist/{SitemapSync-MT3I4W3B.js.map → SitemapSync-JQFUZMGW.js.map} +1 -1
  44. package/dist/SitemapSync-ZJY5DKLU.mjs +9 -0
  45. package/dist/{SitemapSync-RG554AJR.mjs.map → SitemapSync-ZJY5DKLU.mjs.map} +1 -1
  46. package/dist/analytics/index.js +9 -9
  47. package/dist/analytics/index.mjs +4 -4
  48. package/dist/articles/index.js +1 -1
  49. package/dist/articles/index.mjs +1 -1
  50. package/dist/articles/server-ui.js +1 -1
  51. package/dist/articles/server-ui.mjs +1 -1
  52. package/dist/{chunk-W4NTPOBD.js → chunk-4SFSLUBA.js} +5 -5
  53. package/dist/{chunk-W4NTPOBD.js.map → chunk-4SFSLUBA.js.map} +1 -1
  54. package/dist/{chunk-OUM77VZ4.mjs → chunk-562DSBYR.mjs} +3 -3
  55. package/dist/{chunk-OUM77VZ4.mjs.map → chunk-562DSBYR.mjs.map} +1 -1
  56. package/dist/{chunk-S4DTX5E7.js → chunk-5XEEDDZ4.js} +4 -4
  57. package/dist/{chunk-S4DTX5E7.js.map → chunk-5XEEDDZ4.js.map} +1 -1
  58. package/dist/{chunk-W5MHX6SO.mjs → chunk-64HKUCAF.mjs} +3 -3
  59. package/dist/{chunk-W5MHX6SO.mjs.map → chunk-64HKUCAF.mjs.map} +1 -1
  60. package/dist/{chunk-NRBITVDK.js → chunk-6FNFM7JL.js} +3 -4
  61. package/dist/chunk-6FNFM7JL.js.map +1 -0
  62. package/dist/{chunk-KAJT3MOM.mjs → chunk-6GRM7YZG.mjs} +4 -4
  63. package/dist/{chunk-KAJT3MOM.mjs.map → chunk-6GRM7YZG.mjs.map} +1 -1
  64. package/dist/{chunk-FVATDBFX.js → chunk-7LHG6GR3.js} +10 -10
  65. package/dist/{chunk-FVATDBFX.js.map → chunk-7LHG6GR3.js.map} +1 -1
  66. package/dist/{chunk-QRL3QDUE.js → chunk-7RLL67F3.js} +3 -20
  67. package/dist/chunk-7RLL67F3.js.map +1 -0
  68. package/dist/{chunk-GE3CGR2T.js → chunk-7YELMDJ3.js} +14 -14
  69. package/dist/{chunk-GE3CGR2T.js.map → chunk-7YELMDJ3.js.map} +1 -1
  70. package/dist/chunk-AGIMH67E.mjs +26 -0
  71. package/dist/chunk-AGIMH67E.mjs.map +1 -0
  72. package/dist/{chunk-A3QAZJXC.js → chunk-CIBH3PMQ.js} +6 -8
  73. package/dist/chunk-CIBH3PMQ.js.map +1 -0
  74. package/dist/{chunk-6WWGH6E3.js → chunk-CLQSKHE6.js} +14 -14
  75. package/dist/{chunk-6WWGH6E3.js.map → chunk-CLQSKHE6.js.map} +1 -1
  76. package/dist/{chunk-NKBPEZCX.js → chunk-CZMZEUB6.js} +15 -15
  77. package/dist/{chunk-NKBPEZCX.js.map → chunk-CZMZEUB6.js.map} +1 -1
  78. package/dist/{chunk-YUCKBIRE.mjs → chunk-D37H4JI6.mjs} +196 -5
  79. package/dist/chunk-D37H4JI6.mjs.map +1 -0
  80. package/dist/{chunk-YCE6HRWW.js → chunk-DV5KVNA7.js} +10 -11
  81. package/dist/chunk-DV5KVNA7.js.map +1 -0
  82. package/dist/{chunk-7YFUX2CL.mjs → chunk-DVAAKEMG.mjs} +3 -3
  83. package/dist/{chunk-7YFUX2CL.mjs.map → chunk-DVAAKEMG.mjs.map} +1 -1
  84. package/dist/{chunk-3EOUKWHB.mjs → chunk-DXSSC3KO.mjs} +3 -3
  85. package/dist/{chunk-3EOUKWHB.mjs.map → chunk-DXSSC3KO.mjs.map} +1 -1
  86. package/dist/{chunk-HY6EXWLZ.js → chunk-E7KNZVH7.js} +8 -8
  87. package/dist/{chunk-HY6EXWLZ.js.map → chunk-E7KNZVH7.js.map} +1 -1
  88. package/dist/{chunk-KUDSEKV7.mjs → chunk-EEPOZWUL.mjs} +4 -4
  89. package/dist/{chunk-KUDSEKV7.mjs.map → chunk-EEPOZWUL.mjs.map} +1 -1
  90. package/dist/{chunk-4KSE642V.js → chunk-ESFDSHMC.js} +23 -27
  91. package/dist/chunk-ESFDSHMC.js.map +1 -0
  92. package/dist/chunk-EYKLUW63.js +8 -0
  93. package/dist/{chunk-ZKOQCKOR.js.map → chunk-EYKLUW63.js.map} +1 -1
  94. package/dist/chunk-FCAHV5P3.mjs +45 -0
  95. package/dist/chunk-FCAHV5P3.mjs.map +1 -0
  96. package/dist/{chunk-A4WYSHIX.js → chunk-FDSMD2KO.js} +3 -5
  97. package/dist/chunk-FDSMD2KO.js.map +1 -0
  98. package/dist/{chunk-RZUUPX2H.js → chunk-FGA5FT7Y.js} +6 -6
  99. package/dist/{chunk-RZUUPX2H.js.map → chunk-FGA5FT7Y.js.map} +1 -1
  100. package/dist/{chunk-2LX7LHAO.js → chunk-H3365D42.js} +5 -5
  101. package/dist/{chunk-2LX7LHAO.js.map → chunk-H3365D42.js.map} +1 -1
  102. package/dist/{chunk-HCN3IKMW.mjs → chunk-HPGPOUHU.mjs} +3 -4
  103. package/dist/chunk-HPGPOUHU.mjs.map +1 -0
  104. package/dist/{chunk-U274XAMD.js → chunk-IBSHXE73.js} +12 -13
  105. package/dist/chunk-IBSHXE73.js.map +1 -0
  106. package/dist/{chunk-6TSYWYYR.js → chunk-IFFDCOLU.js} +4 -4
  107. package/dist/{chunk-6TSYWYYR.js.map → chunk-IFFDCOLU.js.map} +1 -1
  108. package/dist/{chunk-NO4JQ2G3.mjs → chunk-JONESD5P.mjs} +10 -14
  109. package/dist/chunk-JONESD5P.mjs.map +1 -0
  110. package/dist/{chunk-4EXBEFIV.mjs → chunk-JSLFGJG4.mjs} +4 -4
  111. package/dist/{chunk-4EXBEFIV.mjs.map → chunk-JSLFGJG4.mjs.map} +1 -1
  112. package/dist/{chunk-JS265UKZ.mjs → chunk-KTFZNSRY.mjs} +3 -3
  113. package/dist/{chunk-JS265UKZ.mjs.map → chunk-KTFZNSRY.mjs.map} +1 -1
  114. package/dist/{chunk-IDBA3YZ6.mjs → chunk-M6PJTITK.mjs} +4 -4
  115. package/dist/{chunk-IDBA3YZ6.mjs.map → chunk-M6PJTITK.mjs.map} +1 -1
  116. package/dist/{chunk-UFG2TUBG.js → chunk-MMQLQHZX.js} +3 -3
  117. package/dist/{chunk-UFG2TUBG.js.map → chunk-MMQLQHZX.js.map} +1 -1
  118. package/dist/{chunk-JPXT5LY2.js → chunk-N2LYSNZT.js} +204 -12
  119. package/dist/chunk-N2LYSNZT.js.map +1 -0
  120. package/dist/{chunk-S7S6ZZ3R.js → chunk-NMB47GMV.js} +7 -7
  121. package/dist/{chunk-S7S6ZZ3R.js.map → chunk-NMB47GMV.js.map} +1 -1
  122. package/dist/{chunk-YYOGUTLV.js → chunk-NQYO54EB.js} +3 -3
  123. package/dist/{chunk-YYOGUTLV.js.map → chunk-NQYO54EB.js.map} +1 -1
  124. package/dist/{chunk-4HSZPHF7.mjs → chunk-NUHIODBD.mjs} +4 -6
  125. package/dist/chunk-NUHIODBD.mjs.map +1 -0
  126. package/dist/{chunk-QRN6HQOX.js → chunk-PPY73K4P.js} +15 -15
  127. package/dist/{chunk-QRN6HQOX.js.map → chunk-PPY73K4P.js.map} +1 -1
  128. package/dist/{chunk-XA2GJFVM.js → chunk-RI6DFQCA.js} +3 -5
  129. package/dist/chunk-RI6DFQCA.js.map +1 -0
  130. package/dist/chunk-TULCHQX2.js +29 -0
  131. package/dist/chunk-TULCHQX2.js.map +1 -0
  132. package/dist/{chunk-A5R62CXI.mjs → chunk-V57WFNHY.mjs} +3 -5
  133. package/dist/chunk-V57WFNHY.mjs.map +1 -0
  134. package/dist/{chunk-TDJZFBHI.mjs → chunk-VQL7XDIY.mjs} +8 -9
  135. package/dist/chunk-VQL7XDIY.mjs.map +1 -0
  136. package/dist/{chunk-YOS4A3QE.mjs → chunk-VXVLZAR5.mjs} +3 -3
  137. package/dist/{chunk-YOS4A3QE.mjs.map → chunk-VXVLZAR5.mjs.map} +1 -1
  138. package/dist/{chunk-WIRGZMM5.mjs → chunk-W2UA4UUZ.mjs} +3 -3
  139. package/dist/{chunk-WIRGZMM5.mjs.map → chunk-W2UA4UUZ.mjs.map} +1 -1
  140. package/dist/{chunk-VJV7WUR2.mjs → chunk-WRRVYPMY.mjs} +3 -5
  141. package/dist/chunk-WRRVYPMY.mjs.map +1 -0
  142. package/dist/{chunk-XNZ2WFXR.mjs → chunk-WS3J5T3K.mjs} +4 -4
  143. package/dist/{chunk-XNZ2WFXR.mjs.map → chunk-WS3J5T3K.mjs.map} +1 -1
  144. package/dist/chunk-WT5WNIR2.mjs +6 -0
  145. package/dist/{chunk-C654VCWV.mjs.map → chunk-WT5WNIR2.mjs.map} +1 -1
  146. package/dist/{chunk-DUFU27R2.mjs → chunk-YPDOYPWJ.mjs} +3 -3
  147. package/dist/{chunk-DUFU27R2.mjs.map → chunk-YPDOYPWJ.mjs.map} +1 -1
  148. package/dist/{chunk-XV2FFSJU.mjs → chunk-Z3Q5UTZU.mjs} +10 -11
  149. package/dist/chunk-Z3Q5UTZU.mjs.map +1 -0
  150. package/dist/{chunk-MWIIHMYS.mjs → chunk-ZIZBE6Y6.mjs} +3 -3
  151. package/dist/{chunk-MWIIHMYS.mjs.map → chunk-ZIZBE6Y6.mjs.map} +1 -1
  152. package/dist/cli/index.js +3 -3
  153. package/dist/cli/index.mjs +1 -1
  154. package/dist/client/index.js +4 -4
  155. package/dist/client/index.mjs +3 -3
  156. package/dist/commerce/index.js +57 -57
  157. package/dist/commerce/index.mjs +5 -5
  158. package/dist/engage/index.js +10 -10
  159. package/dist/engage/index.mjs +7 -7
  160. package/dist/fleet/index.js +8 -8
  161. package/dist/fleet/index.mjs +4 -4
  162. package/dist/forms/FileField.d.ts +2 -4
  163. package/dist/forms/FileField.d.ts.map +1 -1
  164. package/dist/forms/FormClient.d.ts +6 -2
  165. package/dist/forms/FormClient.d.ts.map +1 -1
  166. package/dist/forms/FormEnhancer.d.ts +2 -1
  167. package/dist/forms/FormEnhancer.d.ts.map +1 -1
  168. package/dist/forms/FormField.d.ts.map +1 -1
  169. package/dist/forms/ManagedForm.d.ts +1 -1
  170. package/dist/forms/ManagedForm.d.ts.map +1 -1
  171. package/dist/forms/ServerForm.d.ts +3 -7
  172. package/dist/forms/ServerForm.d.ts.map +1 -1
  173. package/dist/forms/StaticForm.d.ts +17 -18
  174. package/dist/forms/StaticForm.d.ts.map +1 -1
  175. package/dist/forms/index.js +18 -18
  176. package/dist/forms/index.mjs +10 -10
  177. package/dist/forms/server.js +8 -13
  178. package/dist/forms/server.js.map +1 -1
  179. package/dist/forms/server.mjs +7 -12
  180. package/dist/forms/server.mjs.map +1 -1
  181. package/dist/forms/static.js +5 -8
  182. package/dist/forms/static.mjs +4 -7
  183. package/dist/forms/types.d.ts +3 -9
  184. package/dist/forms/types.d.ts.map +1 -1
  185. package/dist/images/index.js +11 -11
  186. package/dist/images/index.mjs +4 -4
  187. package/dist/index.js +63 -63
  188. package/dist/index.mjs +9 -9
  189. package/dist/layout/SiteKitClientProviders.d.ts +1 -3
  190. package/dist/layout/SiteKitClientProviders.d.ts.map +1 -1
  191. package/dist/layout/SiteKitLayout.d.ts.map +1 -1
  192. package/dist/layout/client.d.ts +1 -3
  193. package/dist/layout/client.d.ts.map +1 -1
  194. package/dist/layout/client.js +8 -8
  195. package/dist/layout/client.mjs +7 -7
  196. package/dist/layout/index.js +10 -11
  197. package/dist/layout/index.js.map +1 -1
  198. package/dist/layout/index.mjs +8 -9
  199. package/dist/layout/index.mjs.map +1 -1
  200. package/dist/llms/aiRobots.d.ts +22 -14
  201. package/dist/llms/aiRobots.d.ts.map +1 -1
  202. package/dist/llms/index.js +6 -6
  203. package/dist/llms/index.mjs +1 -1
  204. package/dist/maps/api.d.ts +2 -2
  205. package/dist/maps/index.js +6 -6
  206. package/dist/maps/index.js.map +1 -1
  207. package/dist/maps/index.mjs +4 -4
  208. package/dist/maps/index.mjs.map +1 -1
  209. package/dist/robots/index.js +8 -8
  210. package/dist/robots/index.mjs +2 -2
  211. package/dist/search/index.js +4 -4
  212. package/dist/search/index.mjs +3 -3
  213. package/dist/seo/client.js +5 -5
  214. package/dist/seo/client.mjs +4 -4
  215. package/dist/seo/index.js +5 -5
  216. package/dist/seo/index.mjs +4 -4
  217. package/dist/server/index.js +3 -3
  218. package/dist/server/index.mjs +2 -2
  219. package/dist/shared/publishCredential.d.ts +1 -9
  220. package/dist/shared/publishCredential.d.ts.map +1 -1
  221. package/dist/shared/version.d.ts +1 -1
  222. package/dist/signal/index.js +11 -11
  223. package/dist/signal/index.mjs +2 -2
  224. package/dist/sync/index.js +17 -17
  225. package/dist/sync/index.mjs +4 -4
  226. package/package.json +1 -1
  227. package/dist/ChatWidget-36IDUB53.mjs +0 -17
  228. package/dist/EngageWidget-ILRTLGBB.mjs +0 -12
  229. package/dist/EngageWidget-YHMSB4YE.js +0 -21
  230. package/dist/FileField-DXGPA4OB.mjs.map +0 -1
  231. package/dist/FileField-LMSLNNRX.js.map +0 -1
  232. package/dist/ManagedForm-AIPXF5QY.mjs +0 -16
  233. package/dist/ManagedForm-T464AA5B.js +0 -25
  234. package/dist/SitemapSync-MT3I4W3B.js +0 -18
  235. package/dist/SitemapSync-RG554AJR.mjs +0 -9
  236. package/dist/chunk-4HSZPHF7.mjs.map +0 -1
  237. package/dist/chunk-4KSE642V.js.map +0 -1
  238. package/dist/chunk-6FASIYCV.js +0 -224
  239. package/dist/chunk-6FASIYCV.js.map +0 -1
  240. package/dist/chunk-A3QAZJXC.js.map +0 -1
  241. package/dist/chunk-A4WYSHIX.js.map +0 -1
  242. package/dist/chunk-A5R62CXI.mjs.map +0 -1
  243. package/dist/chunk-C654VCWV.mjs +0 -6
  244. package/dist/chunk-HCN3IKMW.mjs.map +0 -1
  245. package/dist/chunk-HIG7COWF.mjs +0 -60
  246. package/dist/chunk-HIG7COWF.mjs.map +0 -1
  247. package/dist/chunk-JPXT5LY2.js.map +0 -1
  248. package/dist/chunk-NO4JQ2G3.mjs.map +0 -1
  249. package/dist/chunk-NRBITVDK.js.map +0 -1
  250. package/dist/chunk-QRL3QDUE.js.map +0 -1
  251. package/dist/chunk-RH46WZRN.mjs +0 -219
  252. package/dist/chunk-RH46WZRN.mjs.map +0 -1
  253. package/dist/chunk-TDJZFBHI.mjs.map +0 -1
  254. package/dist/chunk-U274XAMD.js.map +0 -1
  255. package/dist/chunk-VJV7WUR2.mjs.map +0 -1
  256. package/dist/chunk-XA2GJFVM.js.map +0 -1
  257. package/dist/chunk-XV2FFSJU.mjs.map +0 -1
  258. package/dist/chunk-YCE6HRWW.js.map +0 -1
  259. package/dist/chunk-YUCKBIRE.mjs.map +0 -1
  260. package/dist/chunk-ZKOQCKOR.js +0 -8
  261. package/dist/forms/native-fallback.d.ts +0 -41
  262. package/dist/forms/native-fallback.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server/mint-site-token.ts"],"names":[],"mappings":"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","file":"chunk-JS265UKZ.mjs","sourcesContent":["/**\n * @sonordev/site-kit/server — Client-token minting (server-only)\n *\n * SiteKitLayout holds the raw `SONOR_API_KEY` server-side but must NOT ship it to\n * the browser. This mints a short-lived, project-scoped token (server-to-server,\n * authenticated with the raw key, no browser Origin so it passes domain-binding)\n * and returns THAT for the layout to inject instead.\n *\n * `import 'server-only'` makes importing this into a client bundle a hard build\n * error — the raw key stays on the server.\n *\n * FAILS CLOSED. If minting genuinely fails, this throws and the build fails —\n * it does NOT publish the raw key. A failed deploy is loud and recoverable; a\n * leaked project secret in public HTML is neither. The only paths that still\n * return the raw key are a 404 (an older API that predates the endpoint, an\n * expected state during rollout) and the explicit `SONOR_SITE_TOKEN_FALLBACK=raw`\n * escape hatch, for when shipping beats being blocked.\n *\n * That posture is not academic. Through 3.5.0 a throttled mint (HTTP 429)\n * returned the raw key AND cached it for 60s, so every page rendered in that\n * window shipped the secret: one real build came out with 180 of 272 prerendered\n * pages carrying it. The build \"succeeded\" and nothing downstream noticed.\n * Rate limiting is transient by definition — the fix is to wait and retry, not\n * to downgrade to publishing the key.\n *\n * Two things keep the request count low enough that throttling is rare:\n * - a module-level memo, so a worker mints once and every page reuses the seed;\n * - an in-flight map, because that memo is only populated AFTER the await. Next\n * renders pages concurrently, so without coalescing the first N concurrent\n * renders in a process all miss the cache and all fire a request — a\n * stampede per worker, multiplied by one worker per core. That is how a\n * single site exhausted a 60/min budget.\n *\n * SEED FRESHNESS: a static page's seed carries `iat` = build time, and the API\n * refuses to refresh a seed older than SITE_TOKEN_MAX_REFRESH_AGE_SECONDS\n * (sonor-api common/site-auth/site-token.util.ts — currently 400 days, raised\n * from 30 alongside this fix). A site not rebuilt within that window serves a\n * seed the client cannot refresh, and its forms/analytics/engage start failing\n * for new visitors. Keep that limit comfortably longer than the fleet's\n * realistic redeploy gap; marketing sites routinely go many months untouched.\n */\nimport 'server-only'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { resolveFetch } from '../shared/fresh-fetch'\n\n// Re-exported for this module's unit tests; the helper lives in shared/fresh-fetch.\nexport { resolveFetch }\n\nconst MINT_PATH = '/api/public/site-token'\nconst MINT_TIMEOUT_MS = 4_000\nconst SITE_TOKEN_PREFIX = 'st1.'\n\n/**\n * Retry budget for transient failures (429 / 5xx / network). Rate limiting is\n * measured per minute, so a few seconds of backoff clears it; the whole ladder\n * costs at most ~20s once per build worker, and only when something is wrong.\n */\nconst MINT_MAX_ATTEMPTS = 4\nconst MINT_BACKOFF_BASE_MS = 500\nconst MINT_BACKOFF_CAP_MS = 8_000\n\ninterface CacheEntry {\n credential: string\n expiresAt: number\n}\nconst cache = new Map<string, CacheEntry>()\n\n/**\n * Requests already in flight, keyed by raw key. The `cache` above is only\n * written after the response lands, so concurrent renders would otherwise each\n * start their own mint. See the module header.\n */\nconst inflight = new Map<string, Promise<string>>()\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\n/**\n * Honour `Retry-After` when the server sends it (delta-seconds or HTTP date),\n * otherwise exponential backoff. Capped so a misconfigured server cannot stall\n * a build indefinitely.\n */\nfunction retryDelayMs(res: Response | null, attempt: number): number {\n const header = res?.headers?.get?.('retry-after')\n if (header) {\n const seconds = Number(header)\n const ms = Number.isFinite(seconds)\n ? seconds * 1000\n : Date.parse(header) - Date.now()\n if (Number.isFinite(ms) && ms > 0) return Math.min(ms, MINT_BACKOFF_CAP_MS)\n }\n return Math.min(MINT_BACKOFF_BASE_MS * 2 ** attempt, MINT_BACKOFF_CAP_MS)\n}\n\n/** 429 and 5xx are transient; waiting is the correct response to both. */\nconst isRetryableStatus = (status: number) => status === 429 || status >= 500\n\n/**\n * The escape hatch, for when a broken mint must not block a release. Opt-in and\n * loud: it re-enables publishing the project secret in page HTML.\n */\nfunction rawFallbackAllowed(): boolean {\n return (\n typeof process !== 'undefined' &&\n process.env?.SONOR_SITE_TOKEN_FALLBACK === 'raw'\n )\n}\n\nfunction failClosed(reason: string, rawKey: string, now: number): string {\n if (rawFallbackAllowed()) {\n console.warn(\n `[SiteKitLayout] Token mint failed (${reason}) and SONOR_SITE_TOKEN_FALLBACK=raw is set; ` +\n 'injecting the RAW API KEY into page HTML. The key is publicly readable wherever ' +\n 'this page is served — unset the escape hatch once the mint is healthy.',\n )\n cache.set(rawKey, { credential: rawKey, expiresAt: now + 60_000 })\n return rawKey\n }\n throw new Error(\n `site-kit: could not mint a site token (${reason}). Refusing to publish the raw ` +\n 'SONOR_API_KEY in page HTML, so this build fails instead. Check that the mint ' +\n 'endpoint is reachable and the key is valid, then rebuild. To ship anyway — ' +\n 'which publishes the project secret in public HTML — set SONOR_SITE_TOKEN_FALLBACK=raw.',\n )\n}\n\n/*\n * The mint uses Next's saved original fetch (resolveFetch, shared/fresh-fetch.ts,\n * which documents why). A credential is not page data: `cache: 'no-store'` would\n * bail a static route before the request goes out (verified on Next 16.2.9: the\n * mint endpoint received zero requests for a static build, and every page\n * shipped the raw key), and a Data Cache entry would let a fresh deploy re-bake a\n * months-old seed. The module-level memo above is the only cache we want: one\n * mint per build process, and a build-fresh seed on every deploy.\n */\n\n/**\n * Next signals \"this render must go dynamic\" by THROWING (DynamicServerError,\n * static-gen bailout, redirect/notFound digests, React's postpone). Those are\n * the framework's control flow, not a mint failure. Swallowing one makes Next\n * believe the render succeeded statically — which is precisely how the raw key\n * ended up stranded in prerendered HTML with no error anywhere. Let them\n * propagate; only real network/HTTP failures fall back to the raw key.\n *\n * @internal Exported for unit tests; not part of the package's public API.\n */\nexport function isFrameworkSignal(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false\n const e = err as { digest?: unknown; code?: unknown; $$typeof?: unknown }\n if (e.digest === 'DYNAMIC_SERVER_USAGE') return true\n if (e.code === 'NEXT_STATIC_GEN_BAILOUT') return true\n if (typeof e.digest === 'string' && e.digest.startsWith('NEXT_')) return true\n if (e.$$typeof === Symbol.for('react.postpone')) return true\n return false\n}\n\nexport async function resolveClientCredential(\n rawKey: string,\n apiUrl: string,\n): Promise<string> {\n if (!rawKey) return ''\n // Env should hold a raw key; if it already looks like a token, pass it through.\n if (rawKey.startsWith(SITE_TOKEN_PREFIX)) return rawKey\n\n const cached = cache.get(rawKey)\n if (cached && cached.expiresAt > Date.now()) return cached.credential\n\n // Collapse concurrent renders onto one request rather than a stampede.\n const pending = inflight.get(rawKey)\n if (pending) return pending\n\n const request = mintWithRetry(rawKey, apiUrl)\n inflight.set(rawKey, request)\n try {\n return await request\n } finally {\n inflight.delete(rawKey)\n }\n}\n\nasync function mintWithRetry(rawKey: string, apiUrl: string): Promise<string> {\n const base = (apiUrl || 'https://api.sonor.io').replace(/\\/+$/, '')\n let lastReason = 'unknown error'\n\n for (let attempt = 0; attempt < MINT_MAX_ATTEMPTS; attempt++) {\n let res: Response | null = null\n try {\n const { fn: fetchFn, unpatched } = resolveFetch()\n res = await fetchFn(`${base}${MINT_PATH}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': rawKey,\n 'x-sitekit-version': SITE_KIT_VERSION,\n },\n // Only meaningful on the fallback path, where a future Next stopped\n // exposing `_nextOriginalFetch` and we're back on the patched fetch.\n // `revalidate: false` is the one setting that neither bails out of static\n // generation nor downgrades the route to ISR; a Data Cache entry is the\n // lesser evil versus shipping the raw key again.\n ...(unpatched ? {} : ({ next: { revalidate: false } } as RequestInit)),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(MINT_TIMEOUT_MS)\n : undefined,\n })\n } catch (err) {\n // Never swallow the framework's own control flow — see isFrameworkSignal.\n if (isFrameworkSignal(err)) throw err\n lastReason = err instanceof Error ? err.message : 'network error'\n if (attempt < MINT_MAX_ATTEMPTS - 1) {\n await sleep(retryDelayMs(null, attempt))\n continue\n }\n break\n }\n\n if (res.ok) {\n const data: any = await res.json().catch(() => null)\n const token = data?.token\n const expiresIn = typeof data?.expiresIn === 'number' ? data.expiresIn : 3600\n if (typeof token === 'string' && token.startsWith(SITE_TOKEN_PREFIX)) {\n cache.set(rawKey, {\n credential: token,\n expiresAt: Date.now() + Math.min((expiresIn * 1000) / 2, 30 * 60_000),\n })\n return token\n }\n lastReason = 'mint returned no usable token'\n break\n }\n\n // 404 = an API older than the endpoint. Expected during rollout, and the raw\n // key is exactly what such an API still expects, so this stays a fallback\n // rather than a build failure.\n if (res.status === 404) {\n cache.set(rawKey, { credential: rawKey, expiresAt: Date.now() + 60_000 })\n return rawKey\n }\n\n lastReason = `HTTP ${res.status}`\n if (isRetryableStatus(res.status) && attempt < MINT_MAX_ATTEMPTS - 1) {\n const delay = retryDelayMs(res, attempt)\n console.warn(\n `[SiteKitLayout] Token mint returned ${res.status}; retrying in ${Math.round(delay)}ms ` +\n `(attempt ${attempt + 2}/${MINT_MAX_ATTEMPTS}).`,\n )\n await sleep(delay)\n continue\n }\n break\n }\n\n return failClosed(lastReason, rawKey, Date.now())\n}\n"]}
1
+ {"version":3,"sources":["../src/server/mint-site-token.ts"],"names":[],"mappings":"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","file":"chunk-KTFZNSRY.mjs","sourcesContent":["/**\n * @sonordev/site-kit/server — Client-token minting (server-only)\n *\n * SiteKitLayout holds the raw `SONOR_API_KEY` server-side but must NOT ship it to\n * the browser. This mints a short-lived, project-scoped token (server-to-server,\n * authenticated with the raw key, no browser Origin so it passes domain-binding)\n * and returns THAT for the layout to inject instead.\n *\n * `import 'server-only'` makes importing this into a client bundle a hard build\n * error — the raw key stays on the server.\n *\n * FAILS CLOSED. If minting genuinely fails, this throws and the build fails —\n * it does NOT publish the raw key. A failed deploy is loud and recoverable; a\n * leaked project secret in public HTML is neither. The only paths that still\n * return the raw key are a 404 (an older API that predates the endpoint, an\n * expected state during rollout) and the explicit `SONOR_SITE_TOKEN_FALLBACK=raw`\n * escape hatch, for when shipping beats being blocked.\n *\n * That posture is not academic. Through 3.5.0 a throttled mint (HTTP 429)\n * returned the raw key AND cached it for 60s, so every page rendered in that\n * window shipped the secret: one real build came out with 180 of 272 prerendered\n * pages carrying it. The build \"succeeded\" and nothing downstream noticed.\n * Rate limiting is transient by definition — the fix is to wait and retry, not\n * to downgrade to publishing the key.\n *\n * Two things keep the request count low enough that throttling is rare:\n * - a module-level memo, so a worker mints once and every page reuses the seed;\n * - an in-flight map, because that memo is only populated AFTER the await. Next\n * renders pages concurrently, so without coalescing the first N concurrent\n * renders in a process all miss the cache and all fire a request — a\n * stampede per worker, multiplied by one worker per core. That is how a\n * single site exhausted a 60/min budget.\n *\n * SEED FRESHNESS: a static page's seed carries `iat` = build time, and the API\n * refuses to refresh a seed older than SITE_TOKEN_MAX_REFRESH_AGE_SECONDS\n * (sonor-api common/site-auth/site-token.util.ts — currently 400 days, raised\n * from 30 alongside this fix). A site not rebuilt within that window serves a\n * seed the client cannot refresh, and its forms/analytics/engage start failing\n * for new visitors. Keep that limit comfortably longer than the fleet's\n * realistic redeploy gap; marketing sites routinely go many months untouched.\n */\nimport 'server-only'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { resolveFetch } from '../shared/fresh-fetch'\n\n// Re-exported for this module's unit tests; the helper lives in shared/fresh-fetch.\nexport { resolveFetch }\n\nconst MINT_PATH = '/api/public/site-token'\nconst MINT_TIMEOUT_MS = 4_000\nconst SITE_TOKEN_PREFIX = 'st1.'\n\n/**\n * Retry budget for transient failures (429 / 5xx / network). Rate limiting is\n * measured per minute, so a few seconds of backoff clears it; the whole ladder\n * costs at most ~20s once per build worker, and only when something is wrong.\n */\nconst MINT_MAX_ATTEMPTS = 4\nconst MINT_BACKOFF_BASE_MS = 500\nconst MINT_BACKOFF_CAP_MS = 8_000\n\ninterface CacheEntry {\n credential: string\n expiresAt: number\n}\nconst cache = new Map<string, CacheEntry>()\n\n/**\n * Requests already in flight, keyed by raw key. The `cache` above is only\n * written after the response lands, so concurrent renders would otherwise each\n * start their own mint. See the module header.\n */\nconst inflight = new Map<string, Promise<string>>()\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\n/**\n * Honour `Retry-After` when the server sends it (delta-seconds or HTTP date),\n * otherwise exponential backoff. Capped so a misconfigured server cannot stall\n * a build indefinitely.\n */\nfunction retryDelayMs(res: Response | null, attempt: number): number {\n const header = res?.headers?.get?.('retry-after')\n if (header) {\n const seconds = Number(header)\n const ms = Number.isFinite(seconds)\n ? seconds * 1000\n : Date.parse(header) - Date.now()\n if (Number.isFinite(ms) && ms > 0) return Math.min(ms, MINT_BACKOFF_CAP_MS)\n }\n return Math.min(MINT_BACKOFF_BASE_MS * 2 ** attempt, MINT_BACKOFF_CAP_MS)\n}\n\n/** 429 and 5xx are transient; waiting is the correct response to both. */\nconst isRetryableStatus = (status: number) => status === 429 || status >= 500\n\n/**\n * The escape hatch, for when a broken mint must not block a release. Opt-in and\n * loud: it re-enables publishing the project secret in page HTML.\n */\nfunction rawFallbackAllowed(): boolean {\n return (\n typeof process !== 'undefined' &&\n process.env?.SONOR_SITE_TOKEN_FALLBACK === 'raw'\n )\n}\n\nfunction failClosed(reason: string, rawKey: string, now: number): string {\n if (rawFallbackAllowed()) {\n console.warn(\n `[SiteKitLayout] Token mint failed (${reason}) and SONOR_SITE_TOKEN_FALLBACK=raw is set; ` +\n 'injecting the RAW API KEY into page HTML. The key is publicly readable wherever ' +\n 'this page is served — unset the escape hatch once the mint is healthy.',\n )\n cache.set(rawKey, { credential: rawKey, expiresAt: now + 60_000 })\n return rawKey\n }\n throw new Error(\n `site-kit: could not mint a site token (${reason}). Refusing to publish the raw ` +\n 'SONOR_API_KEY in page HTML, so this build fails instead. Check that the mint ' +\n 'endpoint is reachable and the key is valid, then rebuild. To ship anyway — ' +\n 'which publishes the project secret in public HTML — set SONOR_SITE_TOKEN_FALLBACK=raw.',\n )\n}\n\n/*\n * The mint uses Next's saved original fetch (resolveFetch, shared/fresh-fetch.ts,\n * which documents why). A credential is not page data: `cache: 'no-store'` would\n * bail a static route before the request goes out (verified on Next 16.2.9: the\n * mint endpoint received zero requests for a static build, and every page\n * shipped the raw key), and a Data Cache entry would let a fresh deploy re-bake a\n * months-old seed. The module-level memo above is the only cache we want: one\n * mint per build process, and a build-fresh seed on every deploy.\n */\n\n/**\n * Next signals \"this render must go dynamic\" by THROWING (DynamicServerError,\n * static-gen bailout, redirect/notFound digests, React's postpone). Those are\n * the framework's control flow, not a mint failure. Swallowing one makes Next\n * believe the render succeeded statically — which is precisely how the raw key\n * ended up stranded in prerendered HTML with no error anywhere. Let them\n * propagate; only real network/HTTP failures fall back to the raw key.\n *\n * @internal Exported for unit tests; not part of the package's public API.\n */\nexport function isFrameworkSignal(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false\n const e = err as { digest?: unknown; code?: unknown; $$typeof?: unknown }\n if (e.digest === 'DYNAMIC_SERVER_USAGE') return true\n if (e.code === 'NEXT_STATIC_GEN_BAILOUT') return true\n if (typeof e.digest === 'string' && e.digest.startsWith('NEXT_')) return true\n if (e.$$typeof === Symbol.for('react.postpone')) return true\n return false\n}\n\nexport async function resolveClientCredential(\n rawKey: string,\n apiUrl: string,\n): Promise<string> {\n if (!rawKey) return ''\n // Env should hold a raw key; if it already looks like a token, pass it through.\n if (rawKey.startsWith(SITE_TOKEN_PREFIX)) return rawKey\n\n const cached = cache.get(rawKey)\n if (cached && cached.expiresAt > Date.now()) return cached.credential\n\n // Collapse concurrent renders onto one request rather than a stampede.\n const pending = inflight.get(rawKey)\n if (pending) return pending\n\n const request = mintWithRetry(rawKey, apiUrl)\n inflight.set(rawKey, request)\n try {\n return await request\n } finally {\n inflight.delete(rawKey)\n }\n}\n\nasync function mintWithRetry(rawKey: string, apiUrl: string): Promise<string> {\n const base = (apiUrl || 'https://api.sonor.io').replace(/\\/+$/, '')\n let lastReason = 'unknown error'\n\n for (let attempt = 0; attempt < MINT_MAX_ATTEMPTS; attempt++) {\n let res: Response | null = null\n try {\n const { fn: fetchFn, unpatched } = resolveFetch()\n res = await fetchFn(`${base}${MINT_PATH}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': rawKey,\n 'x-sitekit-version': SITE_KIT_VERSION,\n },\n // Only meaningful on the fallback path, where a future Next stopped\n // exposing `_nextOriginalFetch` and we're back on the patched fetch.\n // `revalidate: false` is the one setting that neither bails out of static\n // generation nor downgrades the route to ISR; a Data Cache entry is the\n // lesser evil versus shipping the raw key again.\n ...(unpatched ? {} : ({ next: { revalidate: false } } as RequestInit)),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(MINT_TIMEOUT_MS)\n : undefined,\n })\n } catch (err) {\n // Never swallow the framework's own control flow — see isFrameworkSignal.\n if (isFrameworkSignal(err)) throw err\n lastReason = err instanceof Error ? err.message : 'network error'\n if (attempt < MINT_MAX_ATTEMPTS - 1) {\n await sleep(retryDelayMs(null, attempt))\n continue\n }\n break\n }\n\n if (res.ok) {\n const data: any = await res.json().catch(() => null)\n const token = data?.token\n const expiresIn = typeof data?.expiresIn === 'number' ? data.expiresIn : 3600\n if (typeof token === 'string' && token.startsWith(SITE_TOKEN_PREFIX)) {\n cache.set(rawKey, {\n credential: token,\n expiresAt: Date.now() + Math.min((expiresIn * 1000) / 2, 30 * 60_000),\n })\n return token\n }\n lastReason = 'mint returned no usable token'\n break\n }\n\n // 404 = an API older than the endpoint. Expected during rollout, and the raw\n // key is exactly what such an API still expects, so this stays a fallback\n // rather than a build failure.\n if (res.status === 404) {\n cache.set(rawKey, { credential: rawKey, expiresAt: Date.now() + 60_000 })\n return rawKey\n }\n\n lastReason = `HTTP ${res.status}`\n if (isRetryableStatus(res.status) && attempt < MINT_MAX_ATTEMPTS - 1) {\n const delay = retryDelayMs(res, attempt)\n console.warn(\n `[SiteKitLayout] Token mint returned ${res.status}; retrying in ${Math.round(delay)}ms ` +\n `(attempt ${attempt + 2}/${MINT_MAX_ATTEMPTS}).`,\n )\n await sleep(delay)\n continue\n }\n break\n }\n\n return failClosed(lastReason, rawKey, Date.now())\n}\n"]}
@@ -2,8 +2,8 @@
2
2
  import { sanitizeFleetVersion, buildFleetHeartbeatPayload } from './chunk-4WCA2HM6.mjs';
3
3
  import { useDeferredActivation } from './chunk-ZVBLXXNJ.mjs';
4
4
  import { resolveAnalyticsTarget } from './chunk-A6UYV475.mjs';
5
- import { sonorFetch } from './chunk-XNZ2WFXR.mjs';
6
- import { SITE_KIT_VERSION } from './chunk-C654VCWV.mjs';
5
+ import { sonorFetch } from './chunk-WS3J5T3K.mjs';
6
+ import { SITE_KIT_VERSION } from './chunk-WT5WNIR2.mjs';
7
7
  import { useRef, useEffect } from 'react';
8
8
 
9
9
  // src/fleet/heartbeat.ts
@@ -97,5 +97,5 @@ function FleetHeartbeat({
97
97
  }
98
98
 
99
99
  export { FLEET_HEARTBEAT_PATH, FleetHeartbeat, detectNextVersion, sendFleetHeartbeat };
100
- //# sourceMappingURL=chunk-IDBA3YZ6.mjs.map
101
- //# sourceMappingURL=chunk-IDBA3YZ6.mjs.map
100
+ //# sourceMappingURL=chunk-M6PJTITK.mjs.map
101
+ //# sourceMappingURL=chunk-M6PJTITK.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":[],"mappings":"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","file":"chunk-IDBA3YZ6.mjs","sourcesContent":["/**\n * @sonordev/site-kit/fleet — heartbeat sender (single source of truth for the POST)\n *\n * One place that turns fleet inputs into a validated payload and ships it to\n * `/api/public/fleet/heartbeat`. Used by BOTH the client component\n * (FleetHeartbeat) and any build-time caller, so the send path never forks.\n *\n * It routes through the shared analytics send gate and `sonorFetch`, which stamps\n * the x-sitekit-version header, attaches x-api-key, and honors the auth\n * circuit breaker — a site on a stale/canceled key will not spam the API.\n */\n\nimport { resolveAnalyticsTarget, type AnalyticsGateOptions } from '../shared/reporting-gate'\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport {\n buildFleetHeartbeatPayload,\n sanitizeFleetVersion,\n type FleetHeartbeatPayload,\n type FleetHeartbeatSource,\n type FleetModule,\n} from './contract'\n\n/** Canonical ingest path — resolved against the site's Sonor API origin. */\nexport const FLEET_HEARTBEAT_PATH = '/api/public/fleet/heartbeat'\n\nexport interface SendFleetHeartbeatOptions extends AnalyticsGateOptions {\n /** Sonor API origin (e.g. https://api.sonor.io). Trailing slash tolerated. */\n apiUrl: string\n /** Project API key — scopes the request; the project is resolved server-side. */\n apiKey: string\n /** Client-observable active modules (validated/deduped by the contract). */\n modules?: FleetModule[] | string[]\n /** Normalized host for the multi-site sub-dimension (server re-normalizes). */\n site?: string | null\n /** Installed Next.js version, if the caller can resolve it. */\n nextVersion?: string | null\n /** 'client' (runtime boot) or 'build' (CI). Default 'client'. */\n source?: FleetHeartbeatSource\n /** Override the reported kit version. Defaults to the runtime SITE_KIT_VERSION. */\n kitVersion?: string\n /** Log one info line with the result. Never logs at error level. */\n debug?: boolean\n}\n\n/**\n * Best-effort runtime detection of the host Next.js version. Unreliable by\n * design — Next does not expose its version consistently across app/pages\n * router — so it returns null unless a site injects `__SITE_KIT_NEXT_VERSION__`\n * or `window.next.version` is present. Build-time callers should pass\n * `nextVersion` explicitly (resolvable from `next/package.json`).\n */\nexport function detectNextVersion(): string | null {\n if (typeof window === 'undefined') return null\n const w = window as unknown as {\n __SITE_KIT_NEXT_VERSION__?: string\n next?: { version?: string }\n }\n return sanitizeFleetVersion(w.__SITE_KIT_NEXT_VERSION__ ?? w.next?.version ?? null)\n}\n\n/**\n * Fire one heartbeat. Fire-and-forget by contract: it never throws — a\n * telemetry POST must never break a visitor's page or a site's build. Returns\n * true only when the POST was accepted (2xx).\n */\nexport async function sendFleetHeartbeat(opts: SendFleetHeartbeatOptions): Promise<boolean> {\n const { apiUrl, apiKey } = opts\n if (!apiUrl || !apiKey) return false\n const target = resolveAnalyticsTarget(opts)\n if (!target) return false\n\n const payload: FleetHeartbeatPayload = buildFleetHeartbeatPayload({\n kitVersion: opts.kitVersion || SITE_KIT_VERSION,\n nextVersion: opts.nextVersion,\n modules: opts.modules,\n site: opts.site,\n source: opts.source,\n })\n\n try {\n const res = await sonorFetch(\n `${target.apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { apiKey: target.apiKey, retries: 0 },\n )\n if (opts.debug) {\n // info, never error: a visitor's console must stay clean (Lighthouse).\n console.info(\n `[Sonor fleet] heartbeat ${res.status} v${payload.kit_version} [${payload.modules.join(',') || 'none'}]`,\n )\n }\n return res.ok\n } catch (err) {\n if (opts.debug) console.info('[Sonor fleet] heartbeat skipped (network)', err)\n return false\n }\n}\n","/**\n * @sonordev/site-kit/fleet — FleetHeartbeat\n *\n * A childless, effect-only client component that reports this site's build\n * fingerprint (kit version, Next.js version, active modules, host) to Sonor\n * exactly once per browser session. It exists so the platform can answer\n * \"which version / modules / key state is each site running\" without shelling\n * into every client repo.\n *\n * It is mounted as a deferred, CHILDLESS SIBLING — never a wrapper — by\n * SiteKitClientProviders, behind the same idle/first-interaction gate as\n * Analytics and Engage, so it never enters the critical hydration path (see\n * CLAUDE.md \"the page must server-render\"). It renders null and carries no\n * visitor data.\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { detectNextVersion, sendFleetHeartbeat } from './heartbeat'\nimport { resolveAnalyticsTarget, type AnalyticsGateOptions } from '../shared/reporting-gate'\nimport type { FleetModule } from './contract'\n\n/** Session guard key — one heartbeat per session, re-sent when version/host changes. */\nconst SESSION_GUARD_KEY = '_sk_fleet_hb'\n\nexport interface FleetHeartbeatProps extends AnalyticsGateOptions {\n /** Sonor API origin. Falls back to `window.__SITE_KIT_API_URL__` then api.sonor.io. */\n apiUrl?: string\n /** Project API key. Falls back to `window.__SITE_KIT_API_KEY__`. */\n apiKey?: string\n /** Host for the multi-site sub-dimension. Falls back to `__SITE_KIT_SITE__` then location.host. */\n site?: string | null\n /** Client-observable active modules (SiteKitClientProviders passes the enabled set). */\n modules?: FleetModule[]\n /** Installed Next.js version if known; otherwise best-effort runtime detection. */\n nextVersion?: string | null\n /**\n * Self-defer to idle / first interaction. Default true so standalone use is\n * safe. SiteKitClientProviders already gates the mount and passes false.\n */\n defer?: boolean\n /** Send at most once per browser session per (version, host). Default true. */\n oncePerSession?: boolean\n debug?: boolean\n}\n\nexport function FleetHeartbeat({\n apiUrl,\n apiKey,\n site,\n modules,\n nextVersion,\n defer = true,\n oncePerSession = true,\n debug = false,\n allowInFrame = false,\n allowLocalhost = false,\n}: FleetHeartbeatProps) {\n const ready = useDeferredActivation(defer)\n const sentRef = useRef(false)\n\n useEffect(() => {\n if (!ready || sentRef.current) return\n if (typeof window === 'undefined') return\n\n const w = window as unknown as {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n __SITE_KIT_SITE__?: string\n }\n const target = resolveAnalyticsTarget({ apiUrl, apiKey, allowInFrame, allowLocalhost, debug })\n if (!target) return // Gate before stamping session storage.\n const { apiUrl: url, apiKey: key } = target\n\n const resolvedSite = site ?? w.__SITE_KIT_SITE__ ?? window.location.host ?? ''\n\n // Once-per-session-per-(version,host): keeps heartbeat volume to one POST\n // per visitor session while still re-reporting after an upgrade or on a\n // different sub-site. Storage-disabled browsers simply send once per load.\n if (oncePerSession) {\n const stamp = `${SITE_KIT_VERSION}:${resolvedSite}`\n try {\n if (sessionStorage.getItem(SESSION_GUARD_KEY) === stamp) {\n sentRef.current = true\n return\n }\n sessionStorage.setItem(SESSION_GUARD_KEY, stamp)\n } catch {\n /* storage unavailable — fall through and send this load */\n }\n }\n\n sentRef.current = true\n void sendFleetHeartbeat({\n apiUrl: url,\n apiKey: key,\n site: resolvedSite,\n modules,\n nextVersion: nextVersion ?? detectNextVersion(),\n source: 'client',\n allowInFrame,\n allowLocalhost,\n debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug, allowInFrame, allowLocalhost])\n\n return null\n}\n"]}
1
+ {"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":[],"mappings":"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","file":"chunk-M6PJTITK.mjs","sourcesContent":["/**\n * @sonordev/site-kit/fleet — heartbeat sender (single source of truth for the POST)\n *\n * One place that turns fleet inputs into a validated payload and ships it to\n * `/api/public/fleet/heartbeat`. Used by BOTH the client component\n * (FleetHeartbeat) and any build-time caller, so the send path never forks.\n *\n * It routes through the shared analytics send gate and `sonorFetch`, which stamps\n * the x-sitekit-version header, attaches x-api-key, and honors the auth\n * circuit breaker — a site on a stale/canceled key will not spam the API.\n */\n\nimport { resolveAnalyticsTarget, type AnalyticsGateOptions } from '../shared/reporting-gate'\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport {\n buildFleetHeartbeatPayload,\n sanitizeFleetVersion,\n type FleetHeartbeatPayload,\n type FleetHeartbeatSource,\n type FleetModule,\n} from './contract'\n\n/** Canonical ingest path — resolved against the site's Sonor API origin. */\nexport const FLEET_HEARTBEAT_PATH = '/api/public/fleet/heartbeat'\n\nexport interface SendFleetHeartbeatOptions extends AnalyticsGateOptions {\n /** Sonor API origin (e.g. https://api.sonor.io). Trailing slash tolerated. */\n apiUrl: string\n /** Project API key — scopes the request; the project is resolved server-side. */\n apiKey: string\n /** Client-observable active modules (validated/deduped by the contract). */\n modules?: FleetModule[] | string[]\n /** Normalized host for the multi-site sub-dimension (server re-normalizes). */\n site?: string | null\n /** Installed Next.js version, if the caller can resolve it. */\n nextVersion?: string | null\n /** 'client' (runtime boot) or 'build' (CI). Default 'client'. */\n source?: FleetHeartbeatSource\n /** Override the reported kit version. Defaults to the runtime SITE_KIT_VERSION. */\n kitVersion?: string\n /** Log one info line with the result. Never logs at error level. */\n debug?: boolean\n}\n\n/**\n * Best-effort runtime detection of the host Next.js version. Unreliable by\n * design — Next does not expose its version consistently across app/pages\n * router — so it returns null unless a site injects `__SITE_KIT_NEXT_VERSION__`\n * or `window.next.version` is present. Build-time callers should pass\n * `nextVersion` explicitly (resolvable from `next/package.json`).\n */\nexport function detectNextVersion(): string | null {\n if (typeof window === 'undefined') return null\n const w = window as unknown as {\n __SITE_KIT_NEXT_VERSION__?: string\n next?: { version?: string }\n }\n return sanitizeFleetVersion(w.__SITE_KIT_NEXT_VERSION__ ?? w.next?.version ?? null)\n}\n\n/**\n * Fire one heartbeat. Fire-and-forget by contract: it never throws — a\n * telemetry POST must never break a visitor's page or a site's build. Returns\n * true only when the POST was accepted (2xx).\n */\nexport async function sendFleetHeartbeat(opts: SendFleetHeartbeatOptions): Promise<boolean> {\n const { apiUrl, apiKey } = opts\n if (!apiUrl || !apiKey) return false\n const target = resolveAnalyticsTarget(opts)\n if (!target) return false\n\n const payload: FleetHeartbeatPayload = buildFleetHeartbeatPayload({\n kitVersion: opts.kitVersion || SITE_KIT_VERSION,\n nextVersion: opts.nextVersion,\n modules: opts.modules,\n site: opts.site,\n source: opts.source,\n })\n\n try {\n const res = await sonorFetch(\n `${target.apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { apiKey: target.apiKey, retries: 0 },\n )\n if (opts.debug) {\n // info, never error: a visitor's console must stay clean (Lighthouse).\n console.info(\n `[Sonor fleet] heartbeat ${res.status} v${payload.kit_version} [${payload.modules.join(',') || 'none'}]`,\n )\n }\n return res.ok\n } catch (err) {\n if (opts.debug) console.info('[Sonor fleet] heartbeat skipped (network)', err)\n return false\n }\n}\n","/**\n * @sonordev/site-kit/fleet — FleetHeartbeat\n *\n * A childless, effect-only client component that reports this site's build\n * fingerprint (kit version, Next.js version, active modules, host) to Sonor\n * exactly once per browser session. It exists so the platform can answer\n * \"which version / modules / key state is each site running\" without shelling\n * into every client repo.\n *\n * It is mounted as a deferred, CHILDLESS SIBLING — never a wrapper — by\n * SiteKitClientProviders, behind the same idle/first-interaction gate as\n * Analytics and Engage, so it never enters the critical hydration path (see\n * CLAUDE.md \"the page must server-render\"). It renders null and carries no\n * visitor data.\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { detectNextVersion, sendFleetHeartbeat } from './heartbeat'\nimport { resolveAnalyticsTarget, type AnalyticsGateOptions } from '../shared/reporting-gate'\nimport type { FleetModule } from './contract'\n\n/** Session guard key — one heartbeat per session, re-sent when version/host changes. */\nconst SESSION_GUARD_KEY = '_sk_fleet_hb'\n\nexport interface FleetHeartbeatProps extends AnalyticsGateOptions {\n /** Sonor API origin. Falls back to `window.__SITE_KIT_API_URL__` then api.sonor.io. */\n apiUrl?: string\n /** Project API key. Falls back to `window.__SITE_KIT_API_KEY__`. */\n apiKey?: string\n /** Host for the multi-site sub-dimension. Falls back to `__SITE_KIT_SITE__` then location.host. */\n site?: string | null\n /** Client-observable active modules (SiteKitClientProviders passes the enabled set). */\n modules?: FleetModule[]\n /** Installed Next.js version if known; otherwise best-effort runtime detection. */\n nextVersion?: string | null\n /**\n * Self-defer to idle / first interaction. Default true so standalone use is\n * safe. SiteKitClientProviders already gates the mount and passes false.\n */\n defer?: boolean\n /** Send at most once per browser session per (version, host). Default true. */\n oncePerSession?: boolean\n debug?: boolean\n}\n\nexport function FleetHeartbeat({\n apiUrl,\n apiKey,\n site,\n modules,\n nextVersion,\n defer = true,\n oncePerSession = true,\n debug = false,\n allowInFrame = false,\n allowLocalhost = false,\n}: FleetHeartbeatProps) {\n const ready = useDeferredActivation(defer)\n const sentRef = useRef(false)\n\n useEffect(() => {\n if (!ready || sentRef.current) return\n if (typeof window === 'undefined') return\n\n const w = window as unknown as {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n __SITE_KIT_SITE__?: string\n }\n const target = resolveAnalyticsTarget({ apiUrl, apiKey, allowInFrame, allowLocalhost, debug })\n if (!target) return // Gate before stamping session storage.\n const { apiUrl: url, apiKey: key } = target\n\n const resolvedSite = site ?? w.__SITE_KIT_SITE__ ?? window.location.host ?? ''\n\n // Once-per-session-per-(version,host): keeps heartbeat volume to one POST\n // per visitor session while still re-reporting after an upgrade or on a\n // different sub-site. Storage-disabled browsers simply send once per load.\n if (oncePerSession) {\n const stamp = `${SITE_KIT_VERSION}:${resolvedSite}`\n try {\n if (sessionStorage.getItem(SESSION_GUARD_KEY) === stamp) {\n sentRef.current = true\n return\n }\n sessionStorage.setItem(SESSION_GUARD_KEY, stamp)\n } catch {\n /* storage unavailable — fall through and send this load */\n }\n }\n\n sentRef.current = true\n void sendFleetHeartbeat({\n apiUrl: url,\n apiKey: key,\n site: resolvedSite,\n modules,\n nextVersion: nextVersion ?? detectNextVersion(),\n source: 'client',\n allowInFrame,\n allowLocalhost,\n debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug, allowInFrame, allowLocalhost])\n\n return null\n}\n"]}
@@ -28,7 +28,7 @@ function useFieldControls(fields) {
28
28
  }
29
29
  if (needsFile && !requested.current.file) {
30
30
  requested.current.file = true;
31
- import('./FileField-LMSLNNRX.js').then((m) => {
31
+ import('./FileField-QSQS6WRY.js').then((m) => {
32
32
  if (!cancelled) setControls((prev) => ({ ...prev, File: m.FileField }));
33
33
  }).catch(() => {
34
34
  requested.current.file = false;
@@ -137,5 +137,5 @@ exports.stageEstimate = stageEstimate;
137
137
  exports.stageFirstName = stageFirstName;
138
138
  exports.stageViews = stageViews;
139
139
  exports.useFieldControls = useFieldControls;
140
- //# sourceMappingURL=chunk-UFG2TUBG.js.map
141
- //# sourceMappingURL=chunk-UFG2TUBG.js.map
140
+ //# sourceMappingURL=chunk-MMQLQHZX.js.map
141
+ //# sourceMappingURL=chunk-MMQLQHZX.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/forms/field-controls.ts","../src/forms/experience-plan.ts"],"names":["useMemo","useState","useRef","useEffect","isDisplayOnly","isEmptyValue"],"mappings":"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","file":"chunk-UFG2TUBG.js","sourcesContent":["'use client'\n\n/**\n * The field controls that need React hooks, loaded only when a form actually\n * has a field that uses them.\n *\n * FormField deliberately does NOT import these. DatePicker carries 17 hooks\n * and FileField holds upload state; importing them there put both in\n * FormField's chunk, which the build stamps `'use client'` — so a Server\n * Component rendering a single text input shipped 38 KB of date picker to the\n * browser. Injecting them from the client side keeps FormField hook-free and\n * therefore server-renderable, which is the whole basis of StaticForm.\n *\n * Injection alone still put both controls in the forms entry's eager graph,\n * because a module-level constant referencing them is a static import: every\n * form paid for the date picker, and most forms have no date field. They are\n * now fetched on demand, per control, from the field list.\n *\n * The wait costs nothing visually. FormField already renders the native\n * control when a control is absent (static mode does exactly this), so a date\n * field paints as `<input type=\"date\">` and upgrades in place when the chunk\n * lands — the same enhancement path a no-JS visitor never leaves.\n *\n * Every client renderer of <FormField> (FormClient, FormSpotlight, FormStage)\n * calls this hook, so the experiences and the classic form always offer\n * identical controls.\n */\n\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { FormField as FormFieldType } from './types'\n\nexport interface FieldControls {\n Date?: React.ComponentType<any>\n File?: React.ComponentType<any>\n}\n\n/** Stable identity for the common case (no date, no file) — no render churn. */\nconst NO_CONTROLS: FieldControls = {}\n\nexport function useFieldControls(fields: FormFieldType[] | undefined): FieldControls {\n const needsDate = useMemo(\n () => (fields ?? []).some((f) => f.field_type === 'date' || f.field_type === 'datetime'),\n [fields],\n )\n const needsFile = useMemo(\n () => (fields ?? []).some((f) => f.field_type === 'file'),\n [fields],\n )\n\n const [controls, setControls] = useState<FieldControls>(NO_CONTROLS)\n // Guards re-import when a conditional field toggles visibility mid-fill.\n const requested = useRef({ date: false, file: false })\n\n useEffect(() => {\n let cancelled = false\n\n if (needsDate && !requested.current.date) {\n requested.current.date = true\n import('./DatePicker')\n .then((m) => {\n if (!cancelled) setControls((prev) => ({ ...prev, Date: m.DatePicker }))\n })\n .catch(() => {\n // Chunk never arrived — the native date input stays, which submits\n // the same value. A failed fetch must not cost the lead.\n requested.current.date = false\n })\n }\n\n if (needsFile && !requested.current.file) {\n requested.current.file = true\n import('./FileField')\n .then((m) => {\n if (!cancelled) setControls((prev) => ({ ...prev, File: m.FileField }))\n })\n .catch(() => {\n requested.current.file = false\n })\n }\n\n return () => {\n cancelled = true\n }\n }, [needsDate, needsFile])\n\n return controls\n}\n","/**\n * Shared planning helpers for the alternate form experiences (stage,\n * spotlight). Pure data — no React, no CSS — so FormClient can import the\n * counts it needs for tracking WITHOUT pulling an experience component (and\n * its stylesheet) into every site's forms bundle. The components themselves\n * are lazy-loaded; this module is the only part that ships eagerly.\n */\n\nimport type {\n FormField as FormFieldType,\n FormStep,\n ManagedFormConfig,\n} from './types'\n// Display-only types are never staged; what counts as empty is shared with\n// validation. Both live in field-rules.ts.\nimport { isDisplayOnly, isEmptyValue } from './field-rules'\n\nexport interface StageQuestion {\n field: FormFieldType\n /** The step (chapter) this question belongs to, when the form is multi-step. */\n step?: FormStep\n}\n\nexport interface StageView {\n fields: FormFieldType[]\n step?: FormStep\n}\n\n/**\n * Flatten a form into its ordered question list. Steps become chapters:\n * multi-step forms iterate steps in step_number order (fields are already\n * sorted by sort_order via normalizeFormConfig); stepless fields trail.\n */\nexport function stageQuestions(config: ManagedFormConfig): StageQuestion[] {\n const input = (config.fields || []).filter(\n (f) => !isDisplayOnly(f),\n )\n if (!config.is_multi_step || !config.steps?.length) {\n return input.map((field) => ({ field }))\n }\n const ordered = [...config.steps].sort((a, b) => a.step_number - b.step_number)\n const out: StageQuestion[] = []\n const claimed = new Set<string>()\n for (const step of ordered) {\n for (const field of input) {\n if (field.step_id === step.id) {\n out.push({ field, step })\n claimed.add(field.id)\n }\n }\n }\n for (const field of input) {\n if (!claimed.has(field.id)) out.push({ field })\n }\n return out\n}\n\nconst WIDTH_FRACTION: Record<string, number> = {\n full: 1,\n half: 1 / 2,\n third: 1 / 3,\n quarter: 1 / 4,\n}\n\n/**\n * Group questions into beats using the classic grid's own row math:\n * consecutive sub-full-width fields fill a row (2 halves, 3 thirds...) and\n * that row is one beat; a full-width field closes any open row and stands\n * alone; a chapter change always starts a fresh beat.\n */\nexport function stageViews(config: ManagedFormConfig): StageView[] {\n const questions = stageQuestions(config)\n const views: StageView[] = []\n let open: StageView | null = null\n let openWidth = 0\n\n const close = () => {\n if (open) views.push(open)\n open = null\n openWidth = 0\n }\n\n for (const q of questions) {\n const fraction = WIDTH_FRACTION[q.field.width] ?? 1\n const chapterChanged = open && open.step?.id !== q.step?.id\n if (fraction >= 1 || chapterChanged) close()\n if (!open) {\n open = { fields: [q.field], step: q.step }\n openWidth = fraction\n if (fraction >= 1) close()\n continue\n }\n if (openWidth + fraction > 1.0001) close()\n if (!open) {\n open = { fields: [q.field], step: q.step }\n openWidth = fraction\n continue\n }\n open.fields.push(q.field)\n openWidth += fraction\n }\n close()\n return views\n}\n\nexport function countStageViews(config: ManagedFormConfig): number {\n return stageViews(config).length\n}\n\nexport function countStageQuestions(config: ManagedFormConfig): number {\n return stageQuestions(config).length\n}\n\n/**\n * Honest, friendly time estimate: \"about 40 seconds\".\n * ~8s per question, rounded to something a human would actually say.\n */\nexport function stageEstimate(questionCount: number): string {\n const seconds = questionCount * 8\n if (seconds < 50) return `about ${Math.max(15, Math.ceil(seconds / 5) * 5)} seconds`\n if (seconds <= 90) return 'about a minute'\n return `about ${Math.round(seconds / 60)} minutes`\n}\n\n/** First name for the greeting — conservative: short, single token, else null. */\nexport function stageFirstName(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const token = value.trim().split(/\\s+/)[0]\n if (!token || token.length > 24) return null\n return token\n}\n\nexport function formatAnswer(value: unknown): string | null {\n if (isEmptyValue(value)) return null\n if (Array.isArray(value)) return value.map(String).join(', ')\n if (typeof value === 'boolean') return value ? 'Yes' : 'No'\n return String(value)\n}\n\n/** Single-select types where picking an answer advances after a lock-in beat. */\nexport const AUTO_ADVANCE_TYPES = ['radio', 'select', 'rating']\n"]}
1
+ {"version":3,"sources":["../src/forms/field-controls.ts","../src/forms/experience-plan.ts"],"names":["useMemo","useState","useRef","useEffect","isDisplayOnly","isEmptyValue"],"mappings":"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","file":"chunk-MMQLQHZX.js","sourcesContent":["'use client'\n\n/**\n * The field controls that need React hooks, loaded only when a form actually\n * has a field that uses them.\n *\n * FormField deliberately does NOT import these. DatePicker carries 17 hooks\n * and FileField holds upload state; importing them there put both in\n * FormField's chunk, which the build stamps `'use client'` — so a Server\n * Component rendering a single text input shipped 38 KB of date picker to the\n * browser. Injecting them from the client side keeps FormField hook-free and\n * therefore server-renderable, which is the whole basis of StaticForm.\n *\n * Injection alone still put both controls in the forms entry's eager graph,\n * because a module-level constant referencing them is a static import: every\n * form paid for the date picker, and most forms have no date field. They are\n * now fetched on demand, per control, from the field list.\n *\n * The wait costs nothing visually. FormField already renders the native\n * control when a control is absent (static mode does exactly this), so a date\n * field paints as `<input type=\"date\">` and upgrades in place when the chunk\n * lands — the same enhancement path a no-JS visitor never leaves.\n *\n * Every client renderer of <FormField> (FormClient, FormSpotlight, FormStage)\n * calls this hook, so the experiences and the classic form always offer\n * identical controls.\n */\n\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { FormField as FormFieldType } from './types'\n\nexport interface FieldControls {\n Date?: React.ComponentType<any>\n File?: React.ComponentType<any>\n}\n\n/** Stable identity for the common case (no date, no file) — no render churn. */\nconst NO_CONTROLS: FieldControls = {}\n\nexport function useFieldControls(fields: FormFieldType[] | undefined): FieldControls {\n const needsDate = useMemo(\n () => (fields ?? []).some((f) => f.field_type === 'date' || f.field_type === 'datetime'),\n [fields],\n )\n const needsFile = useMemo(\n () => (fields ?? []).some((f) => f.field_type === 'file'),\n [fields],\n )\n\n const [controls, setControls] = useState<FieldControls>(NO_CONTROLS)\n // Guards re-import when a conditional field toggles visibility mid-fill.\n const requested = useRef({ date: false, file: false })\n\n useEffect(() => {\n let cancelled = false\n\n if (needsDate && !requested.current.date) {\n requested.current.date = true\n import('./DatePicker')\n .then((m) => {\n if (!cancelled) setControls((prev) => ({ ...prev, Date: m.DatePicker }))\n })\n .catch(() => {\n // Chunk never arrived — the native date input stays, which submits\n // the same value. A failed fetch must not cost the lead.\n requested.current.date = false\n })\n }\n\n if (needsFile && !requested.current.file) {\n requested.current.file = true\n import('./FileField')\n .then((m) => {\n if (!cancelled) setControls((prev) => ({ ...prev, File: m.FileField }))\n })\n .catch(() => {\n requested.current.file = false\n })\n }\n\n return () => {\n cancelled = true\n }\n }, [needsDate, needsFile])\n\n return controls\n}\n","/**\n * Shared planning helpers for the alternate form experiences (stage,\n * spotlight). Pure data — no React, no CSS — so FormClient can import the\n * counts it needs for tracking WITHOUT pulling an experience component (and\n * its stylesheet) into every site's forms bundle. The components themselves\n * are lazy-loaded; this module is the only part that ships eagerly.\n */\n\nimport type {\n FormField as FormFieldType,\n FormStep,\n ManagedFormConfig,\n} from './types'\n// Display-only types are never staged; what counts as empty is shared with\n// validation. Both live in field-rules.ts.\nimport { isDisplayOnly, isEmptyValue } from './field-rules'\n\nexport interface StageQuestion {\n field: FormFieldType\n /** The step (chapter) this question belongs to, when the form is multi-step. */\n step?: FormStep\n}\n\nexport interface StageView {\n fields: FormFieldType[]\n step?: FormStep\n}\n\n/**\n * Flatten a form into its ordered question list. Steps become chapters:\n * multi-step forms iterate steps in step_number order (fields are already\n * sorted by sort_order via normalizeFormConfig); stepless fields trail.\n */\nexport function stageQuestions(config: ManagedFormConfig): StageQuestion[] {\n const input = (config.fields || []).filter(\n (f) => !isDisplayOnly(f),\n )\n if (!config.is_multi_step || !config.steps?.length) {\n return input.map((field) => ({ field }))\n }\n const ordered = [...config.steps].sort((a, b) => a.step_number - b.step_number)\n const out: StageQuestion[] = []\n const claimed = new Set<string>()\n for (const step of ordered) {\n for (const field of input) {\n if (field.step_id === step.id) {\n out.push({ field, step })\n claimed.add(field.id)\n }\n }\n }\n for (const field of input) {\n if (!claimed.has(field.id)) out.push({ field })\n }\n return out\n}\n\nconst WIDTH_FRACTION: Record<string, number> = {\n full: 1,\n half: 1 / 2,\n third: 1 / 3,\n quarter: 1 / 4,\n}\n\n/**\n * Group questions into beats using the classic grid's own row math:\n * consecutive sub-full-width fields fill a row (2 halves, 3 thirds...) and\n * that row is one beat; a full-width field closes any open row and stands\n * alone; a chapter change always starts a fresh beat.\n */\nexport function stageViews(config: ManagedFormConfig): StageView[] {\n const questions = stageQuestions(config)\n const views: StageView[] = []\n let open: StageView | null = null\n let openWidth = 0\n\n const close = () => {\n if (open) views.push(open)\n open = null\n openWidth = 0\n }\n\n for (const q of questions) {\n const fraction = WIDTH_FRACTION[q.field.width] ?? 1\n const chapterChanged = open && open.step?.id !== q.step?.id\n if (fraction >= 1 || chapterChanged) close()\n if (!open) {\n open = { fields: [q.field], step: q.step }\n openWidth = fraction\n if (fraction >= 1) close()\n continue\n }\n if (openWidth + fraction > 1.0001) close()\n if (!open) {\n open = { fields: [q.field], step: q.step }\n openWidth = fraction\n continue\n }\n open.fields.push(q.field)\n openWidth += fraction\n }\n close()\n return views\n}\n\nexport function countStageViews(config: ManagedFormConfig): number {\n return stageViews(config).length\n}\n\nexport function countStageQuestions(config: ManagedFormConfig): number {\n return stageQuestions(config).length\n}\n\n/**\n * Honest, friendly time estimate: \"about 40 seconds\".\n * ~8s per question, rounded to something a human would actually say.\n */\nexport function stageEstimate(questionCount: number): string {\n const seconds = questionCount * 8\n if (seconds < 50) return `about ${Math.max(15, Math.ceil(seconds / 5) * 5)} seconds`\n if (seconds <= 90) return 'about a minute'\n return `about ${Math.round(seconds / 60)} minutes`\n}\n\n/** First name for the greeting — conservative: short, single token, else null. */\nexport function stageFirstName(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const token = value.trim().split(/\\s+/)[0]\n if (!token || token.length > 24) return null\n return token\n}\n\nexport function formatAnswer(value: unknown): string | null {\n if (isEmptyValue(value)) return null\n if (Array.isArray(value)) return value.map(String).join(', ')\n if (typeof value === 'boolean') return value ? 'Yes' : 'No'\n return String(value)\n}\n\n/** Single-select types where picking an answer advances after a lock-in beat. */\nexport const AUTO_ADVANCE_TYPES = ['radio', 'select', 'rating']\n"]}
@@ -2,10 +2,11 @@
2
2
  'use strict';
3
3
 
4
4
  var chunk5XFFUP6D_js = require('./chunk-5XFFUP6D.js');
5
- var chunk6FASIYCV_js = require('./chunk-6FASIYCV.js');
5
+ var chunkTULCHQX2_js = require('./chunk-TULCHQX2.js');
6
+ var chunkKHPJ3CEL_js = require('./chunk-KHPJ3CEL.js');
6
7
  var chunkP4Y75QSU_js = require('./chunk-P4Y75QSU.js');
7
8
  var chunkVJMPIRSD_js = require('./chunk-VJMPIRSD.js');
8
- var chunkNKBPEZCX_js = require('./chunk-NKBPEZCX.js');
9
+ var chunkCZMZEUB6_js = require('./chunk-CZMZEUB6.js');
9
10
  var react = require('react');
10
11
 
11
12
  // src/forms/field-interactions.ts
@@ -197,7 +198,7 @@ function useFormTracking({
197
198
  return null;
198
199
  }
199
200
  try {
200
- const response = await chunkNKBPEZCX_js.sonorFetch(
201
+ const response = await chunkCZMZEUB6_js.sonorFetch(
201
202
  `${apiUrl}/api/public/forms/analytics/start`,
202
203
  {
203
204
  method: "POST",
@@ -238,7 +239,7 @@ function useFormTracking({
238
239
  if (!claimAbandon(session)) return;
239
240
  const now = Date.now();
240
241
  stepTimesRef.current[currentStepRef.current] = Math.floor((now - stepStartTimeRef.current) / 1e3);
241
- chunkNKBPEZCX_js.sonorBeacon(
242
+ chunkCZMZEUB6_js.sonorBeacon(
242
243
  `${apiUrl}/api/public/forms/analytics/abandon`,
243
244
  {
244
245
  analyticsId,
@@ -304,7 +305,7 @@ function useFormTracking({
304
305
  const analyticsId = await resolveAnalyticsId();
305
306
  if (!analyticsId) return;
306
307
  try {
307
- const response = await chunkNKBPEZCX_js.sonorFetch(
308
+ const response = await chunkCZMZEUB6_js.sonorFetch(
308
309
  `${apiUrl}/api/public/forms/analytics/step`,
309
310
  {
310
311
  method: "POST",
@@ -344,7 +345,7 @@ function useFormTracking({
344
345
  const analyticsId = await resolveAnalyticsId();
345
346
  if (!analyticsId) return;
346
347
  try {
347
- const response = await chunkNKBPEZCX_js.sonorFetch(
348
+ const response = await chunkCZMZEUB6_js.sonorFetch(
348
349
  `${apiUrl}/api/public/forms/analytics/complete`,
349
350
  {
350
351
  method: "POST",
@@ -421,6 +422,196 @@ function pruneErrors(errors, fields) {
421
422
  return next;
422
423
  }
423
424
 
425
+ // src/forms/recaptcha.ts
426
+ function getSiteKey(explicitSiteKey) {
427
+ if (typeof window === "undefined") return void 0;
428
+ if (explicitSiteKey) return explicitSiteKey;
429
+ const win = window;
430
+ return win.__SITE_KIT_RECAPTCHA_SITE_KEY__ ?? process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
431
+ }
432
+ var RECAPTCHA_LOAD_TIMEOUT_MS = 1e4;
433
+ function loadScript(siteKey) {
434
+ return new Promise((resolve) => {
435
+ if (typeof window === "undefined") {
436
+ resolve(null);
437
+ return;
438
+ }
439
+ const win = window;
440
+ if (win.grecaptcha?.enterprise) {
441
+ resolve(win.grecaptcha);
442
+ return;
443
+ }
444
+ let settled = false;
445
+ const finish = (value) => {
446
+ if (settled) return;
447
+ settled = true;
448
+ resolve(value);
449
+ };
450
+ const giveUp = setTimeout(() => finish(null), RECAPTCHA_LOAD_TIMEOUT_MS);
451
+ const waitForReady = () => {
452
+ if (settled) return;
453
+ if (win.grecaptcha?.enterprise) {
454
+ clearTimeout(giveUp);
455
+ finish(win.grecaptcha);
456
+ return;
457
+ }
458
+ setTimeout(waitForReady, 50);
459
+ };
460
+ const existing = document.querySelector('script[src*="recaptcha/enterprise"]');
461
+ if (existing) {
462
+ waitForReady();
463
+ return;
464
+ }
465
+ const script = document.createElement("script");
466
+ script.src = `https://www.google.com/recaptcha/enterprise.js?render=${siteKey}`;
467
+ script.async = true;
468
+ script.onload = () => waitForReady();
469
+ script.onerror = () => {
470
+ clearTimeout(giveUp);
471
+ finish(null);
472
+ };
473
+ document.head.appendChild(script);
474
+ });
475
+ }
476
+ async function getRecaptchaToken(explicitSiteKey) {
477
+ const siteKey = getSiteKey(explicitSiteKey);
478
+ if (!siteKey || typeof window === "undefined") return null;
479
+ try {
480
+ const grecaptcha = await loadScript(siteKey);
481
+ if (!grecaptcha?.enterprise?.execute) return null;
482
+ await new Promise((resolve) => grecaptcha.enterprise.ready(resolve));
483
+ const token = await grecaptcha.enterprise.execute(siteKey, { action: "submit" });
484
+ return token || null;
485
+ } catch (err) {
486
+ try {
487
+ await new Promise((resolve) => setTimeout(resolve, 800));
488
+ const win = window;
489
+ const token = await win.grecaptcha.enterprise.execute(siteKey, { action: "submit" });
490
+ return token || null;
491
+ } catch (retryErr) {
492
+ console.warn("[site-kit reCAPTCHA]", retryErr);
493
+ return null;
494
+ }
495
+ }
496
+ }
497
+
498
+ // src/forms/ga4-identity.ts
499
+ function readCookie(jar, name) {
500
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
501
+ const match = jar.match(new RegExp(`(?:^|;\\s*)${escaped}=([^;]*)`));
502
+ return match ? decodeURIComponent(match[1]) : void 0;
503
+ }
504
+ function readGa4Identity() {
505
+ if (typeof document === "undefined") return {};
506
+ const jar = document.cookie || "";
507
+ if (!jar) return {};
508
+ const identity = {};
509
+ const ga = readCookie(jar, "_ga");
510
+ if (ga) {
511
+ const parts = ga.split(".");
512
+ if (parts.length >= 4) {
513
+ const clientId = `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
514
+ if (/^\d+\.\d+$/.test(clientId)) identity.gaClientId = clientId;
515
+ }
516
+ }
517
+ const sessions = {};
518
+ for (const raw of jar.split(";")) {
519
+ const cookie = raw.trim();
520
+ const eq = cookie.indexOf("=");
521
+ if (eq < 0) continue;
522
+ const name = cookie.slice(0, eq);
523
+ const container = /^_ga_([A-Z0-9]+)$/i.exec(name)?.[1];
524
+ if (!container) continue;
525
+ const parts = decodeURIComponent(cookie.slice(eq + 1)).split(".");
526
+ if (parts.length >= 3 && /^\d+$/.test(parts[2])) sessions[container] = parts[2];
527
+ }
528
+ if (Object.keys(sessions).length > 0) identity.gaSessions = sessions;
529
+ return identity;
530
+ }
531
+
532
+ // src/forms/submitForm.ts
533
+ function getApiConfig() {
534
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.sonor.io" : process.env.SONOR_API_URL || process.env.NEXT_PUBLIC_SONOR_API_URL || "https://api.sonor.io";
535
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
536
+ return { apiUrl, apiKey };
537
+ }
538
+ function getUTMParams() {
539
+ if (typeof window === "undefined") return {};
540
+ const params = new URLSearchParams(window.location.search);
541
+ const out = {};
542
+ for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"]) {
543
+ out[key] = params.get(key);
544
+ }
545
+ return out;
546
+ }
547
+ async function submitForm({
548
+ config,
549
+ values,
550
+ honeypotValue,
551
+ formLoadedAt
552
+ }) {
553
+ const { apiUrl, apiKey } = getApiConfig();
554
+ if (!apiKey) {
555
+ throw new Error("API key is required. Set SONOR_API_KEY in your environment.");
556
+ }
557
+ const recaptchaToken = config.recaptcha_enabled ? await getRecaptchaToken(config.recaptcha_site_key) : null;
558
+ if (config.recaptcha_enabled && !recaptchaToken) {
559
+ throw new Error("Verification could not be completed. Please try again.");
560
+ }
561
+ const honeypotFieldName = config.honeypot_field || chunkKHPJ3CEL_js.DEFAULT_HONEYPOT_FIELD;
562
+ const utm = getUTMParams();
563
+ const ga4 = readGa4Identity();
564
+ const payload = {
565
+ formId: config.id,
566
+ form_id: config.id,
567
+ data: {
568
+ ...values,
569
+ ...config.honeypot_enabled ? { [honeypotFieldName]: honeypotValue } : {}
570
+ },
571
+ metadata: {
572
+ pageUrl: typeof window !== "undefined" ? window.location.href : null,
573
+ referrer: typeof document !== "undefined" ? document.referrer || null : null,
574
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : null,
575
+ sessionId: typeof sessionStorage !== "undefined" ? sessionStorage.getItem("_sk_sid") : null,
576
+ // Anonymous visitor id (_sk_vid) — the same id Analytics/Signal stamp on
577
+ // page views, so the Sonor side can bind this lead to its browsing session
578
+ // (page -> lead attribution). Sent on every submission; null pre-hydration.
579
+ visitorId: typeof localStorage !== "undefined" ? localStorage.getItem("_sk_vid") : null,
580
+ recaptchaToken: recaptchaToken || void 0,
581
+ utmSource: utm.utm_source,
582
+ utmMedium: utm.utm_medium,
583
+ utmCampaign: utm.utm_campaign,
584
+ utmTerm: utm.utm_term,
585
+ utmContent: utm.utm_content,
586
+ ...ga4,
587
+ // Sub-site host — preserved on form_submissions for lead attribution
588
+ // across multi-site projects. Resolved by SiteKitClientProviders.
589
+ site: typeof window !== "undefined" ? window.__SITE_KIT_SITE__ || window.location.host : void 0,
590
+ _formLoadedAt: formLoadedAt
591
+ }
592
+ };
593
+ const response = await chunkCZMZEUB6_js.sonorFetch(
594
+ `${apiUrl}/api/public/forms/submit`,
595
+ {
596
+ method: "POST",
597
+ headers: {
598
+ "Content-Type": "application/json",
599
+ Authorization: `Bearer ${apiKey}`
600
+ },
601
+ body: JSON.stringify(payload)
602
+ },
603
+ { retries: 2, timeoutMs: 2e4 }
604
+ );
605
+ const data = await response.json().catch(() => null);
606
+ if (!response.ok) {
607
+ const msg = (data?.message ?? data?.error ?? (response.statusText || `HTTP ${response.status}`)).toString().trim();
608
+ throw new Error(
609
+ msg ? `Failed to submit form: ${msg}` : `Failed to submit form (${response.status})`
610
+ );
611
+ }
612
+ return data;
613
+ }
614
+
424
615
  // src/forms/useForm.ts
425
616
  function useForm(formIdOrSlug, options = {}) {
426
617
  const {
@@ -434,7 +625,7 @@ function useForm(formIdOrSlug, options = {}) {
434
625
  } = options;
435
626
  const projectId = optionsProjectId || (typeof window !== "undefined" ? window.__SITE_KIT_PROJECT_ID__ : void 0);
436
627
  const [form, setForm] = react.useState(
437
- () => initialForm ? chunk6FASIYCV_js.normalizeFormConfig(initialForm) : null
628
+ () => initialForm ? chunkTULCHQX2_js.normalizeFormConfig(initialForm) : null
438
629
  );
439
630
  const [isLoading, setIsLoading] = react.useState(!initialForm);
440
631
  const [fetchError, setFetchError] = react.useState(null);
@@ -490,7 +681,7 @@ function useForm(formIdOrSlug, options = {}) {
490
681
  formIdOrSlug
491
682
  })
492
683
  };
493
- const response = await chunkNKBPEZCX_js.sonorFetch(url, init, { apiKey, retries: 3, timeoutMs: 15e3 });
684
+ const response = await chunkCZMZEUB6_js.sonorFetch(url, init, { apiKey, retries: 3, timeoutMs: 15e3 });
494
685
  if (!response.ok) {
495
686
  throw new Error(`Failed to fetch form: ${response.statusText}`);
496
687
  }
@@ -500,7 +691,7 @@ function useForm(formIdOrSlug, options = {}) {
500
691
  ;
501
692
  window.__SITE_KIT_RECAPTCHA_SITE_KEY__ = data.recaptcha_site_key;
502
693
  }
503
- setForm(chunk6FASIYCV_js.normalizeFormConfig(data));
694
+ setForm(chunkTULCHQX2_js.normalizeFormConfig(data));
504
695
  const nextFields = data.fields || [];
505
696
  setValuesState(
506
697
  (prev) => mergeFormValues({ fields: nextFields, current: prev, initialValues })
@@ -632,7 +823,7 @@ function useForm(formIdOrSlug, options = {}) {
632
823
  setIsSubmitting(true);
633
824
  setSubmitError(null);
634
825
  try {
635
- const data = await chunk6FASIYCV_js.submitForm({
826
+ const data = await submitForm({
636
827
  config: form,
637
828
  values: buildVisibleValues(),
638
829
  honeypotValue: "",
@@ -696,7 +887,8 @@ function useForm(formIdOrSlug, options = {}) {
696
887
  };
697
888
  }
698
889
 
890
+ exports.submitForm = submitForm;
699
891
  exports.useForm = useForm;
700
892
  exports.useFormTracking = useFormTracking;
701
- //# sourceMappingURL=chunk-JPXT5LY2.js.map
702
- //# sourceMappingURL=chunk-JPXT5LY2.js.map
893
+ //# sourceMappingURL=chunk-N2LYSNZT.js.map
894
+ //# sourceMappingURL=chunk-N2LYSNZT.js.map