@sonordev/site-kit 4.3.2 → 5.0.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/agent-manifest.json +1 -1
- package/dist/AnalyticsProvider-KCLRFOX6.mjs +10 -0
- package/dist/{AnalyticsProvider-TWRQHKPZ.mjs.map → AnalyticsProvider-KCLRFOX6.mjs.map} +1 -1
- package/dist/AnalyticsProvider-TXM3DRLI.js +31 -0
- package/dist/{AnalyticsProvider-DNMYYBKX.js.map → AnalyticsProvider-TXM3DRLI.js.map} +1 -1
- package/dist/BlogViewTracker-J6LRGUHU.js +51 -0
- package/dist/BlogViewTracker-J6LRGUHU.js.map +1 -0
- package/dist/BlogViewTracker-PZWAWB3O.mjs +48 -0
- package/dist/BlogViewTracker-PZWAWB3O.mjs.map +1 -0
- package/dist/ChatWidget-LD54Z2PQ.js +20 -0
- package/dist/{ChatWidget-MDWEXISQ.js.map → ChatWidget-LD54Z2PQ.js.map} +1 -1
- package/dist/ChatWidget-PMDPSZXG.mjs +11 -0
- package/dist/{ChatWidget-UQMEJAHY.mjs.map → ChatWidget-PMDPSZXG.mjs.map} +1 -1
- package/dist/EngageWidget-4CBFRN4Y.mjs +9 -0
- package/dist/{EngageWidget-UIIN43ZU.mjs.map → EngageWidget-4CBFRN4Y.mjs.map} +1 -1
- package/dist/EngageWidget-BF5NMQ5A.js +18 -0
- package/dist/{EngageWidget-VRC7NUG6.js.map → EngageWidget-BF5NMQ5A.js.map} +1 -1
- package/dist/{FileField-LVF3SCDC.mjs → FileField-FFRHS3XP.mjs} +5 -5
- package/dist/{FileField-LVF3SCDC.mjs.map → FileField-FFRHS3XP.mjs.map} +1 -1
- package/dist/{FileField-W4VXQ3CM.js → FileField-VXADIOMN.js} +6 -6
- package/dist/{FileField-W4VXQ3CM.js.map → FileField-VXADIOMN.js.map} +1 -1
- package/dist/{FormSpotlight-CRLFAPNI.js → FormSpotlight-JG4ZHS56.js} +15 -15
- package/dist/{FormSpotlight-CRLFAPNI.js.map → FormSpotlight-JG4ZHS56.js.map} +1 -1
- package/dist/{FormSpotlight-ZPISKGIX.mjs → FormSpotlight-KE46EDP5.mjs} +3 -3
- package/dist/{FormSpotlight-ZPISKGIX.mjs.map → FormSpotlight-KE46EDP5.mjs.map} +1 -1
- package/dist/{FormStage-QMRPAROJ.js → FormStage-5Z53Z2WY.js} +13 -13
- package/dist/{FormStage-QMRPAROJ.js.map → FormStage-5Z53Z2WY.js.map} +1 -1
- package/dist/{FormStage-WQOAP7CR.mjs → FormStage-PZKWEXZE.mjs} +3 -3
- package/dist/{FormStage-WQOAP7CR.mjs.map → FormStage-PZKWEXZE.mjs.map} +1 -1
- package/dist/ManagedForm-E4WHMARH.js +23 -0
- package/dist/{ManagedForm-7L2W5BBZ.js.map → ManagedForm-E4WHMARH.js.map} +1 -1
- package/dist/ManagedForm-VU2TKV6J.mjs +14 -0
- package/dist/{ManagedForm-D7CFOIXH.mjs.map → ManagedForm-VU2TKV6J.mjs.map} +1 -1
- package/dist/ManagedNewsletterForm-6DET3VGZ.mjs +62 -0
- package/dist/ManagedNewsletterForm-6DET3VGZ.mjs.map +1 -0
- package/dist/ManagedNewsletterForm-PG4EX3W6.js +64 -0
- package/dist/ManagedNewsletterForm-PG4EX3W6.js.map +1 -0
- package/dist/{SignalCore-JQLRWMPJ.mjs → SignalCore-RZVTLUC2.mjs} +5 -5
- package/dist/{SignalCore-JQLRWMPJ.mjs.map → SignalCore-RZVTLUC2.mjs.map} +1 -1
- package/dist/{SignalCore-DNXKQKRO.js → SignalCore-TCUVXVMN.js} +11 -11
- package/dist/{SignalCore-DNXKQKRO.js.map → SignalCore-TCUVXVMN.js.map} +1 -1
- package/dist/SitemapSync-AJU4DM3G.js +17 -0
- package/dist/{SitemapSync-SR6OJRIA.js.map → SitemapSync-AJU4DM3G.js.map} +1 -1
- package/dist/SitemapSync-T75NAFB3.mjs +8 -0
- package/dist/{SitemapSync-RFY6PUG7.mjs.map → SitemapSync-T75NAFB3.mjs.map} +1 -1
- package/dist/analytics/index.js +9 -9
- package/dist/analytics/index.mjs +4 -4
- package/dist/blog/AuthorCard.d.ts.map +1 -1
- package/dist/blog/AuthorPage.d.ts.map +1 -1
- package/dist/blog/BlogList.d.ts +7 -1
- package/dist/blog/BlogList.d.ts.map +1 -1
- package/dist/blog/BlogPost.d.ts +1 -1
- package/dist/blog/BlogPost.d.ts.map +1 -1
- package/dist/blog/BlogSidebar.d.ts +0 -7
- package/dist/blog/BlogSidebar.d.ts.map +1 -1
- package/dist/blog/BlogViewTracker.d.ts +10 -0
- package/dist/blog/BlogViewTracker.d.ts.map +1 -0
- package/dist/blog/ClusterLandingPage.d.ts.map +1 -1
- package/dist/blog/ManagedNewsletterForm.d.ts +13 -0
- package/dist/blog/ManagedNewsletterForm.d.ts.map +1 -0
- package/dist/blog/NewsletterShell.d.ts +18 -0
- package/dist/blog/NewsletterShell.d.ts.map +1 -0
- package/dist/blog/NewsletterWidget.d.ts +31 -0
- package/dist/blog/NewsletterWidget.d.ts.map +1 -0
- package/dist/blog/RelatedPosts.d.ts.map +1 -1
- package/dist/blog/author-social.d.ts +15 -0
- package/dist/blog/author-social.d.ts.map +1 -0
- package/dist/blog/author-social.test.d.ts +2 -0
- package/dist/blog/author-social.test.d.ts.map +1 -0
- package/dist/blog/blog-view-tracker.test.d.ts +2 -0
- package/dist/blog/blog-view-tracker.test.d.ts.map +1 -0
- package/dist/blog/category-metadata.test.d.ts +2 -0
- package/dist/blog/category-metadata.test.d.ts.map +1 -0
- package/dist/blog/index.d.ts +4 -2
- package/dist/blog/index.d.ts.map +1 -1
- package/dist/blog/index.js +94 -505
- package/dist/blog/index.js.map +1 -1
- package/dist/blog/index.mjs +59 -491
- package/dist/blog/index.mjs.map +1 -1
- package/dist/blog/newsletter-widget.test.d.ts +2 -0
- package/dist/blog/newsletter-widget.test.d.ts.map +1 -0
- package/dist/blog/reading-time.d.ts +10 -0
- package/dist/blog/reading-time.d.ts.map +1 -0
- package/dist/blog/reading-time.test.d.ts +2 -0
- package/dist/blog/reading-time.test.d.ts.map +1 -0
- package/dist/blog/server-core-contract.test.d.ts +2 -0
- package/dist/blog/server-core-contract.test.d.ts.map +1 -0
- package/dist/blog/server-core.d.ts +16 -4
- package/dist/blog/server-core.d.ts.map +1 -1
- package/dist/blog/server-ui.d.ts +5 -0
- package/dist/blog/server-ui.d.ts.map +1 -1
- package/dist/blog/server-ui.js +35 -10
- package/dist/blog/server-ui.js.map +1 -1
- package/dist/blog/server-ui.mjs +8 -6
- package/dist/blog/server-ui.mjs.map +1 -1
- package/dist/blog/server.d.ts +16 -4
- package/dist/blog/server.d.ts.map +1 -1
- package/dist/blog/server.js +40 -40
- package/dist/blog/server.mjs +1 -1
- package/dist/blog/tag-links.test.d.ts +2 -0
- package/dist/blog/tag-links.test.d.ts.map +1 -0
- package/dist/blog/types-wire-shape.test.d.ts +2 -0
- package/dist/blog/types-wire-shape.test.d.ts.map +1 -0
- package/dist/blog/types.d.ts +25 -19
- package/dist/blog/types.d.ts.map +1 -1
- package/dist/blog/widget-styles.d.ts +9 -0
- package/dist/blog/widget-styles.d.ts.map +1 -0
- package/dist/{chunk-EUAOOOIE.js → chunk-3FZ76VI5.js} +14 -14
- package/dist/{chunk-EUAOOOIE.js.map → chunk-3FZ76VI5.js.map} +1 -1
- package/dist/{chunk-C22WW7G2.mjs → chunk-3SMG4EXR.mjs} +3 -3
- package/dist/{chunk-C22WW7G2.mjs.map → chunk-3SMG4EXR.mjs.map} +1 -1
- package/dist/{chunk-FPWPRFTP.js → chunk-4D2G5ZNP.js} +15 -15
- package/dist/{chunk-FPWPRFTP.js.map → chunk-4D2G5ZNP.js.map} +1 -1
- package/dist/{chunk-45VPULF2.mjs → chunk-4UHO6DPJ.mjs} +4 -4
- package/dist/{chunk-45VPULF2.mjs.map → chunk-4UHO6DPJ.mjs.map} +1 -1
- package/dist/{chunk-I6XKZNFT.mjs → chunk-5JNLZRLX.mjs} +3 -3
- package/dist/{chunk-I6XKZNFT.mjs.map → chunk-5JNLZRLX.mjs.map} +1 -1
- package/dist/{chunk-ULP6BTN2.js → chunk-5JQLLF2T.js} +12 -12
- package/dist/{chunk-ULP6BTN2.js.map → chunk-5JQLLF2T.js.map} +1 -1
- package/dist/{chunk-H27UCW6K.js → chunk-5OHHYGD6.js} +15 -15
- package/dist/{chunk-H27UCW6K.js.map → chunk-5OHHYGD6.js.map} +1 -1
- package/dist/{chunk-CQWG5ICB.js → chunk-62ODUWUX.js} +4 -4
- package/dist/{chunk-CQWG5ICB.js.map → chunk-62ODUWUX.js.map} +1 -1
- package/dist/{chunk-I2IA3JXD.js → chunk-6QQFEJIP.js} +8 -8
- package/dist/{chunk-I2IA3JXD.js.map → chunk-6QQFEJIP.js.map} +1 -1
- package/dist/{chunk-RUOEUMAW.js → chunk-7T4VMXKD.js} +3 -3
- package/dist/{chunk-RUOEUMAW.js.map → chunk-7T4VMXKD.js.map} +1 -1
- package/dist/chunk-AK6JZ3OE.mjs +65 -0
- package/dist/chunk-AK6JZ3OE.mjs.map +1 -0
- package/dist/{chunk-MWKYLWCH.mjs → chunk-AXYKBQVI.mjs} +8 -8
- package/dist/{chunk-MWKYLWCH.mjs.map → chunk-AXYKBQVI.mjs.map} +1 -1
- package/dist/{chunk-VLHHBJSV.js → chunk-BQY3FJ3R.js} +8 -8
- package/dist/{chunk-VLHHBJSV.js.map → chunk-BQY3FJ3R.js.map} +1 -1
- package/dist/{chunk-6VHVIBLQ.mjs → chunk-DHXQBTXE.mjs} +62 -41
- package/dist/chunk-DHXQBTXE.mjs.map +1 -0
- package/dist/chunk-DO6ATNLN.js +67 -0
- package/dist/chunk-DO6ATNLN.js.map +1 -0
- package/dist/{chunk-E242GZNE.js → chunk-DX6D7HDI.js} +4 -4
- package/dist/{chunk-E242GZNE.js.map → chunk-DX6D7HDI.js.map} +1 -1
- package/dist/{chunk-ZWMAQ52D.mjs → chunk-EISVUD75.mjs} +3 -3
- package/dist/{chunk-ZWMAQ52D.mjs.map → chunk-EISVUD75.mjs.map} +1 -1
- package/dist/{chunk-JNKQKGYH.mjs → chunk-FCKL5CF3.mjs} +3 -3
- package/dist/{chunk-JNKQKGYH.mjs.map → chunk-FCKL5CF3.mjs.map} +1 -1
- package/dist/{chunk-NDS6MXIP.mjs → chunk-FDWDXMJL.mjs} +3 -3
- package/dist/{chunk-NDS6MXIP.mjs.map → chunk-FDWDXMJL.mjs.map} +1 -1
- package/dist/{chunk-ZXRORNAW.js → chunk-FQ3JQV3M.js} +4 -4
- package/dist/{chunk-ZXRORNAW.js.map → chunk-FQ3JQV3M.js.map} +1 -1
- package/dist/{chunk-A7OO6OPW.mjs → chunk-FTBABL3T.mjs} +3 -3
- package/dist/{chunk-A7OO6OPW.mjs.map → chunk-FTBABL3T.mjs.map} +1 -1
- package/dist/{chunk-5EFSD7VM.js → chunk-GCZMDY2T.js} +490 -20
- package/dist/chunk-GCZMDY2T.js.map +1 -0
- package/dist/{chunk-4OSZ2JD3.js → chunk-HCN2US4M.js} +18 -18
- package/dist/{chunk-4OSZ2JD3.js.map → chunk-HCN2US4M.js.map} +1 -1
- package/dist/{chunk-3BXYZC3V.mjs → chunk-HEUCDF3O.mjs} +3 -3
- package/dist/{chunk-3BXYZC3V.mjs.map → chunk-HEUCDF3O.mjs.map} +1 -1
- package/dist/chunk-HX6ZG3PP.js +19 -0
- package/dist/chunk-HX6ZG3PP.js.map +1 -0
- package/dist/{chunk-UR4HKLHW.js → chunk-IX5RAZWX.js} +6 -6
- package/dist/{chunk-UR4HKLHW.js.map → chunk-IX5RAZWX.js.map} +1 -1
- package/dist/{chunk-NQEUFRMX.mjs → chunk-J4JXAFSW.mjs} +3 -3
- package/dist/{chunk-NQEUFRMX.mjs.map → chunk-J4JXAFSW.mjs.map} +1 -1
- package/dist/{chunk-FBI6LYTM.js → chunk-JBGRXICL.js} +62 -41
- package/dist/chunk-JBGRXICL.js.map +1 -0
- package/dist/{chunk-FIA3WNEM.js → chunk-JIUVBSQZ.js} +3 -3
- package/dist/{chunk-FIA3WNEM.js.map → chunk-JIUVBSQZ.js.map} +1 -1
- package/dist/chunk-JPTJGLKS.mjs +6 -0
- package/dist/{chunk-NQNSATTA.mjs.map → chunk-JPTJGLKS.mjs.map} +1 -1
- package/dist/{chunk-YWBXKEAF.js → chunk-JY3KZDEM.js} +3 -3
- package/dist/{chunk-YWBXKEAF.js.map → chunk-JY3KZDEM.js.map} +1 -1
- package/dist/{chunk-67MTTPJV.js → chunk-KWH6RAVY.js} +11 -11
- package/dist/{chunk-67MTTPJV.js.map → chunk-KWH6RAVY.js.map} +1 -1
- package/dist/{chunk-M52FYLV4.js → chunk-LLKXPJSE.js} +7 -7
- package/dist/{chunk-M52FYLV4.js.map → chunk-LLKXPJSE.js.map} +1 -1
- package/dist/{chunk-6ABCPVJL.js → chunk-LZY374FB.js} +4 -4
- package/dist/{chunk-6ABCPVJL.js.map → chunk-LZY374FB.js.map} +1 -1
- package/dist/{chunk-OQTP5M5Y.js → chunk-MGPHBVOK.js} +9 -9
- package/dist/{chunk-OQTP5M5Y.js.map → chunk-MGPHBVOK.js.map} +1 -1
- package/dist/{chunk-VYNOQCEK.js → chunk-MPO6TRLS.js} +14 -14
- package/dist/{chunk-VYNOQCEK.js.map → chunk-MPO6TRLS.js.map} +1 -1
- package/dist/{chunk-EQZ42LAK.mjs → chunk-MVJQ4ZA2.mjs} +4 -4
- package/dist/{chunk-EQZ42LAK.mjs.map → chunk-MVJQ4ZA2.mjs.map} +1 -1
- package/dist/{chunk-VU45CH5R.mjs → chunk-MWXBRCM2.mjs} +3 -3
- package/dist/{chunk-VU45CH5R.mjs.map → chunk-MWXBRCM2.mjs.map} +1 -1
- package/dist/{chunk-C3M5E4YI.js → chunk-N3RWPAR5.js} +5 -5
- package/dist/{chunk-C3M5E4YI.js.map → chunk-N3RWPAR5.js.map} +1 -1
- package/dist/{chunk-6WJDTGM6.js → chunk-OGPS3DKE.js} +5 -5
- package/dist/{chunk-6WJDTGM6.js.map → chunk-OGPS3DKE.js.map} +1 -1
- package/dist/{chunk-D4KHH44S.mjs → chunk-PPCTL4B5.mjs} +2 -2
- package/dist/{chunk-D4KHH44S.mjs.map → chunk-PPCTL4B5.mjs.map} +1 -1
- package/dist/{chunk-R6XBPA6C.mjs → chunk-PZE5L2FK.mjs} +4 -4
- package/dist/{chunk-R6XBPA6C.mjs.map → chunk-PZE5L2FK.mjs.map} +1 -1
- package/dist/{chunk-PSBAUXUH.mjs → chunk-QH3WVTEK.mjs} +4 -4
- package/dist/{chunk-PSBAUXUH.mjs.map → chunk-QH3WVTEK.mjs.map} +1 -1
- package/dist/{chunk-LQENHKD3.mjs → chunk-QI2H5KK6.mjs} +4 -4
- package/dist/{chunk-LQENHKD3.mjs.map → chunk-QI2H5KK6.mjs.map} +1 -1
- package/dist/chunk-S65JSRWW.js +8 -0
- package/dist/{chunk-UXIUJYFK.js.map → chunk-S65JSRWW.js.map} +1 -1
- package/dist/{chunk-36E6QKGL.mjs → chunk-SW6KWUNI.mjs} +3 -3
- package/dist/{chunk-36E6QKGL.mjs.map → chunk-SW6KWUNI.mjs.map} +1 -1
- package/dist/{chunk-NJ4YX3CI.mjs → chunk-SXPINIJG.mjs} +4 -4
- package/dist/{chunk-NJ4YX3CI.mjs.map → chunk-SXPINIJG.mjs.map} +1 -1
- package/dist/{chunk-R7V7HNJJ.js → chunk-UABMMR7U.js} +2 -2
- package/dist/{chunk-R7V7HNJJ.js.map → chunk-UABMMR7U.js.map} +1 -1
- package/dist/{chunk-X2PAODFM.mjs → chunk-UFURXSAI.mjs} +3 -3
- package/dist/{chunk-X2PAODFM.mjs.map → chunk-UFURXSAI.mjs.map} +1 -1
- package/dist/{chunk-JJS63TDR.mjs → chunk-UV6KQUSL.mjs} +483 -21
- package/dist/chunk-UV6KQUSL.mjs.map +1 -0
- package/dist/{chunk-H3F4GV2S.js → chunk-VACFVPNF.js} +14 -14
- package/dist/{chunk-H3F4GV2S.js.map → chunk-VACFVPNF.js.map} +1 -1
- package/dist/{chunk-LVM63JSA.mjs → chunk-XPAHKYX7.mjs} +3 -3
- package/dist/{chunk-LVM63JSA.mjs.map → chunk-XPAHKYX7.mjs.map} +1 -1
- package/dist/chunk-YFX3Q56O.mjs +16 -0
- package/dist/chunk-YFX3Q56O.mjs.map +1 -0
- package/dist/{chunk-TY3BPHTE.mjs → chunk-YGRHEEPX.mjs} +7 -7
- package/dist/{chunk-TY3BPHTE.mjs.map → chunk-YGRHEEPX.mjs.map} +1 -1
- package/dist/{chunk-BUF6IZUE.mjs → chunk-YSZB2DRX.mjs} +3 -3
- package/dist/{chunk-BUF6IZUE.mjs.map → chunk-YSZB2DRX.mjs.map} +1 -1
- package/dist/{chunk-INR6AQ54.mjs → chunk-YVE65W47.mjs} +4 -4
- package/dist/{chunk-INR6AQ54.mjs.map → chunk-YVE65W47.mjs.map} +1 -1
- package/dist/cli/index.js +7 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +5 -25
- package/dist/cli/index.mjs.map +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/engage/index.js +10 -10
- package/dist/engage/index.mjs +7 -7
- package/dist/fleet/index.js +8 -8
- package/dist/fleet/index.mjs +4 -4
- package/dist/forms/index.js +16 -16
- package/dist/forms/index.mjs +9 -9
- package/dist/forms/server.js +10 -10
- package/dist/forms/server.mjs +8 -8
- package/dist/forms/static.js +7 -7
- package/dist/forms/static.mjs +6 -6
- package/dist/images/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/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/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/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 +1 -1
- package/dist/AnalyticsProvider-DNMYYBKX.js +0 -31
- package/dist/AnalyticsProvider-TWRQHKPZ.mjs +0 -10
- package/dist/ChatWidget-MDWEXISQ.js +0 -20
- package/dist/ChatWidget-UQMEJAHY.mjs +0 -11
- package/dist/EngageWidget-UIIN43ZU.mjs +0 -9
- package/dist/EngageWidget-VRC7NUG6.js +0 -18
- package/dist/ManagedForm-7L2W5BBZ.js +0 -23
- package/dist/ManagedForm-D7CFOIXH.mjs +0 -14
- package/dist/SitemapSync-RFY6PUG7.mjs +0 -8
- package/dist/SitemapSync-SR6OJRIA.js +0 -17
- package/dist/chunk-5EFSD7VM.js.map +0 -1
- package/dist/chunk-6VHVIBLQ.mjs.map +0 -1
- package/dist/chunk-FBI6LYTM.js.map +0 -1
- package/dist/chunk-JJS63TDR.mjs.map +0 -1
- package/dist/chunk-NQNSATTA.mjs +0 -6
- package/dist/chunk-UXIUJYFK.js +0 -8
|
@@ -26,7 +26,7 @@ function useFieldControls(fields) {
|
|
|
26
26
|
}
|
|
27
27
|
if (needsFile && !requested.current.file) {
|
|
28
28
|
requested.current.file = true;
|
|
29
|
-
import('./FileField-
|
|
29
|
+
import('./FileField-FFRHS3XP.mjs').then((m) => {
|
|
30
30
|
if (!cancelled) setControls((prev) => ({ ...prev, File: m.FileField }));
|
|
31
31
|
}).catch(() => {
|
|
32
32
|
requested.current.file = false;
|
|
@@ -134,5 +134,5 @@ function formatAnswer(value) {
|
|
|
134
134
|
var AUTO_ADVANCE_TYPES = ["radio", "select", "rating"];
|
|
135
135
|
|
|
136
136
|
export { AUTO_ADVANCE_TYPES, countStageQuestions, countStageViews, formatAnswer, isEmptyValue, stageEstimate, stageFirstName, stageViews, useFieldControls };
|
|
137
|
-
//# sourceMappingURL=chunk-
|
|
138
|
-
//# sourceMappingURL=chunk-
|
|
137
|
+
//# sourceMappingURL=chunk-3SMG4EXR.mjs.map
|
|
138
|
+
//# sourceMappingURL=chunk-3SMG4EXR.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/forms/field-controls.ts","../src/forms/experience-plan.ts"],"names":[],"mappings":"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","file":"chunk-C22WW7G2.mjs","sourcesContent":["'use client'\n\n/**\n * The field controls that need React hooks, loaded only when a form actually\n * has a field that uses them.\n *\n * FormField deliberately does NOT import these. DatePicker carries 17 hooks\n * and FileField holds upload state; importing them there put both in\n * FormField's chunk, which the build stamps `'use client'` — so a Server\n * Component rendering a single text input shipped 38 KB of date picker to the\n * browser. Injecting them from the client side keeps FormField hook-free and\n * therefore server-renderable, which is the whole basis of StaticForm.\n *\n * Injection alone still put both controls in the forms entry's eager graph,\n * because a module-level constant referencing them is a static import: every\n * form paid for the date picker, and most forms have no date field. They are\n * now fetched on demand, per control, from the field list.\n *\n * The wait costs nothing visually. FormField already renders the native\n * control when a control is absent (static mode does exactly this), so a date\n * field paints as `<input type=\"date\">` and upgrades in place when the chunk\n * lands — the same enhancement path a no-JS visitor never leaves.\n *\n * Every client renderer of <FormField> (FormClient, FormSpotlight, FormStage)\n * calls this hook, so the experiences and the classic form always offer\n * identical controls.\n */\n\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { FormField as FormFieldType } from './types'\n\nexport interface FieldControls {\n Date?: React.ComponentType<any>\n File?: React.ComponentType<any>\n}\n\n/** Stable identity for the common case (no date, no file) — no render churn. */\nconst NO_CONTROLS: FieldControls = {}\n\nexport function useFieldControls(fields: FormFieldType[] | undefined): FieldControls {\n const needsDate = useMemo(\n () => (fields ?? []).some((f) => f.field_type === 'date' || f.field_type === 'datetime'),\n [fields],\n )\n const needsFile = useMemo(\n () => (fields ?? []).some((f) => f.field_type === 'file'),\n [fields],\n )\n\n const [controls, setControls] = useState<FieldControls>(NO_CONTROLS)\n // Guards re-import when a conditional field toggles visibility mid-fill.\n const requested = useRef({ date: false, file: false })\n\n useEffect(() => {\n let cancelled = false\n\n if (needsDate && !requested.current.date) {\n requested.current.date = true\n import('./DatePicker')\n .then((m) => {\n if (!cancelled) setControls((prev) => ({ ...prev, Date: m.DatePicker }))\n })\n .catch(() => {\n // Chunk never arrived — the native date input stays, which submits\n // the same value. A failed fetch must not cost the lead.\n requested.current.date = false\n })\n }\n\n if (needsFile && !requested.current.file) {\n requested.current.file = true\n import('./FileField')\n .then((m) => {\n if (!cancelled) setControls((prev) => ({ ...prev, File: m.FileField }))\n })\n .catch(() => {\n requested.current.file = false\n })\n }\n\n return () => {\n cancelled = true\n }\n }, [needsDate, needsFile])\n\n return controls\n}\n","/**\n * Shared planning helpers for the alternate form experiences (stage,\n * spotlight). Pure data — no React, no CSS — so FormClient can import the\n * counts it needs for tracking WITHOUT pulling an experience component (and\n * its stylesheet) into every site's forms bundle. The components themselves\n * are lazy-loaded; this module is the only part that ships eagerly.\n */\n\nimport type {\n FormField as FormFieldType,\n FormStep,\n ManagedFormConfig,\n} from './types'\n\n/** Display-only / non-question field types — never staged. */\nconst NON_QUESTION_TYPES = ['heading', 'section_header', 'paragraph', 'hidden']\n\nexport interface StageQuestion {\n field: FormFieldType\n /** The step (chapter) this question belongs to, when the form is multi-step. */\n step?: FormStep\n}\n\nexport interface StageView {\n fields: FormFieldType[]\n step?: FormStep\n}\n\n/**\n * Flatten a form into its ordered question list. Steps become chapters:\n * multi-step forms iterate steps in step_number order (fields are already\n * sorted by sort_order via normalizeFormConfig); stepless fields trail.\n */\nexport function stageQuestions(config: ManagedFormConfig): StageQuestion[] {\n const input = (config.fields || []).filter(\n (f) => !NON_QUESTION_TYPES.includes(f.field_type),\n )\n if (!config.is_multi_step || !config.steps?.length) {\n return input.map((field) => ({ field }))\n }\n const ordered = [...config.steps].sort((a, b) => a.step_number - b.step_number)\n const out: StageQuestion[] = []\n const claimed = new Set<string>()\n for (const step of ordered) {\n for (const field of input) {\n if (field.step_id === step.id) {\n out.push({ field, step })\n claimed.add(field.id)\n }\n }\n }\n for (const field of input) {\n if (!claimed.has(field.id)) out.push({ field })\n }\n return out\n}\n\nconst WIDTH_FRACTION: Record<string, number> = {\n full: 1,\n half: 1 / 2,\n third: 1 / 3,\n quarter: 1 / 4,\n}\n\n/**\n * Group questions into beats using the classic grid's own row math:\n * consecutive sub-full-width fields fill a row (2 halves, 3 thirds...) and\n * that row is one beat; a full-width field closes any open row and stands\n * alone; a chapter change always starts a fresh beat.\n */\nexport function stageViews(config: ManagedFormConfig): StageView[] {\n const questions = stageQuestions(config)\n const views: StageView[] = []\n let open: StageView | null = null\n let openWidth = 0\n\n const close = () => {\n if (open) views.push(open)\n open = null\n openWidth = 0\n }\n\n for (const q of questions) {\n const fraction = WIDTH_FRACTION[q.field.width] ?? 1\n const chapterChanged = open && open.step?.id !== q.step?.id\n if (fraction >= 1 || chapterChanged) close()\n if (!open) {\n open = { fields: [q.field], step: q.step }\n openWidth = fraction\n if (fraction >= 1) close()\n continue\n }\n if (openWidth + fraction > 1.0001) close()\n if (!open) {\n open = { fields: [q.field], step: q.step }\n openWidth = fraction\n continue\n }\n open.fields.push(q.field)\n openWidth += fraction\n }\n close()\n return views\n}\n\nexport function countStageViews(config: ManagedFormConfig): number {\n return stageViews(config).length\n}\n\nexport function countStageQuestions(config: ManagedFormConfig): number {\n return stageQuestions(config).length\n}\n\n/**\n * Honest, friendly time estimate: \"about 40 seconds\".\n * ~8s per question, rounded to something a human would actually say.\n */\nexport function stageEstimate(questionCount: number): string {\n const seconds = questionCount * 8\n if (seconds < 50) return `about ${Math.max(15, Math.ceil(seconds / 5) * 5)} seconds`\n if (seconds <= 90) return 'about a minute'\n return `about ${Math.round(seconds / 60)} minutes`\n}\n\n/** First name for the greeting — conservative: short, single token, else null. */\nexport function stageFirstName(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const token = value.trim().split(/\\s+/)[0]\n if (!token || token.length > 24) return null\n return token\n}\n\nexport function isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return true\n if (Array.isArray(value)) return value.length === 0\n return false\n}\n\nexport function formatAnswer(value: unknown): string | null {\n if (isEmptyValue(value)) return null\n if (Array.isArray(value)) return value.map(String).join(', ')\n if (typeof value === 'boolean') return value ? 'Yes' : 'No'\n return String(value)\n}\n\n/** Single-select types where picking an answer advances after a lock-in beat. */\nexport const AUTO_ADVANCE_TYPES = ['radio', 'select', 'rating']\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/forms/field-controls.ts","../src/forms/experience-plan.ts"],"names":[],"mappings":"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","file":"chunk-3SMG4EXR.mjs","sourcesContent":["'use client'\n\n/**\n * The field controls that need React hooks, loaded only when a form actually\n * has a field that uses them.\n *\n * FormField deliberately does NOT import these. DatePicker carries 17 hooks\n * and FileField holds upload state; importing them there put both in\n * FormField's chunk, which the build stamps `'use client'` — so a Server\n * Component rendering a single text input shipped 38 KB of date picker to the\n * browser. Injecting them from the client side keeps FormField hook-free and\n * therefore server-renderable, which is the whole basis of StaticForm.\n *\n * Injection alone still put both controls in the forms entry's eager graph,\n * because a module-level constant referencing them is a static import: every\n * form paid for the date picker, and most forms have no date field. They are\n * now fetched on demand, per control, from the field list.\n *\n * The wait costs nothing visually. FormField already renders the native\n * control when a control is absent (static mode does exactly this), so a date\n * field paints as `<input type=\"date\">` and upgrades in place when the chunk\n * lands — the same enhancement path a no-JS visitor never leaves.\n *\n * Every client renderer of <FormField> (FormClient, FormSpotlight, FormStage)\n * calls this hook, so the experiences and the classic form always offer\n * identical controls.\n */\n\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { FormField as FormFieldType } from './types'\n\nexport interface FieldControls {\n Date?: React.ComponentType<any>\n File?: React.ComponentType<any>\n}\n\n/** Stable identity for the common case (no date, no file) — no render churn. */\nconst NO_CONTROLS: FieldControls = {}\n\nexport function useFieldControls(fields: FormFieldType[] | undefined): FieldControls {\n const needsDate = useMemo(\n () => (fields ?? []).some((f) => f.field_type === 'date' || f.field_type === 'datetime'),\n [fields],\n )\n const needsFile = useMemo(\n () => (fields ?? []).some((f) => f.field_type === 'file'),\n [fields],\n )\n\n const [controls, setControls] = useState<FieldControls>(NO_CONTROLS)\n // Guards re-import when a conditional field toggles visibility mid-fill.\n const requested = useRef({ date: false, file: false })\n\n useEffect(() => {\n let cancelled = false\n\n if (needsDate && !requested.current.date) {\n requested.current.date = true\n import('./DatePicker')\n .then((m) => {\n if (!cancelled) setControls((prev) => ({ ...prev, Date: m.DatePicker }))\n })\n .catch(() => {\n // Chunk never arrived — the native date input stays, which submits\n // the same value. A failed fetch must not cost the lead.\n requested.current.date = false\n })\n }\n\n if (needsFile && !requested.current.file) {\n requested.current.file = true\n import('./FileField')\n .then((m) => {\n if (!cancelled) setControls((prev) => ({ ...prev, File: m.FileField }))\n })\n .catch(() => {\n requested.current.file = false\n })\n }\n\n return () => {\n cancelled = true\n }\n }, [needsDate, needsFile])\n\n return controls\n}\n","/**\n * Shared planning helpers for the alternate form experiences (stage,\n * spotlight). Pure data — no React, no CSS — so FormClient can import the\n * counts it needs for tracking WITHOUT pulling an experience component (and\n * its stylesheet) into every site's forms bundle. The components themselves\n * are lazy-loaded; this module is the only part that ships eagerly.\n */\n\nimport type {\n FormField as FormFieldType,\n FormStep,\n ManagedFormConfig,\n} from './types'\n\n/** Display-only / non-question field types — never staged. */\nconst NON_QUESTION_TYPES = ['heading', 'section_header', 'paragraph', 'hidden']\n\nexport interface StageQuestion {\n field: FormFieldType\n /** The step (chapter) this question belongs to, when the form is multi-step. */\n step?: FormStep\n}\n\nexport interface StageView {\n fields: FormFieldType[]\n step?: FormStep\n}\n\n/**\n * Flatten a form into its ordered question list. Steps become chapters:\n * multi-step forms iterate steps in step_number order (fields are already\n * sorted by sort_order via normalizeFormConfig); stepless fields trail.\n */\nexport function stageQuestions(config: ManagedFormConfig): StageQuestion[] {\n const input = (config.fields || []).filter(\n (f) => !NON_QUESTION_TYPES.includes(f.field_type),\n )\n if (!config.is_multi_step || !config.steps?.length) {\n return input.map((field) => ({ field }))\n }\n const ordered = [...config.steps].sort((a, b) => a.step_number - b.step_number)\n const out: StageQuestion[] = []\n const claimed = new Set<string>()\n for (const step of ordered) {\n for (const field of input) {\n if (field.step_id === step.id) {\n out.push({ field, step })\n claimed.add(field.id)\n }\n }\n }\n for (const field of input) {\n if (!claimed.has(field.id)) out.push({ field })\n }\n return out\n}\n\nconst WIDTH_FRACTION: Record<string, number> = {\n full: 1,\n half: 1 / 2,\n third: 1 / 3,\n quarter: 1 / 4,\n}\n\n/**\n * Group questions into beats using the classic grid's own row math:\n * consecutive sub-full-width fields fill a row (2 halves, 3 thirds...) and\n * that row is one beat; a full-width field closes any open row and stands\n * alone; a chapter change always starts a fresh beat.\n */\nexport function stageViews(config: ManagedFormConfig): StageView[] {\n const questions = stageQuestions(config)\n const views: StageView[] = []\n let open: StageView | null = null\n let openWidth = 0\n\n const close = () => {\n if (open) views.push(open)\n open = null\n openWidth = 0\n }\n\n for (const q of questions) {\n const fraction = WIDTH_FRACTION[q.field.width] ?? 1\n const chapterChanged = open && open.step?.id !== q.step?.id\n if (fraction >= 1 || chapterChanged) close()\n if (!open) {\n open = { fields: [q.field], step: q.step }\n openWidth = fraction\n if (fraction >= 1) close()\n continue\n }\n if (openWidth + fraction > 1.0001) close()\n if (!open) {\n open = { fields: [q.field], step: q.step }\n openWidth = fraction\n continue\n }\n open.fields.push(q.field)\n openWidth += fraction\n }\n close()\n return views\n}\n\nexport function countStageViews(config: ManagedFormConfig): number {\n return stageViews(config).length\n}\n\nexport function countStageQuestions(config: ManagedFormConfig): number {\n return stageQuestions(config).length\n}\n\n/**\n * Honest, friendly time estimate: \"about 40 seconds\".\n * ~8s per question, rounded to something a human would actually say.\n */\nexport function stageEstimate(questionCount: number): string {\n const seconds = questionCount * 8\n if (seconds < 50) return `about ${Math.max(15, Math.ceil(seconds / 5) * 5)} seconds`\n if (seconds <= 90) return 'about a minute'\n return `about ${Math.round(seconds / 60)} minutes`\n}\n\n/** First name for the greeting — conservative: short, single token, else null. */\nexport function stageFirstName(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const token = value.trim().split(/\\s+/)[0]\n if (!token || token.length > 24) return null\n return token\n}\n\nexport function isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return true\n if (Array.isArray(value)) return value.length === 0\n return false\n}\n\nexport function formatAnswer(value: unknown): string | null {\n if (isEmptyValue(value)) return null\n if (Array.isArray(value)) return value.map(String).join(', ')\n if (typeof value === 'boolean') return value ? 'Yes' : 'No'\n return String(value)\n}\n\n/** Single-select types where picking an answer advances after a lock-in beat. */\nexport const AUTO_ADVANCE_TYPES = ['radio', 'select', 'rating']\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkFQ3JQV3M_js = require('./chunk-FQ3JQV3M.js');
|
|
4
|
+
var chunkS65JSRWW_js = require('./chunk-S65JSRWW.js');
|
|
5
5
|
|
|
6
6
|
// src/shared/fetchWithRetry.ts
|
|
7
7
|
function backoffDelay(attempt, response) {
|
|
@@ -50,7 +50,7 @@ var pausedKeys = (() => {
|
|
|
50
50
|
var warnedThisPage = false;
|
|
51
51
|
function isAuthPaused(credential) {
|
|
52
52
|
if (typeof window === "undefined") return false;
|
|
53
|
-
const id =
|
|
53
|
+
const id = chunkFQ3JQV3M_js.credentialBreakerId(credential);
|
|
54
54
|
const until = pausedKeys.get(id);
|
|
55
55
|
if (!until) return false;
|
|
56
56
|
if (Date.now() >= until) {
|
|
@@ -60,7 +60,7 @@ function isAuthPaused(credential) {
|
|
|
60
60
|
return true;
|
|
61
61
|
}
|
|
62
62
|
function tripBreaker(credential) {
|
|
63
|
-
pausedKeys.set(
|
|
63
|
+
pausedKeys.set(chunkFQ3JQV3M_js.credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS);
|
|
64
64
|
if (!warnedThisPage) {
|
|
65
65
|
warnedThisPage = true;
|
|
66
66
|
console.info(
|
|
@@ -70,13 +70,13 @@ function tripBreaker(credential) {
|
|
|
70
70
|
}
|
|
71
71
|
async function sonorFetch(url, init = {}, options = {}) {
|
|
72
72
|
let credential = options.apiKey;
|
|
73
|
-
if (credential !== void 0 ||
|
|
74
|
-
credential = await
|
|
73
|
+
if (credential !== void 0 || chunkFQ3JQV3M_js.isTokenMode()) {
|
|
74
|
+
credential = await chunkFQ3JQV3M_js.getFreshCredential(options.apiKey);
|
|
75
75
|
}
|
|
76
76
|
const headers = new Headers(init.headers || {});
|
|
77
|
-
headers.set("x-sitekit-version",
|
|
77
|
+
headers.set("x-sitekit-version", chunkS65JSRWW_js.SITE_KIT_VERSION);
|
|
78
78
|
if (credential) {
|
|
79
|
-
if (
|
|
79
|
+
if (chunkFQ3JQV3M_js.isTokenMode()) {
|
|
80
80
|
headers.set("x-api-key", credential);
|
|
81
81
|
headers.delete("authorization");
|
|
82
82
|
} else if (!headers.has("x-api-key")) {
|
|
@@ -87,11 +87,11 @@ async function sonorFetch(url, init = {}, options = {}) {
|
|
|
87
87
|
return new Response(null, { status: 401, statusText: "sonor-auth-paused" });
|
|
88
88
|
}
|
|
89
89
|
let response = await fetchWithRetry(url, { ...init, headers }, options);
|
|
90
|
-
if ((response.status === 401 || response.status === 403) && typeof window !== "undefined" &&
|
|
91
|
-
const refreshed = await
|
|
90
|
+
if ((response.status === 401 || response.status === 403) && typeof window !== "undefined" && chunkFQ3JQV3M_js.isSiteToken(credential)) {
|
|
91
|
+
const refreshed = await chunkFQ3JQV3M_js.refreshSiteToken();
|
|
92
92
|
if (refreshed && refreshed !== credential) {
|
|
93
93
|
const retryHeaders = new Headers(init.headers || {});
|
|
94
|
-
retryHeaders.set("x-sitekit-version",
|
|
94
|
+
retryHeaders.set("x-sitekit-version", chunkS65JSRWW_js.SITE_KIT_VERSION);
|
|
95
95
|
retryHeaders.set("x-api-key", refreshed);
|
|
96
96
|
retryHeaders.delete("authorization");
|
|
97
97
|
credential = refreshed;
|
|
@@ -104,7 +104,7 @@ async function sonorFetch(url, init = {}, options = {}) {
|
|
|
104
104
|
return response;
|
|
105
105
|
}
|
|
106
106
|
function sonorBeacon(url, payload, apiKey) {
|
|
107
|
-
const credential =
|
|
107
|
+
const credential = chunkFQ3JQV3M_js.currentCredential(apiKey);
|
|
108
108
|
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
109
109
|
try {
|
|
110
110
|
void fetch(url, {
|
|
@@ -112,7 +112,7 @@ function sonorBeacon(url, payload, apiKey) {
|
|
|
112
112
|
keepalive: true,
|
|
113
113
|
headers: {
|
|
114
114
|
"Content-Type": "application/json",
|
|
115
|
-
"x-sitekit-version":
|
|
115
|
+
"x-sitekit-version": chunkS65JSRWW_js.SITE_KIT_VERSION,
|
|
116
116
|
...credential ? { "x-api-key": credential } : {}
|
|
117
117
|
},
|
|
118
118
|
body
|
|
@@ -130,5 +130,5 @@ function sonorBeacon(url, payload, apiKey) {
|
|
|
130
130
|
|
|
131
131
|
exports.sonorBeacon = sonorBeacon;
|
|
132
132
|
exports.sonorFetch = sonorFetch;
|
|
133
|
-
//# sourceMappingURL=chunk-
|
|
134
|
-
//# sourceMappingURL=chunk-
|
|
133
|
+
//# sourceMappingURL=chunk-4D2G5ZNP.js.map
|
|
134
|
+
//# sourceMappingURL=chunk-4D2G5ZNP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/fetchWithRetry.ts","../src/shared/sonorFetch.ts"],"names":["credentialBreakerId","isTokenMode","getFreshCredential","SITE_KIT_VERSION","isSiteToken","refreshSiteToken","currentCredential"],"mappings":"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","file":"chunk-FPWPRFTP.js","sourcesContent":["/**\n * @sonordev/site-kit shared - Resilient fetch\n *\n * Single source of truth for client-side resilient fetch (forms, SitemapSync).\n * Adds a per-attempt timeout and retries transient failures (network errors,\n * request timeouts, HTTP 429, and 5xx) with exponential backoff.\n *\n * A form submission must survive a brief API blip — a deploy, a cold start, a\n * dropped packet. A single un-retried fetch turning into \"Something went wrong\"\n * is how leads get lost. The explicit abort also matters on its own: a request\n * left to hang until the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices; an aborted fetch\n * logs nothing.\n */\n\nexport interface FetchRetryOptions {\n /** Number of retries AFTER the first attempt (default 2 → up to 3 tries total). */\n retries?: number\n /** Per-attempt timeout in milliseconds (default 15000). */\n timeoutMs?: number\n}\n\nfunction backoffDelay(attempt: number, response?: Response): number {\n // Honor a Retry-After header on 429 responses when present.\n if (response?.status === 429) {\n const header = response.headers.get('retry-after')\n const seconds = header ? parseInt(header, 10) : NaN\n if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1000, 10000)\n }\n // Exponential backoff: 500ms, 1s, 2s, ... capped at 8s.\n return Math.min(8000, 500 * 2 ** attempt)\n}\n\n/**\n * fetch() with a timeout and automatic retries for transient failures.\n *\n * Non-transient responses (2xx, 3xx, and 4xx other than 429) are returned to\n * the caller as-is — only 429/5xx and thrown network/timeout errors are retried.\n * If every attempt fails with a thrown error, the last error is re-thrown.\n */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n options: FetchRetryOptions = {},\n): Promise<Response> {\n const retries = options.retries ?? 2\n const timeoutMs = options.timeoutMs ?? 15000\n let lastError: unknown\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(url, { ...init, signal: controller.signal })\n clearTimeout(timer)\n\n if ((response.status === 429 || response.status >= 500) && attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)))\n continue\n }\n return response\n } catch (error) {\n clearTimeout(timer)\n lastError = error\n // Network failure or timeout (AbortController) — retry while attempts remain.\n if (attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)))\n continue\n }\n }\n }\n\n throw lastError instanceof Error\n ? lastError\n : new Error('Network request failed after multiple attempts. Please try again.')\n}\n","/**\n * @sonordev/site-kit shared - Canonical client transport\n *\n * Single source of truth for how site-kit talks to Sonor from the browser.\n * Every client-side module (analytics, forms, commerce, engage, maps, images,\n * SitemapSync) routes through here instead of bare fetch(), which gives the\n * whole kit, in one place:\n *\n * - per-attempt timeout + retry with backoff (via fetchWithRetry) — a hung\n * request left to the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices\n * - the x-sitekit-version header on every request, so the platform can see\n * which kit version each site actually runs from live traffic\n * - CREDENTIAL LIFECYCLE: sites on 3.2+ ship a short-lived minted token instead\n * of the raw key. This transport resolves the freshest token (refreshing a\n * stale seed transparently), and on a 401/403 for a token it refreshes once\n * and retries before giving up. A raw key (2.x kit, or the mint-failed\n * fallback) is a pure passthrough — legacy behavior is unchanged.\n * - an auth circuit breaker: after a 401/403 the credential is paused for a few\n * minutes and ONE friendly console.info is emitted — a site on a stale or\n * canceled key must not spam the API or the visitor's console. Keyed by the\n * project (from the token) so it survives token rotation.\n * - sonorBeacon for unload-time telemetry that keeps the credential in headers;\n * navigator.sendBeacon can't set headers and forced the key into the URL,\n * where server and CDN access logs capture it\n *\n * Non-idempotent endpoints (checkout, payment intents, uploads) must pass\n * { retries: 0 } — they still get the timeout, but never an automatic retry.\n */\nimport { fetchWithRetry, type FetchRetryOptions } from './fetchWithRetry'\nimport { SITE_KIT_VERSION } from './version'\nimport {\n credentialBreakerId,\n currentCredential,\n getFreshCredential,\n isSiteToken,\n isTokenMode,\n refreshSiteToken,\n} from './siteToken'\n\nexport interface SonorFetchOptions extends FetchRetryOptions {\n /** Project credential — the minted token (3.2+) or raw key. Sent as x-api-key. */\n apiKey?: string\n}\n\n/** How long a credential stays paused after an auth failure. */\nconst AUTH_BREAK_MS = 5 * 60_000\n\n// breaker id → epoch ms until which requests are paused. Shared on window for\n// the same reason as the token state (see siteToken.ts): duplicated module\n// copies must trip and observe ONE breaker, not one each. Never applies on the\n// server, so a per-module Map there is fine.\nconst BREAKER_STATE_KEY = '__SONOR_AUTH_BREAKER__'\nconst pausedKeys: Map<string, number> = (() => {\n if (typeof window === 'undefined') return new Map()\n const w = window as unknown as Record<string, Map<string, number> | undefined>\n return (w[BREAKER_STATE_KEY] ??= new Map())\n})()\nlet warnedThisPage = false\n\n/** True when a previous 401/403 has paused this credential (browser only). */\nexport function isAuthPaused(credential?: string): boolean {\n if (typeof window === 'undefined') return false\n const id = credentialBreakerId(credential)\n const until = pausedKeys.get(id)\n if (!until) return false\n if (Date.now() >= until) {\n pausedKeys.delete(id)\n return false\n }\n return true\n}\n\nfunction tripBreaker(credential?: string): void {\n pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS)\n if (!warnedThisPage) {\n warnedThisPage = true\n // info, not error: a visitor's console must stay clean (Lighthouse\n // errors-in-console), and copy never names env vars or key formats.\n console.info(\n \"[Sonor] This site couldn't authenticate with Sonor, so data features are paused for a few minutes. If this keeps happening, the site is likely running an outdated API key.\",\n )\n }\n}\n\nexport async function sonorFetch(\n url: string,\n init: RequestInit = {},\n options: SonorFetchOptions = {},\n): Promise<Response> {\n // Resolve the credential to send. In token mode this may transparently refresh\n // a stale seed before the first request; in raw-key mode it's a passthrough.\n let credential = options.apiKey\n if (credential !== undefined || isTokenMode()) {\n credential = await getFreshCredential(options.apiKey)\n }\n\n const headers = new Headers(init.headers || {})\n headers.set('x-sitekit-version', SITE_KIT_VERSION)\n if (credential) {\n if (isTokenMode()) {\n // Token mode: `credential` is the freshest token (refreshed just above if\n // the seed was stale). It MUST override any x-api-key a caller hand-set — a\n // stale build-time seed passed as a header would otherwise pin the request\n // and defeat refresh entirely. Also drop a caller-set Authorization:\n // extractApiKey() server-side prefers `Authorization: Bearer` over\n // x-api-key, so a stale Bearer seed would win even once we set a fresh\n // x-api-key. This is the single place the client credential is chosen — do\n // not re-attach it as a header at call sites.\n headers.set('x-api-key', credential)\n headers.delete('authorization')\n } else if (!headers.has('x-api-key')) {\n // Raw-key mode: legacy passthrough — respect a caller-supplied header.\n headers.set('x-api-key', credential)\n }\n }\n\n if (isAuthPaused(credential)) {\n // Synthetic response keeps every caller's !response.ok path working\n // without a network round trip while the breaker is open.\n return new Response(null, { status: 401, statusText: 'sonor-auth-paused' })\n }\n\n let response = await fetchWithRetry(url, { ...init, headers }, options)\n\n // A token can rotate or expire mid-session. On the first auth failure, try ONE\n // refresh + retry before tripping the breaker — only for token credentials\n // (raw keys have nothing to refresh).\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n isSiteToken(credential)\n ) {\n const refreshed = await refreshSiteToken()\n if (refreshed && refreshed !== credential) {\n const retryHeaders = new Headers(init.headers || {})\n retryHeaders.set('x-sitekit-version', SITE_KIT_VERSION)\n retryHeaders.set('x-api-key', refreshed)\n // Same reason as the initial request: a caller-set stale Bearer would win\n // over our fresh x-api-key server-side, so strip it on the retry too.\n retryHeaders.delete('authorization')\n credential = refreshed\n response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options)\n }\n }\n\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n credential\n ) {\n tripBreaker(credential)\n }\n\n return response\n}\n\n/**\n * Fire-and-forget telemetry that survives page unload.\n *\n * Uses fetch({ keepalive: true }) so auth travels in headers. Falls back to\n * navigator.sendBeacon (no auth headers possible — the server may reject it,\n * but we never put the key in the URL) only when keepalive fetch is missing.\n *\n * Cannot await a refresh at unload time, so it sends the freshest token already\n * held (currentCredential) rather than the possibly-stale seed the caller passed.\n */\nexport function sonorBeacon(url: string, payload: unknown, apiKey?: string): void {\n const credential = currentCredential(apiKey)\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload)\n try {\n void fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n ...(credential ? { 'x-api-key': credential } : {}),\n },\n body,\n }).catch(() => {\n /* page is closing — best effort */\n })\n } catch {\n try {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n }\n } catch {\n /* nothing left to try */\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/fetchWithRetry.ts","../src/shared/sonorFetch.ts"],"names":["credentialBreakerId","isTokenMode","getFreshCredential","SITE_KIT_VERSION","isSiteToken","refreshSiteToken","currentCredential"],"mappings":"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","file":"chunk-4D2G5ZNP.js","sourcesContent":["/**\n * @sonordev/site-kit shared - Resilient fetch\n *\n * Single source of truth for client-side resilient fetch (forms, SitemapSync).\n * Adds a per-attempt timeout and retries transient failures (network errors,\n * request timeouts, HTTP 429, and 5xx) with exponential backoff.\n *\n * A form submission must survive a brief API blip — a deploy, a cold start, a\n * dropped packet. A single un-retried fetch turning into \"Something went wrong\"\n * is how leads get lost. The explicit abort also matters on its own: a request\n * left to hang until the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices; an aborted fetch\n * logs nothing.\n */\n\nexport interface FetchRetryOptions {\n /** Number of retries AFTER the first attempt (default 2 → up to 3 tries total). */\n retries?: number\n /** Per-attempt timeout in milliseconds (default 15000). */\n timeoutMs?: number\n}\n\nfunction backoffDelay(attempt: number, response?: Response): number {\n // Honor a Retry-After header on 429 responses when present.\n if (response?.status === 429) {\n const header = response.headers.get('retry-after')\n const seconds = header ? parseInt(header, 10) : NaN\n if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1000, 10000)\n }\n // Exponential backoff: 500ms, 1s, 2s, ... capped at 8s.\n return Math.min(8000, 500 * 2 ** attempt)\n}\n\n/**\n * fetch() with a timeout and automatic retries for transient failures.\n *\n * Non-transient responses (2xx, 3xx, and 4xx other than 429) are returned to\n * the caller as-is — only 429/5xx and thrown network/timeout errors are retried.\n * If every attempt fails with a thrown error, the last error is re-thrown.\n */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n options: FetchRetryOptions = {},\n): Promise<Response> {\n const retries = options.retries ?? 2\n const timeoutMs = options.timeoutMs ?? 15000\n let lastError: unknown\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(url, { ...init, signal: controller.signal })\n clearTimeout(timer)\n\n if ((response.status === 429 || response.status >= 500) && attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)))\n continue\n }\n return response\n } catch (error) {\n clearTimeout(timer)\n lastError = error\n // Network failure or timeout (AbortController) — retry while attempts remain.\n if (attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)))\n continue\n }\n }\n }\n\n throw lastError instanceof Error\n ? lastError\n : new Error('Network request failed after multiple attempts. Please try again.')\n}\n","/**\n * @sonordev/site-kit shared - Canonical client transport\n *\n * Single source of truth for how site-kit talks to Sonor from the browser.\n * Every client-side module (analytics, forms, commerce, engage, maps, images,\n * SitemapSync) routes through here instead of bare fetch(), which gives the\n * whole kit, in one place:\n *\n * - per-attempt timeout + retry with backoff (via fetchWithRetry) — a hung\n * request left to the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices\n * - the x-sitekit-version header on every request, so the platform can see\n * which kit version each site actually runs from live traffic\n * - CREDENTIAL LIFECYCLE: sites on 3.2+ ship a short-lived minted token instead\n * of the raw key. This transport resolves the freshest token (refreshing a\n * stale seed transparently), and on a 401/403 for a token it refreshes once\n * and retries before giving up. A raw key (2.x kit, or the mint-failed\n * fallback) is a pure passthrough — legacy behavior is unchanged.\n * - an auth circuit breaker: after a 401/403 the credential is paused for a few\n * minutes and ONE friendly console.info is emitted — a site on a stale or\n * canceled key must not spam the API or the visitor's console. Keyed by the\n * project (from the token) so it survives token rotation.\n * - sonorBeacon for unload-time telemetry that keeps the credential in headers;\n * navigator.sendBeacon can't set headers and forced the key into the URL,\n * where server and CDN access logs capture it\n *\n * Non-idempotent endpoints (checkout, payment intents, uploads) must pass\n * { retries: 0 } — they still get the timeout, but never an automatic retry.\n */\nimport { fetchWithRetry, type FetchRetryOptions } from './fetchWithRetry'\nimport { SITE_KIT_VERSION } from './version'\nimport {\n credentialBreakerId,\n currentCredential,\n getFreshCredential,\n isSiteToken,\n isTokenMode,\n refreshSiteToken,\n} from './siteToken'\n\nexport interface SonorFetchOptions extends FetchRetryOptions {\n /** Project credential — the minted token (3.2+) or raw key. Sent as x-api-key. */\n apiKey?: string\n}\n\n/** How long a credential stays paused after an auth failure. */\nconst AUTH_BREAK_MS = 5 * 60_000\n\n// breaker id → epoch ms until which requests are paused. Shared on window for\n// the same reason as the token state (see siteToken.ts): duplicated module\n// copies must trip and observe ONE breaker, not one each. Never applies on the\n// server, so a per-module Map there is fine.\nconst BREAKER_STATE_KEY = '__SONOR_AUTH_BREAKER__'\nconst pausedKeys: Map<string, number> = (() => {\n if (typeof window === 'undefined') return new Map()\n const w = window as unknown as Record<string, Map<string, number> | undefined>\n return (w[BREAKER_STATE_KEY] ??= new Map())\n})()\nlet warnedThisPage = false\n\n/** True when a previous 401/403 has paused this credential (browser only). */\nexport function isAuthPaused(credential?: string): boolean {\n if (typeof window === 'undefined') return false\n const id = credentialBreakerId(credential)\n const until = pausedKeys.get(id)\n if (!until) return false\n if (Date.now() >= until) {\n pausedKeys.delete(id)\n return false\n }\n return true\n}\n\nfunction tripBreaker(credential?: string): void {\n pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS)\n if (!warnedThisPage) {\n warnedThisPage = true\n // info, not error: a visitor's console must stay clean (Lighthouse\n // errors-in-console), and copy never names env vars or key formats.\n console.info(\n \"[Sonor] This site couldn't authenticate with Sonor, so data features are paused for a few minutes. If this keeps happening, the site is likely running an outdated API key.\",\n )\n }\n}\n\nexport async function sonorFetch(\n url: string,\n init: RequestInit = {},\n options: SonorFetchOptions = {},\n): Promise<Response> {\n // Resolve the credential to send. In token mode this may transparently refresh\n // a stale seed before the first request; in raw-key mode it's a passthrough.\n let credential = options.apiKey\n if (credential !== undefined || isTokenMode()) {\n credential = await getFreshCredential(options.apiKey)\n }\n\n const headers = new Headers(init.headers || {})\n headers.set('x-sitekit-version', SITE_KIT_VERSION)\n if (credential) {\n if (isTokenMode()) {\n // Token mode: `credential` is the freshest token (refreshed just above if\n // the seed was stale). It MUST override any x-api-key a caller hand-set — a\n // stale build-time seed passed as a header would otherwise pin the request\n // and defeat refresh entirely. Also drop a caller-set Authorization:\n // extractApiKey() server-side prefers `Authorization: Bearer` over\n // x-api-key, so a stale Bearer seed would win even once we set a fresh\n // x-api-key. This is the single place the client credential is chosen — do\n // not re-attach it as a header at call sites.\n headers.set('x-api-key', credential)\n headers.delete('authorization')\n } else if (!headers.has('x-api-key')) {\n // Raw-key mode: legacy passthrough — respect a caller-supplied header.\n headers.set('x-api-key', credential)\n }\n }\n\n if (isAuthPaused(credential)) {\n // Synthetic response keeps every caller's !response.ok path working\n // without a network round trip while the breaker is open.\n return new Response(null, { status: 401, statusText: 'sonor-auth-paused' })\n }\n\n let response = await fetchWithRetry(url, { ...init, headers }, options)\n\n // A token can rotate or expire mid-session. On the first auth failure, try ONE\n // refresh + retry before tripping the breaker — only for token credentials\n // (raw keys have nothing to refresh).\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n isSiteToken(credential)\n ) {\n const refreshed = await refreshSiteToken()\n if (refreshed && refreshed !== credential) {\n const retryHeaders = new Headers(init.headers || {})\n retryHeaders.set('x-sitekit-version', SITE_KIT_VERSION)\n retryHeaders.set('x-api-key', refreshed)\n // Same reason as the initial request: a caller-set stale Bearer would win\n // over our fresh x-api-key server-side, so strip it on the retry too.\n retryHeaders.delete('authorization')\n credential = refreshed\n response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options)\n }\n }\n\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n credential\n ) {\n tripBreaker(credential)\n }\n\n return response\n}\n\n/**\n * Fire-and-forget telemetry that survives page unload.\n *\n * Uses fetch({ keepalive: true }) so auth travels in headers. Falls back to\n * navigator.sendBeacon (no auth headers possible — the server may reject it,\n * but we never put the key in the URL) only when keepalive fetch is missing.\n *\n * Cannot await a refresh at unload time, so it sends the freshest token already\n * held (currentCredential) rather than the possibly-stale seed the caller passed.\n */\nexport function sonorBeacon(url: string, payload: unknown, apiKey?: string): void {\n const credential = currentCredential(apiKey)\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload)\n try {\n void fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n ...(credential ? { 'x-api-key': credential } : {}),\n },\n body,\n }).catch(() => {\n /* page is closing — best effort */\n })\n } catch {\n try {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n }\n } catch {\n /* nothing left to try */\n }\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isTokenMode, getFreshCredential, isSiteToken, refreshSiteToken, currentCredential, credentialBreakerId } from './chunk-
|
|
2
|
-
import { SITE_KIT_VERSION } from './chunk-
|
|
1
|
+
import { isTokenMode, getFreshCredential, isSiteToken, refreshSiteToken, currentCredential, credentialBreakerId } from './chunk-FDWDXMJL.mjs';
|
|
2
|
+
import { SITE_KIT_VERSION } from './chunk-JPTJGLKS.mjs';
|
|
3
3
|
|
|
4
4
|
// src/shared/fetchWithRetry.ts
|
|
5
5
|
function backoffDelay(attempt, response) {
|
|
@@ -127,5 +127,5 @@ function sonorBeacon(url, payload, apiKey) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
export { sonorBeacon, sonorFetch };
|
|
130
|
-
//# sourceMappingURL=chunk-
|
|
131
|
-
//# sourceMappingURL=chunk-
|
|
130
|
+
//# sourceMappingURL=chunk-4UHO6DPJ.mjs.map
|
|
131
|
+
//# sourceMappingURL=chunk-4UHO6DPJ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/fetchWithRetry.ts","../src/shared/sonorFetch.ts"],"names":[],"mappings":"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","file":"chunk-45VPULF2.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared - Resilient fetch\n *\n * Single source of truth for client-side resilient fetch (forms, SitemapSync).\n * Adds a per-attempt timeout and retries transient failures (network errors,\n * request timeouts, HTTP 429, and 5xx) with exponential backoff.\n *\n * A form submission must survive a brief API blip — a deploy, a cold start, a\n * dropped packet. A single un-retried fetch turning into \"Something went wrong\"\n * is how leads get lost. The explicit abort also matters on its own: a request\n * left to hang until the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices; an aborted fetch\n * logs nothing.\n */\n\nexport interface FetchRetryOptions {\n /** Number of retries AFTER the first attempt (default 2 → up to 3 tries total). */\n retries?: number\n /** Per-attempt timeout in milliseconds (default 15000). */\n timeoutMs?: number\n}\n\nfunction backoffDelay(attempt: number, response?: Response): number {\n // Honor a Retry-After header on 429 responses when present.\n if (response?.status === 429) {\n const header = response.headers.get('retry-after')\n const seconds = header ? parseInt(header, 10) : NaN\n if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1000, 10000)\n }\n // Exponential backoff: 500ms, 1s, 2s, ... capped at 8s.\n return Math.min(8000, 500 * 2 ** attempt)\n}\n\n/**\n * fetch() with a timeout and automatic retries for transient failures.\n *\n * Non-transient responses (2xx, 3xx, and 4xx other than 429) are returned to\n * the caller as-is — only 429/5xx and thrown network/timeout errors are retried.\n * If every attempt fails with a thrown error, the last error is re-thrown.\n */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n options: FetchRetryOptions = {},\n): Promise<Response> {\n const retries = options.retries ?? 2\n const timeoutMs = options.timeoutMs ?? 15000\n let lastError: unknown\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(url, { ...init, signal: controller.signal })\n clearTimeout(timer)\n\n if ((response.status === 429 || response.status >= 500) && attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)))\n continue\n }\n return response\n } catch (error) {\n clearTimeout(timer)\n lastError = error\n // Network failure or timeout (AbortController) — retry while attempts remain.\n if (attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)))\n continue\n }\n }\n }\n\n throw lastError instanceof Error\n ? lastError\n : new Error('Network request failed after multiple attempts. Please try again.')\n}\n","/**\n * @sonordev/site-kit shared - Canonical client transport\n *\n * Single source of truth for how site-kit talks to Sonor from the browser.\n * Every client-side module (analytics, forms, commerce, engage, maps, images,\n * SitemapSync) routes through here instead of bare fetch(), which gives the\n * whole kit, in one place:\n *\n * - per-attempt timeout + retry with backoff (via fetchWithRetry) — a hung\n * request left to the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices\n * - the x-sitekit-version header on every request, so the platform can see\n * which kit version each site actually runs from live traffic\n * - CREDENTIAL LIFECYCLE: sites on 3.2+ ship a short-lived minted token instead\n * of the raw key. This transport resolves the freshest token (refreshing a\n * stale seed transparently), and on a 401/403 for a token it refreshes once\n * and retries before giving up. A raw key (2.x kit, or the mint-failed\n * fallback) is a pure passthrough — legacy behavior is unchanged.\n * - an auth circuit breaker: after a 401/403 the credential is paused for a few\n * minutes and ONE friendly console.info is emitted — a site on a stale or\n * canceled key must not spam the API or the visitor's console. Keyed by the\n * project (from the token) so it survives token rotation.\n * - sonorBeacon for unload-time telemetry that keeps the credential in headers;\n * navigator.sendBeacon can't set headers and forced the key into the URL,\n * where server and CDN access logs capture it\n *\n * Non-idempotent endpoints (checkout, payment intents, uploads) must pass\n * { retries: 0 } — they still get the timeout, but never an automatic retry.\n */\nimport { fetchWithRetry, type FetchRetryOptions } from './fetchWithRetry'\nimport { SITE_KIT_VERSION } from './version'\nimport {\n credentialBreakerId,\n currentCredential,\n getFreshCredential,\n isSiteToken,\n isTokenMode,\n refreshSiteToken,\n} from './siteToken'\n\nexport interface SonorFetchOptions extends FetchRetryOptions {\n /** Project credential — the minted token (3.2+) or raw key. Sent as x-api-key. */\n apiKey?: string\n}\n\n/** How long a credential stays paused after an auth failure. */\nconst AUTH_BREAK_MS = 5 * 60_000\n\n// breaker id → epoch ms until which requests are paused. Shared on window for\n// the same reason as the token state (see siteToken.ts): duplicated module\n// copies must trip and observe ONE breaker, not one each. Never applies on the\n// server, so a per-module Map there is fine.\nconst BREAKER_STATE_KEY = '__SONOR_AUTH_BREAKER__'\nconst pausedKeys: Map<string, number> = (() => {\n if (typeof window === 'undefined') return new Map()\n const w = window as unknown as Record<string, Map<string, number> | undefined>\n return (w[BREAKER_STATE_KEY] ??= new Map())\n})()\nlet warnedThisPage = false\n\n/** True when a previous 401/403 has paused this credential (browser only). */\nexport function isAuthPaused(credential?: string): boolean {\n if (typeof window === 'undefined') return false\n const id = credentialBreakerId(credential)\n const until = pausedKeys.get(id)\n if (!until) return false\n if (Date.now() >= until) {\n pausedKeys.delete(id)\n return false\n }\n return true\n}\n\nfunction tripBreaker(credential?: string): void {\n pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS)\n if (!warnedThisPage) {\n warnedThisPage = true\n // info, not error: a visitor's console must stay clean (Lighthouse\n // errors-in-console), and copy never names env vars or key formats.\n console.info(\n \"[Sonor] This site couldn't authenticate with Sonor, so data features are paused for a few minutes. If this keeps happening, the site is likely running an outdated API key.\",\n )\n }\n}\n\nexport async function sonorFetch(\n url: string,\n init: RequestInit = {},\n options: SonorFetchOptions = {},\n): Promise<Response> {\n // Resolve the credential to send. In token mode this may transparently refresh\n // a stale seed before the first request; in raw-key mode it's a passthrough.\n let credential = options.apiKey\n if (credential !== undefined || isTokenMode()) {\n credential = await getFreshCredential(options.apiKey)\n }\n\n const headers = new Headers(init.headers || {})\n headers.set('x-sitekit-version', SITE_KIT_VERSION)\n if (credential) {\n if (isTokenMode()) {\n // Token mode: `credential` is the freshest token (refreshed just above if\n // the seed was stale). It MUST override any x-api-key a caller hand-set — a\n // stale build-time seed passed as a header would otherwise pin the request\n // and defeat refresh entirely. Also drop a caller-set Authorization:\n // extractApiKey() server-side prefers `Authorization: Bearer` over\n // x-api-key, so a stale Bearer seed would win even once we set a fresh\n // x-api-key. This is the single place the client credential is chosen — do\n // not re-attach it as a header at call sites.\n headers.set('x-api-key', credential)\n headers.delete('authorization')\n } else if (!headers.has('x-api-key')) {\n // Raw-key mode: legacy passthrough — respect a caller-supplied header.\n headers.set('x-api-key', credential)\n }\n }\n\n if (isAuthPaused(credential)) {\n // Synthetic response keeps every caller's !response.ok path working\n // without a network round trip while the breaker is open.\n return new Response(null, { status: 401, statusText: 'sonor-auth-paused' })\n }\n\n let response = await fetchWithRetry(url, { ...init, headers }, options)\n\n // A token can rotate or expire mid-session. On the first auth failure, try ONE\n // refresh + retry before tripping the breaker — only for token credentials\n // (raw keys have nothing to refresh).\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n isSiteToken(credential)\n ) {\n const refreshed = await refreshSiteToken()\n if (refreshed && refreshed !== credential) {\n const retryHeaders = new Headers(init.headers || {})\n retryHeaders.set('x-sitekit-version', SITE_KIT_VERSION)\n retryHeaders.set('x-api-key', refreshed)\n // Same reason as the initial request: a caller-set stale Bearer would win\n // over our fresh x-api-key server-side, so strip it on the retry too.\n retryHeaders.delete('authorization')\n credential = refreshed\n response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options)\n }\n }\n\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n credential\n ) {\n tripBreaker(credential)\n }\n\n return response\n}\n\n/**\n * Fire-and-forget telemetry that survives page unload.\n *\n * Uses fetch({ keepalive: true }) so auth travels in headers. Falls back to\n * navigator.sendBeacon (no auth headers possible — the server may reject it,\n * but we never put the key in the URL) only when keepalive fetch is missing.\n *\n * Cannot await a refresh at unload time, so it sends the freshest token already\n * held (currentCredential) rather than the possibly-stale seed the caller passed.\n */\nexport function sonorBeacon(url: string, payload: unknown, apiKey?: string): void {\n const credential = currentCredential(apiKey)\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload)\n try {\n void fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n ...(credential ? { 'x-api-key': credential } : {}),\n },\n body,\n }).catch(() => {\n /* page is closing — best effort */\n })\n } catch {\n try {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n }\n } catch {\n /* nothing left to try */\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/fetchWithRetry.ts","../src/shared/sonorFetch.ts"],"names":[],"mappings":";;;;AAsBA,SAAS,YAAA,CAAa,SAAiB,QAAA,EAA6B;AAElE,EAAA,IAAI,QAAA,EAAU,WAAW,GAAA,EAAK;AAC5B,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAA;AACjD,IAAA,MAAM,OAAA,GAAU,MAAA,GAAS,QAAA,CAAS,MAAA,EAAQ,EAAE,CAAA,GAAI,GAAA;AAChD,IAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,IAAK,OAAA,GAAU,CAAA,EAAG,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,GAAU,GAAA,EAAM,GAAK,CAAA;AAAA,EAClF;AAEA,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAA,EAAM,GAAA,GAAM,KAAK,OAAO,CAAA;AAC1C;AASA,eAAsB,cAAA,CACpB,GAAA,EACA,IAAA,EACA,OAAA,GAA6B,EAAC,EACX;AACnB,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,CAAA;AACnC,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,IAAA;AACvC,EAAA,IAAI,SAAA;AAEJ,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,OAAA,EAAS,OAAA,EAAA,EAAW;AACnD,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,SAAS,CAAA;AAC5D,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,CAAA;AACxE,MAAA,YAAA,CAAa,KAAK,CAAA;AAElB,MAAA,IAAA,CAAK,SAAS,MAAA,KAAW,GAAA,IAAO,SAAS,MAAA,IAAU,GAAA,KAAQ,UAAU,OAAA,EAAS;AAC5E,QAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY,UAAA,CAAW,SAAS,YAAA,CAAa,OAAA,EAAS,QAAQ,CAAC,CAAC,CAAA;AACnF,QAAA;AAAA,MACF;AACA,MAAA,OAAO,QAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,SAAA,GAAY,KAAA;AAEZ,MAAA,IAAI,UAAU,OAAA,EAAS;AACrB,QAAA,MAAM,IAAI,QAAQ,CAAC,OAAA,KAAY,WAAW,OAAA,EAAS,YAAA,CAAa,OAAO,CAAC,CAAC,CAAA;AACzE,QAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,YAAqB,KAAA,GACvB,SAAA,GACA,IAAI,MAAM,mEAAmE,CAAA;AACnF;;;AC7BA,IAAM,gBAAgB,CAAA,GAAI,GAAA;AAM1B,IAAM,iBAAA,GAAoB,wBAAA;AAC1B,IAAM,cAAmC,MAAM;AAC7C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,2BAAW,GAAA,EAAI;AAClD,EAAA,MAAM,CAAA,GAAI,MAAA;AACV,EAAA,OAAQ,CAAA,CAAA,iBAAA,CAAA,KAAA,CAAA,CAAA,iBAAA,CAAA,uBAA6B,GAAA,EAAI,CAAA;AAC3C,CAAA,GAAG;AACH,IAAI,cAAA,GAAiB,KAAA;AAGd,SAAS,aAAa,UAAA,EAA8B;AACzD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,MAAM,EAAA,GAAK,oBAAoB,UAAU,CAAA;AACzC,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,EAAE,CAAA;AAC/B,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,IAAI,IAAA,CAAK,GAAA,EAAI,IAAK,KAAA,EAAO;AACvB,IAAA,UAAA,CAAW,OAAO,EAAE,CAAA;AACpB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,YAAY,UAAA,EAA2B;AAC9C,EAAA,UAAA,CAAW,IAAI,mBAAA,CAAoB,UAAU,GAAG,IAAA,CAAK,GAAA,KAAQ,aAAa,CAAA;AAC1E,EAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,IAAA,cAAA,GAAiB,IAAA;AAGjB,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN;AAAA,KACF;AAAA,EACF;AACF;AAEA,eAAsB,WACpB,GAAA,EACA,IAAA,GAAoB,EAAC,EACrB,OAAA,GAA6B,EAAC,EACX;AAGnB,EAAA,IAAI,aAAa,OAAA,CAAQ,MAAA;AACzB,EAAA,IAAI,UAAA,KAAe,MAAA,IAAa,WAAA,EAAY,EAAG;AAC7C,IAAA,UAAA,GAAa,MAAM,kBAAA,CAAmB,OAAA,CAAQ,MAAM,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,UAAU,IAAI,OAAA,CAAQ,IAAA,CAAK,OAAA,IAAW,EAAE,CAAA;AAC9C,EAAA,OAAA,CAAQ,GAAA,CAAI,qBAAqB,gBAAgB,CAAA;AACjD,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,IAAI,aAAY,EAAG;AASjB,MAAA,OAAA,CAAQ,GAAA,CAAI,aAAa,UAAU,CAAA;AACnC,MAAA,OAAA,CAAQ,OAAO,eAAe,CAAA;AAAA,IAChC,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,EAAG;AAEpC,MAAA,OAAA,CAAQ,GAAA,CAAI,aAAa,UAAU,CAAA;AAAA,IACrC;AAAA,EACF;AAEA,EAAA,IAAI,YAAA,CAAa,UAAU,CAAA,EAAG;AAG5B,IAAA,OAAO,IAAI,SAAS,IAAA,EAAM,EAAE,QAAQ,GAAA,EAAK,UAAA,EAAY,qBAAqB,CAAA;AAAA,EAC5E;AAEA,EAAA,IAAI,QAAA,GAAW,MAAM,cAAA,CAAe,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,OAAA,EAAQ,EAAG,OAAO,CAAA;AAKtE,EAAA,IAAA,CACG,QAAA,CAAS,MAAA,KAAW,GAAA,IAAO,QAAA,CAAS,MAAA,KAAW,GAAA,KAChD,OAAO,MAAA,KAAW,WAAA,IAClB,WAAA,CAAY,UAAU,CAAA,EACtB;AACA,IAAA,MAAM,SAAA,GAAY,MAAM,gBAAA,EAAiB;AACzC,IAAA,IAAI,SAAA,IAAa,cAAc,UAAA,EAAY;AACzC,MAAA,MAAM,eAAe,IAAI,OAAA,CAAQ,IAAA,CAAK,OAAA,IAAW,EAAE,CAAA;AACnD,MAAA,YAAA,CAAa,GAAA,CAAI,qBAAqB,gBAAgB,CAAA;AACtD,MAAA,YAAA,CAAa,GAAA,CAAI,aAAa,SAAS,CAAA;AAGvC,MAAA,YAAA,CAAa,OAAO,eAAe,CAAA;AACnC,MAAA,UAAA,GAAa,SAAA;AACb,MAAA,QAAA,GAAW,MAAM,eAAe,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,OAAA,EAAS,YAAA,EAAa,EAAG,OAAO,CAAA;AAAA,IAClF;AAAA,EACF;AAEA,EAAA,IAAA,CACG,QAAA,CAAS,WAAW,GAAA,IAAO,QAAA,CAAS,WAAW,GAAA,KAChD,OAAO,MAAA,KAAW,WAAA,IAClB,UAAA,EACA;AACA,IAAA,WAAA,CAAY,UAAU,CAAA;AAAA,EACxB;AAEA,EAAA,OAAO,QAAA;AACT;AAYO,SAAS,WAAA,CAAY,GAAA,EAAa,OAAA,EAAkB,MAAA,EAAuB;AAChF,EAAA,MAAM,UAAA,GAAa,kBAAkB,MAAM,CAAA;AAC3C,EAAA,MAAM,OAAO,OAAO,OAAA,KAAY,WAAW,OAAA,GAAU,IAAA,CAAK,UAAU,OAAO,CAAA;AAC3E,EAAA,IAAI;AACF,IAAA,KAAK,MAAM,GAAA,EAAK;AAAA,MACd,MAAA,EAAQ,MAAA;AAAA,MACR,SAAA,EAAW,IAAA;AAAA,MACX,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,mBAAA,EAAqB,gBAAA;AAAA,QACrB,GAAI,UAAA,GAAa,EAAE,WAAA,EAAa,UAAA,KAAe;AAAC,OAClD;AAAA,MACA;AAAA,KACD,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,IAEf,CAAC,CAAA;AAAA,EACH,CAAA,CAAA,MAAQ;AACN,IAAA,IAAI;AACF,MAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,SAAA,CAAU,UAAA,EAAY;AAC5D,QAAA,SAAA,CAAU,UAAA,CAAW,GAAA,EAAK,IAAI,IAAA,CAAK,CAAC,IAAI,CAAA,EAAG,EAAE,IAAA,EAAM,kBAAA,EAAoB,CAAC,CAAA;AAAA,MAC1E;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF","file":"chunk-4UHO6DPJ.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared - Resilient fetch\n *\n * Single source of truth for client-side resilient fetch (forms, SitemapSync).\n * Adds a per-attempt timeout and retries transient failures (network errors,\n * request timeouts, HTTP 429, and 5xx) with exponential backoff.\n *\n * A form submission must survive a brief API blip — a deploy, a cold start, a\n * dropped packet. A single un-retried fetch turning into \"Something went wrong\"\n * is how leads get lost. The explicit abort also matters on its own: a request\n * left to hang until the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices; an aborted fetch\n * logs nothing.\n */\n\nexport interface FetchRetryOptions {\n /** Number of retries AFTER the first attempt (default 2 → up to 3 tries total). */\n retries?: number\n /** Per-attempt timeout in milliseconds (default 15000). */\n timeoutMs?: number\n}\n\nfunction backoffDelay(attempt: number, response?: Response): number {\n // Honor a Retry-After header on 429 responses when present.\n if (response?.status === 429) {\n const header = response.headers.get('retry-after')\n const seconds = header ? parseInt(header, 10) : NaN\n if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1000, 10000)\n }\n // Exponential backoff: 500ms, 1s, 2s, ... capped at 8s.\n return Math.min(8000, 500 * 2 ** attempt)\n}\n\n/**\n * fetch() with a timeout and automatic retries for transient failures.\n *\n * Non-transient responses (2xx, 3xx, and 4xx other than 429) are returned to\n * the caller as-is — only 429/5xx and thrown network/timeout errors are retried.\n * If every attempt fails with a thrown error, the last error is re-thrown.\n */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n options: FetchRetryOptions = {},\n): Promise<Response> {\n const retries = options.retries ?? 2\n const timeoutMs = options.timeoutMs ?? 15000\n let lastError: unknown\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(url, { ...init, signal: controller.signal })\n clearTimeout(timer)\n\n if ((response.status === 429 || response.status >= 500) && attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)))\n continue\n }\n return response\n } catch (error) {\n clearTimeout(timer)\n lastError = error\n // Network failure or timeout (AbortController) — retry while attempts remain.\n if (attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)))\n continue\n }\n }\n }\n\n throw lastError instanceof Error\n ? lastError\n : new Error('Network request failed after multiple attempts. Please try again.')\n}\n","/**\n * @sonordev/site-kit shared - Canonical client transport\n *\n * Single source of truth for how site-kit talks to Sonor from the browser.\n * Every client-side module (analytics, forms, commerce, engage, maps, images,\n * SitemapSync) routes through here instead of bare fetch(), which gives the\n * whole kit, in one place:\n *\n * - per-attempt timeout + retry with backoff (via fetchWithRetry) — a hung\n * request left to the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices\n * - the x-sitekit-version header on every request, so the platform can see\n * which kit version each site actually runs from live traffic\n * - CREDENTIAL LIFECYCLE: sites on 3.2+ ship a short-lived minted token instead\n * of the raw key. This transport resolves the freshest token (refreshing a\n * stale seed transparently), and on a 401/403 for a token it refreshes once\n * and retries before giving up. A raw key (2.x kit, or the mint-failed\n * fallback) is a pure passthrough — legacy behavior is unchanged.\n * - an auth circuit breaker: after a 401/403 the credential is paused for a few\n * minutes and ONE friendly console.info is emitted — a site on a stale or\n * canceled key must not spam the API or the visitor's console. Keyed by the\n * project (from the token) so it survives token rotation.\n * - sonorBeacon for unload-time telemetry that keeps the credential in headers;\n * navigator.sendBeacon can't set headers and forced the key into the URL,\n * where server and CDN access logs capture it\n *\n * Non-idempotent endpoints (checkout, payment intents, uploads) must pass\n * { retries: 0 } — they still get the timeout, but never an automatic retry.\n */\nimport { fetchWithRetry, type FetchRetryOptions } from './fetchWithRetry'\nimport { SITE_KIT_VERSION } from './version'\nimport {\n credentialBreakerId,\n currentCredential,\n getFreshCredential,\n isSiteToken,\n isTokenMode,\n refreshSiteToken,\n} from './siteToken'\n\nexport interface SonorFetchOptions extends FetchRetryOptions {\n /** Project credential — the minted token (3.2+) or raw key. Sent as x-api-key. */\n apiKey?: string\n}\n\n/** How long a credential stays paused after an auth failure. */\nconst AUTH_BREAK_MS = 5 * 60_000\n\n// breaker id → epoch ms until which requests are paused. Shared on window for\n// the same reason as the token state (see siteToken.ts): duplicated module\n// copies must trip and observe ONE breaker, not one each. Never applies on the\n// server, so a per-module Map there is fine.\nconst BREAKER_STATE_KEY = '__SONOR_AUTH_BREAKER__'\nconst pausedKeys: Map<string, number> = (() => {\n if (typeof window === 'undefined') return new Map()\n const w = window as unknown as Record<string, Map<string, number> | undefined>\n return (w[BREAKER_STATE_KEY] ??= new Map())\n})()\nlet warnedThisPage = false\n\n/** True when a previous 401/403 has paused this credential (browser only). */\nexport function isAuthPaused(credential?: string): boolean {\n if (typeof window === 'undefined') return false\n const id = credentialBreakerId(credential)\n const until = pausedKeys.get(id)\n if (!until) return false\n if (Date.now() >= until) {\n pausedKeys.delete(id)\n return false\n }\n return true\n}\n\nfunction tripBreaker(credential?: string): void {\n pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS)\n if (!warnedThisPage) {\n warnedThisPage = true\n // info, not error: a visitor's console must stay clean (Lighthouse\n // errors-in-console), and copy never names env vars or key formats.\n console.info(\n \"[Sonor] This site couldn't authenticate with Sonor, so data features are paused for a few minutes. If this keeps happening, the site is likely running an outdated API key.\",\n )\n }\n}\n\nexport async function sonorFetch(\n url: string,\n init: RequestInit = {},\n options: SonorFetchOptions = {},\n): Promise<Response> {\n // Resolve the credential to send. In token mode this may transparently refresh\n // a stale seed before the first request; in raw-key mode it's a passthrough.\n let credential = options.apiKey\n if (credential !== undefined || isTokenMode()) {\n credential = await getFreshCredential(options.apiKey)\n }\n\n const headers = new Headers(init.headers || {})\n headers.set('x-sitekit-version', SITE_KIT_VERSION)\n if (credential) {\n if (isTokenMode()) {\n // Token mode: `credential` is the freshest token (refreshed just above if\n // the seed was stale). It MUST override any x-api-key a caller hand-set — a\n // stale build-time seed passed as a header would otherwise pin the request\n // and defeat refresh entirely. Also drop a caller-set Authorization:\n // extractApiKey() server-side prefers `Authorization: Bearer` over\n // x-api-key, so a stale Bearer seed would win even once we set a fresh\n // x-api-key. This is the single place the client credential is chosen — do\n // not re-attach it as a header at call sites.\n headers.set('x-api-key', credential)\n headers.delete('authorization')\n } else if (!headers.has('x-api-key')) {\n // Raw-key mode: legacy passthrough — respect a caller-supplied header.\n headers.set('x-api-key', credential)\n }\n }\n\n if (isAuthPaused(credential)) {\n // Synthetic response keeps every caller's !response.ok path working\n // without a network round trip while the breaker is open.\n return new Response(null, { status: 401, statusText: 'sonor-auth-paused' })\n }\n\n let response = await fetchWithRetry(url, { ...init, headers }, options)\n\n // A token can rotate or expire mid-session. On the first auth failure, try ONE\n // refresh + retry before tripping the breaker — only for token credentials\n // (raw keys have nothing to refresh).\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n isSiteToken(credential)\n ) {\n const refreshed = await refreshSiteToken()\n if (refreshed && refreshed !== credential) {\n const retryHeaders = new Headers(init.headers || {})\n retryHeaders.set('x-sitekit-version', SITE_KIT_VERSION)\n retryHeaders.set('x-api-key', refreshed)\n // Same reason as the initial request: a caller-set stale Bearer would win\n // over our fresh x-api-key server-side, so strip it on the retry too.\n retryHeaders.delete('authorization')\n credential = refreshed\n response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options)\n }\n }\n\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n credential\n ) {\n tripBreaker(credential)\n }\n\n return response\n}\n\n/**\n * Fire-and-forget telemetry that survives page unload.\n *\n * Uses fetch({ keepalive: true }) so auth travels in headers. Falls back to\n * navigator.sendBeacon (no auth headers possible — the server may reject it,\n * but we never put the key in the URL) only when keepalive fetch is missing.\n *\n * Cannot await a refresh at unload time, so it sends the freshest token already\n * held (currentCredential) rather than the possibly-stale seed the caller passed.\n */\nexport function sonorBeacon(url: string, payload: unknown, apiKey?: string): void {\n const credential = currentCredential(apiKey)\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload)\n try {\n void fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n ...(credential ? { 'x-api-key': credential } : {}),\n },\n body,\n }).catch(() => {\n /* page is closing — best effort */\n })\n } catch {\n try {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n }\n } catch {\n /* nothing left to try */\n }\n }\n}\n"]}
|
|
@@ -4,7 +4,7 @@ import React, { lazy, useRef, useEffect, useState, Suspense } from 'react';
|
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
var InteractiveForm = lazy(
|
|
7
|
-
() => import('./ManagedForm-
|
|
7
|
+
() => import('./ManagedForm-VU2TKV6J.mjs').then((m) => ({ default: m.ManagedForm }))
|
|
8
8
|
);
|
|
9
9
|
function FormEnhancer({
|
|
10
10
|
formId,
|
|
@@ -100,5 +100,5 @@ function EnterStyles() {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
export { FormEnhancer };
|
|
103
|
-
//# sourceMappingURL=chunk-
|
|
104
|
-
//# sourceMappingURL=chunk-
|
|
103
|
+
//# sourceMappingURL=chunk-5JNLZRLX.mjs.map
|
|
104
|
+
//# sourceMappingURL=chunk-5JNLZRLX.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/forms/FormEnhancer.tsx"],"names":[],"mappings":"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","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/forms/FormEnhancer.tsx"],"names":[],"mappings":"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","file":"chunk-5JNLZRLX.mjs","sourcesContent":["'use client'\n\n/**\n * FormEnhancer — the only client code a managed form needs up front.\n *\n * Renders the server-rendered shell (<StaticForm>, passed as `children`) until\n * an idle/first-interaction gate fires, then lazy-imports the interactive form\n * and swaps it in. Everything expensive — validation, conditional logic,\n * spotlight/stage, celebration, momentum — lives behind that dynamic import,\n * so a page ships this boundary and nothing else in its initial chunk set.\n *\n * The shell is a REAL form the whole time (native action + signed token), so\n * the progression is:\n *\n * no JS at all -> shell, submits natively. Works.\n * JS, pre-idle -> shell, submits natively. Works.\n * JS, post-idle -> enhanced form, submits over JSON. Works, with delight.\n * chunk 404s -> shell stays. Works. (Never left blank.)\n *\n * That last line is the invariant to protect: the shell is only removed once\n * the enhancement has actually rendered. Do not \"hide the shell then load\" —\n * a CDN hiccup would leave a visitor staring at an empty box where the form\n * used to be.\n *\n * ── Entrance animation ──────────────────────────────────────────────────\n *\n * Above-the-fold forms upgrade while the visitor is looking at them, so the\n * swap must not pop. The wrapper carries `data-sk-form-mount=\"shell\"` and\n * flips to `\"enhanced\"`, and the enhanced form gets `data-sk-enter` for one\n * frame, so a site can style the transition entirely in CSS:\n *\n * [data-sk-form-mount] { transition: opacity .35s ease }\n * [data-sk-form-mount=\"shell\"] { opacity: .6 }\n *\n * Defaults ship as CSS variables rather than hard-coded values:\n *\n * --sk-form-enter-duration (default 320ms)\n * --sk-form-enter-easing (default cubic-bezier(.22,1,.36,1))\n * --sk-form-enter-distance (default 6px)\n *\n * `enter=\"none\"` opts out entirely; `enter=\"custom\"` emits the data\n * attributes but no site-kit styles, leaving the animation to the site.\n * Every default is collapsed by prefers-reduced-motion.\n */\n\nimport React, { lazy, Suspense, useEffect, useRef, useState } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport type { FormExperience, FormSubmission } from './types'\n\n/** The heavy half. Nothing else in this module may import it statically. */\nconst InteractiveForm = lazy(() =>\n import('./ManagedForm').then((m) => ({ default: m.ManagedForm })),\n)\n\nexport type FormEnterAnimation = 'fade' | 'none' | 'custom'\n\nexport interface FormEnhancerProps {\n formId: string\n /** Server-fetched raw config — hands the enhancement its fields instantly. */\n initialConfig?: Record<string, unknown> | null\n className?: string\n nativeReturnTo?: string\n experience?: FormExperience\n /** Pre-fill field values by slug. See ManagedForm.defaultValues. */\n defaultValues?: Record<string, unknown>\n onSuccess?: (submission: FormSubmission) => void\n onError?: (error: Error) => void\n /**\n * How the enhanced form arrives. 'fade' (default) crossfades and lifts it\n * into place; 'none' swaps instantly; 'custom' emits the data attributes\n * and no styles so the site owns the animation.\n */\n enter?: FormEnterAnimation\n /**\n * Set false to enhance immediately instead of waiting for idle. Only worth\n * it for a form that is the entire point of the page AND above the fold.\n */\n defer?: boolean\n /** The server-rendered <StaticForm>. Shown until the enhancement mounts. */\n children: React.ReactNode\n}\n\nexport function FormEnhancer({\n formId,\n initialConfig,\n className,\n nativeReturnTo,\n experience,\n defaultValues,\n onSuccess,\n onError,\n enter = 'fade',\n defer = true,\n children,\n}: FormEnhancerProps) {\n const ready = useDeferredActivation(defer)\n // Flipped only after the enhancement has actually committed to the DOM, so\n // the shell never disappears ahead of its replacement.\n const [enhanced, setEnhanced] = useState(false)\n\n return (\n <div\n data-sk-form-mount={enhanced ? 'enhanced' : 'shell'}\n data-sk-part=\"form-mount\"\n style={enter === 'fade' ? FADE_HOST : undefined}\n >\n {enter === 'fade' && <EnterStyles />}\n {ready ? (\n <Suspense fallback={children}>\n <Enhanced onMount={() => setEnhanced(true)} fallback={children}>\n <InteractiveForm\n formId={formId}\n initialConfig={initialConfig}\n className={className}\n nativeReturnTo={nativeReturnTo}\n experience={experience}\n defaultValues={defaultValues}\n onSuccess={onSuccess}\n onError={onError}\n />\n </Enhanced>\n </Suspense>\n ) : (\n children\n )}\n </div>\n )\n}\n\n/**\n * Reports the moment the enhancement is on screen, and keeps the shell\n * rendered if it throws. An error boundary is the difference between \"the\n * fancy version failed\" and \"there is no form on this page\".\n */\nclass Enhanced extends React.Component<\n { onMount: () => void; fallback: React.ReactNode; children: React.ReactNode },\n { failed: boolean }\n> {\n state = { failed: false }\n\n static getDerivedStateFromError() {\n return { failed: true }\n }\n\n componentDidCatch(err: unknown) {\n console.error('[Forms] Enhancement failed; keeping the native form:', err)\n }\n\n componentDidMount() {\n if (!this.state.failed) this.props.onMount()\n }\n\n render() {\n if (this.state.failed) return this.props.fallback\n return <FrameTag>{this.props.children}</FrameTag>\n }\n}\n\n/** Adds data-sk-enter for one frame so a CSS transition has something to run. */\nfunction FrameTag({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n const el = ref.current\n if (!el) return\n el.setAttribute('data-sk-enter', '')\n const id = requestAnimationFrame(() => el.removeAttribute('data-sk-enter'))\n return () => cancelAnimationFrame(id)\n }, [])\n return (\n <div ref={ref} style={{ display: 'contents' }}>\n {children}\n </div>\n )\n}\n\nconst FADE_HOST: React.CSSProperties = {\n transition:\n 'opacity var(--sk-form-enter-duration, 320ms) var(--sk-form-enter-easing, cubic-bezier(.22,1,.36,1))',\n}\n\n/**\n * Scoped to [data-sk-part=\"form-mount\"] so it can never touch a site's own\n * markup. Emitted once per form; identical text dedupes in every browser's\n * style cache and gzips to nothing.\n */\nfunction EnterStyles() {\n return (\n <style\n // biome-ignore lint/security/noDangerouslySetInnerHtml: static string, no interpolation\n dangerouslySetInnerHTML={{\n __html: `\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"shell\"]{opacity:.55}\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"enhanced\"]{opacity:1}\n[data-sk-part=\"form-mount\"] [data-sk-enter]{opacity:0;transform:translateY(var(--sk-form-enter-distance,6px))}\n[data-sk-part=\"form-mount\"] [data-sk-enter] *{transition:none!important}\n@media (prefers-reduced-motion: reduce){\n[data-sk-part=\"form-mount\"]{transition:none!important}\n[data-sk-part=\"form-mount\"][data-sk-form-mount=\"shell\"]{opacity:1}\n[data-sk-part=\"form-mount\"] [data-sk-enter]{opacity:1;transform:none}\n}`,\n }}\n />\n )\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunk62ODUWUX_js = require('./chunk-62ODUWUX.js');
|
|
5
5
|
var chunkVJMPIRSD_js = require('./chunk-VJMPIRSD.js');
|
|
6
|
-
var
|
|
6
|
+
var chunk4D2G5ZNP_js = require('./chunk-4D2G5ZNP.js');
|
|
7
7
|
var react = require('react');
|
|
8
8
|
|
|
9
9
|
// src/forms/tracking-session.ts
|
|
@@ -75,7 +75,7 @@ function useFormTracking({
|
|
|
75
75
|
return null;
|
|
76
76
|
}
|
|
77
77
|
try {
|
|
78
|
-
const response = await
|
|
78
|
+
const response = await chunk4D2G5ZNP_js.sonorFetch(
|
|
79
79
|
`${apiUrl}/api/public/forms/analytics/start`,
|
|
80
80
|
{
|
|
81
81
|
method: "POST",
|
|
@@ -118,7 +118,7 @@ function useFormTracking({
|
|
|
118
118
|
const currentStepTime = Math.floor((now - stepStartTimeRef.current) / 1e3);
|
|
119
119
|
stepTimesRef.current[currentStepRef.current] = currentStepTime;
|
|
120
120
|
session.abandonSent = true;
|
|
121
|
-
|
|
121
|
+
chunk4D2G5ZNP_js.sonorBeacon(
|
|
122
122
|
`${apiUrl}/api/public/forms/analytics/abandon`,
|
|
123
123
|
{
|
|
124
124
|
analyticsId: analyticsIdRef.current,
|
|
@@ -164,7 +164,7 @@ function useFormTracking({
|
|
|
164
164
|
const analyticsId = await resolveAnalyticsId();
|
|
165
165
|
if (!analyticsId) return;
|
|
166
166
|
try {
|
|
167
|
-
const response = await
|
|
167
|
+
const response = await chunk4D2G5ZNP_js.sonorFetch(
|
|
168
168
|
`${apiUrl}/api/public/forms/analytics/step`,
|
|
169
169
|
{
|
|
170
170
|
method: "POST",
|
|
@@ -202,7 +202,7 @@ function useFormTracking({
|
|
|
202
202
|
const session = sessionRef.current;
|
|
203
203
|
if (session) session.abandonSent = true;
|
|
204
204
|
try {
|
|
205
|
-
const response = await
|
|
205
|
+
const response = await chunk4D2G5ZNP_js.sonorFetch(
|
|
206
206
|
`${apiUrl}/api/public/forms/analytics/complete`,
|
|
207
207
|
{
|
|
208
208
|
method: "POST",
|
|
@@ -249,7 +249,7 @@ function useForm(formIdOrSlug, options = {}) {
|
|
|
249
249
|
} = options;
|
|
250
250
|
const projectId = optionsProjectId || (typeof window !== "undefined" ? window.__SITE_KIT_PROJECT_ID__ : void 0);
|
|
251
251
|
const [form, setForm] = react.useState(
|
|
252
|
-
() => initialForm ?
|
|
252
|
+
() => initialForm ? chunk62ODUWUX_js.normalizeFormConfig(initialForm) : null
|
|
253
253
|
);
|
|
254
254
|
const [isLoading, setIsLoading] = react.useState(!initialForm);
|
|
255
255
|
const [fetchError, setFetchError] = react.useState(null);
|
|
@@ -310,7 +310,7 @@ function useForm(formIdOrSlug, options = {}) {
|
|
|
310
310
|
formIdOrSlug
|
|
311
311
|
})
|
|
312
312
|
};
|
|
313
|
-
const response = await
|
|
313
|
+
const response = await chunk4D2G5ZNP_js.sonorFetch(url, init, { apiKey, retries: 3, timeoutMs: 15e3 });
|
|
314
314
|
if (!response.ok) {
|
|
315
315
|
throw new Error(`Failed to fetch form: ${response.statusText}`);
|
|
316
316
|
}
|
|
@@ -320,7 +320,7 @@ function useForm(formIdOrSlug, options = {}) {
|
|
|
320
320
|
;
|
|
321
321
|
window.__SITE_KIT_RECAPTCHA_SITE_KEY__ = data.recaptcha_site_key;
|
|
322
322
|
}
|
|
323
|
-
setForm(
|
|
323
|
+
setForm(chunk62ODUWUX_js.normalizeFormConfig(data));
|
|
324
324
|
const defaults = {};
|
|
325
325
|
for (const field of data.fields || []) {
|
|
326
326
|
if (field.default_value !== void 0 && field.default_value !== null) {
|
|
@@ -489,7 +489,7 @@ function useForm(formIdOrSlug, options = {}) {
|
|
|
489
489
|
setIsSubmitting(true);
|
|
490
490
|
setSubmitError(null);
|
|
491
491
|
try {
|
|
492
|
-
const data = await
|
|
492
|
+
const data = await chunk62ODUWUX_js.submitForm({
|
|
493
493
|
config: form,
|
|
494
494
|
values: buildVisibleValues(),
|
|
495
495
|
honeypotValue: "",
|
|
@@ -553,5 +553,5 @@ function useForm(formIdOrSlug, options = {}) {
|
|
|
553
553
|
|
|
554
554
|
exports.useForm = useForm;
|
|
555
555
|
exports.useFormTracking = useFormTracking;
|
|
556
|
-
//# sourceMappingURL=chunk-
|
|
557
|
-
//# sourceMappingURL=chunk-
|
|
556
|
+
//# sourceMappingURL=chunk-5JQLLF2T.js.map
|
|
557
|
+
//# sourceMappingURL=chunk-5JQLLF2T.js.map
|