@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,264 @@
1
+ 'use strict';
2
+
3
+ var chunkWAAFJPNR_js = require('./chunk-WAAFJPNR.js');
4
+
5
+ // src/shared/siteToken.ts
6
+ var SITE_TOKEN_PREFIX = "st1.";
7
+ var REFRESH_SKEW_MS = 5 * 6e4;
8
+ var REFRESH_TIMEOUT_MS = 6e3;
9
+ var STORAGE_KEY = "__sonor_site_token__";
10
+ function isSiteToken(credential) {
11
+ return typeof credential === "string" && credential.startsWith(SITE_TOKEN_PREFIX);
12
+ }
13
+ function decodeSiteTokenClaims(token) {
14
+ if (!isSiteToken(token)) return null;
15
+ const seg = token.split(".")[1];
16
+ if (!seg) return null;
17
+ try {
18
+ const b64 = seg.replace(/-/g, "+").replace(/_/g, "/");
19
+ const json = typeof atob === "function" ? atob(b64) : Buffer.from(seg, "base64url").toString("utf8");
20
+ const claims = JSON.parse(json);
21
+ return typeof claims === "object" && claims ? claims : null;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+ function credentialBreakerId(credential) {
27
+ if (!credential) return "(anonymous)";
28
+ const claims = decodeSiteTokenClaims(credential);
29
+ if (claims?.pid) return `pid:${claims.pid}`;
30
+ return credential.slice(0, 16);
31
+ }
32
+ var state = {
33
+ seed: void 0,
34
+ isToken: false,
35
+ apiUrl: "https://api.sonor.io",
36
+ token: null,
37
+ refreshing: null
38
+ };
39
+ function readStoredToken() {
40
+ if (typeof window === "undefined") return null;
41
+ try {
42
+ const raw = window.sessionStorage.getItem(STORAGE_KEY);
43
+ if (!raw) return null;
44
+ const parsed = JSON.parse(raw);
45
+ return typeof parsed?.token === "string" ? parsed.token : null;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+ function storeToken(token) {
51
+ if (typeof window === "undefined") return;
52
+ try {
53
+ window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ token }));
54
+ } catch {
55
+ }
56
+ try {
57
+ ;
58
+ window.__SITE_KIT_API_KEY__ = token;
59
+ } catch {
60
+ }
61
+ }
62
+ function tokenExp(token) {
63
+ const claims = decodeSiteTokenClaims(token);
64
+ return typeof claims?.exp === "number" ? claims.exp * 1e3 : 0;
65
+ }
66
+ function configureSiteCredential(opts) {
67
+ state.seed = opts.credential;
68
+ state.isToken = isSiteToken(opts.credential);
69
+ if (opts.apiUrl) state.apiUrl = opts.apiUrl.replace(/\/+$/, "");
70
+ if (!state.isToken) {
71
+ state.token = null;
72
+ return;
73
+ }
74
+ const prior = state.token;
75
+ let best = opts.credential ?? null;
76
+ const stored = readStoredToken();
77
+ if (stored && tokenExp(stored) > tokenExp(best)) best = stored;
78
+ if (prior && tokenExp(prior) > tokenExp(best)) best = prior;
79
+ state.token = best;
80
+ }
81
+ function needsRefresh() {
82
+ if (!state.isToken) return false;
83
+ if (!state.token) return true;
84
+ return Date.now() >= tokenExp(state.token) - REFRESH_SKEW_MS;
85
+ }
86
+ async function doRefresh() {
87
+ const current = state.token;
88
+ if (!current) return null;
89
+ try {
90
+ const res = await fetch(`${state.apiUrl}/api/public/site-token/refresh`, {
91
+ method: "POST",
92
+ headers: {
93
+ "content-type": "application/json",
94
+ "x-sitekit-version": chunkWAAFJPNR_js.SITE_KIT_VERSION,
95
+ // Send the current token as x-api-key too, so the server's key-scoped
96
+ // throttler buckets refreshes by token rather than by shared CDN IP.
97
+ "x-api-key": current
98
+ },
99
+ body: JSON.stringify({ token: current }),
100
+ signal: typeof AbortSignal !== "undefined" && AbortSignal.timeout ? AbortSignal.timeout(REFRESH_TIMEOUT_MS) : void 0
101
+ });
102
+ if (!res.ok) return null;
103
+ const data = await res.json().catch(() => null);
104
+ const next = data?.token;
105
+ if (typeof next === "string" && isSiteToken(next)) {
106
+ state.token = next;
107
+ storeToken(next);
108
+ return next;
109
+ }
110
+ return null;
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+ function refreshSiteToken() {
116
+ if (!state.isToken) return Promise.resolve(null);
117
+ if (state.refreshing) return state.refreshing;
118
+ state.refreshing = doRefresh().finally(() => {
119
+ state.refreshing = null;
120
+ });
121
+ return state.refreshing;
122
+ }
123
+ async function getFreshCredential(seed) {
124
+ if (!state.isToken) return seed ?? state.seed;
125
+ if (needsRefresh()) {
126
+ const refreshed = await refreshSiteToken();
127
+ if (refreshed) return refreshed;
128
+ }
129
+ return state.token ?? seed ?? state.seed;
130
+ }
131
+ function isTokenMode() {
132
+ return state.isToken;
133
+ }
134
+ function currentCredential(seed) {
135
+ if (!state.isToken) return seed ?? state.seed;
136
+ return state.token ?? seed ?? state.seed;
137
+ }
138
+
139
+ // src/shared/fetchWithRetry.ts
140
+ function backoffDelay(attempt, response) {
141
+ if (response?.status === 429) {
142
+ const header = response.headers.get("retry-after");
143
+ const seconds = header ? parseInt(header, 10) : NaN;
144
+ if (!Number.isNaN(seconds) && seconds > 0) return Math.min(seconds * 1e3, 1e4);
145
+ }
146
+ return Math.min(8e3, 500 * 2 ** attempt);
147
+ }
148
+ async function fetchWithRetry(url, init, options = {}) {
149
+ const retries = options.retries ?? 2;
150
+ const timeoutMs = options.timeoutMs ?? 15e3;
151
+ let lastError;
152
+ for (let attempt = 0; attempt <= retries; attempt++) {
153
+ const controller = new AbortController();
154
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
155
+ try {
156
+ const response = await fetch(url, { ...init, signal: controller.signal });
157
+ clearTimeout(timer);
158
+ if ((response.status === 429 || response.status >= 500) && attempt < retries) {
159
+ await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt, response)));
160
+ continue;
161
+ }
162
+ return response;
163
+ } catch (error) {
164
+ clearTimeout(timer);
165
+ lastError = error;
166
+ if (attempt < retries) {
167
+ await new Promise((resolve) => setTimeout(resolve, backoffDelay(attempt)));
168
+ continue;
169
+ }
170
+ }
171
+ }
172
+ throw lastError instanceof Error ? lastError : new Error("Network request failed after multiple attempts. Please try again.");
173
+ }
174
+
175
+ // src/shared/sonorFetch.ts
176
+ var AUTH_BREAK_MS = 5 * 6e4;
177
+ var pausedKeys = /* @__PURE__ */ new Map();
178
+ var warnedThisPage = false;
179
+ function isAuthPaused(credential) {
180
+ if (typeof window === "undefined") return false;
181
+ const id = credentialBreakerId(credential);
182
+ const until = pausedKeys.get(id);
183
+ if (!until) return false;
184
+ if (Date.now() >= until) {
185
+ pausedKeys.delete(id);
186
+ return false;
187
+ }
188
+ return true;
189
+ }
190
+ function tripBreaker(credential) {
191
+ pausedKeys.set(credentialBreakerId(credential), Date.now() + AUTH_BREAK_MS);
192
+ if (!warnedThisPage) {
193
+ warnedThisPage = true;
194
+ console.info(
195
+ "[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."
196
+ );
197
+ }
198
+ }
199
+ async function sonorFetch(url, init = {}, options = {}) {
200
+ let credential = options.apiKey;
201
+ if (credential !== void 0 || isTokenMode()) {
202
+ credential = await getFreshCredential(options.apiKey);
203
+ }
204
+ const headers = new Headers(init.headers || {});
205
+ headers.set("x-sitekit-version", chunkWAAFJPNR_js.SITE_KIT_VERSION);
206
+ if (credential) {
207
+ if (isTokenMode()) {
208
+ headers.set("x-api-key", credential);
209
+ headers.delete("authorization");
210
+ } else if (!headers.has("x-api-key")) {
211
+ headers.set("x-api-key", credential);
212
+ }
213
+ }
214
+ if (isAuthPaused(credential)) {
215
+ return new Response(null, { status: 401, statusText: "sonor-auth-paused" });
216
+ }
217
+ let response = await fetchWithRetry(url, { ...init, headers }, options);
218
+ if ((response.status === 401 || response.status === 403) && typeof window !== "undefined" && isSiteToken(credential)) {
219
+ const refreshed = await refreshSiteToken();
220
+ if (refreshed && refreshed !== credential) {
221
+ const retryHeaders = new Headers(init.headers || {});
222
+ retryHeaders.set("x-sitekit-version", chunkWAAFJPNR_js.SITE_KIT_VERSION);
223
+ retryHeaders.set("x-api-key", refreshed);
224
+ retryHeaders.delete("authorization");
225
+ credential = refreshed;
226
+ response = await fetchWithRetry(url, { ...init, headers: retryHeaders }, options);
227
+ }
228
+ }
229
+ if ((response.status === 401 || response.status === 403) && typeof window !== "undefined" && credential) {
230
+ tripBreaker(credential);
231
+ }
232
+ return response;
233
+ }
234
+ function sonorBeacon(url, payload, apiKey) {
235
+ const credential = currentCredential(apiKey);
236
+ const body = typeof payload === "string" ? payload : JSON.stringify(payload);
237
+ try {
238
+ void fetch(url, {
239
+ method: "POST",
240
+ keepalive: true,
241
+ headers: {
242
+ "Content-Type": "application/json",
243
+ "x-sitekit-version": chunkWAAFJPNR_js.SITE_KIT_VERSION,
244
+ ...credential ? { "x-api-key": credential } : {}
245
+ },
246
+ body
247
+ }).catch(() => {
248
+ });
249
+ } catch {
250
+ try {
251
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
252
+ navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
253
+ }
254
+ } catch {
255
+ }
256
+ }
257
+ }
258
+
259
+ exports.configureSiteCredential = configureSiteCredential;
260
+ exports.currentCredential = currentCredential;
261
+ exports.sonorBeacon = sonorBeacon;
262
+ exports.sonorFetch = sonorFetch;
263
+ //# sourceMappingURL=chunk-GMSPDTEV.js.map
264
+ //# sourceMappingURL=chunk-GMSPDTEV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/siteToken.ts","../src/shared/fetchWithRetry.ts","../src/shared/sonorFetch.ts"],"names":["SITE_KIT_VERSION"],"mappings":";;;;;AAuBO,IAAM,iBAAA,GAAoB,MAAA;AAGjC,IAAM,kBAAkB,CAAA,GAAI,GAAA;AAC5B,IAAM,kBAAA,GAAqB,GAAA;AAC3B,IAAM,WAAA,GAAc,sBAAA;AAEb,SAAS,YAAY,UAAA,EAAqC;AAC/D,EAAA,OAAO,OAAO,UAAA,KAAe,QAAA,IAAY,UAAA,CAAW,WAAW,iBAAiB,CAAA;AAClF;AASO,SAAS,sBAAsB,KAAA,EAA6C;AACjF,EAAA,IAAI,CAAC,WAAA,CAAY,KAAK,CAAA,EAAG,OAAO,IAAA;AAChC,EAAA,MAAM,GAAA,GAAO,KAAA,CAAiB,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AAC1C,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,IAAI;AAEF,IAAA,MAAM,GAAA,GAAM,IAAI,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAM,GAAG,CAAA;AACpD,IAAA,MAAM,IAAA,GACJ,OAAO,IAAA,KAAS,UAAA,GACZ,IAAA,CAAK,GAAG,CAAA,GACR,MAAA,CAAO,IAAA,CAAK,GAAA,EAAK,WAAW,CAAA,CAAE,SAAS,MAAM,CAAA;AACnD,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B,IAAA,OAAO,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,GAAS,MAAA,GAAS,IAAA;AAAA,EACzD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAOO,SAAS,oBAAoB,UAAA,EAAoC;AACtE,EAAA,IAAI,CAAC,YAAY,OAAO,aAAA;AACxB,EAAA,MAAM,MAAA,GAAS,sBAAsB,UAAU,CAAA;AAC/C,EAAA,IAAI,MAAA,EAAQ,GAAA,EAAK,OAAO,CAAA,IAAA,EAAO,OAAO,GAAG,CAAA,CAAA;AACzC,EAAA,OAAO,UAAA,CAAW,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAC/B;AAeA,IAAM,KAAA,GAAoB;AAAA,EACxB,IAAA,EAAM,MAAA;AAAA,EACN,OAAA,EAAS,KAAA;AAAA,EACT,MAAA,EAAQ,sBAAA;AAAA,EACR,KAAA,EAAO,IAAA;AAAA,EACP,UAAA,EAAY;AACd,CAAA;AAEA,SAAS,eAAA,GAAiC;AACxC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,cAAA,CAAe,OAAA,CAAQ,WAAW,CAAA;AACrD,IAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC7B,IAAA,OAAO,OAAO,MAAA,EAAQ,KAAA,KAAU,QAAA,GAAW,OAAO,KAAA,GAAQ,IAAA;AAAA,EAC5D,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,WAAW,KAAA,EAAqB;AACvC,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,cAAA,CAAe,QAAQ,WAAA,EAAa,IAAA,CAAK,UAAU,EAAE,KAAA,EAAO,CAAC,CAAA;AAAA,EACtE,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,IAAI;AACF,IAAA;AAAC,IAAC,OAAe,oBAAA,GAAuB,KAAA;AAAA,EAC1C,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAEA,SAAS,SAAS,KAAA,EAA8B;AAC9C,EAAA,MAAM,MAAA,GAAS,sBAAsB,KAAK,CAAA;AAC1C,EAAA,OAAO,OAAO,MAAA,EAAQ,GAAA,KAAQ,QAAA,GAAW,MAAA,CAAO,MAAM,GAAA,GAAO,CAAA;AAC/D;AAMO,SAAS,wBAAwB,IAAA,EAG/B;AACP,EAAA,KAAA,CAAM,OAAO,IAAA,CAAK,UAAA;AAClB,EAAA,KAAA,CAAM,OAAA,GAAU,WAAA,CAAY,IAAA,CAAK,UAAU,CAAA;AAC3C,EAAA,IAAI,IAAA,CAAK,QAAQ,KAAA,CAAM,MAAA,GAAS,KAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAE9D,EAAA,IAAI,CAAC,MAAM,OAAA,EAAS;AAClB,IAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AACd,IAAA;AAAA,EACF;AAKA,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,IAAI,IAAA,GAAO,KAAK,UAAA,IAAc,IAAA;AAC9B,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,IAAI,UAAU,QAAA,CAAS,MAAM,IAAI,QAAA,CAAS,IAAI,GAAG,IAAA,GAAO,MAAA;AACxD,EAAA,IAAI,SAAS,QAAA,CAAS,KAAK,IAAI,QAAA,CAAS,IAAI,GAAG,IAAA,GAAO,KAAA;AACtD,EAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AAChB;AAEA,SAAS,YAAA,GAAwB;AAC/B,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,KAAA;AAC3B,EAAA,IAAI,CAAC,KAAA,CAAM,KAAA,EAAO,OAAO,IAAA;AACzB,EAAA,OAAO,KAAK,GAAA,EAAI,IAAK,QAAA,CAAS,KAAA,CAAM,KAAK,CAAA,GAAI,eAAA;AAC/C;AAEA,eAAe,SAAA,GAAoC;AACjD,EAAA,MAAM,UAAU,KAAA,CAAM,KAAA;AACtB,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,MAAM,CAAA,8BAAA,CAAA,EAAkC;AAAA,MACvE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,mBAAA,EAAqBA,iCAAA;AAAA;AAAA;AAAA,QAGrB,WAAA,EAAa;AAAA,OACf;AAAA,MACA,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,SAAS,CAAA;AAAA,MACvC,MAAA,EACE,OAAO,WAAA,KAAgB,WAAA,IAAe,YAAY,OAAA,GAC9C,WAAA,CAAY,OAAA,CAAQ,kBAAkB,CAAA,GACtC,KAAA;AAAA,KACP,CAAA;AACD,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,OAAO,IAAA;AACpB,IAAA,MAAM,OAAO,MAAM,GAAA,CAAI,MAAK,CAAE,KAAA,CAAM,MAAM,IAAI,CAAA;AAC9C,IAAA,MAAM,OAAO,IAAA,EAAM,KAAA;AACnB,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,WAAA,CAAY,IAAI,CAAA,EAAG;AACjD,MAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AACd,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAGO,SAAS,gBAAA,GAA2C;AACzD,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAC/C,EAAA,IAAI,KAAA,CAAM,UAAA,EAAY,OAAO,KAAA,CAAM,UAAA;AACnC,EAAA,KAAA,CAAM,UAAA,GAAa,SAAA,EAAU,CAAE,OAAA,CAAQ,MAAM;AAC3C,IAAA,KAAA,CAAM,UAAA,GAAa,IAAA;AAAA,EACrB,CAAC,CAAA;AACD,EAAA,OAAO,KAAA,CAAM,UAAA;AACf;AAMA,eAAsB,mBACpB,IAAA,EAC6B;AAE7B,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,QAAQ,KAAA,CAAM,IAAA;AACzC,EAAA,IAAI,cAAa,EAAG;AAClB,IAAA,MAAM,SAAA,GAAY,MAAM,gBAAA,EAAiB;AACzC,IAAA,IAAI,WAAW,OAAO,SAAA;AAAA,EAGxB;AACA,EAAA,OAAO,KAAA,CAAM,KAAA,IAAS,IAAA,IAAQ,KAAA,CAAM,IAAA;AACtC;AAGO,SAAS,WAAA,GAAuB;AACrC,EAAA,OAAO,KAAA,CAAM,OAAA;AACf;AAOO,SAAS,kBAAkB,IAAA,EAAmC;AACnE,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,QAAQ,KAAA,CAAM,IAAA;AACzC,EAAA,OAAO,KAAA,CAAM,KAAA,IAAS,IAAA,IAAQ,KAAA,CAAM,IAAA;AACtC;;;ACjNA,SAAS,YAAA,CAAa,SAAiB,QAAA,EAA6B;AAElE,EAAA,IAAI,QAAA,EAAU,WAAW,GAAA,EAAK;AAC5B,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAA;AACjD,IAAA,MAAM,OAAA,GAAU,MAAA,GAAS,QAAA,CAAS,MAAA,EAAQ,EAAE,CAAA,GAAI,GAAA;AAChD,IAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,IAAK,OAAA,GAAU,CAAA,EAAG,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,GAAU,GAAA,EAAM,GAAK,CAAA;AAAA,EAClF;AAEA,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAA,EAAM,GAAA,GAAM,KAAK,OAAO,CAAA;AAC1C;AASA,eAAsB,cAAA,CACpB,GAAA,EACA,IAAA,EACA,OAAA,GAA6B,EAAC,EACX;AACnB,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,CAAA;AACnC,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,IAAA;AACvC,EAAA,IAAI,SAAA;AAEJ,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,OAAA,EAAS,OAAA,EAAA,EAAW;AACnD,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,SAAS,CAAA;AAC5D,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,CAAA;AACxE,MAAA,YAAA,CAAa,KAAK,CAAA;AAElB,MAAA,IAAA,CAAK,SAAS,MAAA,KAAW,GAAA,IAAO,SAAS,MAAA,IAAU,GAAA,KAAQ,UAAU,OAAA,EAAS;AAC5E,QAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY,UAAA,CAAW,SAAS,YAAA,CAAa,OAAA,EAAS,QAAQ,CAAC,CAAC,CAAA;AACnF,QAAA;AAAA,MACF;AACA,MAAA,OAAO,QAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,SAAA,GAAY,KAAA;AAEZ,MAAA,IAAI,UAAU,OAAA,EAAS;AACrB,QAAA,MAAM,IAAI,QAAQ,CAAC,OAAA,KAAY,WAAW,OAAA,EAAS,YAAA,CAAa,OAAO,CAAC,CAAC,CAAA;AACzE,QAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,YAAqB,KAAA,GACvB,SAAA,GACA,IAAI,MAAM,mEAAmE,CAAA;AACnF;;;AC7BA,IAAM,gBAAgB,CAAA,GAAI,GAAA;AAI1B,IAAM,UAAA,uBAAiB,GAAA,EAAoB;AAC3C,IAAI,cAAA,GAAiB,KAAA;AAGd,SAAS,aAAa,UAAA,EAA8B;AACzD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,MAAM,EAAA,GAAK,oBAAoB,UAAU,CAAA;AACzC,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,EAAE,CAAA;AAC/B,EAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,EAAA,IAAI,IAAA,CAAK,GAAA,EAAI,IAAK,KAAA,EAAO;AACvB,IAAA,UAAA,CAAW,OAAO,EAAE,CAAA;AACpB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,YAAY,UAAA,EAA2B;AAC9C,EAAA,UAAA,CAAW,IAAI,mBAAA,CAAoB,UAAU,GAAG,IAAA,CAAK,GAAA,KAAQ,aAAa,CAAA;AAC1E,EAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,IAAA,cAAA,GAAiB,IAAA;AAGjB,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN;AAAA,KACF;AAAA,EACF;AACF;AAEA,eAAsB,WACpB,GAAA,EACA,IAAA,GAAoB,EAAC,EACrB,OAAA,GAA6B,EAAC,EACX;AAGnB,EAAA,IAAI,aAAa,OAAA,CAAQ,MAAA;AACzB,EAAA,IAAI,UAAA,KAAe,MAAA,IAAa,WAAA,EAAY,EAAG;AAC7C,IAAA,UAAA,GAAa,MAAM,kBAAA,CAAmB,OAAA,CAAQ,MAAM,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,UAAU,IAAI,OAAA,CAAQ,IAAA,CAAK,OAAA,IAAW,EAAE,CAAA;AAC9C,EAAA,OAAA,CAAQ,GAAA,CAAI,qBAAqBA,iCAAgB,CAAA;AACjD,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,IAAI,aAAY,EAAG;AASjB,MAAA,OAAA,CAAQ,GAAA,CAAI,aAAa,UAAU,CAAA;AACnC,MAAA,OAAA,CAAQ,OAAO,eAAe,CAAA;AAAA,IAChC,CAAA,MAAA,IAAW,CAAC,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,EAAG;AAEpC,MAAA,OAAA,CAAQ,GAAA,CAAI,aAAa,UAAU,CAAA;AAAA,IACrC;AAAA,EACF;AAEA,EAAA,IAAI,YAAA,CAAa,UAAU,CAAA,EAAG;AAG5B,IAAA,OAAO,IAAI,SAAS,IAAA,EAAM,EAAE,QAAQ,GAAA,EAAK,UAAA,EAAY,qBAAqB,CAAA;AAAA,EAC5E;AAEA,EAAA,IAAI,QAAA,GAAW,MAAM,cAAA,CAAe,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,OAAA,EAAQ,EAAG,OAAO,CAAA;AAKtE,EAAA,IAAA,CACG,QAAA,CAAS,MAAA,KAAW,GAAA,IAAO,QAAA,CAAS,MAAA,KAAW,GAAA,KAChD,OAAO,MAAA,KAAW,WAAA,IAClB,WAAA,CAAY,UAAU,CAAA,EACtB;AACA,IAAA,MAAM,SAAA,GAAY,MAAM,gBAAA,EAAiB;AACzC,IAAA,IAAI,SAAA,IAAa,cAAc,UAAA,EAAY;AACzC,MAAA,MAAM,eAAe,IAAI,OAAA,CAAQ,IAAA,CAAK,OAAA,IAAW,EAAE,CAAA;AACnD,MAAA,YAAA,CAAa,GAAA,CAAI,qBAAqBA,iCAAgB,CAAA;AACtD,MAAA,YAAA,CAAa,GAAA,CAAI,aAAa,SAAS,CAAA;AAGvC,MAAA,YAAA,CAAa,OAAO,eAAe,CAAA;AACnC,MAAA,UAAA,GAAa,SAAA;AACb,MAAA,QAAA,GAAW,MAAM,eAAe,GAAA,EAAK,EAAE,GAAG,IAAA,EAAM,OAAA,EAAS,YAAA,EAAa,EAAG,OAAO,CAAA;AAAA,IAClF;AAAA,EACF;AAEA,EAAA,IAAA,CACG,QAAA,CAAS,WAAW,GAAA,IAAO,QAAA,CAAS,WAAW,GAAA,KAChD,OAAO,MAAA,KAAW,WAAA,IAClB,UAAA,EACA;AACA,IAAA,WAAA,CAAY,UAAU,CAAA;AAAA,EACxB;AAEA,EAAA,OAAO,QAAA;AACT;AAYO,SAAS,WAAA,CAAY,GAAA,EAAa,OAAA,EAAkB,MAAA,EAAuB;AAChF,EAAA,MAAM,UAAA,GAAa,kBAAkB,MAAM,CAAA;AAC3C,EAAA,MAAM,OAAO,OAAO,OAAA,KAAY,WAAW,OAAA,GAAU,IAAA,CAAK,UAAU,OAAO,CAAA;AAC3E,EAAA,IAAI;AACF,IAAA,KAAK,MAAM,GAAA,EAAK;AAAA,MACd,MAAA,EAAQ,MAAA;AAAA,MACR,SAAA,EAAW,IAAA;AAAA,MACX,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,mBAAA,EAAqBA,iCAAA;AAAA,QACrB,GAAI,UAAA,GAAa,EAAE,WAAA,EAAa,UAAA,KAAe;AAAC,OAClD;AAAA,MACA;AAAA,KACD,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,IAEf,CAAC,CAAA;AAAA,EACH,CAAA,CAAA,MAAQ;AACN,IAAA,IAAI;AACF,MAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,SAAA,CAAU,UAAA,EAAY;AAC5D,QAAA,SAAA,CAAU,UAAA,CAAW,GAAA,EAAK,IAAI,IAAA,CAAK,CAAC,IAAI,CAAA,EAAG,EAAE,IAAA,EAAM,kBAAA,EAAoB,CAAC,CAAA;AAAA,MAC1E;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF","file":"chunk-GMSPDTEV.js","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"]}
@@ -35,7 +35,7 @@ var defaultStyles = {
35
35
  transition: "border-color 0.2s, box-shadow 0.2s"
36
36
  },
37
37
  itemOpen: {
38
- borderColor: "color-mix(in srgb, var(--sk-primary, #3b82f6) 40%, var(--sk-surface-border, #e2e8f0))",
38
+ borderColor: "color-mix(in srgb, var(--sk-primary, #2563eb) 40%, var(--sk-surface-border, #e2e8f0))",
39
39
  boxShadow: "0 1px 3px rgba(0,0,0,0.06)"
40
40
  },
41
41
  question: {
@@ -59,7 +59,7 @@ var defaultStyles = {
59
59
  flexShrink: 0,
60
60
  width: "1.25rem",
61
61
  height: "1.25rem",
62
- color: "var(--sk-primary, #3b82f6)",
62
+ color: "var(--sk-primary, #2563eb)",
63
63
  transition: "transform 0.2s"
64
64
  },
65
65
  answer: {
@@ -218,5 +218,5 @@ function BlogFAQ({
218
218
  }
219
219
 
220
220
  exports.BlogFAQ = BlogFAQ;
221
- //# sourceMappingURL=chunk-UJQYSVYV.js.map
222
- //# sourceMappingURL=chunk-UJQYSVYV.js.map
221
+ //# sourceMappingURL=chunk-GQF6QAQS.js.map
222
+ //# sourceMappingURL=chunk-GQF6QAQS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/blog/BlogFAQ.tsx"],"names":["jsxs","jsx","useRef","useState","useCallback"],"mappings":"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","file":"chunk-UJQYSVYV.js","sourcesContent":["/**\n * @sonordev/site-kit/blog - Blog FAQ Component\n *\n * Renders FAQ items with collapsible accordion behavior.\n * Content is always in the DOM (not display:none) for SEO crawlability.\n * Uses CSS max-height transitions — no JS animation library required.\n *\n * Pairs with FAQPage schema (generated by Signal AI in the blog pipeline)\n * for Google's \"People Also Ask\" featured snippets.\n *\n * @example\n * ```tsx\n * <BlogFAQ items={post.faqItems} />\n * ```\n */\n\n'use client'\n\nimport React, { useState, useRef, useCallback } from 'react'\n\nexport interface FaqItem {\n question: string\n answer: string\n}\n\nexport interface BlogFAQProps {\n items: FaqItem[]\n /** Section title */\n title?: string\n /** Allow multiple items open simultaneously */\n allowMultiple?: boolean\n /** Override styles with Tailwind classes */\n styles?: {\n section?: string\n title?: string\n list?: string\n item?: string\n question?: string\n questionText?: string\n icon?: string\n answer?: string\n answerInner?: string\n }\n unstyled?: boolean\n}\n\n// CSS custom property defaults\nconst defaultStyles = {\n section: {\n margin: '3rem 0',\n padding: 0,\n },\n title: {\n fontSize: '1.5rem',\n fontWeight: 700,\n color: 'var(--sk-text-primary, #0f172a)',\n marginBottom: '1.5rem',\n display: 'flex',\n alignItems: 'center',\n gap: '0.5rem',\n },\n list: {\n listStyle: 'none',\n margin: 0,\n padding: 0,\n display: 'flex',\n flexDirection: 'column' as const,\n gap: '0.5rem',\n },\n item: {\n borderRadius: '0.75rem',\n border: '1px solid var(--sk-surface-border, #e2e8f0)',\n background: 'var(--sk-bg-elevated, #f8fafc)',\n overflow: 'hidden',\n transition: 'border-color 0.2s, box-shadow 0.2s',\n },\n itemOpen: {\n borderColor: 'color-mix(in srgb, var(--sk-primary, #3b82f6) 40%, var(--sk-surface-border, #e2e8f0))',\n boxShadow: '0 1px 3px rgba(0,0,0,0.06)',\n },\n question: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n width: '100%',\n padding: '1rem 1.25rem',\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n textAlign: 'left' as const,\n },\n questionText: {\n fontSize: '1rem',\n fontWeight: 600,\n color: 'var(--sk-text-primary, #0f172a)',\n paddingRight: '1rem',\n },\n icon: {\n flexShrink: 0,\n width: '1.25rem',\n height: '1.25rem',\n color: 'var(--sk-primary, #3b82f6)',\n transition: 'transform 0.2s',\n },\n answer: {\n overflow: 'hidden',\n transition: 'max-height 0.3s ease, opacity 0.2s ease',\n },\n answerInner: {\n padding: '0 1.25rem 1.25rem',\n fontSize: '0.9375rem',\n lineHeight: 1.7,\n color: 'var(--sk-text-secondary, #475569)',\n },\n}\n\nfunction PlusMinusIcon({ isOpen }: { isOpen: boolean }) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n style={{\n ...defaultStyles.icon,\n transform: isOpen ? 'rotate(45deg)' : 'rotate(0deg)',\n }}\n >\n <line x1=\"10\" y1=\"4\" x2=\"10\" y2=\"16\" />\n <line x1=\"4\" y1=\"10\" x2=\"16\" y2=\"10\" />\n </svg>\n )\n}\n\nfunction FAQItem({\n item,\n isOpen,\n onToggle,\n styles,\n unstyled,\n}: {\n item: FaqItem\n isOpen: boolean\n onToggle: () => void\n styles?: BlogFAQProps['styles']\n unstyled?: boolean\n}) {\n const answerRef = useRef<HTMLDivElement>(null)\n\n return (\n <li\n className={styles?.item}\n style={unstyled ? undefined : {\n ...defaultStyles.item,\n ...(isOpen ? defaultStyles.itemOpen : {}),\n }}\n >\n <button\n className={styles?.question}\n style={unstyled ? undefined : defaultStyles.question}\n onClick={onToggle}\n aria-expanded={isOpen}\n type=\"button\"\n >\n <span\n className={styles?.questionText}\n style={unstyled ? undefined : defaultStyles.questionText}\n >\n {item.question}\n </span>\n <span className={styles?.icon}>\n <PlusMinusIcon isOpen={isOpen} />\n </span>\n </button>\n <div\n ref={answerRef}\n className={styles?.answer}\n style={unstyled ? undefined : {\n ...defaultStyles.answer,\n maxHeight: isOpen ? `${(answerRef.current?.scrollHeight || 500) + 40}px` : '0',\n opacity: isOpen ? 1 : 0,\n }}\n role=\"region\"\n aria-hidden={!isOpen}\n >\n <div\n className={styles?.answerInner}\n style={unstyled ? undefined : defaultStyles.answerInner}\n dangerouslySetInnerHTML={{ __html: item.answer }}\n />\n </div>\n </li>\n )\n}\n\nexport function BlogFAQ({\n items,\n title = 'Frequently Asked Questions',\n allowMultiple = false,\n styles,\n unstyled = false,\n}: BlogFAQProps) {\n const [openIndices, setOpenIndices] = useState<Set<number>>(new Set())\n\n const toggle = useCallback((index: number) => {\n setOpenIndices(prev => {\n const next = new Set(allowMultiple ? prev : [])\n if (prev.has(index)) {\n next.delete(index)\n } else {\n next.add(index)\n }\n return next\n })\n }, [allowMultiple])\n\n if (!items || items.length === 0) return null\n\n return (\n <section\n className={styles?.section}\n style={unstyled ? undefined : defaultStyles.section}\n aria-label=\"Frequently Asked Questions\"\n >\n <h2\n className={styles?.title}\n style={unstyled ? undefined : defaultStyles.title}\n >\n {title}\n </h2>\n <ul\n className={styles?.list}\n style={unstyled ? undefined : defaultStyles.list}\n >\n {items.map((item, index) => (\n <FAQItem\n key={index}\n item={item}\n isOpen={openIndices.has(index)}\n onToggle={() => toggle(index)}\n styles={styles}\n unstyled={unstyled}\n />\n ))}\n </ul>\n </section>\n )\n}\n"]}
1
+ {"version":3,"sources":["../src/blog/BlogFAQ.tsx"],"names":["jsxs","jsx","useRef","useState","useCallback"],"mappings":"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","file":"chunk-GQF6QAQS.js","sourcesContent":["/**\n * @sonordev/site-kit/blog - Blog FAQ Component\n *\n * Renders FAQ items with collapsible accordion behavior.\n * Content is always in the DOM (not display:none) for SEO crawlability.\n * Uses CSS max-height transitions — no JS animation library required.\n *\n * Pairs with FAQPage schema (generated by Signal AI in the blog pipeline)\n * for Google's \"People Also Ask\" featured snippets.\n *\n * @example\n * ```tsx\n * <BlogFAQ items={post.faqItems} />\n * ```\n */\n\n'use client'\n\nimport React, { useState, useRef, useCallback } from 'react'\n\nexport interface FaqItem {\n question: string\n answer: string\n}\n\nexport interface BlogFAQProps {\n items: FaqItem[]\n /** Section title */\n title?: string\n /** Allow multiple items open simultaneously */\n allowMultiple?: boolean\n /** Override styles with Tailwind classes */\n styles?: {\n section?: string\n title?: string\n list?: string\n item?: string\n question?: string\n questionText?: string\n icon?: string\n answer?: string\n answerInner?: string\n }\n unstyled?: boolean\n}\n\n// CSS custom property defaults\nconst defaultStyles = {\n section: {\n margin: '3rem 0',\n padding: 0,\n },\n title: {\n fontSize: '1.5rem',\n fontWeight: 700,\n color: 'var(--sk-text-primary, #0f172a)',\n marginBottom: '1.5rem',\n display: 'flex',\n alignItems: 'center',\n gap: '0.5rem',\n },\n list: {\n listStyle: 'none',\n margin: 0,\n padding: 0,\n display: 'flex',\n flexDirection: 'column' as const,\n gap: '0.5rem',\n },\n item: {\n borderRadius: '0.75rem',\n border: '1px solid var(--sk-surface-border, #e2e8f0)',\n background: 'var(--sk-bg-elevated, #f8fafc)',\n overflow: 'hidden',\n transition: 'border-color 0.2s, box-shadow 0.2s',\n },\n itemOpen: {\n borderColor: 'color-mix(in srgb, var(--sk-primary, #2563eb) 40%, var(--sk-surface-border, #e2e8f0))',\n boxShadow: '0 1px 3px rgba(0,0,0,0.06)',\n },\n question: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n width: '100%',\n padding: '1rem 1.25rem',\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n textAlign: 'left' as const,\n },\n questionText: {\n fontSize: '1rem',\n fontWeight: 600,\n color: 'var(--sk-text-primary, #0f172a)',\n paddingRight: '1rem',\n },\n icon: {\n flexShrink: 0,\n width: '1.25rem',\n height: '1.25rem',\n color: 'var(--sk-primary, #2563eb)',\n transition: 'transform 0.2s',\n },\n answer: {\n overflow: 'hidden',\n transition: 'max-height 0.3s ease, opacity 0.2s ease',\n },\n answerInner: {\n padding: '0 1.25rem 1.25rem',\n fontSize: '0.9375rem',\n lineHeight: 1.7,\n color: 'var(--sk-text-secondary, #475569)',\n },\n}\n\nfunction PlusMinusIcon({ isOpen }: { isOpen: boolean }) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n style={{\n ...defaultStyles.icon,\n transform: isOpen ? 'rotate(45deg)' : 'rotate(0deg)',\n }}\n >\n <line x1=\"10\" y1=\"4\" x2=\"10\" y2=\"16\" />\n <line x1=\"4\" y1=\"10\" x2=\"16\" y2=\"10\" />\n </svg>\n )\n}\n\nfunction FAQItem({\n item,\n isOpen,\n onToggle,\n styles,\n unstyled,\n}: {\n item: FaqItem\n isOpen: boolean\n onToggle: () => void\n styles?: BlogFAQProps['styles']\n unstyled?: boolean\n}) {\n const answerRef = useRef<HTMLDivElement>(null)\n\n return (\n <li\n className={styles?.item}\n style={unstyled ? undefined : {\n ...defaultStyles.item,\n ...(isOpen ? defaultStyles.itemOpen : {}),\n }}\n >\n <button\n className={styles?.question}\n style={unstyled ? undefined : defaultStyles.question}\n onClick={onToggle}\n aria-expanded={isOpen}\n type=\"button\"\n >\n <span\n className={styles?.questionText}\n style={unstyled ? undefined : defaultStyles.questionText}\n >\n {item.question}\n </span>\n <span className={styles?.icon}>\n <PlusMinusIcon isOpen={isOpen} />\n </span>\n </button>\n <div\n ref={answerRef}\n className={styles?.answer}\n style={unstyled ? undefined : {\n ...defaultStyles.answer,\n maxHeight: isOpen ? `${(answerRef.current?.scrollHeight || 500) + 40}px` : '0',\n opacity: isOpen ? 1 : 0,\n }}\n role=\"region\"\n aria-hidden={!isOpen}\n >\n <div\n className={styles?.answerInner}\n style={unstyled ? undefined : defaultStyles.answerInner}\n dangerouslySetInnerHTML={{ __html: item.answer }}\n />\n </div>\n </li>\n )\n}\n\nexport function BlogFAQ({\n items,\n title = 'Frequently Asked Questions',\n allowMultiple = false,\n styles,\n unstyled = false,\n}: BlogFAQProps) {\n const [openIndices, setOpenIndices] = useState<Set<number>>(new Set())\n\n const toggle = useCallback((index: number) => {\n setOpenIndices(prev => {\n const next = new Set(allowMultiple ? prev : [])\n if (prev.has(index)) {\n next.delete(index)\n } else {\n next.add(index)\n }\n return next\n })\n }, [allowMultiple])\n\n if (!items || items.length === 0) return null\n\n return (\n <section\n className={styles?.section}\n style={unstyled ? undefined : defaultStyles.section}\n aria-label=\"Frequently Asked Questions\"\n >\n <h2\n className={styles?.title}\n style={unstyled ? undefined : defaultStyles.title}\n >\n {title}\n </h2>\n <ul\n className={styles?.list}\n style={unstyled ? undefined : defaultStyles.list}\n >\n {items.map((item, index) => (\n <FAQItem\n key={index}\n item={item}\n isOpen={openIndices.has(index)}\n onToggle={() => toggle(index)}\n styles={styles}\n unstyled={unstyled}\n />\n ))}\n </ul>\n </section>\n )\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { registerAnalyticsDispatcher } from './chunk-3DPXOZAU.mjs';
3
3
  import { getOrCreateVisitorId, getOrCreateSessionId } from './chunk-EE6SFAZP.mjs';
4
+ import { sonorFetch, sonorBeacon } from './chunk-SBT4J733.mjs';
4
5
  import { createContext, useRef, useEffect, useState, useCallback, useMemo, useContext } from 'react';
5
6
  import { usePathname } from 'next/navigation';
6
7
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -47,14 +48,15 @@ function WebVitals({ apiUrl: propApiUrl, apiKey: propApiKey, site: propSite, deb
47
48
  console.log("[Analytics] Web Vital:", data);
48
49
  }
49
50
  try {
50
- await fetch(`${apiUrl}/api/public/analytics/web-vitals`, {
51
- method: "POST",
52
- headers: {
53
- "Content-Type": "application/json",
54
- "x-api-key": apiKey
51
+ await sonorFetch(
52
+ `${apiUrl}/api/public/analytics/web-vitals`,
53
+ {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify(data)
55
57
  },
56
- body: JSON.stringify(data)
57
- });
58
+ { apiKey, retries: 0 }
59
+ );
58
60
  } catch (error) {
59
61
  if (debug2) console.error("[Analytics] Error reporting Web Vital:", error);
60
62
  }
@@ -554,13 +556,11 @@ function AnalyticsProvider({
554
556
  return;
555
557
  }
556
558
  try {
557
- const response = await fetch(`${apiUrl}/api/public/seo/pages`, {
558
- method: "GET",
559
- headers: {
560
- "Content-Type": "application/json",
561
- "x-api-key": apiKey
562
- }
563
- });
559
+ const response = await sonorFetch(
560
+ `${apiUrl}/api/public/seo/pages`,
561
+ { method: "GET", headers: { "Content-Type": "application/json" } },
562
+ { apiKey }
563
+ );
564
564
  if (response.ok) {
565
565
  const data = await response.json();
566
566
  const pages = data?.pages || [];
@@ -630,14 +630,15 @@ function AnalyticsProvider({
630
630
  console.log("[Analytics] Page view:", pageView);
631
631
  }
632
632
  try {
633
- const response = await fetch(`${apiUrl}/api/public/analytics/page-view`, {
634
- method: "POST",
635
- headers: {
636
- "Content-Type": "application/json",
637
- "x-api-key": apiKey
633
+ const response = await sonorFetch(
634
+ `${apiUrl}/api/public/analytics/page-view`,
635
+ {
636
+ method: "POST",
637
+ headers: { "Content-Type": "application/json" },
638
+ body: JSON.stringify(pageView)
638
639
  },
639
- body: JSON.stringify(pageView)
640
- });
640
+ { apiKey, retries: 0 }
641
+ );
641
642
  if (debug) {
642
643
  console.log("[Analytics] Page view response:", response.status, response.statusText, "url:", `${apiUrl}/api/public/analytics/page-view`);
643
644
  }
@@ -712,14 +713,15 @@ function AnalyticsProvider({
712
713
  console.log("[Analytics] Journey step:", sessionData);
713
714
  }
714
715
  try {
715
- await fetch(`${apiUrl}/api/public/analytics/session`, {
716
- method: "POST",
717
- headers: {
718
- "Content-Type": "application/json",
719
- "x-api-key": apiKey
716
+ await sonorFetch(
717
+ `${apiUrl}/api/public/analytics/session`,
718
+ {
719
+ method: "POST",
720
+ headers: { "Content-Type": "application/json" },
721
+ body: JSON.stringify(sessionData)
720
722
  },
721
- body: JSON.stringify(sessionData)
722
- });
723
+ { apiKey, retries: 0 }
724
+ );
723
725
  } catch (error) {
724
726
  if (debug) console.error("[Analytics] Error tracking journey:", error);
725
727
  }
@@ -736,13 +738,7 @@ function AnalyticsProvider({
736
738
  previousPageDuration: Math.round((Date.now() - pageEnterTimeRef.current) / 1e3),
737
739
  previousPageScrollDepth: currentScrollDepthRef.current
738
740
  });
739
- if (navigator.sendBeacon) {
740
- const blob = new Blob([payload], { type: "application/json" });
741
- navigator.sendBeacon(
742
- `${apiUrl}/api/public/analytics/session?key=${encodeURIComponent(apiKey)}`,
743
- blob
744
- );
745
- }
741
+ sonorBeacon(`${apiUrl}/api/public/analytics/session`, payload, apiKey);
746
742
  };
747
743
  window.addEventListener("pagehide", handleUnload);
748
744
  return () => {
@@ -806,26 +802,22 @@ function AnalyticsProvider({
806
802
  totalTimeSeconds: totalTime,
807
803
  deviceType: getDeviceType()
808
804
  });
809
- if (useBeacon && navigator.sendBeacon) {
810
- const blob = new Blob([payload], { type: "application/json" });
811
- new Headers({ "x-api-key": apiKey });
812
- navigator.sendBeacon(
813
- `${apiUrl}/api/public/analytics/scroll-depth?key=${encodeURIComponent(apiKey)}`,
814
- blob
815
- );
805
+ if (useBeacon) {
806
+ sonorBeacon(`${apiUrl}/api/public/analytics/scroll-depth`, payload, apiKey);
816
807
  if (debug) console.log("[Analytics] Scroll depth (beacon):", { maxDepth, totalTime });
817
808
  } else {
818
809
  try {
819
- await fetch(`${apiUrl}/api/public/analytics/scroll-depth`, {
820
- method: "POST",
821
- headers: {
822
- "Content-Type": "application/json",
823
- "x-api-key": apiKey
810
+ await sonorFetch(
811
+ `${apiUrl}/api/public/analytics/scroll-depth`,
812
+ {
813
+ method: "POST",
814
+ headers: { "Content-Type": "application/json" },
815
+ body: payload,
816
+ keepalive: true
817
+ // Allows request to outlive the page
824
818
  },
825
- body: payload,
826
- keepalive: true
827
- // Allows request to outlive the page
828
- });
819
+ { apiKey, retries: 0 }
820
+ );
829
821
  if (debug) console.log("[Analytics] Scroll depth:", { maxDepth, totalTime });
830
822
  } catch (error) {
831
823
  if (debug) console.error("[Analytics] Error tracking scroll depth:", error);
@@ -892,14 +884,15 @@ function AnalyticsProvider({
892
884
  if (debug) console.log("[Analytics] Click:", clickData);
893
885
  scheduleIdleTask(async () => {
894
886
  try {
895
- await fetch(`${apiUrl}/api/public/analytics/heatmap-click`, {
896
- method: "POST",
897
- headers: {
898
- "Content-Type": "application/json",
899
- "x-api-key": apiKey
887
+ await sonorFetch(
888
+ `${apiUrl}/api/public/analytics/heatmap-click`,
889
+ {
890
+ method: "POST",
891
+ headers: { "Content-Type": "application/json" },
892
+ body: JSON.stringify(clickData)
900
893
  },
901
- body: JSON.stringify(clickData)
902
- });
894
+ { apiKey, retries: 0 }
895
+ );
903
896
  } catch (error) {
904
897
  if (debug) console.error("[Analytics] Error tracking click:", error);
905
898
  }
@@ -934,14 +927,15 @@ function AnalyticsProvider({
934
927
  console.log("[Analytics] Event:", event);
935
928
  }
936
929
  try {
937
- const response = await fetch(`${apiUrl}/api/public/analytics/event`, {
938
- method: "POST",
939
- headers: {
940
- "Content-Type": "application/json",
941
- "x-api-key": apiKey
930
+ const response = await sonorFetch(
931
+ `${apiUrl}/api/public/analytics/event`,
932
+ {
933
+ method: "POST",
934
+ headers: { "Content-Type": "application/json" },
935
+ body: JSON.stringify(event)
942
936
  },
943
- body: JSON.stringify(event)
944
- });
937
+ { apiKey, retries: 0 }
938
+ );
945
939
  if (!response.ok && debug) {
946
940
  console.error("[Analytics] Error tracking event:", response.statusText);
947
941
  }
@@ -976,14 +970,15 @@ function AnalyticsProvider({
976
970
  console.log("[Analytics] Conversion:", conversion);
977
971
  }
978
972
  try {
979
- const response = await fetch(`${apiUrl}/api/public/analytics/conversion`, {
980
- method: "POST",
981
- headers: {
982
- "Content-Type": "application/json",
983
- "x-api-key": apiKey
973
+ const response = await sonorFetch(
974
+ `${apiUrl}/api/public/analytics/conversion`,
975
+ {
976
+ method: "POST",
977
+ headers: { "Content-Type": "application/json" },
978
+ body: JSON.stringify(conversion)
984
979
  },
985
- body: JSON.stringify(conversion)
986
- });
980
+ { apiKey, retries: 0 }
981
+ );
987
982
  if (!response.ok && debug) {
988
983
  console.error("[Analytics] Error tracking conversion:", response.statusText);
989
984
  }
@@ -1032,5 +1027,5 @@ function useTrackEvent() {
1032
1027
  }
1033
1028
 
1034
1029
  export { AnalyticsProvider, WebVitals, useAnalytics, useAnalyticsOptional, useTrackEvent };
1035
- //# sourceMappingURL=chunk-65SMJBHW.mjs.map
1036
- //# sourceMappingURL=chunk-65SMJBHW.mjs.map
1030
+ //# sourceMappingURL=chunk-IR5UC236.mjs.map
1031
+ //# sourceMappingURL=chunk-IR5UC236.mjs.map