@sonordev/site-kit 3.2.1 → 3.3.1
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-3MZ3O5OB.mjs +8 -0
- package/dist/{AnalyticsProvider-LEXGFM2K.mjs.map → AnalyticsProvider-3MZ3O5OB.mjs.map} +1 -1
- package/dist/AnalyticsProvider-PLGTU56K.js +29 -0
- package/dist/{AnalyticsProvider-A3STXJ65.js.map → AnalyticsProvider-PLGTU56K.js.map} +1 -1
- package/dist/ChatWidget-7JHVRNVE.mjs +8 -0
- package/dist/{ChatWidget-4QR2ZDRE.mjs.map → ChatWidget-7JHVRNVE.mjs.map} +1 -1
- package/dist/ChatWidget-FQARJMGU.js +17 -0
- package/dist/{ChatWidget-U3K2VHVN.js.map → ChatWidget-FQARJMGU.js.map} +1 -1
- package/dist/EngageWidget-DQ62NQPU.js +16 -0
- package/dist/{EngageWidget-TOGMQ2G6.js.map → EngageWidget-DQ62NQPU.js.map} +1 -1
- package/dist/EngageWidget-VXQOZOYA.mjs +7 -0
- package/dist/{EngageWidget-2FMKYJJT.mjs.map → EngageWidget-VXQOZOYA.mjs.map} +1 -1
- package/dist/{SignalCore-JB3VHHTG.js → SignalCore-3SG3LFQL.js} +8 -8
- package/dist/{SignalCore-JB3VHHTG.js.map → SignalCore-3SG3LFQL.js.map} +1 -1
- package/dist/{SignalCore-5QJG3IJB.mjs → SignalCore-BKG66B4C.mjs} +4 -4
- package/dist/{SignalCore-5QJG3IJB.mjs.map → SignalCore-BKG66B4C.mjs.map} +1 -1
- package/dist/SitemapSync-DWX7I6MX.js +16 -0
- package/dist/{SitemapSync-BJWSTERT.js.map → SitemapSync-DWX7I6MX.js.map} +1 -1
- package/dist/SitemapSync-IO2VOR23.mjs +7 -0
- package/dist/{SitemapSync-T6PIDW64.mjs.map → SitemapSync-IO2VOR23.mjs.map} +1 -1
- package/dist/analytics/index.js +11 -11
- package/dist/analytics/index.mjs +4 -4
- package/dist/{chunk-7R7SGJG3.mjs → chunk-25YPXQBA.mjs} +13 -5
- package/dist/chunk-25YPXQBA.mjs.map +1 -0
- package/dist/{chunk-IR5UC236.mjs → chunk-3AG6O3IA.mjs} +3 -3
- package/dist/{chunk-IR5UC236.mjs.map → chunk-3AG6O3IA.mjs.map} +1 -1
- package/dist/{chunk-NDROQRDB.js → chunk-3WRE7ZDC.js} +6 -6
- package/dist/{chunk-NDROQRDB.js.map → chunk-3WRE7ZDC.js.map} +1 -1
- package/dist/chunk-3X2YPJU5.js +8 -0
- package/dist/{chunk-WAAFJPNR.js.map → chunk-3X2YPJU5.js.map} +1 -1
- package/dist/{chunk-BNBCSHTK.js → chunk-4JMXBIUC.js} +12 -12
- package/dist/{chunk-BNBCSHTK.js.map → chunk-4JMXBIUC.js.map} +1 -1
- package/dist/{chunk-MX6DK246.js → chunk-4ROFY6PM.js} +13 -13
- package/dist/{chunk-MX6DK246.js.map → chunk-4ROFY6PM.js.map} +1 -1
- package/dist/{chunk-XHEXIYKY.mjs → chunk-7OROUYZH.mjs} +4 -4
- package/dist/{chunk-XHEXIYKY.mjs.map → chunk-7OROUYZH.mjs.map} +1 -1
- package/dist/{chunk-XM67L4HL.js → chunk-BOWR5QEV.js} +14 -14
- package/dist/{chunk-XM67L4HL.js.map → chunk-BOWR5QEV.js.map} +1 -1
- package/dist/{chunk-E7M5MDGC.js → chunk-C4JAMON3.js} +15 -15
- package/dist/{chunk-E7M5MDGC.js.map → chunk-C4JAMON3.js.map} +1 -1
- package/dist/{chunk-BUCF5FUY.mjs → chunk-DIXWP4BO.mjs} +3 -3
- package/dist/{chunk-BUCF5FUY.mjs.map → chunk-DIXWP4BO.mjs.map} +1 -1
- package/dist/{chunk-5RX4CUR2.js → chunk-DNW7FTJD.js} +97 -22
- package/dist/chunk-DNW7FTJD.js.map +1 -0
- package/dist/{chunk-52VXHSVT.js → chunk-FMQHK6MM.js} +3 -3
- package/dist/{chunk-52VXHSVT.js.map → chunk-FMQHK6MM.js.map} +1 -1
- package/dist/{chunk-DCZUMCEC.js → chunk-FSYXHIAI.js} +14 -14
- package/dist/{chunk-DCZUMCEC.js.map → chunk-FSYXHIAI.js.map} +1 -1
- package/dist/{chunk-SBT4J733.mjs → chunk-GUU3AEFM.mjs} +3 -3
- package/dist/{chunk-SBT4J733.mjs.map → chunk-GUU3AEFM.mjs.map} +1 -1
- package/dist/{chunk-DYCEEU67.mjs → chunk-JD6LV6JZ.mjs} +3 -3
- package/dist/{chunk-DYCEEU67.mjs.map → chunk-JD6LV6JZ.mjs.map} +1 -1
- package/dist/chunk-JEOKDUPQ.js +45 -0
- package/dist/chunk-JEOKDUPQ.js.map +1 -0
- package/dist/chunk-K6YGF2KJ.mjs +6 -0
- package/dist/{chunk-QLM7RNQI.mjs.map → chunk-K6YGF2KJ.mjs.map} +1 -1
- package/dist/{chunk-GMSPDTEV.js → chunk-KPKZBIAQ.js} +7 -7
- package/dist/{chunk-GMSPDTEV.js.map → chunk-KPKZBIAQ.js.map} +1 -1
- package/dist/chunk-LOS5D6B3.js +72 -0
- package/dist/chunk-LOS5D6B3.js.map +1 -0
- package/dist/{chunk-7OK6BI3A.mjs → chunk-MLD3EGM2.mjs} +3 -3
- package/dist/{chunk-7OK6BI3A.mjs.map → chunk-MLD3EGM2.mjs.map} +1 -1
- package/dist/{chunk-VTHCUHMK.mjs → chunk-NQ3K4NWX.mjs} +8 -8
- package/dist/{chunk-VTHCUHMK.mjs.map → chunk-NQ3K4NWX.mjs.map} +1 -1
- package/dist/{chunk-AMBTOIYM.mjs → chunk-OC64AHKN.mjs} +91 -16
- package/dist/chunk-OC64AHKN.mjs.map +1 -0
- package/dist/{chunk-MYAXI33P.js → chunk-PCDAVHVX.js} +8 -8
- package/dist/{chunk-MYAXI33P.js.map → chunk-PCDAVHVX.js.map} +1 -1
- package/dist/{chunk-NJIHMZPC.js → chunk-PIV54R2O.js} +9 -28
- package/dist/chunk-PIV54R2O.js.map +1 -0
- package/dist/{chunk-MKZHVSKL.mjs → chunk-QFWBAGBT.mjs} +3 -3
- package/dist/{chunk-MKZHVSKL.mjs.map → chunk-QFWBAGBT.mjs.map} +1 -1
- package/dist/{chunk-DLJZYWP3.mjs → chunk-QGV7FEUI.mjs} +7 -43
- package/dist/chunk-QGV7FEUI.mjs.map +1 -0
- package/dist/chunk-QJU6HBSK.mjs +39 -0
- package/dist/chunk-QJU6HBSK.mjs.map +1 -0
- package/dist/{chunk-B5YNHSRA.js → chunk-QRIGY6EM.js} +7 -7
- package/dist/{chunk-B5YNHSRA.js.map → chunk-QRIGY6EM.js.map} +1 -1
- package/dist/{chunk-GMO6POZ2.mjs → chunk-QW3T6WH2.mjs} +6 -25
- package/dist/chunk-QW3T6WH2.mjs.map +1 -0
- package/dist/{chunk-5ZNKNNJ4.mjs → chunk-S2N3NSC7.mjs} +4 -4
- package/dist/{chunk-5ZNKNNJ4.mjs.map → chunk-S2N3NSC7.mjs.map} +1 -1
- package/dist/{chunk-SGPIGHMN.js → chunk-TXEOLHMC.js} +4 -4
- package/dist/{chunk-SGPIGHMN.js.map → chunk-TXEOLHMC.js.map} +1 -1
- package/dist/{chunk-Z26CMPBX.js → chunk-VMVEL4WU.js} +7 -43
- package/dist/chunk-VMVEL4WU.js.map +1 -0
- package/dist/chunk-XIJ4F6IE.mjs +63 -0
- package/dist/chunk-XIJ4F6IE.mjs.map +1 -0
- package/dist/{chunk-LF3ID6WN.js → chunk-YFYMOQAX.js} +20 -12
- package/dist/chunk-YFYMOQAX.js.map +1 -0
- package/dist/{chunk-E4TWVVSW.mjs → chunk-ZFF3BKNE.mjs} +4 -4
- package/dist/{chunk-E4TWVVSW.mjs.map → chunk-ZFF3BKNE.mjs.map} +1 -1
- package/dist/{chunk-NQ2P7AB3.mjs → chunk-ZYIONE7A.mjs} +4 -4
- package/dist/{chunk-NQ2P7AB3.mjs.map → chunk-ZYIONE7A.mjs.map} +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +1 -1
- package/dist/commerce/index.js +50 -50
- package/dist/commerce/index.mjs +5 -5
- package/dist/discover-Q5RWC27Y.js +21 -0
- package/dist/discover-Q5RWC27Y.js.map +1 -0
- package/dist/discover-ROPVYJJP.mjs +4 -0
- package/dist/discover-ROPVYJJP.mjs.map +1 -0
- package/dist/engage/index.js +8 -8
- package/dist/engage/index.mjs +5 -5
- package/dist/fleet/index.js +7 -7
- package/dist/fleet/index.mjs +3 -3
- package/dist/forms/index.js +12 -12
- package/dist/forms/index.mjs +4 -4
- package/dist/images/index.js +10 -10
- package/dist/images/index.mjs +3 -3
- package/dist/index.js +63 -63
- package/dist/index.mjs +9 -9
- package/dist/layout/client.js +6 -6
- package/dist/layout/client.mjs +5 -5
- package/dist/layout/index.js +7 -7
- package/dist/layout/index.mjs +5 -5
- package/dist/maps/index.js +4 -4
- package/dist/maps/index.mjs +2 -2
- package/dist/seo/client.js +5 -4
- package/dist/seo/client.mjs +4 -3
- package/dist/seo/index.js +14 -13
- package/dist/seo/index.js.map +1 -1
- package/dist/seo/index.mjs +6 -5
- package/dist/seo/index.mjs.map +1 -1
- package/dist/seo/register-sitemap-cli.js +64 -96
- package/dist/seo/register-sitemap-cli.js.map +1 -1
- package/dist/seo/register-sitemap-cli.mjs +62 -94
- package/dist/seo/register-sitemap-cli.mjs.map +1 -1
- package/dist/seo/server.js +5 -5
- package/dist/seo/server.mjs +1 -1
- package/dist/signal/index.js +11 -11
- package/dist/signal/index.mjs +2 -2
- package/dist/sitemap/index.d.mts +15 -17
- package/dist/sitemap/index.d.ts +15 -17
- package/dist/sitemap/index.js +14 -37
- package/dist/sitemap/index.js.map +1 -1
- package/dist/sitemap/index.mjs +11 -34
- package/dist/sitemap/index.mjs.map +1 -1
- package/dist/sync/index.d.mts +10 -1
- package/dist/sync/index.d.ts +10 -1
- package/dist/sync/index.js +15 -15
- package/dist/sync/index.mjs +3 -3
- package/package.json +1 -1
- package/dist/AnalyticsProvider-A3STXJ65.js +0 -29
- package/dist/AnalyticsProvider-LEXGFM2K.mjs +0 -8
- package/dist/ChatWidget-4QR2ZDRE.mjs +0 -8
- package/dist/ChatWidget-U3K2VHVN.js +0 -17
- package/dist/EngageWidget-2FMKYJJT.mjs +0 -7
- package/dist/EngageWidget-TOGMQ2G6.js +0 -16
- package/dist/SitemapSync-BJWSTERT.js +0 -15
- package/dist/SitemapSync-T6PIDW64.mjs +0 -6
- package/dist/chunk-5RX4CUR2.js.map +0 -1
- package/dist/chunk-7R7SGJG3.mjs.map +0 -1
- package/dist/chunk-AMBTOIYM.mjs.map +0 -1
- package/dist/chunk-DLJZYWP3.mjs.map +0 -1
- package/dist/chunk-GMO6POZ2.mjs.map +0 -1
- package/dist/chunk-LF3ID6WN.js.map +0 -1
- package/dist/chunk-NJIHMZPC.js.map +0 -1
- package/dist/chunk-QLM7RNQI.mjs +0 -6
- package/dist/chunk-WAAFJPNR.js +0 -8
- package/dist/chunk-Z26CMPBX.js.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunk4ROFY6PM_js = require('./chunk-4ROFY6PM.js');
|
|
5
5
|
var react = require('react');
|
|
6
6
|
|
|
7
7
|
function useContactTracking(options = {}) {
|
|
8
8
|
const { autoTrack = true, debug = false } = options;
|
|
9
|
-
const { trackConversion: trackConversion2 } =
|
|
9
|
+
const { trackConversion: trackConversion2 } = chunk4ROFY6PM_js.useAnalytics();
|
|
10
10
|
const trackPhoneClick = react.useCallback((phoneNumber, metadata) => {
|
|
11
11
|
if (debug) console.log("[Analytics] Phone click:", phoneNumber);
|
|
12
12
|
trackConversion2({
|
|
@@ -68,5 +68,5 @@ function ContactTracking({ debug = false }) {
|
|
|
68
68
|
|
|
69
69
|
exports.ContactTracking = ContactTracking;
|
|
70
70
|
exports.useContactTracking = useContactTracking;
|
|
71
|
-
//# sourceMappingURL=chunk-
|
|
72
|
-
//# sourceMappingURL=chunk-
|
|
71
|
+
//# sourceMappingURL=chunk-TXEOLHMC.js.map
|
|
72
|
+
//# sourceMappingURL=chunk-TXEOLHMC.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analytics/useContactTracking.ts"],"names":["trackConversion","useAnalytics","useCallback","useEffect"],"mappings":"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","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/analytics/useContactTracking.ts"],"names":["trackConversion","useAnalytics","useCallback","useEffect"],"mappings":"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","file":"chunk-TXEOLHMC.js","sourcesContent":["/**\n * @sonordev/site-kit/analytics - Contact Tracking Hook\n * \n * Tracks phone calls and email clicks as conversions.\n * Use this hook to automatically track tel: and mailto: links,\n * or manually track contact interactions.\n */\n\n'use client'\n\nimport { useCallback, useEffect } from 'react'\nimport { useAnalytics } from './AnalyticsProvider'\n\nexport interface UseContactTrackingOptions {\n /** Automatically track all tel: and mailto: links (default: true) */\n autoTrack?: boolean\n /** Debug mode - logs events to console */\n debug?: boolean\n}\n\nexport interface ContactTrackingReturn {\n /** Manually track a phone call click */\n trackPhoneClick: (phoneNumber: string, metadata?: Record<string, unknown>) => void\n /** Manually track an email click */\n trackEmailClick: (email: string, metadata?: Record<string, unknown>) => void\n}\n\n/**\n * Hook for tracking phone and email contact interactions as conversions.\n * \n * @example\n * ```tsx\n * // Auto-track all tel: and mailto: links\n * useContactTracking()\n * \n * // Manual tracking\n * const { trackPhoneClick, trackEmailClick } = useContactTracking()\n * trackPhoneClick('513-555-1234')\n * trackEmailClick('hello@example.com')\n * ```\n */\nexport function useContactTracking(options: UseContactTrackingOptions = {}): ContactTrackingReturn {\n const { autoTrack = true, debug = false } = options\n const { trackConversion } = useAnalytics()\n \n const trackPhoneClick = useCallback((phoneNumber: string, metadata?: Record<string, unknown>) => {\n if (debug) console.log('[Analytics] Phone click:', phoneNumber)\n \n trackConversion({\n type: 'phone_call',\n metadata: {\n phoneNumber: phoneNumber.replace(/\\D/g, ''), // Normalize to digits\n rawNumber: phoneNumber,\n ...metadata,\n },\n })\n }, [trackConversion, debug])\n \n const trackEmailClick = useCallback((email: string, metadata?: Record<string, unknown>) => {\n if (debug) console.log('[Analytics] Email click:', email)\n \n trackConversion({\n type: 'email_click',\n metadata: {\n email,\n ...metadata,\n },\n })\n }, [trackConversion, debug])\n \n // Auto-track tel: and mailto: links\n useEffect(() => {\n if (!autoTrack || typeof document === 'undefined') return\n \n const handleClick = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n const anchor = target.closest('a')\n if (!anchor) return\n \n const href = anchor.getAttribute('href')\n if (!href) return\n \n if (href.startsWith('tel:')) {\n const phone = href.replace('tel:', '').trim()\n trackPhoneClick(phone, {\n linkText: anchor.textContent?.trim(),\n source: 'auto',\n })\n } else if (href.startsWith('mailto:')) {\n const email = href.replace('mailto:', '').split('?')[0].trim()\n trackEmailClick(email, {\n linkText: anchor.textContent?.trim(),\n source: 'auto',\n })\n }\n }\n \n document.addEventListener('click', handleClick, { capture: true })\n \n return () => {\n document.removeEventListener('click', handleClick, { capture: true })\n }\n }, [autoTrack, trackPhoneClick, trackEmailClick])\n \n return {\n trackPhoneClick,\n trackEmailClick,\n }\n}\n\n/**\n * Component wrapper for contact tracking\n * Simply include this component to auto-track all tel: and mailto: links\n * \n * @example\n * ```tsx\n * <ContactTracking />\n * ```\n */\nexport function ContactTracking({ debug = false }: { debug?: boolean }) {\n useContactTracking({ autoTrack: true, debug })\n return null\n}\n"]}
|
|
@@ -205,14 +205,12 @@ async function generateSitemap(options) {
|
|
|
205
205
|
async function registerLocalSitemap(options) {
|
|
206
206
|
const { registerSitemap } = await import('./server-api-QOQVM57V.js');
|
|
207
207
|
let entries = options.entries || [];
|
|
208
|
-
const usedExplicitEntries = entries.length > 0;
|
|
209
208
|
if (options.autoDiscover && entries.length === 0) {
|
|
210
209
|
try {
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
entries = discoverNextJsRoutes(appDir, fs, path);
|
|
210
|
+
const { discoverNextJsRoutes, findAppDirForDiscovery } = await import('./discover-Q5RWC27Y.js');
|
|
211
|
+
const appDir = findAppDirForDiscovery();
|
|
212
|
+
if (appDir) {
|
|
213
|
+
entries = discoverNextJsRoutes(appDir);
|
|
216
214
|
console.log(`[Sonor] Auto-discovered ${entries.length} routes from app directory`);
|
|
217
215
|
}
|
|
218
216
|
} catch (error) {
|
|
@@ -223,7 +221,7 @@ async function registerLocalSitemap(options) {
|
|
|
223
221
|
console.warn("[Sonor] No sitemap entries to register");
|
|
224
222
|
return { success: true, created: 0, updated: 0 };
|
|
225
223
|
}
|
|
226
|
-
const mode = options.mode ??
|
|
224
|
+
const mode = options.mode ?? "additive";
|
|
227
225
|
console.log(`[Sonor] Registering ${entries.length} sitemap entries (${mode})...`);
|
|
228
226
|
const result = await registerSitemap(entries, { mode });
|
|
229
227
|
if (result.success) {
|
|
@@ -231,40 +229,6 @@ async function registerLocalSitemap(options) {
|
|
|
231
229
|
}
|
|
232
230
|
return result;
|
|
233
231
|
}
|
|
234
|
-
function discoverNextJsRoutes(appDir, fs, path, basePath = "") {
|
|
235
|
-
const entries = [];
|
|
236
|
-
const items = fs.readdirSync(appDir, { withFileTypes: true });
|
|
237
|
-
for (const item of items) {
|
|
238
|
-
if (item.name.startsWith("_") || item.name.startsWith(".")) continue;
|
|
239
|
-
if (item.name === "api") continue;
|
|
240
|
-
if (item.name === "node_modules") continue;
|
|
241
|
-
const itemPath = path.join(appDir, item.name);
|
|
242
|
-
if (item.isDirectory()) {
|
|
243
|
-
const hasPage = fs.existsSync(path.join(itemPath, "page.tsx")) || fs.existsSync(path.join(itemPath, "page.js")) || fs.existsSync(path.join(itemPath, "page.jsx"));
|
|
244
|
-
const isRouteGroup = item.name.startsWith("(") && item.name.endsWith(")");
|
|
245
|
-
const isDynamic = item.name.startsWith("[") && item.name.endsWith("]");
|
|
246
|
-
let routePath = basePath;
|
|
247
|
-
if (!isRouteGroup && !isDynamic) {
|
|
248
|
-
routePath = `${basePath}/${item.name}`;
|
|
249
|
-
}
|
|
250
|
-
if (hasPage && !isDynamic) {
|
|
251
|
-
const priority = routePath === "" ? 1 : 0.8;
|
|
252
|
-
entries.push({ path: routePath || "/", priority });
|
|
253
|
-
}
|
|
254
|
-
if (!isDynamic) {
|
|
255
|
-
const subEntries = discoverNextJsRoutes(itemPath, fs, path, isRouteGroup ? basePath : routePath);
|
|
256
|
-
entries.push(...subEntries);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (basePath === "") {
|
|
261
|
-
const hasRootPage = fs.existsSync(path.join(appDir, "page.tsx")) || fs.existsSync(path.join(appDir, "page.js")) || fs.existsSync(path.join(appDir, "page.jsx"));
|
|
262
|
-
if (hasRootPage) {
|
|
263
|
-
entries.unshift({ path: "/", priority: 1 });
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return entries;
|
|
267
|
-
}
|
|
268
232
|
async function isIndexable(projectId, path) {
|
|
269
233
|
const directive = await getRobotsDirective({ path });
|
|
270
234
|
return directive.index;
|
|
@@ -278,5 +242,5 @@ exports.getRedirect = getRedirect;
|
|
|
278
242
|
exports.getRobotsDirective = getRobotsDirective;
|
|
279
243
|
exports.isIndexable = isIndexable;
|
|
280
244
|
exports.registerLocalSitemap = registerLocalSitemap;
|
|
281
|
-
//# sourceMappingURL=chunk-
|
|
282
|
-
//# sourceMappingURL=chunk-
|
|
245
|
+
//# sourceMappingURL=chunk-VMVEL4WU.js.map
|
|
246
|
+
//# sourceMappingURL=chunk-VMVEL4WU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo/getManagedMetadata.ts","../src/seo/routing.ts"],"names":["getSEOPageData","ensureMetadata","getABTest","recordABImpression","getRedirectData","getRobotsData","getSitemapEntries"],"mappings":"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","file":"chunk-VMVEL4WU.js","sourcesContent":["import type { Metadata } from 'next'\nimport { getSEOPageData, getABTest, recordABImpression, ensureMetadata } from './server-api'\nimport type { \n GetManagedMetadataOptions, \n ManagedMetadataResult,\n GetABVariantOptions,\n ABTestResult \n} from './types'\n\n/**\n * Get managed metadata for a page\n * \n * Use in generateMetadata() to fetch Portal-managed SEO data\n * \n * @example\n * ```tsx\n * export async function generateMetadata({ params }) {\n * return getManagedMetadata({\n * path: `/services/${params.slug}`,\n * fallback: {\n * title: 'Our Services',\n * description: 'Learn about our services'\n * }\n * })\n * }\n * ```\n */\nexport async function getManagedMetadata(\n options: GetManagedMetadataOptions\n): Promise<ManagedMetadataResult> {\n const { path, fallback = {}, overrides = {}, favicon: faviconMode = 'metadata' } = options\n const omitIcons = faviconMode === 'component'\n\n const result = await getSEOPageData(path)\n const pageData = result?.page\n const projectData = result?.project\n\n // If no managed data, return fallback (still include favicon from project if available, unless using component)\n if (!pageData) {\n const fallbackMeta: ManagedMetadataResult = {\n ...fallback,\n ...overrides,\n _managed: false,\n _source: 'fallback',\n } as ManagedMetadataResult\n\n if (!omitIcons && projectData?.logo_url) {\n fallbackMeta.icons = {\n icon: projectData.logo_url,\n apple: projectData.logo_url,\n }\n }\n\n return fallbackMeta\n }\n\n // Build metadata from managed values, falling back to provided fallbacks\n const metadata: ManagedMetadataResult = {\n _managed: true,\n _source: 'database',\n }\n\n if (!omitIcons && projectData?.logo_url) {\n metadata.icons = {\n icon: projectData.logo_url,\n apple: projectData.logo_url,\n }\n }\n\n // Signal fallback — when page exists but both title & description are empty,\n // trigger Signal AI to generate them (fire-and-forget so it never blocks TTFB;\n // the generated data will be available on the next request)\n if (!pageData.managed_title && !pageData.managed_meta_description && !pageData.managed_description) {\n ensureMetadata(path).catch(() => {})\n }\n\n // Title\n if (pageData.managed_title) {\n metadata.title = pageData.managed_title\n } else if (fallback.title) {\n metadata.title = fallback.title\n }\n\n // Description\n if (pageData.managed_meta_description || pageData.managed_description) {\n metadata.description = pageData.managed_meta_description || pageData.managed_description\n } else if (fallback.description) {\n metadata.description = fallback.description\n }\n\n // Keywords\n if (pageData.managed_keywords?.length) {\n metadata.keywords = pageData.managed_keywords\n } else if (fallback.keywords) {\n metadata.keywords = fallback.keywords\n }\n\n // Robots\n if (pageData.managed_robots) {\n metadata.robots = pageData.managed_robots\n }\n\n // Canonical\n if (pageData.managed_canonical) {\n metadata.alternates = {\n ...metadata.alternates,\n canonical: pageData.managed_canonical,\n }\n }\n\n // Optional hreflang map from Portal (expansion / multilingual sites)\n const langAlts = pageData.language_alternates\n if (langAlts && typeof langAlts === 'object' && !Array.isArray(langAlts)) {\n metadata.alternates = {\n ...metadata.alternates,\n languages: langAlts as Record<string, string>,\n }\n }\n\n // Open Graph\n const ogTitle = pageData.managed_og_title || pageData.managed_title\n const ogDescription = pageData.managed_og_description || pageData.managed_meta_description || pageData.managed_description\n const ogImage = pageData.managed_og_image\n\n if (ogTitle || ogDescription || ogImage) {\n metadata.openGraph = {\n ...(ogTitle && { title: ogTitle }),\n ...(ogDescription && { description: ogDescription }),\n ...(ogImage && { images: [{ url: ogImage }] }),\n }\n }\n\n // Twitter (use OG values as fallback)\n if (ogTitle || ogDescription || ogImage) {\n metadata.twitter = {\n card: 'summary_large_image',\n ...(ogTitle && { title: ogTitle }),\n ...(ogDescription && { description: ogDescription }),\n ...(ogImage && { images: [ogImage] }),\n }\n }\n\n // Apply overrides last\n return {\n ...metadata,\n ...overrides,\n _managed: true,\n _source: 'database',\n }\n}\n\n/**\n * Get A/B test variant for a field\n * \n * @example\n * ```tsx\n * const variant = await getABVariant({\n * path: '/pricing',\n * field: 'title',\n * sessionId: cookies().get('session_id')?.value\n * })\n * \n * if (variant) {\n * // Use variant.value instead of default\n * }\n * ```\n */\nexport async function getABVariant(\n options: GetABVariantOptions\n): Promise<ABTestResult | null> {\n const { path, field, sessionId } = options\n\n const test = await getABTest(path, field)\n\n if (!test) {\n return null\n }\n\n // Determine variant based on session ID or random\n let variant: 'a' | 'b'\n \n if (sessionId) {\n // Consistent variant for same session\n const hash = sessionId.split('').reduce((acc, char) => {\n return ((acc << 5) - acc) + char.charCodeAt(0)\n }, 0)\n variant = (Math.abs(hash) % 100) < (test.traffic_split * 100) ? 'a' : 'b'\n } else {\n // Random variant\n variant = Math.random() < test.traffic_split ? 'a' : 'b'\n }\n\n // Record impression (fire and forget)\n recordABImpression(test.id, variant, sessionId).catch(() => {\n // Silently fail - don't block rendering\n })\n\n return {\n testId: test.id,\n variant,\n value: variant === 'a' ? test.variant_a : test.variant_b,\n }\n}\n\n/**\n * Get managed metadata with A/B test support\n * \n * Automatically applies running A/B test variants to metadata\n */\nexport async function getManagedMetadataWithAB(\n options: GetManagedMetadataOptions & { sessionId?: string }\n): Promise<ManagedMetadataResult> {\n const { sessionId, ...metadataOptions } = options\n \n // Get base metadata\n const metadata = await getManagedMetadata(metadataOptions)\n\n // Check for title A/B test\n const titleTest = await getABVariant({\n path: options.path,\n field: 'title',\n sessionId,\n })\n\n if (titleTest) {\n metadata.title = titleTest.value\n }\n\n // Check for description A/B test\n const descTest = await getABVariant({\n path: options.path,\n field: 'description',\n sessionId,\n })\n\n if (descTest) {\n metadata.description = descTest.value\n }\n\n return metadata\n}\n","import { getRedirectData, getRobotsData, getSitemapEntries } from './server-api'\nimport type { \n GetRedirectOptions, \n RedirectResult, \n GetRobotsOptions, \n RobotsDirective,\n GetSitemapEntriesOptions,\n SitemapEntry \n} from './types'\n\n/**\n * Get redirect for a path if one exists\n * \n * Use in Next.js middleware to handle managed redirects\n * \n * @example\n * ```tsx\n * // middleware.ts\n * import { getRedirect } from '@sonordev/seo'\n * \n * export async function middleware(request) {\n * const redirect = await getRedirect({\n * projectId: process.env.SONOR_PROJECT_ID!,\n * path: request.nextUrl.pathname\n * })\n * \n * if (redirect) {\n * return NextResponse.redirect(redirect.destination, redirect.statusCode)\n * }\n * }\n * ```\n */\nexport async function getRedirect(\n options: GetRedirectOptions\n): Promise<RedirectResult | null> {\n const { path } = options\n\n const redirect = await getRedirectData(path)\n\n if (!redirect) {\n return null\n }\n\n // Check if expired\n if (redirect.expires_at && new Date(redirect.expires_at) < new Date()) {\n return null\n }\n\n // Determine destination\n const destination = redirect.destination_url || redirect.destination_path\n const isExternal = destination.startsWith('http://') || destination.startsWith('https://')\n\n return {\n destination,\n statusCode: redirect.status_code,\n isExternal,\n }\n}\n\n/**\n * Parse robots directive string into structured object\n */\nfunction parseRobotsString(robots: string): RobotsDirective {\n const directive: RobotsDirective = {\n index: true,\n follow: true,\n }\n\n const parts = robots.toLowerCase().split(',').map(p => p.trim())\n\n for (const part of parts) {\n if (part === 'noindex') directive.index = false\n if (part === 'nofollow') directive.follow = false\n if (part === 'noarchive') directive.noarchive = true\n if (part === 'nosnippet') directive.nosnippet = true\n if (part === 'noimageindex') directive.noimageindex = true\n if (part.startsWith('max-snippet:')) {\n directive.max_snippet = parseInt(part.split(':')[1], 10)\n }\n if (part.startsWith('max-image-preview:')) {\n const value = part.split(':')[1] as 'none' | 'standard' | 'large'\n directive.max_image_preview = value\n }\n if (part.startsWith('max-video-preview:')) {\n directive.max_video_preview = parseInt(part.split(':')[1], 10)\n }\n }\n\n return directive\n}\n\n/**\n * Get robots directive for a page\n * \n * @example\n * ```tsx\n * const robots = await getRobotsDirective({\n * projectId: process.env.SONOR_PROJECT_ID!,\n * path: '/private-page'\n * })\n * \n * if (!robots.index) {\n * // Page should not be indexed\n * }\n * ```\n */\nexport async function getRobotsDirective(\n options: GetRobotsOptions\n): Promise<RobotsDirective> {\n const { path } = options\n\n const robotsString = await getRobotsData(path)\n\n if (!robotsString) {\n // Default: index and follow\n return { index: true, follow: true }\n }\n\n return parseRobotsString(robotsString)\n}\n\n/**\n * Get sitemap entries for a project\n * \n * Use in sitemap.ts to generate dynamic sitemap\n * \n * @example\n * ```tsx\n * // app/sitemap.ts\n * import { generateSitemap } from '@sonordev/seo'\n * \n * export default async function sitemap() {\n * return generateSitemap({\n * projectId: process.env.SONOR_PROJECT_ID!,\n * baseUrl: 'https://example.com',\n * publishedOnly: true\n * })\n * }\n * ```\n */\nexport async function generateSitemap(\n options: GetSitemapEntriesOptions\n): Promise<SitemapEntry[]> {\n const { baseUrl, publishedOnly = true } = options\n\n const pages = await getSitemapEntries({ publishedOnly })\n\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl\n\n return pages.map((page: { path: string; lastmod?: string; changefreq?: string; priority?: number }) => ({\n path: page.path,\n url: `${normalizedBase}${page.path}`,\n lastmod: page.lastmod,\n changefreq: (page.changefreq || 'weekly') as SitemapEntry['changefreq'],\n priority: page.priority ?? 0.5,\n }))\n}\n\n/**\n * Register local sitemap entries with Sonor\n * \n * Call this at build time to sync your local routes to seo_pages.\n * This ensures analytics only tracks real pages.\n * \n * After registration, Signal AI will generate optimized meta titles\n * and descriptions for pages that don't have managed meta yet.\n * \n * @example\n * ```ts\n * // scripts/register-sitemap.ts\n * import { registerLocalSitemap } from '@sonordev/seo'\n * \n * // Option 1: Provide entries directly\n * await registerLocalSitemap({\n * entries: [\n * { path: '/', title: 'Home', priority: 1.0 },\n * { path: '/about', title: 'About Us', priority: 0.8 },\n * ]\n * })\n * \n * // Option 2: Auto-discover from Next.js app directory\n * await registerLocalSitemap({ autoDiscover: true })\n * \n * // Option 3: Skip Signal AI meta optimization\n * await registerLocalSitemap({ autoDiscover: true, optimize_meta: false })\n * ```\n */\nexport async function registerLocalSitemap(options: {\n entries?: Array<{\n path: string\n title?: string\n priority?: number\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n }>\n autoDiscover?: boolean\n /** Trigger Signal AI to generate optimized meta titles/descriptions (default: true) */\n optimize_meta?: boolean\n /**\n * Sync mode. Defaults to 'additive' for explicit entries (a partial list must\n * never delete the rest of the site's pages) and 'full-replace' for\n * autoDiscover (which enumerates the complete route set).\n */\n mode?: 'additive' | 'full-replace'\n}): Promise<{\n success: boolean\n created: number\n updated: number\n removed?: number\n meta_optimization?: {\n triggered: boolean\n pages_queued: number\n } | null\n}> {\n const { registerSitemap } = await import('./server-api')\n\n let entries = options.entries || []\n\n // Auto-discover from the Next.js app directory if requested. Lazy-import the\n // fs-backed discovery module so `fs` never enters this file's STATIC graph —\n // getRedirect/isIndexable below are imported by edge middleware, which has no\n // fs. Discovery only runs at build time, in Node, so a dynamic import is fine.\n if (options.autoDiscover && entries.length === 0) {\n try {\n const { discoverNextJsRoutes, findAppDirForDiscovery } = await import('../sitemap/discover')\n const appDir = findAppDirForDiscovery()\n if (appDir) {\n entries = discoverNextJsRoutes(appDir)\n console.log(`[Sonor] Auto-discovered ${entries.length} routes from app directory`)\n }\n } catch (error) {\n console.error('[Sonor] Auto-discovery failed:', error)\n }\n }\n\n if (entries.length === 0) {\n console.warn('[Sonor] No sitemap entries to register')\n return { success: true, created: 0, updated: 0 }\n }\n\n // A route set discovered from the filesystem is structurally incomplete (it\n // can't enumerate dynamic routes and ignores sitemap excludes), and an\n // explicit entry list is often partial — either way a full-replace would\n // silently delete real pages. Default everything to additive; a caller that\n // truly holds the complete page set opts in with mode: 'full-replace'. The\n // recommended `sonor-register-sitemap` postbuild additionally self-skips when\n // an app/sitemap route already owns the sync.\n const mode = options.mode ?? 'additive'\n\n console.log(`[Sonor] Registering ${entries.length} sitemap entries (${mode})...`)\n const result = await registerSitemap(entries, { mode })\n \n if (result.success) {\n console.log(`[Sonor] Sitemap registered: ${result.created} new, ${result.updated} updated`)\n }\n \n return result\n}\n\n/**\n * Check if a path should be indexed\n * \n * Quick helper to check indexability without full directive parsing\n */\nexport async function isIndexable(\n projectId: string,\n path: string\n): Promise<boolean> {\n const directive = await getRobotsDirective({ projectId, path })\n return directive.index\n}\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// src/sitemap/discover.ts
|
|
5
|
+
var APP_DIR_CANDIDATES = ["app", path.join("src", "app")];
|
|
6
|
+
var SITEMAP_ROUTE_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mjs"];
|
|
7
|
+
function findAppDirForDiscovery(cwd = process.cwd()) {
|
|
8
|
+
for (const candidate of APP_DIR_CANDIDATES) {
|
|
9
|
+
const abs = path.join(cwd, candidate);
|
|
10
|
+
if (fs.existsSync(abs)) return abs;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function findSitemapRoute(cwd = process.cwd()) {
|
|
15
|
+
for (const dir of APP_DIR_CANDIDATES) {
|
|
16
|
+
for (const ext of SITEMAP_ROUTE_EXTENSIONS) {
|
|
17
|
+
const rel = path.join(dir, `sitemap.${ext}`);
|
|
18
|
+
if (fs.existsSync(path.join(cwd, rel))) return rel;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function discoverNextJsRoutes(appDir, basePath = "") {
|
|
24
|
+
const entries = [];
|
|
25
|
+
const items = fs.readdirSync(appDir, { withFileTypes: true });
|
|
26
|
+
for (const item of items) {
|
|
27
|
+
if (item.name.startsWith("_") || item.name.startsWith(".")) continue;
|
|
28
|
+
if (item.name === "api") continue;
|
|
29
|
+
if (item.name === "node_modules") continue;
|
|
30
|
+
const itemPath = path.join(appDir, item.name);
|
|
31
|
+
if (item.isDirectory()) {
|
|
32
|
+
const hasPage = fs.existsSync(path.join(itemPath, "page.tsx")) || fs.existsSync(path.join(itemPath, "page.js")) || fs.existsSync(path.join(itemPath, "page.jsx"));
|
|
33
|
+
const isRouteGroup = item.name.startsWith("(") && item.name.endsWith(")");
|
|
34
|
+
const isDynamic = item.name.startsWith("[") && item.name.endsWith("]");
|
|
35
|
+
let routePath = basePath;
|
|
36
|
+
if (!isRouteGroup && !isDynamic) {
|
|
37
|
+
routePath = `${basePath}/${item.name}`;
|
|
38
|
+
}
|
|
39
|
+
if (hasPage && !isDynamic) {
|
|
40
|
+
const priority = routePath === "" ? 1 : 0.8;
|
|
41
|
+
entries.push({ path: routePath || "/", priority });
|
|
42
|
+
}
|
|
43
|
+
if (!isDynamic) {
|
|
44
|
+
const subEntries = discoverNextJsRoutes(
|
|
45
|
+
itemPath,
|
|
46
|
+
isRouteGroup ? basePath : routePath
|
|
47
|
+
);
|
|
48
|
+
entries.push(...subEntries);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (basePath === "") {
|
|
53
|
+
const hasRootPage = fs.existsSync(path.join(appDir, "page.tsx")) || fs.existsSync(path.join(appDir, "page.js")) || fs.existsSync(path.join(appDir, "page.jsx"));
|
|
54
|
+
if (hasRootPage) {
|
|
55
|
+
entries.unshift({ path: "/", priority: 1 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return entries;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { discoverNextJsRoutes, findAppDirForDiscovery, findSitemapRoute };
|
|
62
|
+
//# sourceMappingURL=chunk-XIJ4F6IE.mjs.map
|
|
63
|
+
//# sourceMappingURL=chunk-XIJ4F6IE.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sitemap/discover.ts"],"names":[],"mappings":"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","file":"chunk-XIJ4F6IE.mjs","sourcesContent":["/**\n * @sonordev/site-kit — Node-only filesystem discovery for the sitemap CLI\n *\n * Single source of truth for the build-time filesystem scan used by\n * `registerLocalSitemap` (both the standalone CLI impl and the server routing\n * helper used to keep their own byte-drifting copies of `discoverNextJsRoutes`).\n *\n * Plus `findSitemapRoute` — the guard that makes `--auto-discover` safe to leave\n * in any site's `postbuild`.\n *\n * IMPORTANT — why a filesystem scan must never drive a `full-replace`:\n * a scan of `app/` can only see STATIC route segments. It structurally cannot\n * enumerate the params of a dynamic route (`/work/[slug]`, `/blog/[cat]/[id]`),\n * and it ignores the app's sitemap `exclude` config. So the route set it\n * produces is always a strict, incomplete SUBSET of the real page set. Feeding\n * that incomplete set to a `full-replace` sync tells the API to DELETE every\n * page that isn't in it — i.e. every dynamic page, plus anything the app\n * intentionally excluded. When a site also has an `app/sitemap.{ts,js}` (which\n * `createSitemap` syncs as an authoritative full-replace), the two syncs delete\n * and recreate each other's rows on every build (the sitemap-oscillation bug),\n * destroying managed metadata, Signal optimizations, LLM schemas, and GSC\n * history on real pages. Hence: auto-discover is additive by default, and skips\n * entirely when a sitemap route already exists.\n *\n * Node built-ins (`fs`/`path`) only — never import this from browser/client code.\n */\n\nimport fs from 'fs'\nimport path from 'path'\n\n/** App-directory candidates, in resolution order (Next.js `app/` or `src/app/`). */\nconst APP_DIR_CANDIDATES = ['app', path.join('src', 'app')]\n\n/** Extensions a Next.js `sitemap` route file can use. */\nconst SITEMAP_ROUTE_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'mjs']\n\n/**\n * Return the app directory (absolute path) that exists under `cwd`, or null.\n * Prefers `app/`, then `src/app/`.\n */\nexport function findAppDirForDiscovery(cwd: string = process.cwd()): string | null {\n for (const candidate of APP_DIR_CANDIDATES) {\n const abs = path.join(cwd, candidate)\n if (fs.existsSync(abs)) return abs\n }\n return null\n}\n\n/**\n * Detect a Next.js `sitemap` route file (`app/sitemap.{ts,tsx,js,jsx,mjs}` or\n * `src/app/sitemap.*`). Returns the project-relative path if found, else null.\n *\n * Presence of this file means the framework already emits `/sitemap.xml` — the\n * authoritative page set — and site-kit's runtime `SitemapSync` syncs that real\n * sitemap (dynamic routes and all) additively. In that world an `--auto-discover`\n * filesystem scan is both inferior and dangerous (see the module header), so the\n * CLI skips.\n */\nexport function findSitemapRoute(cwd: string = process.cwd()): string | null {\n for (const dir of APP_DIR_CANDIDATES) {\n for (const ext of SITEMAP_ROUTE_EXTENSIONS) {\n const rel = path.join(dir, `sitemap.${ext}`)\n if (fs.existsSync(path.join(cwd, rel))) return rel\n }\n }\n return null\n}\n\n/**\n * Recursively discover STATIC Next.js routes from an app directory.\n *\n * Intentionally skips dynamic segments (`[slug]`) — they can't be enumerated\n * from disk — which is exactly why the result is incomplete and must only ever\n * drive an additive sync. Route groups `(marketing)` are folded out of the URL.\n */\nexport function discoverNextJsRoutes(\n appDir: string,\n basePath: string = '',\n): Array<{ path: string; priority: number }> {\n const entries: Array<{ path: string; priority: number }> = []\n\n const items = fs.readdirSync(appDir, { withFileTypes: true })\n\n for (const item of items) {\n if (item.name.startsWith('_') || item.name.startsWith('.')) continue\n if (item.name === 'api') continue\n if (item.name === 'node_modules') continue\n\n const itemPath = path.join(appDir, item.name)\n\n if (item.isDirectory()) {\n const hasPage =\n fs.existsSync(path.join(itemPath, 'page.tsx')) ||\n fs.existsSync(path.join(itemPath, 'page.js')) ||\n fs.existsSync(path.join(itemPath, 'page.jsx'))\n\n const isRouteGroup = item.name.startsWith('(') && item.name.endsWith(')')\n const isDynamic = item.name.startsWith('[') && item.name.endsWith(']')\n\n let routePath = basePath\n if (!isRouteGroup && !isDynamic) {\n routePath = `${basePath}/${item.name}`\n }\n\n if (hasPage && !isDynamic) {\n const priority = routePath === '' ? 1.0 : 0.8\n entries.push({ path: routePath || '/', priority })\n }\n\n if (!isDynamic) {\n const subEntries = discoverNextJsRoutes(\n itemPath,\n isRouteGroup ? basePath : routePath,\n )\n entries.push(...subEntries)\n }\n }\n }\n\n if (basePath === '') {\n const hasRootPage =\n fs.existsSync(path.join(appDir, 'page.tsx')) ||\n fs.existsSync(path.join(appDir, 'page.js')) ||\n fs.existsSync(path.join(appDir, 'page.jsx'))\n if (hasRootPage) {\n entries.unshift({ path: '/', priority: 1.0 })\n }\n }\n\n return entries\n}\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var chunkV6NVBFEP_js = require('./chunk-V6NVBFEP.js');
|
|
5
|
-
var
|
|
5
|
+
var chunkKPKZBIAQ_js = require('./chunk-KPKZBIAQ.js');
|
|
6
6
|
var react = require('react');
|
|
7
7
|
|
|
8
8
|
function useFormTracking({
|
|
@@ -35,7 +35,7 @@ function useFormTracking({
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
try {
|
|
38
|
-
const response = await
|
|
38
|
+
const response = await chunkKPKZBIAQ_js.sonorFetch(
|
|
39
39
|
`${apiUrl}/api/public/forms/analytics/start`,
|
|
40
40
|
{
|
|
41
41
|
method: "POST",
|
|
@@ -67,7 +67,7 @@ function useFormTracking({
|
|
|
67
67
|
const now = Date.now();
|
|
68
68
|
const currentStepTime = Math.floor((now - stepStartTimeRef.current) / 1e3);
|
|
69
69
|
stepTimesRef.current[currentStepRef.current] = currentStepTime;
|
|
70
|
-
|
|
70
|
+
chunkKPKZBIAQ_js.sonorBeacon(
|
|
71
71
|
`${apiUrl}/api/public/forms/analytics/abandon`,
|
|
72
72
|
{
|
|
73
73
|
analyticsId: analyticsIdRef.current,
|
|
@@ -100,7 +100,7 @@ function useFormTracking({
|
|
|
100
100
|
maxStepRef.current = Math.max(maxStepRef.current, step);
|
|
101
101
|
stepStartTimeRef.current = now;
|
|
102
102
|
try {
|
|
103
|
-
const response = await
|
|
103
|
+
const response = await chunkKPKZBIAQ_js.sonorFetch(
|
|
104
104
|
`${apiUrl}/api/public/forms/analytics/step`,
|
|
105
105
|
{
|
|
106
106
|
method: "POST",
|
|
@@ -134,7 +134,7 @@ function useFormTracking({
|
|
|
134
134
|
const finalStepTime = Math.floor((now - stepStartTimeRef.current) / 1e3);
|
|
135
135
|
stepTimesRef.current[currentStepRef.current] = finalStepTime;
|
|
136
136
|
try {
|
|
137
|
-
const response = await
|
|
137
|
+
const response = await chunkKPKZBIAQ_js.sonorFetch(
|
|
138
138
|
`${apiUrl}/api/public/forms/analytics/complete`,
|
|
139
139
|
{
|
|
140
140
|
method: "POST",
|
|
@@ -153,7 +153,7 @@ function useFormTracking({
|
|
|
153
153
|
}
|
|
154
154
|
if (debug) console.log("[Forms] Form completed, recording conversion");
|
|
155
155
|
try {
|
|
156
|
-
await
|
|
156
|
+
await chunkKPKZBIAQ_js.sonorFetch(
|
|
157
157
|
`${apiUrl}/api/public/analytics/conversion`,
|
|
158
158
|
{
|
|
159
159
|
method: "POST",
|
|
@@ -250,11 +250,19 @@ async function getRecaptchaToken(explicitSiteKey) {
|
|
|
250
250
|
try {
|
|
251
251
|
const grecaptcha = await loadScript(siteKey);
|
|
252
252
|
if (!grecaptcha?.enterprise?.execute) return null;
|
|
253
|
+
await new Promise((resolve) => grecaptcha.enterprise.ready(resolve));
|
|
253
254
|
const token = await grecaptcha.enterprise.execute(siteKey, { action: "submit" });
|
|
254
255
|
return token || null;
|
|
255
256
|
} catch (err) {
|
|
256
|
-
|
|
257
|
-
|
|
257
|
+
try {
|
|
258
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
259
|
+
const win = window;
|
|
260
|
+
const token = await win.grecaptcha.enterprise.execute(siteKey, { action: "submit" });
|
|
261
|
+
return token || null;
|
|
262
|
+
} catch (retryErr) {
|
|
263
|
+
console.warn("[site-kit reCAPTCHA]", retryErr);
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
258
266
|
}
|
|
259
267
|
}
|
|
260
268
|
|
|
@@ -315,7 +323,7 @@ async function submitForm({
|
|
|
315
323
|
_formLoadedAt: formLoadedAt
|
|
316
324
|
}
|
|
317
325
|
};
|
|
318
|
-
const response = await
|
|
326
|
+
const response = await chunkKPKZBIAQ_js.sonorFetch(
|
|
319
327
|
`${apiUrl}/api/public/forms/submit`,
|
|
320
328
|
{
|
|
321
329
|
method: "POST",
|
|
@@ -389,7 +397,7 @@ function useForm(formIdOrSlug, options = {}) {
|
|
|
389
397
|
formIdOrSlug
|
|
390
398
|
})
|
|
391
399
|
};
|
|
392
|
-
const response = await
|
|
400
|
+
const response = await chunkKPKZBIAQ_js.sonorFetch(url, init, { apiKey, retries: 3, timeoutMs: 15e3 });
|
|
393
401
|
if (!response.ok) {
|
|
394
402
|
throw new Error(`Failed to fetch form: ${response.statusText}`);
|
|
395
403
|
}
|
|
@@ -649,5 +657,5 @@ exports.DEFAULT_FORM_SUCCESS_MESSAGE = DEFAULT_FORM_SUCCESS_MESSAGE;
|
|
|
649
657
|
exports.submitForm = submitForm;
|
|
650
658
|
exports.useForm = useForm;
|
|
651
659
|
exports.useFormTracking = useFormTracking;
|
|
652
|
-
//# sourceMappingURL=chunk-
|
|
653
|
-
//# sourceMappingURL=chunk-
|
|
660
|
+
//# sourceMappingURL=chunk-YFYMOQAX.js.map
|
|
661
|
+
//# sourceMappingURL=chunk-YFYMOQAX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/forms/useFormTracking.ts","../src/forms/defaultSuccessMessage.ts","../src/forms/recaptcha.ts","../src/forms/submitForm.ts","../src/forms/useForm.ts"],"names":["useRef","useEffect","sonorFetch","sonorBeacon","useCallback","DEFAULT_HONEYPOT_FIELD","useState","useMemo"],"mappings":"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","file":"chunk-YFYMOQAX.js","sourcesContent":["/**\n * @sonordev/site-kit/forms - Form Tracking Hook\n * \n * Tracks form analytics including step progress and abandonment\n */\n\n'use client'\n\nimport { useEffect, useRef, useCallback } from 'react'\nimport { sonorFetch, sonorBeacon } from '../shared/sonorFetch'\n\ninterface UseFormTrackingOptions {\n formId: string\n totalSteps: number\n enabled?: boolean\n debug?: boolean\n}\n\ninterface FormTrackingReturn {\n trackStepChange: (step: number) => void\n trackComplete: () => void\n sessionId: string\n}\n\nexport function useFormTracking({\n formId,\n totalSteps,\n enabled = true,\n debug = false,\n}: UseFormTrackingOptions): FormTrackingReturn {\n const sessionIdRef = useRef<string>('')\n const startTimeRef = useRef<number>(0)\n const stepStartTimeRef = useRef<number>(0)\n const stepTimesRef = useRef<Record<number, number>>({})\n const currentStepRef = useRef<number>(1)\n const maxStepRef = useRef<number>(1)\n const analyticsIdRef = useRef<string | null>(null)\n \n // Initialize tracking\n useEffect(() => {\n if (!enabled) return\n if (!formId) {\n if (debug) console.warn('[Forms] Tracking skipped: formId is empty')\n return\n }\n \n // Generate session ID\n sessionIdRef.current = crypto.randomUUID()\n startTimeRef.current = Date.now()\n stepStartTimeRef.current = Date.now()\n \n // Record form start\n const startTracking = async () => {\n const apiUrl = (window as any).__SITE_KIT_API_URL__\n const apiKey = (window as any).__SITE_KIT_API_KEY__\n \n if (!apiUrl || !apiKey) {\n if (debug) console.error('[Forms] Missing API URL or API key')\n return\n }\n \n try {\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/analytics/start`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n formId,\n sessionId: sessionIdRef.current,\n deviceType: getDeviceType(),\n }),\n },\n { apiKey, retries: 0 },\n )\n \n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n \n const data = await response.json()\n analyticsIdRef.current = data.id\n if (debug) console.log('[Forms] Started tracking:', data.id)\n } catch (error) {\n if (debug) console.error('[Forms] Error starting tracking:', error)\n }\n }\n \n startTracking()\n \n // Handle abandonment on page leave\n const handleBeforeUnload = () => {\n if (!analyticsIdRef.current) return\n \n const apiUrl = (window as any).__SITE_KIT_API_URL__\n const apiKey = (window as any).__SITE_KIT_API_KEY__\n \n if (!apiUrl || !apiKey) return\n \n const now = Date.now()\n const currentStepTime = Math.floor((now - stepStartTimeRef.current) / 1000)\n stepTimesRef.current[currentStepRef.current] = currentStepTime\n\n // Fire-and-forget via sonorBeacon: keepalive fetch keeps the credential in\n // the x-api-key header (raw sendBeacon can't set headers), so abandonment\n // authenticates on 3.2+ token sites and uses the freshest minted token.\n sonorBeacon(\n `${apiUrl}/api/public/forms/analytics/abandon`,\n {\n analyticsId: analyticsIdRef.current,\n step: currentStepRef.current,\n maxStep: maxStepRef.current,\n stepTimes: stepTimesRef.current,\n totalTimeSeconds: Math.floor((now - startTimeRef.current) / 1000),\n },\n apiKey,\n )\n\n if (debug) console.log('[Forms] Abandonment tracked')\n }\n \n window.addEventListener('beforeunload', handleBeforeUnload)\n \n return () => {\n window.removeEventListener('beforeunload', handleBeforeUnload)\n }\n }, [formId, enabled, debug])\n \n // Track step changes\n const trackStepChange = useCallback(async (step: number) => {\n if (!enabled || !analyticsIdRef.current) return\n \n const apiUrl = (window as any).__SITE_KIT_API_URL__\n const apiKey = (window as any).__SITE_KIT_API_KEY__\n \n if (!apiUrl || !apiKey) {\n if (debug) console.error('[Forms] Missing API URL or API key')\n return\n }\n \n const now = Date.now()\n \n // Record time spent on previous step\n const prevStepTime = Math.floor((now - stepStartTimeRef.current) / 1000)\n stepTimesRef.current[currentStepRef.current] = prevStepTime\n \n // Update refs\n currentStepRef.current = step\n maxStepRef.current = Math.max(maxStepRef.current, step)\n stepStartTimeRef.current = now\n \n // Update database\n try {\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/analytics/step`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n analyticsId: analyticsIdRef.current,\n step,\n maxStep: maxStepRef.current,\n stepTimes: stepTimesRef.current,\n }),\n },\n { apiKey, retries: 0 },\n )\n \n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n \n if (debug) console.log('[Forms] Step changed to:', step)\n } catch (error) {\n if (debug) console.error('[Forms] Error updating step:', error)\n }\n }, [enabled, debug])\n \n // Track completion\n const trackComplete = useCallback(async () => {\n if (!enabled || !analyticsIdRef.current) return\n \n const apiUrl = (window as any).__SITE_KIT_API_URL__\n const apiKey = (window as any).__SITE_KIT_API_KEY__\n \n if (!apiUrl || !apiKey) {\n if (debug) console.error('[Forms] Missing API URL or API key')\n return\n }\n \n const now = Date.now()\n \n // Record time for final step\n const finalStepTime = Math.floor((now - stepStartTimeRef.current) / 1000)\n stepTimesRef.current[currentStepRef.current] = finalStepTime\n \n try {\n // Complete form analytics tracking\n // Note: A database trigger will automatically log this as a conversion\n // in analytics_conversions when completed_at is set\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/analytics/complete`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n analyticsId: analyticsIdRef.current,\n totalSteps,\n stepTimes: stepTimesRef.current,\n totalTimeSeconds: Math.floor((now - startTimeRef.current) / 1000),\n }),\n },\n { apiKey, retries: 0 },\n )\n \n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n \n if (debug) console.log('[Forms] Form completed, recording conversion')\n\n // Explicitly track as a conversion (don't rely on DB trigger)\n try {\n await sonorFetch(\n `${apiUrl}/api/public/analytics/conversion`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n sessionId: sessionIdRef.current,\n conversionType: 'form_submission',\n metadata: { formId, analyticsId: analyticsIdRef.current },\n pagePath: typeof window !== 'undefined' ? window.location.pathname : '/',\n referrer: typeof document !== 'undefined' ? document.referrer || null : null,\n deviceType: getDeviceType(),\n }),\n },\n { apiKey, retries: 0 },\n )\n } catch (convErr) {\n if (debug) console.error('[Forms] Error recording conversion:', convErr)\n }\n } catch (error) {\n if (debug) console.error('[Forms] Error completing tracking:', error)\n }\n }, [enabled, totalSteps, formId, debug])\n \n return {\n trackStepChange,\n trackComplete,\n sessionId: sessionIdRef.current,\n }\n}\n\n// Helper\nfunction getDeviceType(): 'desktop' | 'mobile' | 'tablet' {\n if (typeof window === 'undefined') return 'desktop'\n const ua = navigator.userAgent\n if (/tablet|ipad|playbook|silk/i.test(ua)) return 'tablet'\n if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) return 'mobile'\n return 'desktop'\n}\n\n","/** Shown when the API omits or returns an empty `success_message`. */\nexport const DEFAULT_FORM_SUCCESS_MESSAGE =\n 'Thank you for your submission! We will be in touch soon.'\n","/**\n * reCAPTCHA Enterprise - Client-side token generation for site-kit forms\n *\n * Site key is read from:\n * - window.__SITE_KIT_RECAPTCHA_SITE_KEY__ (set by SiteKitProvider)\n * - process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY (when bundled in Next.js app)\n */\n\nfunction getSiteKey(explicitSiteKey?: string): string | undefined {\n if (typeof window === 'undefined') return undefined\n if (explicitSiteKey) return explicitSiteKey\n const win = window as any\n return win.__SITE_KIT_RECAPTCHA_SITE_KEY__ ?? process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY\n}\n\n/** Max time to wait for reCAPTCHA Enterprise to become usable before giving up. */\nconst RECAPTCHA_LOAD_TIMEOUT_MS = 10000\n\nfunction loadScript(siteKey: string): Promise<any> {\n return new Promise((resolve) => {\n if (typeof window === 'undefined') {\n resolve(null)\n return\n }\n const win = window as any\n if (win.grecaptcha?.enterprise) {\n resolve(win.grecaptcha)\n return\n }\n\n // Never hang the form: if the script is blocked (ad-blocker), stalls, or\n // never initializes `grecaptcha.enterprise`, resolve null after a timeout.\n // The caller treats a null result as \"no token\" rather than waiting forever.\n let settled = false\n const finish = (value: any) => {\n if (settled) return\n settled = true\n resolve(value)\n }\n const giveUp = setTimeout(() => finish(null), RECAPTCHA_LOAD_TIMEOUT_MS)\n\n const waitForReady = () => {\n if (settled) return\n if (win.grecaptcha?.enterprise) {\n clearTimeout(giveUp)\n finish(win.grecaptcha)\n return\n }\n setTimeout(waitForReady, 50)\n }\n\n const existing = document.querySelector('script[src*=\"recaptcha/enterprise\"]')\n if (existing) {\n waitForReady()\n return\n }\n\n const script = document.createElement('script')\n script.src = `https://www.google.com/recaptcha/enterprise.js?render=${siteKey}`\n script.async = true\n script.onload = () => waitForReady()\n script.onerror = () => {\n clearTimeout(giveUp)\n finish(null)\n }\n document.head.appendChild(script)\n })\n}\n\n/**\n * Get a reCAPTCHA Enterprise token for form submission.\n * Returns null if not configured or in SSR.\n */\nexport async function getRecaptchaToken(explicitSiteKey?: string): Promise<string | null> {\n const siteKey = getSiteKey(explicitSiteKey)\n if (!siteKey || typeof window === 'undefined') return null\n try {\n const grecaptcha = await loadScript(siteKey)\n if (!grecaptcha?.enterprise?.execute) return null\n // loadScript only waits for grecaptcha.enterprise to EXIST — execute()\n // before enterprise.ready() rejects while the widget initializes (cold\n // page load = every first-visit submitter). A swallowed rejection here\n // means no token, and a requireRecaptcha project hard-rejects the lead.\n await new Promise<void>((resolve) => grecaptcha.enterprise.ready(resolve))\n const token = await grecaptcha.enterprise.execute(siteKey, { action: 'submit' })\n return token || null\n } catch (err) {\n // One retry after a beat — first-execute flakes persist on slow devices\n // even post-ready, and a dropped token is a dropped lead.\n try {\n await new Promise((resolve) => setTimeout(resolve, 800))\n const win = window as any\n const token = await win.grecaptcha.enterprise.execute(siteKey, { action: 'submit' })\n return token || null\n } catch (retryErr) {\n console.warn('[site-kit reCAPTCHA]', retryErr)\n return null\n }\n }\n}\n","/**\n * @sonordev/site-kit/forms - Shared submission logic\n *\n * Single source of truth for sending a managed form to the Sonor API. Both\n * <ManagedForm> (via FormClient) and the headless useForm() hook submit through\n * here, so the request shape, retry behaviour, reCAPTCHA handling, and error\n * parsing can never drift between the two paths.\n */\n\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { getRecaptchaToken } from './recaptcha'\nimport { DEFAULT_HONEYPOT_FIELD } from './contract'\nimport type { ManagedFormConfig } from './types'\n\n// The honeypot trap-field name lives once in the shared forms contract\n// (single source of truth for site-kit's client + sonor-api's intake).\n// Re-exported so existing importers of this path keep resolving it.\nexport { DEFAULT_HONEYPOT_FIELD }\n\nfunction getApiConfig(): { apiUrl: string; apiKey: string | undefined } {\n const apiUrl =\n typeof window !== 'undefined'\n ? (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n : 'https://api.sonor.io'\n const apiKey =\n typeof window !== 'undefined' ? (window as any).__SITE_KIT_API_KEY__ : undefined\n return { apiUrl, apiKey }\n}\n\nfunction getUTMParams(): Record<string, string | null> {\n if (typeof window === 'undefined') return {}\n const params = new URLSearchParams(window.location.search)\n const out: Record<string, string | null> = {}\n for (const key of ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']) {\n out[key] = params.get(key)\n }\n return out\n}\n\nexport interface SubmitFormArgs {\n config: ManagedFormConfig\n /**\n * Field values to submit. The caller must pass VISIBLE values only —\n * conditionally-hidden fields should be excluded so stale values are not sent.\n */\n values: Record<string, unknown>\n /** Honeypot input value (empty string for the headless hook, which has no honeypot UI). */\n honeypotValue: string\n /** Timestamp (ms) the form was first rendered — a weak anti-bot timing signal. */\n formLoadedAt: number\n}\n\n/**\n * Submit a managed form. Throws on failure (network, timeout, or a non-2xx\n * response) with a human-readable message; resolves with the parsed API\n * response on success.\n */\nexport async function submitForm({\n config,\n values,\n honeypotValue,\n formLoadedAt,\n}: SubmitFormArgs): Promise<unknown> {\n const { apiUrl, apiKey } = getApiConfig()\n if (!apiKey) {\n throw new Error('API key is required. Set SONOR_API_KEY in your environment.')\n }\n\n // reCAPTCHA token is fetched right before the POST so it cannot expire.\n const recaptchaToken = config.recaptcha_enabled\n ? await getRecaptchaToken(config.recaptcha_site_key)\n : null\n if (config.recaptcha_enabled && !recaptchaToken) {\n throw new Error('Verification could not be completed. Please try again.')\n }\n\n const honeypotFieldName = config.honeypot_field || DEFAULT_HONEYPOT_FIELD\n const utm = getUTMParams()\n\n const payload = {\n formId: config.id,\n form_id: config.id,\n data: {\n ...values,\n ...(config.honeypot_enabled ? { [honeypotFieldName]: honeypotValue } : {}),\n },\n metadata: {\n pageUrl: typeof window !== 'undefined' ? window.location.href : null,\n referrer: typeof document !== 'undefined' ? document.referrer || null : null,\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : null,\n sessionId:\n typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('_sk_sid') : null,\n // Anonymous visitor id (_sk_vid) — the same id Analytics/Signal stamp on\n // page views, so the Sonor side can bind this lead to its browsing session\n // (page -> lead attribution). Sent on every submission; null pre-hydration.\n visitorId:\n typeof localStorage !== 'undefined' ? localStorage.getItem('_sk_vid') : null,\n recaptchaToken: recaptchaToken || undefined,\n utmSource: utm.utm_source,\n utmMedium: utm.utm_medium,\n utmCampaign: utm.utm_campaign,\n // Sub-site host — preserved on form_submissions for lead attribution\n // across multi-site projects. Resolved by SiteKitClientProviders.\n site:\n typeof window !== 'undefined'\n ? (window as { __SITE_KIT_SITE__?: string }).__SITE_KIT_SITE__ ||\n window.location.host\n : undefined,\n _formLoadedAt: formLoadedAt,\n },\n }\n\n const response = await sonorFetch(\n `${apiUrl}/api/public/forms/submit`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(payload),\n },\n { retries: 2, timeoutMs: 20000 },\n )\n\n const data = await response.json().catch(() => null)\n\n if (!response.ok) {\n const msg = (\n data?.message ??\n data?.error ??\n (response.statusText || `HTTP ${response.status}`)\n )\n .toString()\n .trim()\n throw new Error(\n msg ? `Failed to submit form: ${msg}` : `Failed to submit form (${response.status})`,\n )\n }\n\n return data\n}\n","/**\n * @sonordev/site-kit/forms - useForm Hook\n * \n * Headless hook for complete control over form rendering.\n * Fetches form config from API, handles validation, state, and submission.\n * \n * @example\n * ```tsx\n * const { form, fields, values, errors, setFieldValue, submit, isSubmitting } = useForm('contact-form')\n * \n * return (\n * <form onSubmit={(e) => { e.preventDefault(); submit() }}>\n * {fields.map(field => (\n * <MyCustomInput\n * key={field.slug}\n * label={field.label}\n * value={values[field.slug]}\n * error={errors[field.slug]}\n * onChange={(val) => setFieldValue(field.slug, val)}\n * />\n * ))}\n * <button type=\"submit\" disabled={isSubmitting}>Submit</button>\n * </form>\n * )\n * ```\n */\n\n'use client'\n\nimport { useState, useEffect, useCallback, useMemo, useRef } from 'react'\nimport { useFormTracking } from './useFormTracking'\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { submitForm } from './submitForm'\nimport type { \n ManagedFormConfig, \n FormField, \n FormSubmission \n} from './types'\nimport { DEFAULT_FORM_SUCCESS_MESSAGE } from './defaultSuccessMessage'\n\n// ============================================\n// Types\n// ============================================\n\nexport interface UseFormOptions {\n /** Project ID (defaults to SiteKitProvider config) */\n projectId?: string\n /** Callback when form submits successfully */\n onSuccess?: (submission: FormSubmission) => void\n /** Callback when submission fails */\n onError?: (error: Error) => void\n /** Initial values to prefill */\n initialValues?: Record<string, unknown>\n /** Auto-redirect after success (override form config) */\n redirectUrl?: string | false\n}\n\nexport interface UseFormReturn {\n /** Form configuration (null while loading) */\n form: ManagedFormConfig | null\n /** Loading state */\n isLoading: boolean\n /** Error fetching form */\n fetchError: Error | null\n /** Human-readable error from the last submit attempt (null when none) */\n submitError: string | null\n\n /** All form fields */\n allFields: FormField[]\n /** Fields for current step (multi-step) or all fields (single step) */\n fields: FormField[]\n /** Visible fields only (respecting conditional logic) */\n visibleFields: FormField[]\n \n /** Current form values */\n values: Record<string, unknown>\n /** Current validation errors */\n errors: Record<string, string>\n \n /** Set a single field value */\n setFieldValue: (key: string, value: unknown) => void\n /** Set multiple field values at once */\n setValues: (values: Record<string, unknown>) => void\n /** Clear all errors */\n clearErrors: () => void\n \n /** Multi-step state */\n step: number\n totalSteps: number\n isMultiStep: boolean\n progress: number\n \n /** Multi-step navigation */\n nextStep: () => boolean\n prevStep: () => void\n goToStep: (step: number) => void\n canGoNext: boolean\n canGoPrev: boolean\n isLastStep: boolean\n \n /** Validate current step */\n validate: () => boolean\n /** Validate a single field */\n validateField: (field: FormField) => string | null\n /** Check if a field is visible (conditional logic) */\n isFieldVisible: (field: FormField) => boolean\n \n /** Submit the form */\n submit: () => Promise<void>\n /** Submission state */\n isSubmitting: boolean\n isComplete: boolean\n /** Reset form to initial state */\n reset: () => void\n}\n\n// ============================================\n// Hook\n// ============================================\n\nexport function useForm(\n formIdOrSlug: string,\n options: UseFormOptions = {}\n): UseFormReturn {\n const { \n projectId: optionsProjectId,\n onSuccess, \n onError, \n initialValues = {},\n redirectUrl,\n } = options\n \n // Get projectId from window globals if not provided\n const projectId = optionsProjectId || \n (typeof window !== 'undefined' ? (window as any).__SITE_KIT_PROJECT_ID__ : undefined)\n \n // State\n const [form, setForm] = useState<ManagedFormConfig | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n const [fetchError, setFetchError] = useState<Error | null>(null)\n const [values, setValuesState] = useState<Record<string, unknown>>(initialValues)\n const [errors, setErrors] = useState<Record<string, string>>({})\n const [step, setStep] = useState(1)\n const [isSubmitting, setIsSubmitting] = useState(false)\n const [isComplete, setIsComplete] = useState(false)\n const [submitError, setSubmitError] = useState<string | null>(null)\n\n // Synchronous re-entrancy guard + anti-bot timing.\n const isSubmittingRef = useRef(false)\n const formLoadedAt = useRef(Date.now())\n\n const totalSteps = form?.total_steps || 1\n const isMultiStep = (form?.is_multi_step) || false\n \n const { trackStepChange, trackComplete } = useFormTracking({\n formId: form?.id || '',\n totalSteps,\n })\n \n // Fetch form config (project is derived from API key on the backend; projectId is optional)\n useEffect(() => {\n const apiUrl = typeof window !== 'undefined'\n ? (window as any).__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n : 'https://api.sonor.io'\n\n async function fetchForm() {\n try {\n setIsLoading(true)\n setFetchError(null)\n\n // The provider sets __SITE_KIT_API_KEY__ during its render; if this\n // hook's effect happens to run first, poll briefly for it rather than\n // failing permanently and wedging the form on \"API key is required\".\n const readApiKey = (): string | undefined =>\n typeof window !== 'undefined' ? (window as any).__SITE_KIT_API_KEY__ : undefined\n let apiKey = readApiKey()\n for (let i = 0; i < 15 && !apiKey; i++) {\n await new Promise((r) => setTimeout(r, 100))\n apiKey = readApiKey()\n }\n if (!apiKey) {\n throw new Error('API key is required. Provide it via SiteKitLayout/SiteKitProvider or set SONOR_API_KEY.')\n }\n\n const url = `${apiUrl}/api/public/forms/config`\n const init: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n formIdOrSlug: formIdOrSlug,\n }),\n }\n\n // Pass the credential via options.apiKey, NOT an Authorization header.\n // extractApiKey() server-side prefers Authorization: Bearer over x-api-key,\n // so a hand-set Bearer pins the stale build-time seed and defeats\n // sonorFetch's refresh — the seed 401s and the refresh-retry keeps\n // re-sending the same dead Bearer. Letting sonorFetch own x-api-key means\n // the server sees the FRESH token.\n const response = await sonorFetch(url, init, { apiKey, retries: 3, timeoutMs: 15000 })\n\n if (!response.ok) {\n throw new Error(`Failed to fetch form: ${response.statusText}`)\n }\n \n const data = await response.json()\n if (!data) throw new Error('Form not found')\n\n if (typeof window !== 'undefined' && data.recaptcha_site_key) {\n ;(window as any).__SITE_KIT_RECAPTCHA_SITE_KEY__ = data.recaptcha_site_key\n }\n \n // Sort steps and fields\n if (data.steps) {\n data.steps.sort((a: any, b: any) => (a.step_number || 0) - (b.step_number || 0))\n }\n if (data.fields) {\n data.fields.sort((a: any, b: any) => (a.sort_order || 0) - (b.sort_order || 0))\n }\n \n // Compute multi-step properties\n const total_steps = data.steps?.length || 1\n const is_multi_step = total_steps > 1\n\n const apiSuccess =\n typeof data.success_message === 'string' ? data.success_message.trim() : ''\n const success_message =\n apiSuccess ||\n (typeof (data as { successMessage?: string }).successMessage === 'string'\n ? String((data as { successMessage?: string }).successMessage).trim()\n : '') ||\n DEFAULT_FORM_SUCCESS_MESSAGE\n \n setForm({\n ...data,\n success_message,\n total_steps,\n is_multi_step,\n })\n \n // Set default values from field configs\n const defaults: Record<string, unknown> = {}\n for (const field of data.fields || []) {\n if (field.default_value !== undefined && field.default_value !== null) {\n defaults[field.slug] = field.default_value\n }\n }\n setValuesState({ ...defaults, ...initialValues })\n \n } catch (err) {\n setFetchError(err as Error)\n } finally {\n setIsLoading(false)\n }\n }\n \n fetchForm()\n }, [formIdOrSlug, projectId])\n \n // All fields\n const allFields = useMemo(() => form?.fields || [], [form])\n \n // Fields for current step\n const fields = useMemo(() => {\n if (!isMultiStep) return allFields\n \n const currentStepConfig = form?.steps?.find(s => s.step_number === step)\n if (!currentStepConfig) return allFields\n \n return allFields.filter(f => f.step_id === currentStepConfig.id)\n }, [form, step, allFields, isMultiStep])\n \n // Check if field is visible (conditional logic)\n const isFieldVisible = useCallback((field: FormField): boolean => {\n if (!field.conditional?.show_when) return true\n \n const { field: condField, operator, value } = field.conditional.show_when\n const fieldValue = values[condField]\n \n switch (operator) {\n case 'equals':\n return fieldValue === value\n case 'not_equals':\n return fieldValue !== value\n case 'contains':\n return String(fieldValue || '').includes(String(value))\n case 'not_contains':\n return !String(fieldValue || '').includes(String(value))\n case 'is_empty':\n return !fieldValue || fieldValue === ''\n case 'not_empty':\n return !!fieldValue && fieldValue !== ''\n case 'greater_than':\n return Number(fieldValue) > Number(value)\n case 'less_than':\n return Number(fieldValue) < Number(value)\n default:\n return true\n }\n }, [values])\n \n // Visible fields only\n const visibleFields = useMemo(() => {\n return fields.filter(isFieldVisible)\n }, [fields, isFieldVisible])\n \n // Validate a single field\n const validateField = useCallback((field: FormField): string | null => {\n if (!isFieldVisible(field)) return null\n // Skip display-only field types\n if (['heading', 'section_header', 'paragraph', 'hidden'].includes(field.field_type)) return null\n\n const value = values[field.slug]\n const rules = field.validation || {}\n \n // Required check\n if (field.is_required && (!value || value === '')) {\n return rules.custom_error || `${field.label} is required`\n }\n \n if (!value) return null\n \n const strValue = String(value)\n \n // Length checks\n if (rules.min_length && strValue.length < rules.min_length) {\n return `${field.label} must be at least ${rules.min_length} characters`\n }\n if (rules.max_length && strValue.length > rules.max_length) {\n return `${field.label} must be no more than ${rules.max_length} characters`\n }\n \n // Numeric checks\n if (rules.min !== undefined && Number(value) < rules.min) {\n return `${field.label} must be at least ${rules.min}`\n }\n if (rules.max !== undefined && Number(value) > rules.max) {\n return `${field.label} must be no more than ${rules.max}`\n }\n \n // Pattern check — guard against an invalid admin-entered pattern.\n if (rules.pattern) {\n try {\n if (!new RegExp(rules.pattern).test(strValue)) {\n return rules.custom_error || `${field.label} is invalid`\n }\n } catch {\n // Malformed pattern — skip rather than crashing the form.\n }\n }\n \n // Email check\n if (field.field_type === 'email' && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(strValue)) {\n return 'Please enter a valid email address'\n }\n \n // Phone check\n if (field.field_type === 'phone' && !/^[\\d\\s\\-\\+\\(\\)]+$/.test(strValue)) {\n return 'Please enter a valid phone number'\n }\n \n return null\n }, [values, isFieldVisible])\n \n // Validate current step\n const validate = useCallback((): boolean => {\n const newErrors: Record<string, string> = {}\n \n for (const field of fields) {\n const error = validateField(field)\n if (error) {\n newErrors[field.slug] = error\n }\n }\n \n setErrors(newErrors)\n return Object.keys(newErrors).length === 0\n }, [fields, validateField])\n\n // Validate EVERY field across all steps — used on final submit so a required\n // field left empty on an earlier step cannot slip through.\n const validateAll = useCallback((): boolean => {\n const newErrors: Record<string, string> = {}\n for (const field of allFields) {\n const error = validateField(field)\n if (error) newErrors[field.slug] = error\n }\n setErrors(newErrors)\n return Object.keys(newErrors).length === 0\n }, [allFields, validateField])\n\n // Visible field values only — never submit a value from a conditionally-hidden field.\n const buildVisibleValues = useCallback((): Record<string, unknown> => {\n const out: Record<string, unknown> = {}\n for (const field of allFields) {\n if (!isFieldVisible(field)) continue\n if (field.slug in values) out[field.slug] = values[field.slug]\n }\n return out\n }, [allFields, values, isFieldVisible])\n\n // Set single field value\n const setFieldValue = useCallback((key: string, value: unknown) => {\n setValuesState(prev => ({ ...prev, [key]: value }))\n // Clear error when field changes\n setErrors(prev => {\n if (prev[key]) {\n const next = { ...prev }\n delete next[key]\n return next\n }\n return prev\n })\n }, [])\n \n // Set multiple values\n const setValues = useCallback((newValues: Record<string, unknown>) => {\n setValuesState(prev => ({ ...prev, ...newValues }))\n }, [])\n \n // Clear all errors\n const clearErrors = useCallback(() => {\n setErrors({})\n }, [])\n \n // Step navigation\n const canGoNext = step < totalSteps\n const canGoPrev = step > 1\n const isLastStep = step === totalSteps\n const progress = Math.round((step / totalSteps) * 100)\n \n const nextStep = useCallback((): boolean => {\n if (!validate()) return false\n \n if (step < totalSteps) {\n const newStep = step + 1\n setStep(newStep)\n trackStepChange(newStep)\n return true\n }\n return false\n }, [step, totalSteps, validate, trackStepChange])\n \n const prevStep = useCallback(() => {\n if (step > 1) {\n const newStep = step - 1\n setStep(newStep)\n trackStepChange(newStep)\n }\n }, [step, trackStepChange])\n \n const goToStep = useCallback((targetStep: number) => {\n if (targetStep >= 1 && targetStep <= totalSteps) {\n setStep(targetStep)\n trackStepChange(targetStep)\n }\n }, [totalSteps, trackStepChange])\n \n // Submit form\n const submit = useCallback(async () => {\n if (!form) return\n // Re-entrancy guard — blocks a concurrent double-submit in the same tick.\n if (isSubmittingRef.current) return\n if (!validateAll()) return\n\n isSubmittingRef.current = true\n setIsSubmitting(true)\n setSubmitError(null)\n\n try {\n const data = await submitForm({\n config: form,\n values: buildVisibleValues(),\n honeypotValue: '',\n formLoadedAt: formLoadedAt.current,\n })\n\n trackComplete()\n setIsComplete(true)\n onSuccess?.(data as FormSubmission)\n\n const finalRedirect = redirectUrl !== false\n ? (redirectUrl || form.redirect_url)\n : undefined\n if (finalRedirect) {\n window.location.href = finalRedirect\n }\n } catch (error) {\n console.error('[useForm] Submission error:', error)\n setSubmitError('Something went wrong sending your submission. Please try again.')\n onError?.(error as Error)\n } finally {\n isSubmittingRef.current = false\n setIsSubmitting(false)\n }\n }, [form, validateAll, buildVisibleValues, trackComplete, onSuccess, onError, redirectUrl])\n \n // Reset form\n const reset = useCallback(() => {\n setValuesState(initialValues)\n setErrors({})\n setStep(1)\n setIsComplete(false)\n setSubmitError(null)\n }, [initialValues])\n \n return {\n form,\n isLoading,\n fetchError,\n submitError,\n\n allFields,\n fields,\n visibleFields,\n \n values,\n errors,\n setFieldValue,\n setValues,\n clearErrors,\n \n step,\n totalSteps,\n isMultiStep,\n progress,\n \n nextStep,\n prevStep,\n goToStep,\n canGoNext,\n canGoPrev,\n isLastStep,\n \n validate,\n validateField,\n isFieldVisible,\n \n submit,\n isSubmitting,\n isComplete,\n reset,\n }\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useDeferredActivation } from './chunk-ZVBLXXNJ.mjs';
|
|
3
3
|
import { sanitizeFleetVersion, buildFleetHeartbeatPayload } from './chunk-ZPLN537Q.mjs';
|
|
4
|
-
import { sonorFetch } from './chunk-
|
|
5
|
-
import { SITE_KIT_VERSION } from './chunk-
|
|
4
|
+
import { sonorFetch } from './chunk-GUU3AEFM.mjs';
|
|
5
|
+
import { SITE_KIT_VERSION } from './chunk-K6YGF2KJ.mjs';
|
|
6
6
|
import { useRef, useEffect } from 'react';
|
|
7
7
|
|
|
8
8
|
// src/fleet/heartbeat.ts
|
|
@@ -93,5 +93,5 @@ function FleetHeartbeat({
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
export { FLEET_HEARTBEAT_PATH, FleetHeartbeat, detectNextVersion, sendFleetHeartbeat };
|
|
96
|
-
//# sourceMappingURL=chunk-
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
96
|
+
//# sourceMappingURL=chunk-ZFF3BKNE.mjs.map
|
|
97
|
+
//# sourceMappingURL=chunk-ZFF3BKNE.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":[],"mappings":"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","file":"chunk-E4TWVVSW.mjs","sourcesContent":["/**\n * @sonordev/site-kit/fleet — heartbeat sender (single source of truth for the POST)\n *\n * One place that turns fleet inputs into a validated payload and ships it to\n * `/api/public/fleet/heartbeat`. Used by BOTH the client component\n * (FleetHeartbeat) and any build-time caller, so the send path never forks.\n *\n * It routes through the canonical `sonorFetch` transport, which already stamps\n * the x-sitekit-version header, attaches x-api-key, and honors the auth\n * circuit breaker — a site on a stale/canceled key will not spam the API.\n */\n\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport {\n buildFleetHeartbeatPayload,\n sanitizeFleetVersion,\n type FleetHeartbeatPayload,\n type FleetHeartbeatSource,\n type FleetModule,\n} from './contract'\n\n/** Canonical ingest path — resolved against the site's Sonor API origin. */\nexport const FLEET_HEARTBEAT_PATH = '/api/public/fleet/heartbeat'\n\nexport interface SendFleetHeartbeatOptions {\n /** Sonor API origin (e.g. https://api.sonor.io). Trailing slash tolerated. */\n apiUrl: string\n /** Project API key — scopes the request; the project is resolved server-side. */\n apiKey: string\n /** Client-observable active modules (validated/deduped by the contract). */\n modules?: FleetModule[] | string[]\n /** Normalized host for the multi-site sub-dimension (server re-normalizes). */\n site?: string | null\n /** Installed Next.js version, if the caller can resolve it. */\n nextVersion?: string | null\n /** 'client' (runtime boot) or 'build' (CI). Default 'client'. */\n source?: FleetHeartbeatSource\n /** Override the reported kit version. Defaults to the runtime SITE_KIT_VERSION. */\n kitVersion?: string\n /** Log one info line with the result. Never logs at error level. */\n debug?: boolean\n}\n\n/**\n * Best-effort runtime detection of the host Next.js version. Unreliable by\n * design — Next does not expose its version consistently across app/pages\n * router — so it returns null unless a site injects `__SITE_KIT_NEXT_VERSION__`\n * or `window.next.version` is present. Build-time callers should pass\n * `nextVersion` explicitly (resolvable from `next/package.json`).\n */\nexport function detectNextVersion(): string | null {\n if (typeof window === 'undefined') return null\n const w = window as unknown as {\n __SITE_KIT_NEXT_VERSION__?: string\n next?: { version?: string }\n }\n return sanitizeFleetVersion(w.__SITE_KIT_NEXT_VERSION__ ?? w.next?.version ?? null)\n}\n\n/**\n * Fire one heartbeat. Fire-and-forget by contract: it never throws — a\n * telemetry POST must never break a visitor's page or a site's build. Returns\n * true only when the POST was accepted (2xx).\n */\nexport async function sendFleetHeartbeat(opts: SendFleetHeartbeatOptions): Promise<boolean> {\n const { apiUrl, apiKey } = opts\n if (!apiUrl || !apiKey) return false\n\n const payload: FleetHeartbeatPayload = buildFleetHeartbeatPayload({\n kitVersion: opts.kitVersion || SITE_KIT_VERSION,\n nextVersion: opts.nextVersion,\n modules: opts.modules,\n site: opts.site,\n source: opts.source,\n })\n\n try {\n const res = await sonorFetch(\n `${apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n // retries: 0 — the once-per-session guard already bounds volume, and a\n // missed heartbeat is harmless (the next page load or passive\n // x-sitekit-version capture re-registers the site).\n { apiKey, retries: 0 },\n )\n if (opts.debug) {\n // info, never error: a visitor's console must stay clean (Lighthouse).\n console.info(\n `[Sonor fleet] heartbeat ${res.status} v${payload.kit_version} [${payload.modules.join(',') || 'none'}]`,\n )\n }\n return res.ok\n } catch (err) {\n if (opts.debug) console.info('[Sonor fleet] heartbeat skipped (network)', err)\n return false\n }\n}\n","/**\n * @sonordev/site-kit/fleet — FleetHeartbeat\n *\n * A childless, effect-only client component that reports this site's build\n * fingerprint (kit version, Next.js version, active modules, host) to Sonor\n * exactly once per browser session. It exists so the platform can answer\n * \"which version / modules / key state is each site running\" without shelling\n * into every client repo.\n *\n * It is mounted as a deferred, CHILDLESS SIBLING — never a wrapper — by\n * SiteKitClientProviders, behind the same idle/first-interaction gate as\n * Analytics and Engage, so it never enters the critical hydration path (see\n * CLAUDE.md \"the page must server-render\"). It renders null and carries no\n * visitor data.\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { detectNextVersion, sendFleetHeartbeat } from './heartbeat'\nimport type { FleetModule } from './contract'\n\n/** Session guard key — one heartbeat per session, re-sent when version/host changes. */\nconst SESSION_GUARD_KEY = '_sk_fleet_hb'\n\nexport interface FleetHeartbeatProps {\n /** Sonor API origin. Falls back to `window.__SITE_KIT_API_URL__` then api.sonor.io. */\n apiUrl?: string\n /** Project API key. Falls back to `window.__SITE_KIT_API_KEY__`. */\n apiKey?: string\n /** Host for the multi-site sub-dimension. Falls back to `__SITE_KIT_SITE__` then location.host. */\n site?: string | null\n /** Client-observable active modules (SiteKitClientProviders passes the enabled set). */\n modules?: FleetModule[]\n /** Installed Next.js version if known; otherwise best-effort runtime detection. */\n nextVersion?: string | null\n /**\n * Self-defer to idle / first interaction. Default true so standalone use is\n * safe. SiteKitClientProviders already gates the mount and passes false.\n */\n defer?: boolean\n /** Send at most once per browser session per (version, host). Default true. */\n oncePerSession?: boolean\n debug?: boolean\n}\n\nexport function FleetHeartbeat({\n apiUrl,\n apiKey,\n site,\n modules,\n nextVersion,\n defer = true,\n oncePerSession = true,\n debug = false,\n}: FleetHeartbeatProps) {\n const ready = useDeferredActivation(defer)\n const sentRef = useRef(false)\n\n useEffect(() => {\n if (!ready || sentRef.current) return\n if (typeof window === 'undefined') return\n\n const w = window as unknown as {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n __SITE_KIT_SITE__?: string\n }\n const url = apiUrl || w.__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n const key = apiKey || w.__SITE_KIT_API_KEY__\n if (!key) return // no key resolvable — nothing to authenticate the heartbeat\n\n const resolvedSite = site ?? w.__SITE_KIT_SITE__ ?? window.location.host ?? ''\n\n // Once-per-session-per-(version,host): keeps heartbeat volume to one POST\n // per visitor session while still re-reporting after an upgrade or on a\n // different sub-site. Storage-disabled browsers simply send once per load.\n if (oncePerSession) {\n const stamp = `${SITE_KIT_VERSION}:${resolvedSite}`\n try {\n if (sessionStorage.getItem(SESSION_GUARD_KEY) === stamp) {\n sentRef.current = true\n return\n }\n sessionStorage.setItem(SESSION_GUARD_KEY, stamp)\n } catch {\n /* storage unavailable — fall through and send this load */\n }\n }\n\n sentRef.current = true\n void sendFleetHeartbeat({\n apiUrl: url,\n apiKey: key,\n site: resolvedSite,\n modules,\n nextVersion: nextVersion ?? detectNextVersion(),\n source: 'client',\n debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug])\n\n return null\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/fleet/heartbeat.ts","../src/fleet/FleetHeartbeat.tsx"],"names":[],"mappings":"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","file":"chunk-ZFF3BKNE.mjs","sourcesContent":["/**\n * @sonordev/site-kit/fleet — heartbeat sender (single source of truth for the POST)\n *\n * One place that turns fleet inputs into a validated payload and ships it to\n * `/api/public/fleet/heartbeat`. Used by BOTH the client component\n * (FleetHeartbeat) and any build-time caller, so the send path never forks.\n *\n * It routes through the canonical `sonorFetch` transport, which already stamps\n * the x-sitekit-version header, attaches x-api-key, and honors the auth\n * circuit breaker — a site on a stale/canceled key will not spam the API.\n */\n\nimport { sonorFetch } from '../shared/sonorFetch'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport {\n buildFleetHeartbeatPayload,\n sanitizeFleetVersion,\n type FleetHeartbeatPayload,\n type FleetHeartbeatSource,\n type FleetModule,\n} from './contract'\n\n/** Canonical ingest path — resolved against the site's Sonor API origin. */\nexport const FLEET_HEARTBEAT_PATH = '/api/public/fleet/heartbeat'\n\nexport interface SendFleetHeartbeatOptions {\n /** Sonor API origin (e.g. https://api.sonor.io). Trailing slash tolerated. */\n apiUrl: string\n /** Project API key — scopes the request; the project is resolved server-side. */\n apiKey: string\n /** Client-observable active modules (validated/deduped by the contract). */\n modules?: FleetModule[] | string[]\n /** Normalized host for the multi-site sub-dimension (server re-normalizes). */\n site?: string | null\n /** Installed Next.js version, if the caller can resolve it. */\n nextVersion?: string | null\n /** 'client' (runtime boot) or 'build' (CI). Default 'client'. */\n source?: FleetHeartbeatSource\n /** Override the reported kit version. Defaults to the runtime SITE_KIT_VERSION. */\n kitVersion?: string\n /** Log one info line with the result. Never logs at error level. */\n debug?: boolean\n}\n\n/**\n * Best-effort runtime detection of the host Next.js version. Unreliable by\n * design — Next does not expose its version consistently across app/pages\n * router — so it returns null unless a site injects `__SITE_KIT_NEXT_VERSION__`\n * or `window.next.version` is present. Build-time callers should pass\n * `nextVersion` explicitly (resolvable from `next/package.json`).\n */\nexport function detectNextVersion(): string | null {\n if (typeof window === 'undefined') return null\n const w = window as unknown as {\n __SITE_KIT_NEXT_VERSION__?: string\n next?: { version?: string }\n }\n return sanitizeFleetVersion(w.__SITE_KIT_NEXT_VERSION__ ?? w.next?.version ?? null)\n}\n\n/**\n * Fire one heartbeat. Fire-and-forget by contract: it never throws — a\n * telemetry POST must never break a visitor's page or a site's build. Returns\n * true only when the POST was accepted (2xx).\n */\nexport async function sendFleetHeartbeat(opts: SendFleetHeartbeatOptions): Promise<boolean> {\n const { apiUrl, apiKey } = opts\n if (!apiUrl || !apiKey) return false\n\n const payload: FleetHeartbeatPayload = buildFleetHeartbeatPayload({\n kitVersion: opts.kitVersion || SITE_KIT_VERSION,\n nextVersion: opts.nextVersion,\n modules: opts.modules,\n site: opts.site,\n source: opts.source,\n })\n\n try {\n const res = await sonorFetch(\n `${apiUrl.replace(/\\/$/, '')}${FLEET_HEARTBEAT_PATH}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n },\n // retries: 0 — the once-per-session guard already bounds volume, and a\n // missed heartbeat is harmless (the next page load or passive\n // x-sitekit-version capture re-registers the site).\n { apiKey, retries: 0 },\n )\n if (opts.debug) {\n // info, never error: a visitor's console must stay clean (Lighthouse).\n console.info(\n `[Sonor fleet] heartbeat ${res.status} v${payload.kit_version} [${payload.modules.join(',') || 'none'}]`,\n )\n }\n return res.ok\n } catch (err) {\n if (opts.debug) console.info('[Sonor fleet] heartbeat skipped (network)', err)\n return false\n }\n}\n","/**\n * @sonordev/site-kit/fleet — FleetHeartbeat\n *\n * A childless, effect-only client component that reports this site's build\n * fingerprint (kit version, Next.js version, active modules, host) to Sonor\n * exactly once per browser session. It exists so the platform can answer\n * \"which version / modules / key state is each site running\" without shelling\n * into every client repo.\n *\n * It is mounted as a deferred, CHILDLESS SIBLING — never a wrapper — by\n * SiteKitClientProviders, behind the same idle/first-interaction gate as\n * Analytics and Engage, so it never enters the critical hydration path (see\n * CLAUDE.md \"the page must server-render\"). It renders null and carries no\n * visitor data.\n */\n\n'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport { SITE_KIT_VERSION } from '../shared/version'\nimport { detectNextVersion, sendFleetHeartbeat } from './heartbeat'\nimport type { FleetModule } from './contract'\n\n/** Session guard key — one heartbeat per session, re-sent when version/host changes. */\nconst SESSION_GUARD_KEY = '_sk_fleet_hb'\n\nexport interface FleetHeartbeatProps {\n /** Sonor API origin. Falls back to `window.__SITE_KIT_API_URL__` then api.sonor.io. */\n apiUrl?: string\n /** Project API key. Falls back to `window.__SITE_KIT_API_KEY__`. */\n apiKey?: string\n /** Host for the multi-site sub-dimension. Falls back to `__SITE_KIT_SITE__` then location.host. */\n site?: string | null\n /** Client-observable active modules (SiteKitClientProviders passes the enabled set). */\n modules?: FleetModule[]\n /** Installed Next.js version if known; otherwise best-effort runtime detection. */\n nextVersion?: string | null\n /**\n * Self-defer to idle / first interaction. Default true so standalone use is\n * safe. SiteKitClientProviders already gates the mount and passes false.\n */\n defer?: boolean\n /** Send at most once per browser session per (version, host). Default true. */\n oncePerSession?: boolean\n debug?: boolean\n}\n\nexport function FleetHeartbeat({\n apiUrl,\n apiKey,\n site,\n modules,\n nextVersion,\n defer = true,\n oncePerSession = true,\n debug = false,\n}: FleetHeartbeatProps) {\n const ready = useDeferredActivation(defer)\n const sentRef = useRef(false)\n\n useEffect(() => {\n if (!ready || sentRef.current) return\n if (typeof window === 'undefined') return\n\n const w = window as unknown as {\n __SITE_KIT_API_URL__?: string\n __SITE_KIT_API_KEY__?: string\n __SITE_KIT_SITE__?: string\n }\n const url = apiUrl || w.__SITE_KIT_API_URL__ || 'https://api.sonor.io'\n const key = apiKey || w.__SITE_KIT_API_KEY__\n if (!key) return // no key resolvable — nothing to authenticate the heartbeat\n\n const resolvedSite = site ?? w.__SITE_KIT_SITE__ ?? window.location.host ?? ''\n\n // Once-per-session-per-(version,host): keeps heartbeat volume to one POST\n // per visitor session while still re-reporting after an upgrade or on a\n // different sub-site. Storage-disabled browsers simply send once per load.\n if (oncePerSession) {\n const stamp = `${SITE_KIT_VERSION}:${resolvedSite}`\n try {\n if (sessionStorage.getItem(SESSION_GUARD_KEY) === stamp) {\n sentRef.current = true\n return\n }\n sessionStorage.setItem(SESSION_GUARD_KEY, stamp)\n } catch {\n /* storage unavailable — fall through and send this load */\n }\n }\n\n sentRef.current = true\n void sendFleetHeartbeat({\n apiUrl: url,\n apiKey: key,\n site: resolvedSite,\n modules,\n nextVersion: nextVersion ?? detectNextVersion(),\n source: 'client',\n debug,\n })\n }, [ready, apiUrl, apiKey, site, modules, nextVersion, oncePerSession, debug])\n\n return null\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useForm } from './chunk-
|
|
3
|
-
import { sonorFetch } from './chunk-
|
|
2
|
+
import { useForm } from './chunk-25YPXQBA.mjs';
|
|
3
|
+
import { sonorFetch } from './chunk-GUU3AEFM.mjs';
|
|
4
4
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
5
5
|
import ReactMarkdown from 'react-markdown';
|
|
6
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
@@ -1605,5 +1605,5 @@ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, sign
|
|
|
1605
1605
|
}
|
|
1606
1606
|
|
|
1607
1607
|
export { ChatWidget };
|
|
1608
|
-
//# sourceMappingURL=chunk-
|
|
1609
|
-
//# sourceMappingURL=chunk-
|
|
1608
|
+
//# sourceMappingURL=chunk-ZYIONE7A.mjs.map
|
|
1609
|
+
//# sourceMappingURL=chunk-ZYIONE7A.mjs.map
|