@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
|
@@ -212,5 +212,5 @@ exports.getVisibilityScore = getVisibilityScore;
|
|
|
212
212
|
exports.getVisibilitySummary = getVisibilitySummary;
|
|
213
213
|
exports.recordABImpression = recordABImpression;
|
|
214
214
|
exports.registerSitemap = registerSitemap;
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
216
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-JNRHTZL4.js.map
|
|
216
|
+
//# sourceMappingURL=chunk-JNRHTZL4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/seo/server-api.ts"],"names":["requireServerApiConfig","serverApiFetch","cache"],"mappings":"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","file":"chunk-3TXX5KIN.js","sourcesContent":["/**\n * @sonordev/site-kit/seo - Server-Only API Functions\n * \n * SECURITY: These functions use private environment variables\n * and should ONLY be imported in server-side code (RSC, API routes, server actions).\n * \n * DO NOT import this file in client components or it will expose API keys.\n */\n\nimport { cache } from 'react'\nimport 'server-only' // This ensures the module can only be imported server-side\nimport { requireServerApiConfig, serverApiFetch } from '../server/server-fetch'\n\n// ============================================\n// Server-Only API Config\n// ============================================\n\nconst SEO_LOG_PREFIX = '@sonordev/seo'\n\n/**\n * Config for portal API calls. Project is resolved by the portal from the API key;\n * sites never send or read project ID.\n *\n * Delegates to the shared `requireServerApiConfig` (single source of truth for\n * server API config — see `../server/server-fetch`) with an SEO-specific message.\n */\nfunction getSecureApiConfig() {\n return requireServerApiConfig(\n {},\n '@sonordev/seo: SONOR_API_KEY environment variable is required for server-side SEO functions',\n )\n}\n\n/** Derive project ID from API key for Signal API only (format: sonor_{uuid}_{secret}). Not used for portal. */\nfunction getProjectIdFromKey(apiKey: string): string | null {\n const match = apiKey.match(/^sonor_([0-9a-f-]{36})_/)\n return match ? match[1] : null\n}\n\n/**\n * POST to the Portal public API. Retries transient 429/502/503 with backoff\n * (parallel SSG workers behind one egress IP). 24h default revalidate — SEO data\n * rarely changes; Portal can on-demand revalidate. Throws if no API key is set.\n *\n * Thin wrapper over the shared `serverApiFetch` so the retry/backoff lives in one\n * place (see `../server/server-fetch`).\n */\nfunction secureApiPost<T>(endpoint: string, body: Record<string, any> = {}, revalidate = 86400): Promise<T | null> {\n return serverApiFetch<T>(endpoint, {\n method: 'POST',\n body,\n revalidate,\n requireKey: true,\n retry: true,\n logPrefix: SEO_LOG_PREFIX,\n })\n}\n\n/** GET from the Portal public API (single attempt, 24h revalidate). Throws if no API key is set. */\nfunction secureApiGet<T>(endpoint: string): Promise<T | null> {\n return serverApiFetch<T>(endpoint, {\n method: 'GET',\n revalidate: 86400,\n requireKey: true,\n retry: false,\n logPrefix: SEO_LOG_PREFIX,\n })\n}\n\n// ============================================\n// Secure Cached Data Fetchers\n// ============================================\n\n/** Response shape for POST /api/public/seo/page-context */\nexport type PageSeoContext = {\n page: any | null\n schemas: any[]\n project: {\n id: string\n title?: string | null\n domain?: string | null\n logo_url?: string | null\n site_url?: string | null\n } | null\n}\n\n/**\n * One Portal round-trip per path (page row + schemas + project).\n * React cache() dedupes when getSEOPageData + getSchemaMarkups run in the same render (e.g. metadata + ManagedSchema).\n * @server-only\n */\nconst loadPageSeoContext = cache(async (path: string): Promise<PageSeoContext | null> => {\n const result = await secureApiPost<PageSeoContext>('/api/public/seo/page-context', { path })\n if (!result) return null\n return {\n page: result.page ?? null,\n schemas: Array.isArray(result.schemas) ? result.schemas : [],\n project: result.project ?? null,\n }\n})\n\n/**\n * Fetch SEO page data - cached per request\n * @server-only\n */\nexport const getSEOPageData = cache(async (path: string) => {\n const ctx = await loadPageSeoContext(path)\n return {\n page: ctx?.page ?? null,\n project: ctx?.project ?? null,\n }\n})\n\n/**\n * Fetch schema markups for a page - cached per request (shares loadPageSeoContext with getSEOPageData)\n * @server-only\n */\nexport const getSchemaMarkups = cache(async (\n path: string,\n options?: { includeTypes?: string[]; excludeTypes?: string[] }\n) => {\n const ctx = await loadPageSeoContext(path)\n let schemas = ctx?.schemas ?? []\n if (options?.includeTypes?.length) {\n const allow = new Set(options.includeTypes)\n schemas = schemas.filter((s: { schema_type?: string }) => s?.schema_type && allow.has(s.schema_type))\n }\n if (options?.excludeTypes?.length) {\n const deny = new Set(options.excludeTypes)\n schemas = schemas.filter((s: { schema_type?: string }) => !s?.schema_type || !deny.has(s.schema_type))\n }\n return schemas\n})\n\n/**\n * Fetch FAQ data for a page - cached per request\n * @server-only\n */\nexport const getFAQData = cache(async (path: string) => {\n const result = await secureApiPost<{ faq: any }>('/api/public/seo/faq', { path })\n return result?.faq || null\n})\n\n/**\n * Fetch internal links for a page - cached per request\n * @server-only\n */\nexport const getInternalLinks = cache(async (\n sourcePath: string,\n options?: { position?: string; limit?: number }\n) => {\n const result = await secureApiPost<{ links: any[] }>('/api/public/seo/internal-links', {\n sourcePath,\n position: options?.position,\n limit: options?.limit,\n })\n return result?.links || []\n})\n\n/**\n * Fetch content block - cached per request\n * @server-only\n */\nexport const getContentBlock = cache(async (path: string, section: string) => {\n const result = await secureApiPost<{ content: any }>('/api/public/seo/content', { path, section })\n return result?.content || null\n})\n\n/**\n * Fetch A/B test and determine variant - cached per request\n * @server-only\n */\nexport const getABTest = cache(async (path: string, field: string) => {\n const result = await secureApiPost<{ test: any }>('/api/public/seo/ab-test', { path, field })\n return result?.test || null\n})\n\n/**\n * Record A/B test impression\n * @server-only\n */\nexport async function recordABImpression(\n testId: string,\n variant: 'a' | 'b',\n sessionId?: string\n): Promise<void> {\n await secureApiPost('/api/public/seo/ab-impression', { testId, variant, sessionId })\n}\n\n/**\n * Fetch redirect for a path - cached per request\n * @server-only\n */\nexport const getRedirectData = cache(async (path: string) => {\n const result = await secureApiPost<{ redirect: any }>('/api/public/seo/redirect', { path })\n return result?.redirect || null\n})\n\n/**\n * Fetch managed scripts - cached per request\n * @server-only\n */\nexport const getManagedScripts = cache(async (position: string, currentPath?: string) => {\n const result = await secureApiPost<{ scripts: any[] }>('/api/public/seo/scripts', {\n position,\n currentPath,\n })\n return result?.scripts || []\n})\n\n/**\n * Fetch robots directive for a page - cached per request\n * @server-only\n */\nexport const getRobotsData = cache(async (path: string) => {\n const { page } = await getSEOPageData(path)\n return page?.managed_robots || null\n})\n\n/**\n * Fetch sitemap entries - cached per request\n * @server-only\n */\nexport const getSitemapEntries = cache(async (options?: { publishedOnly?: boolean }) => {\n const result = await secureApiPost<{ entries: any[] }>('/api/public/seo/sitemap', {\n publishedOnly: options?.publishedOnly,\n })\n return result?.entries || []\n})\n\n/**\n * Register/sync sitemap entries from the client site\n * Call this at build time to populate seo_pages from your sitemap.xml\n * @server-only\n * \n * @example\n * ```ts\n * // scripts/register-sitemap.ts (run at build time)\n * import { registerSitemap } from '@sonordev/seo/server'\n * \n * await registerSitemap([\n * { path: '/', priority: 1.0, changefreq: 'daily' },\n * { path: '/about', priority: 0.8, changefreq: 'weekly' },\n * ])\n * ```\n */\nexport async function registerSitemap(\n entries: Array<{\n path: string\n title?: string\n priority?: number\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n }>,\n options?: {\n /**\n * Sync mode:\n * - 'full-replace': replaces the entire page set (default for build-time sync)\n * - 'additive': adds/updates pages but never deletes existing ones\n */\n mode?: 'additive' | 'full-replace'\n }\n): Promise<{ success: boolean; created: number; updated: number }> {\n const { apiUrl, apiKey } = getSecureApiConfig()\n const mode = options?.mode ?? 'full-replace'\n\n try {\n const response = await fetch(`${apiUrl}/api/public/seo/register-sitemap`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n body: JSON.stringify({ entries, mode }),\n })\n \n if (!response.ok) {\n console.error(`@sonordev/seo: Sitemap registration failed: ${response.statusText}`)\n return { success: false, created: 0, updated: 0 }\n }\n \n return await response.json()\n } catch (error) {\n console.error('@sonordev/seo: Sitemap registration error:', error)\n return { success: false, created: 0, updated: 0 }\n }\n}\n\n// ============================================\n// Signal-Powered Metadata Fallback\n// ============================================\n\n/**\n * Ensure metadata exists for a page by triggering Signal AI optimization.\n * If the page has no managed_title or managed_meta_description, Signal will\n * generate them and save to the database via Portal proxy.\n *\n * Idempotent — returns existing metadata if already populated.\n * @server-only\n */\nexport const ensureMetadata = cache(async (path: string): Promise<{\n managed_title?: string\n managed_meta_description?: string\n managed_keywords?: string[]\n managed_schema?: Record<string, unknown>\n} | null> => {\n return secureApiPost('/api/public/seo/ensure-metadata', { path })\n})\n\n// ============================================\n// AI Visibility & Entity Graph API (Signal)\n// ============================================\n\nfunction getSignalApiConfig() {\n const apiUrl = process.env.SONOR_SIGNAL_URL || process.env.SIGNAL_API_URL || process.env.NEXT_PUBLIC_SIGNAL_API_URL || 'https://signal.sonor.io'\n const apiKey = process.env.SONOR_API_KEY || ''\n return { apiUrl, apiKey }\n}\n\nasync function signalApiGet<T>(endpoint: string): Promise<T | null> {\n const { apiUrl, apiKey } = getSignalApiConfig()\n \n if (!apiKey) {\n return null\n }\n \n try {\n const response = await fetch(`${apiUrl}${endpoint}`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 300 }, // Cache for 5 minutes\n })\n \n if (!response.ok) {\n return null\n }\n \n const result = await response.json()\n return result?.data || result\n } catch (error) {\n console.error('@sonordev/seo: Signal API error:', error)\n return null\n }\n}\n\n/**\n * Entity types for the knowledge graph\n */\nexport type EntityType = \n | 'organization'\n | 'person'\n | 'service'\n | 'product'\n | 'location'\n | 'concept'\n | 'credential'\n\n/**\n * Entity from the knowledge graph\n */\nexport interface SEOEntity {\n id: string\n project_id: string\n entity_type: EntityType\n name: string\n slug: string\n properties: Record<string, unknown>\n knows_about: string[]\n same_as: string[]\n schema_type?: string\n is_primary: boolean\n}\n\n/**\n * Fetch entities for a project - cached per request\n * Returns the entity graph for enhanced schema markup\n * @server-only\n */\nexport const getEntities = cache(async (\n options?: { type?: EntityType }\n): Promise<SEOEntity[]> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return []\n let endpoint = `/skills/seo/entities/${projectId}`\n if (options?.type) {\n endpoint += `?type=${options.type}`\n }\n const result = await signalApiGet<SEOEntity[]>(endpoint)\n return result || []\n})\n\n/**\n * Fetch primary entity (the business) - cached per request\n * @server-only\n */\nexport const getPrimaryEntity = cache(async (): Promise<SEOEntity | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n return signalApiGet<SEOEntity>(`/skills/seo/entities/${projectId}/primary`)\n})\n\n/**\n * Fetch entity-enhanced schema for a page\n * Returns Organization schema with knowsAbout, areaServed, employee, etc.\n * @server-only\n */\nexport const getEntityEnhancedSchema = cache(async (pagePath: string): Promise<object[]> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return []\n const result = await signalApiGet<{ schemas: object[] }>(\n `/skills/seo/schema/${projectId}/entity-enhanced?pagePath=${encodeURIComponent(pagePath)}`\n )\n return result?.schemas || []\n})\n\n/**\n * Get AI visibility score for a page\n * @server-only\n */\nexport const getVisibilityScore = cache(async (\n pagePath: string\n): Promise<{\n overall_score: number\n entity_coverage: number\n answer_density: number\n chunk_readability: number\n authority_signals: number\n schema_completeness: number\n} | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n const result = await signalApiGet<any[]>(`/skills/seo/visibility/${projectId}`)\n if (!result) return null\n return result.find(s => s.page_path === pagePath) || null\n})\n\n/**\n * Get AI visibility summary for project\n * @server-only\n */\nexport const getVisibilitySummary = cache(async (): Promise<{\n overall_score: number\n total_entities: number\n pages_analyzed: number\n top_recommendations: Array<{ priority: string; type: string; message: string }>\n} | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n return signalApiGet(`/skills/seo/visibility/${projectId}/summary`)\n})\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/seo/server-api.ts"],"names":["requireServerApiConfig","serverApiFetch","cache"],"mappings":"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","file":"chunk-JNRHTZL4.js","sourcesContent":["/**\n * @sonordev/site-kit/seo - Server-Only API Functions\n * \n * SECURITY: These functions use private environment variables\n * and should ONLY be imported in server-side code (RSC, API routes, server actions).\n * \n * DO NOT import this file in client components or it will expose API keys.\n */\n\nimport { cache } from 'react'\nimport 'server-only' // This ensures the module can only be imported server-side\nimport { requireServerApiConfig, serverApiFetch } from '../server/server-fetch'\n\n// ============================================\n// Server-Only API Config\n// ============================================\n\nconst SEO_LOG_PREFIX = '@sonordev/seo'\n\n/**\n * Config for portal API calls. Project is resolved by the portal from the API key;\n * sites never send or read project ID.\n *\n * Delegates to the shared `requireServerApiConfig` (single source of truth for\n * server API config — see `../server/server-fetch`) with an SEO-specific message.\n */\nfunction getSecureApiConfig() {\n return requireServerApiConfig(\n {},\n '@sonordev/seo: SONOR_API_KEY environment variable is required for server-side SEO functions',\n )\n}\n\n/** Derive project ID from API key for Signal API only (format: sonor_{uuid}_{secret}). Not used for portal. */\nfunction getProjectIdFromKey(apiKey: string): string | null {\n const match = apiKey.match(/^sonor_([0-9a-f-]{36})_/)\n return match ? match[1] : null\n}\n\n/**\n * POST to the Portal public API. Retries transient 429/502/503 with backoff\n * (parallel SSG workers behind one egress IP). 24h default revalidate — SEO data\n * rarely changes; Portal can on-demand revalidate. Throws if no API key is set.\n *\n * Thin wrapper over the shared `serverApiFetch` so the retry/backoff lives in one\n * place (see `../server/server-fetch`).\n */\nfunction secureApiPost<T>(endpoint: string, body: Record<string, any> = {}, revalidate = 86400): Promise<T | null> {\n return serverApiFetch<T>(endpoint, {\n method: 'POST',\n body,\n revalidate,\n requireKey: true,\n retry: true,\n logPrefix: SEO_LOG_PREFIX,\n })\n}\n\n/** GET from the Portal public API (single attempt, 24h revalidate). Throws if no API key is set. */\nfunction secureApiGet<T>(endpoint: string): Promise<T | null> {\n return serverApiFetch<T>(endpoint, {\n method: 'GET',\n revalidate: 86400,\n requireKey: true,\n retry: false,\n logPrefix: SEO_LOG_PREFIX,\n })\n}\n\n// ============================================\n// Secure Cached Data Fetchers\n// ============================================\n\n/** Response shape for POST /api/public/seo/page-context */\nexport type PageSeoContext = {\n page: any | null\n schemas: any[]\n project: {\n id: string\n title?: string | null\n domain?: string | null\n logo_url?: string | null\n site_url?: string | null\n } | null\n}\n\n/**\n * One Portal round-trip per path (page row + schemas + project).\n * React cache() dedupes when getSEOPageData + getSchemaMarkups run in the same render (e.g. metadata + ManagedSchema).\n * @server-only\n */\nconst loadPageSeoContext = cache(async (path: string): Promise<PageSeoContext | null> => {\n const result = await secureApiPost<PageSeoContext>('/api/public/seo/page-context', { path })\n if (!result) return null\n return {\n page: result.page ?? null,\n schemas: Array.isArray(result.schemas) ? result.schemas : [],\n project: result.project ?? null,\n }\n})\n\n/**\n * Fetch SEO page data - cached per request\n * @server-only\n */\nexport const getSEOPageData = cache(async (path: string) => {\n const ctx = await loadPageSeoContext(path)\n return {\n page: ctx?.page ?? null,\n project: ctx?.project ?? null,\n }\n})\n\n/**\n * Fetch schema markups for a page - cached per request (shares loadPageSeoContext with getSEOPageData)\n * @server-only\n */\nexport const getSchemaMarkups = cache(async (\n path: string,\n options?: { includeTypes?: string[]; excludeTypes?: string[] }\n) => {\n const ctx = await loadPageSeoContext(path)\n let schemas = ctx?.schemas ?? []\n if (options?.includeTypes?.length) {\n const allow = new Set(options.includeTypes)\n schemas = schemas.filter((s: { schema_type?: string }) => s?.schema_type && allow.has(s.schema_type))\n }\n if (options?.excludeTypes?.length) {\n const deny = new Set(options.excludeTypes)\n schemas = schemas.filter((s: { schema_type?: string }) => !s?.schema_type || !deny.has(s.schema_type))\n }\n return schemas\n})\n\n/**\n * Fetch FAQ data for a page - cached per request\n * @server-only\n */\nexport const getFAQData = cache(async (path: string) => {\n const result = await secureApiPost<{ faq: any }>('/api/public/seo/faq', { path })\n return result?.faq || null\n})\n\n/**\n * Fetch internal links for a page - cached per request\n * @server-only\n */\nexport const getInternalLinks = cache(async (\n sourcePath: string,\n options?: { position?: string; limit?: number }\n) => {\n const result = await secureApiPost<{ links: any[] }>('/api/public/seo/internal-links', {\n sourcePath,\n position: options?.position,\n limit: options?.limit,\n })\n return result?.links || []\n})\n\n/**\n * Fetch content block - cached per request\n * @server-only\n */\nexport const getContentBlock = cache(async (path: string, section: string) => {\n const result = await secureApiPost<{ content: any }>('/api/public/seo/content', { path, section })\n return result?.content || null\n})\n\n/**\n * Fetch A/B test and determine variant - cached per request\n * @server-only\n */\nexport const getABTest = cache(async (path: string, field: string) => {\n const result = await secureApiPost<{ test: any }>('/api/public/seo/ab-test', { path, field })\n return result?.test || null\n})\n\n/**\n * Record A/B test impression\n * @server-only\n */\nexport async function recordABImpression(\n testId: string,\n variant: 'a' | 'b',\n sessionId?: string\n): Promise<void> {\n await secureApiPost('/api/public/seo/ab-impression', { testId, variant, sessionId })\n}\n\n/**\n * Fetch redirect for a path - cached per request\n * @server-only\n */\nexport const getRedirectData = cache(async (path: string) => {\n const result = await secureApiPost<{ redirect: any }>('/api/public/seo/redirect', { path })\n return result?.redirect || null\n})\n\n/**\n * Fetch managed scripts - cached per request\n * @server-only\n */\nexport const getManagedScripts = cache(async (position: string, currentPath?: string) => {\n const result = await secureApiPost<{ scripts: any[] }>('/api/public/seo/scripts', {\n position,\n currentPath,\n })\n return result?.scripts || []\n})\n\n/**\n * Fetch robots directive for a page - cached per request\n * @server-only\n */\nexport const getRobotsData = cache(async (path: string) => {\n const { page } = await getSEOPageData(path)\n return page?.managed_robots || null\n})\n\n/**\n * Fetch sitemap entries - cached per request\n * @server-only\n */\nexport const getSitemapEntries = cache(async (options?: { publishedOnly?: boolean }) => {\n const result = await secureApiPost<{ entries: any[] }>('/api/public/seo/sitemap', {\n publishedOnly: options?.publishedOnly,\n })\n return result?.entries || []\n})\n\n/**\n * Register/sync sitemap entries from the client site\n * Call this at build time to populate seo_pages from your sitemap.xml\n * @server-only\n *\n * Defaults to mode 'full-replace', which PRUNES every page missing from\n * `entries`. Pass `{ mode: 'additive' }` unless the list is the whole site.\n *\n * @example\n * ```ts\n * // scripts/register-sitemap.ts (run at build time)\n * import { registerSitemap } from '@sonordev/site-kit/seo/server'\n *\n * await registerSitemap(\n * [\n * { path: '/', priority: 1.0, changefreq: 'daily' },\n * { path: '/about', priority: 0.8, changefreq: 'weekly' },\n * ],\n * { mode: 'additive' },\n * )\n * ```\n */\nexport async function registerSitemap(\n entries: Array<{\n path: string\n title?: string\n priority?: number\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n }>,\n options?: {\n /**\n * Sync mode:\n * - 'full-replace': replaces the entire page set (default for build-time sync)\n * - 'additive': adds/updates pages but never deletes existing ones\n */\n mode?: 'additive' | 'full-replace'\n }\n): Promise<{ success: boolean; created: number; updated: number }> {\n const { apiUrl, apiKey } = getSecureApiConfig()\n const mode = options?.mode ?? 'full-replace'\n\n try {\n const response = await fetch(`${apiUrl}/api/public/seo/register-sitemap`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n body: JSON.stringify({ entries, mode }),\n })\n \n if (!response.ok) {\n console.error(`@sonordev/seo: Sitemap registration failed: ${response.statusText}`)\n return { success: false, created: 0, updated: 0 }\n }\n \n return await response.json()\n } catch (error) {\n console.error('@sonordev/seo: Sitemap registration error:', error)\n return { success: false, created: 0, updated: 0 }\n }\n}\n\n// ============================================\n// Signal-Powered Metadata Fallback\n// ============================================\n\n/**\n * Ensure metadata exists for a page by triggering Signal AI optimization.\n * If the page has no managed_title or managed_meta_description, Signal will\n * generate them and save to the database via Portal proxy.\n *\n * Idempotent — returns existing metadata if already populated.\n * @server-only\n */\nexport const ensureMetadata = cache(async (path: string): Promise<{\n managed_title?: string\n managed_meta_description?: string\n managed_keywords?: string[]\n managed_schema?: Record<string, unknown>\n} | null> => {\n return secureApiPost('/api/public/seo/ensure-metadata', { path })\n})\n\n// ============================================\n// AI Visibility & Entity Graph API (Signal)\n// ============================================\n\nfunction getSignalApiConfig() {\n const apiUrl = process.env.SONOR_SIGNAL_URL || process.env.SIGNAL_API_URL || process.env.NEXT_PUBLIC_SIGNAL_API_URL || 'https://signal.sonor.io'\n const apiKey = process.env.SONOR_API_KEY || ''\n return { apiUrl, apiKey }\n}\n\nasync function signalApiGet<T>(endpoint: string): Promise<T | null> {\n const { apiUrl, apiKey } = getSignalApiConfig()\n \n if (!apiKey) {\n return null\n }\n \n try {\n const response = await fetch(`${apiUrl}${endpoint}`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 300 }, // Cache for 5 minutes\n })\n \n if (!response.ok) {\n return null\n }\n \n const result = await response.json()\n return result?.data || result\n } catch (error) {\n console.error('@sonordev/seo: Signal API error:', error)\n return null\n }\n}\n\n/**\n * Entity types for the knowledge graph\n */\nexport type EntityType = \n | 'organization'\n | 'person'\n | 'service'\n | 'product'\n | 'location'\n | 'concept'\n | 'credential'\n\n/**\n * Entity from the knowledge graph\n */\nexport interface SEOEntity {\n id: string\n project_id: string\n entity_type: EntityType\n name: string\n slug: string\n properties: Record<string, unknown>\n knows_about: string[]\n same_as: string[]\n schema_type?: string\n is_primary: boolean\n}\n\n/**\n * Fetch entities for a project - cached per request\n * Returns the entity graph for enhanced schema markup\n * @server-only\n */\nexport const getEntities = cache(async (\n options?: { type?: EntityType }\n): Promise<SEOEntity[]> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return []\n let endpoint = `/skills/seo/entities/${projectId}`\n if (options?.type) {\n endpoint += `?type=${options.type}`\n }\n const result = await signalApiGet<SEOEntity[]>(endpoint)\n return result || []\n})\n\n/**\n * Fetch primary entity (the business) - cached per request\n * @server-only\n */\nexport const getPrimaryEntity = cache(async (): Promise<SEOEntity | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n return signalApiGet<SEOEntity>(`/skills/seo/entities/${projectId}/primary`)\n})\n\n/**\n * Fetch entity-enhanced schema for a page\n * Returns Organization schema with knowsAbout, areaServed, employee, etc.\n * @server-only\n */\nexport const getEntityEnhancedSchema = cache(async (pagePath: string): Promise<object[]> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return []\n const result = await signalApiGet<{ schemas: object[] }>(\n `/skills/seo/schema/${projectId}/entity-enhanced?pagePath=${encodeURIComponent(pagePath)}`\n )\n return result?.schemas || []\n})\n\n/**\n * Get AI visibility score for a page\n * @server-only\n */\nexport const getVisibilityScore = cache(async (\n pagePath: string\n): Promise<{\n overall_score: number\n entity_coverage: number\n answer_density: number\n chunk_readability: number\n authority_signals: number\n schema_completeness: number\n} | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n const result = await signalApiGet<any[]>(`/skills/seo/visibility/${projectId}`)\n if (!result) return null\n return result.find(s => s.page_path === pagePath) || null\n})\n\n/**\n * Get AI visibility summary for project\n * @server-only\n */\nexport const getVisibilitySummary = cache(async (): Promise<{\n overall_score: number\n total_entities: number\n pages_analyzed: number\n top_recommendations: Array<{ priority: string; type: string; message: string }>\n} | null> => {\n const { apiKey } = getSecureApiConfig()\n const projectId = getProjectIdFromKey(apiKey)\n if (!projectId) return null\n return signalApiGet(`/skills/seo/visibility/${projectId}/summary`)\n})\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
var chunkJEOKDUPQ_js = require('./chunk-JEOKDUPQ.js');
|
|
5
5
|
var chunkOL2QBWAU_js = require('./chunk-OL2QBWAU.js');
|
|
6
|
-
var
|
|
6
|
+
var chunkQEGUI2Q2_js = require('./chunk-QEGUI2Q2.js');
|
|
7
7
|
var react = require('react');
|
|
8
8
|
|
|
9
9
|
var SYNC_STAMP_KEY = "_sk_sitemap_synced_at";
|
|
@@ -54,7 +54,7 @@ function getApiConfig() {
|
|
|
54
54
|
}
|
|
55
55
|
async function parseSitemap(sitemapUrl, debug) {
|
|
56
56
|
try {
|
|
57
|
-
const response = await
|
|
57
|
+
const response = await chunkQEGUI2Q2_js.sonorFetch(sitemapUrl);
|
|
58
58
|
if (!response.ok) {
|
|
59
59
|
if (debug) console.warn("[SitemapSync] Failed to fetch sitemap:", response.status);
|
|
60
60
|
return [];
|
|
@@ -102,7 +102,7 @@ async function syncToPortal(entries, apiUrl, apiKey, site, debug, mode = "additi
|
|
|
102
102
|
return { success: true, created: 0, updated: 0 };
|
|
103
103
|
}
|
|
104
104
|
try {
|
|
105
|
-
const response = await
|
|
105
|
+
const response = await chunkQEGUI2Q2_js.sonorFetch(`${apiUrl}/api/public/seo/register-sitemap`, {
|
|
106
106
|
method: "POST",
|
|
107
107
|
headers: {
|
|
108
108
|
"Content-Type": "application/json"
|
|
@@ -197,5 +197,5 @@ function SitemapSync({
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
exports.SitemapSync = SitemapSync;
|
|
200
|
-
//# sourceMappingURL=chunk-
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
200
|
+
//# sourceMappingURL=chunk-KAUUGRYB.js.map
|
|
201
|
+
//# sourceMappingURL=chunk-KAUUGRYB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/SitemapSync.tsx"],"names":["sonorFetch","locToPath","isMalformedSitemapPath","inferPageType","useRef","useEffect","isCrossOriginFrame"],"mappings":"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","file":"chunk-U53KN4SA.js","sourcesContent":["/**\n * SitemapSync - Automatically sync sitemap.xml to Portal API\n *\n * Client-only component. Lives at package root so the main entry does not\n * pull in seo/ (and server-only code). For use in SiteKitLayout and\n * from @sonordev/site-kit/seo.\n *\n * @example\n * ```tsx\n * import { SitemapSync } from '@sonordev/site-kit'\n * // or\n * import { SitemapSync } from '@sonordev/site-kit/seo'\n * ```\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { sonorFetch } from './shared/sonorFetch'\n// Same frame fact the analytics send gate uses — see shared/frame.ts.\nimport { isCrossOriginFrame } from './shared/frame'\n// Single source of truth for page-type inference + path normalization, shared\n// with createSitemap (build) and the register-sitemap CLI. Pure string logic,\n// safe in the browser.\nimport { inferPageType, locToPath, isMalformedSitemapPath } from './sitemap/shared'\n\ninterface SitemapSyncProps {\n /** Custom sitemap URL (defaults to /sitemap.xml) */\n sitemapUrl?: string\n /** How often to re-sync in minutes (0 = only on mount, default: 60) */\n syncInterval?: number\n /** Enable debug logging */\n debug?: boolean\n /**\n * Sync mode:\n * - 'additive' (default for runtime): adds/updates pages but never deletes existing ones\n * - 'full-replace': replaces the entire page set (use only for build-time sync via createSitemap)\n */\n mode?: 'additive' | 'full-replace'\n}\n\ninterface SitemapEntry {\n path: string\n lastmod?: string\n changefreq?: string\n priority?: number\n pageType?: string\n}\n\n/**\n * Per-browser-session sync throttle. Build-time sync (createSitemap) is the\n * canonical source of truth; runtime SitemapSync is only a safety net — so\n * re-registering the sitemap on every full page load of every visitor is pure\n * waste (fetch + DOMParser on the visitor's main thread, plus a write per\n * pageview against the API). Stamp the last successful sync in sessionStorage\n * and skip while inside the throttle window.\n */\nconst SYNC_STAMP_KEY = '_sk_sitemap_synced_at'\nconst SYNC_HASH_KEY = '_sk_sitemap_hash'\n\nfunction isRecentlySynced(throttleMinutes: number): boolean {\n try {\n const stamp = sessionStorage.getItem(SYNC_STAMP_KEY)\n if (!stamp) return false\n const elapsed = Date.now() - parseInt(stamp, 10)\n return elapsed >= 0 && elapsed < throttleMinutes * 60 * 1000\n } catch {\n return false // storage unavailable (private mode) — sync as before\n }\n}\n\nfunction stampSynced(): void {\n try {\n sessionStorage.setItem(SYNC_STAMP_KEY, Date.now().toString())\n } catch {\n /* storage unavailable — throttle simply won't apply */\n }\n}\n\n/**\n * Content-hash guard (localStorage, so it survives across sessions): the\n * sitemap of a static site only changes at deploy, so once ONE sync of a given\n * sitemap succeeded from this browser, re-POSTing the identical entry set is\n * pure API load. Hash the parsed entries; skip the POST when unchanged.\n * Keyed by sitemap URL so multiple SitemapSync instances don't collide.\n */\nfunction hashEntries(sitemapUrl: string, entries: SitemapEntry[]): string {\n const text = entries\n .map(e => `${e.path}|${e.lastmod ?? ''}|${e.changefreq ?? ''}|${e.priority ?? ''}`)\n .join('\\n')\n let hash = 0\n for (let i = 0; i < text.length; i++) {\n hash = ((hash << 5) - hash + text.charCodeAt(i)) | 0\n }\n return `${sitemapUrl}:${entries.length}:${hash.toString(16)}`\n}\n\nfunction isAlreadySyncedHash(hash: string): boolean {\n try {\n return localStorage.getItem(SYNC_HASH_KEY) === hash\n } catch {\n return false\n }\n}\n\nfunction storeSyncedHash(hash: string): void {\n try {\n localStorage.setItem(SYNC_HASH_KEY, hash)\n } catch {\n /* storage unavailable — guard simply won't apply */\n }\n}\n\nfunction getApiConfig() {\n if (typeof window === 'undefined') return { apiUrl: '', apiKey: '', site: '' }\n\n const apiUrl = (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n const apiKey = (window as any).__SITE_KIT_API_KEY__ || ''\n // Sub-site host — set by SiteKitClientProviders from analytics.site config,\n // NEXT_PUBLIC_SITE_URL host, or window.location.host. Tagged on every\n // seo_pages row from this sitemap so the dashboard can scope the page tree\n // when one Sonor project hosts many sites.\n const site = (window as any).__SITE_KIT_SITE__ || window.location.host || ''\n return { apiUrl, apiKey, site }\n}\n\nasync function parseSitemap(sitemapUrl: string, debug: boolean): Promise<SitemapEntry[]> {\n try {\n const response = await sonorFetch(sitemapUrl)\n if (!response.ok) {\n if (debug) console.warn('[SitemapSync] Failed to fetch sitemap:', response.status)\n return []\n }\n\n const text = await response.text()\n const parser = new DOMParser()\n const doc = parser.parseFromString(text, 'application/xml')\n\n const parseError = doc.querySelector('parsererror')\n if (parseError) {\n if (debug) console.warn('[SitemapSync] Sitemap parse error:', parseError.textContent)\n return []\n }\n\n const urls = doc.querySelectorAll('url')\n const entries: SitemapEntry[] = []\n\n urls.forEach(url => {\n const loc = url.querySelector('loc')?.textContent?.trim()\n if (!loc) return\n\n const path = locToPath(loc)\n\n // Reject malformed paths (embedded domains, double protocols, etc.)\n if (isMalformedSitemapPath(path)) {\n if (debug) console.warn(`[SitemapSync] Skipping malformed path: ${path}`)\n return\n }\n\n const lastmod = url.querySelector('lastmod')?.textContent || undefined\n const changefreq = url.querySelector('changefreq')?.textContent || undefined\n const priorityStr = url.querySelector('priority')?.textContent\n const priority = priorityStr ? parseFloat(priorityStr) : undefined\n\n entries.push({ path, lastmod, changefreq, priority })\n })\n\n if (debug) {\n console.log(`[SitemapSync] Parsed ${entries.length} entries from sitemap`)\n }\n\n return entries\n } catch (error) {\n if (debug) console.error('[SitemapSync] Error parsing sitemap:', error)\n return []\n }\n}\n\nasync function syncToPortal(\n entries: SitemapEntry[],\n apiUrl: string,\n apiKey: string,\n site: string,\n debug: boolean,\n mode: 'additive' | 'full-replace' = 'additive'\n): Promise<{ success: boolean; created: number; updated: number }> {\n if (!apiKey) {\n if (debug) console.warn('[SitemapSync] No API key configured')\n return { success: false, created: 0, updated: 0 }\n }\n\n if (entries.length === 0) {\n if (debug) console.log('[SitemapSync] No entries to sync')\n return { success: true, created: 0, updated: 0 }\n }\n\n try {\n // Registration is idempotent in both modes (same payload → same final\n // state), so retrying a timed-out attempt is safe.\n const response = await sonorFetch(`${apiUrl}/api/public/seo/register-sitemap`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n entries: entries.map(e => ({\n path: e.path,\n priority: e.priority,\n changefreq: e.changefreq,\n pageType: inferPageType(e.path),\n })),\n mode,\n // Sub-site host — tagged on every seo_pages row so the dashboard\n // can scope the page tree to one microsite. Resolved by\n // SiteKitClientProviders (see __SITE_KIT_SITE__).\n ...(site ? { site } : {}),\n }),\n }, { apiKey })\n\n if (!response.ok) {\n if (debug) console.error('[SitemapSync] API error:', response.status, response.statusText)\n return { success: false, created: 0, updated: 0 }\n }\n\n const result = await response.json()\n\n if (debug) {\n console.log(`[SitemapSync] Synced: ${result.created} created, ${result.updated} updated`)\n }\n\n return {\n success: true,\n created: result.created || 0,\n updated: result.updated || 0,\n }\n } catch (error) {\n if (debug) console.error('[SitemapSync] Sync error:', error)\n return { success: false, created: 0, updated: 0 }\n }\n}\n\nexport function SitemapSync({\n sitemapUrl = '/sitemap.xml',\n syncInterval = 60,\n debug = false,\n mode = 'additive',\n}: SitemapSyncProps) {\n const hasSyncedRef = useRef(false)\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)\n\n useEffect(() => {\n // Build-time sync via createSitemap is the canonical source of truth, but\n // it can fail during ISR revalidation (app/ dir unavailable on serverless).\n // Runtime SitemapSync in additive mode is a safe fallback — it adds/updates\n // pages that may be missing without ever deleting existing ones.\n // Full-replace is never safe at runtime — always force additive.\n const effectiveMode: 'additive' | 'full-replace' = 'additive'\n\n // Throttle window: reuse syncInterval when periodic syncing is on,\n // otherwise default to 60 minutes for the on-mount sync.\n const throttleMinutes = syncInterval > 0 ? syncInterval : 60\n\n const doSync = async () => {\n // Never register a sitemap from inside someone else's page.\n //\n // The rows would not be mis-attributed — inside the frame this document\n // still resolves the framed site's own host and its own sitemap — but a\n // WRITE that a third party's page view triggers is wrong for two\n // reasons. It runs a fetch + DOMParser on a visitor's main thread in a\n // page that gets nothing from it; and it bumps `seo_pages.updated_at`,\n // which is a tiebreaker in `pickSeoPageRow`'s \"most recent\" fallback.\n // On a multi-site project that is how a shared path like `/` starts\n // resolving to a different host's row (see CLAUDE.md, the BD-AEC\n // llms.txt contamination) — a third party's traffic should not be able\n // to move which row wins.\n //\n // Same-origin frames sync as before; they are the site itself.\n if (isCrossOriginFrame()) {\n if (debug) console.log('[SitemapSync] Skipped — page is a cross-origin embed')\n return\n }\n\n const { apiUrl, apiKey, site } = getApiConfig()\n\n if (!apiKey) {\n if (debug) console.warn('[SitemapSync] No API key found')\n return\n }\n\n if (isRecentlySynced(throttleMinutes)) {\n if (debug) console.log('[SitemapSync] Skipped — synced within the last', throttleMinutes, 'min this session')\n return\n }\n\n if (debug) console.log('[SitemapSync] Starting sitemap sync...', site ? `(site: ${site})` : '')\n\n const entries = await parseSitemap(sitemapUrl, debug)\n\n if (entries.length > 0) {\n const hash = hashEntries(sitemapUrl, entries)\n if (isAlreadySyncedHash(hash)) {\n // This exact sitemap already synced from this browser — the server\n // state can't be improved by re-POSTing it. Stamp the session so we\n // don't even re-fetch/parse for the rest of the throttle window.\n stampSynced()\n if (debug) console.log('[SitemapSync] Skipped POST — sitemap unchanged since last successful sync')\n return\n }\n const result = await syncToPortal(entries, apiUrl, apiKey, site, debug, effectiveMode)\n if (result.success) {\n stampSynced()\n storeSyncedHash(hash)\n }\n }\n }\n\n if (!hasSyncedRef.current) {\n hasSyncedRef.current = true\n doSync()\n }\n\n if (syncInterval > 0) {\n intervalRef.current = setInterval(doSync, syncInterval * 60 * 1000)\n }\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n }\n }\n }, [sitemapUrl, syncInterval, debug, mode])\n\n return null\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/SitemapSync.tsx"],"names":["sonorFetch","locToPath","isMalformedSitemapPath","inferPageType","useRef","useEffect","isCrossOriginFrame"],"mappings":"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","file":"chunk-KAUUGRYB.js","sourcesContent":["/**\n * SitemapSync - Automatically sync sitemap.xml to Portal API\n *\n * Client-only component. Lives at package root so the main entry does not\n * pull in seo/ (and server-only code). For use in SiteKitLayout and\n * from @sonordev/site-kit/seo.\n *\n * @example\n * ```tsx\n * import { SitemapSync } from '@sonordev/site-kit'\n * // or\n * import { SitemapSync } from '@sonordev/site-kit/seo'\n * ```\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { sonorFetch } from './shared/sonorFetch'\n// Same frame fact the analytics send gate uses — see shared/frame.ts.\nimport { isCrossOriginFrame } from './shared/frame'\n// Single source of truth for page-type inference + path normalization, shared\n// with createSitemap (build) and the register-sitemap CLI. Pure string logic,\n// safe in the browser.\nimport { inferPageType, locToPath, isMalformedSitemapPath } from './sitemap/shared'\n\ninterface SitemapSyncProps {\n /** Custom sitemap URL (defaults to /sitemap.xml) */\n sitemapUrl?: string\n /** How often to re-sync in minutes (0 = only on mount, default: 60) */\n syncInterval?: number\n /** Enable debug logging */\n debug?: boolean\n /**\n * Sync mode:\n * - 'additive' (default for runtime): adds/updates pages but never deletes existing ones\n * - 'full-replace': replaces the entire page set (use only for build-time sync via createSitemap)\n */\n mode?: 'additive' | 'full-replace'\n}\n\ninterface SitemapEntry {\n path: string\n lastmod?: string\n changefreq?: string\n priority?: number\n pageType?: string\n}\n\n/**\n * Per-browser-session sync throttle. Build-time sync (createSitemap) is the\n * canonical source of truth; runtime SitemapSync is only a safety net — so\n * re-registering the sitemap on every full page load of every visitor is pure\n * waste (fetch + DOMParser on the visitor's main thread, plus a write per\n * pageview against the API). Stamp the last successful sync in sessionStorage\n * and skip while inside the throttle window.\n */\nconst SYNC_STAMP_KEY = '_sk_sitemap_synced_at'\nconst SYNC_HASH_KEY = '_sk_sitemap_hash'\n\nfunction isRecentlySynced(throttleMinutes: number): boolean {\n try {\n const stamp = sessionStorage.getItem(SYNC_STAMP_KEY)\n if (!stamp) return false\n const elapsed = Date.now() - parseInt(stamp, 10)\n return elapsed >= 0 && elapsed < throttleMinutes * 60 * 1000\n } catch {\n return false // storage unavailable (private mode) — sync as before\n }\n}\n\nfunction stampSynced(): void {\n try {\n sessionStorage.setItem(SYNC_STAMP_KEY, Date.now().toString())\n } catch {\n /* storage unavailable — throttle simply won't apply */\n }\n}\n\n/**\n * Content-hash guard (localStorage, so it survives across sessions): the\n * sitemap of a static site only changes at deploy, so once ONE sync of a given\n * sitemap succeeded from this browser, re-POSTing the identical entry set is\n * pure API load. Hash the parsed entries; skip the POST when unchanged.\n * Keyed by sitemap URL so multiple SitemapSync instances don't collide.\n */\nfunction hashEntries(sitemapUrl: string, entries: SitemapEntry[]): string {\n const text = entries\n .map(e => `${e.path}|${e.lastmod ?? ''}|${e.changefreq ?? ''}|${e.priority ?? ''}`)\n .join('\\n')\n let hash = 0\n for (let i = 0; i < text.length; i++) {\n hash = ((hash << 5) - hash + text.charCodeAt(i)) | 0\n }\n return `${sitemapUrl}:${entries.length}:${hash.toString(16)}`\n}\n\nfunction isAlreadySyncedHash(hash: string): boolean {\n try {\n return localStorage.getItem(SYNC_HASH_KEY) === hash\n } catch {\n return false\n }\n}\n\nfunction storeSyncedHash(hash: string): void {\n try {\n localStorage.setItem(SYNC_HASH_KEY, hash)\n } catch {\n /* storage unavailable — guard simply won't apply */\n }\n}\n\nfunction getApiConfig() {\n if (typeof window === 'undefined') return { apiUrl: '', apiKey: '', site: '' }\n\n const apiUrl = (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n const apiKey = (window as any).__SITE_KIT_API_KEY__ || ''\n // Sub-site host — set by SiteKitClientProviders from analytics.site config,\n // NEXT_PUBLIC_SITE_URL host, or window.location.host. Tagged on every\n // seo_pages row from this sitemap so the dashboard can scope the page tree\n // when one Sonor project hosts many sites.\n const site = (window as any).__SITE_KIT_SITE__ || window.location.host || ''\n return { apiUrl, apiKey, site }\n}\n\nasync function parseSitemap(sitemapUrl: string, debug: boolean): Promise<SitemapEntry[]> {\n try {\n const response = await sonorFetch(sitemapUrl)\n if (!response.ok) {\n if (debug) console.warn('[SitemapSync] Failed to fetch sitemap:', response.status)\n return []\n }\n\n const text = await response.text()\n const parser = new DOMParser()\n const doc = parser.parseFromString(text, 'application/xml')\n\n const parseError = doc.querySelector('parsererror')\n if (parseError) {\n if (debug) console.warn('[SitemapSync] Sitemap parse error:', parseError.textContent)\n return []\n }\n\n const urls = doc.querySelectorAll('url')\n const entries: SitemapEntry[] = []\n\n urls.forEach(url => {\n const loc = url.querySelector('loc')?.textContent?.trim()\n if (!loc) return\n\n const path = locToPath(loc)\n\n // Reject malformed paths (embedded domains, double protocols, etc.)\n if (isMalformedSitemapPath(path)) {\n if (debug) console.warn(`[SitemapSync] Skipping malformed path: ${path}`)\n return\n }\n\n const lastmod = url.querySelector('lastmod')?.textContent || undefined\n const changefreq = url.querySelector('changefreq')?.textContent || undefined\n const priorityStr = url.querySelector('priority')?.textContent\n const priority = priorityStr ? parseFloat(priorityStr) : undefined\n\n entries.push({ path, lastmod, changefreq, priority })\n })\n\n if (debug) {\n console.log(`[SitemapSync] Parsed ${entries.length} entries from sitemap`)\n }\n\n return entries\n } catch (error) {\n if (debug) console.error('[SitemapSync] Error parsing sitemap:', error)\n return []\n }\n}\n\nasync function syncToPortal(\n entries: SitemapEntry[],\n apiUrl: string,\n apiKey: string,\n site: string,\n debug: boolean,\n mode: 'additive' | 'full-replace' = 'additive'\n): Promise<{ success: boolean; created: number; updated: number }> {\n if (!apiKey) {\n if (debug) console.warn('[SitemapSync] No API key configured')\n return { success: false, created: 0, updated: 0 }\n }\n\n if (entries.length === 0) {\n if (debug) console.log('[SitemapSync] No entries to sync')\n return { success: true, created: 0, updated: 0 }\n }\n\n try {\n // Registration is idempotent in both modes (same payload → same final\n // state), so retrying a timed-out attempt is safe.\n const response = await sonorFetch(`${apiUrl}/api/public/seo/register-sitemap`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n entries: entries.map(e => ({\n path: e.path,\n priority: e.priority,\n changefreq: e.changefreq,\n pageType: inferPageType(e.path),\n })),\n mode,\n // Sub-site host — tagged on every seo_pages row so the dashboard\n // can scope the page tree to one microsite. Resolved by\n // SiteKitClientProviders (see __SITE_KIT_SITE__).\n ...(site ? { site } : {}),\n }),\n }, { apiKey })\n\n if (!response.ok) {\n if (debug) console.error('[SitemapSync] API error:', response.status, response.statusText)\n return { success: false, created: 0, updated: 0 }\n }\n\n const result = await response.json()\n\n if (debug) {\n console.log(`[SitemapSync] Synced: ${result.created} created, ${result.updated} updated`)\n }\n\n return {\n success: true,\n created: result.created || 0,\n updated: result.updated || 0,\n }\n } catch (error) {\n if (debug) console.error('[SitemapSync] Sync error:', error)\n return { success: false, created: 0, updated: 0 }\n }\n}\n\nexport function SitemapSync({\n sitemapUrl = '/sitemap.xml',\n syncInterval = 60,\n debug = false,\n mode = 'additive',\n}: SitemapSyncProps) {\n const hasSyncedRef = useRef(false)\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)\n\n useEffect(() => {\n // Build-time sync via createSitemap is the canonical source of truth, but\n // it can fail during ISR revalidation (app/ dir unavailable on serverless).\n // Runtime SitemapSync in additive mode is a safe fallback — it adds/updates\n // pages that may be missing without ever deleting existing ones.\n // Full-replace is never safe at runtime — always force additive.\n const effectiveMode: 'additive' | 'full-replace' = 'additive'\n\n // Throttle window: reuse syncInterval when periodic syncing is on,\n // otherwise default to 60 minutes for the on-mount sync.\n const throttleMinutes = syncInterval > 0 ? syncInterval : 60\n\n const doSync = async () => {\n // Never register a sitemap from inside someone else's page.\n //\n // The rows would not be mis-attributed — inside the frame this document\n // still resolves the framed site's own host and its own sitemap — but a\n // WRITE that a third party's page view triggers is wrong for two\n // reasons. It runs a fetch + DOMParser on a visitor's main thread in a\n // page that gets nothing from it; and it bumps `seo_pages.updated_at`,\n // which is a tiebreaker in `pickSeoPageRow`'s \"most recent\" fallback.\n // On a multi-site project that is how a shared path like `/` starts\n // resolving to a different host's row (see CLAUDE.md, the BD-AEC\n // llms.txt contamination) — a third party's traffic should not be able\n // to move which row wins.\n //\n // Same-origin frames sync as before; they are the site itself.\n if (isCrossOriginFrame()) {\n if (debug) console.log('[SitemapSync] Skipped — page is a cross-origin embed')\n return\n }\n\n const { apiUrl, apiKey, site } = getApiConfig()\n\n if (!apiKey) {\n if (debug) console.warn('[SitemapSync] No API key found')\n return\n }\n\n if (isRecentlySynced(throttleMinutes)) {\n if (debug) console.log('[SitemapSync] Skipped — synced within the last', throttleMinutes, 'min this session')\n return\n }\n\n if (debug) console.log('[SitemapSync] Starting sitemap sync...', site ? `(site: ${site})` : '')\n\n const entries = await parseSitemap(sitemapUrl, debug)\n\n if (entries.length > 0) {\n const hash = hashEntries(sitemapUrl, entries)\n if (isAlreadySyncedHash(hash)) {\n // This exact sitemap already synced from this browser — the server\n // state can't be improved by re-POSTing it. Stamp the session so we\n // don't even re-fetch/parse for the rest of the throttle window.\n stampSynced()\n if (debug) console.log('[SitemapSync] Skipped POST — sitemap unchanged since last successful sync')\n return\n }\n const result = await syncToPortal(entries, apiUrl, apiKey, site, debug, effectiveMode)\n if (result.success) {\n stampSynced()\n storeSyncedHash(hash)\n }\n }\n }\n\n if (!hasSyncedRef.current) {\n hasSyncedRef.current = true\n doSync()\n }\n\n if (syncInterval > 0) {\n intervalRef.current = setInterval(doSync, syncInterval * 60 * 1000)\n }\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n }\n }\n }, [sitemapUrl, syncInterval, debug, mode])\n\n return null\n}\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkJNRHTZL4_js = require('./chunk-JNRHTZL4.js');
|
|
4
4
|
|
|
5
5
|
// src/seo/getManagedMetadata.ts
|
|
6
6
|
async function getManagedMetadata(options) {
|
|
7
7
|
const { path, fallback = {}, overrides = {}, favicon: faviconMode = "metadata" } = options;
|
|
8
8
|
const omitIcons = faviconMode === "component";
|
|
9
|
-
const result = await
|
|
9
|
+
const result = await chunkJNRHTZL4_js.getSEOPageData(path);
|
|
10
10
|
const pageData = result?.page;
|
|
11
11
|
const projectData = result?.project;
|
|
12
12
|
if (!pageData) {
|
|
@@ -35,7 +35,7 @@ async function getManagedMetadata(options) {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
if (!pageData.managed_title && !pageData.managed_meta_description && !pageData.managed_description) {
|
|
38
|
-
|
|
38
|
+
chunkJNRHTZL4_js.ensureMetadata(path).catch(() => {
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
if (pageData.managed_title) {
|
|
@@ -96,7 +96,7 @@ async function getManagedMetadata(options) {
|
|
|
96
96
|
}
|
|
97
97
|
async function getABVariant(options) {
|
|
98
98
|
const { path, field, sessionId } = options;
|
|
99
|
-
const test = await
|
|
99
|
+
const test = await chunkJNRHTZL4_js.getABTest(path, field);
|
|
100
100
|
if (!test) {
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
@@ -109,7 +109,7 @@ async function getABVariant(options) {
|
|
|
109
109
|
} else {
|
|
110
110
|
variant = Math.random() < test.traffic_split ? "a" : "b";
|
|
111
111
|
}
|
|
112
|
-
|
|
112
|
+
chunkJNRHTZL4_js.recordABImpression(test.id, variant, sessionId).catch(() => {
|
|
113
113
|
});
|
|
114
114
|
return {
|
|
115
115
|
testId: test.id,
|
|
@@ -142,7 +142,7 @@ async function getManagedMetadataWithAB(options) {
|
|
|
142
142
|
// src/seo/routing.ts
|
|
143
143
|
async function getRedirect(options) {
|
|
144
144
|
const { path } = options;
|
|
145
|
-
const redirect = await
|
|
145
|
+
const redirect = await chunkJNRHTZL4_js.getRedirectData(path);
|
|
146
146
|
if (!redirect) {
|
|
147
147
|
return null;
|
|
148
148
|
}
|
|
@@ -184,7 +184,7 @@ function parseRobotsString(robots) {
|
|
|
184
184
|
}
|
|
185
185
|
async function getRobotsDirective(options) {
|
|
186
186
|
const { path } = options;
|
|
187
|
-
const robotsString = await
|
|
187
|
+
const robotsString = await chunkJNRHTZL4_js.getRobotsData(path);
|
|
188
188
|
if (!robotsString) {
|
|
189
189
|
return { index: true, follow: true };
|
|
190
190
|
}
|
|
@@ -192,7 +192,7 @@ async function getRobotsDirective(options) {
|
|
|
192
192
|
}
|
|
193
193
|
async function generateSitemap(options) {
|
|
194
194
|
const { baseUrl, publishedOnly = true } = options;
|
|
195
|
-
const pages = await
|
|
195
|
+
const pages = await chunkJNRHTZL4_js.getSitemapEntries({ publishedOnly });
|
|
196
196
|
const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
197
197
|
return pages.map((page) => ({
|
|
198
198
|
path: page.path,
|
|
@@ -203,7 +203,7 @@ async function generateSitemap(options) {
|
|
|
203
203
|
}));
|
|
204
204
|
}
|
|
205
205
|
async function registerLocalSitemap(options) {
|
|
206
|
-
const { registerSitemap } = await import('./server-api-
|
|
206
|
+
const { registerSitemap } = await import('./server-api-RYJQAUGO.js');
|
|
207
207
|
let entries = options.entries || [];
|
|
208
208
|
if (options.autoDiscover && entries.length === 0) {
|
|
209
209
|
try {
|
|
@@ -242,5 +242,5 @@ exports.getRedirect = getRedirect;
|
|
|
242
242
|
exports.getRobotsDirective = getRobotsDirective;
|
|
243
243
|
exports.isIndexable = isIndexable;
|
|
244
244
|
exports.registerLocalSitemap = registerLocalSitemap;
|
|
245
|
-
//# sourceMappingURL=chunk-
|
|
246
|
-
//# sourceMappingURL=chunk-
|
|
245
|
+
//# sourceMappingURL=chunk-KPKQKFT4.js.map
|
|
246
|
+
//# sourceMappingURL=chunk-KPKQKFT4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo/getManagedMetadata.ts","../src/seo/routing.ts"],"names":["getSEOPageData","ensureMetadata","getABTest","recordABImpression","getRedirectData","getRobotsData","getSitemapEntries"],"mappings":"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","file":"chunk-KPKQKFT4.js","sourcesContent":["import type { Metadata } from 'next'\nimport { getSEOPageData, getABTest, recordABImpression, ensureMetadata } from './server-api'\nimport type { \n GetManagedMetadataOptions, \n ManagedMetadataResult,\n GetABVariantOptions,\n ABTestResult \n} from './types'\n\n/**\n * Get managed metadata for a page\n * \n * Use in generateMetadata() to fetch Portal-managed SEO data\n * \n * @example\n * ```tsx\n * export async function generateMetadata({ params }) {\n * return getManagedMetadata({\n * path: `/services/${params.slug}`,\n * fallback: {\n * title: 'Our Services',\n * description: 'Learn about our services'\n * }\n * })\n * }\n * ```\n */\nexport async function getManagedMetadata(\n options: GetManagedMetadataOptions\n): Promise<ManagedMetadataResult> {\n const { path, fallback = {}, overrides = {}, favicon: faviconMode = 'metadata' } = options\n const omitIcons = faviconMode === 'component'\n\n const result = await getSEOPageData(path)\n const pageData = result?.page\n const projectData = result?.project\n\n // If no managed data, return fallback (still include favicon from project if available, unless using component)\n if (!pageData) {\n const fallbackMeta: ManagedMetadataResult = {\n ...fallback,\n ...overrides,\n _managed: false,\n _source: 'fallback',\n } as ManagedMetadataResult\n\n if (!omitIcons && projectData?.logo_url) {\n fallbackMeta.icons = {\n icon: projectData.logo_url,\n apple: projectData.logo_url,\n }\n }\n\n return fallbackMeta\n }\n\n // Build metadata from managed values, falling back to provided fallbacks\n const metadata: ManagedMetadataResult = {\n _managed: true,\n _source: 'database',\n }\n\n if (!omitIcons && projectData?.logo_url) {\n metadata.icons = {\n icon: projectData.logo_url,\n apple: projectData.logo_url,\n }\n }\n\n // Signal fallback — when page exists but both title & description are empty,\n // trigger Signal AI to generate them (fire-and-forget so it never blocks TTFB;\n // the generated data will be available on the next request)\n if (!pageData.managed_title && !pageData.managed_meta_description && !pageData.managed_description) {\n ensureMetadata(path).catch(() => {})\n }\n\n // Title\n if (pageData.managed_title) {\n metadata.title = pageData.managed_title\n } else if (fallback.title) {\n metadata.title = fallback.title\n }\n\n // Description\n if (pageData.managed_meta_description || pageData.managed_description) {\n metadata.description = pageData.managed_meta_description || pageData.managed_description\n } else if (fallback.description) {\n metadata.description = fallback.description\n }\n\n // Keywords\n if (pageData.managed_keywords?.length) {\n metadata.keywords = pageData.managed_keywords\n } else if (fallback.keywords) {\n metadata.keywords = fallback.keywords\n }\n\n // Robots\n if (pageData.managed_robots) {\n metadata.robots = pageData.managed_robots\n }\n\n // Canonical\n if (pageData.managed_canonical) {\n metadata.alternates = {\n ...metadata.alternates,\n canonical: pageData.managed_canonical,\n }\n }\n\n // Optional hreflang map from Portal (expansion / multilingual sites)\n const langAlts = pageData.language_alternates\n if (langAlts && typeof langAlts === 'object' && !Array.isArray(langAlts)) {\n metadata.alternates = {\n ...metadata.alternates,\n languages: langAlts as Record<string, string>,\n }\n }\n\n // Open Graph\n const ogTitle = pageData.managed_og_title || pageData.managed_title\n const ogDescription = pageData.managed_og_description || pageData.managed_meta_description || pageData.managed_description\n const ogImage = pageData.managed_og_image\n\n if (ogTitle || ogDescription || ogImage) {\n metadata.openGraph = {\n ...(ogTitle && { title: ogTitle }),\n ...(ogDescription && { description: ogDescription }),\n ...(ogImage && { images: [{ url: ogImage }] }),\n }\n }\n\n // Twitter (use OG values as fallback)\n if (ogTitle || ogDescription || ogImage) {\n metadata.twitter = {\n card: 'summary_large_image',\n ...(ogTitle && { title: ogTitle }),\n ...(ogDescription && { description: ogDescription }),\n ...(ogImage && { images: [ogImage] }),\n }\n }\n\n // Apply overrides last\n return {\n ...metadata,\n ...overrides,\n _managed: true,\n _source: 'database',\n }\n}\n\n/**\n * Get A/B test variant for a field\n * \n * @example\n * ```tsx\n * const variant = await getABVariant({\n * path: '/pricing',\n * field: 'title',\n * sessionId: cookies().get('session_id')?.value\n * })\n * \n * if (variant) {\n * // Use variant.value instead of default\n * }\n * ```\n */\nexport async function getABVariant(\n options: GetABVariantOptions\n): Promise<ABTestResult | null> {\n const { path, field, sessionId } = options\n\n const test = await getABTest(path, field)\n\n if (!test) {\n return null\n }\n\n // Determine variant based on session ID or random\n let variant: 'a' | 'b'\n \n if (sessionId) {\n // Consistent variant for same session\n const hash = sessionId.split('').reduce((acc, char) => {\n return ((acc << 5) - acc) + char.charCodeAt(0)\n }, 0)\n variant = (Math.abs(hash) % 100) < (test.traffic_split * 100) ? 'a' : 'b'\n } else {\n // Random variant\n variant = Math.random() < test.traffic_split ? 'a' : 'b'\n }\n\n // Record impression (fire and forget)\n recordABImpression(test.id, variant, sessionId).catch(() => {\n // Silently fail - don't block rendering\n })\n\n return {\n testId: test.id,\n variant,\n value: variant === 'a' ? test.variant_a : test.variant_b,\n }\n}\n\n/**\n * Get managed metadata with A/B test support\n * \n * Automatically applies running A/B test variants to metadata\n */\nexport async function getManagedMetadataWithAB(\n options: GetManagedMetadataOptions & { sessionId?: string }\n): Promise<ManagedMetadataResult> {\n const { sessionId, ...metadataOptions } = options\n \n // Get base metadata\n const metadata = await getManagedMetadata(metadataOptions)\n\n // Check for title A/B test\n const titleTest = await getABVariant({\n path: options.path,\n field: 'title',\n sessionId,\n })\n\n if (titleTest) {\n metadata.title = titleTest.value\n }\n\n // Check for description A/B test\n const descTest = await getABVariant({\n path: options.path,\n field: 'description',\n sessionId,\n })\n\n if (descTest) {\n metadata.description = descTest.value\n }\n\n return metadata\n}\n","import { getRedirectData, getRobotsData, getSitemapEntries } from './server-api'\nimport type { \n GetRedirectOptions, \n RedirectResult, \n GetRobotsOptions, \n RobotsDirective,\n GetSitemapEntriesOptions,\n SitemapEntry \n} from './types'\n\n/**\n * Get redirect for a path if one exists\n * \n * Use in Next.js middleware to handle managed redirects\n * \n * @example\n * ```tsx\n * // proxy.ts\n * import { getRedirect } from '@sonordev/site-kit/seo'\n * \n * export default async function proxy(request) {\n * const redirect = await getRedirect({ path: request.nextUrl.pathname })\n * \n * if (redirect) {\n * return NextResponse.redirect(redirect.destination, redirect.statusCode)\n * }\n * }\n * ```\n */\nexport async function getRedirect(\n options: GetRedirectOptions\n): Promise<RedirectResult | null> {\n const { path } = options\n\n const redirect = await getRedirectData(path)\n\n if (!redirect) {\n return null\n }\n\n // Check if expired\n if (redirect.expires_at && new Date(redirect.expires_at) < new Date()) {\n return null\n }\n\n // Determine destination\n const destination = redirect.destination_url || redirect.destination_path\n const isExternal = destination.startsWith('http://') || destination.startsWith('https://')\n\n return {\n destination,\n statusCode: redirect.status_code,\n isExternal,\n }\n}\n\n/**\n * Parse robots directive string into structured object\n */\nfunction parseRobotsString(robots: string): RobotsDirective {\n const directive: RobotsDirective = {\n index: true,\n follow: true,\n }\n\n const parts = robots.toLowerCase().split(',').map(p => p.trim())\n\n for (const part of parts) {\n if (part === 'noindex') directive.index = false\n if (part === 'nofollow') directive.follow = false\n if (part === 'noarchive') directive.noarchive = true\n if (part === 'nosnippet') directive.nosnippet = true\n if (part === 'noimageindex') directive.noimageindex = true\n if (part.startsWith('max-snippet:')) {\n directive.max_snippet = parseInt(part.split(':')[1], 10)\n }\n if (part.startsWith('max-image-preview:')) {\n const value = part.split(':')[1] as 'none' | 'standard' | 'large'\n directive.max_image_preview = value\n }\n if (part.startsWith('max-video-preview:')) {\n directive.max_video_preview = parseInt(part.split(':')[1], 10)\n }\n }\n\n return directive\n}\n\n/**\n * Get robots directive for a page\n * \n * @example\n * ```tsx\n * const robots = await getRobotsDirective({ path: '/private-page' })\n * \n * if (!robots.index) {\n * // Page should not be indexed\n * }\n * ```\n */\nexport async function getRobotsDirective(\n options: GetRobotsOptions\n): Promise<RobotsDirective> {\n const { path } = options\n\n const robotsString = await getRobotsData(path)\n\n if (!robotsString) {\n // Default: index and follow\n return { index: true, follow: true }\n }\n\n return parseRobotsString(robotsString)\n}\n\n/**\n * Get the project's managed page list as sitemap entries\n *\n * Returns `{ path, url, lastmod, changefreq, priority }`, which is NOT Next's\n * `MetadataRoute.Sitemap` shape (`lastModified` / `changeFrequency`). For\n * app/sitemap.ts prefer `createSitemap` from `@sonordev/site-kit/sitemap`.\n *\n * @example\n * ```tsx\n * import { generateSitemap } from '@sonordev/site-kit/seo'\n *\n * const entries = await generateSitemap({ baseUrl: 'https://example.com' })\n * ```\n */\nexport async function generateSitemap(\n options: GetSitemapEntriesOptions\n): Promise<SitemapEntry[]> {\n const { baseUrl, publishedOnly = true } = options\n\n const pages = await getSitemapEntries({ publishedOnly })\n\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl\n\n return pages.map((page: { path: string; lastmod?: string; changefreq?: string; priority?: number }) => ({\n path: page.path,\n url: `${normalizedBase}${page.path}`,\n lastmod: page.lastmod,\n changefreq: (page.changefreq || 'weekly') as SitemapEntry['changefreq'],\n priority: page.priority ?? 0.5,\n }))\n}\n\n/**\n * Register local sitemap entries with Sonor\n * \n * Call this at build time to sync your local routes to seo_pages.\n * This ensures analytics only tracks real pages.\n * \n * After registration, Signal AI will generate optimized meta titles\n * and descriptions for pages that don't have managed meta yet.\n * \n * @example\n * ```ts\n * // scripts/register-sitemap.ts\n * import { registerLocalSitemap } from '@sonordev/site-kit/seo/server'\n *\n * // Option 1: Provide entries directly\n * await registerLocalSitemap({\n * entries: [\n * { path: '/', title: 'Home', priority: 1.0 },\n * { path: '/about', title: 'About Us', priority: 0.8 },\n * ]\n * })\n *\n * // Option 2: Auto-discover from Next.js app directory\n * await registerLocalSitemap({ autoDiscover: true })\n * ```\n */\nexport async function registerLocalSitemap(options: {\n entries?: Array<{\n path: string\n title?: string\n priority?: number\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n }>\n autoDiscover?: boolean\n /** Trigger Signal AI to generate optimized meta titles/descriptions (default: true) */\n optimize_meta?: boolean\n /**\n * Sync mode. Defaults to 'additive' for explicit entries (a partial list must\n * never delete the rest of the site's pages) and 'full-replace' for\n * autoDiscover (which enumerates the complete route set).\n */\n mode?: 'additive' | 'full-replace'\n}): Promise<{\n success: boolean\n created: number\n updated: number\n removed?: number\n meta_optimization?: {\n triggered: boolean\n pages_queued: number\n } | null\n}> {\n const { registerSitemap } = await import('./server-api')\n\n let entries = options.entries || []\n\n // Auto-discover from the Next.js app directory if requested. Lazy-import the\n // fs-backed discovery module so `fs` never enters this file's STATIC graph —\n // getRedirect/isIndexable below are imported by edge middleware, which has no\n // fs. Discovery only runs at build time, in Node, so a dynamic import is fine.\n if (options.autoDiscover && entries.length === 0) {\n try {\n const { discoverNextJsRoutes, findAppDirForDiscovery } = await import('../sitemap/discover')\n const appDir = findAppDirForDiscovery()\n if (appDir) {\n entries = discoverNextJsRoutes(appDir)\n console.log(`[Sonor] Auto-discovered ${entries.length} routes from app directory`)\n }\n } catch (error) {\n console.error('[Sonor] Auto-discovery failed:', error)\n }\n }\n\n if (entries.length === 0) {\n console.warn('[Sonor] No sitemap entries to register')\n return { success: true, created: 0, updated: 0 }\n }\n\n // A route set discovered from the filesystem is structurally incomplete (it\n // can't enumerate dynamic routes and ignores sitemap excludes), and an\n // explicit entry list is often partial — either way a full-replace would\n // silently delete real pages. Default everything to additive; a caller that\n // truly holds the complete page set opts in with mode: 'full-replace'. The\n // recommended `sonor-register-sitemap` postbuild additionally self-skips when\n // an app/sitemap route already owns the sync.\n const mode = options.mode ?? 'additive'\n\n console.log(`[Sonor] Registering ${entries.length} sitemap entries (${mode})...`)\n const result = await registerSitemap(entries, { mode })\n \n if (result.success) {\n console.log(`[Sonor] Sitemap registered: ${result.created} new, ${result.updated} updated`)\n }\n \n return result\n}\n\n/**\n * Check if a path should be indexed\n * \n * Quick helper to check indexability without full directive parsing\n */\nexport async function isIndexable(\n projectId: string,\n path: string\n): Promise<boolean> {\n const directive = await getRobotsDirective({ projectId, path })\n return directive.index\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkOZ3FU4BT_js = require('./chunk-OZ3FU4BT.js');
|
|
4
|
-
var
|
|
4
|
+
var chunkHFJQMIFM_js = require('./chunk-HFJQMIFM.js');
|
|
5
5
|
|
|
6
6
|
// src/shared/publishCredential.ts
|
|
7
7
|
function publishSiteCredential({
|
|
@@ -12,14 +12,14 @@ function publishSiteCredential({
|
|
|
12
12
|
mapsBrowserKey
|
|
13
13
|
}) {
|
|
14
14
|
if (typeof window === "undefined") return;
|
|
15
|
-
|
|
15
|
+
chunkHFJQMIFM_js.configureSiteCredential({ credential: apiKey, apiUrl });
|
|
16
16
|
window.__SITE_KIT_API_URL__ = apiUrl;
|
|
17
|
-
window.__SITE_KIT_API_KEY__ =
|
|
17
|
+
window.__SITE_KIT_API_KEY__ = chunkHFJQMIFM_js.currentCredential(apiKey);
|
|
18
18
|
window.__SITE_KIT_DEBUG__ = debug;
|
|
19
19
|
window.__SITE_KIT_SITE__ = site ?? chunkOZ3FU4BT_js.resolveSiteHost();
|
|
20
20
|
if (mapsBrowserKey) window.__GOOGLE_MAPS_API_KEY__ = mapsBrowserKey;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
exports.publishSiteCredential = publishSiteCredential;
|
|
24
|
-
//# sourceMappingURL=chunk-
|
|
25
|
-
//# sourceMappingURL=chunk-
|
|
24
|
+
//# sourceMappingURL=chunk-MCJF2KJ6.js.map
|
|
25
|
+
//# sourceMappingURL=chunk-MCJF2KJ6.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/publishCredential.ts"],"names":["configureSiteCredential","currentCredential","resolveSiteHost"],"mappings":";;;;;;AAkEO,SAAS,qBAAA,CAAsB;AAAA,EACpC,MAAA;AAAA,EACA,MAAA,GAAS,sBAAA;AAAA,EACT,IAAA;AAAA,EACA,KAAA,GAAQ,KAAA;AAAA,EACR;AACF,CAAA,EAAuC;AACrC,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAKnC,EAAAA,wCAAA,CAAwB,EAAE,UAAA,EAAY,MAAA,EAAQ,MAAA,EAAQ,CAAA;AACrD,EAAC,OAAe,oBAAA,GAAuB,MAAA;AACvC,EAAC,MAAA,CAAe,oBAAA,GAAuBC,kCAAA,CAAkB,MAAM,CAAA;AAC/D,EAAC,OAAe,kBAAA,GAAqB,KAAA;AACrC,EAAC,MAAA,CAAe,iBAAA,GAAoB,IAAA,IAAQC,gCAAA,EAAgB;AAG7D,EAAA,IAAI,cAAA,EAAiB,MAAA,CAAe,uBAAA,GAA0B,cAAA;AAChE","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/shared/publishCredential.ts"],"names":["configureSiteCredential","currentCredential","resolveSiteHost"],"mappings":";;;;;;AAkEO,SAAS,qBAAA,CAAsB;AAAA,EACpC,MAAA;AAAA,EACA,MAAA,GAAS,sBAAA;AAAA,EACT,IAAA;AAAA,EACA,KAAA,GAAQ,KAAA;AAAA,EACR;AACF,CAAA,EAAuC;AACrC,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAKnC,EAAAA,wCAAA,CAAwB,EAAE,UAAA,EAAY,MAAA,EAAQ,MAAA,EAAQ,CAAA;AACrD,EAAC,OAAe,oBAAA,GAAuB,MAAA;AACvC,EAAC,MAAA,CAAe,oBAAA,GAAuBC,kCAAA,CAAkB,MAAM,CAAA;AAC/D,EAAC,OAAe,kBAAA,GAAqB,KAAA;AACrC,EAAC,MAAA,CAAe,iBAAA,GAAoB,IAAA,IAAQC,gCAAA,EAAgB;AAG7D,EAAA,IAAI,cAAA,EAAiB,MAAA,CAAe,uBAAA,GAA0B,cAAA;AAChE","file":"chunk-MCJF2KJ6.js","sourcesContent":["/**\n * @sonordev/site-kit shared — Client credential + window-global publishing\n *\n * SINGLE SOURCE OF TRUTH for seeding the browser-side token manager and the\n * `window.__SITE_KIT_*` globals that every client module reads. Used by:\n *\n * - `SiteKitClientProviders` (core `SiteKitLayout`)\n * - `SiteKitCredential` (`@sonordev/site-kit/client`) — the public component\n * for layouts that are NOT core `SiteKitLayout`, e.g.\n * `@sonordev/agency-site-kit`'s `AgencySiteKitLayout`.\n *\n * Do not re-fork this. Before it existed, agency sites hand-rolled their own\n * \"set the globals on mount\" component (upforge's `UptradeSiteKitInit`), and\n * because those copies had no access to the token manager they published the\n * RAW `sonor_` key into the client bundle — the exact exposure the mint exists\n * to prevent. Any layout that puts a credential on `window` goes through here.\n *\n * Client-safe: no `server-only` guard, SSR-safe (every write is window-gated).\n * The minting half is server-only and lives in `../server/mint-site-token`.\n */\n// Leaf modules, NOT '../sites/contract' — see sites/normalize.ts. Importing the\n// full contract here shipped its JSON schema to every browser that loads a\n// credential-publishing entry.\nimport { resolveSiteHost } from '../sites/resolve'\nimport { configureSiteCredential, currentCredential } from './siteToken'\n\n/**\n * Re-exported so the client-side callers below and every existing importer keep\n * a single name for this. The implementation lives in `../sites/resolve` — the\n * ONE resolver shared by the browser (this file) and the build-time sitemap /\n * llms.txt path, so both tag the same host.\n */\nexport { resolveSiteHost }\n\nexport interface PublishSiteCredentialOptions {\n /**\n * The credential the layout minted server-side. In 3.2+ this is a short-lived\n * `st1.` token that `sonorFetch` refreshes transparently. A raw `sonor_` key\n * still works (2.x kit, or the mint-failed fallback) but should never be\n * routed here from a client component — it would ship the secret to the\n * browser.\n */\n apiKey?: string\n apiUrl?: string\n /** Pre-resolved sub-site host. Omit to resolve via `resolveSiteHost`. */\n site?: string\n debug?: boolean\n /**\n * Referrer-restricted Google Maps BROWSER key for client map components.\n * Resolved server-side by SiteKitLayout (NEXT_PUBLIC_GOOGLE_MAPS_API_KEY /\n * GOOGLE_MAPS_BROWSER_KEY) because maps/api.ts runs client-side, where a bare\n * process.env read is dead code unless the app's bundler happens to inline it.\n * Never route a server secret here.\n */\n mapsBrowserKey?: string\n}\n\n/**\n * Seed the token manager and publish the `window.__SITE_KIT_*` globals.\n *\n * Called during RENDER, not in an effect: client modules read these globals in\n * their own mount effects, and effects run child-first, so an effect-based\n * publish would land after the modules that need it.\n *\n * No-op on the server.\n */\nexport function publishSiteCredential({\n apiKey,\n apiUrl = 'https://api.sonor.io',\n site,\n debug = false,\n mapsBrowserKey,\n}: PublishSiteCredentialOptions): void {\n if (typeof window === 'undefined') return\n\n // Seed the client token manager. The window global is set to the freshest\n // token held — a prior page this session may already have refreshed past the\n // seed the layout injected. In raw-key mode this is a passthrough.\n configureSiteCredential({ credential: apiKey, apiUrl })\n ;(window as any).__SITE_KIT_API_URL__ = apiUrl\n ;(window as any).__SITE_KIT_API_KEY__ = currentCredential(apiKey)\n ;(window as any).__SITE_KIT_DEBUG__ = debug\n ;(window as any).__SITE_KIT_SITE__ = site ?? resolveSiteHost()\n // maps/api.ts falls back to this global when the Sonor platform hasn't\n // provisioned a browser key. Don't clobber a value another layout already set.\n if (mapsBrowserKey) (window as any).__GOOGLE_MAPS_API_KEY__ = mapsBrowserKey\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkSUZ3ZLB2_js = require('./chunk-SUZ3ZLB2.js');
|
|
4
4
|
require('server-only');
|
|
5
5
|
|
|
6
6
|
var MINT_PATH = "/api/public/site-token";
|
|
@@ -80,7 +80,7 @@ async function mintWithRetry(rawKey, apiUrl) {
|
|
|
80
80
|
headers: {
|
|
81
81
|
"content-type": "application/json",
|
|
82
82
|
"x-api-key": rawKey,
|
|
83
|
-
"x-sitekit-version":
|
|
83
|
+
"x-sitekit-version": chunkSUZ3ZLB2_js.SITE_KIT_VERSION
|
|
84
84
|
},
|
|
85
85
|
// Only meaningful on the fallback path, where a future Next stopped
|
|
86
86
|
// exposing `_nextOriginalFetch` and we're back on the patched fetch.
|
|
@@ -132,5 +132,5 @@ async function mintWithRetry(rawKey, apiUrl) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
exports.resolveClientCredential = resolveClientCredential;
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
136
|
-
//# sourceMappingURL=chunk-
|
|
135
|
+
//# sourceMappingURL=chunk-MNYM2XU4.js.map
|
|
136
|
+
//# sourceMappingURL=chunk-MNYM2XU4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/mint-site-token.ts"],"names":["SITE_KIT_VERSION"],"mappings":"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","file":"chunk-URVGBWCP.js","sourcesContent":["/**\n * @sonordev/site-kit/server — Client-token minting (server-only)\n *\n * SiteKitLayout holds the raw `SONOR_API_KEY` server-side but must NOT ship it to\n * the browser. This mints a short-lived, project-scoped token (server-to-server,\n * authenticated with the raw key, no browser Origin so it passes domain-binding)\n * and returns THAT for the layout to inject instead.\n *\n * `import 'server-only'` makes importing this into a client bundle a hard build\n * error — the raw key stays on the server.\n *\n * FAILS CLOSED. If minting genuinely fails, this throws and the build fails —\n * it does NOT publish the raw key. A failed deploy is loud and recoverable; a\n * leaked project secret in public HTML is neither. The only paths that still\n * return the raw key are a 404 (an older API that predates the endpoint, an\n * expected state during rollout) and the explicit `SONOR_SITE_TOKEN_FALLBACK=raw`\n * escape hatch, for when shipping beats being blocked.\n *\n * That posture is not academic. Through 3.5.0 a throttled mint (HTTP 429)\n * returned the raw key AND cached it for 60s, so every page rendered in that\n * window shipped the secret: one real build came out with 180 of 272 prerendered\n * pages carrying it. The build \"succeeded\" and nothing downstream noticed.\n * Rate limiting is transient by definition — the fix is to wait and retry, not\n * to downgrade to publishing the key.\n *\n * Two things keep the request count low enough that throttling is rare:\n * - a module-level memo, so a worker mints once and every page reuses the seed;\n * - an in-flight map, because that memo is only populated AFTER the await. Next\n * renders pages concurrently, so without coalescing the first N concurrent\n * renders in a process all miss the cache and all fire a request — a\n * stampede per worker, multiplied by one worker per core. That is how a\n * single site exhausted a 60/min budget.\n *\n * SEED FRESHNESS: a static page's seed carries `iat` = build time, and the API\n * refuses to refresh a seed older than SITE_TOKEN_MAX_REFRESH_AGE_SECONDS\n * (sonor-api common/site-auth/site-token.util.ts — currently 400 days, raised\n * from 30 alongside this fix). A site not rebuilt within that window serves a\n * seed the client cannot refresh, and its forms/analytics/engage start failing\n * for new visitors. Keep that limit comfortably longer than the fleet's\n * realistic redeploy gap; marketing sites routinely go many months untouched.\n */\nimport 'server-only'\nimport { SITE_KIT_VERSION } from '../shared/version'\n\nconst MINT_PATH = '/api/public/site-token'\nconst MINT_TIMEOUT_MS = 4_000\nconst SITE_TOKEN_PREFIX = 'st1.'\n\n/**\n * Retry budget for transient failures (429 / 5xx / network). Rate limiting is\n * measured per minute, so a few seconds of backoff clears it; the whole ladder\n * costs at most ~20s once per build worker, and only when something is wrong.\n */\nconst MINT_MAX_ATTEMPTS = 4\nconst MINT_BACKOFF_BASE_MS = 500\nconst MINT_BACKOFF_CAP_MS = 8_000\n\ninterface CacheEntry {\n credential: string\n expiresAt: number\n}\nconst cache = new Map<string, CacheEntry>()\n\n/**\n * Requests already in flight, keyed by raw key. The `cache` above is only\n * written after the response lands, so concurrent renders would otherwise each\n * start their own mint. See the module header.\n */\nconst inflight = new Map<string, Promise<string>>()\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\n/**\n * Honour `Retry-After` when the server sends it (delta-seconds or HTTP date),\n * otherwise exponential backoff. Capped so a misconfigured server cannot stall\n * a build indefinitely.\n */\nfunction retryDelayMs(res: Response | null, attempt: number): number {\n const header = res?.headers?.get?.('retry-after')\n if (header) {\n const seconds = Number(header)\n const ms = Number.isFinite(seconds)\n ? seconds * 1000\n : Date.parse(header) - Date.now()\n if (Number.isFinite(ms) && ms > 0) return Math.min(ms, MINT_BACKOFF_CAP_MS)\n }\n return Math.min(MINT_BACKOFF_BASE_MS * 2 ** attempt, MINT_BACKOFF_CAP_MS)\n}\n\n/** 429 and 5xx are transient; waiting is the correct response to both. */\nconst isRetryableStatus = (status: number) => status === 429 || status >= 500\n\n/**\n * The escape hatch, for when a broken mint must not block a release. Opt-in and\n * loud: it re-enables publishing the project secret in page HTML.\n */\nfunction rawFallbackAllowed(): boolean {\n return (\n typeof process !== 'undefined' &&\n process.env?.SONOR_SITE_TOKEN_FALLBACK === 'raw'\n )\n}\n\nfunction failClosed(reason: string, rawKey: string, now: number): string {\n if (rawFallbackAllowed()) {\n console.warn(\n `[SiteKitLayout] Token mint failed (${reason}) and SONOR_SITE_TOKEN_FALLBACK=raw is set; ` +\n 'injecting the RAW API KEY into page HTML. The key is publicly readable wherever ' +\n 'this page is served — unset the escape hatch once the mint is healthy.',\n )\n cache.set(rawKey, { credential: rawKey, expiresAt: now + 60_000 })\n return rawKey\n }\n throw new Error(\n `site-kit: could not mint a site token (${reason}). Refusing to publish the raw ` +\n 'SONOR_API_KEY in page HTML, so this build fails instead. Check that the mint ' +\n 'endpoint is reachable and the key is valid, then rebuild. To ship anyway — ' +\n 'which publishes the project secret in public HTML — set SONOR_SITE_TOKEN_FALLBACK=raw.',\n )\n}\n\ntype FetchLike = typeof fetch\n\n/**\n * Next.js monkey-patches `globalThis.fetch` so every call participates in its\n * Data Cache and static-generation tracking. This mint must NOT participate.\n *\n * `cache: 'no-store'` marks the calling scope dynamic, so during `next build`\n * Next throws DynamicServerError *before the request goes out*. On a statically\n * prerendered route the mint therefore never ran at all — the catch below\n * swallowed the bailout and every page shipped the raw key. (Verified against\n * Next 16.2.9: the mint endpoint received zero requests for a static build.)\n *\n * The two obvious repairs are both wrong:\n * - `next: { revalidate: N }` LOWERS the route's revalidate window\n * (patch-fetch.js: `finalRevalidate < revalidateStore.revalidate`), quietly\n * converting fully-static pages into ISR across the whole fleet.\n * - `next: { revalidate: false }` keeps pages static but writes the token into\n * the Data Cache, which hosts persist across builds — a fresh deploy could\n * re-bake a months-old seed, which is the exact failure we're removing.\n *\n * A credential mint is not page data and has no business in the page data cache.\n * So we call the original fetch Next saves on the patched one and stay invisible\n * to the framework: no bailout, no cache entry, no effect on staticness. The\n * module-level memo above is the only cache we want — one mint per build\n * process, and a build-fresh seed on every deploy.\n *\n * @internal Exported for unit tests; not part of the package's public API.\n */\nexport function resolveFetch(): { fn: FetchLike; unpatched: boolean } {\n const current = globalThis.fetch as\n | (FetchLike & { _nextOriginalFetch?: FetchLike })\n | undefined\n if (!current) throw new Error('site-kit: global fetch is unavailable')\n const original = current._nextOriginalFetch\n if (typeof original === 'function') return { fn: original, unpatched: true }\n return { fn: current, unpatched: false }\n}\n\n/**\n * Next signals \"this render must go dynamic\" by THROWING (DynamicServerError,\n * static-gen bailout, redirect/notFound digests, React's postpone). Those are\n * the framework's control flow, not a mint failure. Swallowing one makes Next\n * believe the render succeeded statically — which is precisely how the raw key\n * ended up stranded in prerendered HTML with no error anywhere. Let them\n * propagate; only real network/HTTP failures fall back to the raw key.\n *\n * @internal Exported for unit tests; not part of the package's public API.\n */\nexport function isFrameworkSignal(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false\n const e = err as { digest?: unknown; code?: unknown; $$typeof?: unknown }\n if (e.digest === 'DYNAMIC_SERVER_USAGE') return true\n if (e.code === 'NEXT_STATIC_GEN_BAILOUT') return true\n if (typeof e.digest === 'string' && e.digest.startsWith('NEXT_')) return true\n if (e.$$typeof === Symbol.for('react.postpone')) return true\n return false\n}\n\nexport async function resolveClientCredential(\n rawKey: string,\n apiUrl: string,\n): Promise<string> {\n if (!rawKey) return ''\n // Env should hold a raw key; if it already looks like a token, pass it through.\n if (rawKey.startsWith(SITE_TOKEN_PREFIX)) return rawKey\n\n const cached = cache.get(rawKey)\n if (cached && cached.expiresAt > Date.now()) return cached.credential\n\n // Collapse concurrent renders onto one request rather than a stampede.\n const pending = inflight.get(rawKey)\n if (pending) return pending\n\n const request = mintWithRetry(rawKey, apiUrl)\n inflight.set(rawKey, request)\n try {\n return await request\n } finally {\n inflight.delete(rawKey)\n }\n}\n\nasync function mintWithRetry(rawKey: string, apiUrl: string): Promise<string> {\n const base = (apiUrl || 'https://api.sonor.io').replace(/\\/+$/, '')\n let lastReason = 'unknown error'\n\n for (let attempt = 0; attempt < MINT_MAX_ATTEMPTS; attempt++) {\n let res: Response | null = null\n try {\n const { fn: fetchFn, unpatched } = resolveFetch()\n res = await fetchFn(`${base}${MINT_PATH}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': rawKey,\n 'x-sitekit-version': SITE_KIT_VERSION,\n },\n // Only meaningful on the fallback path, where a future Next stopped\n // exposing `_nextOriginalFetch` and we're back on the patched fetch.\n // `revalidate: false` is the one setting that neither bails out of static\n // generation nor downgrades the route to ISR; a Data Cache entry is the\n // lesser evil versus shipping the raw key again.\n ...(unpatched ? {} : ({ next: { revalidate: false } } as RequestInit)),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(MINT_TIMEOUT_MS)\n : undefined,\n })\n } catch (err) {\n // Never swallow the framework's own control flow — see isFrameworkSignal.\n if (isFrameworkSignal(err)) throw err\n lastReason = err instanceof Error ? err.message : 'network error'\n if (attempt < MINT_MAX_ATTEMPTS - 1) {\n await sleep(retryDelayMs(null, attempt))\n continue\n }\n break\n }\n\n if (res.ok) {\n const data: any = await res.json().catch(() => null)\n const token = data?.token\n const expiresIn = typeof data?.expiresIn === 'number' ? data.expiresIn : 3600\n if (typeof token === 'string' && token.startsWith(SITE_TOKEN_PREFIX)) {\n cache.set(rawKey, {\n credential: token,\n expiresAt: Date.now() + Math.min((expiresIn * 1000) / 2, 30 * 60_000),\n })\n return token\n }\n lastReason = 'mint returned no usable token'\n break\n }\n\n // 404 = an API older than the endpoint. Expected during rollout, and the raw\n // key is exactly what such an API still expects, so this stays a fallback\n // rather than a build failure.\n if (res.status === 404) {\n cache.set(rawKey, { credential: rawKey, expiresAt: Date.now() + 60_000 })\n return rawKey\n }\n\n lastReason = `HTTP ${res.status}`\n if (isRetryableStatus(res.status) && attempt < MINT_MAX_ATTEMPTS - 1) {\n const delay = retryDelayMs(res, attempt)\n console.warn(\n `[SiteKitLayout] Token mint returned ${res.status}; retrying in ${Math.round(delay)}ms ` +\n `(attempt ${attempt + 2}/${MINT_MAX_ATTEMPTS}).`,\n )\n await sleep(delay)\n continue\n }\n break\n }\n\n return failClosed(lastReason, rawKey, Date.now())\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/mint-site-token.ts"],"names":["SITE_KIT_VERSION"],"mappings":"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","file":"chunk-MNYM2XU4.js","sourcesContent":["/**\n * @sonordev/site-kit/server — Client-token minting (server-only)\n *\n * SiteKitLayout holds the raw `SONOR_API_KEY` server-side but must NOT ship it to\n * the browser. This mints a short-lived, project-scoped token (server-to-server,\n * authenticated with the raw key, no browser Origin so it passes domain-binding)\n * and returns THAT for the layout to inject instead.\n *\n * `import 'server-only'` makes importing this into a client bundle a hard build\n * error — the raw key stays on the server.\n *\n * FAILS CLOSED. If minting genuinely fails, this throws and the build fails —\n * it does NOT publish the raw key. A failed deploy is loud and recoverable; a\n * leaked project secret in public HTML is neither. The only paths that still\n * return the raw key are a 404 (an older API that predates the endpoint, an\n * expected state during rollout) and the explicit `SONOR_SITE_TOKEN_FALLBACK=raw`\n * escape hatch, for when shipping beats being blocked.\n *\n * That posture is not academic. Through 3.5.0 a throttled mint (HTTP 429)\n * returned the raw key AND cached it for 60s, so every page rendered in that\n * window shipped the secret: one real build came out with 180 of 272 prerendered\n * pages carrying it. The build \"succeeded\" and nothing downstream noticed.\n * Rate limiting is transient by definition — the fix is to wait and retry, not\n * to downgrade to publishing the key.\n *\n * Two things keep the request count low enough that throttling is rare:\n * - a module-level memo, so a worker mints once and every page reuses the seed;\n * - an in-flight map, because that memo is only populated AFTER the await. Next\n * renders pages concurrently, so without coalescing the first N concurrent\n * renders in a process all miss the cache and all fire a request — a\n * stampede per worker, multiplied by one worker per core. That is how a\n * single site exhausted a 60/min budget.\n *\n * SEED FRESHNESS: a static page's seed carries `iat` = build time, and the API\n * refuses to refresh a seed older than SITE_TOKEN_MAX_REFRESH_AGE_SECONDS\n * (sonor-api common/site-auth/site-token.util.ts — currently 400 days, raised\n * from 30 alongside this fix). A site not rebuilt within that window serves a\n * seed the client cannot refresh, and its forms/analytics/engage start failing\n * for new visitors. Keep that limit comfortably longer than the fleet's\n * realistic redeploy gap; marketing sites routinely go many months untouched.\n */\nimport 'server-only'\nimport { SITE_KIT_VERSION } from '../shared/version'\n\nconst MINT_PATH = '/api/public/site-token'\nconst MINT_TIMEOUT_MS = 4_000\nconst SITE_TOKEN_PREFIX = 'st1.'\n\n/**\n * Retry budget for transient failures (429 / 5xx / network). Rate limiting is\n * measured per minute, so a few seconds of backoff clears it; the whole ladder\n * costs at most ~20s once per build worker, and only when something is wrong.\n */\nconst MINT_MAX_ATTEMPTS = 4\nconst MINT_BACKOFF_BASE_MS = 500\nconst MINT_BACKOFF_CAP_MS = 8_000\n\ninterface CacheEntry {\n credential: string\n expiresAt: number\n}\nconst cache = new Map<string, CacheEntry>()\n\n/**\n * Requests already in flight, keyed by raw key. The `cache` above is only\n * written after the response lands, so concurrent renders would otherwise each\n * start their own mint. See the module header.\n */\nconst inflight = new Map<string, Promise<string>>()\n\nconst sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))\n\n/**\n * Honour `Retry-After` when the server sends it (delta-seconds or HTTP date),\n * otherwise exponential backoff. Capped so a misconfigured server cannot stall\n * a build indefinitely.\n */\nfunction retryDelayMs(res: Response | null, attempt: number): number {\n const header = res?.headers?.get?.('retry-after')\n if (header) {\n const seconds = Number(header)\n const ms = Number.isFinite(seconds)\n ? seconds * 1000\n : Date.parse(header) - Date.now()\n if (Number.isFinite(ms) && ms > 0) return Math.min(ms, MINT_BACKOFF_CAP_MS)\n }\n return Math.min(MINT_BACKOFF_BASE_MS * 2 ** attempt, MINT_BACKOFF_CAP_MS)\n}\n\n/** 429 and 5xx are transient; waiting is the correct response to both. */\nconst isRetryableStatus = (status: number) => status === 429 || status >= 500\n\n/**\n * The escape hatch, for when a broken mint must not block a release. Opt-in and\n * loud: it re-enables publishing the project secret in page HTML.\n */\nfunction rawFallbackAllowed(): boolean {\n return (\n typeof process !== 'undefined' &&\n process.env?.SONOR_SITE_TOKEN_FALLBACK === 'raw'\n )\n}\n\nfunction failClosed(reason: string, rawKey: string, now: number): string {\n if (rawFallbackAllowed()) {\n console.warn(\n `[SiteKitLayout] Token mint failed (${reason}) and SONOR_SITE_TOKEN_FALLBACK=raw is set; ` +\n 'injecting the RAW API KEY into page HTML. The key is publicly readable wherever ' +\n 'this page is served — unset the escape hatch once the mint is healthy.',\n )\n cache.set(rawKey, { credential: rawKey, expiresAt: now + 60_000 })\n return rawKey\n }\n throw new Error(\n `site-kit: could not mint a site token (${reason}). Refusing to publish the raw ` +\n 'SONOR_API_KEY in page HTML, so this build fails instead. Check that the mint ' +\n 'endpoint is reachable and the key is valid, then rebuild. To ship anyway — ' +\n 'which publishes the project secret in public HTML — set SONOR_SITE_TOKEN_FALLBACK=raw.',\n )\n}\n\ntype FetchLike = typeof fetch\n\n/**\n * Next.js monkey-patches `globalThis.fetch` so every call participates in its\n * Data Cache and static-generation tracking. This mint must NOT participate.\n *\n * `cache: 'no-store'` marks the calling scope dynamic, so during `next build`\n * Next throws DynamicServerError *before the request goes out*. On a statically\n * prerendered route the mint therefore never ran at all — the catch below\n * swallowed the bailout and every page shipped the raw key. (Verified against\n * Next 16.2.9: the mint endpoint received zero requests for a static build.)\n *\n * The two obvious repairs are both wrong:\n * - `next: { revalidate: N }` LOWERS the route's revalidate window\n * (patch-fetch.js: `finalRevalidate < revalidateStore.revalidate`), quietly\n * converting fully-static pages into ISR across the whole fleet.\n * - `next: { revalidate: false }` keeps pages static but writes the token into\n * the Data Cache, which hosts persist across builds — a fresh deploy could\n * re-bake a months-old seed, which is the exact failure we're removing.\n *\n * A credential mint is not page data and has no business in the page data cache.\n * So we call the original fetch Next saves on the patched one and stay invisible\n * to the framework: no bailout, no cache entry, no effect on staticness. The\n * module-level memo above is the only cache we want — one mint per build\n * process, and a build-fresh seed on every deploy.\n *\n * @internal Exported for unit tests; not part of the package's public API.\n */\nexport function resolveFetch(): { fn: FetchLike; unpatched: boolean } {\n const current = globalThis.fetch as\n | (FetchLike & { _nextOriginalFetch?: FetchLike })\n | undefined\n if (!current) throw new Error('site-kit: global fetch is unavailable')\n const original = current._nextOriginalFetch\n if (typeof original === 'function') return { fn: original, unpatched: true }\n return { fn: current, unpatched: false }\n}\n\n/**\n * Next signals \"this render must go dynamic\" by THROWING (DynamicServerError,\n * static-gen bailout, redirect/notFound digests, React's postpone). Those are\n * the framework's control flow, not a mint failure. Swallowing one makes Next\n * believe the render succeeded statically — which is precisely how the raw key\n * ended up stranded in prerendered HTML with no error anywhere. Let them\n * propagate; only real network/HTTP failures fall back to the raw key.\n *\n * @internal Exported for unit tests; not part of the package's public API.\n */\nexport function isFrameworkSignal(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false\n const e = err as { digest?: unknown; code?: unknown; $$typeof?: unknown }\n if (e.digest === 'DYNAMIC_SERVER_USAGE') return true\n if (e.code === 'NEXT_STATIC_GEN_BAILOUT') return true\n if (typeof e.digest === 'string' && e.digest.startsWith('NEXT_')) return true\n if (e.$$typeof === Symbol.for('react.postpone')) return true\n return false\n}\n\nexport async function resolveClientCredential(\n rawKey: string,\n apiUrl: string,\n): Promise<string> {\n if (!rawKey) return ''\n // Env should hold a raw key; if it already looks like a token, pass it through.\n if (rawKey.startsWith(SITE_TOKEN_PREFIX)) return rawKey\n\n const cached = cache.get(rawKey)\n if (cached && cached.expiresAt > Date.now()) return cached.credential\n\n // Collapse concurrent renders onto one request rather than a stampede.\n const pending = inflight.get(rawKey)\n if (pending) return pending\n\n const request = mintWithRetry(rawKey, apiUrl)\n inflight.set(rawKey, request)\n try {\n return await request\n } finally {\n inflight.delete(rawKey)\n }\n}\n\nasync function mintWithRetry(rawKey: string, apiUrl: string): Promise<string> {\n const base = (apiUrl || 'https://api.sonor.io').replace(/\\/+$/, '')\n let lastReason = 'unknown error'\n\n for (let attempt = 0; attempt < MINT_MAX_ATTEMPTS; attempt++) {\n let res: Response | null = null\n try {\n const { fn: fetchFn, unpatched } = resolveFetch()\n res = await fetchFn(`${base}${MINT_PATH}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': rawKey,\n 'x-sitekit-version': SITE_KIT_VERSION,\n },\n // Only meaningful on the fallback path, where a future Next stopped\n // exposing `_nextOriginalFetch` and we're back on the patched fetch.\n // `revalidate: false` is the one setting that neither bails out of static\n // generation nor downgrades the route to ISR; a Data Cache entry is the\n // lesser evil versus shipping the raw key again.\n ...(unpatched ? {} : ({ next: { revalidate: false } } as RequestInit)),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(MINT_TIMEOUT_MS)\n : undefined,\n })\n } catch (err) {\n // Never swallow the framework's own control flow — see isFrameworkSignal.\n if (isFrameworkSignal(err)) throw err\n lastReason = err instanceof Error ? err.message : 'network error'\n if (attempt < MINT_MAX_ATTEMPTS - 1) {\n await sleep(retryDelayMs(null, attempt))\n continue\n }\n break\n }\n\n if (res.ok) {\n const data: any = await res.json().catch(() => null)\n const token = data?.token\n const expiresIn = typeof data?.expiresIn === 'number' ? data.expiresIn : 3600\n if (typeof token === 'string' && token.startsWith(SITE_TOKEN_PREFIX)) {\n cache.set(rawKey, {\n credential: token,\n expiresAt: Date.now() + Math.min((expiresIn * 1000) / 2, 30 * 60_000),\n })\n return token\n }\n lastReason = 'mint returned no usable token'\n break\n }\n\n // 404 = an API older than the endpoint. Expected during rollout, and the raw\n // key is exactly what such an API still expects, so this stays a fallback\n // rather than a build failure.\n if (res.status === 404) {\n cache.set(rawKey, { credential: rawKey, expiresAt: Date.now() + 60_000 })\n return rawKey\n }\n\n lastReason = `HTTP ${res.status}`\n if (isRetryableStatus(res.status) && attempt < MINT_MAX_ATTEMPTS - 1) {\n const delay = retryDelayMs(res, attempt)\n console.warn(\n `[SiteKitLayout] Token mint returned ${res.status}; retrying in ${Math.round(delay)}ms ` +\n `(attempt ${attempt + 2}/${MINT_MAX_ATTEMPTS}).`,\n )\n await sleep(delay)\n continue\n }\n break\n }\n\n return failClosed(lastReason, rawKey, Date.now())\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { sonorFetch } from './chunk-
|
|
2
|
+
import { sonorFetch } from './chunk-OFSA7LUH.mjs';
|
|
3
3
|
import { useState, useCallback, useEffect } from 'react';
|
|
4
4
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
var isDevMode = () => {
|
|
7
7
|
if (typeof window === "undefined") return false;
|
|
8
|
-
return process.env.NODE_ENV === "development" || window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.search.
|
|
8
|
+
return process.env.NODE_ENV === "development" || window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || new URLSearchParams(window.location.search).get("sonor_dev") === "true";
|
|
9
9
|
};
|
|
10
10
|
function ManagedImage({
|
|
11
11
|
apiKey = process.env.SONOR_API_KEY,
|
|
@@ -521,5 +521,5 @@ async function clearImageSlot(config, slotId, pagePath) {
|
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
export { ManagedImage, assignImageToSlot, clearImageSlot, fetchManagedImage, fetchManagedImages, listImageFiles, uploadImage };
|
|
524
|
-
//# sourceMappingURL=chunk-
|
|
525
|
-
//# sourceMappingURL=chunk-
|
|
524
|
+
//# sourceMappingURL=chunk-MU7IEPDG.mjs.map
|
|
525
|
+
//# sourceMappingURL=chunk-MU7IEPDG.mjs.map
|