@sonordev/site-kit 3.0.5 → 3.2.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.
Files changed (227) hide show
  1. package/AGENTS.md +107 -0
  2. package/agent-manifest.json +377 -0
  3. package/dist/AnalyticsProvider-A3STXJ65.js +29 -0
  4. package/dist/{AnalyticsProvider-WTOGN7LV.js.map → AnalyticsProvider-A3STXJ65.js.map} +1 -1
  5. package/dist/AnalyticsProvider-LEXGFM2K.mjs +8 -0
  6. package/dist/{AnalyticsProvider-MLT26JRP.mjs.map → AnalyticsProvider-LEXGFM2K.mjs.map} +1 -1
  7. package/dist/BlogPostFaqSection-4AD2NIGG.js +13 -0
  8. package/dist/{BlogPostFaqSection-H556RV62.js.map → BlogPostFaqSection-4AD2NIGG.js.map} +1 -1
  9. package/dist/{BlogPostFaqSection-VSHOKKUD.mjs → BlogPostFaqSection-TU6YEC7P.mjs} +3 -3
  10. package/dist/{BlogPostFaqSection-VSHOKKUD.mjs.map → BlogPostFaqSection-TU6YEC7P.mjs.map} +1 -1
  11. package/dist/ChatWidget-4QR2ZDRE.mjs +8 -0
  12. package/dist/{ChatWidget-QPZTPSPE.mjs.map → ChatWidget-4QR2ZDRE.mjs.map} +1 -1
  13. package/dist/ChatWidget-U3K2VHVN.js +17 -0
  14. package/dist/{ChatWidget-MAL7FB4M.js.map → ChatWidget-U3K2VHVN.js.map} +1 -1
  15. package/dist/EngageWidget-2FMKYJJT.mjs +7 -0
  16. package/dist/{EngageWidget-IPBUA4DW.mjs.map → EngageWidget-2FMKYJJT.mjs.map} +1 -1
  17. package/dist/EngageWidget-TOGMQ2G6.js +16 -0
  18. package/dist/{EngageWidget-N3CABK3L.js.map → EngageWidget-TOGMQ2G6.js.map} +1 -1
  19. package/dist/{SignalCore-4MDIJUAP.mjs → SignalCore-5QJG3IJB.mjs} +60 -47
  20. package/dist/SignalCore-5QJG3IJB.mjs.map +1 -0
  21. package/dist/{SignalCore-ZMFUE3N4.js → SignalCore-JB3VHHTG.js} +60 -47
  22. package/dist/SignalCore-JB3VHHTG.js.map +1 -0
  23. package/dist/SitemapSync-BJWSTERT.js +15 -0
  24. package/dist/{SitemapSync-FUCH6Y6J.js.map → SitemapSync-BJWSTERT.js.map} +1 -1
  25. package/dist/SitemapSync-T6PIDW64.mjs +6 -0
  26. package/dist/{SitemapSync-LCSPRAWU.mjs.map → SitemapSync-T6PIDW64.mjs.map} +1 -1
  27. package/dist/analytics/index.js +11 -9
  28. package/dist/analytics/index.mjs +4 -2
  29. package/dist/blog/index.js +12 -12
  30. package/dist/blog/index.mjs +2 -2
  31. package/dist/blog/server-ui.js +5 -5
  32. package/dist/blog/server-ui.mjs +1 -1
  33. package/dist/brand.css +21 -6
  34. package/dist/{chunk-O7E6QVK3.js → chunk-52VXHSVT.js} +3 -3
  35. package/dist/{chunk-O7E6QVK3.js.map → chunk-52VXHSVT.js.map} +1 -1
  36. package/dist/{chunk-I2GTT2DG.js → chunk-5RX4CUR2.js} +19 -21
  37. package/dist/chunk-5RX4CUR2.js.map +1 -0
  38. package/dist/{chunk-5AESOLXS.mjs → chunk-5ZNKNNJ4.mjs} +13 -11
  39. package/dist/chunk-5ZNKNNJ4.mjs.map +1 -0
  40. package/dist/{chunk-3YQCPLGP.mjs → chunk-7OK6BI3A.mjs} +41 -79
  41. package/dist/chunk-7OK6BI3A.mjs.map +1 -0
  42. package/dist/{chunk-TCVHI3JP.mjs → chunk-7R7SGJG3.mjs} +71 -109
  43. package/dist/chunk-7R7SGJG3.mjs.map +1 -0
  44. package/dist/{chunk-FT5JNCD4.mjs → chunk-AB2VYIYU.mjs} +11 -11
  45. package/dist/{chunk-FT5JNCD4.mjs.map → chunk-AB2VYIYU.mjs.map} +1 -1
  46. package/dist/{chunk-FMZOZQBV.mjs → chunk-AMBTOIYM.mjs} +19 -21
  47. package/dist/chunk-AMBTOIYM.mjs.map +1 -0
  48. package/dist/chunk-B5YNHSRA.js +102 -0
  49. package/dist/chunk-B5YNHSRA.js.map +1 -0
  50. package/dist/{chunk-I5TJIESQ.js → chunk-BMKZHXWQ.js} +11 -11
  51. package/dist/{chunk-I5TJIESQ.js.map → chunk-BMKZHXWQ.js.map} +1 -1
  52. package/dist/{chunk-NNZDHLXN.js → chunk-BNBCSHTK.js} +35 -9
  53. package/dist/chunk-BNBCSHTK.js.map +1 -0
  54. package/dist/{chunk-AN4ATCYQ.mjs → chunk-BUCF5FUY.mjs} +3 -3
  55. package/dist/{chunk-AN4ATCYQ.mjs.map → chunk-BUCF5FUY.mjs.map} +1 -1
  56. package/dist/{chunk-3GCEFS73.js → chunk-DCZUMCEC.js} +39 -62
  57. package/dist/chunk-DCZUMCEC.js.map +1 -0
  58. package/dist/{chunk-EUDTPXUI.mjs → chunk-DYCEEU67.mjs} +3 -3
  59. package/dist/{chunk-EUDTPXUI.mjs.map → chunk-DYCEEU67.mjs.map} +1 -1
  60. package/dist/chunk-E4TWVVSW.mjs +97 -0
  61. package/dist/chunk-E4TWVVSW.mjs.map +1 -0
  62. package/dist/{chunk-VCWSSGCU.js → chunk-E7M5MDGC.js} +32 -43
  63. package/dist/chunk-E7M5MDGC.js.map +1 -0
  64. package/dist/{chunk-QTWA44S3.mjs → chunk-GMO6POZ2.mjs} +7 -8
  65. package/dist/chunk-GMO6POZ2.mjs.map +1 -0
  66. package/dist/chunk-GMSPDTEV.js +264 -0
  67. package/dist/chunk-GMSPDTEV.js.map +1 -0
  68. package/dist/{chunk-UJQYSVYV.js → chunk-GQF6QAQS.js} +4 -4
  69. package/dist/{chunk-UJQYSVYV.js.map → chunk-GQF6QAQS.js.map} +1 -1
  70. package/dist/{chunk-65SMJBHW.mjs → chunk-IR5UC236.mjs} +69 -74
  71. package/dist/chunk-IR5UC236.mjs.map +1 -0
  72. package/dist/{chunk-MLJXELPA.js → chunk-LF3ID6WN.js} +71 -111
  73. package/dist/chunk-LF3ID6WN.js.map +1 -0
  74. package/dist/{chunk-YFGKIKXA.mjs → chunk-MKZHVSKL.mjs} +3 -3
  75. package/dist/{chunk-YFGKIKXA.mjs.map → chunk-MKZHVSKL.mjs.map} +1 -1
  76. package/dist/{chunk-IBDVZ6BF.js → chunk-MX6DK246.js} +69 -74
  77. package/dist/chunk-MX6DK246.js.map +1 -0
  78. package/dist/{chunk-DGHPOWLU.js → chunk-MYAXI33P.js} +8 -8
  79. package/dist/{chunk-DGHPOWLU.js.map → chunk-MYAXI33P.js.map} +1 -1
  80. package/dist/{chunk-YJWKHCOB.js → chunk-NDROQRDB.js} +13 -11
  81. package/dist/chunk-NDROQRDB.js.map +1 -0
  82. package/dist/{chunk-RI6JZUAE.js → chunk-NJIHMZPC.js} +7 -8
  83. package/dist/chunk-NJIHMZPC.js.map +1 -0
  84. package/dist/{chunk-KHFNPPOX.mjs → chunk-NQ2P7AB3.mjs} +31 -42
  85. package/dist/chunk-NQ2P7AB3.mjs.map +1 -0
  86. package/dist/{chunk-PST3NOEM.mjs → chunk-O237KKCX.mjs} +4 -4
  87. package/dist/{chunk-PST3NOEM.mjs.map → chunk-O237KKCX.mjs.map} +1 -1
  88. package/dist/chunk-Q2NBD54V.js +69 -0
  89. package/dist/chunk-Q2NBD54V.js.map +1 -0
  90. package/dist/chunk-QLM7RNQI.mjs +6 -0
  91. package/dist/chunk-QLM7RNQI.mjs.map +1 -0
  92. package/dist/chunk-SBT4J733.mjs +259 -0
  93. package/dist/chunk-SBT4J733.mjs.map +1 -0
  94. package/dist/{chunk-EBJ4S5DU.js → chunk-SGPIGHMN.js} +4 -4
  95. package/dist/{chunk-EBJ4S5DU.js.map → chunk-SGPIGHMN.js.map} +1 -1
  96. package/dist/chunk-V6NVBFEP.js +54 -0
  97. package/dist/chunk-V6NVBFEP.js.map +1 -0
  98. package/dist/{chunk-KMZ527EM.mjs → chunk-VTHCUHMK.mjs} +34 -8
  99. package/dist/chunk-VTHCUHMK.mjs.map +1 -0
  100. package/dist/chunk-WAAFJPNR.js +8 -0
  101. package/dist/chunk-WAAFJPNR.js.map +1 -0
  102. package/dist/chunk-WZHWWOUC.js +51 -0
  103. package/dist/chunk-WZHWWOUC.js.map +1 -0
  104. package/dist/chunk-X3S6KXAP.mjs +47 -0
  105. package/dist/chunk-X3S6KXAP.mjs.map +1 -0
  106. package/dist/chunk-X4OPM3SK.mjs +43 -0
  107. package/dist/chunk-X4OPM3SK.mjs.map +1 -0
  108. package/dist/{chunk-T4QBSRA4.mjs → chunk-XHEXIYKY.mjs} +37 -60
  109. package/dist/chunk-XHEXIYKY.mjs.map +1 -0
  110. package/dist/{chunk-IFVMZFNT.js → chunk-XM67L4HL.js} +41 -79
  111. package/dist/chunk-XM67L4HL.js.map +1 -0
  112. package/dist/chunk-ZPLN537Q.mjs +57 -0
  113. package/dist/chunk-ZPLN537Q.mjs.map +1 -0
  114. package/dist/cli/index.js +8986 -1276
  115. package/dist/cli/index.js.map +1 -1
  116. package/dist/cli/index.mjs +8972 -1280
  117. package/dist/cli/index.mjs.map +1 -1
  118. package/dist/commerce/index.js +50 -48
  119. package/dist/commerce/index.mjs +5 -3
  120. package/dist/engage/index.js +11 -8
  121. package/dist/engage/index.js.map +1 -1
  122. package/dist/engage/index.mjs +8 -5
  123. package/dist/engage/index.mjs.map +1 -1
  124. package/dist/fleet/contract.d.mts +99 -0
  125. package/dist/fleet/contract.d.ts +99 -0
  126. package/dist/fleet/contract.js +53 -0
  127. package/dist/fleet/contract.js.map +1 -0
  128. package/dist/fleet/contract.mjs +4 -0
  129. package/dist/fleet/contract.mjs.map +1 -0
  130. package/dist/fleet/index.d.mts +89 -0
  131. package/dist/fleet/index.d.ts +89 -0
  132. package/dist/fleet/index.js +74 -0
  133. package/dist/fleet/index.js.map +1 -0
  134. package/dist/fleet/index.mjs +9 -0
  135. package/dist/fleet/index.mjs.map +1 -0
  136. package/dist/forms/contract.d.mts +69 -0
  137. package/dist/forms/contract.d.ts +69 -0
  138. package/dist/forms/contract.js +33 -0
  139. package/dist/forms/contract.js.map +1 -0
  140. package/dist/forms/contract.mjs +4 -0
  141. package/dist/forms/contract.mjs.map +1 -0
  142. package/dist/forms/index.js +27 -23
  143. package/dist/forms/index.js.map +1 -1
  144. package/dist/forms/index.mjs +20 -16
  145. package/dist/forms/index.mjs.map +1 -1
  146. package/dist/forms/styles.css +14 -10
  147. package/dist/images/index.js +10 -8
  148. package/dist/images/index.mjs +3 -1
  149. package/dist/index.js +103 -102
  150. package/dist/index.js.map +1 -1
  151. package/dist/index.mjs +14 -13
  152. package/dist/index.mjs.map +1 -1
  153. package/dist/layout/client.d.mts +8 -1
  154. package/dist/layout/client.d.ts +8 -1
  155. package/dist/layout/client.js +8 -3
  156. package/dist/layout/client.mjs +7 -2
  157. package/dist/layout/index.d.mts +7 -1
  158. package/dist/layout/index.d.ts +7 -1
  159. package/dist/layout/index.js +57 -4
  160. package/dist/layout/index.js.map +1 -1
  161. package/dist/layout/index.mjs +56 -3
  162. package/dist/layout/index.mjs.map +1 -1
  163. package/dist/maps/index.d.mts +4 -2
  164. package/dist/maps/index.d.ts +4 -2
  165. package/dist/maps/index.js +9 -18
  166. package/dist/maps/index.js.map +1 -1
  167. package/dist/maps/index.mjs +9 -18
  168. package/dist/maps/index.mjs.map +1 -1
  169. package/dist/portfolio/contract.d.mts +61 -0
  170. package/dist/portfolio/contract.d.ts +61 -0
  171. package/dist/portfolio/contract.js +43 -0
  172. package/dist/portfolio/contract.js.map +1 -0
  173. package/dist/portfolio/contract.mjs +38 -0
  174. package/dist/portfolio/contract.mjs.map +1 -0
  175. package/dist/seo/client.js +4 -2
  176. package/dist/seo/client.mjs +3 -1
  177. package/dist/seo/index.js +4 -2
  178. package/dist/seo/index.js.map +1 -1
  179. package/dist/seo/index.mjs +3 -1
  180. package/dist/seo/index.mjs.map +1 -1
  181. package/dist/seo-pages/contract.d.mts +62 -0
  182. package/dist/seo-pages/contract.d.ts +62 -0
  183. package/dist/seo-pages/contract.js +48 -0
  184. package/dist/seo-pages/contract.js.map +1 -0
  185. package/dist/seo-pages/contract.mjs +44 -0
  186. package/dist/seo-pages/contract.mjs.map +1 -0
  187. package/dist/signal/index.js +11 -11
  188. package/dist/signal/index.mjs +2 -2
  189. package/dist/sites/contract.d.mts +57 -0
  190. package/dist/sites/contract.d.ts +57 -0
  191. package/dist/sites/contract.js +37 -0
  192. package/dist/sites/contract.js.map +1 -0
  193. package/dist/sites/contract.mjs +4 -0
  194. package/dist/sites/contract.mjs.map +1 -0
  195. package/dist/sync/index.js +15 -13
  196. package/dist/sync/index.mjs +3 -1
  197. package/package.json +44 -4
  198. package/skills/site-kit/SKILL.md +66 -0
  199. package/dist/AnalyticsProvider-MLT26JRP.mjs +0 -6
  200. package/dist/AnalyticsProvider-WTOGN7LV.js +0 -27
  201. package/dist/BlogPostFaqSection-H556RV62.js +0 -13
  202. package/dist/ChatWidget-MAL7FB4M.js +0 -14
  203. package/dist/ChatWidget-QPZTPSPE.mjs +0 -5
  204. package/dist/EngageWidget-IPBUA4DW.mjs +0 -5
  205. package/dist/EngageWidget-N3CABK3L.js +0 -14
  206. package/dist/SignalCore-4MDIJUAP.mjs.map +0 -1
  207. package/dist/SignalCore-ZMFUE3N4.js.map +0 -1
  208. package/dist/SitemapSync-FUCH6Y6J.js +0 -13
  209. package/dist/SitemapSync-LCSPRAWU.mjs +0 -4
  210. package/dist/chunk-3GCEFS73.js.map +0 -1
  211. package/dist/chunk-3YQCPLGP.mjs.map +0 -1
  212. package/dist/chunk-5AESOLXS.mjs.map +0 -1
  213. package/dist/chunk-65SMJBHW.mjs.map +0 -1
  214. package/dist/chunk-FMZOZQBV.mjs.map +0 -1
  215. package/dist/chunk-I2GTT2DG.js.map +0 -1
  216. package/dist/chunk-IBDVZ6BF.js.map +0 -1
  217. package/dist/chunk-IFVMZFNT.js.map +0 -1
  218. package/dist/chunk-KHFNPPOX.mjs.map +0 -1
  219. package/dist/chunk-KMZ527EM.mjs.map +0 -1
  220. package/dist/chunk-MLJXELPA.js.map +0 -1
  221. package/dist/chunk-NNZDHLXN.js.map +0 -1
  222. package/dist/chunk-QTWA44S3.mjs.map +0 -1
  223. package/dist/chunk-RI6JZUAE.js.map +0 -1
  224. package/dist/chunk-T4QBSRA4.mjs.map +0 -1
  225. package/dist/chunk-TCVHI3JP.mjs.map +0 -1
  226. package/dist/chunk-VCWSSGCU.js.map +0 -1
  227. package/dist/chunk-YJWKHCOB.js.map +0 -1
@@ -0,0 +1,259 @@
1
+ import { SITE_KIT_VERSION } from './chunk-QLM7RNQI.mjs';
2
+
3
+ // src/shared/siteToken.ts
4
+ var SITE_TOKEN_PREFIX = "st1.";
5
+ var REFRESH_SKEW_MS = 5 * 6e4;
6
+ var REFRESH_TIMEOUT_MS = 6e3;
7
+ var STORAGE_KEY = "__sonor_site_token__";
8
+ function isSiteToken(credential) {
9
+ return typeof credential === "string" && credential.startsWith(SITE_TOKEN_PREFIX);
10
+ }
11
+ function decodeSiteTokenClaims(token) {
12
+ if (!isSiteToken(token)) return null;
13
+ const seg = token.split(".")[1];
14
+ if (!seg) return null;
15
+ try {
16
+ const b64 = seg.replace(/-/g, "+").replace(/_/g, "/");
17
+ const json = typeof atob === "function" ? atob(b64) : Buffer.from(seg, "base64url").toString("utf8");
18
+ const claims = JSON.parse(json);
19
+ return typeof claims === "object" && claims ? claims : null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+ function credentialBreakerId(credential) {
25
+ if (!credential) return "(anonymous)";
26
+ const claims = decodeSiteTokenClaims(credential);
27
+ if (claims?.pid) return `pid:${claims.pid}`;
28
+ return credential.slice(0, 16);
29
+ }
30
+ var state = {
31
+ seed: void 0,
32
+ isToken: false,
33
+ apiUrl: "https://api.sonor.io",
34
+ token: null,
35
+ refreshing: null
36
+ };
37
+ function readStoredToken() {
38
+ if (typeof window === "undefined") return null;
39
+ try {
40
+ const raw = window.sessionStorage.getItem(STORAGE_KEY);
41
+ if (!raw) return null;
42
+ const parsed = JSON.parse(raw);
43
+ return typeof parsed?.token === "string" ? parsed.token : null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ function storeToken(token) {
49
+ if (typeof window === "undefined") return;
50
+ try {
51
+ window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ token }));
52
+ } catch {
53
+ }
54
+ try {
55
+ ;
56
+ window.__SITE_KIT_API_KEY__ = token;
57
+ } catch {
58
+ }
59
+ }
60
+ function tokenExp(token) {
61
+ const claims = decodeSiteTokenClaims(token);
62
+ return typeof claims?.exp === "number" ? claims.exp * 1e3 : 0;
63
+ }
64
+ function configureSiteCredential(opts) {
65
+ state.seed = opts.credential;
66
+ state.isToken = isSiteToken(opts.credential);
67
+ if (opts.apiUrl) state.apiUrl = opts.apiUrl.replace(/\/+$/, "");
68
+ if (!state.isToken) {
69
+ state.token = null;
70
+ return;
71
+ }
72
+ const prior = state.token;
73
+ let best = opts.credential ?? null;
74
+ const stored = readStoredToken();
75
+ if (stored && tokenExp(stored) > tokenExp(best)) best = stored;
76
+ if (prior && tokenExp(prior) > tokenExp(best)) best = prior;
77
+ state.token = best;
78
+ }
79
+ function needsRefresh() {
80
+ if (!state.isToken) return false;
81
+ if (!state.token) return true;
82
+ return Date.now() >= tokenExp(state.token) - REFRESH_SKEW_MS;
83
+ }
84
+ async function doRefresh() {
85
+ const current = state.token;
86
+ if (!current) return null;
87
+ try {
88
+ const res = await fetch(`${state.apiUrl}/api/public/site-token/refresh`, {
89
+ method: "POST",
90
+ headers: {
91
+ "content-type": "application/json",
92
+ "x-sitekit-version": SITE_KIT_VERSION,
93
+ // Send the current token as x-api-key too, so the server's key-scoped
94
+ // throttler buckets refreshes by token rather than by shared CDN IP.
95
+ "x-api-key": current
96
+ },
97
+ body: JSON.stringify({ token: current }),
98
+ signal: typeof AbortSignal !== "undefined" && AbortSignal.timeout ? AbortSignal.timeout(REFRESH_TIMEOUT_MS) : void 0
99
+ });
100
+ if (!res.ok) return null;
101
+ const data = await res.json().catch(() => null);
102
+ const next = data?.token;
103
+ if (typeof next === "string" && isSiteToken(next)) {
104
+ state.token = next;
105
+ storeToken(next);
106
+ return next;
107
+ }
108
+ return null;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+ function refreshSiteToken() {
114
+ if (!state.isToken) return Promise.resolve(null);
115
+ if (state.refreshing) return state.refreshing;
116
+ state.refreshing = doRefresh().finally(() => {
117
+ state.refreshing = null;
118
+ });
119
+ return state.refreshing;
120
+ }
121
+ async function getFreshCredential(seed) {
122
+ if (!state.isToken) return seed ?? state.seed;
123
+ if (needsRefresh()) {
124
+ const refreshed = await refreshSiteToken();
125
+ if (refreshed) return refreshed;
126
+ }
127
+ return state.token ?? seed ?? state.seed;
128
+ }
129
+ function isTokenMode() {
130
+ return state.isToken;
131
+ }
132
+ function currentCredential(seed) {
133
+ if (!state.isToken) return seed ?? state.seed;
134
+ return state.token ?? seed ?? state.seed;
135
+ }
136
+
137
+ // src/shared/fetchWithRetry.ts
138
+ function backoffDelay(attempt, response) {
139
+ if (response?.status === 429) {
140
+ const header = response.headers.get("retry-after");
141
+ const seconds = header ? parseInt(header, 10) : NaN;
142
+ if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1e3, 1e4);
143
+ }
144
+ return Math.min(8e3, 500 * 2 ** attempt);
145
+ }
146
+ async function fetchWithRetry(url, init, options = {}) {
147
+ const retries = options.retries ?? 2;
148
+ const timeoutMs = options.timeoutMs ?? 15e3;
149
+ let lastError;
150
+ for (let attempt = 0; attempt <= retries; attempt++) {
151
+ const controller = new AbortController();
152
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
153
+ try {
154
+ const response = await fetch(url, { ...init, signal: controller.signal });
155
+ clearTimeout(timer);
156
+ if ((response.status === 429 || response.status >= 500) && attempt < retries) {
157
+ await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)));
158
+ continue;
159
+ }
160
+ return response;
161
+ } catch (error) {
162
+ clearTimeout(timer);
163
+ lastError = error;
164
+ if (attempt < retries) {
165
+ await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)));
166
+ continue;
167
+ }
168
+ }
169
+ }
170
+ throw lastError instanceof Error ? lastError : new Error("Network request failed after multiple attempts. Please try again.");
171
+ }
172
+
173
+ // src/shared/sonorFetch.ts
174
+ var AUTH_BREAK_MS = 5 * 6e4;
175
+ var pausedKeys = /* @__PURE__ */ new Map();
176
+ var warnedThisPage = false;
177
+ function isAuthPaused(credential) {
178
+ if (typeof window === "undefined") return false;
179
+ const id = credentialBreakerId(credential);
180
+ const until = pausedKeys.get(id);
181
+ if (!until) return false;
182
+ if (Date.now() >= until) {
183
+ pausedKeys.delete(id);
184
+ return false;
185
+ }
186
+ return true;
187
+ }
188
+ function tripBreaker(credential) {
189
+ pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS);
190
+ if (!warnedThisPage) {
191
+ warnedThisPage = true;
192
+ console.info(
193
+ "[Sonor] This site couldn't authenticate with Sonor, so data features are paused for a few minutes. If this keeps happening, the site is likely running an outdated API key."
194
+ );
195
+ }
196
+ }
197
+ async function sonorFetch(url, init = {}, options = {}) {
198
+ let credential = options.apiKey;
199
+ if (credential !== void 0 || isTokenMode()) {
200
+ credential = await getFreshCredential(options.apiKey);
201
+ }
202
+ const headers = new Headers(init.headers || {});
203
+ headers.set("x-sitekit-version", SITE_KIT_VERSION);
204
+ if (credential) {
205
+ if (isTokenMode()) {
206
+ headers.set("x-api-key", credential);
207
+ headers.delete("authorization");
208
+ } else if (!headers.has("x-api-key")) {
209
+ headers.set("x-api-key", credential);
210
+ }
211
+ }
212
+ if (isAuthPaused(credential)) {
213
+ return new Response(null, { status: 401, statusText: "sonor-auth-paused" });
214
+ }
215
+ let response = await fetchWithRetry(url, { ...init, headers }, options);
216
+ if ((response.status === 401 || response.status === 403) && typeof window !== "undefined" && isSiteToken(credential)) {
217
+ const refreshed = await refreshSiteToken();
218
+ if (refreshed && refreshed !== credential) {
219
+ const retryHeaders = new Headers(init.headers || {});
220
+ retryHeaders.set("x-sitekit-version", SITE_KIT_VERSION);
221
+ retryHeaders.set("x-api-key", refreshed);
222
+ retryHeaders.delete("authorization");
223
+ credential = refreshed;
224
+ response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options);
225
+ }
226
+ }
227
+ if ((response.status === 401 || response.status === 403) && typeof window !== "undefined" && credential) {
228
+ tripBreaker(credential);
229
+ }
230
+ return response;
231
+ }
232
+ function sonorBeacon(url, payload, apiKey) {
233
+ const credential = currentCredential(apiKey);
234
+ const body = typeof payload === "string" ? payload : JSON.stringify(payload);
235
+ try {
236
+ void fetch(url, {
237
+ method: "POST",
238
+ keepalive: true,
239
+ headers: {
240
+ "Content-Type": "application/json",
241
+ "x-sitekit-version": SITE_KIT_VERSION,
242
+ ...credential ? { "x-api-key": credential } : {}
243
+ },
244
+ body
245
+ }).catch(() => {
246
+ });
247
+ } catch {
248
+ try {
249
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
250
+ navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
251
+ }
252
+ } catch {
253
+ }
254
+ }
255
+ }
256
+
257
+ export { configureSiteCredential, currentCredential, sonorBeacon, sonorFetch };
258
+ //# sourceMappingURL=chunk-SBT4J733.mjs.map
259
+ //# sourceMappingURL=chunk-SBT4J733.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/siteToken.ts","../src/shared/fetchWithRetry.ts","../src/shared/sonorFetch.ts"],"names":[],"mappings":"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","file":"chunk-SBT4J733.mjs","sourcesContent":["/**\n * @sonordev/site-kit shared — Client token manager\n *\n * SiteKitLayout (a server component) no longer ships the raw `sonor_` API key to\n * the browser. Instead it mints a short-lived, project-scoped token server-side\n * and injects THAT. This module is the browser-side lifecycle for that token:\n *\n * - holds the current token (seeded from the injected value)\n * - a static page's seed can be days old and already expired on first view, so\n * it transparently REFRESHES via the public endpoint before first use\n * - dedupes concurrent refreshes (one in-flight request, awaited by all)\n * - persists the freshest token in sessionStorage so navigations within a\n * session don't each re-refresh\n *\n * Everything here is a no-op passthrough when the injected credential is a raw\n * key (a 2.x kit, or the mint-failed fallback) — legacy behavior is unchanged.\n * Fully SSR-safe: on the server there is no refresh, just the seed.\n *\n * The token payload is NOT secret (only its HMAC is unforgeable), so decoding it\n * client-side to read `exp`/`pid` is fine and needs no crypto.\n */\nimport { SITE_KIT_VERSION } from './version'\n\nexport const SITE_TOKEN_PREFIX = 'st1.'\n\n/** Refresh this many ms before expiry, so a request never races the deadline. */\nconst REFRESH_SKEW_MS = 5 * 60_000\nconst REFRESH_TIMEOUT_MS = 6_000\nconst STORAGE_KEY = '__sonor_site_token__'\n\nexport function isSiteToken(credential?: string | null): boolean {\n return typeof credential === 'string' && credential.startsWith(SITE_TOKEN_PREFIX)\n}\n\ninterface DecodedClaims {\n pid?: string\n iat?: number\n exp?: number\n}\n\n/** Decode the (non-secret) token payload. Returns null if not a token / malformed. */\nexport function decodeSiteTokenClaims(token?: string | null): DecodedClaims | null {\n if (!isSiteToken(token)) return null\n const seg = (token as string).split('.')[1]\n if (!seg) return null\n try {\n // base64url → JSON. atob handles standard base64; restore url-safe chars.\n const b64 = seg.replace(/-/g, '+').replace(/_/g, '/')\n const json =\n typeof atob === 'function'\n ? atob(b64)\n : Buffer.from(seg, 'base64url').toString('utf8')\n const claims = JSON.parse(json)\n return typeof claims === 'object' && claims ? claims : null\n } catch {\n return null\n }\n}\n\n/**\n * Stable id for the auth circuit breaker. Tokens rotate, so keying the breaker\n * by the raw value would reset it every refresh — key by the project id encoded\n * in the token instead. Raw keys use a safe prefix.\n */\nexport function credentialBreakerId(credential?: string | null): string {\n if (!credential) return '(anonymous)'\n const claims = decodeSiteTokenClaims(credential)\n if (claims?.pid) return `pid:${claims.pid}`\n return credential.slice(0, 16)\n}\n\ninterface TokenState {\n /** The credential injected by the layout (token or raw key). */\n seed: string | undefined\n /** Whether we're in token mode (seed is a token). */\n isToken: boolean\n /** Base API URL for the refresh endpoint. */\n apiUrl: string\n /** The freshest known token (>= seed). Null in raw-key mode. */\n token: string | null\n /** In-flight refresh, deduped. */\n refreshing: Promise<string | null> | null\n}\n\nconst state: TokenState = {\n seed: undefined,\n isToken: false,\n apiUrl: 'https://api.sonor.io',\n token: null,\n refreshing: null,\n}\n\nfunction readStoredToken(): string | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.sessionStorage.getItem(STORAGE_KEY)\n if (!raw) return null\n const parsed = JSON.parse(raw)\n return typeof parsed?.token === 'string' ? parsed.token : null\n } catch {\n return null\n }\n}\n\nfunction storeToken(token: string): void {\n if (typeof window === 'undefined') return\n try {\n window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ token }))\n } catch {\n /* private mode / disabled storage — memory-only is fine */\n }\n try {\n ;(window as any).__SITE_KIT_API_KEY__ = token\n } catch {\n /* ignore */\n }\n}\n\nfunction tokenExp(token: string | null): number {\n const claims = decodeSiteTokenClaims(token)\n return typeof claims?.exp === 'number' ? claims.exp * 1000 : 0\n}\n\n/**\n * Configure the manager from the layout-injected values. Adopts a fresher token\n * from sessionStorage if a prior page in this session already refreshed.\n */\nexport function configureSiteCredential(opts: {\n credential?: string\n apiUrl?: string\n}): void {\n state.seed = opts.credential\n state.isToken = isSiteToken(opts.credential)\n if (opts.apiUrl) state.apiUrl = opts.apiUrl.replace(/\\/+$/, '')\n\n if (!state.isToken) {\n state.token = null\n return\n }\n\n // configure runs on every render — never downgrade. Keep the freshest of the\n // injected seed, a token stored by an earlier page this session, and whatever\n // an in-flight/prior refresh already put in memory.\n const prior = state.token\n let best = opts.credential ?? null\n const stored = readStoredToken()\n if (stored && tokenExp(stored) > tokenExp(best)) best = stored\n if (prior && tokenExp(prior) > tokenExp(best)) best = prior\n state.token = best\n}\n\nfunction needsRefresh(): boolean {\n if (!state.isToken) return false\n if (!state.token) return true\n return Date.now() >= tokenExp(state.token) - REFRESH_SKEW_MS\n}\n\nasync function doRefresh(): Promise<string | null> {\n const current = state.token\n if (!current) return null\n try {\n const res = await fetch(`${state.apiUrl}/api/public/site-token/refresh`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n // Send the current token as x-api-key too, so the server's key-scoped\n // throttler buckets refreshes by token rather than by shared CDN IP.\n 'x-api-key': current,\n },\n body: JSON.stringify({ token: current }),\n signal:\n typeof AbortSignal !== 'undefined' && AbortSignal.timeout\n ? AbortSignal.timeout(REFRESH_TIMEOUT_MS)\n : undefined,\n })\n if (!res.ok) return null\n const data = await res.json().catch(() => null)\n const next = data?.token\n if (typeof next === 'string' && isSiteToken(next)) {\n state.token = next\n storeToken(next)\n return next\n }\n return null\n } catch {\n return null\n }\n}\n\n/** Refresh now (deduped). Returns the new token, or null on failure. */\nexport function refreshSiteToken(): Promise<string | null> {\n if (!state.isToken) return Promise.resolve(null)\n if (state.refreshing) return state.refreshing\n state.refreshing = doRefresh().finally(() => {\n state.refreshing = null\n })\n return state.refreshing\n}\n\n/**\n * The credential to put on the next request. In token mode, refreshes a stale\n * seed first. In raw-key mode, returns the seed unchanged (legacy passthrough).\n */\nexport async function getFreshCredential(\n seed?: string,\n): Promise<string | undefined> {\n // A raw key (or nothing configured) — no token machinery.\n if (!state.isToken) return seed ?? state.seed\n if (needsRefresh()) {\n const refreshed = await refreshSiteToken()\n if (refreshed) return refreshed\n // Refresh failed — fall through to whatever we have; the request may 401 and\n // trip the breaker with friendly copy.\n }\n return state.token ?? seed ?? state.seed\n}\n\n/** Whether the given credential is being managed as a token. */\nexport function isTokenMode(): boolean {\n return state.isToken\n}\n\n/**\n * Best-known credential WITHOUT refreshing — for synchronous unload paths\n * (sonorBeacon) that can't await. In token mode returns the freshest token held;\n * in raw-key mode returns the seed.\n */\nexport function currentCredential(seed?: string): string | undefined {\n if (!state.isToken) return seed ?? state.seed\n return state.token ?? seed ?? state.seed\n}\n","/**\n * @sonordev/site-kit shared - Resilient fetch\n *\n * Single source of truth for client-side resilient fetch (forms, SitemapSync).\n * Adds a per-attempt timeout and retries transient failures (network errors,\n * request timeouts, HTTP 429, and 5xx) with exponential backoff.\n *\n * A form submission must survive a brief API blip — a deploy, a cold start, a\n * dropped packet. A single un-retried fetch turning into \"Something went wrong\"\n * is how leads get lost. The explicit abort also matters on its own: a request\n * left to hang until the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices; an aborted fetch\n * logs nothing.\n */\n\nexport interface FetchRetryOptions {\n /** Number of retries AFTER the first attempt (default 2 → up to 3 tries total). */\n retries?: number\n /** Per-attempt timeout in milliseconds (default 15000). */\n timeoutMs?: number\n}\n\nfunction backoffDelay(attempt: number, response?: Response): number {\n // Honor a Retry-After header on 429 responses when present.\n if (response?.status === 429) {\n const header = response.headers.get('retry-after')\n const seconds = header ? parseInt(header, 10) : NaN\n if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1000, 10000)\n }\n // Exponential backoff: 500ms, 1s, 2s, ... capped at 8s.\n return Math.min(8000, 500 * 2 ** attempt)\n}\n\n/**\n * fetch() with a timeout and automatic retries for transient failures.\n *\n * Non-transient responses (2xx, 3xx, and 4xx other than 429) are returned to\n * the caller as-is — only 429/5xx and thrown network/timeout errors are retried.\n * If every attempt fails with a thrown error, the last error is re-thrown.\n */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n options: FetchRetryOptions = {},\n): Promise<Response> {\n const retries = options.retries ?? 2\n const timeoutMs = options.timeoutMs ?? 15000\n let lastError: unknown\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await fetch(url, { ...init, signal: controller.signal })\n clearTimeout(timer)\n\n if ((response.status === 429 || response.status >= 500) && attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)))\n continue\n }\n return response\n } catch (error) {\n clearTimeout(timer)\n lastError = error\n // Network failure or timeout (AbortController) — retry while attempts remain.\n if (attempt < retries) {\n await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)))\n continue\n }\n }\n }\n\n throw lastError instanceof Error\n ? lastError\n : new Error('Network request failed after multiple attempts. Please try again.')\n}\n","/**\n * @sonordev/site-kit shared - Canonical client transport\n *\n * Single source of truth for how site-kit talks to Sonor from the browser.\n * Every client-side module (analytics, forms, commerce, engage, maps, images,\n * SitemapSync) routes through here instead of bare fetch(), which gives the\n * whole kit, in one place:\n *\n * - per-attempt timeout + retry with backoff (via fetchWithRetry) — a hung\n * request left to the browser's own network timeout logs a console error\n * (net::ERR_TIMED_OUT) that fails Lighthouse best-practices\n * - the x-sitekit-version header on every request, so the platform can see\n * which kit version each site actually runs from live traffic\n * - CREDENTIAL LIFECYCLE: sites on 3.2+ ship a short-lived minted token instead\n * of the raw key. This transport resolves the freshest token (refreshing a\n * stale seed transparently), and on a 401/403 for a token it refreshes once\n * and retries before giving up. A raw key (2.x kit, or the mint-failed\n * fallback) is a pure passthrough — legacy behavior is unchanged.\n * - an auth circuit breaker: after a 401/403 the credential is paused for a few\n * minutes and ONE friendly console.info is emitted — a site on a stale or\n * canceled key must not spam the API or the visitor's console. Keyed by the\n * project (from the token) so it survives token rotation.\n * - sonorBeacon for unload-time telemetry that keeps the credential in headers;\n * navigator.sendBeacon can't set headers and forced the key into the URL,\n * where server and CDN access logs capture it\n *\n * Non-idempotent endpoints (checkout, payment intents, uploads) must pass\n * { retries: 0 } — they still get the timeout, but never an automatic retry.\n */\nimport { fetchWithRetry, type FetchRetryOptions } from './fetchWithRetry'\nimport { SITE_KIT_VERSION } from './version'\nimport {\n credentialBreakerId,\n currentCredential,\n getFreshCredential,\n isSiteToken,\n isTokenMode,\n refreshSiteToken,\n} from './siteToken'\n\nexport interface SonorFetchOptions extends FetchRetryOptions {\n /** Project credential — the minted token (3.2+) or raw key. Sent as x-api-key. */\n apiKey?: string\n}\n\n/** How long a credential stays paused after an auth failure. */\nconst AUTH_BREAK_MS = 5 * 60_000\n\n// breaker id → epoch ms until which requests are paused. Module-scoped: the\n// breaker lives for the page's lifetime and never applies on the server.\nconst pausedKeys = new Map<string, number>()\nlet warnedThisPage = false\n\n/** True when a previous 401/403 has paused this credential (browser only). */\nexport function isAuthPaused(credential?: string): boolean {\n if (typeof window === 'undefined') return false\n const id = credentialBreakerId(credential)\n const until = pausedKeys.get(id)\n if (!until) return false\n if (Date.now() >= until) {\n pausedKeys.delete(id)\n return false\n }\n return true\n}\n\nfunction tripBreaker(credential?: string): void {\n pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS)\n if (!warnedThisPage) {\n warnedThisPage = true\n // info, not error: a visitor's console must stay clean (Lighthouse\n // errors-in-console), and copy never names env vars or key formats.\n console.info(\n \"[Sonor] This site couldn't authenticate with Sonor, so data features are paused for a few minutes. If this keeps happening, the site is likely running an outdated API key.\",\n )\n }\n}\n\nexport async function sonorFetch(\n url: string,\n init: RequestInit = {},\n options: SonorFetchOptions = {},\n): Promise<Response> {\n // Resolve the credential to send. In token mode this may transparently refresh\n // a stale seed before the first request; in raw-key mode it's a passthrough.\n let credential = options.apiKey\n if (credential !== undefined || isTokenMode()) {\n credential = await getFreshCredential(options.apiKey)\n }\n\n const headers = new Headers(init.headers || {})\n headers.set('x-sitekit-version', SITE_KIT_VERSION)\n if (credential) {\n if (isTokenMode()) {\n // Token mode: `credential` is the freshest token (refreshed just above if\n // the seed was stale). It MUST override any x-api-key a caller hand-set — a\n // stale build-time seed passed as a header would otherwise pin the request\n // and defeat refresh entirely. Also drop a caller-set Authorization:\n // extractApiKey() server-side prefers `Authorization: Bearer` over\n // x-api-key, so a stale Bearer seed would win even once we set a fresh\n // x-api-key. This is the single place the client credential is chosen — do\n // not re-attach it as a header at call sites.\n headers.set('x-api-key', credential)\n headers.delete('authorization')\n } else if (!headers.has('x-api-key')) {\n // Raw-key mode: legacy passthrough — respect a caller-supplied header.\n headers.set('x-api-key', credential)\n }\n }\n\n if (isAuthPaused(credential)) {\n // Synthetic response keeps every caller's !response.ok path working\n // without a network round trip while the breaker is open.\n return new Response(null, { status: 401, statusText: 'sonor-auth-paused' })\n }\n\n let response = await fetchWithRetry(url, { ...init, headers }, options)\n\n // A token can rotate or expire mid-session. On the first auth failure, try ONE\n // refresh + retry before tripping the breaker — only for token credentials\n // (raw keys have nothing to refresh).\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n isSiteToken(credential)\n ) {\n const refreshed = await refreshSiteToken()\n if (refreshed && refreshed !== credential) {\n const retryHeaders = new Headers(init.headers || {})\n retryHeaders.set('x-sitekit-version', SITE_KIT_VERSION)\n retryHeaders.set('x-api-key', refreshed)\n // Same reason as the initial request: a caller-set stale Bearer would win\n // over our fresh x-api-key server-side, so strip it on the retry too.\n retryHeaders.delete('authorization')\n credential = refreshed\n response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options)\n }\n }\n\n if (\n (response.status === 401 || response.status === 403) &&\n typeof window !== 'undefined' &&\n credential\n ) {\n tripBreaker(credential)\n }\n\n return response\n}\n\n/**\n * Fire-and-forget telemetry that survives page unload.\n *\n * Uses fetch({ keepalive: true }) so auth travels in headers. Falls back to\n * navigator.sendBeacon (no auth headers possible — the server may reject it,\n * but we never put the key in the URL) only when keepalive fetch is missing.\n *\n * Cannot await a refresh at unload time, so it sends the freshest token already\n * held (currentCredential) rather than the possibly-stale seed the caller passed.\n */\nexport function sonorBeacon(url: string, payload: unknown, apiKey?: string): void {\n const credential = currentCredential(apiKey)\n const body = typeof payload === 'string' ? payload : JSON.stringify(payload)\n try {\n void fetch(url, {\n method: 'POST',\n keepalive: true,\n headers: {\n 'Content-Type': 'application/json',\n 'x-sitekit-version': SITE_KIT_VERSION,\n ...(credential ? { 'x-api-key': credential } : {}),\n },\n body,\n }).catch(() => {\n /* page is closing — best effort */\n })\n } catch {\n try {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n }\n } catch {\n /* nothing left to try */\n }\n }\n}\n"]}
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
- var chunkIBDVZ6BF_js = require('./chunk-IBDVZ6BF.js');
4
+ var chunkMX6DK246_js = require('./chunk-MX6DK246.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 } = chunkIBDVZ6BF_js.useAnalytics();
9
+ const { trackConversion: trackConversion2 } = chunkMX6DK246_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-EBJ4S5DU.js.map
72
- //# sourceMappingURL=chunk-EBJ4S5DU.js.map
71
+ //# sourceMappingURL=chunk-SGPIGHMN.js.map
72
+ //# sourceMappingURL=chunk-SGPIGHMN.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/analytics/useContactTracking.ts"],"names":["trackConversion","useAnalytics","useCallback","useEffect"],"mappings":"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","file":"chunk-EBJ4S5DU.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"]}
1
+ {"version":3,"sources":["../src/analytics/useContactTracking.ts"],"names":["trackConversion","useAnalytics","useCallback","useEffect"],"mappings":";;;;;AAyCO,SAAS,kBAAA,CAAmB,OAAA,GAAqC,EAAC,EAA0B;AACjG,EAAA,MAAM,EAAE,SAAA,GAAY,IAAA,EAAM,KAAA,GAAQ,OAAM,GAAI,OAAA;AAC5C,EAAA,MAAM,EAAE,eAAA,EAAAA,gBAAAA,EAAgB,GAAIC,6BAAA,EAAa;AAEzC,EAAA,MAAM,eAAA,GAAkBC,iBAAA,CAAY,CAAC,WAAA,EAAqB,QAAA,KAAuC;AAC/F,IAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,0BAAA,EAA4B,WAAW,CAAA;AAE9D,IAAAF,gBAAAA,CAAgB;AAAA,MACd,IAAA,EAAM,YAAA;AAAA,MACN,QAAA,EAAU;AAAA,QACR,WAAA,EAAa,WAAA,CAAY,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAAA;AAAA,QAC1C,SAAA,EAAW,WAAA;AAAA,QACX,GAAG;AAAA;AACL,KACD,CAAA;AAAA,EACH,CAAA,EAAG,CAACA,gBAAAA,EAAiB,KAAK,CAAC,CAAA;AAE3B,EAAA,MAAM,eAAA,GAAkBE,iBAAA,CAAY,CAAC,KAAA,EAAe,QAAA,KAAuC;AACzF,IAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,0BAAA,EAA4B,KAAK,CAAA;AAExD,IAAAF,gBAAAA,CAAgB;AAAA,MACd,IAAA,EAAM,aAAA;AAAA,MACN,QAAA,EAAU;AAAA,QACR,KAAA;AAAA,QACA,GAAG;AAAA;AACL,KACD,CAAA;AAAA,EACH,CAAA,EAAG,CAACA,gBAAAA,EAAiB,KAAK,CAAC,CAAA;AAG3B,EAAAG,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,SAAA,IAAa,OAAO,QAAA,KAAa,WAAA,EAAa;AAEnD,IAAA,MAAM,WAAA,GAAc,CAAC,CAAA,KAAkB;AACrC,MAAA,MAAM,SAAS,CAAA,CAAE,MAAA;AACjB,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA;AACjC,MAAA,IAAI,CAAC,MAAA,EAAQ;AAEb,MAAA,MAAM,IAAA,GAAO,MAAA,CAAO,YAAA,CAAa,MAAM,CAAA;AACvC,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,IAAI,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA,EAAG;AAC3B,QAAA,MAAM,QAAQ,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,EAAE,IAAA,EAAK;AAC5C,QAAA,eAAA,CAAgB,KAAA,EAAO;AAAA,UACrB,QAAA,EAAU,MAAA,CAAO,WAAA,EAAa,IAAA,EAAK;AAAA,UACnC,MAAA,EAAQ;AAAA,SACT,CAAA;AAAA,MACH,CAAA,MAAA,IAAW,IAAA,CAAK,UAAA,CAAW,SAAS,CAAA,EAAG;AACrC,QAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,CAAE,IAAA,EAAK;AAC7D,QAAA,eAAA,CAAgB,KAAA,EAAO;AAAA,UACrB,QAAA,EAAU,MAAA,CAAO,WAAA,EAAa,IAAA,EAAK;AAAA,UACnC,MAAA,EAAQ;AAAA,SACT,CAAA;AAAA,MACH;AAAA,IACF,CAAA;AAEA,IAAA,QAAA,CAAS,iBAAiB,OAAA,EAAS,WAAA,EAAa,EAAE,OAAA,EAAS,MAAM,CAAA;AAEjE,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,oBAAoB,OAAA,EAAS,WAAA,EAAa,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,IACtE,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,SAAA,EAAW,eAAA,EAAiB,eAAe,CAAC,CAAA;AAEhD,EAAA,OAAO;AAAA,IACL,eAAA;AAAA,IACA;AAAA,GACF;AACF;AAWO,SAAS,eAAA,CAAgB,EAAE,KAAA,GAAQ,KAAA,EAAM,EAAwB;AACtE,EAAA,kBAAA,CAAmB,EAAE,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,CAAA;AAC7C,EAAA,OAAO,IAAA;AACT","file":"chunk-SGPIGHMN.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"]}
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ // src/forms/contract.ts
4
+ var FORMS_HONEYPOT_CONTRACT_VERSION = 1;
5
+ var DEFAULT_HONEYPOT_FIELD = "_sk_hp_url";
6
+ var GENERIC_HONEYPOT_FIELDS = Object.freeze([
7
+ DEFAULT_HONEYPOT_FIELD,
8
+ "website",
9
+ "_hp_field",
10
+ "bot-field",
11
+ "bot_field"
12
+ ]);
13
+ function resolveHoneypotFieldNames(opts) {
14
+ const names = /* @__PURE__ */ new Set();
15
+ if (!opts.enabled) return names;
16
+ const real = new Set(opts.realFieldSlugs ?? []);
17
+ for (const generic of GENERIC_HONEYPOT_FIELDS) {
18
+ if (!real.has(generic)) names.add(generic);
19
+ }
20
+ if (opts.formHoneypotField) names.add(opts.formHoneypotField);
21
+ return names;
22
+ }
23
+ function isHoneypotTriggered(data, fieldNames) {
24
+ for (const fieldName of fieldNames) {
25
+ const value = data?.[fieldName];
26
+ if (value == null) continue;
27
+ if (typeof value !== "string" || value.trim() !== "") return true;
28
+ }
29
+ return false;
30
+ }
31
+ var HONEYPOT_CONFIG_JSON_SCHEMA = {
32
+ $schema: "http://json-schema.org/draft-07/schema#",
33
+ $id: "https://sonor.io/schemas/honeypot-config.v1.json",
34
+ title: "HoneypotConfig",
35
+ description: "The honeypot slice of a public managed-form config.",
36
+ type: "object",
37
+ properties: {
38
+ honeypot_enabled: { type: "boolean" },
39
+ honeypot_field: {
40
+ description: `Trap field name; defaults to "${DEFAULT_HONEYPOT_FIELD}" when unset.`,
41
+ type: "string"
42
+ }
43
+ },
44
+ additionalProperties: true
45
+ };
46
+
47
+ exports.DEFAULT_HONEYPOT_FIELD = DEFAULT_HONEYPOT_FIELD;
48
+ exports.FORMS_HONEYPOT_CONTRACT_VERSION = FORMS_HONEYPOT_CONTRACT_VERSION;
49
+ exports.GENERIC_HONEYPOT_FIELDS = GENERIC_HONEYPOT_FIELDS;
50
+ exports.HONEYPOT_CONFIG_JSON_SCHEMA = HONEYPOT_CONFIG_JSON_SCHEMA;
51
+ exports.isHoneypotTriggered = isHoneypotTriggered;
52
+ exports.resolveHoneypotFieldNames = resolveHoneypotFieldNames;
53
+ //# sourceMappingURL=chunk-V6NVBFEP.js.map
54
+ //# sourceMappingURL=chunk-V6NVBFEP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/forms/contract.ts"],"names":[],"mappings":";;;AAkBO,IAAM,+BAAA,GAAkC;AAOxC,IAAM,sBAAA,GAAyB;AAO/B,IAAM,uBAAA,GAA6C,OAAO,MAAA,CAAO;AAAA,EACtE,sBAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAC;AAQM,SAAS,0BAA0B,IAAA,EAI1B;AACd,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAY;AAC9B,EAAA,IAAI,CAAC,IAAA,CAAK,OAAA,EAAS,OAAO,KAAA;AAC1B,EAAA,MAAM,OAAO,IAAI,GAAA,CAAI,IAAA,CAAK,cAAA,IAAkB,EAAE,CAAA;AAC9C,EAAA,KAAA,MAAW,WAAW,uBAAA,EAAyB;AAC7C,IAAA,IAAI,CAAC,IAAA,CAAK,GAAA,CAAI,OAAO,CAAA,EAAG,KAAA,CAAM,IAAI,OAAO,CAAA;AAAA,EAC3C;AACA,EAAA,IAAI,IAAA,CAAK,iBAAA,EAAmB,KAAA,CAAM,GAAA,CAAI,KAAK,iBAAiB,CAAA;AAC5D,EAAA,OAAO,KAAA;AACT;AAMO,SAAS,mBAAA,CACd,MACA,UAAA,EACS;AACT,EAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,IAAA,MAAM,KAAA,GAAQ,OAAO,SAAS,CAAA;AAC9B,IAAA,IAAI,SAAS,IAAA,EAAM;AAGnB,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,MAAM,IAAA,EAAK,KAAM,IAAI,OAAO,IAAA;AAAA,EAC/D;AACA,EAAA,OAAO,KAAA;AACT;AAMO,IAAM,2BAAA,GAA8B;AAAA,EACzC,OAAA,EAAS,yCAAA;AAAA,EACT,GAAA,EAAK,kDAAA;AAAA,EACL,KAAA,EAAO,gBAAA;AAAA,EACP,WAAA,EAAa,qDAAA;AAAA,EACb,IAAA,EAAM,QAAA;AAAA,EACN,UAAA,EAAY;AAAA,IACV,gBAAA,EAAkB,EAAE,IAAA,EAAM,SAAA,EAAU;AAAA,IACpC,cAAA,EAAgB;AAAA,MACd,WAAA,EAAa,iCAAiC,sBAAsB,CAAA,aAAA,CAAA;AAAA,MACpE,IAAA,EAAM;AAAA;AACR,GACF;AAAA,EACA,oBAAA,EAAsB;AACxB","file":"chunk-V6NVBFEP.js","sourcesContent":["/**\n * @sonordev/site-kit/forms/contract — managed-form honeypot policy (v1)\n *\n * Single source of truth for the honeypot trap-field naming + server-side\n * detection policy, shared by the client (site-kit emits the trap field) and\n * the server (sonor-api names the trap in the form config it returns AND\n * detects hits on intake). Same pattern as the other site-kit contracts:\n * sonor-api keeps a byte-identical inline mirror\n * (`src/modules/forms/honeypot-contract.ts`) guarded by the shared drift-test\n * vectors in `contract.test.ts`.\n *\n * The default field name (`_sk_hp_url`) is deliberately obscure: a common name\n * like \"website\" collides with real form fields and flags genuine submissions\n * as spam, so generic names are only trusted when they DON'T match a real\n * field of the form (see `resolveHoneypotFieldNames`).\n */\n\n/** Bump on breaking changes to the honeypot policy. */\nexport const FORMS_HONEYPOT_CONTRACT_VERSION = 1 as const\n\n/**\n * Default honeypot field name emitted by site-kit and used by the server as the\n * fallback trap name when a form has no explicitly configured `honeypot_field`.\n * Deliberately obscure to avoid colliding with real form fields.\n */\nexport const DEFAULT_HONEYPOT_FIELD = '_sk_hp_url'\n\n/**\n * Generic trap names the server also watches on intake — the canonical default\n * plus legacy names still emitted by older deployed forms. Each is only trusted\n * when it doesn't collide with a real configured field of the form.\n */\nexport const GENERIC_HONEYPOT_FIELDS: readonly string[] = Object.freeze([\n DEFAULT_HONEYPOT_FIELD,\n 'website',\n '_hp_field',\n 'bot-field',\n 'bot_field',\n])\n\n/**\n * Build the set of field names the server treats as honeypot traps for one form.\n * Generic names that collide with a real field slug are dropped (or every real\n * lead using that field would be flagged); the form's dedicated\n * `honeypot_field` is always honored. Returns an empty set when disabled.\n */\nexport function resolveHoneypotFieldNames(opts: {\n enabled: boolean\n formHoneypotField?: string | null\n realFieldSlugs?: Iterable<string>\n}): Set<string> {\n const names = new Set<string>()\n if (!opts.enabled) return names\n const real = new Set(opts.realFieldSlugs ?? [])\n for (const generic of GENERIC_HONEYPOT_FIELDS) {\n if (!real.has(generic)) names.add(generic)\n }\n if (opts.formHoneypotField) names.add(opts.formHoneypotField)\n return names\n}\n\n/**\n * True when any watched trap field carries a non-empty value — the marker of an\n * automated submission (real users never see or fill the trap).\n */\nexport function isHoneypotTriggered(\n data: Record<string, unknown> | null | undefined,\n fieldNames: Iterable<string>,\n): boolean {\n for (const fieldName of fieldNames) {\n const value = data?.[fieldName]\n if (value == null) continue\n // A filled trap is almost always a non-empty string; any non-string value a\n // bot injected (number/object/bool) also counts as filled.\n if (typeof value !== 'string' || value.trim() !== '') return true\n }\n return false\n}\n\n/**\n * Agent-native contract: JSON Schema for the honeypot slice of the public form\n * config, so services and agents can validate it mechanically.\n */\nexport const HONEYPOT_CONFIG_JSON_SCHEMA = {\n $schema: 'http://json-schema.org/draft-07/schema#',\n $id: 'https://sonor.io/schemas/honeypot-config.v1.json',\n title: 'HoneypotConfig',\n description: 'The honeypot slice of a public managed-form config.',\n type: 'object',\n properties: {\n honeypot_enabled: { type: 'boolean' },\n honeypot_field: {\n description: `Trap field name; defaults to \"${DEFAULT_HONEYPOT_FIELD}\" when unset.`,\n type: 'string',\n },\n },\n additionalProperties: true,\n} as const\n"]}
@@ -1,21 +1,24 @@
1
1
  'use client';
2
+ import { FleetHeartbeat } from './chunk-E4TWVVSW.mjs';
2
3
  import { useDeferredActivation } from './chunk-ZVBLXXNJ.mjs';
3
- import { SignalBridge } from './chunk-YFGKIKXA.mjs';
4
+ import { normalizeSiteHost } from './chunk-X4OPM3SK.mjs';
5
+ import { SignalBridge } from './chunk-MKZHVSKL.mjs';
4
6
  import { getOrCreateVisitorId, getOrCreateSessionId, SiteKitIdentityProvider } from './chunk-EE6SFAZP.mjs';
7
+ import { configureSiteCredential, currentCredential } from './chunk-SBT4J733.mjs';
5
8
  import { useState, useRef, useCallback, Suspense } from 'react';
6
9
  import dynamic from 'next/dynamic';
7
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
11
 
9
12
  var LazyAnalyticsProvider = dynamic(
10
- () => import('./AnalyticsProvider-MLT26JRP.mjs').then((m) => ({ default: m.AnalyticsProvider })),
13
+ () => import('./AnalyticsProvider-LEXGFM2K.mjs').then((m) => ({ default: m.AnalyticsProvider })),
11
14
  { ssr: false }
12
15
  );
13
16
  var LazyEngageWidget = dynamic(
14
- () => import('./EngageWidget-IPBUA4DW.mjs').then((m) => ({ default: m.EngageWidget })),
17
+ () => import('./EngageWidget-2FMKYJJT.mjs').then((m) => ({ default: m.EngageWidget })),
15
18
  { ssr: false }
16
19
  );
17
20
  var LazySitemapSync = dynamic(
18
- () => import('./SitemapSync-LCSPRAWU.mjs').then((m) => ({ default: m.SitemapSync })),
21
+ () => import('./SitemapSync-T6PIDW64.mjs').then((m) => ({ default: m.SitemapSync })),
19
22
  { ssr: false }
20
23
  );
21
24
  function SiteKitClientProviders({
@@ -27,6 +30,7 @@ function SiteKitClientProviders({
27
30
  engage = true,
28
31
  signal = false,
29
32
  sitemapSync = true,
33
+ fleet = true,
30
34
  defer = true,
31
35
  debug = false
32
36
  }) {
@@ -44,10 +48,11 @@ function SiteKitClientProviders({
44
48
  if (!resolvedSite && typeof window !== "undefined") {
45
49
  resolvedSite = window.location.host;
46
50
  }
47
- const site = resolvedSite;
51
+ const site = normalizeSiteHost(resolvedSite) ?? "";
48
52
  if (typeof window !== "undefined") {
53
+ configureSiteCredential({ credential: apiKey, apiUrl });
49
54
  window.__SITE_KIT_API_URL__ = apiUrl;
50
- window.__SITE_KIT_API_KEY__ = apiKey;
55
+ window.__SITE_KIT_API_KEY__ = currentCredential(apiKey);
51
56
  window.__SITE_KIT_DEBUG__ = debug;
52
57
  window.__SITE_KIT_SITE__ = site;
53
58
  }
@@ -120,9 +125,30 @@ function SiteKitClientProviders({
120
125
  modulesReady && /* @__PURE__ */ jsx(LazySitemapSync, { debug })
121
126
  ] });
122
127
  }
128
+ if (fleet) {
129
+ const activeModules = [];
130
+ if (analytics) activeModules.push("analytics");
131
+ if (engage) activeModules.push("engage");
132
+ if (signal) activeModules.push("signal");
133
+ if (sitemapSync) activeModules.push("sitemapSync");
134
+ content = /* @__PURE__ */ jsxs(Fragment, { children: [
135
+ content,
136
+ modulesReady && /* @__PURE__ */ jsx(
137
+ FleetHeartbeat,
138
+ {
139
+ apiUrl,
140
+ apiKey,
141
+ site,
142
+ modules: activeModules,
143
+ defer: false,
144
+ debug
145
+ }
146
+ )
147
+ ] });
148
+ }
123
149
  return /* @__PURE__ */ jsx(SiteKitIdentityProvider, { visitorId, sessionId, children: content });
124
150
  }
125
151
 
126
152
  export { SiteKitClientProviders };
127
- //# sourceMappingURL=chunk-KMZ527EM.mjs.map
128
- //# sourceMappingURL=chunk-KMZ527EM.mjs.map
153
+ //# sourceMappingURL=chunk-VTHCUHMK.mjs.map
154
+ //# sourceMappingURL=chunk-VTHCUHMK.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/layout/SiteKitClientProviders.tsx"],"names":[],"mappings":"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","file":"chunk-VTHCUHMK.mjs","sourcesContent":["'use client'\n\n/**\n * SiteKitClientProviders — Client Island\n *\n * This is the 'use client' boundary for SiteKitLayout.\n * It composes the client-only providers (Analytics, Engage, Signal, SitemapSync)\n * that require React context or browser APIs.\n *\n * All modules are lazy-loaded via next/dynamic so their JS is only downloaded\n * when the feature is enabled — keeping the initial bundle lightweight.\n *\n * All modules share a unified identity (visitor ID + session ID) via\n * SiteKitIdentityProvider, ensuring consistent tracking across Analytics,\n * Signal, and Engage.\n *\n * Props are passed down from the server-side SiteKitLayout component,\n * which reads env vars at render time.\n */\n\nimport React, { Suspense, useCallback, useRef, useState, type ReactNode } from 'react'\nimport dynamic from 'next/dynamic'\nimport {\n SiteKitIdentityProvider,\n getOrCreateVisitorId,\n getOrCreateSessionId,\n} from '../shared/identity'\nimport { normalizeSiteHost } from '../sites/contract'\n// Defer non-critical module mounting until the page is quiet: the lazy\n// modules below are dynamic(ssr:false) and never render on the server, but\n// rendering them during hydration still kicks off their chunk downloads +\n// mount effects inside the critical startup window. useDeferredActivation\n// holds them until window.load + idle or first interaction — the same\n// strategy as the hand-rolled per-site deferrals this replaces (upforge\n// `deferred-analytics.tsx`, TPS `DeferredAnalytics.tsx`). The hook is public\n// (`@sonordev/site-kit/client`) so sites can reuse it for their own scripts.\nimport { useDeferredActivation } from '../shared/defer'\nimport { configureSiteCredential, currentCredential } from '../shared/siteToken'\nimport type { AnalyticsConfig, EngageConfig, SignalConfig } from './types'\n\n// Lazy-load feature modules — JS only downloaded when feature is enabled\nconst LazyAnalyticsProvider = dynamic(\n () => import('../analytics/AnalyticsProvider').then(m => ({ default: m.AnalyticsProvider })),\n { ssr: false },\n)\nconst LazyEngageWidget = dynamic(\n () => import('../engage/EngageWidget').then(m => ({ default: m.EngageWidget })),\n { ssr: false },\n)\nconst LazySitemapSync = dynamic(\n () => import('../SitemapSync').then(m => ({ default: m.SitemapSync })),\n { ssr: false },\n)\n// SignalBridge is imported STATICALLY, not via dynamic(ssr:false): it is a\n// tiny SSR-safe context shell that renders children immediately and\n// lazy-loads its machinery (SignalCore) as a childless sibling internally.\n// Wrapping children in a dynamic(ssr:false) component was the old SSR-bailout\n// failure mode — do not reintroduce it.\nimport { SignalBridge } from '../signal/SignalBridge'\n// Fleet heartbeat: a deferred, CHILDLESS SIBLING (like Analytics/Engage) that\n// reports this site's kit version + active modules once per session, so the\n// platform can see version drift across the fleet. Tiny and SSR-safe — renders\n// null until the shared idle gate fires; never wraps page content.\nimport { FleetHeartbeat } from '../fleet/FleetHeartbeat'\nimport type { FleetModule } from '../fleet/contract'\n\nexport interface SiteKitClientProvidersProps {\n children: ReactNode\n apiKey: string\n apiUrl: string\n projectId?: string\n analytics?: boolean | AnalyticsConfig\n engage?: boolean | EngageConfig\n signal?: boolean | SignalConfig\n sitemapSync?: boolean\n /**\n * Fleet heartbeat — reports kit version + active modules to Sonor once per\n * session so the platform can track version drift and key state across every\n * site. Default true. Deferred exactly like Analytics; never in the hydration\n * path, carries no visitor data.\n */\n fleet?: boolean\n /**\n * Defer Analytics / Engage / SitemapSync until window load + idle or first\n * interaction (default true). Pass false to restore legacy eager mounting\n * on hydration. SignalBridge is never deferred — it must apply experiment\n * variants as early as possible.\n */\n defer?: boolean\n debug?: boolean\n}\n\nexport function SiteKitClientProviders({\n children,\n apiKey,\n apiUrl,\n projectId,\n analytics = true,\n engage = true,\n signal = false,\n sitemapSync = true,\n fleet = true,\n defer = true,\n debug = false,\n}: SiteKitClientProvidersProps) {\n // One shared activation gate for all deferred siblings, so they mount in a\n // single commit instead of staggered re-renders.\n const modulesReady = useDeferredActivation(defer)\n // Hoist analytics config so the `site` dimension is resolvable up here.\n const analyticsConfig: AnalyticsConfig =\n typeof analytics === 'object' ? analytics : {}\n\n // Resolve the sub-site identifier — the dimension that lets one Sonor project\n // host many sites (e.g. truepowersystems.com + ohiopowerstudies.com under one\n // TPS project). Precedence: explicit `analytics.site` > NEXT_PUBLIC_SITE_URL\n // host > window.location.host. Always a string (possibly empty).\n const explicitSite = analyticsConfig.site\n const envSiteUrl =\n typeof process !== 'undefined'\n ? process.env.NEXT_PUBLIC_SITE_URL\n : undefined\n let resolvedSite = explicitSite ?? ''\n if (!resolvedSite && envSiteUrl) {\n try {\n resolvedSite = new URL(envSiteUrl).host\n } catch {\n /* malformed URL — fall through to client-side host */\n }\n }\n if (!resolvedSite && typeof window !== 'undefined') {\n resolvedSite = window.location.host\n }\n // Normalize with the shared site contract (lowercase host, no protocol/path/\n // port) so `__SITE_KIT_SITE__` matches exactly what the Sonor API stores —\n // servers re-normalize with the same rule, so this only makes the client\n // emit the canonical value. Always a string (possibly empty).\n const site = normalizeSiteHost(resolvedSite) ?? ''\n\n // Set window globals for modules that still read from them.\n if (typeof window !== 'undefined') {\n // Seed the client token manager. In 3.2+ `apiKey` is a short-lived minted\n // token that sonorFetch refreshes transparently; the window global is set to\n // the freshest token held (a prior page this session may have refreshed).\n // In raw-key mode (2.x kit or mint-failed fallback) this is a passthrough.\n configureSiteCredential({ credential: apiKey, apiUrl })\n ;(window as any).__SITE_KIT_API_URL__ = apiUrl\n ;(window as any).__SITE_KIT_API_KEY__ = currentCredential(apiKey)\n ;(window as any).__SITE_KIT_DEBUG__ = debug\n ;(window as any).__SITE_KIT_SITE__ = site\n }\n\n // Unified identity — single source of truth for all modules\n const [visitorId] = useState(() => getOrCreateVisitorId())\n const [sessionId] = useState(() => getOrCreateSessionId())\n\n // Page metadata bridge: Analytics writes, Signal reads\n const pageMetadataRef = useRef<Record<string, unknown> | null>(null)\n const onPageMetadata = useCallback((metadata: Record<string, unknown>) => {\n pageMetadataRef.current = metadata\n }, [])\n\n let content = <>{children}</>\n\n // Wrap with SignalBridge if enabled. Safe to wrap: the shell is SSR-safe\n // and renders children straight through — the heavy machinery mounts as a\n // childless sibling inside it. Not gated on modulesReady: experiment\n // variants must apply as early as possible.\n if (signal) {\n const signalConfig: SignalConfig =\n typeof signal === 'object' ? signal : {}\n content = (\n <SignalBridge\n enabled\n realtime={signalConfig.realtime !== false}\n experiments={signalConfig.experiments !== false}\n behaviorTracking={signalConfig.behaviorTracking !== false}\n visitorId={visitorId}\n sessionId={sessionId}\n pageMetadataRef={pageMetadataRef}\n >\n {content}\n </SignalBridge>\n )\n }\n\n // Add Analytics if enabled (renders alongside, doesn't wrap).\n //\n // CRITICAL: LazyAnalyticsProvider is dynamic(..., { ssr: false }). Wrapping\n // {content} inside an ssr:false component makes the entire page tree skip\n // server rendering — the route de-opts to 100% client rendering and the\n // hero / LCP element only paints after hydration (mobile LCP ~4s, Lighthouse\n // ~85). Analytics tracks via effects + a metadata ref, so it never needs to\n // wrap page content — mount it as a childless sibling, exactly like Engage.\n // Each deferred sibling is appended as `{modulesReady && <Module/>}` so the\n // children slot never moves in the tree — flipping ready only mounts the\n // sibling, it never remounts page content.\n if (analytics) {\n content = (\n <>\n {content}\n {modulesReady && (\n <Suspense fallback={null}>\n <LazyAnalyticsProvider\n apiUrl={apiUrl}\n apiKey={apiKey}\n site={site}\n trackPageViews={analyticsConfig.trackPageViews !== false}\n trackWebVitals={analyticsConfig.trackWebVitals !== false}\n trackScrollDepth={analyticsConfig.trackScrollDepth !== false}\n trackClicks={analyticsConfig.trackClicks !== false}\n sessionTimeout={analyticsConfig.sessionDuration}\n excludePaths={analyticsConfig.excludePaths}\n debug={debug}\n externalVisitorId={visitorId}\n externalSessionId={sessionId}\n onPageMetadata={onPageMetadata}\n />\n </Suspense>\n )}\n </>\n )\n }\n\n // Add Engage widget if enabled (renders alongside, doesn't wrap)\n if (engage) {\n const engageConfig: EngageConfig =\n typeof engage === 'object' ? engage : {}\n content = (\n <>\n {content}\n {modulesReady && (\n <LazyEngageWidget\n apiUrl={apiUrl}\n apiKey={apiKey}\n projectId={projectId}\n position={engageConfig.position || 'bottom-right'}\n zIndex={engageConfig.zIndex}\n chatEnabled={engageConfig.chatEnabled !== false}\n />\n )}\n </>\n )\n }\n\n // Add SitemapSync if enabled\n if (sitemapSync) {\n content = (\n <>\n {content}\n {modulesReady && <LazySitemapSync debug={debug} />}\n </>\n )\n }\n\n // Fleet heartbeat — childless sibling on the same modulesReady idle gate.\n // Reports the modules actually enabled on THIS layout (honest client-observable\n // set) so the platform can answer \"which version / modules is each site\n // running\". defer={false}: the modulesReady gate already handled deferral, so\n // the heartbeat fires on mount (still bounded to once per session internally).\n if (fleet) {\n const activeModules: FleetModule[] = []\n if (analytics) activeModules.push('analytics')\n if (engage) activeModules.push('engage')\n if (signal) activeModules.push('signal')\n if (sitemapSync) activeModules.push('sitemapSync')\n content = (\n <>\n {content}\n {modulesReady && (\n <FleetHeartbeat\n apiUrl={apiUrl}\n apiKey={apiKey}\n site={site}\n modules={activeModules}\n defer={false}\n debug={debug}\n />\n )}\n </>\n )\n }\n\n return (\n <SiteKitIdentityProvider visitorId={visitorId} sessionId={sessionId}>\n {content}\n </SiteKitIdentityProvider>\n )\n}\n"]}
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ // src/shared/version.ts
4
+ var SITE_KIT_VERSION = "3.2.1";
5
+
6
+ exports.SITE_KIT_VERSION = SITE_KIT_VERSION;
7
+ //# sourceMappingURL=chunk-WAAFJPNR.js.map
8
+ //# sourceMappingURL=chunk-WAAFJPNR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/version.ts"],"names":[],"mappings":";;;AASO,IAAM,gBAAA,GAAmB","file":"chunk-WAAFJPNR.js","sourcesContent":["/**\n * @sonordev/site-kit shared - Package version constant\n *\n * Single source of truth for the version the kit reports at runtime (the\n * x-sitekit-version request header set by sonorFetch). Kept as a plain const\n * so client bundles don't need JSON imports; scripts/verify-dts.cjs asserts\n * it matches package.json at publish time, and version.test.ts asserts it in\n * dev, so it can't silently drift.\n */\nexport const SITE_KIT_VERSION = '3.2.1'\n"]}
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ // src/sites/contract.ts
4
+ var SITE_HOST_CONTRACT_VERSION = 1;
5
+ function normalizeSiteHost(site) {
6
+ if (!site || typeof site !== "string") return null;
7
+ let s = site.trim().toLowerCase();
8
+ if (!s) return null;
9
+ s = s.replace(/^https?:\/\//, "");
10
+ const slash = s.indexOf("/");
11
+ if (slash >= 0) s = s.slice(0, slash);
12
+ const colon = s.indexOf(":");
13
+ if (colon >= 0) s = s.slice(0, colon);
14
+ return s || null;
15
+ }
16
+ function stripWww(host) {
17
+ return host.startsWith("www.") ? host.slice(4) : host;
18
+ }
19
+ function sameSite(a, b) {
20
+ if (!a || !b) return false;
21
+ return stripWww(a) === stripWww(b);
22
+ }
23
+ function hostFromUrl(value) {
24
+ if (!value || !value.startsWith("http://") && !value.startsWith("https://")) return null;
25
+ try {
26
+ return normalizeSiteHost(new URL(value).host);
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function isNormalizedSiteHost(value) {
32
+ return typeof value === "string" && value.length > 0 && normalizeSiteHost(value) === value;
33
+ }
34
+ var SITE_HOST_JSON_SCHEMA = {
35
+ $schema: "http://json-schema.org/draft-07/schema#",
36
+ $id: "https://sonor.io/schemas/site-host.v1.json",
37
+ title: "NormalizedSiteHost",
38
+ description: "A normalized sub-site host (the multi-site `site` dimension): lowercase, no protocol, no path, no port. null denotes a project-wide (unattributed) row.",
39
+ type: ["string", "null"],
40
+ pattern: "^[a-z0-9.-]+$"
41
+ };
42
+
43
+ exports.SITE_HOST_CONTRACT_VERSION = SITE_HOST_CONTRACT_VERSION;
44
+ exports.SITE_HOST_JSON_SCHEMA = SITE_HOST_JSON_SCHEMA;
45
+ exports.hostFromUrl = hostFromUrl;
46
+ exports.isNormalizedSiteHost = isNormalizedSiteHost;
47
+ exports.normalizeSiteHost = normalizeSiteHost;
48
+ exports.sameSite = sameSite;
49
+ exports.stripWww = stripWww;
50
+ //# sourceMappingURL=chunk-WZHWWOUC.js.map
51
+ //# sourceMappingURL=chunk-WZHWWOUC.js.map