@sonordev/site-kit 5.4.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +16 -6
- package/README.md +9 -7
- package/agent-manifest.json +7 -7
- package/dist/AnalyticsProvider-KFXAH4GR.mjs +11 -0
- package/dist/{AnalyticsProvider-2XIOMRCN.mjs.map → AnalyticsProvider-KFXAH4GR.mjs.map} +1 -1
- package/dist/AnalyticsProvider-WDDW3IJW.js +32 -0
- package/dist/{AnalyticsProvider-Y2P2BPOI.js.map → AnalyticsProvider-WDDW3IJW.js.map} +1 -1
- package/dist/{BlogViewTracker-6EGEDPP3.js → BlogViewTracker-D4DIEP4Q.js} +6 -6
- package/dist/{BlogViewTracker-6EGEDPP3.js.map → BlogViewTracker-D4DIEP4Q.js.map} +1 -1
- package/dist/{BlogViewTracker-R4YQ76WP.mjs → BlogViewTracker-ZVEGGH3Y.mjs} +5 -5
- package/dist/{BlogViewTracker-R4YQ76WP.mjs.map → BlogViewTracker-ZVEGGH3Y.mjs.map} +1 -1
- package/dist/ChatWidget-2ZYPYIMQ.js +22 -0
- package/dist/{ChatWidget-2OFWPCWF.js.map → ChatWidget-2ZYPYIMQ.js.map} +1 -1
- package/dist/ChatWidget-FYCCLMQE.mjs +13 -0
- package/dist/{ChatWidget-4AOGHYXQ.mjs.map → ChatWidget-FYCCLMQE.mjs.map} +1 -1
- package/dist/EngageWidget-6HUZYOY7.mjs +11 -0
- package/dist/{EngageWidget-SFO2XRNG.mjs.map → EngageWidget-6HUZYOY7.mjs.map} +1 -1
- package/dist/EngageWidget-MYQTBBW6.js +20 -0
- package/dist/{EngageWidget-T4KAAD3G.js.map → EngageWidget-MYQTBBW6.js.map} +1 -1
- package/dist/{FileField-CE2XNBBG.js → FileField-JK2S24XU.js} +6 -6
- package/dist/{FileField-CE2XNBBG.js.map → FileField-JK2S24XU.js.map} +1 -1
- package/dist/{FileField-7DF2SLPF.mjs → FileField-OZZHWGPA.mjs} +5 -5
- package/dist/{FileField-7DF2SLPF.mjs.map → FileField-OZZHWGPA.mjs.map} +1 -1
- package/dist/{FormSpotlight-ZD3WR5BC.js → FormSpotlight-5AXFQU5M.js} +15 -15
- package/dist/{FormSpotlight-ZD3WR5BC.js.map → FormSpotlight-5AXFQU5M.js.map} +1 -1
- package/dist/{FormSpotlight-IRLKSZGV.mjs → FormSpotlight-XV42L72H.mjs} +3 -3
- package/dist/{FormSpotlight-IRLKSZGV.mjs.map → FormSpotlight-XV42L72H.mjs.map} +1 -1
- package/dist/{FormStage-OSPXOUQE.js → FormStage-HATUHNRV.js} +13 -13
- package/dist/{FormStage-OSPXOUQE.js.map → FormStage-HATUHNRV.js.map} +1 -1
- package/dist/{FormStage-2OSRNQJL.mjs → FormStage-RUK675HV.mjs} +3 -3
- package/dist/{FormStage-2OSRNQJL.mjs.map → FormStage-RUK675HV.mjs.map} +1 -1
- package/dist/ManagedForm-JWEXJEXK.js +23 -0
- package/dist/{ManagedForm-JCHGBBQW.js.map → ManagedForm-JWEXJEXK.js.map} +1 -1
- package/dist/ManagedForm-OA3MNGXT.mjs +14 -0
- package/dist/{ManagedForm-5G37EDJT.mjs.map → ManagedForm-OA3MNGXT.mjs.map} +1 -1
- package/dist/{ManagedNewsletterForm-4LVJZROZ.js → ManagedNewsletterForm-AAHH63KT.js} +8 -8
- package/dist/{ManagedNewsletterForm-4LVJZROZ.js.map → ManagedNewsletterForm-AAHH63KT.js.map} +1 -1
- package/dist/{ManagedNewsletterForm-QJSVH23S.mjs → ManagedNewsletterForm-HZKTNWLW.mjs} +7 -7
- package/dist/{ManagedNewsletterForm-QJSVH23S.mjs.map → ManagedNewsletterForm-HZKTNWLW.mjs.map} +1 -1
- package/dist/{SignalCore-L6Z3YTCF.mjs → SignalCore-X3GDGBZD.mjs} +5 -5
- package/dist/{SignalCore-L6Z3YTCF.mjs.map → SignalCore-X3GDGBZD.mjs.map} +1 -1
- package/dist/{SignalCore-NQ4YEOI7.js → SignalCore-YSNZYBKJ.js} +11 -11
- package/dist/{SignalCore-NQ4YEOI7.js.map → SignalCore-YSNZYBKJ.js.map} +1 -1
- package/dist/SitemapSync-2PTDBH5E.mjs +9 -0
- package/dist/{SitemapSync-MYSTLJOM.mjs.map → SitemapSync-2PTDBH5E.mjs.map} +1 -1
- package/dist/SitemapSync-3SFAI546.js +18 -0
- package/dist/{SitemapSync-3LUZWBTB.js.map → SitemapSync-3SFAI546.js.map} +1 -1
- package/dist/analytics/dispatch.d.ts +4 -4
- package/dist/analytics/index.js +15 -15
- package/dist/analytics/index.mjs +6 -6
- package/dist/{api-NTHZ6PAW.mjs → api-D745LGGU.mjs} +3 -3
- package/dist/api-D745LGGU.mjs.map +1 -0
- package/dist/{api-N5OLQXYN.js → api-XR5APCH3.js} +3 -3
- package/dist/api-XR5APCH3.js.map +1 -0
- package/dist/blog/index.js +20 -20
- package/dist/blog/index.mjs +3 -3
- package/dist/blog/server-ui.js +14 -14
- package/dist/blog/server-ui.mjs +2 -2
- package/dist/{chunk-WSUQ76LN.js → chunk-2CMSWJWC.js} +18 -18
- package/dist/{chunk-WSUQ76LN.js.map → chunk-2CMSWJWC.js.map} +1 -1
- package/dist/{chunk-QWLL3B6U.js → chunk-2OU4KBUN.js} +26 -23
- package/dist/chunk-2OU4KBUN.js.map +1 -0
- package/dist/{chunk-A2VLEORI.mjs → chunk-3B6CXGAK.mjs} +3 -3
- package/dist/{chunk-A2VLEORI.mjs.map → chunk-3B6CXGAK.mjs.map} +1 -1
- package/dist/{chunk-HMFFAUCD.js → chunk-3DWIYCQX.js} +2 -2
- package/dist/chunk-3DWIYCQX.js.map +1 -0
- package/dist/{chunk-3EPMDPWI.js → chunk-3SDMJMN2.js} +5 -5
- package/dist/{chunk-3EPMDPWI.js.map → chunk-3SDMJMN2.js.map} +1 -1
- package/dist/chunk-4U5NCKQY.mjs +39 -0
- package/dist/chunk-4U5NCKQY.mjs.map +1 -0
- package/dist/{chunk-I4PDSDVE.mjs → chunk-4XGACM7D.mjs} +3 -3
- package/dist/{chunk-I4PDSDVE.mjs.map → chunk-4XGACM7D.mjs.map} +1 -1
- package/dist/{chunk-HDDV63XJ.mjs → chunk-5TGAUWNO.mjs} +3 -3
- package/dist/{chunk-HDDV63XJ.mjs.map → chunk-5TGAUWNO.mjs.map} +1 -1
- package/dist/{chunk-RO55G3OA.mjs → chunk-64BOVMXG.mjs} +3 -3
- package/dist/{chunk-RO55G3OA.mjs.map → chunk-64BOVMXG.mjs.map} +1 -1
- package/dist/{chunk-JJ26JKBJ.mjs → chunk-7PPNXORV.mjs} +3 -3
- package/dist/{chunk-JJ26JKBJ.mjs.map → chunk-7PPNXORV.mjs.map} +1 -1
- package/dist/{chunk-LHYVFWHL.js → chunk-A3F5GRNJ.js} +8 -8
- package/dist/{chunk-LHYVFWHL.js.map → chunk-A3F5GRNJ.js.map} +1 -1
- package/dist/{chunk-SZHKNGNR.mjs → chunk-AEARU24Q.mjs} +40 -26
- package/dist/chunk-AEARU24Q.mjs.map +1 -0
- package/dist/{chunk-HBMF3M5F.js → chunk-BNOQXDPJ.js} +4 -4
- package/dist/{chunk-HBMF3M5F.js.map → chunk-BNOQXDPJ.js.map} +1 -1
- package/dist/{chunk-5737U6DI.js → chunk-CT5REGAK.js} +15 -15
- package/dist/{chunk-5737U6DI.js.map → chunk-CT5REGAK.js.map} +1 -1
- package/dist/{chunk-HH5KGXTK.mjs → chunk-DRLHQQBH.mjs} +4 -4
- package/dist/{chunk-HH5KGXTK.mjs.map → chunk-DRLHQQBH.mjs.map} +1 -1
- package/dist/{chunk-UOLY4NXK.mjs → chunk-DYULWKDC.mjs} +4 -4
- package/dist/{chunk-UOLY4NXK.mjs.map → chunk-DYULWKDC.mjs.map} +1 -1
- package/dist/chunk-ESP5RMB7.js +42 -0
- package/dist/chunk-ESP5RMB7.js.map +1 -0
- package/dist/{chunk-LF4L3ZP5.js → chunk-FF5N5D3D.js} +8 -8
- package/dist/{chunk-LF4L3ZP5.js.map → chunk-FF5N5D3D.js.map} +1 -1
- package/dist/{chunk-ITFZZ6YT.js → chunk-FYVLQ3OU.js} +3 -3
- package/dist/{chunk-ITFZZ6YT.js.map → chunk-FYVLQ3OU.js.map} +1 -1
- package/dist/{chunk-N6X76QUF.mjs → chunk-G5HUI4BO.mjs} +3 -3
- package/dist/{chunk-N6X76QUF.mjs.map → chunk-G5HUI4BO.mjs.map} +1 -1
- package/dist/chunk-G6WF4G3E.js +43 -0
- package/dist/chunk-G6WF4G3E.js.map +1 -0
- package/dist/{chunk-DQM5GR2P.mjs → chunk-HBI67P7Y.mjs} +4 -4
- package/dist/{chunk-DQM5GR2P.mjs.map → chunk-HBI67P7Y.mjs.map} +1 -1
- package/dist/{chunk-VA36CJRV.js → chunk-HFJQMIFM.js} +4 -4
- package/dist/{chunk-VA36CJRV.js.map → chunk-HFJQMIFM.js.map} +1 -1
- package/dist/{chunk-VWDT7H6L.js → chunk-HX3BRMOX.js} +3 -3
- package/dist/{chunk-VWDT7H6L.js.map → chunk-HX3BRMOX.js.map} +1 -1
- package/dist/{chunk-3TXX5KIN.js → chunk-JNRHTZL4.js} +2 -2
- package/dist/{chunk-3TXX5KIN.js.map → chunk-JNRHTZL4.js.map} +1 -1
- package/dist/{chunk-U53KN4SA.js → chunk-KAUUGRYB.js} +5 -5
- package/dist/{chunk-U53KN4SA.js.map → chunk-KAUUGRYB.js.map} +1 -1
- package/dist/{chunk-L2LJQIM3.js → chunk-KPKQKFT4.js} +11 -11
- package/dist/chunk-KPKQKFT4.js.map +1 -0
- package/dist/{chunk-ZLPXFV5Y.js → chunk-MCJF2KJ6.js} +5 -5
- package/dist/{chunk-ZLPXFV5Y.js.map → chunk-MCJF2KJ6.js.map} +1 -1
- package/dist/{chunk-URVGBWCP.js → chunk-MNYM2XU4.js} +4 -4
- package/dist/{chunk-URVGBWCP.js.map → chunk-MNYM2XU4.js.map} +1 -1
- package/dist/{chunk-ACTLQ2RH.mjs → chunk-MU7IEPDG.mjs} +4 -4
- package/dist/chunk-MU7IEPDG.mjs.map +1 -0
- package/dist/chunk-NSWO43XW.mjs +39 -0
- package/dist/chunk-NSWO43XW.mjs.map +1 -0
- package/dist/{chunk-KMGGAL2X.js → chunk-NZSFOZJV.js} +40 -26
- package/dist/chunk-NZSFOZJV.js.map +1 -0
- package/dist/{chunk-MIFP6QEQ.mjs → chunk-OFSA7LUH.mjs} +4 -4
- package/dist/{chunk-MIFP6QEQ.mjs.map → chunk-OFSA7LUH.mjs.map} +1 -1
- package/dist/{chunk-2KIGFOPM.js → chunk-PIPLBBDH.js} +8 -8
- package/dist/{chunk-2KIGFOPM.js.map → chunk-PIPLBBDH.js.map} +1 -1
- package/dist/{chunk-7PLH3H3W.js → chunk-Q26VZNM2.js} +3 -3
- package/dist/{chunk-7PLH3H3W.js.map → chunk-Q26VZNM2.js.map} +1 -1
- package/dist/{chunk-GQ6CUOLA.js → chunk-QEGUI2Q2.js} +15 -15
- package/dist/{chunk-GQ6CUOLA.js.map → chunk-QEGUI2Q2.js.map} +1 -1
- package/dist/{chunk-LBS4PRC7.js → chunk-QFNSM77X.js} +3 -3
- package/dist/{chunk-LBS4PRC7.js.map → chunk-QFNSM77X.js.map} +1 -1
- package/dist/{chunk-MTANHZKJ.mjs → chunk-R4MX6EOM.mjs} +7 -7
- package/dist/{chunk-MTANHZKJ.mjs.map → chunk-R4MX6EOM.mjs.map} +1 -1
- package/dist/{chunk-LIYSLNEK.mjs → chunk-RMMHWSPP.mjs} +3 -3
- package/dist/{chunk-LIYSLNEK.mjs.map → chunk-RMMHWSPP.mjs.map} +1 -1
- package/dist/{chunk-B4BFQSSS.mjs → chunk-RU54J2PY.mjs} +4 -4
- package/dist/chunk-RU54J2PY.mjs.map +1 -0
- package/dist/{chunk-SJXGIUHP.mjs → chunk-RZUZMUTN.mjs} +9 -8
- package/dist/chunk-RZUZMUTN.mjs.map +1 -0
- package/dist/{chunk-N6AVWMIB.mjs → chunk-S6FEC6UT.mjs} +9 -5
- package/dist/chunk-S6FEC6UT.mjs.map +1 -0
- package/dist/{chunk-3DPXOZAU.mjs → chunk-SHLWA6JY.mjs} +2 -2
- package/dist/chunk-SHLWA6JY.mjs.map +1 -0
- package/dist/{chunk-7IPSQ5JR.js → chunk-SQVYLIIN.js} +11 -7
- package/dist/chunk-SQVYLIIN.js.map +1 -0
- package/dist/chunk-SUZ3ZLB2.js +8 -0
- package/dist/{chunk-UTOHMK52.js.map → chunk-SUZ3ZLB2.js.map} +1 -1
- package/dist/{chunk-7Y2UPFSY.mjs → chunk-TALEV3IH.mjs} +3 -3
- package/dist/{chunk-7Y2UPFSY.mjs.map → chunk-TALEV3IH.mjs.map} +1 -1
- package/dist/{chunk-UOTXSWKZ.mjs → chunk-TP7H6BSK.mjs} +2 -2
- package/dist/{chunk-UOTXSWKZ.mjs.map → chunk-TP7H6BSK.mjs.map} +1 -1
- package/dist/{chunk-XWF7QPIP.mjs → chunk-TX2QQ7IX.mjs} +3 -3
- package/dist/{chunk-XWF7QPIP.mjs.map → chunk-TX2QQ7IX.mjs.map} +1 -1
- package/dist/{chunk-AWFZIXCT.js → chunk-UNPEMSLK.js} +12 -11
- package/dist/chunk-UNPEMSLK.js.map +1 -0
- package/dist/{chunk-NUH245XK.mjs → chunk-UZL2QU2V.mjs} +16 -13
- package/dist/chunk-UZL2QU2V.mjs.map +1 -0
- package/dist/{chunk-G5I3XEQS.mjs → chunk-V6NOM7PJ.mjs} +3 -3
- package/dist/{chunk-G5I3XEQS.mjs.map → chunk-V6NOM7PJ.mjs.map} +1 -1
- package/dist/{chunk-ZY5AVC4P.js → chunk-VAS3LH5W.js} +10 -10
- package/dist/{chunk-ZY5AVC4P.js.map → chunk-VAS3LH5W.js.map} +1 -1
- package/dist/{chunk-KQ3VAOAF.mjs → chunk-VYHRAICF.mjs} +4 -4
- package/dist/{chunk-KQ3VAOAF.mjs.map → chunk-VYHRAICF.mjs.map} +1 -1
- package/dist/{chunk-LUYUFILE.js → chunk-W5NVW23S.js} +4 -4
- package/dist/{chunk-LUYUFILE.js.map → chunk-W5NVW23S.js.map} +1 -1
- package/dist/chunk-WDBCXRTT.mjs +6 -0
- package/dist/{chunk-7U7C6XRI.mjs.map → chunk-WDBCXRTT.mjs.map} +1 -1
- package/dist/{chunk-6CUBSQMD.mjs → chunk-WIZRCI6J.mjs} +3 -3
- package/dist/{chunk-6CUBSQMD.mjs.map → chunk-WIZRCI6J.mjs.map} +1 -1
- package/dist/{chunk-EBMPXFOP.mjs → chunk-X2XFLYZ3.mjs} +3 -3
- package/dist/{chunk-EBMPXFOP.mjs.map → chunk-X2XFLYZ3.mjs.map} +1 -1
- package/dist/{chunk-B36PTS7B.mjs → chunk-XQYC4KMH.mjs} +3 -3
- package/dist/{chunk-B36PTS7B.mjs.map → chunk-XQYC4KMH.mjs.map} +1 -1
- package/dist/{chunk-DPDV5Z4G.js → chunk-YNC5D3FT.js} +12 -12
- package/dist/{chunk-DPDV5Z4G.js.map → chunk-YNC5D3FT.js.map} +1 -1
- package/dist/{chunk-4WQB2ORG.js → chunk-YYAFHIY7.js} +15 -15
- package/dist/chunk-YYAFHIY7.js.map +1 -0
- package/dist/{chunk-OXZYP32B.js → chunk-Z6S4MWSR.js} +7 -7
- package/dist/{chunk-OXZYP32B.js.map → chunk-Z6S4MWSR.js.map} +1 -1
- package/dist/{chunk-ZDFVYSKK.mjs → chunk-ZRQFG3L6.mjs} +5 -5
- package/dist/{chunk-ZDFVYSKK.mjs.map → chunk-ZRQFG3L6.mjs.map} +1 -1
- package/dist/cli/agent/checks.d.ts.map +1 -1
- package/dist/cli/agent/ssr-bailout.d.ts +27 -0
- package/dist/cli/agent/ssr-bailout.d.ts.map +1 -0
- package/dist/cli/agent/ssr-bailout.test.d.ts +2 -0
- package/dist/cli/agent/ssr-bailout.test.d.ts.map +1 -0
- package/dist/cli/codemod/transforms.d.ts.map +1 -1
- package/dist/cli/index.js +38 -51
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +36 -49
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/migrator/index.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +20 -7
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +5 -4
- package/dist/client/index.mjs.map +1 -1
- package/dist/cms/index.js.map +1 -1
- package/dist/cms/index.mjs.map +1 -1
- package/dist/cms/sanity-image.d.ts +1 -1
- package/dist/commerce/index.js +58 -58
- package/dist/commerce/index.mjs +7 -7
- package/dist/engage/ChatWidget.d.ts.map +1 -1
- package/dist/engage/EngageWidget.d.ts +3 -4
- package/dist/engage/EngageWidget.d.ts.map +1 -1
- package/dist/engage/chat-launcher.test.d.ts +2 -0
- package/dist/engage/chat-launcher.test.d.ts.map +1 -0
- package/dist/engage/index.js +12 -10
- package/dist/engage/index.js.map +1 -1
- package/dist/engage/index.mjs +9 -7
- package/dist/engage/index.mjs.map +1 -1
- package/dist/engage/launcher-placement.d.ts +56 -0
- package/dist/engage/launcher-placement.d.ts.map +1 -0
- package/dist/engage/launcher-placement.test.d.ts +2 -0
- package/dist/engage/launcher-placement.test.d.ts.map +1 -0
- package/dist/engage/types.d.ts +41 -7
- package/dist/engage/types.d.ts.map +1 -1
- package/dist/fleet/index.js +8 -8
- package/dist/fleet/index.mjs +4 -4
- package/dist/forms/index.js +16 -16
- package/dist/forms/index.mjs +9 -9
- package/dist/forms/server.js +10 -10
- package/dist/forms/server.mjs +8 -8
- package/dist/forms/static.js +7 -7
- package/dist/forms/static.mjs +6 -6
- package/dist/images/ManagedImage.d.ts.map +1 -1
- package/dist/images/index.js +11 -11
- package/dist/images/index.mjs +4 -4
- package/dist/index.js +65 -65
- package/dist/index.mjs +11 -11
- package/dist/layout/SiteKitClientProviders.d.ts.map +1 -1
- package/dist/layout/client.d.ts.map +1 -1
- package/dist/layout/client.js +8 -8
- package/dist/layout/client.mjs +7 -7
- package/dist/layout/index.js +10 -10
- package/dist/layout/index.mjs +8 -8
- package/dist/layout/types.d.ts +7 -3
- package/dist/layout/types.d.ts.map +1 -1
- package/dist/llms/generateLLMsTxt.d.ts.map +1 -1
- package/dist/llms/index.js +12 -12
- package/dist/llms/index.mjs +2 -2
- package/dist/llms/types.d.ts +7 -1
- package/dist/llms/types.d.ts.map +1 -1
- package/dist/maps/index.js +5 -5
- package/dist/maps/index.mjs +3 -3
- package/dist/mcp/WebMcpTools.d.ts +5 -2
- package/dist/mcp/WebMcpTools.d.ts.map +1 -1
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/client.mjs.map +1 -1
- package/dist/search/index.js +4 -4
- package/dist/search/index.mjs +3 -3
- package/dist/seo/LocationPageContent.d.ts +6 -5
- package/dist/seo/LocationPageContent.d.ts.map +1 -1
- package/dist/seo/api.d.ts +4 -5
- package/dist/seo/api.d.ts.map +1 -1
- package/dist/seo/client.js +5 -5
- package/dist/seo/client.mjs +4 -4
- package/dist/seo/index.js +42 -42
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/index.mjs +8 -8
- package/dist/seo/index.mjs.map +1 -1
- package/dist/seo/routing.d.ts +9 -23
- package/dist/seo/routing.d.ts.map +1 -1
- package/dist/seo/server-api.d.ts +11 -5
- package/dist/seo/server-api.d.ts.map +1 -1
- package/dist/seo/server.js +18 -18
- package/dist/seo/server.mjs +2 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/dist/{server-api-HW4PL2L2.mjs → server-api-RG3ZL3DV.mjs} +3 -3
- package/dist/{server-api-HW4PL2L2.mjs.map → server-api-RG3ZL3DV.mjs.map} +1 -1
- package/dist/{server-api-DB2SIUP4.js → server-api-RYJQAUGO.js} +21 -21
- package/dist/{server-api-DB2SIUP4.js.map → server-api-RYJQAUGO.js.map} +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/visual-viewport-gap.d.ts +52 -0
- package/dist/shared/visual-viewport-gap.d.ts.map +1 -0
- package/dist/shared/visual-viewport-gap.test.d.ts +2 -0
- package/dist/shared/visual-viewport-gap.test.d.ts.map +1 -0
- package/dist/signal/index.js +11 -11
- package/dist/signal/index.mjs +2 -2
- package/dist/sitemap/index.js +2 -2
- package/dist/sitemap/index.mjs +1 -1
- package/dist/sync/index.js +17 -17
- package/dist/sync/index.mjs +4 -4
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/site-kit/SKILL.md +8 -3
- package/dist/AnalyticsProvider-2XIOMRCN.mjs +0 -11
- package/dist/AnalyticsProvider-Y2P2BPOI.js +0 -32
- package/dist/ChatWidget-2OFWPCWF.js +0 -20
- package/dist/ChatWidget-4AOGHYXQ.mjs +0 -11
- package/dist/EngageWidget-SFO2XRNG.mjs +0 -9
- package/dist/EngageWidget-T4KAAD3G.js +0 -18
- package/dist/ManagedForm-5G37EDJT.mjs +0 -14
- package/dist/ManagedForm-JCHGBBQW.js +0 -23
- package/dist/SitemapSync-3LUZWBTB.js +0 -18
- package/dist/SitemapSync-MYSTLJOM.mjs +0 -9
- package/dist/api-N5OLQXYN.js.map +0 -1
- package/dist/api-NTHZ6PAW.mjs.map +0 -1
- package/dist/chunk-3DPXOZAU.mjs.map +0 -1
- package/dist/chunk-4WQB2ORG.js.map +0 -1
- package/dist/chunk-7IPSQ5JR.js.map +0 -1
- package/dist/chunk-7U7C6XRI.mjs +0 -6
- package/dist/chunk-ACTLQ2RH.mjs.map +0 -1
- package/dist/chunk-AWFZIXCT.js.map +0 -1
- package/dist/chunk-B4BFQSSS.mjs.map +0 -1
- package/dist/chunk-HMFFAUCD.js.map +0 -1
- package/dist/chunk-KMGGAL2X.js.map +0 -1
- package/dist/chunk-L2LJQIM3.js.map +0 -1
- package/dist/chunk-N6AVWMIB.mjs.map +0 -1
- package/dist/chunk-NUH245XK.mjs.map +0 -1
- package/dist/chunk-QWLL3B6U.js.map +0 -1
- package/dist/chunk-SJXGIUHP.mjs.map +0 -1
- package/dist/chunk-SZHKNGNR.mjs.map +0 -1
- package/dist/chunk-UTOHMK52.js +0 -8
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkW5NVW23S_js = require('./chunk-W5NVW23S.js');
|
|
4
4
|
var chunkIPV2TUAT_js = require('./chunk-IPV2TUAT.js');
|
|
5
|
-
var
|
|
5
|
+
var chunkBNOQXDPJ_js = require('./chunk-BNOQXDPJ.js');
|
|
6
6
|
var chunkKHPJ3CEL_js = require('./chunk-KHPJ3CEL.js');
|
|
7
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ function visibleStaticFields(config) {
|
|
|
14
14
|
}
|
|
15
15
|
function StaticForm({ config: raw, className, nativeReturnTo }) {
|
|
16
16
|
if (!raw) return null;
|
|
17
|
-
const config =
|
|
17
|
+
const config = chunkBNOQXDPJ_js.normalizeFormConfig(raw);
|
|
18
18
|
const fields = visibleStaticFields(config);
|
|
19
19
|
const honeypotFieldName = config.honeypot_field || chunkKHPJ3CEL_js.DEFAULT_HONEYPOT_FIELD;
|
|
20
20
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -23,7 +23,7 @@ function StaticForm({ config: raw, className, nativeReturnTo }) {
|
|
|
23
23
|
"data-sk-state": "idle",
|
|
24
24
|
"data-sk-mount": "shell",
|
|
25
25
|
className: `sk-form ${className || ""}`,
|
|
26
|
-
...
|
|
26
|
+
...chunkW5NVW23S_js.nativeFormProps(config),
|
|
27
27
|
children: [
|
|
28
28
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sk-form__step", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sk-form__fields", children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
29
29
|
"div",
|
|
@@ -34,13 +34,13 @@ function StaticForm({ config: raw, className, nativeReturnTo }) {
|
|
|
34
34
|
field.id || field.slug
|
|
35
35
|
)) }) }),
|
|
36
36
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sk-form__actions", style: { justifyContent: "flex-end" }, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "sk-form__btn sk-form__btn--submit", children: config.submit_button_text || "Submit" }) }),
|
|
37
|
-
config.honeypot_enabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
38
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
37
|
+
config.honeypot_enabled && /* @__PURE__ */ jsxRuntime.jsx(chunkW5NVW23S_js.HoneypotField, { name: honeypotFieldName }),
|
|
38
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkW5NVW23S_js.NativeFallbackFields, { config, returnTo: nativeReturnTo })
|
|
39
39
|
]
|
|
40
40
|
}
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
exports.StaticForm = StaticForm;
|
|
45
|
-
//# sourceMappingURL=chunk-
|
|
46
|
-
//# sourceMappingURL=chunk-
|
|
45
|
+
//# sourceMappingURL=chunk-FF5N5D3D.js.map
|
|
46
|
+
//# sourceMappingURL=chunk-FF5N5D3D.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/forms/StaticForm.tsx"],"names":["normalizeFormConfig","DEFAULT_HONEYPOT_FIELD","jsxs","nativeFormProps","jsx","FormField","HoneypotField","NativeFallbackFields"],"mappings":";;;;;;;;AA6DA,SAAS,oBAAoB,MAAA,EAA2B;AACtD,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,MAAA,IAAU,EAAC;AAC9B,EAAA,IAAI,CAAC,MAAA,CAAO,aAAA,EAAe,OAAO,GAAA;AAGlC,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,KAAA,GAAQ,CAAC,CAAA,EAAG,EAAA;AACvC,EAAA,OAAO,GAAA,CAAI,OAAO,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA,KAAY,WAAW,CAAA;AAClE;AAEO,SAAS,WAAW,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAA,EAAW,gBAAe,EAAoB;AACtF,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,MAAM,MAAA,GAASA,qCAAoB,GAAU,CAAA;AAC7C,EAAA,MAAM,MAAA,GAAS,oBAAoB,MAAM,CAAA;AACzC,EAAA,MAAM,iBAAA,GAAoB,OAAO,cAAA,IAAkBC,uCAAA;AAEnD,EAAA,uBACEC,eAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MAGC,eAAA,EAAc,MAAA;AAAA,MACd,eAAA,EAAc,OAAA;AAAA,MACd,SAAA,EAAW,CAAA,QAAA,EAAW,SAAA,IAAa,EAAE,CAAA,CAAA;AAAA,MACpC,GAAGC,iCAAgB,MAAM,CAAA;AAAA,MAE1B,QAAA,EAAA;AAAA,wBAAAC,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eAAA,EACb,QAAA,kBAAAA,cAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iBAAA,EACZ,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,qBACXA,cAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YAEC,SAAA,EAAW,CAAA,+CAAA,EAAkD,KAAA,CAAM,KAAA,IAAS,MAAM,CAAA,CAAA;AAAA,YAGlF,yCAACC,0BAAA,EAAA,EAAU,KAAA,EAAc,KAAA,EAAO,KAAA,CAAM,iBAAiB,EAAA,EAAI;AAAA,WAAA;AAAA,UAJtD,KAAA,CAAM,MAAM,KAAA,CAAM;AAAA,SAM1B,GACH,CAAA,EACF,CAAA;AAAA,uCAEC,KAAA,EAAA,EAAI,SAAA,EAAU,oBAAmB,KAAA,EAAO,EAAE,gBAAgB,UAAA,EAAW,EACpE,QAAA,kBAAAD,cAAA,CAAC,QAAA,EAAA,EAAO,MAAK,QAAA,EAAS,SAAA,EAAU,qCAC7B,QAAA,EAAA,MAAA,CAAO,kBAAA,IAAsB,UAChC,CAAA,EACF,CAAA;AAAA,QAIC,MAAA,CAAO,gBAAA,oBAAoBA,cAAA,CAACE,8BAAA,EAAA,EAAc,MAAM,iBAAA,EAAmB,CAAA;AAAA,wBAGpEF,cAAA,CAACG,qCAAA,EAAA,EAAqB,MAAA,EAAgB,QAAA,EAAU,cAAA,EAAgB;AAAA;AAAA;AAAA,GAClE;AAEJ","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/forms/StaticForm.tsx"],"names":["normalizeFormConfig","DEFAULT_HONEYPOT_FIELD","jsxs","nativeFormProps","jsx","FormField","HoneypotField","NativeFallbackFields"],"mappings":";;;;;;;;AA6DA,SAAS,oBAAoB,MAAA,EAA2B;AACtD,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,MAAA,IAAU,EAAC;AAC9B,EAAA,IAAI,CAAC,MAAA,CAAO,aAAA,EAAe,OAAO,GAAA;AAGlC,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,KAAA,GAAQ,CAAC,CAAA,EAAG,EAAA;AACvC,EAAA,OAAO,GAAA,CAAI,OAAO,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA,KAAY,WAAW,CAAA;AAClE;AAEO,SAAS,WAAW,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAA,EAAW,gBAAe,EAAoB;AACtF,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,MAAM,MAAA,GAASA,qCAAoB,GAAU,CAAA;AAC7C,EAAA,MAAM,MAAA,GAAS,oBAAoB,MAAM,CAAA;AACzC,EAAA,MAAM,iBAAA,GAAoB,OAAO,cAAA,IAAkBC,uCAAA;AAEnD,EAAA,uBACEC,eAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MAGC,eAAA,EAAc,MAAA;AAAA,MACd,eAAA,EAAc,OAAA;AAAA,MACd,SAAA,EAAW,CAAA,QAAA,EAAW,SAAA,IAAa,EAAE,CAAA,CAAA;AAAA,MACpC,GAAGC,iCAAgB,MAAM,CAAA;AAAA,MAE1B,QAAA,EAAA;AAAA,wBAAAC,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eAAA,EACb,QAAA,kBAAAA,cAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iBAAA,EACZ,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,qBACXA,cAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YAEC,SAAA,EAAW,CAAA,+CAAA,EAAkD,KAAA,CAAM,KAAA,IAAS,MAAM,CAAA,CAAA;AAAA,YAGlF,yCAACC,0BAAA,EAAA,EAAU,KAAA,EAAc,KAAA,EAAO,KAAA,CAAM,iBAAiB,EAAA,EAAI;AAAA,WAAA;AAAA,UAJtD,KAAA,CAAM,MAAM,KAAA,CAAM;AAAA,SAM1B,GACH,CAAA,EACF,CAAA;AAAA,uCAEC,KAAA,EAAA,EAAI,SAAA,EAAU,oBAAmB,KAAA,EAAO,EAAE,gBAAgB,UAAA,EAAW,EACpE,QAAA,kBAAAD,cAAA,CAAC,QAAA,EAAA,EAAO,MAAK,QAAA,EAAS,SAAA,EAAU,qCAC7B,QAAA,EAAA,MAAA,CAAO,kBAAA,IAAsB,UAChC,CAAA,EACF,CAAA;AAAA,QAIC,MAAA,CAAO,gBAAA,oBAAoBA,cAAA,CAACE,8BAAA,EAAA,EAAc,MAAM,iBAAA,EAAmB,CAAA;AAAA,wBAGpEF,cAAA,CAACG,qCAAA,EAAA,EAAqB,MAAA,EAAgB,QAAA,EAAU,cAAA,EAAgB;AAAA;AAAA;AAAA,GAClE;AAEJ","file":"chunk-FF5N5D3D.js","sourcesContent":["/**\n * StaticForm — a complete, submittable managed form with ZERO client JavaScript.\n *\n * No `'use client'`. This renders in a Server Component, and what it emits is\n * a real `<form>`: every field, the honeypot, the signed native token, and a\n * submit button. With JavaScript disabled, off, or still downloading, the\n * browser performs an ordinary form-encoded POST to\n * `/api/public/forms/submit-native`, sonor-api runs the honeypot +\n * render-timestamp + quarantine stack, and the lead lands. 4.0.1 built that\n * endpoint; this is what finally puts it under every form by default.\n *\n * ── Why this exists ─────────────────────────────────────────────────────\n *\n * ManagedForm was a client component for historical reasons that no longer\n * hold: it used to fetch its own config (getFormConfig removed that) and it\n * could only submit via JSON (the native endpoint removed that). Nobody moved\n * the boundary afterward, so every site that rendered a form paid for the\n * whole forms runtime — validation, spotlight, celebration, momentum — as an\n * INITIAL script, on the LCP-critical path.\n *\n * Measured on upforge.io's homepage: 48 KB of form code in the initial chunk\n * set cost 6 Lighthouse points (88 -> 82, LCP 3.8s -> 4.7s). Deferring the\n * client component recovered the score but deleted the form from the HTML,\n * taking the no-JS floor and crawlable fields with it. Neither half was\n * acceptable; a server-rendered form is what makes both true at once.\n *\n * Pair it with <FormEnhancer> to upgrade this markup at idle. Alone, it is\n * still a working form — that is the point. Never make this file `'use\n * client'`, and never import a hook into it.\n */\n\nimport React from 'react'\nimport { DEFAULT_HONEYPOT_FIELD } from './contract'\nimport { FormField } from './FormField'\nimport { HoneypotField } from './HoneypotField'\nimport { nativeFormProps, NativeFallbackFields } from './native-fallback'\nimport { normalizeFormConfig } from './normalize-config'\nimport type { ManagedFormConfig } from './types'\n\nexport interface StaticFormProps {\n /**\n * RAW /api/public/forms/config payload from `getFormConfig`, or an\n * already-normalized config. Normalized here through the SAME function the\n * client fetch uses, so the shell and the enhancement cannot disagree about\n * field order, step count, or success copy.\n */\n config: Record<string, unknown> | ManagedFormConfig | null | undefined\n className?: string\n /**\n * Absolute URL the no-JS 303 should return to (usually the page hosting the\n * form). Without it the API falls back to the Referer origin — right site,\n * wrong page.\n */\n nativeReturnTo?: string\n}\n\n/**\n * Fields the static shell can render meaningfully. Conditional logic is a\n * runtime concern (it depends on values the visitor has not entered yet), so\n * a conditional field renders here and the enhancement takes over hiding it.\n */\nfunction visibleStaticFields(config: ManagedFormConfig) {\n const all = config.fields || []\n if (!config.is_multi_step) return all\n // Multi-step is JS-only navigation and is excluded from native POST, so the\n // shell shows step one as a preview of what the form asks for.\n const firstStepId = config.steps?.[0]?.id\n return all.filter((f) => !f.step_id || f.step_id === firstStepId)\n}\n\nexport function StaticForm({ config: raw, className, nativeReturnTo }: StaticFormProps) {\n if (!raw) return null\n const config = normalizeFormConfig(raw as any)\n const fields = visibleStaticFields(config)\n const honeypotFieldName = config.honeypot_field || DEFAULT_HONEYPOT_FIELD\n\n return (\n <form\n // Matches FormClient's classic branch attribute-for-attribute so the\n // enhancement swap is invisible. Change one, change the other.\n data-sk-state=\"idle\"\n data-sk-mount=\"shell\"\n className={`sk-form ${className || ''}`}\n {...nativeFormProps(config)}\n >\n <div className=\"sk-form__step\">\n <div className=\"sk-form__fields\">\n {fields.map((field) => (\n <div\n key={field.id || field.slug}\n className={`sk-form__field-wrapper sk-form__field-wrapper--${field.width || 'full'}`}\n >\n {/* No onChange — FormField renders native uncontrolled controls. */}\n <FormField field={field} value={field.default_value ?? ''} />\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"sk-form__actions\" style={{ justifyContent: 'flex-end' }}>\n <button type=\"submit\" className=\"sk-form__btn sk-form__btn--submit\">\n {config.submit_button_text || 'Submit'}\n </button>\n </div>\n\n {/* Same trap the hydrated form uses; no state needed because the value\n is read and stripped server-side. */}\n {config.honeypot_enabled && <HoneypotField name={honeypotFieldName} />}\n\n {/* _sk_token auth + _sk_return destination for the native POST. */}\n <NativeFallbackFields config={config} returnTo={nativeReturnTo} />\n </form>\n )\n}\n"]}
|
|
@@ -10,7 +10,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
10
10
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
11
11
|
|
|
12
12
|
var InteractiveForm = React.lazy(
|
|
13
|
-
() => import('./ManagedForm-
|
|
13
|
+
() => import('./ManagedForm-JWEXJEXK.js').then((m) => ({ default: m.ManagedForm }))
|
|
14
14
|
);
|
|
15
15
|
function FormEnhancer({
|
|
16
16
|
formId,
|
|
@@ -106,5 +106,5 @@ function EnterStyles() {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
exports.FormEnhancer = FormEnhancer;
|
|
109
|
-
//# sourceMappingURL=chunk-
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
109
|
+
//# sourceMappingURL=chunk-FYVLQ3OU.js.map
|
|
110
|
+
//# sourceMappingURL=chunk-FYVLQ3OU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/forms/FormEnhancer.tsx"],"names":["lazy","useDeferredActivation","useState","jsxs","jsx","Suspense","React","useRef","useEffect"],"mappings":"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","file":"chunk-ITFZZ6YT.js","sourcesContent":["'use client'\n\n/**\n * FormEnhancer — the only client code a managed form needs up front.\n *\n * Renders the server-rendered shell (<StaticForm>, passed as `children`) until\n * an idle/first-interaction gate fires, then lazy-imports the interactive form\n * and swaps it in. Everything expensive — validation, conditional logic,\n * spotlight/stage, celebration, momentum — lives behind that dynamic import,\n * so a page ships this boundary and nothing else in its initial chunk set.\n *\n * The shell is a REAL form the whole time (native action + signed token), so\n * the progression is:\n *\n * no JS at all -> shell, submits natively. Works.\n * JS, pre-idle -> shell, submits natively. Works.\n * JS, post-idle -> enhanced form, submits over JSON. Works, with delight.\n * chunk 404s -> shell stays. Works. (Never left blank.)\n *\n * That last line is the invariant to protect: the shell is only removed once\n * the enhancement has actually rendered. Do not \"hide the shell then load\" —\n * a CDN hiccup would leave a visitor staring at an empty box where the form\n * used to be.\n *\n * ── Entrance animation ──────────────────────────────────────────────────\n *\n * Above-the-fold forms upgrade while the visitor is looking at them, so the\n * swap must not pop. The wrapper carries `data-sk-form-mount=\"shell\"` and\n * flips to `\"enhanced\"`, and the enhanced form gets `data-sk-enter` for one\n * frame, so a site can style the transition entirely in CSS:\n *\n * [data-sk-form-mount] { transition: opacity .35s ease }\n * [data-sk-form-mount=\"shell\"] { opacity: .6 }\n *\n * Defaults ship as CSS variables rather than hard-coded values:\n *\n * --sk-form-enter-duration (default 320ms)\n * --sk-form-enter-easing (default cubic-bezier(.22,1,.36,1))\n * --sk-form-enter-distance (default 6px)\n *\n * `enter=\"none\"` opts out entirely; `enter=\"custom\"` emits the data\n * attributes but no site-kit styles, leaving the animation to the site.\n * Every default is collapsed by prefers-reduced-motion.\n */\n\nimport React, { lazy, Suspense, useEffect, useRef, useState } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport type { FormExperience, FormSubmission } from './types'\n\n/** The heavy half. Nothing else in this module may import it statically. */\nconst InteractiveForm = lazy(() =>\n import('./ManagedForm').then((m) => ({ default: m.ManagedForm })),\n)\n\nexport type FormEnterAnimation = 'fade' | 'none' | 'custom'\n\nexport interface FormEnhancerProps {\n formId: string\n /** Server-fetched raw config — hands the enhancement its fields instantly. */\n initialConfig?: Record<string, unknown> | null\n className?: string\n nativeReturnTo?: string\n experience?: FormExperience\n /** Pre-fill field values by slug. See ManagedForm.defaultValues. */\n defaultValues?: Record<string, unknown>\n onSuccess?: (submission: FormSubmission) => void\n onError?: (error: Error) => void\n /**\n * How the enhanced form arrives. 'fade' (default) crossfades and lifts it\n * into place; 'none' swaps instantly; 'custom' emits the data attributes\n * and no styles so the site owns the animation.\n */\n enter?: FormEnterAnimation\n /**\n * Set false to enhance immediately instead of waiting for idle. Only worth\n * it for a form that is the entire point of the page AND above the fold.\n */\n defer?: boolean\n /** The server-rendered <StaticForm>. Shown until the enhancement mounts. */\n children: React.ReactNode\n}\n\nexport function FormEnhancer({\n formId,\n initialConfig,\n className,\n nativeReturnTo,\n experience,\n defaultValues,\n onSuccess,\n onError,\n enter = 'fade',\n defer = true,\n children,\n}: FormEnhancerProps) {\n const ready = useDeferredActivation(defer)\n // Flipped only after the enhancement has actually committed to the DOM, so\n // the shell never disappears ahead of its replacement.\n const [enhanced, setEnhanced] = useState(false)\n\n return (\n <div\n data-sk-form-mount={enhanced ? 'enhanced' : 'shell'}\n data-sk-part=\"form-mount\"\n style={enter === 'fade' ? FADE_HOST : undefined}\n >\n {enter === 'fade' && <EnterStyles />}\n {ready ? (\n <Suspense fallback={children}>\n <Enhanced onMount={() => setEnhanced(true)} fallback={children}>\n <InteractiveForm\n formId={formId}\n initialConfig={initialConfig}\n className={className}\n nativeReturnTo={nativeReturnTo}\n experience={experience}\n defaultValues={defaultValues}\n onSuccess={onSuccess}\n onError={onError}\n />\n </Enhanced>\n </Suspense>\n ) : (\n children\n )}\n </div>\n )\n}\n\n/**\n * Reports the moment the enhancement is on screen, and keeps the shell\n * rendered if it throws. An error boundary is the difference between \"the\n * fancy version failed\" and \"there is no form on this page\".\n */\nclass Enhanced extends React.Component<\n { onMount: () => void; fallback: React.ReactNode; children: React.ReactNode },\n { failed: boolean }\n> {\n state = { failed: false }\n\n static getDerivedStateFromError() {\n return { failed: true }\n }\n\n componentDidCatch(err: unknown) {\n console.error('[Forms] Enhancement failed; keeping the native form:', err)\n }\n\n componentDidMount() {\n if (!this.state.failed) this.props.onMount()\n }\n\n render() {\n if (this.state.failed) return this.props.fallback\n return <FrameTag>{this.props.children}</FrameTag>\n }\n}\n\n/** Adds data-sk-enter for one frame so a CSS transition has something to run. */\nfunction FrameTag({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n const el = ref.current\n if (!el) return\n el.setAttribute('data-sk-enter', '')\n const id = requestAnimationFrame(() => el.removeAttribute('data-sk-enter'))\n return () => cancelAnimationFrame(id)\n }, [])\n return (\n <div ref={ref} style={{ display: 'contents' }}>\n {children}\n </div>\n )\n}\n\nconst FADE_HOST: React.CSSProperties = {\n transition:\n 'opacity var(--sk-form-enter-duration, 320ms) var(--sk-form-enter-easing, cubic-bezier(.22,1,.36,1))',\n}\n\n/**\n * Scoped to [data-sk-part=\"form-mount\"] so it can never touch a site's own\n * markup. Emitted once per form; identical text dedupes in every browser's\n * style cache and gzips to nothing.\n */\nfunction EnterStyles() {\n return (\n <style\n // biome-ignore lint/security/noDangerouslySetInnerHtml: static string, no interpolation\n dangerouslySetInnerHTML={{\n __html: `\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"shell\"]{opacity:.55}\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"enhanced\"]{opacity:1}\n[data-sk-part=\"form-mount\"] [data-sk-enter]{opacity:0;transform:translateY(var(--sk-form-enter-distance,6px))}\n[data-sk-part=\"form-mount\"] [data-sk-enter] *{transition:none!important}\n@media (prefers-reduced-motion: reduce){\n[data-sk-part=\"form-mount\"]{transition:none!important}\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"shell\"]{opacity:1}\n[data-sk-part=\"form-mount\"] [data-sk-enter]{opacity:1;transform:none}\n}`,\n }}\n />\n )\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/forms/FormEnhancer.tsx"],"names":["lazy","useDeferredActivation","useState","jsxs","jsx","Suspense","React","useRef","useEffect"],"mappings":"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","file":"chunk-FYVLQ3OU.js","sourcesContent":["'use client'\n\n/**\n * FormEnhancer — the only client code a managed form needs up front.\n *\n * Renders the server-rendered shell (<StaticForm>, passed as `children`) until\n * an idle/first-interaction gate fires, then lazy-imports the interactive form\n * and swaps it in. Everything expensive — validation, conditional logic,\n * spotlight/stage, celebration, momentum — lives behind that dynamic import,\n * so a page ships this boundary and nothing else in its initial chunk set.\n *\n * The shell is a REAL form the whole time (native action + signed token), so\n * the progression is:\n *\n * no JS at all -> shell, submits natively. Works.\n * JS, pre-idle -> shell, submits natively. Works.\n * JS, post-idle -> enhanced form, submits over JSON. Works, with delight.\n * chunk 404s -> shell stays. Works. (Never left blank.)\n *\n * That last line is the invariant to protect: the shell is only removed once\n * the enhancement has actually rendered. Do not \"hide the shell then load\" —\n * a CDN hiccup would leave a visitor staring at an empty box where the form\n * used to be.\n *\n * ── Entrance animation ──────────────────────────────────────────────────\n *\n * Above-the-fold forms upgrade while the visitor is looking at them, so the\n * swap must not pop. The wrapper carries `data-sk-form-mount=\"shell\"` and\n * flips to `\"enhanced\"`, and the enhanced form gets `data-sk-enter` for one\n * frame, so a site can style the transition entirely in CSS:\n *\n * [data-sk-form-mount] { transition: opacity .35s ease }\n * [data-sk-form-mount=\"shell\"] { opacity: .6 }\n *\n * Defaults ship as CSS variables rather than hard-coded values:\n *\n * --sk-form-enter-duration (default 320ms)\n * --sk-form-enter-easing (default cubic-bezier(.22,1,.36,1))\n * --sk-form-enter-distance (default 6px)\n *\n * `enter=\"none\"` opts out entirely; `enter=\"custom\"` emits the data\n * attributes but no site-kit styles, leaving the animation to the site.\n * Every default is collapsed by prefers-reduced-motion.\n */\n\nimport React, { lazy, Suspense, useEffect, useRef, useState } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport type { FormExperience, FormSubmission } from './types'\n\n/** The heavy half. Nothing else in this module may import it statically. */\nconst InteractiveForm = lazy(() =>\n import('./ManagedForm').then((m) => ({ default: m.ManagedForm })),\n)\n\nexport type FormEnterAnimation = 'fade' | 'none' | 'custom'\n\nexport interface FormEnhancerProps {\n formId: string\n /** Server-fetched raw config — hands the enhancement its fields instantly. */\n initialConfig?: Record<string, unknown> | null\n className?: string\n nativeReturnTo?: string\n experience?: FormExperience\n /** Pre-fill field values by slug. See ManagedForm.defaultValues. */\n defaultValues?: Record<string, unknown>\n onSuccess?: (submission: FormSubmission) => void\n onError?: (error: Error) => void\n /**\n * How the enhanced form arrives. 'fade' (default) crossfades and lifts it\n * into place; 'none' swaps instantly; 'custom' emits the data attributes\n * and no styles so the site owns the animation.\n */\n enter?: FormEnterAnimation\n /**\n * Set false to enhance immediately instead of waiting for idle. Only worth\n * it for a form that is the entire point of the page AND above the fold.\n */\n defer?: boolean\n /** The server-rendered <StaticForm>. Shown until the enhancement mounts. */\n children: React.ReactNode\n}\n\nexport function FormEnhancer({\n formId,\n initialConfig,\n className,\n nativeReturnTo,\n experience,\n defaultValues,\n onSuccess,\n onError,\n enter = 'fade',\n defer = true,\n children,\n}: FormEnhancerProps) {\n const ready = useDeferredActivation(defer)\n // Flipped only after the enhancement has actually committed to the DOM, so\n // the shell never disappears ahead of its replacement.\n const [enhanced, setEnhanced] = useState(false)\n\n return (\n <div\n data-sk-form-mount={enhanced ? 'enhanced' : 'shell'}\n data-sk-part=\"form-mount\"\n style={enter === 'fade' ? FADE_HOST : undefined}\n >\n {enter === 'fade' && <EnterStyles />}\n {ready ? (\n <Suspense fallback={children}>\n <Enhanced onMount={() => setEnhanced(true)} fallback={children}>\n <InteractiveForm\n formId={formId}\n initialConfig={initialConfig}\n className={className}\n nativeReturnTo={nativeReturnTo}\n experience={experience}\n defaultValues={defaultValues}\n onSuccess={onSuccess}\n onError={onError}\n />\n </Enhanced>\n </Suspense>\n ) : (\n children\n )}\n </div>\n )\n}\n\n/**\n * Reports the moment the enhancement is on screen, and keeps the shell\n * rendered if it throws. An error boundary is the difference between \"the\n * fancy version failed\" and \"there is no form on this page\".\n */\nclass Enhanced extends React.Component<\n { onMount: () => void; fallback: React.ReactNode; children: React.ReactNode },\n { failed: boolean }\n> {\n state = { failed: false }\n\n static getDerivedStateFromError() {\n return { failed: true }\n }\n\n componentDidCatch(err: unknown) {\n console.error('[Forms] Enhancement failed; keeping the native form:', err)\n }\n\n componentDidMount() {\n if (!this.state.failed) this.props.onMount()\n }\n\n render() {\n if (this.state.failed) return this.props.fallback\n return <FrameTag>{this.props.children}</FrameTag>\n }\n}\n\n/** Adds data-sk-enter for one frame so a CSS transition has something to run. */\nfunction FrameTag({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n const el = ref.current\n if (!el) return\n el.setAttribute('data-sk-enter', '')\n const id = requestAnimationFrame(() => el.removeAttribute('data-sk-enter'))\n return () => cancelAnimationFrame(id)\n }, [])\n return (\n <div ref={ref} style={{ display: 'contents' }}>\n {children}\n </div>\n )\n}\n\nconst FADE_HOST: React.CSSProperties = {\n transition:\n 'opacity var(--sk-form-enter-duration, 320ms) var(--sk-form-enter-easing, cubic-bezier(.22,1,.36,1))',\n}\n\n/**\n * Scoped to [data-sk-part=\"form-mount\"] so it can never touch a site's own\n * markup. Emitted once per form; identical text dedupes in every browser's\n * style cache and gzips to nothing.\n */\nfunction EnterStyles() {\n return (\n <style\n // biome-ignore lint/security/noDangerouslySetInnerHtml: static string, no interpolation\n dangerouslySetInnerHTML={{\n __html: `\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"shell\"]{opacity:.55}\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"enhanced\"]{opacity:1}\n[data-sk-part=\"form-mount\"] [data-sk-enter]{opacity:0;transform:translateY(var(--sk-form-enter-distance,6px))}\n[data-sk-part=\"form-mount\"] [data-sk-enter] *{transition:none!important}\n@media (prefers-reduced-motion: reduce){\n[data-sk-part=\"form-mount\"]{transition:none!important}\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"shell\"]{opacity:1}\n[data-sk-part=\"form-mount\"] [data-sk-enter]{opacity:1;transform:none}\n}`,\n }}\n />\n )\n}\n"]}
|
|
@@ -54,7 +54,7 @@ function useSignal() {
|
|
|
54
54
|
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
55
55
|
}
|
|
56
56
|
var LazySignalCore = dynamic(
|
|
57
|
-
() => import('./SignalCore-
|
|
57
|
+
() => import('./SignalCore-X3GDGBZD.mjs').then((m) => ({ default: m.SignalCore })),
|
|
58
58
|
{ ssr: false }
|
|
59
59
|
);
|
|
60
60
|
function SignalBridge({
|
|
@@ -161,5 +161,5 @@ function useSignalExperiment(experimentId) {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
export { SignalBridge, useSignal, useSignalConfig, useSignalEvent, useSignalExperiment, useSignalOutcome };
|
|
164
|
-
//# sourceMappingURL=chunk-
|
|
165
|
-
//# sourceMappingURL=chunk-
|
|
164
|
+
//# sourceMappingURL=chunk-G5HUI4BO.mjs.map
|
|
165
|
+
//# sourceMappingURL=chunk-G5HUI4BO.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/signal/store.ts","../src/signal/SignalBridge.tsx"],"names":[],"mappings":"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","file":"chunk-N6X76QUF.mjs","sourcesContent":["'use client'\n\n/**\n * Signal store — module singleton behind useSignal().\n *\n * 4.0: SignalBridge no longer wraps page children in a React context; it\n * mounts as a childless sibling and publishes its API here. useSignal()\n * subscribes via useSyncExternalStore, so consumers work anywhere in the\n * tree — and BEFORE the bridge mounts, they get a buffering stub whose\n * trackEvent/trackOutcome queue calls that the bridge drains on publish.\n * getExperiment returns null pre-publish, identical to the pre-config\n * `loading` window consumers already handle.\n *\n * Behavior change vs 3.x (major): useSignal() no longer throws outside a\n * SignalBridge. With signal disabled it stays a bounded no-op stub — a\n * one-time debug warning fires instead of a crash.\n */\n\nimport { useSyncExternalStore } from 'react'\nimport type {\n SignalContextValue,\n SignalEvent,\n SignalOutcome,\n} from './types'\n\nconst pendingEvents: Partial<SignalEvent>[] = []\nconst pendingOutcomes: SignalOutcome[] = []\nconst MAX_PENDING = 100\nlet warnedNoBridge = false\n\n/** Frozen pre-bridge stub — stable reference (useSyncExternalStore requirement). */\nconst STUB: SignalContextValue = Object.freeze({\n config: null,\n loading: true,\n error: null,\n trackEvent: (event: Partial<SignalEvent>) => {\n if (pendingEvents.length < MAX_PENDING) pendingEvents.push(event)\n else if (!warnedNoBridge) {\n warnedNoBridge = true\n console.warn('[site-kit] Signal events are queuing but no SignalBridge has mounted — is signal enabled on SiteKitLayout?')\n }\n },\n trackOutcome: (outcome: SignalOutcome) => {\n if (pendingOutcomes.length < MAX_PENDING) pendingOutcomes.push(outcome)\n },\n getExperiment: () => null,\n refreshConfig: async () => {},\n})\n\nlet current: SignalContextValue = STUB\nconst listeners = new Set<() => void>()\n\nfunction emit() {\n for (const l of listeners) l()\n}\n\n/** Bridge-side: publish the live API (called whenever its value changes). */\nexport function publishSignal(value: SignalContextValue): void {\n const firstPublish = current === STUB\n current = value\n if (firstPublish) {\n // Drain calls made before the bridge mounted. The bridge's own trackEvent\n // buffers again internally until SignalCore loads, so nothing is lost.\n pendingEvents.splice(0).forEach(e => value.trackEvent(e))\n pendingOutcomes.splice(0).forEach(o => value.trackOutcome(o))\n }\n emit()\n}\n\n/** Bridge-side: reset on unmount so a remount starts clean (tests, HMR). */\nexport function resetSignal(): void {\n current = STUB\n emit()\n}\n\nfunction subscribe(onChange: () => void) {\n listeners.add(onChange)\n return () => listeners.delete(onChange)\n}\n\nconst getSnapshot = () => current\nconst getServerSnapshot = () => STUB\n\n/**\n * Access Signal (experiments, behavioral events) from anywhere in the tree.\n * No provider required — see module docs for pre-mount buffering semantics.\n */\nexport function useSignal(): SignalContextValue {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n","/**\n * @sonordev/site-kit/signal - SignalBridge\n *\n * Central coordination layer for Signal AI integration.\n *\n * Architecture (2026-07 perf release): SignalBridge is a tiny SSR-SAFE context shell. It\n * renders {children} immediately — children server-render and hydrate\n * normally, no ssr:false wrapper — and lazy-loads the heavy machinery\n * (SignalCore: config fetch, SSE streaming, experiment assignment, event\n * queue, behavior tracking) as a CHILDLESS sibling. The core registers its\n * imperative API into the shell on mount; `trackEvent`/`trackOutcome` calls\n * made before the core is ready are buffered and drained on registration.\n *\n * This removes the old failure mode where enabling `signal` de-opted the\n * whole route to client rendering (the same SSR bailout the analytics\n * sibling-mount fixed) — the #1 recommendation of the 2026-07 perf audit.\n *\n * The core is NOT idle-deferred: experiment variants must apply as early as\n * possible, so it starts loading on hydration.\n */\n\n'use client'\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport dynamic from 'next/dynamic'\nimport type { SignalCoreApi, SignalCoreState } from './SignalCore'\nimport type {\n SignalConfig,\n SignalContextValue,\n SignalBridgeProps,\n SignalEvent,\n SignalOutcome,\n ExperimentAssignment,\n} from './types'\n\n// Heavy machinery — loaded client-side only, never wraps content.\nimport { publishSignal, resetSignal, useSignal } from './store'\n\nconst LazySignalCore = dynamic(\n () => import('./SignalCore').then(m => ({ default: m.SignalCore })),\n { ssr: false },\n)\n\n// ============================================\n// Context\n// ============================================\n\n// 4.0: the context is gone. useSignal() reads the module store, so consumers\n// work anywhere in the tree and SignalBridge mounts as a childless sibling.\nexport { useSignal } from './store'\n\n// ============================================\n// SignalBridge Component (shell)\n// ============================================\n\nexport function SignalBridge({\n enabled = true,\n realtime = true,\n experiments = true,\n behaviorTracking = true,\n visitorId: externalVisitorId,\n sessionId: externalSessionId,\n pageMetadataRef,\n children,\n}: SignalBridgeProps) {\n const [config, setConfig] = useState<SignalConfig | null>(null)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const coreRef = useRef<SignalCoreApi | null>(null)\n // Calls made before the core chunk loads are buffered here and drained on\n // registration — consumers can call trackEvent/trackOutcome from first render.\n const pendingEventsRef = useRef<Partial<SignalEvent>[]>([])\n const pendingOutcomesRef = useRef<SignalOutcome[]>([])\n\n const handleState = useCallback((state: SignalCoreState) => {\n setConfig(state.config)\n setLoading(state.loading)\n setError(state.error)\n }, [])\n\n const handleRegister = useCallback((api: SignalCoreApi | null) => {\n coreRef.current = api\n if (api) {\n pendingEventsRef.current.splice(0).forEach(event => api.trackEvent(event))\n pendingOutcomesRef.current.splice(0).forEach(outcome => api.trackOutcome(outcome))\n }\n }, [])\n\n const trackEvent = useCallback((event: Partial<SignalEvent>) => {\n if (coreRef.current) {\n coreRef.current.trackEvent(event)\n } else {\n pendingEventsRef.current.push(event)\n }\n }, [])\n\n const trackOutcome = useCallback((outcome: SignalOutcome) => {\n if (coreRef.current) {\n coreRef.current.trackOutcome(outcome)\n } else {\n pendingOutcomesRef.current.push(outcome)\n }\n }, [])\n\n const getExperiment = useCallback((experimentId: string): ExperimentAssignment | null => {\n // null before the core loads — identical to the pre-config-fetch behavior\n // consumers already handle via `loading`.\n return coreRef.current?.getExperiment(experimentId) ?? null\n }, [])\n\n const refreshConfig = useCallback(async () => {\n await coreRef.current?.refreshConfig()\n }, [])\n\n const contextValue = useMemo<SignalContextValue>(() => ({\n config,\n loading,\n error,\n trackEvent,\n trackOutcome,\n getExperiment,\n refreshConfig,\n }), [config, loading, error, trackEvent, trackOutcome, getExperiment, refreshConfig])\n\n // Publish to the module store (drains pre-mount buffered calls on first\n // publish); reset on unmount so tests/HMR start clean.\n useEffect(() => {\n publishSignal(contextValue)\n }, [contextValue])\n useEffect(() => () => resetSignal(), [])\n\n return (\n <>\n {children}\n {enabled && (\n <LazySignalCore\n enabled={enabled}\n realtime={realtime}\n experiments={experiments}\n behaviorTracking={behaviorTracking}\n externalVisitorId={externalVisitorId}\n externalSessionId={externalSessionId}\n pageMetadataRef={pageMetadataRef}\n onRegister={handleRegister}\n onState={handleState}\n />\n )}\n </>\n )\n}\n\n// ============================================\n// Convenience Hooks\n// ============================================\n\n/**\n * Hook to access Signal config\n */\nexport function useSignalConfig(): SignalConfig | null {\n const { config } = useSignal()\n return config\n}\n\n/**\n * Hook for tracking events\n */\nexport function useSignalEvent() {\n const { trackEvent } = useSignal()\n return trackEvent\n}\n\n/**\n * Hook for tracking outcomes/conversions\n */\nexport function useSignalOutcome() {\n const { trackOutcome } = useSignal()\n return { trackOutcome }\n}\n\n/**\n * Hook for experiment assignment\n */\nexport function useSignalExperiment(experimentId: string): {\n assignment: ExperimentAssignment | null\n variant: string | null\n isControl: boolean\n} {\n const { getExperiment, config } = useSignal()\n const assignment = getExperiment(experimentId)\n\n // Check if experiment is running\n const experiment = config?.experiments?.find(e => e.id === experimentId)\n const isRunning = experiment?.status === 'running'\n\n return {\n assignment: isRunning ? assignment : null,\n variant: isRunning && assignment ? assignment.variant_key : null,\n isControl: !assignment || assignment.variant_key === 'control',\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/signal/store.ts","../src/signal/SignalBridge.tsx"],"names":[],"mappings":"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","file":"chunk-G5HUI4BO.mjs","sourcesContent":["'use client'\n\n/**\n * Signal store — module singleton behind useSignal().\n *\n * 4.0: SignalBridge no longer wraps page children in a React context; it\n * mounts as a childless sibling and publishes its API here. useSignal()\n * subscribes via useSyncExternalStore, so consumers work anywhere in the\n * tree — and BEFORE the bridge mounts, they get a buffering stub whose\n * trackEvent/trackOutcome queue calls that the bridge drains on publish.\n * getExperiment returns null pre-publish, identical to the pre-config\n * `loading` window consumers already handle.\n *\n * Behavior change vs 3.x (major): useSignal() no longer throws outside a\n * SignalBridge. With signal disabled it stays a bounded no-op stub — a\n * one-time debug warning fires instead of a crash.\n */\n\nimport { useSyncExternalStore } from 'react'\nimport type {\n SignalContextValue,\n SignalEvent,\n SignalOutcome,\n} from './types'\n\nconst pendingEvents: Partial<SignalEvent>[] = []\nconst pendingOutcomes: SignalOutcome[] = []\nconst MAX_PENDING = 100\nlet warnedNoBridge = false\n\n/** Frozen pre-bridge stub — stable reference (useSyncExternalStore requirement). */\nconst STUB: SignalContextValue = Object.freeze({\n config: null,\n loading: true,\n error: null,\n trackEvent: (event: Partial<SignalEvent>) => {\n if (pendingEvents.length < MAX_PENDING) pendingEvents.push(event)\n else if (!warnedNoBridge) {\n warnedNoBridge = true\n console.warn('[site-kit] Signal events are queuing but no SignalBridge has mounted — is signal enabled on SiteKitLayout?')\n }\n },\n trackOutcome: (outcome: SignalOutcome) => {\n if (pendingOutcomes.length < MAX_PENDING) pendingOutcomes.push(outcome)\n },\n getExperiment: () => null,\n refreshConfig: async () => {},\n})\n\nlet current: SignalContextValue = STUB\nconst listeners = new Set<() => void>()\n\nfunction emit() {\n for (const l of listeners) l()\n}\n\n/** Bridge-side: publish the live API (called whenever its value changes). */\nexport function publishSignal(value: SignalContextValue): void {\n const firstPublish = current === STUB\n current = value\n if (firstPublish) {\n // Drain calls made before the bridge mounted. The bridge's own trackEvent\n // buffers again internally until SignalCore loads, so nothing is lost.\n pendingEvents.splice(0).forEach(e => value.trackEvent(e))\n pendingOutcomes.splice(0).forEach(o => value.trackOutcome(o))\n }\n emit()\n}\n\n/** Bridge-side: reset on unmount so a remount starts clean (tests, HMR). */\nexport function resetSignal(): void {\n current = STUB\n emit()\n}\n\nfunction subscribe(onChange: () => void) {\n listeners.add(onChange)\n return () => listeners.delete(onChange)\n}\n\nconst getSnapshot = () => current\nconst getServerSnapshot = () => STUB\n\n/**\n * Access Signal (experiments, behavioral events) from anywhere in the tree.\n * No provider required — see module docs for pre-mount buffering semantics.\n */\nexport function useSignal(): SignalContextValue {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n","/**\n * @sonordev/site-kit/signal - SignalBridge\n *\n * Central coordination layer for Signal AI integration.\n *\n * Architecture (2026-07 perf release): SignalBridge is a tiny SSR-SAFE context shell. It\n * renders {children} immediately — children server-render and hydrate\n * normally, no ssr:false wrapper — and lazy-loads the heavy machinery\n * (SignalCore: config fetch, SSE streaming, experiment assignment, event\n * queue, behavior tracking) as a CHILDLESS sibling. The core registers its\n * imperative API into the shell on mount; `trackEvent`/`trackOutcome` calls\n * made before the core is ready are buffered and drained on registration.\n *\n * This removes the old failure mode where enabling `signal` de-opted the\n * whole route to client rendering (the same SSR bailout the analytics\n * sibling-mount fixed) — the #1 recommendation of the 2026-07 perf audit.\n *\n * The core is NOT idle-deferred: experiment variants must apply as early as\n * possible, so it starts loading on hydration.\n */\n\n'use client'\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport dynamic from 'next/dynamic'\nimport type { SignalCoreApi, SignalCoreState } from './SignalCore'\nimport type {\n SignalConfig,\n SignalContextValue,\n SignalBridgeProps,\n SignalEvent,\n SignalOutcome,\n ExperimentAssignment,\n} from './types'\n\n// Heavy machinery — loaded client-side only, never wraps content.\nimport { publishSignal, resetSignal, useSignal } from './store'\n\nconst LazySignalCore = dynamic(\n () => import('./SignalCore').then(m => ({ default: m.SignalCore })),\n { ssr: false },\n)\n\n// ============================================\n// Context\n// ============================================\n\n// 4.0: the context is gone. useSignal() reads the module store, so consumers\n// work anywhere in the tree and SignalBridge mounts as a childless sibling.\nexport { useSignal } from './store'\n\n// ============================================\n// SignalBridge Component (shell)\n// ============================================\n\nexport function SignalBridge({\n enabled = true,\n realtime = true,\n experiments = true,\n behaviorTracking = true,\n visitorId: externalVisitorId,\n sessionId: externalSessionId,\n pageMetadataRef,\n children,\n}: SignalBridgeProps) {\n const [config, setConfig] = useState<SignalConfig | null>(null)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const coreRef = useRef<SignalCoreApi | null>(null)\n // Calls made before the core chunk loads are buffered here and drained on\n // registration — consumers can call trackEvent/trackOutcome from first render.\n const pendingEventsRef = useRef<Partial<SignalEvent>[]>([])\n const pendingOutcomesRef = useRef<SignalOutcome[]>([])\n\n const handleState = useCallback((state: SignalCoreState) => {\n setConfig(state.config)\n setLoading(state.loading)\n setError(state.error)\n }, [])\n\n const handleRegister = useCallback((api: SignalCoreApi | null) => {\n coreRef.current = api\n if (api) {\n pendingEventsRef.current.splice(0).forEach(event => api.trackEvent(event))\n pendingOutcomesRef.current.splice(0).forEach(outcome => api.trackOutcome(outcome))\n }\n }, [])\n\n const trackEvent = useCallback((event: Partial<SignalEvent>) => {\n if (coreRef.current) {\n coreRef.current.trackEvent(event)\n } else {\n pendingEventsRef.current.push(event)\n }\n }, [])\n\n const trackOutcome = useCallback((outcome: SignalOutcome) => {\n if (coreRef.current) {\n coreRef.current.trackOutcome(outcome)\n } else {\n pendingOutcomesRef.current.push(outcome)\n }\n }, [])\n\n const getExperiment = useCallback((experimentId: string): ExperimentAssignment | null => {\n // null before the core loads — identical to the pre-config-fetch behavior\n // consumers already handle via `loading`.\n return coreRef.current?.getExperiment(experimentId) ?? null\n }, [])\n\n const refreshConfig = useCallback(async () => {\n await coreRef.current?.refreshConfig()\n }, [])\n\n const contextValue = useMemo<SignalContextValue>(() => ({\n config,\n loading,\n error,\n trackEvent,\n trackOutcome,\n getExperiment,\n refreshConfig,\n }), [config, loading, error, trackEvent, trackOutcome, getExperiment, refreshConfig])\n\n // Publish to the module store (drains pre-mount buffered calls on first\n // publish); reset on unmount so tests/HMR start clean.\n useEffect(() => {\n publishSignal(contextValue)\n }, [contextValue])\n useEffect(() => () => resetSignal(), [])\n\n return (\n <>\n {children}\n {enabled && (\n <LazySignalCore\n enabled={enabled}\n realtime={realtime}\n experiments={experiments}\n behaviorTracking={behaviorTracking}\n externalVisitorId={externalVisitorId}\n externalSessionId={externalSessionId}\n pageMetadataRef={pageMetadataRef}\n onRegister={handleRegister}\n onState={handleState}\n />\n )}\n </>\n )\n}\n\n// ============================================\n// Convenience Hooks\n// ============================================\n\n/**\n * Hook to access Signal config\n */\nexport function useSignalConfig(): SignalConfig | null {\n const { config } = useSignal()\n return config\n}\n\n/**\n * Hook for tracking events\n */\nexport function useSignalEvent() {\n const { trackEvent } = useSignal()\n return trackEvent\n}\n\n/**\n * Hook for tracking outcomes/conversions\n */\nexport function useSignalOutcome() {\n const { trackOutcome } = useSignal()\n return { trackOutcome }\n}\n\n/**\n * Hook for experiment assignment\n */\nexport function useSignalExperiment(experimentId: string): {\n assignment: ExperimentAssignment | null\n variant: string | null\n isControl: boolean\n} {\n const { getExperiment, config } = useSignal()\n const assignment = getExperiment(experimentId)\n\n // Check if experiment is running\n const experiment = config?.experiments?.find(e => e.id === experimentId)\n const isRunning = experiment?.status === 'running'\n\n return {\n assignment: isRunning ? assignment : null,\n variant: isRunning && assignment ? assignment.variant_key : null,\n isControl: !assignment || assignment.variant_key === 'control',\n }\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
// src/shared/visual-viewport-gap.ts
|
|
7
|
+
var VISUAL_VIEWPORT_GAP_PROPERTY = "--sk-vv-layout-gap";
|
|
8
|
+
function measureVisualViewportGap(win) {
|
|
9
|
+
const vv = win.visualViewport;
|
|
10
|
+
if (!vv) return 0;
|
|
11
|
+
return Math.max(0, Math.round(win.innerHeight - vv.offsetTop - vv.height));
|
|
12
|
+
}
|
|
13
|
+
function useVisualViewportGap() {
|
|
14
|
+
react.useEffect(() => {
|
|
15
|
+
const root = document.documentElement;
|
|
16
|
+
const vv = window.visualViewport;
|
|
17
|
+
const sync = () => {
|
|
18
|
+
const next = `${measureVisualViewportGap(window)}px`;
|
|
19
|
+
if (root.style.getPropertyValue(VISUAL_VIEWPORT_GAP_PROPERTY) === next) return;
|
|
20
|
+
root.style.setProperty(VISUAL_VIEWPORT_GAP_PROPERTY, next);
|
|
21
|
+
};
|
|
22
|
+
sync();
|
|
23
|
+
vv?.addEventListener("resize", sync);
|
|
24
|
+
vv?.addEventListener("scroll", sync);
|
|
25
|
+
window.addEventListener("resize", sync);
|
|
26
|
+
return () => {
|
|
27
|
+
vv?.removeEventListener("resize", sync);
|
|
28
|
+
vv?.removeEventListener("scroll", sync);
|
|
29
|
+
window.removeEventListener("resize", sync);
|
|
30
|
+
root.style.removeProperty(VISUAL_VIEWPORT_GAP_PROPERTY);
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
}
|
|
34
|
+
function VisualViewportGap() {
|
|
35
|
+
useVisualViewportGap();
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exports.VISUAL_VIEWPORT_GAP_PROPERTY = VISUAL_VIEWPORT_GAP_PROPERTY;
|
|
40
|
+
exports.VisualViewportGap = VisualViewportGap;
|
|
41
|
+
exports.useVisualViewportGap = useVisualViewportGap;
|
|
42
|
+
//# sourceMappingURL=chunk-G6WF4G3E.js.map
|
|
43
|
+
//# sourceMappingURL=chunk-G6WF4G3E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/visual-viewport-gap.ts"],"names":["useEffect"],"mappings":";;;;;AA+BO,IAAM,4BAAA,GAA+B;AAOrC,SAAS,yBACd,GAAA,EACQ;AACR,EAAA,MAAM,KAAK,GAAA,CAAI,cAAA;AACf,EAAA,IAAI,CAAC,IAAI,OAAO,CAAA;AAChB,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,WAAA,GAAc,EAAA,CAAG,SAAA,GAAY,EAAA,CAAG,MAAM,CAAC,CAAA;AAC3E;AAMO,SAAS,oBAAA,GAA6B;AAC3C,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,OAAO,QAAA,CAAS,eAAA;AACtB,IAAA,MAAM,KAAK,MAAA,CAAO,cAAA;AAElB,IAAA,MAAM,OAAO,MAAM;AACjB,MAAA,MAAM,IAAA,GAAO,CAAA,EAAG,wBAAA,CAAyB,MAAM,CAAC,CAAA,EAAA,CAAA;AAMhD,MAAA,IAAI,IAAA,CAAK,KAAA,CAAM,gBAAA,CAAiB,4BAA4B,MAAM,IAAA,EAAM;AACxE,MAAA,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,4BAAA,EAA8B,IAAI,CAAA;AAAA,IAC3D,CAAA;AAEA,IAAA,IAAA,EAAK;AACL,IAAA,EAAA,EAAI,gBAAA,CAAiB,UAAU,IAAI,CAAA;AACnC,IAAA,EAAA,EAAI,gBAAA,CAAiB,UAAU,IAAI,CAAA;AACnC,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAU,IAAI,CAAA;AAEtC,IAAA,OAAO,MAAM;AACX,MAAA,EAAA,EAAI,mBAAA,CAAoB,UAAU,IAAI,CAAA;AACtC,MAAA,EAAA,EAAI,mBAAA,CAAoB,UAAU,IAAI,CAAA;AACtC,MAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,IAAI,CAAA;AACzC,MAAA,IAAA,CAAK,KAAA,CAAM,eAAe,4BAA4B,CAAA;AAAA,IACxD,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AACP;AAcO,SAAS,iBAAA,GAA0B;AACxC,EAAA,oBAAA,EAAqB;AACrB,EAAA,OAAO,IAAA;AACT","file":"chunk-G6WF4G3E.js","sourcesContent":["/**\n * @sonordev/site-kit — visual viewport gap\n *\n * Single source of truth for measuring how far the layout viewport extends\n * below the visual viewport and publishing it as `--sk-vv-layout-gap` on\n * <html>. Moved into the kit from queencityriverboats.com and\n * destinyyachtcharters.com, which each carried a byte-identical copy.\n *\n * On a phone the two viewports can disagree (browser chrome, in-app browsers,\n * pinch zoom, an on-screen keyboard), and `position: fixed; bottom: …` is\n * measured against the LAYOUT viewport. The difference is how far a fixed\n * bottom element sits below what the visitor can actually see. Adding the\n * property to a `bottom` calc puts it back on screen:\n *\n * bottom: calc(24px + env(safe-area-inset-bottom, 0px) + var(--sk-vv-layout-gap, 0px));\n *\n * The Echo launcher and chat popup already include it (see\n * engage/launcher-placement.ts), so mounting the tracker is all a site needs\n * for those; the property is also there for the site's own fixed UI.\n *\n * Opt-in on purpose. The gap also grows to the keyboard's height while a\n * field has focus, which lifts every element that reads it above the\n * keyboard. That is what the two sites above want; it is not a default to\n * impose on the fleet.\n */\n\n'use client'\n\nimport { useEffect } from 'react'\n\n/** The custom property the gap is published on, in px. Unset when no tracker is mounted. */\nexport const VISUAL_VIEWPORT_GAP_PROPERTY = '--sk-vv-layout-gap'\n\n/**\n * Whole pixels between the bottom of the visual viewport and the bottom of\n * the layout viewport. 0 when the browser has no VisualViewport API, and\n * never negative.\n */\nexport function measureVisualViewportGap(\n win: Pick<Window, 'innerHeight' | 'visualViewport'>,\n): number {\n const vv = win.visualViewport\n if (!vv) return 0\n return Math.max(0, Math.round(win.innerHeight - vv.offsetTop - vv.height))\n}\n\n/**\n * Keep `--sk-vv-layout-gap` on <html> current for as long as the calling\n * component is mounted, and remove it on unmount.\n */\nexport function useVisualViewportGap(): void {\n useEffect(() => {\n const root = document.documentElement\n const vv = window.visualViewport\n\n const sync = () => {\n const next = `${measureVisualViewportGap(window)}px`\n // A custom property on <html> invalidates style for the whole document,\n // and visualViewport fires resize/scroll continuously during a pinch or\n // a toolbar animation. Only write when the value actually moved.\n // Compared against the live inline value rather than a cached copy, so\n // anything else that wrote or removed it is corrected on the next event.\n if (root.style.getPropertyValue(VISUAL_VIEWPORT_GAP_PROPERTY) === next) return\n root.style.setProperty(VISUAL_VIEWPORT_GAP_PROPERTY, next)\n }\n\n sync()\n vv?.addEventListener('resize', sync)\n vv?.addEventListener('scroll', sync)\n window.addEventListener('resize', sync)\n\n return () => {\n vv?.removeEventListener('resize', sync)\n vv?.removeEventListener('scroll', sync)\n window.removeEventListener('resize', sync)\n root.style.removeProperty(VISUAL_VIEWPORT_GAP_PROPERTY)\n }\n }, [])\n}\n\n/**\n * Childless component form of `useVisualViewportGap`, for a server layout:\n *\n * ```tsx\n * import { VisualViewportGap } from '@sonordev/site-kit/client'\n *\n * <SiteKitLayout>\n * <VisualViewportGap />\n * {children}\n * </SiteKitLayout>\n * ```\n */\nexport function VisualViewportGap(): null {\n useVisualViewportGap()\n return null\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { sanitizeFleetVersion, buildFleetHeartbeatPayload } from './chunk-ZPLN537Q.mjs';
|
|
3
3
|
import { useDeferredActivation } from './chunk-ZVBLXXNJ.mjs';
|
|
4
|
-
import { sonorFetch } from './chunk-
|
|
5
|
-
import { SITE_KIT_VERSION } from './chunk-
|
|
4
|
+
import { sonorFetch } from './chunk-OFSA7LUH.mjs';
|
|
5
|
+
import { SITE_KIT_VERSION } from './chunk-WDBCXRTT.mjs';
|
|
6
6
|
import { useRef, useEffect } from 'react';
|
|
7
7
|
|
|
8
8
|
// src/fleet/heartbeat.ts
|
|
@@ -93,5 +93,5 @@ function FleetHeartbeat({
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
export { FLEET_HEARTBEAT_PATH, FleetHeartbeat, detectNextVersion, sendFleetHeartbeat };
|
|
96
|
-
//# sourceMappingURL=chunk-
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
96
|
+
//# sourceMappingURL=chunk-HBI67P7Y.mjs.map
|
|
97
|
+
//# sourceMappingURL=chunk-HBI67P7Y.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":[],"mappings":"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","file":"chunk-DQM5GR2P.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 canonical `sonorFetch` transport, which already 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 { 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 {\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\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 `${apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n // retries: 0 — the once-per-session guard already bounds volume, and a\n // missed heartbeat is harmless (the next page load or passive\n // x-sitekit-version capture re-registers the site).\n { 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 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 {\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}: 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 url = apiUrl || w.__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n const key = apiKey || w.__SITE_KIT_API_KEY__\n if (!key) return // no key resolvable — nothing to authenticate the heartbeat\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 debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug])\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-HBI67P7Y.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 canonical `sonorFetch` transport, which already 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 { 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 {\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\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 `${apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n // retries: 0 — the once-per-session guard already bounds volume, and a\n // missed heartbeat is harmless (the next page load or passive\n // x-sitekit-version capture re-registers the site).\n { 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 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 {\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}: 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 url = apiUrl || w.__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n const key = apiKey || w.__SITE_KIT_API_KEY__\n if (!key) return // no key resolvable — nothing to authenticate the heartbeat\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 debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug])\n\n return null\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkSUZ3ZLB2_js = require('./chunk-SUZ3ZLB2.js');
|
|
4
4
|
|
|
5
5
|
// src/shared/siteToken.ts
|
|
6
6
|
var SITE_TOKEN_PREFIX = "st1.";
|
|
@@ -99,7 +99,7 @@ async function doRefresh() {
|
|
|
99
99
|
method: "POST",
|
|
100
100
|
headers: {
|
|
101
101
|
"content-type": "application/json",
|
|
102
|
-
"x-sitekit-version":
|
|
102
|
+
"x-sitekit-version": chunkSUZ3ZLB2_js.SITE_KIT_VERSION,
|
|
103
103
|
// Send the current token as x-api-key too, so the server's key-scoped
|
|
104
104
|
// throttler buckets refreshes by token rather than by shared CDN IP.
|
|
105
105
|
"x-api-key": current
|
|
@@ -154,5 +154,5 @@ exports.getFreshCredential = getFreshCredential;
|
|
|
154
154
|
exports.isSiteToken = isSiteToken;
|
|
155
155
|
exports.isTokenMode = isTokenMode;
|
|
156
156
|
exports.refreshSiteToken = refreshSiteToken;
|
|
157
|
-
//# sourceMappingURL=chunk-
|
|
158
|
-
//# sourceMappingURL=chunk-
|
|
157
|
+
//# sourceMappingURL=chunk-HFJQMIFM.js.map
|
|
158
|
+
//# sourceMappingURL=chunk-HFJQMIFM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/siteToken.ts"],"names":["SITE_KIT_VERSION"],"mappings":"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","file":"chunk-VA36CJRV.js","sourcesContent":["/**\n * @sonordev/site-kit shared — Client token manager\n *\n * SiteKitLayout (a server component) no longer ships the raw `sonor_` API key to\n * the browser. Instead it mints a short-lived, project-scoped token server-side\n * and injects THAT. This module is the browser-side lifecycle for that token:\n *\n * - holds the current token (seeded from the injected value)\n * - a static page's seed can be days old and already expired on first view, so\n * it transparently REFRESHES via the public endpoint before first use\n * - dedupes concurrent refreshes (one in-flight request, awaited by all)\n * - persists the freshest token in sessionStorage so navigations within a\n * session don't each re-refresh\n *\n * Everything here is a no-op passthrough when the injected credential is a raw\n * key (a 2.x kit, or the mint-failed fallback) — legacy behavior is unchanged.\n * Fully SSR-safe: on the server there is no refresh, just the seed.\n *\n * The token payload is NOT secret (only its HMAC is unforgeable), so decoding it\n * client-side to read `exp`/`pid` is fine and needs no crypto.\n */\nimport { SITE_KIT_VERSION } from './version'\n\nexport const SITE_TOKEN_PREFIX = 'st1.'\n\n/** Refresh this many ms before expiry, so a request never races the deadline. */\nconst REFRESH_SKEW_MS = 5 * 60_000\nconst REFRESH_TIMEOUT_MS = 6_000\nconst STORAGE_KEY = '__sonor_site_token__'\n\nexport function isSiteToken(credential?: string | null): boolean {\n return typeof credential === 'string' && credential.startsWith(SITE_TOKEN_PREFIX)\n}\n\ninterface DecodedClaims {\n pid?: string\n iat?: number\n exp?: number\n}\n\n/** Decode the (non-secret) token payload. Returns null if not a token / malformed. */\nexport function decodeSiteTokenClaims(token?: string | null): DecodedClaims | null {\n if (!isSiteToken(token)) return null\n const seg = (token as string).split('.')[1]\n if (!seg) return null\n try {\n // base64url → JSON. atob handles standard base64; restore url-safe chars.\n const b64 = seg.replace(/-/g, '+').replace(/_/g, '/')\n const json =\n typeof atob === 'function'\n ? atob(b64)\n : Buffer.from(seg, 'base64url').toString('utf8')\n const claims = JSON.parse(json)\n return typeof claims === 'object' && claims ? claims : null\n } catch {\n return null\n }\n}\n\n/**\n * Stable id for the auth circuit breaker. Tokens rotate, so keying the breaker\n * by the raw value would reset it every refresh — key by the project id encoded\n * in the token instead. Raw keys use a safe prefix.\n */\nexport function credentialBreakerId(credential?: string | null): string {\n if (!credential) return '(anonymous)'\n const claims = decodeSiteTokenClaims(credential)\n if (claims?.pid) return `pid:${claims.pid}`\n return credential.slice(0, 16)\n}\n\ninterface TokenState {\n /** The credential injected by the layout (token or raw key). */\n seed: string | undefined\n /** Whether we're in token mode (seed is a token). */\n isToken: boolean\n /** Base API URL for the refresh endpoint. */\n apiUrl: string\n /** The freshest known token (>= seed). Null in raw-key mode. */\n token: string | null\n /** In-flight refresh, deduped. */\n refreshing: Promise<string | null> | null\n}\n\nfunction initialTokenState(): TokenState {\n return {\n seed: undefined,\n isToken: false,\n apiUrl: 'https://api.sonor.io',\n token: null,\n refreshing: null,\n }\n}\n\n/**\n * Page-wide singleton, keyed on window. Bundlers can (and in the field, do)\n * inline more than one copy of this module into a page — e.g. an app importing\n * site-kit directly while a transpiled component library bundles its own copy.\n * Module-scoped state then splits into per-copy brains: one copy gets\n * configured and refreshes fine while the other keeps sending the stale\n * prerendered seed, 401s, and trips its breaker (\"sonor-auth-paused\" with a\n * working network). Sharing the state object on window gives every copy the\n * same token, the same deduped refresh, and the same breaker. On the server\n * (no window) each module instance keeps private state — there's no refresh\n * lifecycle there anyway.\n */\nconst TOKEN_STATE_KEY = '__SONOR_SITE_TOKEN_STATE__'\n\nconst state: TokenState = (() => {\n if (typeof window === 'undefined') return initialTokenState()\n const w = window as unknown as Record<string, TokenState | undefined>\n return (w[TOKEN_STATE_KEY] ??= initialTokenState())\n})()\n\nfunction readStoredToken(): string | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.sessionStorage.getItem(STORAGE_KEY)\n if (!raw) return null\n const parsed = JSON.parse(raw)\n return typeof parsed?.token === 'string' ? parsed.token : null\n } catch {\n return null\n }\n}\n\nfunction storeToken(token: string): void {\n if (typeof window === 'undefined') return\n try {\n window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ token }))\n } catch {\n /* private mode / disabled storage — memory-only is fine */\n }\n try {\n ;(window as any).__SITE_KIT_API_KEY__ = token\n } catch {\n /* ignore */\n }\n}\n\nfunction tokenExp(token: string | null): number {\n const claims = decodeSiteTokenClaims(token)\n return typeof claims?.exp === 'number' ? claims.exp * 1000 : 0\n}\n\n/**\n * Configure the manager from the layout-injected values. Adopts a fresher token\n * from sessionStorage if a prior page in this session already refreshed.\n */\nexport function configureSiteCredential(opts: {\n credential?: string\n apiUrl?: string\n}): void {\n state.seed = opts.credential\n state.isToken = isSiteToken(opts.credential)\n if (opts.apiUrl) state.apiUrl = opts.apiUrl.replace(/\\/+$/, '')\n\n if (!state.isToken) {\n state.token = null\n return\n }\n\n // configure runs on every render — never downgrade. Keep the freshest of the\n // injected seed, a token stored by an earlier page this session, and whatever\n // an in-flight/prior refresh already put in memory.\n const prior = state.token\n let best = opts.credential ?? null\n const stored = readStoredToken()\n if (stored && tokenExp(stored) > tokenExp(best)) best = stored\n if (prior && tokenExp(prior) > tokenExp(best)) best = prior\n state.token = best\n}\n\nfunction needsRefresh(): boolean {\n if (!state.isToken) return false\n if (!state.token) return true\n return Date.now() >= tokenExp(state.token) - REFRESH_SKEW_MS\n}\n\nasync function doRefresh(): Promise<string | null> {\n const current = state.token\n if (!current) return null\n try {\n const res = await fetch(`${state.apiUrl}/api/public/site-token/refresh`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n // Send the current token as x-api-key too, so the server's key-scoped\n // throttler buckets refreshes by token rather than by shared CDN IP.\n 'x-api-key': current,\n },\n body: JSON.stringify({ token: current }),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(REFRESH_TIMEOUT_MS)\n : undefined,\n })\n if (!res.ok) return null\n const data = await res.json().catch(() => null)\n const next = data?.token\n if (typeof next === 'string' && isSiteToken(next)) {\n state.token = next\n storeToken(next)\n return next\n }\n return null\n } catch {\n return null\n }\n}\n\n/** Refresh now (deduped). Returns the new token, or null on failure. */\nexport function refreshSiteToken(): Promise<string | null> {\n if (!state.isToken) return Promise.resolve(null)\n if (state.refreshing) return state.refreshing\n state.refreshing = doRefresh().finally(() => {\n state.refreshing = null\n })\n return state.refreshing\n}\n\n/**\n * The credential to put on the next request. In token mode, refreshes a stale\n * seed first. In raw-key mode, returns the seed unchanged (legacy passthrough).\n */\nexport async function getFreshCredential(\n seed?: string,\n): Promise<string | undefined> {\n // Unconfigured but handed a token (a component received the prerendered seed\n // as a prop before any provider configured the manager): adopt it so the\n // refresh machinery works instead of replaying a possibly-expired seed.\n if (!state.isToken && isSiteToken(seed)) {\n configureSiteCredential({ credential: seed })\n }\n // A raw key (or nothing configured) — no token machinery.\n if (!state.isToken) return seed ?? state.seed\n if (needsRefresh()) {\n const refreshed = await refreshSiteToken()\n if (refreshed) return refreshed\n // Refresh failed — fall through to whatever we have; the request may 401 and\n // trip the breaker with friendly copy.\n }\n return state.token ?? seed ?? state.seed\n}\n\n/** Whether the given credential is being managed as a token. */\nexport function isTokenMode(): boolean {\n return state.isToken\n}\n\n/**\n * Best-known credential WITHOUT refreshing — for synchronous unload paths\n * (sonorBeacon) that can't await. In token mode returns the freshest token held;\n * in raw-key mode returns the seed.\n */\nexport function currentCredential(seed?: string): string | undefined {\n if (!state.isToken) return seed ?? state.seed\n return state.token ?? seed ?? state.seed\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/siteToken.ts"],"names":["SITE_KIT_VERSION"],"mappings":"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","file":"chunk-HFJQMIFM.js","sourcesContent":["/**\n * @sonordev/site-kit shared — Client token manager\n *\n * SiteKitLayout (a server component) no longer ships the raw `sonor_` API key to\n * the browser. Instead it mints a short-lived, project-scoped token server-side\n * and injects THAT. This module is the browser-side lifecycle for that token:\n *\n * - holds the current token (seeded from the injected value)\n * - a static page's seed can be days old and already expired on first view, so\n * it transparently REFRESHES via the public endpoint before first use\n * - dedupes concurrent refreshes (one in-flight request, awaited by all)\n * - persists the freshest token in sessionStorage so navigations within a\n * session don't each re-refresh\n *\n * Everything here is a no-op passthrough when the injected credential is a raw\n * key (a 2.x kit, or the mint-failed fallback) — legacy behavior is unchanged.\n * Fully SSR-safe: on the server there is no refresh, just the seed.\n *\n * The token payload is NOT secret (only its HMAC is unforgeable), so decoding it\n * client-side to read `exp`/`pid` is fine and needs no crypto.\n */\nimport { SITE_KIT_VERSION } from './version'\n\nexport const SITE_TOKEN_PREFIX = 'st1.'\n\n/** Refresh this many ms before expiry, so a request never races the deadline. */\nconst REFRESH_SKEW_MS = 5 * 60_000\nconst REFRESH_TIMEOUT_MS = 6_000\nconst STORAGE_KEY = '__sonor_site_token__'\n\nexport function isSiteToken(credential?: string | null): boolean {\n return typeof credential === 'string' && credential.startsWith(SITE_TOKEN_PREFIX)\n}\n\ninterface DecodedClaims {\n pid?: string\n iat?: number\n exp?: number\n}\n\n/** Decode the (non-secret) token payload. Returns null if not a token / malformed. */\nexport function decodeSiteTokenClaims(token?: string | null): DecodedClaims | null {\n if (!isSiteToken(token)) return null\n const seg = (token as string).split('.')[1]\n if (!seg) return null\n try {\n // base64url → JSON. atob handles standard base64; restore url-safe chars.\n const b64 = seg.replace(/-/g, '+').replace(/_/g, '/')\n const json =\n typeof atob === 'function'\n ? atob(b64)\n : Buffer.from(seg, 'base64url').toString('utf8')\n const claims = JSON.parse(json)\n return typeof claims === 'object' && claims ? claims : null\n } catch {\n return null\n }\n}\n\n/**\n * Stable id for the auth circuit breaker. Tokens rotate, so keying the breaker\n * by the raw value would reset it every refresh — key by the project id encoded\n * in the token instead. Raw keys use a safe prefix.\n */\nexport function credentialBreakerId(credential?: string | null): string {\n if (!credential) return '(anonymous)'\n const claims = decodeSiteTokenClaims(credential)\n if (claims?.pid) return `pid:${claims.pid}`\n return credential.slice(0, 16)\n}\n\ninterface TokenState {\n /** The credential injected by the layout (token or raw key). */\n seed: string | undefined\n /** Whether we're in token mode (seed is a token). */\n isToken: boolean\n /** Base API URL for the refresh endpoint. */\n apiUrl: string\n /** The freshest known token (>= seed). Null in raw-key mode. */\n token: string | null\n /** In-flight refresh, deduped. */\n refreshing: Promise<string | null> | null\n}\n\nfunction initialTokenState(): TokenState {\n return {\n seed: undefined,\n isToken: false,\n apiUrl: 'https://api.sonor.io',\n token: null,\n refreshing: null,\n }\n}\n\n/**\n * Page-wide singleton, keyed on window. Bundlers can (and in the field, do)\n * inline more than one copy of this module into a page — e.g. an app importing\n * site-kit directly while a transpiled component library bundles its own copy.\n * Module-scoped state then splits into per-copy brains: one copy gets\n * configured and refreshes fine while the other keeps sending the stale\n * prerendered seed, 401s, and trips its breaker (\"sonor-auth-paused\" with a\n * working network). Sharing the state object on window gives every copy the\n * same token, the same deduped refresh, and the same breaker. On the server\n * (no window) each module instance keeps private state — there's no refresh\n * lifecycle there anyway.\n */\nconst TOKEN_STATE_KEY = '__SONOR_SITE_TOKEN_STATE__'\n\nconst state: TokenState = (() => {\n if (typeof window === 'undefined') return initialTokenState()\n const w = window as unknown as Record<string, TokenState | undefined>\n return (w[TOKEN_STATE_KEY] ??= initialTokenState())\n})()\n\nfunction readStoredToken(): string | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.sessionStorage.getItem(STORAGE_KEY)\n if (!raw) return null\n const parsed = JSON.parse(raw)\n return typeof parsed?.token === 'string' ? parsed.token : null\n } catch {\n return null\n }\n}\n\nfunction storeToken(token: string): void {\n if (typeof window === 'undefined') return\n try {\n window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ token }))\n } catch {\n /* private mode / disabled storage — memory-only is fine */\n }\n try {\n ;(window as any).__SITE_KIT_API_KEY__ = token\n } catch {\n /* ignore */\n }\n}\n\nfunction tokenExp(token: string | null): number {\n const claims = decodeSiteTokenClaims(token)\n return typeof claims?.exp === 'number' ? claims.exp * 1000 : 0\n}\n\n/**\n * Configure the manager from the layout-injected values. Adopts a fresher token\n * from sessionStorage if a prior page in this session already refreshed.\n */\nexport function configureSiteCredential(opts: {\n credential?: string\n apiUrl?: string\n}): void {\n state.seed = opts.credential\n state.isToken = isSiteToken(opts.credential)\n if (opts.apiUrl) state.apiUrl = opts.apiUrl.replace(/\\/+$/, '')\n\n if (!state.isToken) {\n state.token = null\n return\n }\n\n // configure runs on every render — never downgrade. Keep the freshest of the\n // injected seed, a token stored by an earlier page this session, and whatever\n // an in-flight/prior refresh already put in memory.\n const prior = state.token\n let best = opts.credential ?? null\n const stored = readStoredToken()\n if (stored && tokenExp(stored) > tokenExp(best)) best = stored\n if (prior && tokenExp(prior) > tokenExp(best)) best = prior\n state.token = best\n}\n\nfunction needsRefresh(): boolean {\n if (!state.isToken) return false\n if (!state.token) return true\n return Date.now() >= tokenExp(state.token) - REFRESH_SKEW_MS\n}\n\nasync function doRefresh(): Promise<string | null> {\n const current = state.token\n if (!current) return null\n try {\n const res = await fetch(`${state.apiUrl}/api/public/site-token/refresh`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n // Send the current token as x-api-key too, so the server's key-scoped\n // throttler buckets refreshes by token rather than by shared CDN IP.\n 'x-api-key': current,\n },\n body: JSON.stringify({ token: current }),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(REFRESH_TIMEOUT_MS)\n : undefined,\n })\n if (!res.ok) return null\n const data = await res.json().catch(() => null)\n const next = data?.token\n if (typeof next === 'string' && isSiteToken(next)) {\n state.token = next\n storeToken(next)\n return next\n }\n return null\n } catch {\n return null\n }\n}\n\n/** Refresh now (deduped). Returns the new token, or null on failure. */\nexport function refreshSiteToken(): Promise<string | null> {\n if (!state.isToken) return Promise.resolve(null)\n if (state.refreshing) return state.refreshing\n state.refreshing = doRefresh().finally(() => {\n state.refreshing = null\n })\n return state.refreshing\n}\n\n/**\n * The credential to put on the next request. In token mode, refreshes a stale\n * seed first. In raw-key mode, returns the seed unchanged (legacy passthrough).\n */\nexport async function getFreshCredential(\n seed?: string,\n): Promise<string | undefined> {\n // Unconfigured but handed a token (a component received the prerendered seed\n // as a prop before any provider configured the manager): adopt it so the\n // refresh machinery works instead of replaying a possibly-expired seed.\n if (!state.isToken && isSiteToken(seed)) {\n configureSiteCredential({ credential: seed })\n }\n // A raw key (or nothing configured) — no token machinery.\n if (!state.isToken) return seed ?? state.seed\n if (needsRefresh()) {\n const refreshed = await refreshSiteToken()\n if (refreshed) return refreshed\n // Refresh failed — fall through to whatever we have; the request may 401 and\n // trip the breaker with friendly copy.\n }\n return state.token ?? seed ?? state.seed\n}\n\n/** Whether the given credential is being managed as a token. */\nexport function isTokenMode(): boolean {\n return state.isToken\n}\n\n/**\n * Best-known credential WITHOUT refreshing — for synchronous unload paths\n * (sonorBeacon) that can't await. In token mode returns the freshest token held;\n * in raw-key mode returns the seed.\n */\nexport function currentCredential(seed?: string): string | undefined {\n if (!state.isToken) return seed ?? state.seed\n return state.token ?? seed ?? state.seed\n}\n"]}
|
|
@@ -535,7 +535,7 @@ async function BlogPost({
|
|
|
535
535
|
...item,
|
|
536
536
|
answer: addExternalLinkTargets(item.answer || "", siteUrlResolved)
|
|
537
537
|
})) ?? faqRaw;
|
|
538
|
-
const { BlogViewTracker } = await import('./BlogViewTracker-
|
|
538
|
+
const { BlogViewTracker } = await import('./BlogViewTracker-D4DIEP4Q.js');
|
|
539
539
|
if (children) {
|
|
540
540
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
541
541
|
/* @__PURE__ */ jsxRuntime.jsx(BlogViewTracker, { slug: post.slug }),
|
|
@@ -1379,5 +1379,5 @@ exports.getAuthorSocialLinks = getAuthorSocialLinks;
|
|
|
1379
1379
|
exports.normalizeSiteHost = normalizeSiteHost;
|
|
1380
1380
|
exports.readingTimeMinutes = readingTimeMinutes;
|
|
1381
1381
|
exports.resolveBlogSiteUrl = resolveBlogSiteUrl;
|
|
1382
|
-
//# sourceMappingURL=chunk-
|
|
1383
|
-
//# sourceMappingURL=chunk-
|
|
1382
|
+
//# sourceMappingURL=chunk-HX3BRMOX.js.map
|
|
1383
|
+
//# sourceMappingURL=chunk-HX3BRMOX.js.map
|