@nexushub/client 0.9.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -1078
- package/dist/chunk-OP3LNZPC.cjs +116 -0
- package/dist/chunk-PB3YCBFH.js +116 -0
- package/dist/cli.cjs +259 -428
- package/dist/content/local-cache-server.cjs +36 -15
- package/dist/content/local-cache-server.d.cts +2 -5
- package/dist/content/local-cache-server.d.ts +2 -5
- package/dist/content/local-cache-server.js +36 -15
- package/dist/index.cjs +662 -341
- package/dist/index.d.cts +221 -32
- package/dist/index.d.ts +221 -32
- package/dist/index.js +652 -341
- package/dist/{local-cache-server-KUGMBMJP.cjs → local-cache-server-EKRKNL6N.cjs} +16 -8
- package/dist/{local-cache-server-RM7T456Z.js → local-cache-server-POO2S6OU.js} +15 -7
- package/dist/react.cjs +971 -374
- package/dist/react.d.cts +193 -76
- package/dist/react.d.ts +193 -76
- package/dist/react.js +1013 -416
- package/package.json +34 -28
- package/dist/chunk-3TDCUTZR.cjs +0 -53
- package/dist/chunk-VQ6IEN4I.js +0 -53
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 _chunk3TDCUTZRcjs = require('./chunk-3TDCUTZR.cjs');
|
|
5
4
|
|
|
6
|
-
// src/components/NexusProvider.tsx
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
|
|
9
|
+
var _chunkOP3LNZPCcjs = require('./chunk-OP3LNZPC.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 (
|
|
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 (
|
|
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 (
|
|
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-
|
|
695
|
+
const mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./local-cache-server-EKRKNL6N.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
|
|
324
|
-
this.requests = this.requests.filter((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
352
|
-
for (let
|
|
753
|
+
let last;
|
|
754
|
+
for (let a = 0; a <= this.config.maxRetries; a++) {
|
|
353
755
|
try {
|
|
354
756
|
return await fn();
|
|
355
|
-
} catch (
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
395
|
-
|
|
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
|
|
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.
|
|
409
|
-
if (this.failures >= this.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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(
|
|
421
|
-
this.
|
|
805
|
+
constructor(windowMs = 10, maxBatchSize = 50) {
|
|
806
|
+
this.windowMs = windowMs;
|
|
422
807
|
this.maxBatchSize = maxBatchSize;
|
|
423
|
-
this.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
440
|
-
|
|
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 } = _chunkOP3LNZPCcjs.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
|
+
_chunkOP3LNZPCcjs.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 } = _chunkOP3LNZPCcjs.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 = _chunkOP3LNZPCcjs.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 = _chunkOP3LNZPCcjs.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 (
|
|
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
|
|
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 (
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
|
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
|
|
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',
|
|
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',
|
|
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 (
|
|
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 = _chunkOP3LNZPCcjs.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":
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
1821
|
+
} catch (e11) {
|
|
1507
1822
|
}
|
|
1508
1823
|
};
|
|
1509
1824
|
var safeRemoveItem = (key) => {
|
|
1510
1825
|
try {
|
|
1511
1826
|
localStorage.removeItem(key);
|
|
1512
|
-
} catch (
|
|
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
|
|
1555
|
-
sid = `sess_${
|
|
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: _chunkOP3LNZPCcjs.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
|
-
|
|
1594
|
-
|
|
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.
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
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/${_chunkOP3LNZPCcjs.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
|
-
|
|
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
|
-
}
|
|
1653
|
-
|
|
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 (
|
|
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 (
|
|
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',
|
|
1770
|
-
title: _optionalChain([data, 'optionalAccess',
|
|
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',
|
|
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',
|
|
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',
|
|
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 (
|
|
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
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
});
|
|
1951
|
-
video.addEventListener("
|
|
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
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
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) =>
|
|
1990
|
-
const observer = new MutationObserver((
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
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',
|
|
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
|
|
2707
|
+
constructor(config = {}) {
|
|
2708
|
+
const full = _chunkOP3LNZPCcjs.getFullConfig.call(void 0, config);
|
|
2196
2709
|
this.config = {
|
|
2197
|
-
debug:
|
|
2198
|
-
cacheStrategy:
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2710
|
+
debug: false,
|
|
2711
|
+
cacheStrategy: "memory",
|
|
2712
|
+
revalidateTime: false,
|
|
2713
|
+
timeout: 1e4,
|
|
2714
|
+
retries: 3,
|
|
2715
|
+
sdkVersion: _chunkOP3LNZPCcjs.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 =
|
|
2211
|
-
if (errors.length
|
|
2212
|
-
console.warn("
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2725
|
+
const errors = _chunkOP3LNZPCcjs.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
|
|
2746
|
+
* Returns a readonly snapshot of the active configuration.
|
|
2231
2747
|
*/
|
|
2232
2748
|
getConfig() {
|
|
2233
|
-
return {
|
|
2749
|
+
return Object.freeze({
|
|
2750
|
+
...this.config,
|
|
2751
|
+
privacy: { ...this.config.privacy }
|
|
2752
|
+
});
|
|
2234
2753
|
}
|
|
2235
2754
|
/**
|
|
2236
|
-
* Updates
|
|
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 = {
|
|
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(_chunkOP3LNZPCcjs.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
|
-
|
|
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 (
|
|
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',
|
|
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 (
|
|
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',
|
|
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
|
|
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
|
-
|
|
3281
|
+
NexusRenderer,
|
|
2706
3282
|
{
|
|
2707
|
-
|
|
2708
|
-
|
|
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',
|
|
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.
|
|
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
|
-
|
|
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;
|