@sonordev/site-kit 5.0.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/agent-manifest.json +30 -6
- package/dist/AnalyticsProvider-2YV4ZVZD.js +31 -0
- package/dist/{AnalyticsProvider-TXM3DRLI.js.map → AnalyticsProvider-2YV4ZVZD.js.map} +1 -1
- package/dist/AnalyticsProvider-XSPROVHM.mjs +10 -0
- package/dist/{AnalyticsProvider-KCLRFOX6.mjs.map → AnalyticsProvider-XSPROVHM.mjs.map} +1 -1
- package/dist/{BlogViewTracker-J6LRGUHU.js → BlogViewTracker-BQBJTL5L.js} +6 -6
- package/dist/{BlogViewTracker-J6LRGUHU.js.map → BlogViewTracker-BQBJTL5L.js.map} +1 -1
- package/dist/{BlogViewTracker-PZWAWB3O.mjs → BlogViewTracker-FEAXIUEW.mjs} +5 -5
- package/dist/{BlogViewTracker-PZWAWB3O.mjs.map → BlogViewTracker-FEAXIUEW.mjs.map} +1 -1
- package/dist/ChatWidget-B2DI2OWH.mjs +11 -0
- package/dist/{ChatWidget-PMDPSZXG.mjs.map → ChatWidget-B2DI2OWH.mjs.map} +1 -1
- package/dist/ChatWidget-GPBFJ4A3.js +20 -0
- package/dist/{ChatWidget-LD54Z2PQ.js.map → ChatWidget-GPBFJ4A3.js.map} +1 -1
- package/dist/EngageWidget-JXJSFONK.js +18 -0
- package/dist/{EngageWidget-BF5NMQ5A.js.map → EngageWidget-JXJSFONK.js.map} +1 -1
- package/dist/EngageWidget-KF4HU4YA.mjs +9 -0
- package/dist/{EngageWidget-4CBFRN4Y.mjs.map → EngageWidget-KF4HU4YA.mjs.map} +1 -1
- package/dist/{FileField-VXADIOMN.js → FileField-22BB34L3.js} +6 -6
- package/dist/{FileField-VXADIOMN.js.map → FileField-22BB34L3.js.map} +1 -1
- package/dist/{FileField-FFRHS3XP.mjs → FileField-WI6SADBA.mjs} +5 -5
- package/dist/{FileField-FFRHS3XP.mjs.map → FileField-WI6SADBA.mjs.map} +1 -1
- package/dist/{FormSpotlight-KE46EDP5.mjs → FormSpotlight-6FZV6JOA.mjs} +3 -3
- package/dist/{FormSpotlight-KE46EDP5.mjs.map → FormSpotlight-6FZV6JOA.mjs.map} +1 -1
- package/dist/{FormSpotlight-JG4ZHS56.js → FormSpotlight-QCIN67NB.js} +15 -15
- package/dist/{FormSpotlight-JG4ZHS56.js.map → FormSpotlight-QCIN67NB.js.map} +1 -1
- package/dist/{FormStage-PZKWEXZE.mjs → FormStage-P3IIHVCC.mjs} +3 -3
- package/dist/{FormStage-PZKWEXZE.mjs.map → FormStage-P3IIHVCC.mjs.map} +1 -1
- package/dist/{FormStage-5Z53Z2WY.js → FormStage-SBQQZDW6.js} +13 -13
- package/dist/{FormStage-5Z53Z2WY.js.map → FormStage-SBQQZDW6.js.map} +1 -1
- package/dist/ManagedForm-6LII4DED.js +23 -0
- package/dist/{ManagedForm-E4WHMARH.js.map → ManagedForm-6LII4DED.js.map} +1 -1
- package/dist/ManagedForm-O3JRRWZ6.mjs +14 -0
- package/dist/{ManagedForm-VU2TKV6J.mjs.map → ManagedForm-O3JRRWZ6.mjs.map} +1 -1
- package/dist/{ManagedNewsletterForm-6DET3VGZ.mjs → ManagedNewsletterForm-FFFF5UEX.mjs} +7 -7
- package/dist/{ManagedNewsletterForm-6DET3VGZ.mjs.map → ManagedNewsletterForm-FFFF5UEX.mjs.map} +1 -1
- package/dist/{ManagedNewsletterForm-PG4EX3W6.js → ManagedNewsletterForm-Z7NYZPAN.js} +8 -8
- package/dist/{ManagedNewsletterForm-PG4EX3W6.js.map → ManagedNewsletterForm-Z7NYZPAN.js.map} +1 -1
- package/dist/{SignalCore-RZVTLUC2.mjs → SignalCore-IUOSVYUE.mjs} +5 -5
- package/dist/{SignalCore-RZVTLUC2.mjs.map → SignalCore-IUOSVYUE.mjs.map} +1 -1
- package/dist/{SignalCore-TCUVXVMN.js → SignalCore-WL5WKQEI.js} +11 -11
- package/dist/{SignalCore-TCUVXVMN.js.map → SignalCore-WL5WKQEI.js.map} +1 -1
- package/dist/SitemapSync-2O5UM4GM.mjs +8 -0
- package/dist/{SitemapSync-T75NAFB3.mjs.map → SitemapSync-2O5UM4GM.mjs.map} +1 -1
- package/dist/SitemapSync-LA67GKKS.js +17 -0
- package/dist/{SitemapSync-AJU4DM3G.js.map → SitemapSync-LA67GKKS.js.map} +1 -1
- package/dist/analytics/index.js +9 -9
- package/dist/analytics/index.mjs +4 -4
- package/dist/blog/index.d.ts +9 -5
- package/dist/blog/index.d.ts.map +1 -1
- 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-LZY374FB.js → chunk-2OZ2SMZH.js} +4 -4
- package/dist/{chunk-LZY374FB.js.map → chunk-2OZ2SMZH.js.map} +1 -1
- package/dist/{chunk-BQY3FJ3R.js → chunk-3DQXXYCS.js} +8 -8
- package/dist/{chunk-BQY3FJ3R.js.map → chunk-3DQXXYCS.js.map} +1 -1
- package/dist/chunk-3PTKHSF7.mjs +6 -0
- package/dist/{chunk-JPTJGLKS.mjs.map → chunk-3PTKHSF7.mjs.map} +1 -1
- package/dist/{chunk-PPCTL4B5.mjs → chunk-4TU246Q4.mjs} +10 -3
- package/dist/chunk-4TU246Q4.mjs.map +1 -0
- package/dist/{chunk-3FZ76VI5.js → chunk-4YJFC6FC.js} +14 -14
- package/dist/{chunk-3FZ76VI5.js.map → chunk-4YJFC6FC.js.map} +1 -1
- package/dist/{chunk-7T4VMXKD.js → chunk-5I7UEXJL.js} +3 -3
- package/dist/{chunk-7T4VMXKD.js.map → chunk-5I7UEXJL.js.map} +1 -1
- package/dist/{chunk-MGPHBVOK.js → chunk-6ABEAO2K.js} +9 -9
- package/dist/{chunk-MGPHBVOK.js.map → chunk-6ABEAO2K.js.map} +1 -1
- package/dist/{chunk-SW6KWUNI.mjs → chunk-6RYCAW5B.mjs} +3 -3
- package/dist/{chunk-SW6KWUNI.mjs.map → chunk-6RYCAW5B.mjs.map} +1 -1
- package/dist/{chunk-MVJQ4ZA2.mjs → chunk-7MQR4KDL.mjs} +4 -4
- package/dist/{chunk-MVJQ4ZA2.mjs.map → chunk-7MQR4KDL.mjs.map} +1 -1
- package/dist/{chunk-FQ3JQV3M.js → chunk-7NMI7M56.js} +4 -4
- package/dist/{chunk-FQ3JQV3M.js.map → chunk-7NMI7M56.js.map} +1 -1
- package/dist/{chunk-XPAHKYX7.mjs → chunk-7OCQ4646.mjs} +3 -3
- package/dist/{chunk-XPAHKYX7.mjs.map → chunk-7OCQ4646.mjs.map} +1 -1
- package/dist/chunk-AEW7EFYU.js +147 -0
- package/dist/chunk-AEW7EFYU.js.map +1 -0
- package/dist/{chunk-OGPS3DKE.js → chunk-B7FHRTBA.js} +5 -5
- package/dist/{chunk-OGPS3DKE.js.map → chunk-B7FHRTBA.js.map} +1 -1
- package/dist/{chunk-SXPINIJG.mjs → chunk-BAFGZQWP.mjs} +4 -4
- package/dist/{chunk-SXPINIJG.mjs.map → chunk-BAFGZQWP.mjs.map} +1 -1
- package/dist/chunk-BIZT7AYK.js +8 -0
- package/dist/{chunk-S65JSRWW.js.map → chunk-BIZT7AYK.js.map} +1 -1
- package/dist/{chunk-EISVUD75.mjs → chunk-D2D3YKWT.mjs} +3 -3
- package/dist/{chunk-EISVUD75.mjs.map → chunk-D2D3YKWT.mjs.map} +1 -1
- package/dist/{chunk-FTBABL3T.mjs → chunk-DXFZA34N.mjs} +3 -3
- package/dist/{chunk-FTBABL3T.mjs.map → chunk-DXFZA34N.mjs.map} +1 -1
- package/dist/{chunk-4UHO6DPJ.mjs → chunk-F4DZH4E5.mjs} +4 -4
- package/dist/{chunk-4UHO6DPJ.mjs.map → chunk-F4DZH4E5.mjs.map} +1 -1
- package/dist/{chunk-QH3WVTEK.mjs → chunk-FDLFXHUU.mjs} +4 -4
- package/dist/{chunk-QH3WVTEK.mjs.map → chunk-FDLFXHUU.mjs.map} +1 -1
- package/dist/{chunk-MWXBRCM2.mjs → chunk-GCY6VPBG.mjs} +3 -3
- package/dist/{chunk-MWXBRCM2.mjs.map → chunk-GCY6VPBG.mjs.map} +1 -1
- package/dist/{chunk-LLKXPJSE.js → chunk-GF7CQMBL.js} +7 -7
- package/dist/{chunk-LLKXPJSE.js.map → chunk-GF7CQMBL.js.map} +1 -1
- package/dist/{chunk-FDWDXMJL.mjs → chunk-HHX72A2B.mjs} +3 -3
- package/dist/{chunk-FDWDXMJL.mjs.map → chunk-HHX72A2B.mjs.map} +1 -1
- package/dist/{chunk-UFURXSAI.mjs → chunk-ITV76G3L.mjs} +39 -3
- package/dist/chunk-ITV76G3L.mjs.map +1 -0
- package/dist/{chunk-N3RWPAR5.js → chunk-JJ6AI6LW.js} +5 -5
- package/dist/{chunk-N3RWPAR5.js.map → chunk-JJ6AI6LW.js.map} +1 -1
- package/dist/{chunk-AXYKBQVI.mjs → chunk-K447AIIB.mjs} +8 -8
- package/dist/{chunk-AXYKBQVI.mjs.map → chunk-K447AIIB.mjs.map} +1 -1
- package/dist/{chunk-6QQFEJIP.js → chunk-K6YQ73IX.js} +8 -8
- package/dist/{chunk-6QQFEJIP.js.map → chunk-K6YQ73IX.js.map} +1 -1
- package/dist/{chunk-FCKL5CF3.mjs → chunk-KC2FCI5V.mjs} +3 -3
- package/dist/{chunk-FCKL5CF3.mjs.map → chunk-KC2FCI5V.mjs.map} +1 -1
- package/dist/{chunk-YVE65W47.mjs → chunk-KHHMU4UH.mjs} +4 -4
- package/dist/{chunk-YVE65W47.mjs.map → chunk-KHHMU4UH.mjs.map} +1 -1
- package/dist/{chunk-HEUCDF3O.mjs → chunk-KIGIAA6Q.mjs} +3 -3
- package/dist/{chunk-HEUCDF3O.mjs.map → chunk-KIGIAA6Q.mjs.map} +1 -1
- package/dist/{chunk-5OHHYGD6.js → chunk-KMNIW4K4.js} +15 -15
- package/dist/{chunk-5OHHYGD6.js.map → chunk-KMNIW4K4.js.map} +1 -1
- package/dist/{chunk-PZE5L2FK.mjs → chunk-LCRWCOPI.mjs} +4 -4
- package/dist/{chunk-PZE5L2FK.mjs.map → chunk-LCRWCOPI.mjs.map} +1 -1
- package/dist/{chunk-62ODUWUX.js → chunk-LFBMFACZ.js} +40 -4
- package/dist/chunk-LFBMFACZ.js.map +1 -0
- package/dist/chunk-MLXUMVBK.mjs +142 -0
- package/dist/chunk-MLXUMVBK.mjs.map +1 -0
- package/dist/{chunk-3SMG4EXR.mjs → chunk-MOVMDHGE.mjs} +3 -3
- package/dist/{chunk-3SMG4EXR.mjs.map → chunk-MOVMDHGE.mjs.map} +1 -1
- package/dist/{chunk-KWH6RAVY.js → chunk-NQLE3V3T.js} +11 -11
- package/dist/{chunk-KWH6RAVY.js.map → chunk-NQLE3V3T.js.map} +1 -1
- package/dist/{chunk-5JNLZRLX.mjs → chunk-O2KN2SC7.mjs} +3 -3
- package/dist/{chunk-5JNLZRLX.mjs.map → chunk-O2KN2SC7.mjs.map} +1 -1
- package/dist/{chunk-IX5RAZWX.js → chunk-OQBDJRVP.js} +6 -6
- package/dist/{chunk-IX5RAZWX.js.map → chunk-OQBDJRVP.js.map} +1 -1
- package/dist/{chunk-MPO6TRLS.js → chunk-PEIWCJXS.js} +14 -14
- package/dist/{chunk-MPO6TRLS.js.map → chunk-PEIWCJXS.js.map} +1 -1
- package/dist/{chunk-VACFVPNF.js → chunk-RE54UKET.js} +14 -14
- package/dist/{chunk-VACFVPNF.js.map → chunk-RE54UKET.js.map} +1 -1
- package/dist/{chunk-4D2G5ZNP.js → chunk-RMLRZOVE.js} +15 -15
- package/dist/{chunk-4D2G5ZNP.js.map → chunk-RMLRZOVE.js.map} +1 -1
- package/dist/{chunk-GCZMDY2T.js → chunk-ULGTZ2VU.js} +3 -3
- package/dist/{chunk-GCZMDY2T.js.map → chunk-ULGTZ2VU.js.map} +1 -1
- package/dist/{chunk-JY3KZDEM.js → chunk-VTEIXKBA.js} +3 -3
- package/dist/{chunk-JY3KZDEM.js.map → chunk-VTEIXKBA.js.map} +1 -1
- package/dist/{chunk-YGRHEEPX.mjs → chunk-W7QMCG2A.mjs} +7 -7
- package/dist/{chunk-YGRHEEPX.mjs.map → chunk-W7QMCG2A.mjs.map} +1 -1
- package/dist/{chunk-HCN2US4M.js → chunk-WZ6QNL2D.js} +18 -18
- package/dist/{chunk-HCN2US4M.js.map → chunk-WZ6QNL2D.js.map} +1 -1
- package/dist/{chunk-YSZB2DRX.mjs → chunk-X4H2RAIQ.mjs} +3 -3
- package/dist/{chunk-YSZB2DRX.mjs.map → chunk-X4H2RAIQ.mjs.map} +1 -1
- package/dist/{chunk-JIUVBSQZ.js → chunk-X6SAM7CR.js} +3 -3
- package/dist/{chunk-JIUVBSQZ.js.map → chunk-X6SAM7CR.js.map} +1 -1
- package/dist/{chunk-J4JXAFSW.mjs → chunk-XRONA2QL.mjs} +3 -3
- package/dist/{chunk-J4JXAFSW.mjs.map → chunk-XRONA2QL.mjs.map} +1 -1
- package/dist/{chunk-5JQLLF2T.js → chunk-XYZZJZGS.js} +12 -12
- package/dist/{chunk-5JQLLF2T.js.map → chunk-XYZZJZGS.js.map} +1 -1
- package/dist/{chunk-DX6D7HDI.js → chunk-XZI6KUB3.js} +4 -4
- package/dist/{chunk-DX6D7HDI.js.map → chunk-XZI6KUB3.js.map} +1 -1
- package/dist/{chunk-UV6KQUSL.mjs → chunk-YDIINUWD.mjs} +3 -3
- package/dist/{chunk-UV6KQUSL.mjs.map → chunk-YDIINUWD.mjs.map} +1 -1
- package/dist/{chunk-QI2H5KK6.mjs → chunk-YUOVAWCL.mjs} +4 -4
- package/dist/{chunk-QI2H5KK6.mjs.map → chunk-YUOVAWCL.mjs.map} +1 -1
- package/dist/{chunk-UABMMR7U.js → chunk-Z33JXJTJ.js} +10 -3
- package/dist/chunk-Z33JXJTJ.js.map +1 -0
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.mjs +1 -1
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/commerce/index.js +56 -56
- package/dist/commerce/index.mjs +5 -5
- package/dist/commerce/server-result.test.d.ts +2 -0
- package/dist/commerce/server-result.test.d.ts.map +1 -0
- package/dist/commerce/server.d.ts +51 -0
- package/dist/commerce/server.d.ts.map +1 -1
- package/dist/commerce/server.js +57 -24
- package/dist/commerce/server.js.map +1 -1
- package/dist/commerce/server.mjs +54 -25
- package/dist/commerce/server.mjs.map +1 -1
- package/dist/engage/index.js +13 -13
- package/dist/engage/index.mjs +7 -7
- package/dist/fleet/index.js +8 -8
- package/dist/fleet/index.mjs +4 -4
- package/dist/forms/ga4-identity.d.ts +31 -0
- package/dist/forms/ga4-identity.d.ts.map +1 -0
- package/dist/forms/ga4-identity.test.d.ts +2 -0
- package/dist/forms/ga4-identity.test.d.ts.map +1 -0
- package/dist/forms/index.js +22 -22
- 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/forms/submitForm.d.ts.map +1 -1
- package/dist/forms/types.d.ts +13 -0
- package/dist/forms/types.d.ts.map +1 -1
- package/dist/images/index.js +11 -11
- package/dist/images/index.mjs +4 -4
- package/dist/index.js +63 -63
- package/dist/index.mjs +9 -9
- 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/llms/generateLLMsTxt.d.ts.map +1 -1
- package/dist/llms/humanize-industry.d.ts +9 -0
- package/dist/llms/humanize-industry.d.ts.map +1 -0
- package/dist/llms/humanize-industry.test.d.ts +2 -0
- package/dist/llms/humanize-industry.test.d.ts.map +1 -0
- package/dist/llms/index.js +12 -12
- package/dist/llms/index.mjs +2 -2
- package/dist/maps/index.js +5 -5
- package/dist/maps/index.mjs +3 -3
- package/dist/motion/Parallax.d.ts +42 -0
- package/dist/motion/Parallax.d.ts.map +1 -0
- package/dist/motion/Reveal.d.ts +59 -0
- package/dist/motion/Reveal.d.ts.map +1 -0
- package/dist/motion/ScrollScene.d.ts +42 -0
- package/dist/motion/ScrollScene.d.ts.map +1 -0
- package/dist/motion/engine.d.ts +74 -0
- package/dist/motion/engine.d.ts.map +1 -0
- package/dist/motion/failsafe.d.ts +20 -0
- package/dist/motion/failsafe.d.ts.map +1 -0
- package/dist/motion/gsap.d.ts +84 -0
- package/dist/motion/gsap.d.ts.map +1 -0
- package/dist/motion/gsap.js +103 -0
- package/dist/motion/gsap.js.map +1 -0
- package/dist/motion/gsap.mjs +99 -0
- package/dist/motion/gsap.mjs.map +1 -0
- package/dist/motion/index.d.ts +31 -0
- package/dist/motion/index.d.ts.map +1 -0
- package/dist/motion/index.js +252 -0
- package/dist/motion/index.js.map +1 -0
- package/dist/motion/index.mjs +230 -0
- package/dist/motion/index.mjs.map +1 -0
- package/dist/motion/motion.test.d.ts +16 -0
- package/dist/motion/motion.test.d.ts.map +1 -0
- package/dist/motion/three.d.ts +105 -0
- package/dist/motion/three.d.ts.map +1 -0
- package/dist/motion/three.js +150 -0
- package/dist/motion/three.js.map +1 -0
- package/dist/motion/three.mjs +145 -0
- package/dist/motion/three.mjs.map +1 -0
- package/dist/search/index.js +4 -4
- package/dist/search/index.mjs +3 -3
- package/dist/seo/client.js +5 -5
- package/dist/seo/client.mjs +4 -4
- package/dist/seo/index.js +5 -5
- package/dist/seo/index.mjs +4 -4
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/dist/shared/build-entries.d.ts +7 -1
- package/dist/shared/build-entries.d.ts.map +1 -1
- package/dist/shared/version.d.ts +1 -1
- 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 +16 -16
- package/dist/sync/index.mjs +4 -4
- package/package.json +28 -2
- package/skills/site-kit/SKILL.md +6 -0
- package/dist/AnalyticsProvider-KCLRFOX6.mjs +0 -10
- package/dist/AnalyticsProvider-TXM3DRLI.js +0 -31
- package/dist/ChatWidget-LD54Z2PQ.js +0 -20
- package/dist/ChatWidget-PMDPSZXG.mjs +0 -11
- package/dist/EngageWidget-4CBFRN4Y.mjs +0 -9
- package/dist/EngageWidget-BF5NMQ5A.js +0 -18
- package/dist/ManagedForm-E4WHMARH.js +0 -23
- package/dist/ManagedForm-VU2TKV6J.mjs +0 -14
- package/dist/SitemapSync-AJU4DM3G.js +0 -17
- package/dist/SitemapSync-T75NAFB3.mjs +0 -8
- package/dist/chunk-62ODUWUX.js.map +0 -1
- package/dist/chunk-JPTJGLKS.mjs +0 -6
- package/dist/chunk-PPCTL4B5.mjs.map +0 -1
- package/dist/chunk-S65JSRWW.js +0 -8
- package/dist/chunk-UABMMR7U.js.map +0 -1
- package/dist/chunk-UFURXSAI.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":["sanitizeFleetVersion","buildFleetHeartbeatPayload","SITE_KIT_VERSION","sonorFetch","useDeferredActivation","useRef","useEffect"],"mappings":"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","file":"chunk-LLKXPJSE.js","sourcesContent":["/**\n * @sonordev/site-kit/fleet — heartbeat sender (single source of truth for the POST)\n *\n * One place that turns fleet inputs into a validated payload and ships it to\n * `/api/public/fleet/heartbeat`. Used by BOTH the client component\n * (FleetHeartbeat) and any build-time caller, so the send path never forks.\n *\n * It routes through the canonical `sonorFetch` transport, which already stamps\n * the x-sitekit-version header, attaches x-api-key, and honors the auth\n * circuit breaker — a site on a stale/canceled key will not spam the API.\n */\n\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport {\n buildFleetHeartbeatPayload,\n sanitizeFleetVersion,\n type FleetHeartbeatPayload,\n type FleetHeartbeatSource,\n type FleetModule,\n} from './contract'\n\n/** Canonical ingest path — resolved against the site's Sonor API origin. */\nexport const FLEET_HEARTBEAT_PATH = '/api/public/fleet/heartbeat'\n\nexport interface SendFleetHeartbeatOptions {\n /** Sonor API origin (e.g. https://api.sonor.io). Trailing slash tolerated. */\n apiUrl: string\n /** Project API key — scopes the request; the project is resolved server-side. */\n apiKey: string\n /** Client-observable active modules (validated/deduped by the contract). */\n modules?: FleetModule[] | string[]\n /** Normalized host for the multi-site sub-dimension (server re-normalizes). */\n site?: string | null\n /** Installed Next.js version, if the caller can resolve it. */\n nextVersion?: string | null\n /** 'client' (runtime boot) or 'build' (CI). Default 'client'. */\n source?: FleetHeartbeatSource\n /** Override the reported kit version. Defaults to the runtime SITE_KIT_VERSION. */\n kitVersion?: string\n /** Log one info line with the result. Never logs at error level. */\n debug?: boolean\n}\n\n/**\n * Best-effort runtime detection of the host Next.js version. Unreliable by\n * design — Next does not expose its version consistently across app/pages\n * router — so it returns null unless a site injects `__SITE_KIT_NEXT_VERSION__`\n * or `window.next.version` is present. Build-time callers should pass\n * `nextVersion` explicitly (resolvable from `next/package.json`).\n */\nexport function detectNextVersion(): string | null {\n if (typeof window === 'undefined') return null\n const w = window as unknown as {\n __SITE_KIT_NEXT_VERSION__?: string\n next?: { version?: string }\n }\n return sanitizeFleetVersion(w.__SITE_KIT_NEXT_VERSION__ ?? w.next?.version ?? null)\n}\n\n/**\n * Fire one heartbeat. Fire-and-forget by contract: it never throws — a\n * telemetry POST must never break a visitor's page or a site's build. Returns\n * true only when the POST was accepted (2xx).\n */\nexport async function sendFleetHeartbeat(opts: SendFleetHeartbeatOptions): Promise<boolean> {\n const { apiUrl, apiKey } = opts\n if (!apiUrl || !apiKey) return false\n\n const payload: FleetHeartbeatPayload = buildFleetHeartbeatPayload({\n kitVersion: opts.kitVersion || SITE_KIT_VERSION,\n nextVersion: opts.nextVersion,\n modules: opts.modules,\n site: opts.site,\n source: opts.source,\n })\n\n try {\n const res = await sonorFetch(\n `${apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n // retries: 0 — the once-per-session guard already bounds volume, and a\n // missed heartbeat is harmless (the next page load or passive\n // x-sitekit-version capture re-registers the site).\n { apiKey, retries: 0 },\n )\n if (opts.debug) {\n // info, never error: a visitor's console must stay clean (Lighthouse).\n console.info(\n `[Sonor fleet] heartbeat ${res.status} v${payload.kit_version} [${payload.modules.join(',') || 'none'}]`,\n )\n }\n return res.ok\n } catch (err) {\n if (opts.debug) console.info('[Sonor fleet] heartbeat skipped (network)', err)\n return false\n }\n}\n","/**\n * @sonordev/site-kit/fleet — FleetHeartbeat\n *\n * A childless, effect-only client component that reports this site's build\n * fingerprint (kit version, Next.js version, active modules, host) to Sonor\n * exactly once per browser session. It exists so the platform can answer\n * \"which version / modules / key state is each site running\" without shelling\n * into every client repo.\n *\n * It is mounted as a deferred, CHILDLESS SIBLING — never a wrapper — by\n * SiteKitClientProviders, behind the same idle/first-interaction gate as\n * Analytics and Engage, so it never enters the critical hydration path (see\n * CLAUDE.md \"the page must server-render\"). It renders null and carries no\n * visitor data.\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { detectNextVersion, sendFleetHeartbeat } from './heartbeat'\nimport type { FleetModule } from './contract'\n\n/** Session guard key — one heartbeat per session, re-sent when version/host changes. */\nconst SESSION_GUARD_KEY = '_sk_fleet_hb'\n\nexport interface FleetHeartbeatProps {\n /** Sonor API origin. Falls back to `window.__SITE_KIT_API_URL__` then api.sonor.io. */\n apiUrl?: string\n /** Project API key. Falls back to `window.__SITE_KIT_API_KEY__`. */\n apiKey?: string\n /** Host for the multi-site sub-dimension. Falls back to `__SITE_KIT_SITE__` then location.host. */\n site?: string | null\n /** Client-observable active modules (SiteKitClientProviders passes the enabled set). */\n modules?: FleetModule[]\n /** Installed Next.js version if known; otherwise best-effort runtime detection. */\n nextVersion?: string | null\n /**\n * Self-defer to idle / first interaction. Default true so standalone use is\n * safe. SiteKitClientProviders already gates the mount and passes false.\n */\n defer?: boolean\n /** Send at most once per browser session per (version, host). Default true. */\n oncePerSession?: boolean\n debug?: boolean\n}\n\nexport function FleetHeartbeat({\n apiUrl,\n apiKey,\n site,\n modules,\n nextVersion,\n defer = true,\n oncePerSession = true,\n debug = false,\n}: FleetHeartbeatProps) {\n const ready = useDeferredActivation(defer)\n const sentRef = useRef(false)\n\n useEffect(() => {\n if (!ready || sentRef.current) return\n if (typeof window === 'undefined') return\n\n const w = window as unknown as {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n __SITE_KIT_SITE__?: string\n }\n const url = apiUrl || w.__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n const key = apiKey || w.__SITE_KIT_API_KEY__\n if (!key) return // no key resolvable — nothing to authenticate the heartbeat\n\n const resolvedSite = site ?? w.__SITE_KIT_SITE__ ?? window.location.host ?? ''\n\n // Once-per-session-per-(version,host): keeps heartbeat volume to one POST\n // per visitor session while still re-reporting after an upgrade or on a\n // different sub-site. Storage-disabled browsers simply send once per load.\n if (oncePerSession) {\n const stamp = `${SITE_KIT_VERSION}:${resolvedSite}`\n try {\n if (sessionStorage.getItem(SESSION_GUARD_KEY) === stamp) {\n sentRef.current = true\n return\n }\n sessionStorage.setItem(SESSION_GUARD_KEY, stamp)\n } catch {\n /* storage unavailable — fall through and send this load */\n }\n }\n\n sentRef.current = true\n void sendFleetHeartbeat({\n apiUrl: url,\n apiKey: key,\n site: resolvedSite,\n modules,\n nextVersion: nextVersion ?? detectNextVersion(),\n source: 'client',\n debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug])\n\n return null\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":["sanitizeFleetVersion","buildFleetHeartbeatPayload","SITE_KIT_VERSION","sonorFetch","useDeferredActivation","useRef","useEffect"],"mappings":"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","file":"chunk-GF7CQMBL.js","sourcesContent":["/**\n * @sonordev/site-kit/fleet — heartbeat sender (single source of truth for the POST)\n *\n * One place that turns fleet inputs into a validated payload and ships it to\n * `/api/public/fleet/heartbeat`. Used by BOTH the client component\n * (FleetHeartbeat) and any build-time caller, so the send path never forks.\n *\n * It routes through the canonical `sonorFetch` transport, which already stamps\n * the x-sitekit-version header, attaches x-api-key, and honors the auth\n * circuit breaker — a site on a stale/canceled key will not spam the API.\n */\n\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport {\n buildFleetHeartbeatPayload,\n sanitizeFleetVersion,\n type FleetHeartbeatPayload,\n type FleetHeartbeatSource,\n type FleetModule,\n} from './contract'\n\n/** Canonical ingest path — resolved against the site's Sonor API origin. */\nexport const FLEET_HEARTBEAT_PATH = '/api/public/fleet/heartbeat'\n\nexport interface SendFleetHeartbeatOptions {\n /** Sonor API origin (e.g. https://api.sonor.io). Trailing slash tolerated. */\n apiUrl: string\n /** Project API key — scopes the request; the project is resolved server-side. */\n apiKey: string\n /** Client-observable active modules (validated/deduped by the contract). */\n modules?: FleetModule[] | string[]\n /** Normalized host for the multi-site sub-dimension (server re-normalizes). */\n site?: string | null\n /** Installed Next.js version, if the caller can resolve it. */\n nextVersion?: string | null\n /** 'client' (runtime boot) or 'build' (CI). Default 'client'. */\n source?: FleetHeartbeatSource\n /** Override the reported kit version. Defaults to the runtime SITE_KIT_VERSION. */\n kitVersion?: string\n /** Log one info line with the result. Never logs at error level. */\n debug?: boolean\n}\n\n/**\n * Best-effort runtime detection of the host Next.js version. Unreliable by\n * design — Next does not expose its version consistently across app/pages\n * router — so it returns null unless a site injects `__SITE_KIT_NEXT_VERSION__`\n * or `window.next.version` is present. Build-time callers should pass\n * `nextVersion` explicitly (resolvable from `next/package.json`).\n */\nexport function detectNextVersion(): string | null {\n if (typeof window === 'undefined') return null\n const w = window as unknown as {\n __SITE_KIT_NEXT_VERSION__?: string\n next?: { version?: string }\n }\n return sanitizeFleetVersion(w.__SITE_KIT_NEXT_VERSION__ ?? w.next?.version ?? null)\n}\n\n/**\n * Fire one heartbeat. Fire-and-forget by contract: it never throws — a\n * telemetry POST must never break a visitor's page or a site's build. Returns\n * true only when the POST was accepted (2xx).\n */\nexport async function sendFleetHeartbeat(opts: SendFleetHeartbeatOptions): Promise<boolean> {\n const { apiUrl, apiKey } = opts\n if (!apiUrl || !apiKey) return false\n\n const payload: FleetHeartbeatPayload = buildFleetHeartbeatPayload({\n kitVersion: opts.kitVersion || SITE_KIT_VERSION,\n nextVersion: opts.nextVersion,\n modules: opts.modules,\n site: opts.site,\n source: opts.source,\n })\n\n try {\n const res = await sonorFetch(\n `${apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n // retries: 0 — the once-per-session guard already bounds volume, and a\n // missed heartbeat is harmless (the next page load or passive\n // x-sitekit-version capture re-registers the site).\n { apiKey, retries: 0 },\n )\n if (opts.debug) {\n // info, never error: a visitor's console must stay clean (Lighthouse).\n console.info(\n `[Sonor fleet] heartbeat ${res.status} v${payload.kit_version} [${payload.modules.join(',') || 'none'}]`,\n )\n }\n return res.ok\n } catch (err) {\n if (opts.debug) console.info('[Sonor fleet] heartbeat skipped (network)', err)\n return false\n }\n}\n","/**\n * @sonordev/site-kit/fleet — FleetHeartbeat\n *\n * A childless, effect-only client component that reports this site's build\n * fingerprint (kit version, Next.js version, active modules, host) to Sonor\n * exactly once per browser session. It exists so the platform can answer\n * \"which version / modules / key state is each site running\" without shelling\n * into every client repo.\n *\n * It is mounted as a deferred, CHILDLESS SIBLING — never a wrapper — by\n * SiteKitClientProviders, behind the same idle/first-interaction gate as\n * Analytics and Engage, so it never enters the critical hydration path (see\n * CLAUDE.md \"the page must server-render\"). It renders null and carries no\n * visitor data.\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { detectNextVersion, sendFleetHeartbeat } from './heartbeat'\nimport type { FleetModule } from './contract'\n\n/** Session guard key — one heartbeat per session, re-sent when version/host changes. */\nconst SESSION_GUARD_KEY = '_sk_fleet_hb'\n\nexport interface FleetHeartbeatProps {\n /** Sonor API origin. Falls back to `window.__SITE_KIT_API_URL__` then api.sonor.io. */\n apiUrl?: string\n /** Project API key. Falls back to `window.__SITE_KIT_API_KEY__`. */\n apiKey?: string\n /** Host for the multi-site sub-dimension. Falls back to `__SITE_KIT_SITE__` then location.host. */\n site?: string | null\n /** Client-observable active modules (SiteKitClientProviders passes the enabled set). */\n modules?: FleetModule[]\n /** Installed Next.js version if known; otherwise best-effort runtime detection. */\n nextVersion?: string | null\n /**\n * Self-defer to idle / first interaction. Default true so standalone use is\n * safe. SiteKitClientProviders already gates the mount and passes false.\n */\n defer?: boolean\n /** Send at most once per browser session per (version, host). Default true. */\n oncePerSession?: boolean\n debug?: boolean\n}\n\nexport function FleetHeartbeat({\n apiUrl,\n apiKey,\n site,\n modules,\n nextVersion,\n defer = true,\n oncePerSession = true,\n debug = false,\n}: FleetHeartbeatProps) {\n const ready = useDeferredActivation(defer)\n const sentRef = useRef(false)\n\n useEffect(() => {\n if (!ready || sentRef.current) return\n if (typeof window === 'undefined') return\n\n const w = window as unknown as {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n __SITE_KIT_SITE__?: string\n }\n const url = apiUrl || w.__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n const key = apiKey || w.__SITE_KIT_API_KEY__\n if (!key) return // no key resolvable — nothing to authenticate the heartbeat\n\n const resolvedSite = site ?? w.__SITE_KIT_SITE__ ?? window.location.host ?? ''\n\n // Once-per-session-per-(version,host): keeps heartbeat volume to one POST\n // per visitor session while still re-reporting after an upgrade or on a\n // different sub-site. Storage-disabled browsers simply send once per load.\n if (oncePerSession) {\n const stamp = `${SITE_KIT_VERSION}:${resolvedSite}`\n try {\n if (sessionStorage.getItem(SESSION_GUARD_KEY) === stamp) {\n sentRef.current = true\n return\n }\n sessionStorage.setItem(SESSION_GUARD_KEY, stamp)\n } catch {\n /* storage unavailable — fall through and send this load */\n }\n }\n\n sentRef.current = true\n void sendFleetHeartbeat({\n apiUrl: url,\n apiKey: key,\n site: resolvedSite,\n modules,\n nextVersion: nextVersion ?? detectNextVersion(),\n source: 'client',\n debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug])\n\n return null\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SITE_KIT_VERSION } from './chunk-
|
|
1
|
+
import { SITE_KIT_VERSION } from './chunk-3PTKHSF7.mjs';
|
|
2
2
|
|
|
3
3
|
// src/shared/siteToken.ts
|
|
4
4
|
var SITE_TOKEN_PREFIX = "st1.";
|
|
@@ -146,5 +146,5 @@ function currentCredential(seed) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
export { configureSiteCredential, credentialBreakerId, currentCredential, getFreshCredential, isSiteToken, isTokenMode, refreshSiteToken };
|
|
149
|
-
//# sourceMappingURL=chunk-
|
|
150
|
-
//# sourceMappingURL=chunk-
|
|
149
|
+
//# sourceMappingURL=chunk-HHX72A2B.mjs.map
|
|
150
|
+
//# sourceMappingURL=chunk-HHX72A2B.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/siteToken.ts"],"names":[],"mappings":";;;AAuBO,IAAM,iBAAA,GAAoB,MAAA;AAGjC,IAAM,kBAAkB,CAAA,GAAI,GAAA;AAC5B,IAAM,kBAAA,GAAqB,GAAA;AAC3B,IAAM,WAAA,GAAc,sBAAA;AAEb,SAAS,YAAY,UAAA,EAAqC;AAC/D,EAAA,OAAO,OAAO,UAAA,KAAe,QAAA,IAAY,UAAA,CAAW,WAAW,iBAAiB,CAAA;AAClF;AASO,SAAS,sBAAsB,KAAA,EAA6C;AACjF,EAAA,IAAI,CAAC,WAAA,CAAY,KAAK,CAAA,EAAG,OAAO,IAAA;AAChC,EAAA,MAAM,GAAA,GAAO,KAAA,CAAiB,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AAC1C,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,IAAI;AAEF,IAAA,MAAM,GAAA,GAAM,IAAI,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAM,GAAG,CAAA;AACpD,IAAA,MAAM,IAAA,GACJ,OAAO,IAAA,KAAS,UAAA,GACZ,IAAA,CAAK,GAAG,CAAA,GACR,MAAA,CAAO,IAAA,CAAK,GAAA,EAAK,WAAW,CAAA,CAAE,SAAS,MAAM,CAAA;AACnD,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B,IAAA,OAAO,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,GAAS,MAAA,GAAS,IAAA;AAAA,EACzD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAOO,SAAS,oBAAoB,UAAA,EAAoC;AACtE,EAAA,IAAI,CAAC,YAAY,OAAO,aAAA;AACxB,EAAA,MAAM,MAAA,GAAS,sBAAsB,UAAU,CAAA;AAC/C,EAAA,IAAI,MAAA,EAAQ,GAAA,EAAK,OAAO,CAAA,IAAA,EAAO,OAAO,GAAG,CAAA,CAAA;AACzC,EAAA,OAAO,UAAA,CAAW,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAC/B;AAeA,SAAS,iBAAA,GAAgC;AACvC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,MAAA,EAAQ,sBAAA;AAAA,IACR,KAAA,EAAO,IAAA;AAAA,IACP,UAAA,EAAY;AAAA,GACd;AACF;AAcA,IAAM,eAAA,GAAkB,4BAAA;AAExB,IAAM,SAAqB,MAAM;AAC/B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,iBAAA,EAAkB;AAC5D,EAAA,MAAM,CAAA,GAAI,MAAA;AACV,EAAA,OAAQ,4CAAuB,iBAAA,EAAkB,CAAA;AACnD,CAAA,GAAG;AAEH,SAAS,eAAA,GAAiC;AACxC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,cAAA,CAAe,OAAA,CAAQ,WAAW,CAAA;AACrD,IAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC7B,IAAA,OAAO,OAAO,MAAA,EAAQ,KAAA,KAAU,QAAA,GAAW,OAAO,KAAA,GAAQ,IAAA;AAAA,EAC5D,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,WAAW,KAAA,EAAqB;AACvC,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,cAAA,CAAe,QAAQ,WAAA,EAAa,IAAA,CAAK,UAAU,EAAE,KAAA,EAAO,CAAC,CAAA;AAAA,EACtE,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,IAAI;AACF,IAAA;AAAC,IAAC,OAAe,oBAAA,GAAuB,KAAA;AAAA,EAC1C,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAEA,SAAS,SAAS,KAAA,EAA8B;AAC9C,EAAA,MAAM,MAAA,GAAS,sBAAsB,KAAK,CAAA;AAC1C,EAAA,OAAO,OAAO,MAAA,EAAQ,GAAA,KAAQ,QAAA,GAAW,MAAA,CAAO,MAAM,GAAA,GAAO,CAAA;AAC/D;AAMO,SAAS,wBAAwB,IAAA,EAG/B;AACP,EAAA,KAAA,CAAM,OAAO,IAAA,CAAK,UAAA;AAClB,EAAA,KAAA,CAAM,OAAA,GAAU,WAAA,CAAY,IAAA,CAAK,UAAU,CAAA;AAC3C,EAAA,IAAI,IAAA,CAAK,QAAQ,KAAA,CAAM,MAAA,GAAS,KAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAE9D,EAAA,IAAI,CAAC,MAAM,OAAA,EAAS;AAClB,IAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AACd,IAAA;AAAA,EACF;AAKA,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,IAAI,IAAA,GAAO,KAAK,UAAA,IAAc,IAAA;AAC9B,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,IAAI,UAAU,QAAA,CAAS,MAAM,IAAI,QAAA,CAAS,IAAI,GAAG,IAAA,GAAO,MAAA;AACxD,EAAA,IAAI,SAAS,QAAA,CAAS,KAAK,IAAI,QAAA,CAAS,IAAI,GAAG,IAAA,GAAO,KAAA;AACtD,EAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AAChB;AAEA,SAAS,YAAA,GAAwB;AAC/B,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,KAAA;AAC3B,EAAA,IAAI,CAAC,KAAA,CAAM,KAAA,EAAO,OAAO,IAAA;AACzB,EAAA,OAAO,KAAK,GAAA,EAAI,IAAK,QAAA,CAAS,KAAA,CAAM,KAAK,CAAA,GAAI,eAAA;AAC/C;AAEA,eAAe,SAAA,GAAoC;AACjD,EAAA,MAAM,UAAU,KAAA,CAAM,KAAA;AACtB,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,MAAM,CAAA,8BAAA,CAAA,EAAkC;AAAA,MACvE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,mBAAA,EAAqB,gBAAA;AAAA;AAAA;AAAA,QAGrB,WAAA,EAAa;AAAA,OACf;AAAA,MACA,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,SAAS,CAAA;AAAA,MACvC,MAAA,EACE,OAAO,WAAA,KAAgB,WAAA,IAAe,YAAY,OAAA,GAC9C,WAAA,CAAY,OAAA,CAAQ,kBAAkB,CAAA,GACtC,KAAA;AAAA,KACP,CAAA;AACD,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,OAAO,IAAA;AACpB,IAAA,MAAM,OAAO,MAAM,GAAA,CAAI,MAAK,CAAE,KAAA,CAAM,MAAM,IAAI,CAAA;AAC9C,IAAA,MAAM,OAAO,IAAA,EAAM,KAAA;AACnB,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,WAAA,CAAY,IAAI,CAAA,EAAG;AACjD,MAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AACd,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAGO,SAAS,gBAAA,GAA2C;AACzD,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAC/C,EAAA,IAAI,KAAA,CAAM,UAAA,EAAY,OAAO,KAAA,CAAM,UAAA;AACnC,EAAA,KAAA,CAAM,UAAA,GAAa,SAAA,EAAU,CAAE,OAAA,CAAQ,MAAM;AAC3C,IAAA,KAAA,CAAM,UAAA,GAAa,IAAA;AAAA,EACrB,CAAC,CAAA;AACD,EAAA,OAAO,KAAA,CAAM,UAAA;AACf;AAMA,eAAsB,mBACpB,IAAA,EAC6B;AAI7B,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,IAAW,WAAA,CAAY,IAAI,CAAA,EAAG;AACvC,IAAA,uBAAA,CAAwB,EAAE,UAAA,EAAY,IAAA,EAAM,CAAA;AAAA,EAC9C;AAEA,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,QAAQ,KAAA,CAAM,IAAA;AACzC,EAAA,IAAI,cAAa,EAAG;AAClB,IAAA,MAAM,SAAA,GAAY,MAAM,gBAAA,EAAiB;AACzC,IAAA,IAAI,WAAW,OAAO,SAAA;AAAA,EAGxB;AACA,EAAA,OAAO,KAAA,CAAM,KAAA,IAAS,IAAA,IAAQ,KAAA,CAAM,IAAA;AACtC;AAGO,SAAS,WAAA,GAAuB;AACrC,EAAA,OAAO,KAAA,CAAM,OAAA;AACf;AAOO,SAAS,kBAAkB,IAAA,EAAmC;AACnE,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,QAAQ,KAAA,CAAM,IAAA;AACzC,EAAA,OAAO,KAAA,CAAM,KAAA,IAAS,IAAA,IAAQ,KAAA,CAAM,IAAA;AACtC","file":"chunk-FDWDXMJL.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared — Client token manager\n *\n * SiteKitLayout (a server component) no longer ships the raw `sonor_` API key to\n * the browser. Instead it mints a short-lived, project-scoped token server-side\n * and injects THAT. This module is the browser-side lifecycle for that token:\n *\n * - holds the current token (seeded from the injected value)\n * - a static page's seed can be days old and already expired on first view, so\n * it transparently REFRESHES via the public endpoint before first use\n * - dedupes concurrent refreshes (one in-flight request, awaited by all)\n * - persists the freshest token in sessionStorage so navigations within a\n * session don't each re-refresh\n *\n * Everything here is a no-op passthrough when the injected credential is a raw\n * key (a 2.x kit, or the mint-failed fallback) — legacy behavior is unchanged.\n * Fully SSR-safe: on the server there is no refresh, just the seed.\n *\n * The token payload is NOT secret (only its HMAC is unforgeable), so decoding it\n * client-side to read `exp`/`pid` is fine and needs no crypto.\n */\nimport { SITE_KIT_VERSION } from './version'\n\nexport const SITE_TOKEN_PREFIX = 'st1.'\n\n/** Refresh this many ms before expiry, so a request never races the deadline. */\nconst REFRESH_SKEW_MS = 5 * 60_000\nconst REFRESH_TIMEOUT_MS = 6_000\nconst STORAGE_KEY = '__sonor_site_token__'\n\nexport function isSiteToken(credential?: string | null): boolean {\n return typeof credential === 'string' && credential.startsWith(SITE_TOKEN_PREFIX)\n}\n\ninterface DecodedClaims {\n pid?: string\n iat?: number\n exp?: number\n}\n\n/** Decode the (non-secret) token payload. Returns null if not a token / malformed. */\nexport function decodeSiteTokenClaims(token?: string | null): DecodedClaims | null {\n if (!isSiteToken(token)) return null\n const seg = (token as string).split('.')[1]\n if (!seg) return null\n try {\n // base64url → JSON. atob handles standard base64; restore url-safe chars.\n const b64 = seg.replace(/-/g, '+').replace(/_/g, '/')\n const json =\n typeof atob === 'function'\n ? atob(b64)\n : Buffer.from(seg, 'base64url').toString('utf8')\n const claims = JSON.parse(json)\n return typeof claims === 'object' && claims ? claims : null\n } catch {\n return null\n }\n}\n\n/**\n * Stable id for the auth circuit breaker. Tokens rotate, so keying the breaker\n * by the raw value would reset it every refresh — key by the project id encoded\n * in the token instead. Raw keys use a safe prefix.\n */\nexport function credentialBreakerId(credential?: string | null): string {\n if (!credential) return '(anonymous)'\n const claims = decodeSiteTokenClaims(credential)\n if (claims?.pid) return `pid:${claims.pid}`\n return credential.slice(0, 16)\n}\n\ninterface TokenState {\n /** The credential injected by the layout (token or raw key). */\n seed: string | undefined\n /** Whether we're in token mode (seed is a token). */\n isToken: boolean\n /** Base API URL for the refresh endpoint. */\n apiUrl: string\n /** The freshest known token (>= seed). Null in raw-key mode. */\n token: string | null\n /** In-flight refresh, deduped. */\n refreshing: Promise<string | null> | null\n}\n\nfunction initialTokenState(): TokenState {\n return {\n seed: undefined,\n isToken: false,\n apiUrl: 'https://api.sonor.io',\n token: null,\n refreshing: null,\n }\n}\n\n/**\n * Page-wide singleton, keyed on window. Bundlers can (and in the field, do)\n * inline more than one copy of this module into a page — e.g. an app importing\n * site-kit directly while a transpiled component library bundles its own copy.\n * Module-scoped state then splits into per-copy brains: one copy gets\n * configured and refreshes fine while the other keeps sending the stale\n * prerendered seed, 401s, and trips its breaker (\"sonor-auth-paused\" with a\n * working network). Sharing the state object on window gives every copy the\n * same token, the same deduped refresh, and the same breaker. On the server\n * (no window) each module instance keeps private state — there's no refresh\n * lifecycle there anyway.\n */\nconst TOKEN_STATE_KEY = '__SONOR_SITE_TOKEN_STATE__'\n\nconst state: TokenState = (() => {\n if (typeof window === 'undefined') return initialTokenState()\n const w = window as unknown as Record<string, TokenState | undefined>\n return (w[TOKEN_STATE_KEY] ??= initialTokenState())\n})()\n\nfunction readStoredToken(): string | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.sessionStorage.getItem(STORAGE_KEY)\n if (!raw) return null\n const parsed = JSON.parse(raw)\n return typeof parsed?.token === 'string' ? parsed.token : null\n } catch {\n return null\n }\n}\n\nfunction storeToken(token: string): void {\n if (typeof window === 'undefined') return\n try {\n window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ token }))\n } catch {\n /* private mode / disabled storage — memory-only is fine */\n }\n try {\n ;(window as any).__SITE_KIT_API_KEY__ = token\n } catch {\n /* ignore */\n }\n}\n\nfunction tokenExp(token: string | null): number {\n const claims = decodeSiteTokenClaims(token)\n return typeof claims?.exp === 'number' ? claims.exp * 1000 : 0\n}\n\n/**\n * Configure the manager from the layout-injected values. Adopts a fresher token\n * from sessionStorage if a prior page in this session already refreshed.\n */\nexport function configureSiteCredential(opts: {\n credential?: string\n apiUrl?: string\n}): void {\n state.seed = opts.credential\n state.isToken = isSiteToken(opts.credential)\n if (opts.apiUrl) state.apiUrl = opts.apiUrl.replace(/\\/+$/, '')\n\n if (!state.isToken) {\n state.token = null\n return\n }\n\n // configure runs on every render — never downgrade. Keep the freshest of the\n // injected seed, a token stored by an earlier page this session, and whatever\n // an in-flight/prior refresh already put in memory.\n const prior = state.token\n let best = opts.credential ?? null\n const stored = readStoredToken()\n if (stored && tokenExp(stored) > tokenExp(best)) best = stored\n if (prior && tokenExp(prior) > tokenExp(best)) best = prior\n state.token = best\n}\n\nfunction needsRefresh(): boolean {\n if (!state.isToken) return false\n if (!state.token) return true\n return Date.now() >= tokenExp(state.token) - REFRESH_SKEW_MS\n}\n\nasync function doRefresh(): Promise<string | null> {\n const current = state.token\n if (!current) return null\n try {\n const res = await fetch(`${state.apiUrl}/api/public/site-token/refresh`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n // Send the current token as x-api-key too, so the server's key-scoped\n // throttler buckets refreshes by token rather than by shared CDN IP.\n 'x-api-key': current,\n },\n body: JSON.stringify({ token: current }),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(REFRESH_TIMEOUT_MS)\n : undefined,\n })\n if (!res.ok) return null\n const data = await res.json().catch(() => null)\n const next = data?.token\n if (typeof next === 'string' && isSiteToken(next)) {\n state.token = next\n storeToken(next)\n return next\n }\n return null\n } catch {\n return null\n }\n}\n\n/** Refresh now (deduped). Returns the new token, or null on failure. */\nexport function refreshSiteToken(): Promise<string | null> {\n if (!state.isToken) return Promise.resolve(null)\n if (state.refreshing) return state.refreshing\n state.refreshing = doRefresh().finally(() => {\n state.refreshing = null\n })\n return state.refreshing\n}\n\n/**\n * The credential to put on the next request. In token mode, refreshes a stale\n * seed first. In raw-key mode, returns the seed unchanged (legacy passthrough).\n */\nexport async function getFreshCredential(\n seed?: string,\n): Promise<string | undefined> {\n // Unconfigured but handed a token (a component received the prerendered seed\n // as a prop before any provider configured the manager): adopt it so the\n // refresh machinery works instead of replaying a possibly-expired seed.\n if (!state.isToken && isSiteToken(seed)) {\n configureSiteCredential({ credential: seed })\n }\n // A raw key (or nothing configured) — no token machinery.\n if (!state.isToken) return seed ?? state.seed\n if (needsRefresh()) {\n const refreshed = await refreshSiteToken()\n if (refreshed) return refreshed\n // Refresh failed — fall through to whatever we have; the request may 401 and\n // trip the breaker with friendly copy.\n }\n return state.token ?? seed ?? state.seed\n}\n\n/** Whether the given credential is being managed as a token. */\nexport function isTokenMode(): boolean {\n return state.isToken\n}\n\n/**\n * Best-known credential WITHOUT refreshing — for synchronous unload paths\n * (sonorBeacon) that can't await. In token mode returns the freshest token held;\n * in raw-key mode returns the seed.\n */\nexport function currentCredential(seed?: string): string | undefined {\n if (!state.isToken) return seed ?? state.seed\n return state.token ?? seed ?? state.seed\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/siteToken.ts"],"names":[],"mappings":"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","file":"chunk-HHX72A2B.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared — Client token manager\n *\n * SiteKitLayout (a server component) no longer ships the raw `sonor_` API key to\n * the browser. Instead it mints a short-lived, project-scoped token server-side\n * and injects THAT. This module is the browser-side lifecycle for that token:\n *\n * - holds the current token (seeded from the injected value)\n * - a static page's seed can be days old and already expired on first view, so\n * it transparently REFRESHES via the public endpoint before first use\n * - dedupes concurrent refreshes (one in-flight request, awaited by all)\n * - persists the freshest token in sessionStorage so navigations within a\n * session don't each re-refresh\n *\n * Everything here is a no-op passthrough when the injected credential is a raw\n * key (a 2.x kit, or the mint-failed fallback) — legacy behavior is unchanged.\n * Fully SSR-safe: on the server there is no refresh, just the seed.\n *\n * The token payload is NOT secret (only its HMAC is unforgeable), so decoding it\n * client-side to read `exp`/`pid` is fine and needs no crypto.\n */\nimport { SITE_KIT_VERSION } from './version'\n\nexport const SITE_TOKEN_PREFIX = 'st1.'\n\n/** Refresh this many ms before expiry, so a request never races the deadline. */\nconst REFRESH_SKEW_MS = 5 * 60_000\nconst REFRESH_TIMEOUT_MS = 6_000\nconst STORAGE_KEY = '__sonor_site_token__'\n\nexport function isSiteToken(credential?: string | null): boolean {\n return typeof credential === 'string' && credential.startsWith(SITE_TOKEN_PREFIX)\n}\n\ninterface DecodedClaims {\n pid?: string\n iat?: number\n exp?: number\n}\n\n/** Decode the (non-secret) token payload. Returns null if not a token / malformed. */\nexport function decodeSiteTokenClaims(token?: string | null): DecodedClaims | null {\n if (!isSiteToken(token)) return null\n const seg = (token as string).split('.')[1]\n if (!seg) return null\n try {\n // base64url → JSON. atob handles standard base64; restore url-safe chars.\n const b64 = seg.replace(/-/g, '+').replace(/_/g, '/')\n const json =\n typeof atob === 'function'\n ? atob(b64)\n : Buffer.from(seg, 'base64url').toString('utf8')\n const claims = JSON.parse(json)\n return typeof claims === 'object' && claims ? claims : null\n } catch {\n return null\n }\n}\n\n/**\n * Stable id for the auth circuit breaker. Tokens rotate, so keying the breaker\n * by the raw value would reset it every refresh — key by the project id encoded\n * in the token instead. Raw keys use a safe prefix.\n */\nexport function credentialBreakerId(credential?: string | null): string {\n if (!credential) return '(anonymous)'\n const claims = decodeSiteTokenClaims(credential)\n if (claims?.pid) return `pid:${claims.pid}`\n return credential.slice(0, 16)\n}\n\ninterface TokenState {\n /** The credential injected by the layout (token or raw key). */\n seed: string | undefined\n /** Whether we're in token mode (seed is a token). */\n isToken: boolean\n /** Base API URL for the refresh endpoint. */\n apiUrl: string\n /** The freshest known token (>= seed). Null in raw-key mode. */\n token: string | null\n /** In-flight refresh, deduped. */\n refreshing: Promise<string | null> | null\n}\n\nfunction initialTokenState(): TokenState {\n return {\n seed: undefined,\n isToken: false,\n apiUrl: 'https://api.sonor.io',\n token: null,\n refreshing: null,\n }\n}\n\n/**\n * Page-wide singleton, keyed on window. Bundlers can (and in the field, do)\n * inline more than one copy of this module into a page — e.g. an app importing\n * site-kit directly while a transpiled component library bundles its own copy.\n * Module-scoped state then splits into per-copy brains: one copy gets\n * configured and refreshes fine while the other keeps sending the stale\n * prerendered seed, 401s, and trips its breaker (\"sonor-auth-paused\" with a\n * working network). Sharing the state object on window gives every copy the\n * same token, the same deduped refresh, and the same breaker. On the server\n * (no window) each module instance keeps private state — there's no refresh\n * lifecycle there anyway.\n */\nconst TOKEN_STATE_KEY = '__SONOR_SITE_TOKEN_STATE__'\n\nconst state: TokenState = (() => {\n if (typeof window === 'undefined') return initialTokenState()\n const w = window as unknown as Record<string, TokenState | undefined>\n return (w[TOKEN_STATE_KEY] ??= initialTokenState())\n})()\n\nfunction readStoredToken(): string | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.sessionStorage.getItem(STORAGE_KEY)\n if (!raw) return null\n const parsed = JSON.parse(raw)\n return typeof parsed?.token === 'string' ? parsed.token : null\n } catch {\n return null\n }\n}\n\nfunction storeToken(token: string): void {\n if (typeof window === 'undefined') return\n try {\n window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ token }))\n } catch {\n /* private mode / disabled storage — memory-only is fine */\n }\n try {\n ;(window as any).__SITE_KIT_API_KEY__ = token\n } catch {\n /* ignore */\n }\n}\n\nfunction tokenExp(token: string | null): number {\n const claims = decodeSiteTokenClaims(token)\n return typeof claims?.exp === 'number' ? claims.exp * 1000 : 0\n}\n\n/**\n * Configure the manager from the layout-injected values. Adopts a fresher token\n * from sessionStorage if a prior page in this session already refreshed.\n */\nexport function configureSiteCredential(opts: {\n credential?: string\n apiUrl?: string\n}): void {\n state.seed = opts.credential\n state.isToken = isSiteToken(opts.credential)\n if (opts.apiUrl) state.apiUrl = opts.apiUrl.replace(/\\/+$/, '')\n\n if (!state.isToken) {\n state.token = null\n return\n }\n\n // configure runs on every render — never downgrade. Keep the freshest of the\n // injected seed, a token stored by an earlier page this session, and whatever\n // an in-flight/prior refresh already put in memory.\n const prior = state.token\n let best = opts.credential ?? null\n const stored = readStoredToken()\n if (stored && tokenExp(stored) > tokenExp(best)) best = stored\n if (prior && tokenExp(prior) > tokenExp(best)) best = prior\n state.token = best\n}\n\nfunction needsRefresh(): boolean {\n if (!state.isToken) return false\n if (!state.token) return true\n return Date.now() >= tokenExp(state.token) - REFRESH_SKEW_MS\n}\n\nasync function doRefresh(): Promise<string | null> {\n const current = state.token\n if (!current) return null\n try {\n const res = await fetch(`${state.apiUrl}/api/public/site-token/refresh`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n // Send the current token as x-api-key too, so the server's key-scoped\n // throttler buckets refreshes by token rather than by shared CDN IP.\n 'x-api-key': current,\n },\n body: JSON.stringify({ token: current }),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(REFRESH_TIMEOUT_MS)\n : undefined,\n })\n if (!res.ok) return null\n const data = await res.json().catch(() => null)\n const next = data?.token\n if (typeof next === 'string' && isSiteToken(next)) {\n state.token = next\n storeToken(next)\n return next\n }\n return null\n } catch {\n return null\n }\n}\n\n/** Refresh now (deduped). Returns the new token, or null on failure. */\nexport function refreshSiteToken(): Promise<string | null> {\n if (!state.isToken) return Promise.resolve(null)\n if (state.refreshing) return state.refreshing\n state.refreshing = doRefresh().finally(() => {\n state.refreshing = null\n })\n return state.refreshing\n}\n\n/**\n * The credential to put on the next request. In token mode, refreshes a stale\n * seed first. In raw-key mode, returns the seed unchanged (legacy passthrough).\n */\nexport async function getFreshCredential(\n seed?: string,\n): Promise<string | undefined> {\n // Unconfigured but handed a token (a component received the prerendered seed\n // as a prop before any provider configured the manager): adopt it so the\n // refresh machinery works instead of replaying a possibly-expired seed.\n if (!state.isToken && isSiteToken(seed)) {\n configureSiteCredential({ credential: seed })\n }\n // A raw key (or nothing configured) — no token machinery.\n if (!state.isToken) return seed ?? state.seed\n if (needsRefresh()) {\n const refreshed = await refreshSiteToken()\n if (refreshed) return refreshed\n // Refresh failed — fall through to whatever we have; the request may 401 and\n // trip the breaker with friendly copy.\n }\n return state.token ?? seed ?? state.seed\n}\n\n/** Whether the given credential is being managed as a token. */\nexport function isTokenMode(): boolean {\n return state.isToken\n}\n\n/**\n * Best-known credential WITHOUT refreshing — for synchronous unload paths\n * (sonorBeacon) that can't await. In token mode returns the freshest token held;\n * in raw-key mode returns the seed.\n */\nexport function currentCredential(seed?: string): string | undefined {\n if (!state.isToken) return seed ?? state.seed\n return state.token ?? seed ?? state.seed\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_HONEYPOT_FIELD } from './chunk-TMVKRQGJ.mjs';
|
|
2
|
-
import { sonorFetch } from './chunk-
|
|
2
|
+
import { sonorFetch } from './chunk-F4DZH4E5.mjs';
|
|
3
3
|
|
|
4
4
|
// src/forms/defaultSuccessMessage.ts
|
|
5
5
|
var DEFAULT_FORM_SUCCESS_MESSAGE = "Thank you for your submission! We will be in touch soon.";
|
|
@@ -97,6 +97,40 @@ async function getRecaptchaToken(explicitSiteKey) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// src/forms/ga4-identity.ts
|
|
101
|
+
function readCookie(jar, name) {
|
|
102
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
103
|
+
const match = jar.match(new RegExp(`(?:^|;\\s*)${escaped}=([^;]*)`));
|
|
104
|
+
return match ? decodeURIComponent(match[1]) : void 0;
|
|
105
|
+
}
|
|
106
|
+
function readGa4Identity() {
|
|
107
|
+
if (typeof document === "undefined") return {};
|
|
108
|
+
const jar = document.cookie || "";
|
|
109
|
+
if (!jar) return {};
|
|
110
|
+
const identity = {};
|
|
111
|
+
const ga = readCookie(jar, "_ga");
|
|
112
|
+
if (ga) {
|
|
113
|
+
const parts = ga.split(".");
|
|
114
|
+
if (parts.length >= 4) {
|
|
115
|
+
const clientId = `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
|
|
116
|
+
if (/^\d+\.\d+$/.test(clientId)) identity.gaClientId = clientId;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const sessions = {};
|
|
120
|
+
for (const raw of jar.split(";")) {
|
|
121
|
+
const cookie = raw.trim();
|
|
122
|
+
const eq = cookie.indexOf("=");
|
|
123
|
+
if (eq < 0) continue;
|
|
124
|
+
const name = cookie.slice(0, eq);
|
|
125
|
+
const container = /^_ga_([A-Z0-9]+)$/i.exec(name)?.[1];
|
|
126
|
+
if (!container) continue;
|
|
127
|
+
const parts = decodeURIComponent(cookie.slice(eq + 1)).split(".");
|
|
128
|
+
if (parts.length >= 3 && /^\d+$/.test(parts[2])) sessions[container] = parts[2];
|
|
129
|
+
}
|
|
130
|
+
if (Object.keys(sessions).length > 0) identity.gaSessions = sessions;
|
|
131
|
+
return identity;
|
|
132
|
+
}
|
|
133
|
+
|
|
100
134
|
// src/forms/submitForm.ts
|
|
101
135
|
function getApiConfig() {
|
|
102
136
|
const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.sonor.io" : process.env.SONOR_API_URL || process.env.NEXT_PUBLIC_SONOR_API_URL || "https://api.sonor.io";
|
|
@@ -128,6 +162,7 @@ async function submitForm({
|
|
|
128
162
|
}
|
|
129
163
|
const honeypotFieldName = config.honeypot_field || DEFAULT_HONEYPOT_FIELD;
|
|
130
164
|
const utm = getUTMParams();
|
|
165
|
+
const ga4 = readGa4Identity();
|
|
131
166
|
const payload = {
|
|
132
167
|
formId: config.id,
|
|
133
168
|
form_id: config.id,
|
|
@@ -150,6 +185,7 @@ async function submitForm({
|
|
|
150
185
|
utmCampaign: utm.utm_campaign,
|
|
151
186
|
utmTerm: utm.utm_term,
|
|
152
187
|
utmContent: utm.utm_content,
|
|
188
|
+
...ga4,
|
|
153
189
|
// Sub-site host — preserved on form_submissions for lead attribution
|
|
154
190
|
// across multi-site projects. Resolved by SiteKitClientProviders.
|
|
155
191
|
site: typeof window !== "undefined" ? window.__SITE_KIT_SITE__ || window.location.host : void 0,
|
|
@@ -179,5 +215,5 @@ async function submitForm({
|
|
|
179
215
|
}
|
|
180
216
|
|
|
181
217
|
export { DEFAULT_FORM_SUCCESS_MESSAGE, getApiConfig, normalizeFormConfig, submitForm };
|
|
182
|
-
//# sourceMappingURL=chunk-
|
|
183
|
-
//# sourceMappingURL=chunk-
|
|
218
|
+
//# sourceMappingURL=chunk-ITV76G3L.mjs.map
|
|
219
|
+
//# sourceMappingURL=chunk-ITV76G3L.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/forms/defaultSuccessMessage.ts","../src/forms/normalize-config.ts","../src/forms/recaptcha.ts","../src/forms/ga4-identity.ts","../src/forms/submitForm.ts"],"names":[],"mappings":"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","file":"chunk-ITV76G3L.mjs","sourcesContent":["/** Shown when the API omits or returns an empty `success_message`. */\nexport const DEFAULT_FORM_SUCCESS_MESSAGE =\n 'Thank you for your submission! We will be in touch soon.'\n","/**\n * Shared form-config normalizer — the ONE place a raw\n * /api/public/forms/config payload becomes a ManagedFormConfig.\n *\n * Deliberately has no `'use client'` directive and no hooks, so it is usable\n * from a Server Component (StaticForm) as well as from useForm's client\n * fetch. Both the server shell and the hydrated enhancement must agree on\n * field order, step count and success copy or the swap visibly jumps, so\n * there must never be a second copy of this.\n */\n\nimport type { ManagedFormConfig } from './types'\nimport { DEFAULT_FORM_SUCCESS_MESSAGE } from './defaultSuccessMessage'\n\n/**\n * Normalize a raw /api/public/forms/config payload into ManagedFormConfig.\n * Single source of truth — used for both the client fetch and server-provided\n * `initialForm`, so the two paths can never drift.\n */\nexport function normalizeFormConfig(data: any): ManagedFormConfig {\n if (data.steps) {\n data.steps.sort((a: any, b: any) => (a.step_number || 0) - (b.step_number || 0))\n }\n if (data.fields) {\n data.fields.sort((a: any, b: any) => (a.sort_order || 0) - (b.sort_order || 0))\n }\n\n const total_steps = data.steps?.length || 1\n const is_multi_step = total_steps > 1\n\n const apiSuccess =\n typeof data.success_message === 'string' ? data.success_message.trim() : ''\n const success_message =\n apiSuccess ||\n (typeof (data as { successMessage?: string }).successMessage === 'string'\n ? String((data as { successMessage?: string }).successMessage).trim()\n : '') ||\n DEFAULT_FORM_SUCCESS_MESSAGE\n\n return {\n ...data,\n success_message,\n total_steps,\n is_multi_step,\n }\n}\n","/**\n * reCAPTCHA Enterprise - Client-side token generation for site-kit forms\n *\n * Site key is read from:\n * - window.__SITE_KIT_RECAPTCHA_SITE_KEY__ (set by SiteKitLayout)\n * - process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY (when bundled in Next.js app)\n */\n\nfunction getSiteKey(explicitSiteKey?: string): string | undefined {\n if (typeof window === 'undefined') return undefined\n if (explicitSiteKey) return explicitSiteKey\n const win = window as any\n return win.__SITE_KIT_RECAPTCHA_SITE_KEY__ ?? process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY\n}\n\n/** Max time to wait for reCAPTCHA Enterprise to become usable before giving up. */\nconst RECAPTCHA_LOAD_TIMEOUT_MS = 10000\n\nfunction loadScript(siteKey: string): Promise<any> {\n return new Promise((resolve) => {\n if (typeof window === 'undefined') {\n resolve(null)\n return\n }\n const win = window as any\n if (win.grecaptcha?.enterprise) {\n resolve(win.grecaptcha)\n return\n }\n\n // Never hang the form: if the script is blocked (ad-blocker), stalls, or\n // never initializes `grecaptcha.enterprise`, resolve null after a timeout.\n // The caller treats a null result as \"no token\" rather than waiting forever.\n let settled = false\n const finish = (value: any) => {\n if (settled) return\n settled = true\n resolve(value)\n }\n const giveUp = setTimeout(() => finish(null), RECAPTCHA_LOAD_TIMEOUT_MS)\n\n const waitForReady = () => {\n if (settled) return\n if (win.grecaptcha?.enterprise) {\n clearTimeout(giveUp)\n finish(win.grecaptcha)\n return\n }\n setTimeout(waitForReady, 50)\n }\n\n const existing = document.querySelector('script[src*=\"recaptcha/enterprise\"]')\n if (existing) {\n waitForReady()\n return\n }\n\n const script = document.createElement('script')\n script.src = `https://www.google.com/recaptcha/enterprise.js?render=${siteKey}`\n script.async = true\n script.onload = () => waitForReady()\n script.onerror = () => {\n clearTimeout(giveUp)\n finish(null)\n }\n document.head.appendChild(script)\n })\n}\n\n/**\n * Get a reCAPTCHA Enterprise token for form submission.\n * Returns null if not configured or in SSR.\n */\nexport async function getRecaptchaToken(explicitSiteKey?: string): Promise<string | null> {\n const siteKey = getSiteKey(explicitSiteKey)\n if (!siteKey || typeof window === 'undefined') return null\n try {\n const grecaptcha = await loadScript(siteKey)\n if (!grecaptcha?.enterprise?.execute) return null\n // loadScript only waits for grecaptcha.enterprise to EXIST — execute()\n // before enterprise.ready() rejects while the widget initializes (cold\n // page load = every first-visit submitter). A swallowed rejection here\n // means no token, and a requireRecaptcha project hard-rejects the lead.\n await new Promise<void>((resolve) => grecaptcha.enterprise.ready(resolve))\n const token = await grecaptcha.enterprise.execute(siteKey, { action: 'submit' })\n return token || null\n } catch (err) {\n // One retry after a beat — first-execute flakes persist on slow devices\n // even post-ready, and a dropped token is a dropped lead.\n try {\n await new Promise((resolve) => setTimeout(resolve, 800))\n const win = window as any\n const token = await win.grecaptcha.enterprise.execute(siteKey, { action: 'submit' })\n return token || null\n } catch (retryErr) {\n console.warn('[site-kit reCAPTCHA]', retryErr)\n return null\n }\n }\n}\n","/**\n * Reads the visitor's GA4 identity out of their own first-party cookies so a\n * lead conversion can be reported SERVER-side.\n *\n * Why the server has to do the reporting: Sonor's form-submit endpoint answers\n * with a byte-identical success payload whether it accepted a submission or\n * quarantined it as spam (deliberately — a bot must never learn it was\n * caught). So the browser cannot tell a real lead from a filtered one, and a\n * site firing its own ad conversion bills both. The accept/reject verdict only\n * exists on the server, so the conversion has to be sent from there — and to\n * send it, the server needs the GA4 identity that only the browser can see.\n *\n * Nothing here is a new identifier: these are cookies Google's own tag already\n * set on this visitor. We read them, we never write them.\n */\n\nexport interface Ga4Identity {\n /** GA4 client id, from `_ga`. Without it a server-sent event has no identity. */\n gaClientId?: string\n /**\n * GA4 session ids keyed by container id (the measurement id minus `G-`), from\n * the `_ga_<CONTAINER>` cookies.\n *\n * A map rather than one value because a page may carry more than one GA4\n * property, and picking the wrong one would silently attach the lead to the\n * wrong session — the sender knows which measurement id it is configured for,\n * this side does not.\n */\n gaSessions?: Record<string, string>\n}\n\nfunction readCookie(jar: string, name: string): string | undefined {\n const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const match = jar.match(new RegExp(`(?:^|;\\\\s*)${escaped}=([^;]*)`))\n return match ? decodeURIComponent(match[1]) : undefined\n}\n\nexport function readGa4Identity(): Ga4Identity {\n if (typeof document === 'undefined') return {}\n const jar = document.cookie || ''\n if (!jar) return {}\n\n const identity: Ga4Identity = {}\n\n // `_ga` is `GA1.<depth>.<id1>.<id2>`; the client id is the last two parts\n // joined. Reading the raw cookie rather than the trailing two segments\n // blindly keeps it correct when the depth prefix varies by domain scope.\n const ga = readCookie(jar, '_ga')\n if (ga) {\n const parts = ga.split('.')\n if (parts.length >= 4) {\n const clientId = `${parts[parts.length - 2]}.${parts[parts.length - 1]}`\n if (/^\\d+\\.\\d+$/.test(clientId)) identity.gaClientId = clientId\n }\n }\n\n // `_ga_<CONTAINER>` is `GS<v>.<depth>.<session_id>.<session_number>....`\n const sessions: Record<string, string> = {}\n for (const raw of jar.split(';')) {\n const cookie = raw.trim()\n const eq = cookie.indexOf('=')\n if (eq < 0) continue\n const name = cookie.slice(0, eq)\n const container = /^_ga_([A-Z0-9]+)$/i.exec(name)?.[1]\n if (!container) continue\n const parts = decodeURIComponent(cookie.slice(eq + 1)).split('.')\n if (parts.length >= 3 && /^\\d+$/.test(parts[2])) sessions[container] = parts[2]\n }\n if (Object.keys(sessions).length > 0) identity.gaSessions = sessions\n\n return identity\n}\n","/**\n * @sonordev/site-kit/forms - Shared submission logic\n *\n * Single source of truth for sending a managed form to the Sonor API. Both\n * <ManagedForm> (via FormClient) and the headless useForm() hook submit through\n * here, so the request shape, retry behaviour, reCAPTCHA handling, and error\n * parsing can never drift between the two paths.\n */\n\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { getRecaptchaToken } from './recaptcha'\nimport { DEFAULT_HONEYPOT_FIELD } from './contract'\nimport { readGa4Identity } from './ga4-identity'\nimport type { ManagedFormConfig } from './types'\n\n// The honeypot trap-field name lives once in the shared forms contract\n// (single source of truth for site-kit's client + sonor-api's intake).\n// Re-exported so existing importers of this path keep resolving it.\nexport { DEFAULT_HONEYPOT_FIELD }\n\nexport function getApiConfig(): { apiUrl: string; apiKey: string | undefined } {\n // Server branch must honor the same env override SiteKitLayout feeds into\n // __SITE_KIT_API_URL__, or SSR-rendered absolute URLs (the native form\n // action) would point at prod from a staging site.\n const apiUrl =\n typeof window !== 'undefined'\n ? (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n : process.env.SONOR_API_URL || process.env.NEXT_PUBLIC_SONOR_API_URL || 'https://api.sonor.io'\n const apiKey =\n typeof window !== 'undefined' ? (window as any).__SITE_KIT_API_KEY__ : undefined\n return { apiUrl, apiKey }\n}\n\nfunction getUTMParams(): Record<string, string | null> {\n if (typeof window === 'undefined') return {}\n const params = new URLSearchParams(window.location.search)\n const out: Record<string, string | null> = {}\n for (const key of ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']) {\n out[key] = params.get(key)\n }\n return out\n}\n\nexport interface SubmitFormArgs {\n config: ManagedFormConfig\n /**\n * Field values to submit. The caller must pass VISIBLE values only —\n * conditionally-hidden fields should be excluded so stale values are not sent.\n */\n values: Record<string, unknown>\n /** Honeypot input value (empty string for the headless hook, which has no honeypot UI). */\n honeypotValue: string\n /** Timestamp (ms) the form was first rendered — a weak anti-bot timing signal. */\n formLoadedAt: number\n}\n\n/**\n * Submit a managed form. Throws on failure (network, timeout, or a non-2xx\n * response) with a human-readable message; resolves with the parsed API\n * response on success.\n */\nexport async function submitForm({\n config,\n values,\n honeypotValue,\n formLoadedAt,\n}: SubmitFormArgs): Promise<unknown> {\n const { apiUrl, apiKey } = getApiConfig()\n if (!apiKey) {\n throw new Error('API key is required. Set SONOR_API_KEY in your environment.')\n }\n\n // reCAPTCHA token is fetched right before the POST so it cannot expire.\n const recaptchaToken = config.recaptcha_enabled\n ? await getRecaptchaToken(config.recaptcha_site_key)\n : null\n if (config.recaptcha_enabled && !recaptchaToken) {\n throw new Error('Verification could not be completed. Please try again.')\n }\n\n const honeypotFieldName = config.honeypot_field || DEFAULT_HONEYPOT_FIELD\n const utm = getUTMParams()\n // The visitor's existing GA4 cookies, so Sonor can report an accepted lead\n // as a conversion server-side. Nothing is written or created here — see\n // ga4-identity.ts for why the server, not this browser, has to do it.\n const ga4 = readGa4Identity()\n\n const payload = {\n formId: config.id,\n form_id: config.id,\n data: {\n ...values,\n ...(config.honeypot_enabled ? { [honeypotFieldName]: honeypotValue } : {}),\n },\n metadata: {\n pageUrl: typeof window !== 'undefined' ? window.location.href : null,\n referrer: typeof document !== 'undefined' ? document.referrer || null : null,\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : null,\n sessionId:\n typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('_sk_sid') : null,\n // Anonymous visitor id (_sk_vid) — the same id Analytics/Signal stamp on\n // page views, so the Sonor side can bind this lead to its browsing session\n // (page -> lead attribution). Sent on every submission; null pre-hydration.\n visitorId:\n typeof localStorage !== 'undefined' ? localStorage.getItem('_sk_vid') : null,\n recaptchaToken: recaptchaToken || undefined,\n utmSource: utm.utm_source,\n utmMedium: utm.utm_medium,\n utmCampaign: utm.utm_campaign,\n utmTerm: utm.utm_term,\n utmContent: utm.utm_content,\n ...ga4,\n // Sub-site host — preserved on form_submissions for lead attribution\n // across multi-site projects. Resolved by SiteKitClientProviders.\n site:\n typeof window !== 'undefined'\n ? (window as { __SITE_KIT_SITE__?: string }).__SITE_KIT_SITE__ ||\n window.location.host\n : undefined,\n _formLoadedAt: formLoadedAt,\n },\n }\n\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/submit`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(payload),\n },\n { retries: 2, timeoutMs: 20000 },\n )\n\n const data = await response.json().catch(() => null)\n\n if (!response.ok) {\n const msg = (\n data?.message ??\n data?.error ??\n (response.statusText || `HTTP ${response.status}`)\n )\n .toString()\n .trim()\n throw new Error(\n msg ? `Failed to submit form: ${msg}` : `Failed to submit form (${response.status})`,\n )\n }\n\n return data\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 chunk7NMI7M56_js = require('./chunk-7NMI7M56.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
|
+
chunk7NMI7M56_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__ = chunk7NMI7M56_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-JJ6AI6LW.js.map
|
|
25
|
+
//# sourceMappingURL=chunk-JJ6AI6LW.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-JJ6AI6LW.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,24 +1,24 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { publishSiteCredential } from './chunk-
|
|
3
|
-
import { FleetHeartbeat } from './chunk-
|
|
2
|
+
import { publishSiteCredential } from './chunk-D2D3YKWT.mjs';
|
|
3
|
+
import { FleetHeartbeat } from './chunk-FDLFXHUU.mjs';
|
|
4
4
|
import { resolveSiteHost } from './chunk-GZJE23Y5.mjs';
|
|
5
5
|
import { useDeferredActivation } from './chunk-ZVBLXXNJ.mjs';
|
|
6
|
-
import { SignalBridge } from './chunk-
|
|
6
|
+
import { SignalBridge } from './chunk-KC2FCI5V.mjs';
|
|
7
7
|
import { getOrCreateVisitorId, getOrCreateSessionId } from './chunk-ZZQAGCSI.mjs';
|
|
8
8
|
import React, { useState, useRef, useCallback, Suspense } from 'react';
|
|
9
9
|
import dynamic from 'next/dynamic';
|
|
10
10
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
11
|
|
|
12
12
|
var LazyAnalyticsProvider = dynamic(
|
|
13
|
-
() => import('./AnalyticsProvider-
|
|
13
|
+
() => import('./AnalyticsProvider-XSPROVHM.mjs').then((m) => ({ default: m.AnalyticsProvider })),
|
|
14
14
|
{ ssr: false }
|
|
15
15
|
);
|
|
16
16
|
var LazyEngageWidget = dynamic(
|
|
17
|
-
() => import('./EngageWidget-
|
|
17
|
+
() => import('./EngageWidget-KF4HU4YA.mjs').then((m) => ({ default: m.EngageWidget })),
|
|
18
18
|
{ ssr: false }
|
|
19
19
|
);
|
|
20
20
|
var LazySitemapSync = dynamic(
|
|
21
|
-
() => import('./SitemapSync-
|
|
21
|
+
() => import('./SitemapSync-2O5UM4GM.mjs').then((m) => ({ default: m.SitemapSync })),
|
|
22
22
|
{ ssr: false }
|
|
23
23
|
);
|
|
24
24
|
function SiteKitClientProviders({
|
|
@@ -134,5 +134,5 @@ function SiteKitClientProviders({
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
export { SiteKitClientProviders };
|
|
137
|
-
//# sourceMappingURL=chunk-
|
|
138
|
-
//# sourceMappingURL=chunk-
|
|
137
|
+
//# sourceMappingURL=chunk-K447AIIB.mjs.map
|
|
138
|
+
//# sourceMappingURL=chunk-K447AIIB.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/layout/SiteKitClientProviders.tsx"],"names":[],"mappings":"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","file":"chunk-AXYKBQVI.mjs","sourcesContent":["'use client'\n\n/**\n * SiteKitClientProviders — Client Island\n *\n * This is the 'use client' boundary for SiteKitLayout.\n * It composes the client-only providers (Analytics, Engage, Signal, SitemapSync)\n * that require React context or browser APIs.\n *\n * All modules are lazy-loaded via next/dynamic so their JS is only downloaded\n * when the feature is enabled — keeping the initial bundle lightweight.\n *\n * All modules share a unified identity (visitor ID + session ID) read from\n * the storage-backed singleton (shared/identity), ensuring consistent\n * tracking across Analytics, Signal, and Engage.\n *\n * 4.0 INVARIANT — CHILDREN ARE NEVER WRAPPED. Every module here renders as a\n * childless sibling AFTER {children}; nothing provides context around page\n * content. That makes the prerender-bailout / remount-flash class of bug\n * structurally impossible. Do not reintroduce a wrapper.\n *\n * Props are passed down from the server-side SiteKitLayout component,\n * which reads env vars at render time.\n */\n\nimport React, { Suspense, useCallback, useRef, useState, type ReactNode } from 'react'\nimport dynamic from 'next/dynamic'\nimport {\n getOrCreateVisitorId,\n getOrCreateSessionId,\n} from '../shared/identity'\n// Defer non-critical module mounting until the page is quiet: the lazy\n// modules below are dynamic(ssr:false) and never render on the server, but\n// rendering them during hydration still kicks off their chunk downloads +\n// mount effects inside the critical startup window. useDeferredActivation\n// holds them until window.load + idle or first interaction — the same\n// strategy as the hand-rolled per-site deferrals this replaces (upforge\n// `deferred-analytics.tsx`, TPS `DeferredAnalytics.tsx`). The hook is public\n// (`@sonordev/site-kit/client`) so sites can reuse it for their own scripts.\nimport { useDeferredActivation } from '../shared/defer'\n// Single source of truth for seeding the token manager + the __SITE_KIT_*\n// globals — shared with the public `SiteKitCredential` component that\n// non-core layouts (agency-site-kit) use. Do not re-inline it here.\nimport { publishSiteCredential, resolveSiteHost } from '../shared/publishCredential'\nimport type { AnalyticsConfig, EngageConfig, SignalConfig } from './types'\n\n// Lazy-load feature modules — JS only downloaded when feature is enabled\nconst LazyAnalyticsProvider = dynamic(\n () => import('../analytics/AnalyticsProvider').then(m => ({ default: m.AnalyticsProvider })),\n { ssr: false },\n)\nconst LazyEngageWidget = dynamic(\n () => import('../engage/EngageWidget').then(m => ({ default: m.EngageWidget })),\n { ssr: false },\n)\nconst LazySitemapSync = dynamic(\n () => import('../SitemapSync').then(m => ({ default: m.SitemapSync })),\n { ssr: false },\n)\n// SignalBridge is imported STATICALLY, not via dynamic(ssr:false): it is a\n// tiny SSR-safe context shell that renders children immediately and\n// lazy-loads its machinery (SignalCore) as a childless sibling internally.\n// Wrapping children in a dynamic(ssr:false) component was the old SSR-bailout\n// failure mode — do not reintroduce it.\nimport { SignalBridge } from '../signal/SignalBridge'\n// Fleet heartbeat: a deferred, CHILDLESS SIBLING (like Analytics/Engage) that\n// reports this site's kit version + active modules once per session, so the\n// platform can see version drift across the fleet. Tiny and SSR-safe — renders\n// null until the shared idle gate fires; never wraps page content.\nimport { FleetHeartbeat } from '../fleet/FleetHeartbeat'\nimport type { FleetModule } from '../fleet/contract'\n\nexport interface SiteKitClientProvidersProps {\n children: ReactNode\n apiKey: string\n apiUrl: string\n projectId?: string\n analytics?: boolean | AnalyticsConfig\n engage?: boolean | EngageConfig\n signal?: boolean | SignalConfig\n /**\n * Runtime SitemapSync from the visitor's browser. Default FALSE — sitemap\n * registration belongs to build time and to sonor-api's nightly reconciler,\n * not to whoever happens to load the page. See SiteKitLayout for the full\n * reasoning; in short, its guards live in session/localStorage, so every new\n * visitor missed them and re-POSTed the whole sitemap.\n */\n sitemapSync?: boolean\n /**\n * Fleet heartbeat — reports kit version + active modules to Sonor once per\n * session so the platform can track version drift and key state across every\n * site. Default true. Deferred exactly like Analytics; never in the hydration\n * path, carries no visitor data.\n */\n fleet?: boolean\n /**\n * Defer Analytics / Engage / SitemapSync until window load + idle or first\n * interaction (default true). Pass false to restore legacy eager mounting\n * on hydration. SignalBridge is never deferred — it must apply experiment\n * variants as early as possible.\n */\n defer?: boolean\n debug?: boolean\n /** Browser-safe Google Maps key, resolved server-side by SiteKitLayout. */\n mapsBrowserKey?: string\n}\n\nexport function SiteKitClientProviders({\n children,\n apiKey,\n apiUrl,\n projectId,\n analytics = true,\n engage = true,\n signal = false,\n sitemapSync = false,\n fleet = true,\n defer = true,\n debug = false,\n mapsBrowserKey,\n}: SiteKitClientProvidersProps) {\n // One shared activation gate for all deferred siblings, so they mount in a\n // single commit instead of staggered re-renders.\n const modulesReady = useDeferredActivation(defer)\n // Hoist analytics config so the `site` dimension is resolvable up here.\n const analyticsConfig: AnalyticsConfig =\n typeof analytics === 'object' ? analytics : {}\n\n // Resolve the sub-site identifier — the dimension that lets one Sonor project\n // host many sites (e.g. truepowersystems.com + ohiopowerstudies.com under one\n // TPS project). Precedence lives in resolveSiteHost (shared contract):\n // explicit `analytics.site` > NEXT_PUBLIC_SITE_URL host > window.location.host.\n // Needed as a local too — SignalBridge and the analytics config read it below.\n const site = resolveSiteHost(analyticsConfig.site)\n\n // Seed the token manager + publish the window globals modules read from.\n // No-op on the server; runs during render so it lands before child effects.\n publishSiteCredential({ apiKey, apiUrl, site, debug, mapsBrowserKey })\n\n // Unified identity — single source of truth for all modules\n const [visitorId] = useState(() => getOrCreateVisitorId())\n const [sessionId] = useState(() => getOrCreateSessionId())\n\n // Page metadata bridge: Analytics writes, Signal reads\n const pageMetadataRef = useRef<Record<string, unknown> | null>(null)\n const onPageMetadata = useCallback((metadata: Record<string, unknown>) => {\n pageMetadataRef.current = metadata\n }, [])\n\n // Children render FIRST and are never re-parented. Siblings accumulate after.\n const siblings: ReactNode[] = []\n\n // SignalBridge — childless sibling (4.0: useSignal reads the module store,\n // so it no longer needs to wrap anything). Not gated on modulesReady:\n // experiment variants must apply as early as possible.\n if (signal) {\n const signalConfig: SignalConfig =\n typeof signal === 'object' ? signal : {}\n siblings.push(\n <SignalBridge\n key=\"sk-signal\"\n enabled\n realtime={signalConfig.realtime !== false}\n experiments={signalConfig.experiments !== false}\n behaviorTracking={signalConfig.behaviorTracking !== false}\n visitorId={visitorId}\n sessionId={sessionId}\n pageMetadataRef={pageMetadataRef}\n />,\n )\n }\n\n // Add Analytics if enabled (renders alongside, doesn't wrap).\n //\n // CRITICAL: LazyAnalyticsProvider is dynamic(..., { ssr: false }). Wrapping\n // {content} inside an ssr:false component makes the entire page tree skip\n // server rendering — the route de-opts to 100% client rendering and the\n // hero / LCP element only paints after hydration (mobile LCP ~4s, Lighthouse\n // ~85). Analytics tracks via effects + a metadata ref, so it never needs to\n // wrap page content — mount it as a childless sibling, exactly like Engage.\n // Each deferred sibling is appended as `{modulesReady && <Module/>}` so the\n // children slot never moves in the tree — flipping ready only mounts the\n // sibling, it never remounts page content.\n if (analytics) {\n siblings.push(\n <React.Fragment key=\"sk-analytics\">\n {modulesReady && (\n <Suspense fallback={null}>\n <LazyAnalyticsProvider\n apiUrl={apiUrl}\n apiKey={apiKey}\n site={site}\n trackPageViews={analyticsConfig.trackPageViews !== false}\n trackWebVitals={analyticsConfig.trackWebVitals !== false}\n trackScrollDepth={analyticsConfig.trackScrollDepth !== false}\n trackClicks={analyticsConfig.trackClicks !== false}\n sessionTimeout={analyticsConfig.sessionDuration}\n excludePaths={analyticsConfig.excludePaths}\n debug={debug}\n externalVisitorId={visitorId}\n externalSessionId={sessionId}\n onPageMetadata={onPageMetadata}\n />\n </Suspense>\n )}\n </React.Fragment>,\n )\n }\n\n // Engage widget — childless sibling\n if (engage) {\n const engageConfig: EngageConfig =\n typeof engage === 'object' ? engage : {}\n siblings.push(\n <React.Fragment key=\"sk-engage\">\n {modulesReady && (\n <LazyEngageWidget\n apiUrl={apiUrl}\n apiKey={apiKey}\n projectId={projectId}\n position={engageConfig.position || 'bottom-right'}\n zIndex={engageConfig.zIndex}\n chatEnabled={engageConfig.chatEnabled !== false}\n />\n )}\n </React.Fragment>,\n )\n }\n\n // SitemapSync — childless sibling\n if (sitemapSync) {\n siblings.push(\n <React.Fragment key=\"sk-sitemap\">\n {modulesReady && <LazySitemapSync debug={debug} />}\n </React.Fragment>,\n )\n }\n\n // Fleet heartbeat — childless sibling on the same modulesReady idle gate.\n // Reports the modules actually enabled on THIS layout (honest client-observable\n // set) so the platform can answer \"which version / modules is each site\n // running\". defer={false}: the modulesReady gate already handled deferral, so\n // the heartbeat fires on mount (still bounded to once per session internally).\n if (fleet) {\n const activeModules: FleetModule[] = []\n if (analytics) activeModules.push('analytics')\n if (engage) activeModules.push('engage')\n if (signal) activeModules.push('signal')\n if (sitemapSync) activeModules.push('sitemapSync')\n siblings.push(\n <React.Fragment key=\"sk-fleet\">\n {modulesReady && (\n <FleetHeartbeat\n apiUrl={apiUrl}\n apiKey={apiKey}\n site={site}\n modules={activeModules}\n defer={false}\n debug={debug}\n />\n )}\n </React.Fragment>,\n )\n }\n\n return (\n <>\n {children}\n {siblings}\n </>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/layout/SiteKitClientProviders.tsx"],"names":[],"mappings":"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","file":"chunk-K447AIIB.mjs","sourcesContent":["'use client'\n\n/**\n * SiteKitClientProviders — Client Island\n *\n * This is the 'use client' boundary for SiteKitLayout.\n * It composes the client-only providers (Analytics, Engage, Signal, SitemapSync)\n * that require React context or browser APIs.\n *\n * All modules are lazy-loaded via next/dynamic so their JS is only downloaded\n * when the feature is enabled — keeping the initial bundle lightweight.\n *\n * All modules share a unified identity (visitor ID + session ID) read from\n * the storage-backed singleton (shared/identity), ensuring consistent\n * tracking across Analytics, Signal, and Engage.\n *\n * 4.0 INVARIANT — CHILDREN ARE NEVER WRAPPED. Every module here renders as a\n * childless sibling AFTER {children}; nothing provides context around page\n * content. That makes the prerender-bailout / remount-flash class of bug\n * structurally impossible. Do not reintroduce a wrapper.\n *\n * Props are passed down from the server-side SiteKitLayout component,\n * which reads env vars at render time.\n */\n\nimport React, { Suspense, useCallback, useRef, useState, type ReactNode } from 'react'\nimport dynamic from 'next/dynamic'\nimport {\n getOrCreateVisitorId,\n getOrCreateSessionId,\n} from '../shared/identity'\n// Defer non-critical module mounting until the page is quiet: the lazy\n// modules below are dynamic(ssr:false) and never render on the server, but\n// rendering them during hydration still kicks off their chunk downloads +\n// mount effects inside the critical startup window. useDeferredActivation\n// holds them until window.load + idle or first interaction — the same\n// strategy as the hand-rolled per-site deferrals this replaces (upforge\n// `deferred-analytics.tsx`, TPS `DeferredAnalytics.tsx`). The hook is public\n// (`@sonordev/site-kit/client`) so sites can reuse it for their own scripts.\nimport { useDeferredActivation } from '../shared/defer'\n// Single source of truth for seeding the token manager + the __SITE_KIT_*\n// globals — shared with the public `SiteKitCredential` component that\n// non-core layouts (agency-site-kit) use. Do not re-inline it here.\nimport { publishSiteCredential, resolveSiteHost } from '../shared/publishCredential'\nimport type { AnalyticsConfig, EngageConfig, SignalConfig } from './types'\n\n// Lazy-load feature modules — JS only downloaded when feature is enabled\nconst LazyAnalyticsProvider = dynamic(\n () => import('../analytics/AnalyticsProvider').then(m => ({ default: m.AnalyticsProvider })),\n { ssr: false },\n)\nconst LazyEngageWidget = dynamic(\n () => import('../engage/EngageWidget').then(m => ({ default: m.EngageWidget })),\n { ssr: false },\n)\nconst LazySitemapSync = dynamic(\n () => import('../SitemapSync').then(m => ({ default: m.SitemapSync })),\n { ssr: false },\n)\n// SignalBridge is imported STATICALLY, not via dynamic(ssr:false): it is a\n// tiny SSR-safe context shell that renders children immediately and\n// lazy-loads its machinery (SignalCore) as a childless sibling internally.\n// Wrapping children in a dynamic(ssr:false) component was the old SSR-bailout\n// failure mode — do not reintroduce it.\nimport { SignalBridge } from '../signal/SignalBridge'\n// Fleet heartbeat: a deferred, CHILDLESS SIBLING (like Analytics/Engage) that\n// reports this site's kit version + active modules once per session, so the\n// platform can see version drift across the fleet. Tiny and SSR-safe — renders\n// null until the shared idle gate fires; never wraps page content.\nimport { FleetHeartbeat } from '../fleet/FleetHeartbeat'\nimport type { FleetModule } from '../fleet/contract'\n\nexport interface SiteKitClientProvidersProps {\n children: ReactNode\n apiKey: string\n apiUrl: string\n projectId?: string\n analytics?: boolean | AnalyticsConfig\n engage?: boolean | EngageConfig\n signal?: boolean | SignalConfig\n /**\n * Runtime SitemapSync from the visitor's browser. Default FALSE — sitemap\n * registration belongs to build time and to sonor-api's nightly reconciler,\n * not to whoever happens to load the page. See SiteKitLayout for the full\n * reasoning; in short, its guards live in session/localStorage, so every new\n * visitor missed them and re-POSTed the whole sitemap.\n */\n sitemapSync?: boolean\n /**\n * Fleet heartbeat — reports kit version + active modules to Sonor once per\n * session so the platform can track version drift and key state across every\n * site. Default true. Deferred exactly like Analytics; never in the hydration\n * path, carries no visitor data.\n */\n fleet?: boolean\n /**\n * Defer Analytics / Engage / SitemapSync until window load + idle or first\n * interaction (default true). Pass false to restore legacy eager mounting\n * on hydration. SignalBridge is never deferred — it must apply experiment\n * variants as early as possible.\n */\n defer?: boolean\n debug?: boolean\n /** Browser-safe Google Maps key, resolved server-side by SiteKitLayout. */\n mapsBrowserKey?: string\n}\n\nexport function SiteKitClientProviders({\n children,\n apiKey,\n apiUrl,\n projectId,\n analytics = true,\n engage = true,\n signal = false,\n sitemapSync = false,\n fleet = true,\n defer = true,\n debug = false,\n mapsBrowserKey,\n}: SiteKitClientProvidersProps) {\n // One shared activation gate for all deferred siblings, so they mount in a\n // single commit instead of staggered re-renders.\n const modulesReady = useDeferredActivation(defer)\n // Hoist analytics config so the `site` dimension is resolvable up here.\n const analyticsConfig: AnalyticsConfig =\n typeof analytics === 'object' ? analytics : {}\n\n // Resolve the sub-site identifier — the dimension that lets one Sonor project\n // host many sites (e.g. truepowersystems.com + ohiopowerstudies.com under one\n // TPS project). Precedence lives in resolveSiteHost (shared contract):\n // explicit `analytics.site` > NEXT_PUBLIC_SITE_URL host > window.location.host.\n // Needed as a local too — SignalBridge and the analytics config read it below.\n const site = resolveSiteHost(analyticsConfig.site)\n\n // Seed the token manager + publish the window globals modules read from.\n // No-op on the server; runs during render so it lands before child effects.\n publishSiteCredential({ apiKey, apiUrl, site, debug, mapsBrowserKey })\n\n // Unified identity — single source of truth for all modules\n const [visitorId] = useState(() => getOrCreateVisitorId())\n const [sessionId] = useState(() => getOrCreateSessionId())\n\n // Page metadata bridge: Analytics writes, Signal reads\n const pageMetadataRef = useRef<Record<string, unknown> | null>(null)\n const onPageMetadata = useCallback((metadata: Record<string, unknown>) => {\n pageMetadataRef.current = metadata\n }, [])\n\n // Children render FIRST and are never re-parented. Siblings accumulate after.\n const siblings: ReactNode[] = []\n\n // SignalBridge — childless sibling (4.0: useSignal reads the module store,\n // so it no longer needs to wrap anything). Not gated on modulesReady:\n // experiment variants must apply as early as possible.\n if (signal) {\n const signalConfig: SignalConfig =\n typeof signal === 'object' ? signal : {}\n siblings.push(\n <SignalBridge\n key=\"sk-signal\"\n enabled\n realtime={signalConfig.realtime !== false}\n experiments={signalConfig.experiments !== false}\n behaviorTracking={signalConfig.behaviorTracking !== false}\n visitorId={visitorId}\n sessionId={sessionId}\n pageMetadataRef={pageMetadataRef}\n />,\n )\n }\n\n // Add Analytics if enabled (renders alongside, doesn't wrap).\n //\n // CRITICAL: LazyAnalyticsProvider is dynamic(..., { ssr: false }). Wrapping\n // {content} inside an ssr:false component makes the entire page tree skip\n // server rendering — the route de-opts to 100% client rendering and the\n // hero / LCP element only paints after hydration (mobile LCP ~4s, Lighthouse\n // ~85). Analytics tracks via effects + a metadata ref, so it never needs to\n // wrap page content — mount it as a childless sibling, exactly like Engage.\n // Each deferred sibling is appended as `{modulesReady && <Module/>}` so the\n // children slot never moves in the tree — flipping ready only mounts the\n // sibling, it never remounts page content.\n if (analytics) {\n siblings.push(\n <React.Fragment key=\"sk-analytics\">\n {modulesReady && (\n <Suspense fallback={null}>\n <LazyAnalyticsProvider\n apiUrl={apiUrl}\n apiKey={apiKey}\n site={site}\n trackPageViews={analyticsConfig.trackPageViews !== false}\n trackWebVitals={analyticsConfig.trackWebVitals !== false}\n trackScrollDepth={analyticsConfig.trackScrollDepth !== false}\n trackClicks={analyticsConfig.trackClicks !== false}\n sessionTimeout={analyticsConfig.sessionDuration}\n excludePaths={analyticsConfig.excludePaths}\n debug={debug}\n externalVisitorId={visitorId}\n externalSessionId={sessionId}\n onPageMetadata={onPageMetadata}\n />\n </Suspense>\n )}\n </React.Fragment>,\n )\n }\n\n // Engage widget — childless sibling\n if (engage) {\n const engageConfig: EngageConfig =\n typeof engage === 'object' ? engage : {}\n siblings.push(\n <React.Fragment key=\"sk-engage\">\n {modulesReady && (\n <LazyEngageWidget\n apiUrl={apiUrl}\n apiKey={apiKey}\n projectId={projectId}\n position={engageConfig.position || 'bottom-right'}\n zIndex={engageConfig.zIndex}\n chatEnabled={engageConfig.chatEnabled !== false}\n />\n )}\n </React.Fragment>,\n )\n }\n\n // SitemapSync — childless sibling\n if (sitemapSync) {\n siblings.push(\n <React.Fragment key=\"sk-sitemap\">\n {modulesReady && <LazySitemapSync debug={debug} />}\n </React.Fragment>,\n )\n }\n\n // Fleet heartbeat — childless sibling on the same modulesReady idle gate.\n // Reports the modules actually enabled on THIS layout (honest client-observable\n // set) so the platform can answer \"which version / modules is each site\n // running\". defer={false}: the modulesReady gate already handled deferral, so\n // the heartbeat fires on mount (still bounded to once per session internally).\n if (fleet) {\n const activeModules: FleetModule[] = []\n if (analytics) activeModules.push('analytics')\n if (engage) activeModules.push('engage')\n if (signal) activeModules.push('signal')\n if (sitemapSync) activeModules.push('sitemapSync')\n siblings.push(\n <React.Fragment key=\"sk-fleet\">\n {modulesReady && (\n <FleetHeartbeat\n apiUrl={apiUrl}\n apiKey={apiKey}\n site={site}\n modules={activeModules}\n defer={false}\n debug={debug}\n />\n )}\n </React.Fragment>,\n )\n }\n\n return (\n <>\n {children}\n {siblings}\n </>\n )\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkX6SAM7CR_js = require('./chunk-X6SAM7CR.js');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
|
|
@@ -16,8 +16,8 @@ function SignalExperiment({
|
|
|
16
16
|
trackImpression = true,
|
|
17
17
|
children
|
|
18
18
|
}) {
|
|
19
|
-
const { assignment, variant, isControl } =
|
|
20
|
-
const trackEvent =
|
|
19
|
+
const { assignment, variant, isControl } = chunkX6SAM7CR_js.useSignalExperiment(experimentId);
|
|
20
|
+
const trackEvent = chunkX6SAM7CR_js.useSignalEvent();
|
|
21
21
|
React.useEffect(() => {
|
|
22
22
|
if (trackImpression && variant) {
|
|
23
23
|
trackEvent({
|
|
@@ -44,7 +44,7 @@ function SignalExperiment({
|
|
|
44
44
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: variants.control || null });
|
|
45
45
|
}
|
|
46
46
|
function useExperimentVariant(experimentId) {
|
|
47
|
-
return
|
|
47
|
+
return chunkX6SAM7CR_js.useSignalExperiment(experimentId);
|
|
48
48
|
}
|
|
49
49
|
function ExperimentConversion({
|
|
50
50
|
experimentId,
|
|
@@ -52,8 +52,8 @@ function ExperimentConversion({
|
|
|
52
52
|
value,
|
|
53
53
|
children
|
|
54
54
|
}) {
|
|
55
|
-
const trackEvent =
|
|
56
|
-
const { variant } =
|
|
55
|
+
const trackEvent = chunkX6SAM7CR_js.useSignalEvent();
|
|
56
|
+
const { variant } = chunkX6SAM7CR_js.useSignalExperiment(experimentId);
|
|
57
57
|
const handleInteraction = () => {
|
|
58
58
|
trackEvent({
|
|
59
59
|
event_type: "experiment",
|
|
@@ -79,5 +79,5 @@ function ExperimentConversion({
|
|
|
79
79
|
exports.ExperimentConversion = ExperimentConversion;
|
|
80
80
|
exports.SignalExperiment = SignalExperiment;
|
|
81
81
|
exports.useExperimentVariant = useExperimentVariant;
|
|
82
|
-
//# sourceMappingURL=chunk-
|
|
83
|
-
//# sourceMappingURL=chunk-
|
|
82
|
+
//# sourceMappingURL=chunk-K6YQ73IX.js.map
|
|
83
|
+
//# sourceMappingURL=chunk-K6YQ73IX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/signal/SignalExperiment.tsx"],"names":["useSignalExperiment","useSignalEvent","useEffect","jsx","Fragment","React"],"mappings":";;;;;;;;;;AAgDO,SAAS,gBAAA,CAAiB;AAAA,EAC/B,YAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA,GAAkB,IAAA;AAAA,EAClB;AACF,CAAA,EAA0B;AACxB,EAAA,MAAM,EAAE,UAAA,EAAY,OAAA,EAAS,SAAA,EAAU,GAAIA,qCAAoB,YAAY,CAAA;AAC3E,EAAA,MAAM,aAAaC,+BAAA,EAAe;AAGlC,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,MAAA,UAAA,CAAW;AAAA,QACT,UAAA,EAAY,YAAA;AAAA,QACZ,UAAA,EAAY,YAAA;AAAA,QACZ,UAAA,EAAY;AAAA,UACV,aAAA,EAAe,YAAA;AAAA,UACf,WAAA,EAAa;AAAA;AACf,OACD,CAAA;AAAA,IACH;AAAA,EACF,GAAG,CAAC,YAAA,EAAc,OAAA,EAAS,eAAA,EAAiB,UAAU,CAAC,CAAA;AAGvD,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,uBAAOC,cAAA,CAAAC,mBAAA,EAAA,EAAG,mBAAS,EAAE,OAAA,EAAS,WAAW,SAAA,EAAW,SAAA,EAAW,CAAA,EAAE,CAAA;AAAA,EACnE;AAGA,EAAA,MAAM,kBAAkB,OAAA,IAAW,SAAA;AACnC,EAAA,MAAM,OAAA,GAAU,SAAS,eAAe,CAAA;AAExC,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,6DAAU,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,EACpB;AAGA,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,6DAAU,QAAA,EAAA,QAAA,EAAS,CAAA;AAAA,EACrB;AAEA,EAAA,uBAAOD,cAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,QAAA,CAAS,OAAA,IAAW,IAAA,EAAK,CAAA;AACrC;AAYO,SAAS,qBAAqB,YAAA,EAAsB;AACzD,EAAA,OAAOJ,qCAAoB,YAAY,CAAA;AACzC;AAoBO,SAAS,oBAAA,CAAqB;AAAA,EACnC,YAAA;AAAA,EACA,WAAA,GAAc,OAAA;AAAA,EACd,KAAA;AAAA,EACA;AACF,CAAA,EAKG;AACD,EAAA,MAAM,aAAaC,+BAAA,EAAe;AAClC,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAID,oCAAA,CAAoB,YAAY,CAAA;AAEpD,EAAA,MAAM,oBAAoB,MAAM;AAC9B,IAAA,UAAA,CAAW;AAAA,MACT,UAAA,EAAY,YAAA;AAAA,MACZ,UAAA,EAAY,YAAA;AAAA,MACZ,UAAA,EAAY;AAAA,QACV,aAAA,EAAe,YAAA;AAAA,QACf,WAAA,EAAa,OAAA;AAAA,QACb,YAAA,EAAc,WAAA;AAAA,QACd;AAAA;AACF,KACD,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,OAAOK,sBAAA,CAAM,aAAa,QAAA,EAAU;AAAA,IAClC,OAAA,EAAS,CAAC,CAAA,KAAwB;AAChC,MAAA,iBAAA,EAAkB;AAElB,MAAA,IAAK,QAAA,CAAS,MAAsD,OAAA,EAAS;AAC3E,QAAC,QAAA,CAAS,KAAA,CAAsD,OAAA,CAAS,CAAC,CAAA;AAAA,MAC5E;AAAA,IACF;AAAA,GACD,CAAA;AACH","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/signal/SignalExperiment.tsx"],"names":["useSignalExperiment","useSignalEvent","useEffect","jsx","Fragment","React"],"mappings":";;;;;;;;;;AAgDO,SAAS,gBAAA,CAAiB;AAAA,EAC/B,YAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA,GAAkB,IAAA;AAAA,EAClB;AACF,CAAA,EAA0B;AACxB,EAAA,MAAM,EAAE,UAAA,EAAY,OAAA,EAAS,SAAA,EAAU,GAAIA,qCAAoB,YAAY,CAAA;AAC3E,EAAA,MAAM,aAAaC,+BAAA,EAAe;AAGlC,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,MAAA,UAAA,CAAW;AAAA,QACT,UAAA,EAAY,YAAA;AAAA,QACZ,UAAA,EAAY,YAAA;AAAA,QACZ,UAAA,EAAY;AAAA,UACV,aAAA,EAAe,YAAA;AAAA,UACf,WAAA,EAAa;AAAA;AACf,OACD,CAAA;AAAA,IACH;AAAA,EACF,GAAG,CAAC,YAAA,EAAc,OAAA,EAAS,eAAA,EAAiB,UAAU,CAAC,CAAA;AAGvD,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,uBAAOC,cAAA,CAAAC,mBAAA,EAAA,EAAG,mBAAS,EAAE,OAAA,EAAS,WAAW,SAAA,EAAW,SAAA,EAAW,CAAA,EAAE,CAAA;AAAA,EACnE;AAGA,EAAA,MAAM,kBAAkB,OAAA,IAAW,SAAA;AACnC,EAAA,MAAM,OAAA,GAAU,SAAS,eAAe,CAAA;AAExC,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,6DAAU,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,EACpB;AAGA,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,6DAAU,QAAA,EAAA,QAAA,EAAS,CAAA;AAAA,EACrB;AAEA,EAAA,uBAAOD,cAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,QAAA,CAAS,OAAA,IAAW,IAAA,EAAK,CAAA;AACrC;AAYO,SAAS,qBAAqB,YAAA,EAAsB;AACzD,EAAA,OAAOJ,qCAAoB,YAAY,CAAA;AACzC;AAoBO,SAAS,oBAAA,CAAqB;AAAA,EACnC,YAAA;AAAA,EACA,WAAA,GAAc,OAAA;AAAA,EACd,KAAA;AAAA,EACA;AACF,CAAA,EAKG;AACD,EAAA,MAAM,aAAaC,+BAAA,EAAe;AAClC,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAID,oCAAA,CAAoB,YAAY,CAAA;AAEpD,EAAA,MAAM,oBAAoB,MAAM;AAC9B,IAAA,UAAA,CAAW;AAAA,MACT,UAAA,EAAY,YAAA;AAAA,MACZ,UAAA,EAAY,YAAA;AAAA,MACZ,UAAA,EAAY;AAAA,QACV,aAAA,EAAe,YAAA;AAAA,QACf,WAAA,EAAa,OAAA;AAAA,QACb,YAAA,EAAc,WAAA;AAAA,QACd;AAAA;AACF,KACD,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,OAAOK,sBAAA,CAAM,aAAa,QAAA,EAAU;AAAA,IAClC,OAAA,EAAS,CAAC,CAAA,KAAwB;AAChC,MAAA,iBAAA,EAAkB;AAElB,MAAA,IAAK,QAAA,CAAS,MAAsD,OAAA,EAAS;AAC3E,QAAC,QAAA,CAAS,KAAA,CAAsD,OAAA,CAAS,CAAC,CAAA;AAAA,MAC5E;AAAA,IACF;AAAA,GACD,CAAA;AACH","file":"chunk-K6YQ73IX.js","sourcesContent":["/**\n * @sonordev/site-kit/signal - SignalExperiment\n * \n * A/B test component that renders different variants based on Signal assignment.\n * Uses Thompson Sampling (Bayesian bandit) for optimization.\n */\n\n'use client'\n\nimport React, { useMemo, useEffect } from 'react'\nimport { useSignalExperiment, useSignalEvent } from './SignalBridge'\n\ninterface SignalExperimentProps {\n /** Unique experiment ID */\n experimentId: string\n \n /** Variants to test - keys are variant names, values are React nodes */\n variants: Record<string, React.ReactNode>\n \n /** Fallback content if no assignment (defaults to 'control' variant) */\n fallback?: React.ReactNode\n \n /** Track impression automatically */\n trackImpression?: boolean\n \n /** Children function for more control */\n children?: (props: { variant: string; isControl: boolean }) => React.ReactNode\n}\n\n/**\n * Renders different content variants based on Signal's A/B assignment.\n * \n * @example\n * // Simple variant switching\n * <SignalExperiment experimentId=\"hero-cta\" variants={{\n * control: <Button>Get Started</Button>,\n * variant_a: <Button variant=\"primary\">Start Free Trial</Button>,\n * variant_b: <Button variant=\"secondary\">Book a Demo</Button>,\n * }} />\n * \n * @example\n * // With render prop for more control\n * <SignalExperiment experimentId=\"pricing-layout\">\n * {({ variant, isControl }) => (\n * <PricingSection layout={isControl ? 'cards' : 'table'} />\n * )}\n * </SignalExperiment>\n */\nexport function SignalExperiment({\n experimentId,\n variants,\n fallback,\n trackImpression = true,\n children,\n}: SignalExperimentProps) {\n const { assignment, variant, isControl } = useSignalExperiment(experimentId)\n const trackEvent = useSignalEvent()\n \n // Track impression when variant is rendered\n useEffect(() => {\n if (trackImpression && variant) {\n trackEvent({\n event_type: 'experiment',\n event_name: 'impression',\n event_data: {\n experiment_id: experimentId,\n variant_key: variant,\n },\n })\n }\n }, [experimentId, variant, trackImpression, trackEvent])\n \n // If using render prop\n if (children) {\n return <>{children({ variant: variant || 'control', isControl })}</>\n }\n \n // Determine what to render\n const selectedVariant = variant || 'control'\n const content = variants[selectedVariant]\n \n if (content !== undefined) {\n return <>{content}</>\n }\n \n // Fallback to provided fallback or control\n if (fallback) {\n return <>{fallback}</>\n }\n \n return <>{variants.control || null}</>\n}\n\n/**\n * Hook for conditional experiment logic\n * \n * @example\n * const { variant, isControl } = useExperimentVariant('pricing-test')\n * \n * return isControl \n * ? <OldPricing />\n * : <NewPricing showAnnual={variant === 'annual_first'} />\n */\nexport function useExperimentVariant(experimentId: string) {\n return useSignalExperiment(experimentId)\n}\n\n/**\n * Component for tracking experiment conversions\n * Wraps interactive elements to track when they convert\n * \n * @example\n * <SignalExperiment experimentId=\"signup-button\" variants={{\n * control: (\n * <ExperimentConversion experimentId=\"signup-button\" outcomeType=\"click\">\n * <Button>Sign Up</Button>\n * </ExperimentConversion>\n * ),\n * variant_a: (\n * <ExperimentConversion experimentId=\"signup-button\" outcomeType=\"click\">\n * <Button variant=\"large\">Join Now - It's Free!</Button>\n * </ExperimentConversion>\n * ),\n * }} />\n */\nexport function ExperimentConversion({\n experimentId,\n outcomeType = 'click',\n value,\n children,\n}: {\n experimentId: string\n outcomeType?: string\n value?: number\n children: React.ReactElement<{ onClick?: (e: React.MouseEvent) => void }>\n}) {\n const trackEvent = useSignalEvent()\n const { variant } = useSignalExperiment(experimentId)\n\n const handleInteraction = () => {\n trackEvent({\n event_type: 'experiment',\n event_name: 'conversion',\n event_data: {\n experiment_id: experimentId,\n variant_key: variant,\n outcome_type: outcomeType,\n value,\n },\n })\n }\n\n return React.cloneElement(children, {\n onClick: (e: React.MouseEvent) => {\n handleInteraction()\n // Call original onClick if exists\n if ((children.props as { onClick?: (e: React.MouseEvent) => void }).onClick) {\n (children.props as { onClick?: (e: React.MouseEvent) => void }).onClick!(e)\n }\n },\n })\n}\n"]}
|
|
@@ -54,7 +54,7 @@ function useSignal() {
|
|
|
54
54
|
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
55
55
|
}
|
|
56
56
|
var LazySignalCore = dynamic(
|
|
57
|
-
() => import('./SignalCore-
|
|
57
|
+
() => import('./SignalCore-IUOSVYUE.mjs').then((m) => ({ default: m.SignalCore })),
|
|
58
58
|
{ ssr: false }
|
|
59
59
|
);
|
|
60
60
|
function SignalBridge({
|
|
@@ -161,5 +161,5 @@ function useSignalExperiment(experimentId) {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
export { SignalBridge, useSignal, useSignalConfig, useSignalEvent, useSignalExperiment, useSignalOutcome };
|
|
164
|
-
//# sourceMappingURL=chunk-
|
|
165
|
-
//# sourceMappingURL=chunk-
|
|
164
|
+
//# sourceMappingURL=chunk-KC2FCI5V.mjs.map
|
|
165
|
+
//# sourceMappingURL=chunk-KC2FCI5V.mjs.map
|