@nexushub/client 0.8.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -1,18 +1,428 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
3
 
4
- var _chunkWZ47UVA2cjs = require('./chunk-WZ47UVA2.cjs');
5
4
 
6
- // src/components/NexusProvider.tsx
7
5
 
8
6
 
9
7
 
10
8
 
9
+ var _chunkZ7UZC7IKcjs = require('./chunk-Z7UZC7IK.cjs');
10
+
11
+ // src/components/NexusRenderer.tsx
12
+
13
+
11
14
 
12
15
 
13
16
 
14
17
 
15
18
  var _react = require('react'); var _react2 = _interopRequireDefault(_react);
19
+ var _reactdom = require('react-dom');
20
+ var _jsxruntime = require('react/jsx-runtime');
21
+ function cn(...classes) {
22
+ return classes.filter(Boolean).join(" ");
23
+ }
24
+ var DEFAULT_CHART = {
25
+ chartType: "bar",
26
+ title: "Untitled Chart",
27
+ categories: ["Category A", "Category B", "Category C", "Category D"],
28
+ series: [
29
+ {
30
+ label: "Series 1",
31
+ color: "#6366f1",
32
+ data: [40, 65, 30, 80]
33
+ }
34
+ ],
35
+ showLegend: true,
36
+ showGrid: true,
37
+ width: 560,
38
+ height: 320,
39
+ sourceNote: null
40
+ };
41
+ var FALLBACK_CHART_COLORS = [
42
+ "#6366f1",
43
+ "#f97316",
44
+ "#10b981",
45
+ "#ec4899",
46
+ "#0ea5e9",
47
+ "#eab308",
48
+ "#a855f7",
49
+ "#ef4444",
50
+ "#14b8a6",
51
+ "#84cc16"
52
+ ];
53
+ function decodeChartSpec(raw) {
54
+ if (!raw) return null;
55
+ try {
56
+ const decoded = JSON.parse(
57
+ decodeURIComponent(atob(raw))
58
+ );
59
+ if (!decoded || typeof decoded !== "object") return null;
60
+ const categories = Array.isArray(decoded.categories) ? decoded.categories.map(String) : DEFAULT_CHART.categories;
61
+ const series = Array.isArray(decoded.series) ? decoded.series.filter(Boolean).map((s, index) => ({
62
+ label: typeof s.label === "string" ? s.label : `Series ${index + 1}`,
63
+ color: typeof s.color === "string" ? s.color : FALLBACK_CHART_COLORS[index % FALLBACK_CHART_COLORS.length],
64
+ data: Array.isArray(s.data) ? s.data.map((v) => {
65
+ const n = Number(v);
66
+ return Number.isFinite(n) ? n : 0;
67
+ }) : []
68
+ })) : DEFAULT_CHART.series;
69
+ return {
70
+ ...DEFAULT_CHART,
71
+ ...decoded,
72
+ categories,
73
+ series: series.length ? series : DEFAULT_CHART.series,
74
+ chartType: ["bar", "line", "pie", "donut", "area"].includes(decoded.chartType) ? decoded.chartType : DEFAULT_CHART.chartType,
75
+ title: typeof decoded.title === "string" ? decoded.title : DEFAULT_CHART.title,
76
+ showLegend: typeof decoded.showLegend === "boolean" ? decoded.showLegend : DEFAULT_CHART.showLegend,
77
+ showGrid: typeof decoded.showGrid === "boolean" ? decoded.showGrid : DEFAULT_CHART.showGrid,
78
+ width: typeof decoded.width === "number" && decoded.width > 0 ? decoded.width : DEFAULT_CHART.width,
79
+ height: typeof decoded.height === "number" && decoded.height > 0 ? decoded.height : DEFAULT_CHART.height,
80
+ sourceNote: typeof decoded.sourceNote === "string" ? decoded.sourceNote : null
81
+ };
82
+ } catch (e2) {
83
+ return null;
84
+ }
85
+ }
86
+ function recoverChartFromElement(element) {
87
+ const spec = decodeChartSpec(element.getAttribute("data-chart-spec"));
88
+ if (spec) return spec;
89
+ const title = element.getAttribute("data-chart-title") || DEFAULT_CHART.title;
90
+ const chartType = element.getAttribute("data-chart-type");
91
+ return {
92
+ ...DEFAULT_CHART,
93
+ title,
94
+ chartType: ["bar", "line", "pie", "donut", "area"].includes(chartType) ? chartType : "bar"
95
+ };
96
+ }
97
+ function prepareDocumentHTML(html) {
98
+ if (typeof window === "undefined") return html;
99
+ const parser = new DOMParser();
100
+ const doc = parser.parseFromString(html, "text/html");
101
+ doc.querySelectorAll("script, noscript").forEach((node) => node.remove());
102
+ doc.querySelectorAll("*").forEach((element) => {
103
+ [...element.attributes].forEach((attr) => {
104
+ if (attr.name.toLowerCase().startsWith("on")) {
105
+ element.removeAttribute(attr.name);
106
+ }
107
+ });
108
+ const href = element.getAttribute("href");
109
+ const src = element.getAttribute("src");
110
+ if (href && /^\s*javascript:/i.test(href)) element.removeAttribute("href");
111
+ if (src && /^\s*javascript:/i.test(src)) element.removeAttribute("src");
112
+ });
113
+ return doc.body.innerHTML;
114
+ }
115
+ function NativeNexusChart({ chart }) {
116
+ const [hoveredValue, setHoveredValue] = _react.useState.call(void 0, null);
117
+ const categories = chart.categories;
118
+ const series = chart.series;
119
+ const allNumbers = series.flatMap((s) => s.data);
120
+ const maxVal = Math.max(...allNumbers, 10);
121
+ const minVal = Math.min(0, ...allNumbers);
122
+ const width = 560;
123
+ const height = Math.max(220, Math.min(chart.height || 300, 450));
124
+ const padLeft = 45;
125
+ const padRight = 20;
126
+ const padTop = 20;
127
+ const padBottom = 35;
128
+ const plotW = width - padLeft - padRight;
129
+ const plotH = height - padTop - padBottom;
130
+ const getY = (val) => {
131
+ const range = maxVal - minVal;
132
+ return padTop + plotH - (val - minVal) / range * plotH;
133
+ };
134
+ const getX = (catIndex) => {
135
+ return padLeft + catIndex / Math.max(categories.length - 1, 1) * plotW;
136
+ };
137
+ const renderGridAndAxes = () => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
138
+ chart.showGrid && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: [0, 0.25, 0.5, 0.75, 1].map((pct, i) => {
139
+ const y = padTop + plotH * (1 - pct);
140
+ const val = Math.round(minVal + (maxVal - minVal) * pct);
141
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "g", { children: [
142
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
143
+ "line",
144
+ {
145
+ x1: padLeft,
146
+ y1: y,
147
+ x2: width - padRight,
148
+ y2: y,
149
+ stroke: "currentColor",
150
+ strokeOpacity: 0.08,
151
+ strokeDasharray: "3 3"
152
+ }
153
+ ),
154
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
155
+ "text",
156
+ {
157
+ x: padLeft - 8,
158
+ y: y + 3,
159
+ textAnchor: "end",
160
+ fontSize: 10,
161
+ fill: "currentColor",
162
+ opacity: 0.5,
163
+ className: "font-mono",
164
+ children: val
165
+ }
166
+ )
167
+ ] }, `grid-${i}`);
168
+ }) }),
169
+ categories.map((cat, i) => {
170
+ const x = categories.length === 1 ? padLeft + plotW / 2 : getX(i);
171
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
172
+ "text",
173
+ {
174
+ x,
175
+ y: height - 10,
176
+ textAnchor: "middle",
177
+ fontSize: 11,
178
+ fill: "currentColor",
179
+ opacity: 0.6,
180
+ className: "font-mono",
181
+ children: cat
182
+ },
183
+ `cat-${i}`
184
+ );
185
+ })
186
+ ] });
187
+ const renderBars = () => {
188
+ const groupW = plotW / categories.length;
189
+ const barWidth = Math.max(6, Math.min(28, groupW * 0.7 / series.length));
190
+ return categories.map((cat, catIdx) => {
191
+ const groupCenterX = padLeft + catIdx * groupW + groupW / 2;
192
+ const startX = groupCenterX - series.length * barWidth / 2;
193
+ return series.map((s, sIdx) => {
194
+ const val = s.data[catIdx] || 0;
195
+ const barH = (val - minVal) / (maxVal - minVal) * plotH;
196
+ const x = startX + sIdx * barWidth;
197
+ const y = padTop + plotH - barH;
198
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
199
+ "rect",
200
+ {
201
+ x,
202
+ y,
203
+ width: barWidth - 2,
204
+ height: Math.max(barH, 2),
205
+ rx: 3,
206
+ fill: s.color,
207
+ opacity: 0.9,
208
+ className: "transition-all duration-300 hover:opacity-100 cursor-pointer",
209
+ onMouseEnter: () => setHoveredValue(`${cat} \u2022 ${s.label}: ${val.toLocaleString()}`),
210
+ onMouseLeave: () => setHoveredValue(null)
211
+ },
212
+ `bar-${catIdx}-${sIdx}`
213
+ );
214
+ });
215
+ });
216
+ };
217
+ const renderLinesOrArea = (isArea) => {
218
+ return series.map((s, sIdx) => {
219
+ const points = s.data.map((val, catIdx) => `${getX(catIdx)},${getY(val)}`).join(" ");
220
+ const areaPoints = `${getX(0)},${padTop + plotH} ${points} ${getX(s.data.length - 1)},${padTop + plotH}`;
221
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "g", { children: [
222
+ isArea && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "polygon", { points: areaPoints, fill: s.color, fillOpacity: 0.15 }),
223
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
224
+ "polyline",
225
+ {
226
+ points,
227
+ fill: "none",
228
+ stroke: s.color,
229
+ strokeWidth: 2.5,
230
+ strokeLinecap: "round",
231
+ strokeLinejoin: "round"
232
+ }
233
+ ),
234
+ s.data.map((val, catIdx) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
235
+ "circle",
236
+ {
237
+ cx: getX(catIdx),
238
+ cy: getY(val),
239
+ r: 4,
240
+ fill: s.color,
241
+ className: "cursor-pointer transition-transform hover:scale-150",
242
+ onMouseEnter: () => setHoveredValue(
243
+ `${categories[catIdx]} \u2022 ${s.label}: ${val.toLocaleString()}`
244
+ ),
245
+ onMouseLeave: () => setHoveredValue(null)
246
+ },
247
+ `dot-${catIdx}`
248
+ ))
249
+ ] }, `series-${sIdx}`);
250
+ });
251
+ };
252
+ const renderPieOrDonut = () => {
253
+ const s = series[0] || { data: [] };
254
+ const total = s.data.reduce((a, b) => a + (Number(b) || 0), 0) || 1;
255
+ let accumulated = 0;
256
+ const cx = width / 2;
257
+ const cy = height / 2;
258
+ const r = Math.min(cx, cy) - 30;
259
+ const isDonut = chart.chartType === "donut";
260
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "g", { transform: `translate(${cx}, ${cy})`, children: [
261
+ categories.map((cat, idx) => {
262
+ const val = s.data[idx] || 0;
263
+ const sliceAngle = val / total * 2 * Math.PI;
264
+ const startAngle = accumulated;
265
+ const endAngle = accumulated + sliceAngle;
266
+ accumulated += sliceAngle;
267
+ const x1 = Math.cos(startAngle) * r;
268
+ const y1 = Math.sin(startAngle) * r;
269
+ const x2 = Math.cos(endAngle) * r;
270
+ const y2 = Math.sin(endAngle) * r;
271
+ const largeArc = sliceAngle > Math.PI ? 1 : 0;
272
+ const color = FALLBACK_CHART_COLORS[idx % FALLBACK_CHART_COLORS.length];
273
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
274
+ "path",
275
+ {
276
+ d: `M 0 0 L ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} Z`,
277
+ fill: color,
278
+ className: "cursor-pointer transition-transform duration-200 hover:scale-105",
279
+ onMouseEnter: () => setHoveredValue(
280
+ `${cat}: ${val.toLocaleString()} (${Math.round(val / total * 100)}%)`
281
+ ),
282
+ onMouseLeave: () => setHoveredValue(null)
283
+ },
284
+ `pie-${idx}`
285
+ );
286
+ }),
287
+ isDonut && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
288
+ "circle",
289
+ {
290
+ cx: 0,
291
+ cy: 0,
292
+ r: r * 0.55,
293
+ className: "fill-white dark:fill-zinc-900"
294
+ }
295
+ )
296
+ ] });
297
+ };
298
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "figure", { className: "nexus-chart-rendered my-6 w-full rounded-2xl border border-zinc-200 bg-white/70 p-4 backdrop-blur-md dark:border-zinc-800 dark:bg-zinc-900/60", children: [
299
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-between mb-2", children: [
300
+ chart.title && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "figcaption", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: chart.title }),
301
+ hoveredValue && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "font-mono text-xs font-medium text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-950/50 px-2 py-0.5 rounded-md", children: hoveredValue })
302
+ ] }),
303
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full overflow-hidden text-zinc-700 dark:text-zinc-300", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
304
+ "svg",
305
+ {
306
+ viewBox: `0 0 ${width} ${height}`,
307
+ className: "w-full h-auto max-h-[360px] overflow-visible",
308
+ children: chart.chartType === "pie" || chart.chartType === "donut" ? renderPieOrDonut() : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
309
+ renderGridAndAxes(),
310
+ chart.chartType === "bar" && renderBars(),
311
+ chart.chartType === "line" && renderLinesOrArea(false),
312
+ chart.chartType === "area" && renderLinesOrArea(true)
313
+ ] })
314
+ }
315
+ ) }),
316
+ chart.showLegend && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex flex-wrap items-center justify-center gap-3 mt-3 text-xs font-medium text-zinc-600 dark:text-zinc-400", children: (chart.chartType === "pie" || chart.chartType === "donut" ? categories : series).map((item, idx) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-1.5", children: [
317
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
318
+ "span",
319
+ {
320
+ className: "h-2.5 w-2.5 rounded-full",
321
+ style: {
322
+ backgroundColor: item.color || FALLBACK_CHART_COLORS[idx % FALLBACK_CHART_COLORS.length]
323
+ }
324
+ }
325
+ ),
326
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: item.label || item })
327
+ ] }, `legend-${idx}`)) }),
328
+ chart.sourceNote && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "figcaption", { className: "mt-2 text-center text-xs text-zinc-400", children: chart.sourceNote })
329
+ ] });
330
+ }
331
+ function NexusRenderer({
332
+ content,
333
+ className,
334
+ onCommentClick,
335
+ onImageClick,
336
+ hydrateCharts = true,
337
+ enableImageInteraction = true,
338
+ documentClassName
339
+ }) {
340
+ const containerRef = _react.useRef.call(void 0, null);
341
+ const mounted = typeof window !== "undefined";
342
+ const preparedHTML = _react.useMemo.call(void 0,
343
+ () => prepareDocumentHTML(content || ""),
344
+ [content]
345
+ );
346
+ const isEmpty = !content || content.trim() === "" || content.trim() === "<p></p>" || content.trim() === "<p><br></p>";
347
+ const handleDocumentClick = _react.useCallback.call(void 0,
348
+ (event) => {
349
+ const target = event.target;
350
+ if (!(target instanceof Element)) return;
351
+ const commentElement = target.closest("[data-comment-id]");
352
+ if (commentElement && onCommentClick) {
353
+ const commentId = commentElement.getAttribute("data-comment-id");
354
+ if (commentId) {
355
+ event.preventDefault();
356
+ onCommentClick(commentId);
357
+ return;
358
+ }
359
+ }
360
+ if (enableImageInteraction && onImageClick && target instanceof HTMLImageElement) {
361
+ const src = target.currentSrc || target.src;
362
+ if (src) {
363
+ event.preventDefault();
364
+ onImageClick(src, target.alt || void 0);
365
+ }
366
+ }
367
+ },
368
+ [enableImageInteraction, onCommentClick, onImageClick]
369
+ );
370
+ const [chartTargets, setChartTargets] = _react.useState.call(void 0, []);
371
+ const updateChartTargets = _react.useCallback.call(void 0, () => {
372
+ if (!hydrateCharts) {
373
+ setChartTargets([]);
374
+ return;
375
+ }
376
+ const container = containerRef.current;
377
+ if (!container) return;
378
+ setChartTargets(
379
+ Array.from(
380
+ container.querySelectorAll("[data-nexus-chart]")
381
+ ).map((element) => ({
382
+ element,
383
+ chart: recoverChartFromElement(element)
384
+ }))
385
+ );
386
+ }, [hydrateCharts]);
387
+ _react.useEffect.call(void 0, () => {
388
+ updateChartTargets();
389
+ }, [preparedHTML, updateChartTargets]);
390
+ if (isEmpty) {
391
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: cn("text-sm italic text-zinc-500", className), children: "No descriptive data provisioned for this node." });
392
+ }
393
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
394
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
395
+ "article",
396
+ {
397
+ ref: containerRef,
398
+ onClick: handleDocumentClick,
399
+ className: cn(
400
+ "nexus-renderer max-w-none w-full min-w-0 text-[15px] leading-7 md:text-base text-zinc-700 dark:text-zinc-300",
401
+ documentClassName,
402
+ className
403
+ ),
404
+ dangerouslySetInnerHTML: { __html: preparedHTML }
405
+ }
406
+ ),
407
+ mounted && hydrateCharts && chartTargets.map(
408
+ ({ element, chart }, index) => _reactdom.createPortal.call(void 0,
409
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, NativeNexusChart, { chart }, `nexus-chart-${index}`),
410
+ element
411
+ )
412
+ )
413
+ ] });
414
+ }
415
+
416
+ // src/components/NexusProvider.tsx
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+
16
426
  var _navigation = require('next/navigation');
17
427
 
18
428
  // src/content/cache-implementations.ts
@@ -139,7 +549,7 @@ var MemoryCache = class {
139
549
  try {
140
550
  const jsonString = JSON.stringify(data);
141
551
  return new Blob([jsonString]).size;
142
- } catch (e2) {
552
+ } catch (e3) {
143
553
  return 0;
144
554
  }
145
555
  }
@@ -199,7 +609,7 @@ var BrowserCache = class {
199
609
  return null;
200
610
  }
201
611
  return entry.data;
202
- } catch (e3) {
612
+ } catch (e4) {
203
613
  this.delete(key);
204
614
  return null;
205
615
  }
@@ -257,7 +667,7 @@ var BrowserCache = class {
257
667
  oldestTime = entry.metadata.timestamp;
258
668
  oldestKey = key;
259
669
  }
260
- } catch (e4) {
670
+ } catch (e5) {
261
671
  localStorage.removeItem(key);
262
672
  }
263
673
  }
@@ -282,7 +692,7 @@ var LocalCacheProxy = class {
282
692
  async getInstance() {
283
693
  if (this.instance) return this.instance;
284
694
  if (typeof window === "undefined") {
285
- const mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./local-cache-server-S5KIABVH.cjs")));
695
+ const mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./local-cache-server-AMGC6BOZ.cjs")));
286
696
  this.instance = new mod.LocalCache(this.cachePath);
287
697
  } else {
288
698
  const mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./local-cache-client-IGAL7SZ6.cjs")));
@@ -314,90 +724,69 @@ var LocalCacheProxy = class {
314
724
  // src/content/strategies.ts
315
725
  var RateLimiter = class {
316
726
  constructor(config) {
317
- this.requests = [];
318
727
  this.config = config;
728
+ this.requests = [];
319
729
  }
320
730
  async checkLimit() {
321
731
  while (true) {
322
732
  const now = Date.now();
323
- const windowStart = now - this.config.timeWindow;
324
- this.requests = this.requests.filter((time) => time > windowStart);
733
+ const cutoff = now - this.config.timeWindow;
734
+ this.requests = this.requests.filter((t) => t > cutoff);
325
735
  if (this.requests.length < this.config.maxRequests) {
326
736
  this.requests.push(now);
327
737
  return;
328
738
  }
329
- const oldestRequest = this.requests[0];
330
- const waitTime = oldestRequest + this.config.timeWindow - now;
331
- if (waitTime > 0) {
332
- await new Promise((resolve) => setTimeout(resolve, waitTime));
333
- } else {
334
- }
739
+ await new Promise((r) => setTimeout(r, Math.max(1, this.requests[0] + this.config.timeWindow - now)));
335
740
  }
336
741
  }
337
742
  getStats() {
338
743
  const now = Date.now();
339
- const windowStart = now - this.config.timeWindow;
340
- const currentRequests = this.requests.filter(
341
- (time) => time > windowStart
342
- ).length;
343
- return { currentRequests, limit: this.config.maxRequests };
744
+ return { currentRequests: this.requests.filter((t) => t > now - this.config.timeWindow).length, limit: this.config.maxRequests };
344
745
  }
345
746
  };
346
747
  var ExponentialBackoff = class {
347
748
  constructor(config) {
749
+ this.config = config;
348
750
  this.config = { jitter: true, ...config };
349
751
  }
350
752
  async execute(fn, onRetry) {
351
- let lastError = new Error("Unknown error");
352
- for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
753
+ let last;
754
+ for (let a = 0; a <= this.config.maxRetries; a++) {
353
755
  try {
354
756
  return await fn();
355
- } catch (error) {
356
- lastError = error;
357
- if (this.isClientError(error) && !this.isRateLimitError(error)) {
358
- throw error;
359
- }
360
- if (attempt === this.config.maxRetries) break;
361
- const delay = this.calculateDelay(attempt);
362
- if (onRetry) onRetry(attempt + 1, delay, error);
363
- await new Promise((resolve) => setTimeout(resolve, delay));
757
+ } catch (e) {
758
+ last = e;
759
+ if (_optionalChain([e, 'optionalAccess', _11 => _11.code]) === "ABORTED" || _optionalChain([e, 'optionalAccess', _12 => _12.code]) === "VALIDATION_ERROR" || _optionalChain([e, 'optionalAccess', _13 => _13.status]) >= 400 && _optionalChain([e, 'optionalAccess', _14 => _14.status]) < 500 && _optionalChain([e, 'optionalAccess', _15 => _15.status]) !== 429) throw e;
760
+ if (a === this.config.maxRetries) break;
761
+ let delay = Math.min(this.config.maxDelay, this.config.baseDelay * 2 ** a);
762
+ if (this.config.jitter) delay *= 0.5 + Math.random();
763
+ _optionalChain([onRetry, 'optionalCall', _16 => _16(a + 1, delay, e)]);
764
+ await new Promise((r) => setTimeout(r, delay));
364
765
  }
365
766
  }
366
- throw lastError;
367
- }
368
- calculateDelay(attempt) {
369
- let delay = this.config.baseDelay * Math.pow(2, attempt);
370
- delay = Math.min(delay, this.config.maxDelay);
371
- if (this.config.jitter) {
372
- delay = delay * (0.5 + Math.random());
373
- }
374
- return delay;
375
- }
376
- isClientError(error) {
377
- return _optionalChain([error, 'optionalAccess', _11 => _11.status]) >= 400 && _optionalChain([error, 'optionalAccess', _12 => _12.status]) < 500;
378
- }
379
- isRateLimitError(error) {
380
- return _optionalChain([error, 'optionalAccess', _13 => _13.status]) === 429;
767
+ throw last;
381
768
  }
382
769
  };
770
+ var CircuitState = /* @__PURE__ */ ((CircuitState2) => {
771
+ CircuitState2[CircuitState2["CLOSED"] = 0] = "CLOSED";
772
+ CircuitState2[CircuitState2["OPEN"] = 1] = "OPEN";
773
+ CircuitState2[CircuitState2["HALF_OPEN"] = 2] = "HALF_OPEN";
774
+ return CircuitState2;
775
+ })(CircuitState || {});
383
776
  var CircuitBreaker = class {
384
- constructor() {
777
+ constructor(threshold = 5, reset = 3e4) {
778
+ this.threshold = threshold;
779
+ this.reset = reset;
385
780
  this.state = 0 /* CLOSED */;
386
781
  this.failures = 0;
387
- this.lastFailureTime = 0;
388
- this.failureThreshold = 5;
389
- this.resetTimeout = 3e4;
782
+ this.lastFailure = 0;
390
783
  }
391
- // 30 seconds
392
784
  isOpen() {
393
- if (this.state === 1 /* OPEN */) {
394
- if (Date.now() - this.lastFailureTime > this.resetTimeout) {
395
- this.state = 2 /* HALF_OPEN */;
396
- return false;
397
- }
398
- return true;
785
+ if (this.state === 1 /* OPEN */ && Date.now() - this.lastFailure >= this.reset) {
786
+ this.state = 2 /* HALF_OPEN */;
787
+ return false;
399
788
  }
400
- return false;
789
+ return this.state === 1 /* OPEN */;
401
790
  }
402
791
  recordSuccess() {
403
792
  this.failures = 0;
@@ -405,110 +794,33 @@ var CircuitBreaker = class {
405
794
  }
406
795
  recordFailure() {
407
796
  this.failures++;
408
- this.lastFailureTime = Date.now();
409
- if (this.failures >= this.failureThreshold) {
410
- this.state = 1 /* OPEN */;
411
- if (process.env.NODE_ENV === "development") {
412
- console.warn(
413
- "[NexusHub] \u{1F50C} Circuit Breaker OPEN. Pausing network requests."
414
- );
415
- }
416
- }
797
+ this.lastFailure = Date.now();
798
+ if (this.failures >= this.threshold) this.state = 1 /* OPEN */;
799
+ }
800
+ getState() {
801
+ return CircuitState[this.state];
417
802
  }
418
803
  };
419
804
  var RequestBatcher = class {
420
- constructor(batchWindow = 10, maxBatchSize = 20) {
421
- this.batchWindow = batchWindow;
805
+ constructor(windowMs = 10, maxBatchSize = 50) {
806
+ this.windowMs = windowMs;
422
807
  this.maxBatchSize = maxBatchSize;
423
- this.batch = [];
424
- this.processing = false;
425
- }
426
- async schedule(key, request) {
427
- return new Promise((resolve, reject) => {
428
- this.batch.push({ key, resolve, reject });
429
- if (this.batch.length >= this.maxBatchSize) {
430
- this.processBatch(request);
431
- } else if (!this.batchTimeout) {
432
- this.batchTimeout = setTimeout(
433
- () => this.processBatch(request),
434
- this.batchWindow
435
- );
436
- }
808
+ this.pending = /* @__PURE__ */ new Map();
809
+ }
810
+ schedule(key, request) {
811
+ const existing = this.pending.get(key);
812
+ if (existing) return existing;
813
+ const promise = new Promise((resolve, reject) => setTimeout(() => request().then(resolve, reject), this.windowMs));
814
+ this.pending.set(key, promise);
815
+ promise.finally(() => this.pending.delete(key)).catch(() => {
437
816
  });
817
+ return promise;
438
818
  }
439
- async processBatch(request) {
440
- if (this.processing || this.batch.length === 0) return;
441
- this.processing = true;
442
- if (this.batchTimeout) {
443
- clearTimeout(this.batchTimeout);
444
- this.batchTimeout = void 0;
445
- }
446
- const currentBatch = [...this.batch];
447
- this.batch = [];
448
- try {
449
- const result = await request();
450
- currentBatch.forEach((item) => item.resolve(result));
451
- } catch (error) {
452
- currentBatch.forEach((item) => item.reject(error));
453
- } finally {
454
- this.processing = false;
455
- if (this.batch.length > 0) {
456
- setTimeout(() => this.processBatch(request), 0);
457
- }
458
- }
819
+ clear() {
820
+ this.pending.clear();
459
821
  }
460
822
  };
461
823
 
462
- // src/content/utils.ts
463
- function validateSlug(slug) {
464
- if (!slug || typeof slug !== "string") {
465
- throw new Error("Slug must be a non-empty string");
466
- }
467
- if (!/^[a-z0-9-_]+$/.test(slug)) {
468
- throw new Error("Slug can only contain lowercase letters, numbers, hyphens, and underscores");
469
- }
470
- }
471
- function normalizeQuery(query) {
472
- const normalized = { ...query };
473
- normalized.page = Math.max(1, normalized.page || 1);
474
- normalized.limit = Math.min(100, Math.max(1, normalized.limit || 10));
475
- normalized.order = normalized.order || "desc";
476
- if (normalized.page < 1) {
477
- throw new Error("Page must be greater than 0");
478
- }
479
- if (normalized.limit < 1 || normalized.limit > 100) {
480
- throw new Error("Limit must be between 1 and 100");
481
- }
482
- return normalized;
483
- }
484
- function buildQueryString(query) {
485
- const params = new URLSearchParams();
486
- if (query.page) params.append("page", query.page.toString());
487
- if (query.limit) params.append("limit", query.limit.toString());
488
- if (query.sort) params.append("sort", query.sort);
489
- if (query.order) params.append("order", query.order);
490
- if (query.search) params.append("search", query.search);
491
- if (_optionalChain([query, 'access', _14 => _14.include, 'optionalAccess', _15 => _15.length])) {
492
- params.append("include", query.include.join(","));
493
- }
494
- if (_optionalChain([query, 'access', _16 => _16.fields, 'optionalAccess', _17 => _17.length])) {
495
- params.append("fields", query.fields.join(","));
496
- }
497
- if (query.filter) {
498
- params.append("filter", JSON.stringify(query.filter));
499
- }
500
- return params.toString();
501
- }
502
- function measurePerformance(name, fn) {
503
- const start = performance.now();
504
- const result = fn();
505
- const end = performance.now();
506
- if (process.env.NODE_ENV === "development") {
507
- console.log(`\u23F1\uFE0F ${name}: ${(end - start).toFixed(2)}ms`);
508
- }
509
- return { result, duration: end - start };
510
- }
511
-
512
824
  // src/content/index.ts
513
825
  var ContentEngine = class {
514
826
  constructor(config) {
@@ -541,11 +853,17 @@ var ContentEngine = class {
541
853
  window.addEventListener("beforeunload", this.cleanup.bind(this));
542
854
  }
543
855
  }
856
+ /** Update runtime configuration without exposing internal mutation. */
857
+ updateConfig(config) {
858
+ this.config = config;
859
+ this.defaultRevalidate = _nullishCoalesce(config.revalidateTime, () => ( false));
860
+ this.cacheStrategy = config.cacheStrategy || "memory";
861
+ }
544
862
  /**
545
863
  * Fetch a Single Page with full strategy pipeline
546
864
  */
547
865
  async getPage(slug, options = {}) {
548
- const { result, duration } = measurePerformance(
866
+ const { result, duration } = _chunkZ7UZC7IKcjs.measurePerformance.call(void 0,
549
867
  `getPage("${slug}")`,
550
868
  () => this._getPage(slug, options)
551
869
  );
@@ -557,7 +875,7 @@ var ContentEngine = class {
557
875
  return result;
558
876
  }
559
877
  async _getPage(slug, options = {}) {
560
- validateSlug(slug);
878
+ _chunkZ7UZC7IKcjs.validateSlug.call(void 0, slug);
561
879
  const {
562
880
  revalidate = this.defaultRevalidate,
563
881
  tags = [],
@@ -613,7 +931,7 @@ var ContentEngine = class {
613
931
  * Fetch a Collection (Optimized)
614
932
  */
615
933
  async getCollection(collectionId, query = {}, options = {}) {
616
- const { result, duration } = measurePerformance(
934
+ const { result, duration } = _chunkZ7UZC7IKcjs.measurePerformance.call(void 0,
617
935
  `getCollection("${collectionId}")`,
618
936
  () => this._getCollection(collectionId, query, options)
619
937
  );
@@ -625,14 +943,14 @@ var ContentEngine = class {
625
943
  return result;
626
944
  }
627
945
  async _getCollection(collectionId, query = {}, options = {}) {
628
- const normalizedQuery = normalizeQuery(query);
946
+ const normalizedQuery = _chunkZ7UZC7IKcjs.normalizeQuery.call(void 0, query);
629
947
  const {
630
948
  revalidate = this.defaultRevalidate,
631
949
  tags = [],
632
950
  forceRefresh = false,
633
951
  includeMetadata = false
634
952
  } = options;
635
- const queryString = buildQueryString(normalizedQuery);
953
+ const queryString = _chunkZ7UZC7IKcjs.buildQueryString.call(void 0, normalizedQuery);
636
954
  const cacheKey = `collection:${collectionId}:${queryString}`;
637
955
  const cached = await this.checkCaches(
638
956
  cacheKey,
@@ -666,7 +984,7 @@ var ContentEngine = class {
666
984
  });
667
985
  return result;
668
986
  }
669
- } catch (e5) {
987
+ } catch (e6) {
670
988
  }
671
989
  }
672
990
  if (this.circuitBreaker.isOpen()) throw new Error("Circuit open");
@@ -699,7 +1017,7 @@ var ContentEngine = class {
699
1017
  }
700
1018
  }
701
1019
  /**
702
- * Fetch Global Settings with nested includes support
1020
+ * Fetch Global Settings
703
1021
  */
704
1022
  async getGlobals(options = {}) {
705
1023
  const {
@@ -732,7 +1050,7 @@ var ContentEngine = class {
732
1050
  });
733
1051
  return localGlobals;
734
1052
  }
735
- } catch (e6) {
1053
+ } catch (e7) {
736
1054
  }
737
1055
  }
738
1056
  try {
@@ -751,6 +1069,9 @@ var ContentEngine = class {
751
1069
  revalidate
752
1070
  });
753
1071
  if (!res.ok) {
1072
+ if (res.status === 408) {
1073
+ throw new Error("Request timeout");
1074
+ }
754
1075
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
755
1076
  }
756
1077
  const json = await res.json();
@@ -785,7 +1106,7 @@ var ContentEngine = class {
785
1106
  }
786
1107
  return this.requestBatcher.schedule(cacheKey, async () => {
787
1108
  const params = new URLSearchParams();
788
- if (_optionalChain([options, 'access', _18 => _18.include, 'optionalAccess', _19 => _19.length])) {
1109
+ if (_optionalChain([options, 'access', _17 => _17.include, 'optionalAccess', _18 => _18.length])) {
789
1110
  params.append("include", options.include.join(","));
790
1111
  }
791
1112
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}/${itemId}?${params}`;
@@ -799,12 +1120,12 @@ var ContentEngine = class {
799
1120
  method: "GET",
800
1121
  headers: this.getHeaders(),
801
1122
  tags: fetchTags,
802
- // ?? not || — see the constructor comment on defaultRevalidate for
803
- // why: an explicit `revalidate: 0` on this call must not be
804
- // discarded in favor of the engine's default.
805
1123
  revalidate: _nullishCoalesce(options.revalidate, () => ( this.defaultRevalidate))
806
1124
  });
807
1125
  if (!res.ok) {
1126
+ if (res.status === 408) {
1127
+ throw new Error("Request timeout");
1128
+ }
808
1129
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
809
1130
  }
810
1131
  const json = await res.json();
@@ -824,10 +1145,10 @@ var ContentEngine = class {
824
1145
  q: query,
825
1146
  limit: (options.limit || 20).toString()
826
1147
  });
827
- if (_optionalChain([options, 'access', _20 => _20.collections, 'optionalAccess', _21 => _21.length])) {
1148
+ if (_optionalChain([options, 'access', _19 => _19.collections, 'optionalAccess', _20 => _20.length])) {
828
1149
  params.append("collections", options.collections.join(","));
829
1150
  }
830
- if (_optionalChain([options, 'access', _22 => _22.fields, 'optionalAccess', _23 => _23.length])) {
1151
+ if (_optionalChain([options, 'access', _21 => _21.fields, 'optionalAccess', _22 => _22.length])) {
831
1152
  params.append("fields", options.fields.join(","));
832
1153
  }
833
1154
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/search?${params}`;
@@ -836,12 +1157,15 @@ var ContentEngine = class {
836
1157
  headers: this.getHeaders()
837
1158
  });
838
1159
  if (!res.ok) {
1160
+ if (res.status === 408) {
1161
+ throw new Error("Request timeout");
1162
+ }
839
1163
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
840
1164
  }
841
1165
  return await res.json();
842
1166
  }
843
1167
  /**
844
- * Prefetch content for better performance
1168
+ * Prefetch content
845
1169
  */
846
1170
  async prefetch(urls) {
847
1171
  if (typeof window !== "undefined" && "requestIdleCallback" in window) {
@@ -854,41 +1178,19 @@ var ContentEngine = class {
854
1178
  }
855
1179
  /**
856
1180
  * Robust Real-time Subscriptions (SSE) with Auto-Reconnect
857
- *
858
- * ⚠️ SCOPE — READ BEFORE RELYING ON THIS FOR "instant" UPDATES:
859
- * This connects from the BROWSER TAB it's called in, and on message it
860
- * only clears THIS SDK INSTANCE's in-memory `memoryCache` (via
861
- * invalidateCache below) in THAT browser tab's JS heap. In a typical
862
- * Next.js deployment — and Cloudflare specifically, which is stateless
863
- * per-request at the edge — that is a different process/isolate than the
864
- * one that will render the NEXT server request for this content. So:
865
- * - ✅ Useful for: a client component that reads from `nexus.content`
866
- * directly in the browser and re-renders in place without a page
867
- * navigation (e.g. a live-updating dashboard widget).
868
- * - ❌ NOT sufficient for: "user A edits content in the CMS, user B's
869
- * already-loaded page updates" via a server-rendered page. That
870
- * requires the backend's /api/revalidate webhook (see
871
- * content.service.ts `pingNextRevalidateWebhook`) to have actually
872
- * cleared the *server's* Data Cache, so the NEXT navigation or
873
- * server request picks up fresh data. This SSE channel does not
874
- * replace that — it's a complementary, browser-local optimization.
875
- * If your symptom was "stale content after editing," fix the webhook
876
- * wiring first; treat this method as an enhancement layered on top.
877
1181
  */
878
1182
  subscribeToUpdates(callback) {
879
1183
  if (this.isServer || typeof EventSource === "undefined") {
880
1184
  if (this.config.debug) {
881
1185
  console.warn(
882
- "[NexusHub] subscribeToUpdates() called in a non-browser environment (server render or SSR pass) \u2014 this is expected and safely skipped; EventSource only makes sense client-side."
1186
+ "[NexusHub] subscribeToUpdates() called in a non-browser environment."
883
1187
  );
884
1188
  }
885
1189
  return () => {
886
1190
  };
887
1191
  }
888
1192
  if (!this.config.apiKey) {
889
- console.warn(
890
- "[NexusHub] subscribeToUpdates(): no apiKey configured, the SSE connection will likely be rejected by the backend. Set NEXT_PUBLIC_NEXUS_KEY."
891
- );
1193
+ console.warn("[NexusHub] subscribeToUpdates(): no apiKey configured.");
892
1194
  }
893
1195
  let eventSource = null;
894
1196
  let retryCount = 0;
@@ -926,7 +1228,7 @@ var ContentEngine = class {
926
1228
  callback(data);
927
1229
  };
928
1230
  eventSource.onerror = () => {
929
- _optionalChain([eventSource, 'optionalAccess', _24 => _24.close, 'call', _25 => _25()]);
1231
+ _optionalChain([eventSource, 'optionalAccess', _23 => _23.close, 'call', _24 => _24()]);
930
1232
  if (isClosed) return;
931
1233
  const timeout = Math.min(
932
1234
  1e3 * Math.pow(2, retryCount),
@@ -944,7 +1246,7 @@ var ContentEngine = class {
944
1246
  connect();
945
1247
  return () => {
946
1248
  isClosed = true;
947
- _optionalChain([eventSource, 'optionalAccess', _26 => _26.close, 'call', _27 => _27()]);
1249
+ _optionalChain([eventSource, 'optionalAccess', _25 => _25.close, 'call', _26 => _26()]);
948
1250
  };
949
1251
  }
950
1252
  // --- CACHE MANAGEMENT ---
@@ -986,7 +1288,7 @@ var ContentEngine = class {
986
1288
  }
987
1289
  };
988
1290
  }
989
- } catch (e7) {
1291
+ } catch (e8) {
990
1292
  }
991
1293
  }
992
1294
  }
@@ -1052,6 +1354,9 @@ var ContentEngine = class {
1052
1354
  `Page '${slug}' not found. Check your Dashboard or Seed data.`
1053
1355
  );
1054
1356
  }
1357
+ if (res.status === 408) {
1358
+ throw new Error("Request timeout");
1359
+ }
1055
1360
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
1056
1361
  }
1057
1362
  const json = await res.json();
@@ -1072,7 +1377,7 @@ var ContentEngine = class {
1072
1377
  return cacheEntry;
1073
1378
  }
1074
1379
  async fetchCollection(collectionId, query, cacheKey, tags, revalidate) {
1075
- const params = buildQueryString(query);
1380
+ const params = _chunkZ7UZC7IKcjs.buildQueryString.call(void 0, query);
1076
1381
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}?${params}`;
1077
1382
  const fetchTags = [
1078
1383
  CacheTags.project(this.config.projectId),
@@ -1086,6 +1391,9 @@ var ContentEngine = class {
1086
1391
  revalidate
1087
1392
  });
1088
1393
  if (!res.ok) {
1394
+ if (res.status === 408) {
1395
+ throw new Error("Request timeout");
1396
+ }
1089
1397
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
1090
1398
  }
1091
1399
  const json = await res.json();
@@ -1172,7 +1480,6 @@ var ContentEngine = class {
1172
1480
  const response = await fetch(url, {
1173
1481
  ...fetchOptions,
1174
1482
  ...nextConfig,
1175
- // Inject Next.js tags
1176
1483
  signal: controller.signal
1177
1484
  });
1178
1485
  clearTimeout(id);
@@ -1185,7 +1492,8 @@ var ContentEngine = class {
1185
1492
  getHeaders() {
1186
1493
  const headers = {
1187
1494
  "Content-Type": "application/json",
1188
- "X-Nexus-Client": "client-sdk/1.0.0",
1495
+ "X-Nexus-Client": `client-sdk/${_nullishCoalesce(this.config.sdkVersion, () => ( "1.1.0"))}`,
1496
+ "X-Nexus-Request-ID": typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `nx_${Date.now()}_${Math.random().toString(36).slice(2)}`,
1189
1497
  "X-Nexus-Project": this.config.projectId
1190
1498
  };
1191
1499
  if (this.config.apiKey) {
@@ -1196,13 +1504,22 @@ var ContentEngine = class {
1196
1504
  isCacheValid(metadata) {
1197
1505
  return Date.now() < metadata.expiresAt;
1198
1506
  }
1507
+ /**
1508
+ * 🛡️ FIX: Normalizes timeout errors (AbortError, 408, or Timeout messages)
1509
+ * into clean, standardized "Request timeout" errors.
1510
+ */
1199
1511
  normalizeError(error, context) {
1200
1512
  if (error instanceof Error) {
1201
- if (error.name === "AbortError") {
1513
+ const msg = error.message.toLowerCase();
1514
+ if (error.name === "AbortError" || error.message.includes("408") || msg.includes("timeout") || msg.includes("aborted")) {
1202
1515
  return new Error(`${context}: Request timeout`);
1203
1516
  }
1204
1517
  return error;
1205
1518
  }
1519
+ const str = String(error).toLowerCase();
1520
+ if (str.includes("408") || str.includes("timeout") || str.includes("aborted")) {
1521
+ return new Error(`${context}: Request timeout`);
1522
+ }
1206
1523
  return new Error(`${context}: ${String(error)}`);
1207
1524
  }
1208
1525
  cancelRequests() {
@@ -1221,7 +1538,7 @@ var ContentEngine = class {
1221
1538
 
1222
1539
  // src/analytics/fingerprint.ts
1223
1540
  var cachedEntropy = null;
1224
- var getDeviceEntropy = async () => {
1541
+ var getDeviceEntropy = async (allowFingerprinting = true) => {
1225
1542
  if (cachedEntropy) return cachedEntropy;
1226
1543
  if (typeof window === "undefined") return {};
1227
1544
  const nav = window.navigator;
@@ -1236,7 +1553,7 @@ var getDeviceEntropy = async () => {
1236
1553
  platform: nav.platform,
1237
1554
  language: nav.language,
1238
1555
  touch_support: "ontouchstart" in window || nav.maxTouchPoints > 0,
1239
- canvas_hash: await generateCanvasHash()
1556
+ canvas_hash: allowFingerprinting ? await generateCanvasHash() : void 0
1240
1557
  };
1241
1558
  return cachedEntropy;
1242
1559
  };
@@ -1249,7 +1566,6 @@ var generateCanvasHash = async () => {
1249
1566
  canvas.height = 50;
1250
1567
  ctx.textBaseline = "top";
1251
1568
  ctx.font = '16px "Arial"';
1252
- ctx.textBaseline = "alphabetic";
1253
1569
  ctx.fillStyle = "#f60";
1254
1570
  ctx.fillRect(125, 1, 62, 20);
1255
1571
  ctx.fillStyle = "#069";
@@ -1264,16 +1580,16 @@ var generateCanvasHash = async () => {
1264
1580
  hash = hash & hash;
1265
1581
  }
1266
1582
  return hash.toString(16);
1267
- } catch (e8) {
1583
+ } catch (e9) {
1268
1584
  return "";
1269
1585
  }
1270
1586
  };
1271
- var getVisitorId = async () => {
1587
+ var getVisitorId = async (allowFingerprinting = true) => {
1272
1588
  if (typeof window === "undefined") return "server_visitor";
1273
1589
  const STORAGE_KEY = "nexus_vid";
1274
1590
  let vid = localStorage.getItem(STORAGE_KEY);
1275
1591
  if (!vid) {
1276
- const entropy = await getDeviceEntropy();
1592
+ const entropy = await getDeviceEntropy(allowFingerprinting);
1277
1593
  const random = Math.random().toString(36).substring(2, 15);
1278
1594
  const timestamp = Date.now().toString(36);
1279
1595
  const fingerprint = [
@@ -1281,7 +1597,7 @@ var getVisitorId = async () => {
1281
1597
  entropy.hardware_concurrency,
1282
1598
  entropy.timezone_offset,
1283
1599
  entropy.platform,
1284
- entropy.canvas_hash
1600
+ entropy.canvas_hash || "standard_entropy"
1285
1601
  ].join("|");
1286
1602
  let hash = 0;
1287
1603
  for (let i = 0; i < fingerprint.length; i++) {
@@ -1492,24 +1808,23 @@ var EventStorage = class {
1492
1808
  var eventStorage = new EventStorage();
1493
1809
 
1494
1810
  // src/analytics/tracker.ts
1495
- var SDK_VERSION = "0.1.0";
1496
1811
  var safeGetItem = (key) => {
1497
1812
  try {
1498
1813
  return localStorage.getItem(key);
1499
- } catch (e9) {
1814
+ } catch (e10) {
1500
1815
  return null;
1501
1816
  }
1502
1817
  };
1503
1818
  var safeSetItem = (key, value) => {
1504
1819
  try {
1505
1820
  localStorage.setItem(key, value);
1506
- } catch (e10) {
1821
+ } catch (e11) {
1507
1822
  }
1508
1823
  };
1509
1824
  var safeRemoveItem = (key) => {
1510
1825
  try {
1511
1826
  localStorage.removeItem(key);
1512
- } catch (e11) {
1827
+ } catch (e12) {
1513
1828
  }
1514
1829
  };
1515
1830
  var generateUUID = () => {
@@ -1528,6 +1843,8 @@ var Tracker = class {
1528
1843
  this.visitorId = "";
1529
1844
  this.anonymousId = "";
1530
1845
  this.isFlushing = false;
1846
+ this.flushPromise = null;
1847
+ this.batchSize = 25;
1531
1848
  this.config = config;
1532
1849
  this.endpoint = `${this.config.analyticsUrl}/api/collect`;
1533
1850
  this.circuitBreaker = new CircuitBreaker();
@@ -1538,7 +1855,9 @@ var Tracker = class {
1538
1855
  }
1539
1856
  }
1540
1857
  async initSession() {
1541
- this.visitorId = await getVisitorId();
1858
+ this.visitorId = await getVisitorId(
1859
+ _nullishCoalesce(_optionalChain([this, 'access', _27 => _27.config, 'access', _28 => _28.privacy, 'optionalAccess', _29 => _29.fingerprinting]), () => ( false))
1860
+ );
1542
1861
  let anonId = safeGetItem("nexus_anon_id");
1543
1862
  if (!anonId) {
1544
1863
  anonId = `anon_${generateUUID().replace(/-/g, "")}`;
@@ -1551,8 +1870,8 @@ var Tracker = class {
1551
1870
  const SESSION_TIMEOUT = 30 * 60 * 1e3;
1552
1871
  const isExpired = !sid || !lastActivity || now - parseInt(lastActivity, 10) > SESSION_TIMEOUT;
1553
1872
  if (isExpired) {
1554
- const uuid = generateUUID().replace(/-/g, "").substring(0, 16);
1555
- sid = `sess_${uuid}_${now}`;
1873
+ const uuid2 = generateUUID().replace(/-/g, "").substring(0, 16);
1874
+ sid = `sess_${uuid2}_${now}`;
1556
1875
  safeSetItem("nexus_sid", sid);
1557
1876
  this.sessionStart = now;
1558
1877
  }
@@ -1562,7 +1881,9 @@ var Tracker = class {
1562
1881
  async send(eventType, data = {}, eventName, ecommerce) {
1563
1882
  if (typeof window === "undefined") return;
1564
1883
  safeSetItem("nexus_last_active", Date.now().toString());
1565
- const entropy = await getDeviceEntropy();
1884
+ const entropy = await getDeviceEntropy(
1885
+ _nullishCoalesce(_optionalChain([this, 'access', _30 => _30.config, 'access', _31 => _31.privacy, 'optionalAccess', _32 => _32.fingerprinting]), () => ( false))
1886
+ );
1566
1887
  const perfMetrics = vitalsCollector.getMetricsSnapshot();
1567
1888
  const utmParams = extractUtmParams(window.location.href);
1568
1889
  const payload = {
@@ -1572,7 +1893,8 @@ var Tracker = class {
1572
1893
  anonymousId: this.anonymousId,
1573
1894
  messageId: generateUUID(),
1574
1895
  sentAt: (/* @__PURE__ */ new Date()).toISOString(),
1575
- version: SDK_VERSION,
1896
+ version: _chunkZ7UZC7IKcjs.SDK_VERSION,
1897
+ // Dynamically synced to SDK 1.1.0
1576
1898
  url: window.location.href,
1577
1899
  referrer: document.referrer,
1578
1900
  userAgent: window.navigator.userAgent,
@@ -1590,8 +1912,8 @@ var Tracker = class {
1590
1912
  hardwareConcurrency: entropy.hardware_concurrency,
1591
1913
  deviceMemory: entropy.device_memory,
1592
1914
  pixelRatio: entropy.pixel_ratio,
1593
- canvasFingerprint: entropy.canvas_hash,
1594
- platform: entropy.platform
1915
+ platform: entropy.platform,
1916
+ ..._optionalChain([this, 'access', _33 => _33.config, 'access', _34 => _34.privacy, 'optionalAccess', _35 => _35.fingerprinting]) ? { canvasFingerprint: entropy.canvas_hash } : {}
1595
1917
  },
1596
1918
  visitorIdLocal: this.visitorId,
1597
1919
  utm: utmParams
@@ -1611,53 +1933,69 @@ var Tracker = class {
1611
1933
  });
1612
1934
  }
1613
1935
  async flushQueue(useBeacon = false) {
1614
- if (this.isFlushing) return;
1615
- if (this.circuitBreaker.isOpen()) return;
1616
- this.isFlushing = true;
1617
- try {
1618
- const storedEvents = await eventStorage.peek(20);
1619
- if (storedEvents.length === 0) {
1620
- this.isFlushing = false;
1621
- return;
1622
- }
1623
- const payloads = storedEvents.map((e) => e.payload);
1624
- const promises = payloads.map(
1625
- (event) => fetch(this.endpoint, {
1626
- method: "POST",
1627
- headers: {
1628
- "Content-Type": "application/json",
1629
- Authorization: `Bearer ${this.config.apiKey}`
1630
- },
1631
- body: JSON.stringify(event),
1632
- keepalive: useBeacon
1633
- })
1634
- );
1635
- const results = await Promise.allSettled(promises);
1636
- const successIds = [];
1637
- let failureCount = 0;
1638
- results.forEach((res, index) => {
1639
- if (res.status === "fulfilled" && res.value.ok) {
1640
- successIds.push(storedEvents[index].id);
1936
+ if (this.flushPromise) return this.flushPromise;
1937
+ this.flushPromise = (async () => {
1938
+ if (this.circuitBreaker.isOpen()) return;
1939
+ try {
1940
+ const storedEvents = await eventStorage.peek(this.batchSize);
1941
+ if (!storedEvents.length) return;
1942
+ const payloads = storedEvents.map((e) => e.payload);
1943
+ const headers = {
1944
+ "Content-Type": "application/json",
1945
+ Authorization: this.config.apiKey ? `Bearer ${this.config.apiKey}` : "",
1946
+ "X-Nexus-Client": `analytics/${_chunkZ7UZC7IKcjs.SDK_VERSION}`,
1947
+ "X-Nexus-Project": this.config.projectId
1948
+ };
1949
+ let response;
1950
+ try {
1951
+ response = await fetch(
1952
+ `${this.config.analyticsUrl}/api/collect/batch`,
1953
+ {
1954
+ method: "POST",
1955
+ headers,
1956
+ body: JSON.stringify({ events: payloads }),
1957
+ keepalive: useBeacon
1958
+ }
1959
+ );
1960
+ } catch (e13) {
1961
+ response = void 0;
1962
+ }
1963
+ if (!response || response.status === 404 || response.status === 405) {
1964
+ const results = await Promise.allSettled(
1965
+ payloads.map(
1966
+ (event) => fetch(this.endpoint, {
1967
+ method: "POST",
1968
+ headers,
1969
+ body: JSON.stringify(event),
1970
+ keepalive: useBeacon
1971
+ })
1972
+ )
1973
+ );
1974
+ const successIds = results.flatMap(
1975
+ (r, i) => r.status === "fulfilled" && r.value.ok ? [storedEvents[i].id] : []
1976
+ );
1977
+ if (successIds.length) await eventStorage.remove(successIds);
1978
+ if (successIds.length === storedEvents.length) {
1979
+ this.circuitBreaker.recordSuccess();
1980
+ } else {
1981
+ this.circuitBreaker.recordFailure();
1982
+ }
1983
+ } else if (response.ok) {
1984
+ await eventStorage.remove(storedEvents.map((e) => e.id));
1985
+ this.circuitBreaker.recordSuccess();
1641
1986
  } else {
1642
- failureCount++;
1987
+ this.circuitBreaker.recordFailure();
1643
1988
  }
1644
- });
1645
- if (successIds.length > 0) {
1646
- await eventStorage.remove(successIds);
1647
- this.circuitBreaker.recordSuccess();
1648
- }
1649
- if (failureCount > 0) {
1989
+ } catch (err) {
1650
1990
  this.circuitBreaker.recordFailure();
1991
+ if (this.config.debug) {
1992
+ console.warn("[GN-Apex] Analytics flush deferred:", err);
1993
+ }
1994
+ } finally {
1995
+ this.flushPromise = null;
1651
1996
  }
1652
- } catch (err) {
1653
- console.error("[NexusHub] Network Error:", err);
1654
- this.circuitBreaker.recordFailure();
1655
- } finally {
1656
- this.isFlushing = false;
1657
- if (!this.circuitBreaker.isOpen() && await eventStorage.count() > 0) {
1658
- setTimeout(() => this.flushQueue(), 100);
1659
- }
1660
- }
1997
+ })();
1998
+ return this.flushPromise;
1661
1999
  }
1662
2000
  getSession() {
1663
2001
  return this.sessionId;
@@ -1698,7 +2036,7 @@ function extractUtmParams(url) {
1698
2036
  if (val) result[key] = val;
1699
2037
  });
1700
2038
  return result;
1701
- } catch (e12) {
2039
+ } catch (e14) {
1702
2040
  return {};
1703
2041
  }
1704
2042
  }
@@ -1707,7 +2045,7 @@ function extractUtmParams(url) {
1707
2045
  var safeRemoveItem2 = (key) => {
1708
2046
  try {
1709
2047
  localStorage.removeItem(key);
1710
- } catch (e13) {
2048
+ } catch (e15) {
1711
2049
  }
1712
2050
  };
1713
2051
  var AnalyticsEngine = class {
@@ -1766,8 +2104,8 @@ var AnalyticsEngine = class {
1766
2104
  navigator.share = (data) => {
1767
2105
  this.tracker.send("social_share", {
1768
2106
  method: "native_share_menu",
1769
- url: _optionalChain([data, 'optionalAccess', _28 => _28.url]) || window.location.href,
1770
- title: _optionalChain([data, 'optionalAccess', _29 => _29.title])
2107
+ url: _optionalChain([data, 'optionalAccess', _36 => _36.url]) || window.location.href,
2108
+ title: _optionalChain([data, 'optionalAccess', _37 => _37.title])
1771
2109
  });
1772
2110
  return originalShare(data);
1773
2111
  };
@@ -1816,7 +2154,7 @@ var AnalyticsEngine = class {
1816
2154
  this.tracker.send("click", {
1817
2155
  element_type: "link",
1818
2156
  href: link.href,
1819
- text: _optionalChain([link, 'access', _30 => _30.innerText, 'optionalAccess', _31 => _31.substring, 'call', _32 => _32(0, 50)]),
2157
+ text: _optionalChain([link, 'access', _38 => _38.innerText, 'optionalAccess', _39 => _39.substring, 'call', _40 => _40(0, 50)]),
1820
2158
  id: link.id,
1821
2159
  classes: link.className,
1822
2160
  dataset: { ...link.dataset },
@@ -1827,7 +2165,7 @@ var AnalyticsEngine = class {
1827
2165
  if (button) {
1828
2166
  this.tracker.send("click", {
1829
2167
  element_type: "button",
1830
- text: _optionalChain([button, 'access', _33 => _33.innerText, 'optionalAccess', _34 => _34.substring, 'call', _35 => _35(0, 50)]),
2168
+ text: _optionalChain([button, 'access', _41 => _41.innerText, 'optionalAccess', _42 => _42.substring, 'call', _43 => _43(0, 50)]),
1831
2169
  id: button.id,
1832
2170
  classes: button.className,
1833
2171
  coordinates: { x: e.clientX, y: e.clientY }
@@ -1919,14 +2257,14 @@ var AnalyticsEngine = class {
1919
2257
  {
1920
2258
  event_name: "outbound_click",
1921
2259
  href: link.href,
1922
- text: _optionalChain([link, 'access', _36 => _36.innerText, 'optionalAccess', _37 => _37.substring, 'call', _38 => _38(0, 50)]),
2260
+ text: _optionalChain([link, 'access', _44 => _44.innerText, 'optionalAccess', _45 => _45.substring, 'call', _46 => _46(0, 50)]),
1923
2261
  destination_host: linkHost,
1924
2262
  coordinates: { x: e.clientX, y: e.clientY }
1925
2263
  },
1926
2264
  "outbound_click"
1927
2265
  );
1928
2266
  }
1929
- } catch (e14) {
2267
+ } catch (e16) {
1930
2268
  }
1931
2269
  };
1932
2270
  window.addEventListener("click", outboundHandler, { passive: true });
@@ -1936,70 +2274,35 @@ var AnalyticsEngine = class {
1936
2274
  }
1937
2275
  setupVideoTracking() {
1938
2276
  if (typeof document === "undefined") return;
1939
- const attachVideoListeners = (video) => {
1940
- if (video.__nexus_tracked) return;
1941
- video.__nexus_tracked = true;
1942
- const src = video.src || video.currentSrc || "unknown";
1943
- let milestone50Fired = false;
1944
- video.addEventListener("play", () => {
1945
- this.tracker.send(
1946
- "custom_event",
1947
- { event_name: "video_play", src },
1948
- "video_play"
1949
- );
1950
- });
1951
- video.addEventListener("pause", () => {
1952
- this.tracker.send(
1953
- "custom_event",
1954
- {
1955
- event_name: "video_pause",
1956
- src,
1957
- position_seconds: Math.round(video.currentTime)
1958
- },
1959
- "video_pause"
1960
- );
1961
- });
2277
+ const attach = (video) => {
2278
+ const v = video;
2279
+ if (v.__gnexusTracked) return;
2280
+ v.__gnexusTracked = true;
2281
+ v.__gnexusMilestones = /* @__PURE__ */ new Set();
2282
+ const src = () => video.currentSrc || video.src || "unknown";
2283
+ const emit = (name, data = {}) => this.tracker.send("media", { media_type: "video", event_name: name, src: src(), ...data }, name);
2284
+ video.addEventListener("loadedmetadata", () => emit("video_loaded", { duration_seconds: Number.isFinite(video.duration) ? Math.round(video.duration) : void 0 }));
2285
+ video.addEventListener("play", () => emit("video_play", { position_seconds: Math.round(video.currentTime) }));
2286
+ video.addEventListener("pause", () => emit("video_pause", { position_seconds: Math.round(video.currentTime) }));
2287
+ video.addEventListener("seeking", () => emit("video_seek", { position_seconds: Math.round(video.currentTime) }));
2288
+ video.addEventListener("ended", () => emit("video_complete", { duration_seconds: Math.round(video.duration) }));
2289
+ video.addEventListener("error", () => emit("video_error"));
1962
2290
  video.addEventListener("timeupdate", () => {
1963
- if (!video.duration || video.duration === Infinity) return;
1964
- const pct = video.currentTime / video.duration;
1965
- if (pct >= 0.5 && !milestone50Fired) {
1966
- milestone50Fired = true;
1967
- this.tracker.send(
1968
- "custom_event",
1969
- {
1970
- event_name: "video_50_percent",
1971
- src
1972
- },
1973
- "video_50_percent"
1974
- );
2291
+ if (!Number.isFinite(video.duration) || video.duration <= 0) return;
2292
+ for (const milestone of [25, 50, 75, 90, 100]) {
2293
+ if (video.currentTime / video.duration * 100 >= milestone && !v.__gnexusMilestones.has(milestone)) {
2294
+ v.__gnexusMilestones.add(milestone);
2295
+ emit(`video_${milestone}_percent`, { progress: milestone / 100 });
2296
+ }
1975
2297
  }
1976
2298
  });
1977
- video.addEventListener("ended", () => {
1978
- this.tracker.send(
1979
- "custom_event",
1980
- {
1981
- event_name: "video_complete",
1982
- src,
1983
- duration_seconds: Math.round(video.duration)
1984
- },
1985
- "video_complete"
1986
- );
1987
- });
1988
2299
  };
1989
- document.querySelectorAll("video").forEach((v) => attachVideoListeners(v));
1990
- const observer = new MutationObserver((mutations) => {
1991
- mutations.forEach((m) => {
1992
- m.addedNodes.forEach((node) => {
1993
- if (node instanceof HTMLVideoElement) {
1994
- attachVideoListeners(node);
1995
- }
1996
- if (node instanceof Element) {
1997
- node.querySelectorAll("video").forEach((v) => attachVideoListeners(v));
1998
- }
1999
- });
2000
- });
2001
- });
2002
- observer.observe(document.body, { childList: true, subtree: true });
2300
+ document.querySelectorAll("video").forEach((v) => attach(v));
2301
+ const observer = new MutationObserver((ms) => ms.forEach((m) => m.addedNodes.forEach((n) => {
2302
+ if (n instanceof HTMLVideoElement) attach(n);
2303
+ if (n instanceof Element) n.querySelectorAll("video").forEach((v) => attach(v));
2304
+ })));
2305
+ if (document.body) observer.observe(document.body, { childList: true, subtree: true });
2003
2306
  this.cleanupFns.push(() => observer.disconnect());
2004
2307
  }
2005
2308
  setupErrorTracking() {
@@ -2010,7 +2313,7 @@ var AnalyticsEngine = class {
2010
2313
  filename: e.filename,
2011
2314
  lineno: e.lineno,
2012
2315
  colno: e.colno,
2013
- stack: _optionalChain([e, 'access', _39 => _39.error, 'optionalAccess', _40 => _40.stack]),
2316
+ stack: _optionalChain([e, 'access', _47 => _47.error, 'optionalAccess', _48 => _48.stack]),
2014
2317
  url: window.location.href,
2015
2318
  type: "uncaught_error"
2016
2319
  });
@@ -2188,57 +2491,303 @@ function getSelector(el, depth = 0) {
2188
2491
  return `${parent}${self}`;
2189
2492
  }
2190
2493
 
2494
+ // src/errors.ts
2495
+ var NexusError = class extends Error {
2496
+ constructor(message, code = "UNKNOWN", status, requestId, details, retryable = false, cause) {
2497
+ super(message);
2498
+ this.code = code;
2499
+ this.status = status;
2500
+ this.requestId = requestId;
2501
+ this.details = details;
2502
+ this.retryable = retryable;
2503
+ this.cause = cause;
2504
+ this.name = "NexusError";
2505
+ Object.setPrototypeOf(this, new.target.prototype);
2506
+ }
2507
+ };
2508
+
2509
+ // src/events.ts
2510
+ var NexusEventBus = class {
2511
+ constructor() {
2512
+ this.listeners = /* @__PURE__ */ new Map();
2513
+ }
2514
+ on(event, listener) {
2515
+ const set = _nullishCoalesce(this.listeners.get(event), () => ( /* @__PURE__ */ new Set()));
2516
+ set.add(listener);
2517
+ this.listeners.set(event, set);
2518
+ return () => set.delete(listener);
2519
+ }
2520
+ emit(event, payload) {
2521
+ _optionalChain([this, 'access', _49 => _49.listeners, 'access', _50 => _50.get, 'call', _51 => _51(event), 'optionalAccess', _52 => _52.forEach, 'call', _53 => _53((l) => {
2522
+ try {
2523
+ l(payload);
2524
+ } catch (e17) {
2525
+ }
2526
+ })]);
2527
+ }
2528
+ clear() {
2529
+ this.listeners.clear();
2530
+ }
2531
+ };
2532
+
2533
+ // src/http.ts
2534
+ var uuid = () => typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `nx_${Date.now()}_${Math.random().toString(36).slice(2)}`;
2535
+ var NexusHttpClient = class {
2536
+ constructor(config, events = new NexusEventBus()) {
2537
+ this.config = config;
2538
+ this.events = events;
2539
+ this.breaker = new CircuitBreaker();
2540
+ this.limiter = new RateLimiter({
2541
+ maxRequests: config.debug ? 200 : 100,
2542
+ timeWindow: 6e4
2543
+ });
2544
+ this.backoff = new ExponentialBackoff({
2545
+ maxRetries: _nullishCoalesce(config.retries, () => ( 3)),
2546
+ baseDelay: 150,
2547
+ maxDelay: 8e3,
2548
+ jitter: true
2549
+ });
2550
+ }
2551
+ updateConfig(config) {
2552
+ this.config = config;
2553
+ }
2554
+ async request(input, options = {}) {
2555
+ if (this.breaker.isOpen()) {
2556
+ throw new NexusError(
2557
+ "GN-Apex service circuit is open",
2558
+ "CIRCUIT_OPEN",
2559
+ void 0,
2560
+ void 0,
2561
+ void 0,
2562
+ true
2563
+ );
2564
+ }
2565
+ const requestId = uuid();
2566
+ const url = input;
2567
+ const started = Date.now();
2568
+ const headers = new Headers(options.headers);
2569
+ headers.set("X-Nexus-Request-ID", requestId);
2570
+ headers.set(
2571
+ "X-Nexus-Client",
2572
+ `gnexus-sdk/${_nullishCoalesce(this.config.sdkVersion, () => ( "1.1.0"))}`
2573
+ );
2574
+ headers.set("X-Nexus-Project", this.config.projectId);
2575
+ if (this.config.apiKey)
2576
+ headers.set("Authorization", `Bearer ${this.config.apiKey}`);
2577
+ this.events.emit("request:start", {
2578
+ requestId,
2579
+ url,
2580
+ method: _nullishCoalesce(options.method, () => ( "GET"))
2581
+ });
2582
+ try {
2583
+ await this.limiter.checkLimit();
2584
+ const response = await this.backoff.execute(async () => {
2585
+ const controller = new AbortController();
2586
+ const timeout = _nullishCoalesce(_nullishCoalesce(options.timeout, () => ( this.config.timeout)), () => ( 1e4));
2587
+ const timer = setTimeout(() => controller.abort(), timeout);
2588
+ try {
2589
+ return await fetch(url, {
2590
+ ...options,
2591
+ headers,
2592
+ signal: _nullishCoalesce(options.signal, () => ( controller.signal))
2593
+ });
2594
+ } catch (e) {
2595
+ if (_optionalChain([e, 'optionalAccess', _54 => _54.name]) === "AbortError") {
2596
+ throw new NexusError(
2597
+ `Request timed out after ${timeout}ms`,
2598
+ "TIMEOUT",
2599
+ void 0,
2600
+ requestId,
2601
+ void 0,
2602
+ true,
2603
+ e
2604
+ );
2605
+ }
2606
+ throw new NexusError(
2607
+ "Network request failed",
2608
+ "NETWORK_ERROR",
2609
+ void 0,
2610
+ requestId,
2611
+ void 0,
2612
+ true,
2613
+ e
2614
+ );
2615
+ } finally {
2616
+ clearTimeout(timer);
2617
+ }
2618
+ });
2619
+ if (!response.ok) {
2620
+ const retryable = response.status === 408 || response.status === 429 || response.status >= 500;
2621
+ let details;
2622
+ try {
2623
+ details = await response.clone().json();
2624
+ } catch (e18) {
2625
+ }
2626
+ const code = response.status === 404 ? "NOT_FOUND" : response.status === 429 ? "RATE_LIMITED" : "HTTP_ERROR";
2627
+ const err = new NexusError(
2628
+ `API request failed (${response.status})`,
2629
+ code,
2630
+ response.status,
2631
+ _nullishCoalesce(response.headers.get("x-nexus-request-id"), () => ( requestId)),
2632
+ details,
2633
+ retryable
2634
+ );
2635
+ throw err;
2636
+ }
2637
+ this.breaker.recordSuccess();
2638
+ this.events.emit("request:end", {
2639
+ requestId,
2640
+ url,
2641
+ status: response.status,
2642
+ duration: Date.now() - started
2643
+ });
2644
+ return response;
2645
+ } catch (e) {
2646
+ if (e instanceof NexusError) {
2647
+ if (e.retryable || e.status && (e.status >= 500 || e.status === 429)) {
2648
+ this.breaker.recordFailure();
2649
+ } else {
2650
+ this.breaker.recordSuccess();
2651
+ }
2652
+ } else {
2653
+ this.breaker.recordFailure();
2654
+ }
2655
+ this.events.emit("error", { error: e });
2656
+ throw e;
2657
+ }
2658
+ }
2659
+ getStats() {
2660
+ return {
2661
+ rateLimit: this.limiter.getStats(),
2662
+ circuit: this.breaker.getState()
2663
+ };
2664
+ }
2665
+ };
2666
+
2667
+ // src/flags.ts
2668
+ var FeatureFlags = class {
2669
+ constructor(initial) {
2670
+ this.values = {};
2671
+ this.values = { ...initial };
2672
+ }
2673
+ set(values) {
2674
+ this.values = { ...this.values, ...values };
2675
+ }
2676
+ isEnabled(key, fallback = false) {
2677
+ const v = this.values[key];
2678
+ return typeof v === "boolean" ? v : fallback;
2679
+ }
2680
+ get(key, fallback) {
2681
+ return _nullishCoalesce(this.values[key], () => ( fallback));
2682
+ }
2683
+ all() {
2684
+ return { ...this.values };
2685
+ }
2686
+ };
2687
+ var RemoteConfig = class {
2688
+ constructor() {
2689
+ this.values = {};
2690
+ }
2691
+ set(values) {
2692
+ this.values = { ...this.values, ...values };
2693
+ }
2694
+ get(key, fallback) {
2695
+ return _nullishCoalesce(this.values[key], () => ( fallback));
2696
+ }
2697
+ all() {
2698
+ return { ...this.values };
2699
+ }
2700
+ };
2701
+
2702
+ // src/diagnostics.ts
2703
+ var getDiagnostics = (version, environment) => ({ version, environment, online: typeof navigator === "undefined" ? true : navigator.onLine, userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent, memory: typeof performance !== "undefined" && "memory" in performance ? _optionalChain([performance, 'access', _55 => _55.memory, 'optionalAccess', _56 => _56.usedJSHeapSize]) : void 0 });
2704
+
2191
2705
  // src/client.ts
2192
- var DEFAULT_REVALIDATE_SECONDS = false;
2193
2706
  var NexusClient = class {
2194
- constructor(config) {
2195
- const fullConfig = _chunkWZ47UVA2cjs.getFullConfig.call(void 0, config);
2707
+ constructor(config = {}) {
2708
+ const full = _chunkZ7UZC7IKcjs.getFullConfig.call(void 0, config);
2196
2709
  this.config = {
2197
- debug: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _41 => _41.debug]), () => ( false)),
2198
- cacheStrategy: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _42 => _42.cacheStrategy]), () => ( "memory")),
2199
- // Only fall through to the safe default when revalidateTime is
2200
- // genuinely *unset* (undefined). An explicit `false` from the caller
2201
- // is a deliberate "never revalidate" choice and must be respected,
2202
- // not silently upgraded — `??` (not `||`) is required here so that
2203
- // `0` (revalidate on every request) also passes through untouched
2204
- // instead of being treated as falsy.
2205
- revalidateTime: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _43 => _43.revalidateTime]), () => ( DEFAULT_REVALIDATE_SECONDS)),
2206
- timeout: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _44 => _44.timeout]), () => ( 1e4)),
2207
- retries: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _45 => _45.retries]), () => ( 3)),
2208
- ...fullConfig
2710
+ debug: false,
2711
+ cacheStrategy: "memory",
2712
+ revalidateTime: false,
2713
+ timeout: 1e4,
2714
+ retries: 3,
2715
+ sdkVersion: _chunkZ7UZC7IKcjs.SDK_VERSION,
2716
+ cacheInvalidation: "platform",
2717
+ environment: typeof process !== "undefined" && _optionalChain([process, 'access', _57 => _57.env, 'optionalAccess', _58 => _58.NODE_ENV]) === "development" ? "development" : "production",
2718
+ autoTracking: true,
2719
+ publicKeyOnly: true,
2720
+ // 🚀 Default: fingerprinting: true (Active by default for high-precision analytics)
2721
+ privacy: { analytics: true, fingerprinting: true, redact: true },
2722
+ ...full,
2723
+ ...config
2209
2724
  };
2210
- const errors = _chunkWZ47UVA2cjs.validateConfig.call(void 0, this.config);
2211
- if (errors.length > 0) {
2212
- console.warn("\u26A0\uFE0F NexusHub: Configuration issues:", errors.join(", "));
2213
- if (!this.config.projectId) {
2214
- console.warn("\u26A0\uFE0F NexusHub: No Project ID found. Tracking will fail.");
2215
- }
2216
- }
2725
+ const errors = _chunkZ7UZC7IKcjs.validateConfig.call(void 0, this.config);
2726
+ if (errors.length && this.config.debug) {
2727
+ console.warn("[GN-Apex] Configuration warnings:", errors);
2728
+ }
2729
+ this.events = new NexusEventBus();
2730
+ this.http = new NexusHttpClient(this.config, this.events);
2731
+ this.flags = new FeatureFlags();
2732
+ this.remoteConfig = new RemoteConfig();
2217
2733
  this.content = new ContentEngine(this.config);
2218
- if (typeof window !== "undefined") {
2734
+ if (typeof window !== "undefined" && this.config.autoTracking !== false && _optionalChain([this, 'access', _59 => _59.config, 'access', _60 => _60.privacy, 'optionalAccess', _61 => _61.analytics]) !== false) {
2219
2735
  this.analytics = new AnalyticsEngine(this.config);
2220
2736
  this.analytics.start();
2221
2737
  }
2222
2738
  }
2223
2739
  /**
2224
- * Helper alias for cleaner content fetching.
2740
+ * Helper alias for cleaner page content fetching.
2225
2741
  */
2226
2742
  getPage(slug, options) {
2227
2743
  return this.content.getPage(slug, options);
2228
2744
  }
2229
2745
  /**
2230
- * Returns a readonly snapshot of the current config.
2746
+ * Returns a readonly snapshot of the active configuration.
2231
2747
  */
2232
2748
  getConfig() {
2233
- return { ...this.config };
2749
+ return Object.freeze({
2750
+ ...this.config,
2751
+ privacy: { ...this.config.privacy }
2752
+ });
2234
2753
  }
2235
2754
  /**
2236
- * Updates specific config fields at runtime.
2237
- * Replaces the previous pattern of `(nexus as any).config.projectId = x`
2238
- * which bypassed TypeScript and mutated internal state unsafely.
2755
+ * Updates runtime configuration dynamically without breaking active listeners.
2239
2756
  */
2240
2757
  updateConfig(updates) {
2241
- this.config = { ...this.config, ...updates };
2758
+ this.config = {
2759
+ ...this.config,
2760
+ ...updates,
2761
+ privacy: { ...this.config.privacy, ...updates.privacy }
2762
+ };
2763
+ this.http.updateConfig(this.config);
2764
+ _optionalChain([this, 'access', _62 => _62.content, 'access', _63 => _63.updateConfig, 'optionalCall', _64 => _64(this.config)]);
2765
+ if (typeof window !== "undefined" && this.config.autoTracking !== false && _optionalChain([this, 'access', _65 => _65.config, 'access', _66 => _66.privacy, 'optionalAccess', _67 => _67.analytics]) !== false && !this.analytics) {
2766
+ this.analytics = new AnalyticsEngine(this.config);
2767
+ this.analytics.start();
2768
+ }
2769
+ if (_optionalChain([this, 'access', _68 => _68.config, 'access', _69 => _69.privacy, 'optionalAccess', _70 => _70.analytics]) === false) {
2770
+ _optionalChain([this, 'access', _71 => _71.analytics, 'optionalAccess', _72 => _72.stop, 'call', _73 => _73()]);
2771
+ }
2772
+ }
2773
+ /**
2774
+ * Returns deep system diagnostics including HTTP and cache performance.
2775
+ */
2776
+ diagnostics() {
2777
+ return {
2778
+ ...getDiagnostics(_chunkZ7UZC7IKcjs.SDK_VERSION, _nullishCoalesce(this.config.environment, () => ( "production"))),
2779
+ cache: this.content.getCacheStats(),
2780
+ http: this.http.getStats(),
2781
+ analyticsQueue: this.analytics ? void 0 : 0
2782
+ };
2783
+ }
2784
+ /**
2785
+ * Gracefully terminates background tasks and cleans up listeners.
2786
+ */
2787
+ destroy() {
2788
+ _optionalChain([this, 'access', _74 => _74.analytics, 'optionalAccess', _75 => _75.stop, 'call', _76 => _76(true)]);
2789
+ _optionalChain([this, 'access', _77 => _77.content, 'access', _78 => _78.cleanup, 'optionalCall', _79 => _79()]);
2790
+ this.events.clear();
2242
2791
  }
2243
2792
  };
2244
2793
  var nexus = new NexusClient();
@@ -2251,7 +2800,7 @@ var nexus = new NexusClient();
2251
2800
 
2252
2801
 
2253
2802
 
2254
- var _jsxruntime = require('react/jsx-runtime');
2803
+
2255
2804
  var AuthContext = _react.createContext.call(void 0, null);
2256
2805
  var AuthProvider = ({
2257
2806
  children,
@@ -2429,7 +2978,7 @@ async function parseError(res) {
2429
2978
  code: json.code || "UNKNOWN_ERROR",
2430
2979
  message: json.message || "An error occurred during authentication"
2431
2980
  };
2432
- } catch (e15) {
2981
+ } catch (e19) {
2433
2982
  return {
2434
2983
  status: res.status,
2435
2984
  code: "NETWORK_ERROR",
@@ -2491,7 +3040,7 @@ var NexusPushClient = class {
2491
3040
  applicationServerKey: this.urlBase64ToUint8Array(publicKey)
2492
3041
  });
2493
3042
  const rawSub = subscription.toJSON();
2494
- if (!rawSub.endpoint || !_optionalChain([rawSub, 'access', _46 => _46.keys, 'optionalAccess', _47 => _47.auth]) || !_optionalChain([rawSub, 'access', _48 => _48.keys, 'optionalAccess', _49 => _49.p256dh])) {
3043
+ if (!rawSub.endpoint || !_optionalChain([rawSub, 'access', _80 => _80.keys, 'optionalAccess', _81 => _81.auth]) || !_optionalChain([rawSub, 'access', _82 => _82.keys, 'optionalAccess', _83 => _83.p256dh])) {
2495
3044
  throw new Error(
2496
3045
  "Malformed subscription payload received from browser."
2497
3046
  );
@@ -2586,7 +3135,7 @@ var NexusProvider = ({
2586
3135
  try {
2587
3136
  const pushClient = new NexusPushClient(nexus.getConfig());
2588
3137
  await pushClient.requestSubscription("/sw.js");
2589
- } catch (e16) {
3138
+ } catch (e20) {
2590
3139
  }
2591
3140
  }
2592
3141
  }).catch((err) => {
@@ -2636,7 +3185,7 @@ var NexusProvider = ({
2636
3185
  });
2637
3186
  socket.on("live_event", (event) => {
2638
3187
  setLatestEvent(event);
2639
- _optionalChain([onLiveEvent, 'optionalCall', _50 => _50(event)]);
3188
+ _optionalChain([onLiveEvent, 'optionalCall', _84 => _84(event)]);
2640
3189
  });
2641
3190
  socketRef.current = socket;
2642
3191
  } catch (err) {
@@ -2699,13 +3248,43 @@ var useNexusAnalytics = () => {
2699
3248
 
2700
3249
  var _lucidereact = require('lucide-react'); var LucideIcons = _interopRequireWildcard(_lucidereact);
2701
3250
 
2702
- function NexusRichText({ value, className = "" }) {
3251
+ function sanitizeHtml(html) {
3252
+ if (!html) return "";
3253
+ if (typeof window === "undefined") {
3254
+ return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/<noscript\b[^<]*(?:(?!<\/noscript>)<[^<]*)*<\/noscript>/gi, "").replace(/\bon\w+\s*=\s*(?:'[^']*'|"[^"]*"|[^\s>]+)/gi, "").replace(/href\s*=\s*['"]\s*javascript:[^'"]*['"]/gi, 'href="#"').replace(/src\s*=\s*['"]\s*javascript:[^'"]*['"]/gi, 'src=""');
3255
+ }
3256
+ const parser = new DOMParser();
3257
+ const doc = parser.parseFromString(html, "text/html");
3258
+ doc.querySelectorAll("script, noscript").forEach((n) => n.remove());
3259
+ doc.querySelectorAll("*").forEach((el) => {
3260
+ [...el.attributes].forEach((attr) => {
3261
+ if (attr.name.toLowerCase().startsWith("on")) {
3262
+ el.removeAttribute(attr.name);
3263
+ }
3264
+ });
3265
+ const href = el.getAttribute("href");
3266
+ const src = el.getAttribute("src");
3267
+ if (href && /^\s*javascript:/i.test(href)) el.removeAttribute("href");
3268
+ if (src && /^\s*javascript:/i.test(src)) el.removeAttribute("src");
3269
+ });
3270
+ return doc.body.innerHTML;
3271
+ }
3272
+ function NexusRichText({
3273
+ value,
3274
+ className = "",
3275
+ hydrateCharts = true,
3276
+ onCommentClick,
3277
+ onImageClick
3278
+ }) {
2703
3279
  if (!value) return null;
2704
3280
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2705
- "div",
3281
+ NexusRenderer,
2706
3282
  {
2707
- className: `prose dark:prose-invert max-w-none text-foreground leading-relaxed ${className}`,
2708
- dangerouslySetInnerHTML: { __html: value }
3283
+ content: value,
3284
+ className,
3285
+ hydrateCharts,
3286
+ onCommentClick,
3287
+ onImageClick
2709
3288
  }
2710
3289
  );
2711
3290
  }
@@ -2715,7 +3294,7 @@ function NexusLongText({ value, className = "" }) {
2715
3294
  "p",
2716
3295
  {
2717
3296
  className: `text-sm text-muted-foreground whitespace-pre-line leading-relaxed ${className}`,
2718
- dangerouslySetInnerHTML: { __html: value }
3297
+ dangerouslySetInnerHTML: { __html: sanitizeHtml(value) }
2719
3298
  }
2720
3299
  );
2721
3300
  }
@@ -2757,6 +3336,8 @@ function NexusImage({
2757
3336
  {
2758
3337
  src: imageUrl,
2759
3338
  alt: imageAlt,
3339
+ loading: "lazy",
3340
+ decoding: "async",
2760
3341
  className: `max-w-full h-auto object-cover rounded-xl ${className}`,
2761
3342
  ...props
2762
3343
  }
@@ -2799,7 +3380,7 @@ function NexusVideo({
2799
3380
  const isYouTube = value.includes("youtube.com") || value.includes("youtu.be");
2800
3381
  const isVimeo = value.includes("vimeo.com");
2801
3382
  if (isYouTube) {
2802
- const videoId = value.includes("youtu.be") ? value.split("/").pop() : _optionalChain([value, 'access', _51 => _51.split, 'call', _52 => _52("v="), 'access', _53 => _53[1], 'optionalAccess', _54 => _54.split, 'call', _55 => _55("&"), 'access', _56 => _56[0]]);
3383
+ const videoId = value.includes("youtu.be") ? _optionalChain([value, 'access', _85 => _85.split, 'call', _86 => _86("/"), 'access', _87 => _87.pop, 'call', _88 => _88(), 'optionalAccess', _89 => _89.split, 'call', _90 => _90("?"), 'access', _91 => _91[0]]) : _optionalChain([value, 'access', _92 => _92.split, 'call', _93 => _93("v="), 'access', _94 => _94[1], 'optionalAccess', _95 => _95.split, 'call', _96 => _96("&"), 'access', _97 => _97[0]]);
2803
3384
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2804
3385
  "iframe",
2805
3386
  {
@@ -2812,7 +3393,7 @@ function NexusVideo({
2812
3393
  );
2813
3394
  }
2814
3395
  if (isVimeo) {
2815
- const videoId = value.split("/").pop();
3396
+ const videoId = _optionalChain([value, 'access', _98 => _98.split, 'call', _99 => _99("/"), 'access', _100 => _100.pop, 'call', _101 => _101(), 'optionalAccess', _102 => _102.split, 'call', _103 => _103("?"), 'access', _104 => _104[0]]);
2816
3397
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2817
3398
  "iframe",
2818
3399
  {
@@ -2958,7 +3539,7 @@ function NexusProgress({
2958
3539
  color = "bg-primary"
2959
3540
  }) {
2960
3541
  if (value === null) return null;
2961
- const percent = Math.min(value / max * 100, 100);
3542
+ const percent = Math.min(Math.max(value / max * 100, 0), 100);
2962
3543
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: `w-full ${className}`, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "h-2 w-full rounded-full bg-muted overflow-hidden border border-border/30", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
2963
3544
  "div",
2964
3545
  {
@@ -2984,7 +3565,22 @@ function NexusCode({
2984
3565
  const [copied, setCopied] = _react.useState.call(void 0, false);
2985
3566
  if (!value) return null;
2986
3567
  const handleCopy = () => {
2987
- navigator.clipboard.writeText(value);
3568
+ if (navigator.clipboard && window.isSecureContext) {
3569
+ navigator.clipboard.writeText(value);
3570
+ } else {
3571
+ const textArea = document.createElement("textarea");
3572
+ textArea.value = value;
3573
+ textArea.style.position = "fixed";
3574
+ textArea.style.left = "-999999px";
3575
+ document.body.appendChild(textArea);
3576
+ textArea.focus();
3577
+ textArea.select();
3578
+ try {
3579
+ document.execCommand("copy");
3580
+ } catch (e21) {
3581
+ }
3582
+ textArea.remove();
3583
+ }
2988
3584
  setCopied(true);
2989
3585
  setTimeout(() => setCopied(false), 2e3);
2990
3586
  };
@@ -3065,4 +3661,5 @@ function NexusBoolean({
3065
3661
 
3066
3662
 
3067
3663
 
3068
- exports.AuthProvider = AuthProvider; exports.NexusAddress = NexusAddress; exports.NexusBlendContainer = NexusBlendContainer; exports.NexusBoolean = NexusBoolean; exports.NexusCode = NexusCode; exports.NexusColor = NexusColor; exports.NexusGallery = NexusGallery; exports.NexusGradient = NexusGradient; exports.NexusIcon = NexusIcon; exports.NexusImage = NexusImage; exports.NexusKeyValue = NexusKeyValue; exports.NexusLongText = NexusLongText; exports.NexusMap = NexusMap; exports.NexusProgress = NexusProgress; exports.NexusProvider = NexusProvider; exports.NexusRichText = NexusRichText; exports.NexusTags = NexusTags; exports.NexusVideo = NexusVideo; exports.useNexus = useNexus; exports.useNexusAnalytics = useNexusAnalytics; exports.useNexusAuth = useNexusAuth; exports.useNexusLiveFeed = useNexusLiveFeed;
3664
+
3665
+ exports.AuthProvider = AuthProvider; exports.NexusAddress = NexusAddress; exports.NexusBlendContainer = NexusBlendContainer; exports.NexusBoolean = NexusBoolean; exports.NexusCode = NexusCode; exports.NexusColor = NexusColor; exports.NexusGallery = NexusGallery; exports.NexusGradient = NexusGradient; exports.NexusIcon = NexusIcon; exports.NexusImage = NexusImage; exports.NexusKeyValue = NexusKeyValue; exports.NexusLongText = NexusLongText; exports.NexusMap = NexusMap; exports.NexusProgress = NexusProgress; exports.NexusProvider = NexusProvider; exports.NexusRenderer = NexusRenderer; exports.NexusRichText = NexusRichText; exports.NexusTags = NexusTags; exports.NexusVideo = NexusVideo; exports.useNexus = useNexus; exports.useNexusAnalytics = useNexusAnalytics; exports.useNexusAuth = useNexusAuth; exports.useNexusLiveFeed = useNexusLiveFeed;