@nexushub/client 0.3.1 → 0.3.3
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/index.cjs +523 -117
- package/dist/index.d.cts +38 -32
- package/dist/index.d.ts +38 -32
- package/dist/index.js +518 -112
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -1315,12 +1315,6 @@ var getVisitorId = async () => {
|
|
|
1315
1315
|
};
|
|
1316
1316
|
|
|
1317
1317
|
// src/analytics/vitals.ts
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
1318
|
var _webvitals = require('web-vitals');
|
|
1325
1319
|
var METRIC_KEY_MAP = {
|
|
1326
1320
|
CLS: "cls",
|
|
@@ -1354,7 +1348,38 @@ var VitalsCollector = class {
|
|
|
1354
1348
|
dns_lookup: navEntry.domainLookupEnd - navEntry.domainLookupStart,
|
|
1355
1349
|
tcp_connect: navEntry.connectEnd - navEntry.connectStart,
|
|
1356
1350
|
request_time: navEntry.responseEnd - navEntry.requestStart,
|
|
1357
|
-
dom_load: navEntry.domComplete - navEntry.domInteractive
|
|
1351
|
+
dom_load: navEntry.domComplete - navEntry.domInteractive,
|
|
1352
|
+
// NEW: raw fields matching Rust NavigationTiming struct
|
|
1353
|
+
domain_lookup_start: navEntry.domainLookupStart,
|
|
1354
|
+
domain_lookup_end: navEntry.domainLookupEnd,
|
|
1355
|
+
connect_start: navEntry.connectStart,
|
|
1356
|
+
connect_end: navEntry.connectEnd,
|
|
1357
|
+
secure_connection_start: navEntry.secureConnectionStart > 0 ? navEntry.secureConnectionStart : void 0
|
|
1358
|
+
};
|
|
1359
|
+
this.hasUpdates = true;
|
|
1360
|
+
}
|
|
1361
|
+
const resourceEntries = performance.getEntriesByType(
|
|
1362
|
+
"resource"
|
|
1363
|
+
);
|
|
1364
|
+
if (resourceEntries.length > 0) {
|
|
1365
|
+
this.metrics.resources = resourceEntries.filter((r) => !r.name.includes("/api/collect")).sort((a, b) => b.duration - a.duration).slice(0, 20).map((r) => ({
|
|
1366
|
+
name: r.name,
|
|
1367
|
+
initiatorType: r.initiatorType,
|
|
1368
|
+
duration: Math.round(r.duration),
|
|
1369
|
+
transferSize: r.transferSize > 0 ? r.transferSize : void 0,
|
|
1370
|
+
encodedBodySize: r.encodedBodySize > 0 ? r.encodedBodySize : void 0,
|
|
1371
|
+
decodedBodySize: r.decodedBodySize > 0 ? r.decodedBodySize : void 0,
|
|
1372
|
+
startTime: Math.round(r.startTime),
|
|
1373
|
+
responseEnd: Math.round(r.responseEnd)
|
|
1374
|
+
}));
|
|
1375
|
+
this.hasUpdates = true;
|
|
1376
|
+
}
|
|
1377
|
+
const mem = performance.memory;
|
|
1378
|
+
if (mem) {
|
|
1379
|
+
this.metrics.memory_usage = {
|
|
1380
|
+
used_js_heap_size: mem.usedJSHeapSize,
|
|
1381
|
+
total_js_heap_size: mem.totalJSHeapSize,
|
|
1382
|
+
js_heap_size_limit: mem.jsHeapSizeLimit
|
|
1358
1383
|
};
|
|
1359
1384
|
this.hasUpdates = true;
|
|
1360
1385
|
}
|
|
@@ -1362,11 +1387,13 @@ var VitalsCollector = class {
|
|
|
1362
1387
|
getMetricsSnapshot() {
|
|
1363
1388
|
if (!this.hasUpdates) return null;
|
|
1364
1389
|
this.hasUpdates = false;
|
|
1365
|
-
|
|
1390
|
+
const snapshot = { ...this.metrics };
|
|
1391
|
+
this.metrics.resources = void 0;
|
|
1392
|
+
return snapshot;
|
|
1366
1393
|
}
|
|
1367
1394
|
};
|
|
1368
1395
|
var vitalsCollector = new VitalsCollector();
|
|
1369
|
-
var initVitals = (
|
|
1396
|
+
var initVitals = (_tracker) => {
|
|
1370
1397
|
if (process.env.NODE_ENV === "development") {
|
|
1371
1398
|
console.log("[NexusHub] Web Vitals monitoring active");
|
|
1372
1399
|
}
|
|
@@ -1485,10 +1512,41 @@ var EventStorage = class {
|
|
|
1485
1512
|
var eventStorage = new EventStorage();
|
|
1486
1513
|
|
|
1487
1514
|
// src/analytics/tracker.ts
|
|
1515
|
+
var SDK_VERSION = "0.1.0";
|
|
1516
|
+
var safeGetItem = (key) => {
|
|
1517
|
+
try {
|
|
1518
|
+
return localStorage.getItem(key);
|
|
1519
|
+
} catch (e6) {
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
var safeSetItem = (key, value) => {
|
|
1524
|
+
try {
|
|
1525
|
+
localStorage.setItem(key, value);
|
|
1526
|
+
} catch (e7) {
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
var safeRemoveItem = (key) => {
|
|
1530
|
+
try {
|
|
1531
|
+
localStorage.removeItem(key);
|
|
1532
|
+
} catch (e8) {
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
var generateUUID = () => {
|
|
1536
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1537
|
+
return crypto.randomUUID();
|
|
1538
|
+
}
|
|
1539
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1540
|
+
const r = Math.random() * 16 | 0;
|
|
1541
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
1542
|
+
return v.toString(16);
|
|
1543
|
+
});
|
|
1544
|
+
};
|
|
1488
1545
|
var Tracker = class {
|
|
1489
1546
|
constructor(config) {
|
|
1490
1547
|
this.sessionId = "";
|
|
1491
1548
|
this.visitorId = "";
|
|
1549
|
+
this.anonymousId = "";
|
|
1492
1550
|
this.isFlushing = false;
|
|
1493
1551
|
this.config = config;
|
|
1494
1552
|
this.endpoint = `${this.config.analyticsUrl}/api/collect`;
|
|
@@ -1501,28 +1559,40 @@ var Tracker = class {
|
|
|
1501
1559
|
}
|
|
1502
1560
|
async initSession() {
|
|
1503
1561
|
this.visitorId = await getVisitorId();
|
|
1504
|
-
let
|
|
1505
|
-
|
|
1562
|
+
let anonId = safeGetItem("nexus_anon_id");
|
|
1563
|
+
if (!anonId) {
|
|
1564
|
+
anonId = `anon_${generateUUID().replace(/-/g, "")}`;
|
|
1565
|
+
safeSetItem("nexus_anon_id", anonId);
|
|
1566
|
+
}
|
|
1567
|
+
this.anonymousId = anonId;
|
|
1568
|
+
let sid = safeGetItem("nexus_sid");
|
|
1569
|
+
const lastActivity = safeGetItem("nexus_last_active");
|
|
1506
1570
|
const now = Date.now();
|
|
1507
1571
|
const SESSION_TIMEOUT = 30 * 60 * 1e3;
|
|
1508
1572
|
const isExpired = !sid || !lastActivity || now - parseInt(lastActivity, 10) > SESSION_TIMEOUT;
|
|
1509
1573
|
if (isExpired) {
|
|
1510
|
-
const uuid =
|
|
1574
|
+
const uuid = generateUUID().replace(/-/g, "").substring(0, 16);
|
|
1511
1575
|
sid = `sess_${uuid}_${now}`;
|
|
1512
|
-
|
|
1576
|
+
safeSetItem("nexus_sid", sid);
|
|
1513
1577
|
this.sessionStart = now;
|
|
1514
1578
|
}
|
|
1515
|
-
|
|
1579
|
+
safeSetItem("nexus_last_active", now.toString());
|
|
1516
1580
|
this.sessionId = sid;
|
|
1517
1581
|
}
|
|
1518
|
-
async send(eventType, data = {}, eventName) {
|
|
1582
|
+
async send(eventType, data = {}, eventName, ecommerce) {
|
|
1519
1583
|
if (typeof window === "undefined") return;
|
|
1520
|
-
|
|
1584
|
+
safeSetItem("nexus_last_active", Date.now().toString());
|
|
1521
1585
|
const entropy = await getDeviceEntropy();
|
|
1522
1586
|
const perfMetrics = vitalsCollector.getMetricsSnapshot();
|
|
1587
|
+
const utmParams = extractUtmParams(window.location.href);
|
|
1523
1588
|
const payload = {
|
|
1524
1589
|
projectId: this.config.projectId,
|
|
1525
1590
|
sessionId: this.sessionId,
|
|
1591
|
+
visitorId: this.visitorId,
|
|
1592
|
+
anonymousId: this.anonymousId,
|
|
1593
|
+
messageId: generateUUID(),
|
|
1594
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1595
|
+
version: SDK_VERSION,
|
|
1526
1596
|
url: window.location.href,
|
|
1527
1597
|
referrer: document.referrer,
|
|
1528
1598
|
userAgent: window.navigator.userAgent,
|
|
@@ -1533,6 +1603,7 @@ var Tracker = class {
|
|
|
1533
1603
|
eventType,
|
|
1534
1604
|
eventName,
|
|
1535
1605
|
eventData: data,
|
|
1606
|
+
ecommerce: _nullishCoalesce(ecommerce, () => ( void 0)),
|
|
1536
1607
|
performance: perfMetrics || void 0,
|
|
1537
1608
|
context: {
|
|
1538
1609
|
device: {
|
|
@@ -1542,7 +1613,8 @@ var Tracker = class {
|
|
|
1542
1613
|
canvasFingerprint: entropy.canvas_hash,
|
|
1543
1614
|
platform: entropy.platform
|
|
1544
1615
|
},
|
|
1545
|
-
visitorIdLocal: this.visitorId
|
|
1616
|
+
visitorIdLocal: this.visitorId,
|
|
1617
|
+
utm: utmParams
|
|
1546
1618
|
},
|
|
1547
1619
|
clientTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1548
1620
|
};
|
|
@@ -1610,9 +1682,19 @@ var Tracker = class {
|
|
|
1610
1682
|
getSession() {
|
|
1611
1683
|
return this.sessionId;
|
|
1612
1684
|
}
|
|
1685
|
+
getVisitorId() {
|
|
1686
|
+
return this.visitorId;
|
|
1687
|
+
}
|
|
1688
|
+
getAnonymousId() {
|
|
1689
|
+
return this.anonymousId;
|
|
1690
|
+
}
|
|
1613
1691
|
getSessionDuration() {
|
|
1614
1692
|
return Date.now() - this.sessionStart;
|
|
1615
1693
|
}
|
|
1694
|
+
clearIdentity() {
|
|
1695
|
+
safeRemoveItem("nexus_anon_id");
|
|
1696
|
+
this.anonymousId = "";
|
|
1697
|
+
}
|
|
1616
1698
|
stop() {
|
|
1617
1699
|
if (this.flushInterval) {
|
|
1618
1700
|
clearInterval(this.flushInterval);
|
|
@@ -1620,13 +1702,42 @@ var Tracker = class {
|
|
|
1620
1702
|
this.flushQueue(true);
|
|
1621
1703
|
}
|
|
1622
1704
|
};
|
|
1705
|
+
function extractUtmParams(url) {
|
|
1706
|
+
try {
|
|
1707
|
+
const params = new URL(url).searchParams;
|
|
1708
|
+
const utmKeys = [
|
|
1709
|
+
"utm_source",
|
|
1710
|
+
"utm_medium",
|
|
1711
|
+
"utm_campaign",
|
|
1712
|
+
"utm_term",
|
|
1713
|
+
"utm_content"
|
|
1714
|
+
];
|
|
1715
|
+
const result = {};
|
|
1716
|
+
utmKeys.forEach((key) => {
|
|
1717
|
+
const val = params.get(key);
|
|
1718
|
+
if (val) result[key] = val;
|
|
1719
|
+
});
|
|
1720
|
+
return result;
|
|
1721
|
+
} catch (e9) {
|
|
1722
|
+
return {};
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1623
1725
|
|
|
1624
1726
|
// src/analytics/index.ts
|
|
1727
|
+
var safeRemoveItem2 = (key) => {
|
|
1728
|
+
try {
|
|
1729
|
+
localStorage.removeItem(key);
|
|
1730
|
+
} catch (e10) {
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1625
1733
|
var AnalyticsEngine = class {
|
|
1626
1734
|
constructor(config) {
|
|
1627
1735
|
this.cleanupFns = [];
|
|
1628
1736
|
this.isInitialized = false;
|
|
1737
|
+
this.maxScrollDepth = 0;
|
|
1738
|
+
this.scrollThresholdsFired = /* @__PURE__ */ new Set();
|
|
1629
1739
|
this.lastPath = typeof window !== "undefined" ? window.location.pathname : "";
|
|
1740
|
+
this.clickBuffer = [];
|
|
1630
1741
|
this.tracker = new Tracker(config);
|
|
1631
1742
|
}
|
|
1632
1743
|
start() {
|
|
@@ -1638,6 +1749,10 @@ var AnalyticsEngine = class {
|
|
|
1638
1749
|
this.setupFormTracking();
|
|
1639
1750
|
this.setupRouteTracking();
|
|
1640
1751
|
this.setupShareTracking();
|
|
1752
|
+
this.setupScrollTracking();
|
|
1753
|
+
this.setupOutboundTracking();
|
|
1754
|
+
this.setupVideoTracking();
|
|
1755
|
+
this.setupErrorTracking();
|
|
1641
1756
|
if (process.env.NODE_ENV === "development") {
|
|
1642
1757
|
console.log("[NexusHub] \u{1F680} Analytics Engine Started");
|
|
1643
1758
|
}
|
|
@@ -1645,17 +1760,17 @@ var AnalyticsEngine = class {
|
|
|
1645
1760
|
pageView(customReferrer) {
|
|
1646
1761
|
if (typeof window === "undefined") return;
|
|
1647
1762
|
const currentPath = window.location.pathname;
|
|
1763
|
+
this.maxScrollDepth = 0;
|
|
1764
|
+
this.scrollThresholdsFired.clear();
|
|
1648
1765
|
this.tracker.send("page_view", {
|
|
1649
1766
|
path: currentPath,
|
|
1650
1767
|
search: window.location.search,
|
|
1651
1768
|
title: document.title,
|
|
1652
1769
|
timezone_offset: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
|
|
1653
|
-
// Use the virtual path as referrer if navigating internally
|
|
1654
1770
|
referrer: customReferrer || (this.lastPath !== currentPath ? this.lastPath : document.referrer)
|
|
1655
1771
|
});
|
|
1656
1772
|
this.lastPath = currentPath;
|
|
1657
1773
|
}
|
|
1658
|
-
// --- SOCIAL / DARK SOCIAL TRACKING ---
|
|
1659
1774
|
setupShareTracking() {
|
|
1660
1775
|
if (typeof window === "undefined") return;
|
|
1661
1776
|
const copyHandler = () => {
|
|
@@ -1681,92 +1796,36 @@ var AnalyticsEngine = class {
|
|
|
1681
1796
|
});
|
|
1682
1797
|
}
|
|
1683
1798
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
user_id: newId
|
|
1695
|
-
});
|
|
1696
|
-
}
|
|
1697
|
-
reset(performGdprScrub = false) {
|
|
1698
|
-
const config = this.tracker.config;
|
|
1699
|
-
this.tracker.stop();
|
|
1700
|
-
localStorage.removeItem("nexus_sid");
|
|
1701
|
-
localStorage.removeItem("nexus_vid");
|
|
1702
|
-
if (performGdprScrub) {
|
|
1703
|
-
const endpoint = `${config.analyticsUrl}/api/privacy/scrub`;
|
|
1704
|
-
const userId = this.tracker.visitorId;
|
|
1705
|
-
fetch(endpoint, {
|
|
1706
|
-
method: "DELETE",
|
|
1707
|
-
headers: {
|
|
1708
|
-
"Content-Type": "application/json",
|
|
1709
|
-
Authorization: `Bearer ${config.apiKey}`
|
|
1710
|
-
},
|
|
1711
|
-
body: JSON.stringify({
|
|
1712
|
-
project_id: config.projectId,
|
|
1713
|
-
user_id: userId
|
|
1714
|
-
})
|
|
1715
|
-
}).catch((err) => console.error("[NexusHub] Privacy scrub failed:", err));
|
|
1716
|
-
}
|
|
1717
|
-
window.location.reload();
|
|
1718
|
-
}
|
|
1719
|
-
// --- E-COMMERCE & CUSTOM ---
|
|
1720
|
-
track(eventName, properties = {}) {
|
|
1721
|
-
this.tracker.send(
|
|
1722
|
-
"custom_event",
|
|
1723
|
-
{ event_name: eventName, ...properties },
|
|
1724
|
-
eventName
|
|
1725
|
-
);
|
|
1726
|
-
}
|
|
1727
|
-
trackPurchase(orderData) {
|
|
1728
|
-
this.tracker.send("purchase", {
|
|
1729
|
-
...orderData,
|
|
1730
|
-
currency: orderData.currency || "USD",
|
|
1731
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1732
|
-
});
|
|
1733
|
-
}
|
|
1734
|
-
trackError(error, context) {
|
|
1735
|
-
this.tracker.send("error", {
|
|
1736
|
-
message: error.message,
|
|
1737
|
-
stack: error.stack,
|
|
1738
|
-
context,
|
|
1739
|
-
url: typeof window !== "undefined" ? window.location.href : ""
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
// --- INTERNAL ---
|
|
1743
|
-
async sendIdentityRequest(type, data) {
|
|
1744
|
-
try {
|
|
1745
|
-
const config = this.tracker.config;
|
|
1746
|
-
const endpoint = `${config.analyticsUrl}/api/${type}`;
|
|
1747
|
-
const payload = {
|
|
1748
|
-
projectId: config.projectId,
|
|
1749
|
-
sessionId: this.tracker.getSession(),
|
|
1750
|
-
visitorId: this.tracker.visitorId,
|
|
1751
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1752
|
-
...data
|
|
1753
|
-
};
|
|
1754
|
-
const response = await fetch(endpoint, {
|
|
1755
|
-
method: "POST",
|
|
1756
|
-
headers: {
|
|
1757
|
-
"Content-Type": "application/json",
|
|
1758
|
-
Authorization: `Bearer ${config.apiKey}`
|
|
1759
|
-
},
|
|
1760
|
-
body: JSON.stringify(payload)
|
|
1761
|
-
});
|
|
1762
|
-
if (response.ok) {
|
|
1763
|
-
return true;
|
|
1799
|
+
setupScrollTracking() {
|
|
1800
|
+
if (typeof window === "undefined") return;
|
|
1801
|
+
const THRESHOLDS = [25, 50, 75, 100];
|
|
1802
|
+
const scrollHandler = () => {
|
|
1803
|
+
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
1804
|
+
const docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
|
1805
|
+
if (docHeight <= 0) return;
|
|
1806
|
+
const pct = Math.round(scrollTop / docHeight * 100);
|
|
1807
|
+
if (pct > this.maxScrollDepth) {
|
|
1808
|
+
this.maxScrollDepth = pct;
|
|
1764
1809
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1810
|
+
clearTimeout(this.scrollTimer);
|
|
1811
|
+
this.scrollTimer = setTimeout(() => {
|
|
1812
|
+
for (const threshold of THRESHOLDS) {
|
|
1813
|
+
if (this.maxScrollDepth >= threshold && !this.scrollThresholdsFired.has(threshold)) {
|
|
1814
|
+
this.scrollThresholdsFired.add(threshold);
|
|
1815
|
+
this.tracker.send("scroll", {
|
|
1816
|
+
depth: threshold / 100,
|
|
1817
|
+
depth_percent: threshold,
|
|
1818
|
+
path: window.location.pathname
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}, 300);
|
|
1823
|
+
};
|
|
1824
|
+
window.addEventListener("scroll", scrollHandler, { passive: true });
|
|
1825
|
+
this.cleanupFns.push(() => {
|
|
1826
|
+
window.removeEventListener("scroll", scrollHandler);
|
|
1827
|
+
clearTimeout(this.scrollTimer);
|
|
1828
|
+
});
|
|
1770
1829
|
}
|
|
1771
1830
|
setupClickTracking() {
|
|
1772
1831
|
if (typeof window === "undefined") return;
|
|
@@ -1794,6 +1853,55 @@ var AnalyticsEngine = class {
|
|
|
1794
1853
|
coordinates: { x: e.clientX, y: e.clientY }
|
|
1795
1854
|
});
|
|
1796
1855
|
}
|
|
1856
|
+
const now = Date.now();
|
|
1857
|
+
const ZONE = 400;
|
|
1858
|
+
const WINDOW_MS = 1e3;
|
|
1859
|
+
const RAGE_THRESHOLD = 3;
|
|
1860
|
+
this.clickBuffer.push({ x: e.clientX, y: e.clientY, t: now });
|
|
1861
|
+
this.clickBuffer = this.clickBuffer.filter((c) => now - c.t < WINDOW_MS);
|
|
1862
|
+
const zone = {
|
|
1863
|
+
x: Math.floor(e.clientX / ZONE),
|
|
1864
|
+
y: Math.floor(e.clientY / ZONE)
|
|
1865
|
+
};
|
|
1866
|
+
const zoneClicks = this.clickBuffer.filter(
|
|
1867
|
+
(c) => Math.floor(c.x / ZONE) === zone.x && Math.floor(c.y / ZONE) === zone.y
|
|
1868
|
+
);
|
|
1869
|
+
if (zoneClicks.length >= RAGE_THRESHOLD) {
|
|
1870
|
+
this.tracker.send(
|
|
1871
|
+
"click",
|
|
1872
|
+
{
|
|
1873
|
+
element_type: target.tagName.toLowerCase(),
|
|
1874
|
+
event_name: "rage_click",
|
|
1875
|
+
coordinates: { x: e.clientX, y: e.clientY },
|
|
1876
|
+
click_count: zoneClicks.length,
|
|
1877
|
+
path: window.location.pathname
|
|
1878
|
+
},
|
|
1879
|
+
"rage_click"
|
|
1880
|
+
);
|
|
1881
|
+
this.clickBuffer = [];
|
|
1882
|
+
}
|
|
1883
|
+
const isInteractive = target.closest(
|
|
1884
|
+
"a, button, input, select, textarea, [onclick], [role='button']"
|
|
1885
|
+
);
|
|
1886
|
+
if (!isInteractive) {
|
|
1887
|
+
const pathBefore = window.location.pathname;
|
|
1888
|
+
setTimeout(() => {
|
|
1889
|
+
const pathAfter = window.location.pathname;
|
|
1890
|
+
if (pathBefore === pathAfter) {
|
|
1891
|
+
this.tracker.send(
|
|
1892
|
+
"click",
|
|
1893
|
+
{
|
|
1894
|
+
element_type: target.tagName.toLowerCase(),
|
|
1895
|
+
event_name: "dead_click",
|
|
1896
|
+
selector: getSelector(target),
|
|
1897
|
+
coordinates: { x: e.clientX, y: e.clientY },
|
|
1898
|
+
path: window.location.pathname
|
|
1899
|
+
},
|
|
1900
|
+
"dead_click"
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
}, 300);
|
|
1904
|
+
}
|
|
1797
1905
|
};
|
|
1798
1906
|
window.addEventListener("click", clickHandler, { passive: true });
|
|
1799
1907
|
this.cleanupFns.push(
|
|
@@ -1818,6 +1926,132 @@ var AnalyticsEngine = class {
|
|
|
1818
1926
|
() => document.removeEventListener("submit", submitHandler)
|
|
1819
1927
|
);
|
|
1820
1928
|
}
|
|
1929
|
+
setupOutboundTracking() {
|
|
1930
|
+
if (typeof window === "undefined") return;
|
|
1931
|
+
const outboundHandler = (e) => {
|
|
1932
|
+
const link = e.target.closest("a");
|
|
1933
|
+
if (!link || !link.href) return;
|
|
1934
|
+
try {
|
|
1935
|
+
const linkHost = new URL(link.href).hostname;
|
|
1936
|
+
if (linkHost && linkHost !== window.location.hostname) {
|
|
1937
|
+
this.tracker.send(
|
|
1938
|
+
"click",
|
|
1939
|
+
{
|
|
1940
|
+
event_name: "outbound_click",
|
|
1941
|
+
href: link.href,
|
|
1942
|
+
text: _optionalChain([link, 'access', _35 => _35.innerText, 'optionalAccess', _36 => _36.substring, 'call', _37 => _37(0, 50)]),
|
|
1943
|
+
destination_host: linkHost,
|
|
1944
|
+
coordinates: { x: e.clientX, y: e.clientY }
|
|
1945
|
+
},
|
|
1946
|
+
"outbound_click"
|
|
1947
|
+
);
|
|
1948
|
+
}
|
|
1949
|
+
} catch (e11) {
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
window.addEventListener("click", outboundHandler, { passive: true });
|
|
1953
|
+
this.cleanupFns.push(
|
|
1954
|
+
() => window.removeEventListener("click", outboundHandler)
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
setupVideoTracking() {
|
|
1958
|
+
if (typeof document === "undefined") return;
|
|
1959
|
+
const attachVideoListeners = (video) => {
|
|
1960
|
+
if (video.__nexus_tracked) return;
|
|
1961
|
+
video.__nexus_tracked = true;
|
|
1962
|
+
const src = video.src || video.currentSrc || "unknown";
|
|
1963
|
+
let milestone50Fired = false;
|
|
1964
|
+
video.addEventListener("play", () => {
|
|
1965
|
+
this.tracker.send(
|
|
1966
|
+
"custom_event",
|
|
1967
|
+
{ event_name: "video_play", src },
|
|
1968
|
+
"video_play"
|
|
1969
|
+
);
|
|
1970
|
+
});
|
|
1971
|
+
video.addEventListener("pause", () => {
|
|
1972
|
+
this.tracker.send(
|
|
1973
|
+
"custom_event",
|
|
1974
|
+
{
|
|
1975
|
+
event_name: "video_pause",
|
|
1976
|
+
src,
|
|
1977
|
+
position_seconds: Math.round(video.currentTime)
|
|
1978
|
+
},
|
|
1979
|
+
"video_pause"
|
|
1980
|
+
);
|
|
1981
|
+
});
|
|
1982
|
+
video.addEventListener("timeupdate", () => {
|
|
1983
|
+
if (!video.duration || video.duration === Infinity) return;
|
|
1984
|
+
const pct = video.currentTime / video.duration;
|
|
1985
|
+
if (pct >= 0.5 && !milestone50Fired) {
|
|
1986
|
+
milestone50Fired = true;
|
|
1987
|
+
this.tracker.send(
|
|
1988
|
+
"custom_event",
|
|
1989
|
+
{
|
|
1990
|
+
event_name: "video_50_percent",
|
|
1991
|
+
src
|
|
1992
|
+
},
|
|
1993
|
+
"video_50_percent"
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
video.addEventListener("ended", () => {
|
|
1998
|
+
this.tracker.send(
|
|
1999
|
+
"custom_event",
|
|
2000
|
+
{
|
|
2001
|
+
event_name: "video_complete",
|
|
2002
|
+
src,
|
|
2003
|
+
duration_seconds: Math.round(video.duration)
|
|
2004
|
+
},
|
|
2005
|
+
"video_complete"
|
|
2006
|
+
);
|
|
2007
|
+
});
|
|
2008
|
+
};
|
|
2009
|
+
document.querySelectorAll("video").forEach((v) => attachVideoListeners(v));
|
|
2010
|
+
const observer = new MutationObserver((mutations) => {
|
|
2011
|
+
mutations.forEach((m) => {
|
|
2012
|
+
m.addedNodes.forEach((node) => {
|
|
2013
|
+
if (node instanceof HTMLVideoElement) {
|
|
2014
|
+
attachVideoListeners(node);
|
|
2015
|
+
}
|
|
2016
|
+
if (node instanceof Element) {
|
|
2017
|
+
node.querySelectorAll("video").forEach((v) => attachVideoListeners(v));
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
});
|
|
2021
|
+
});
|
|
2022
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
2023
|
+
this.cleanupFns.push(() => observer.disconnect());
|
|
2024
|
+
}
|
|
2025
|
+
setupErrorTracking() {
|
|
2026
|
+
if (typeof window === "undefined") return;
|
|
2027
|
+
const errorHandler = (e) => {
|
|
2028
|
+
this.tracker.send("error", {
|
|
2029
|
+
message: e.message,
|
|
2030
|
+
filename: e.filename,
|
|
2031
|
+
lineno: e.lineno,
|
|
2032
|
+
colno: e.colno,
|
|
2033
|
+
stack: _optionalChain([e, 'access', _38 => _38.error, 'optionalAccess', _39 => _39.stack]),
|
|
2034
|
+
url: window.location.href,
|
|
2035
|
+
type: "uncaught_error"
|
|
2036
|
+
});
|
|
2037
|
+
};
|
|
2038
|
+
const rejectionHandler = (e) => {
|
|
2039
|
+
const reason = e.reason instanceof Error ? e.reason.message : String(e.reason);
|
|
2040
|
+
const stack = e.reason instanceof Error ? e.reason.stack : void 0;
|
|
2041
|
+
this.tracker.send("error", {
|
|
2042
|
+
message: reason,
|
|
2043
|
+
stack,
|
|
2044
|
+
url: window.location.href,
|
|
2045
|
+
type: "unhandled_rejection"
|
|
2046
|
+
});
|
|
2047
|
+
};
|
|
2048
|
+
window.addEventListener("error", errorHandler);
|
|
2049
|
+
window.addEventListener("unhandledrejection", rejectionHandler);
|
|
2050
|
+
this.cleanupFns.push(() => {
|
|
2051
|
+
window.removeEventListener("error", errorHandler);
|
|
2052
|
+
window.removeEventListener("unhandledrejection", rejectionHandler);
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
1821
2055
|
setupRouteTracking() {
|
|
1822
2056
|
if (typeof window === "undefined" || typeof window.history === "undefined")
|
|
1823
2057
|
return;
|
|
@@ -1845,10 +2079,108 @@ var AnalyticsEngine = class {
|
|
|
1845
2079
|
window.removeEventListener("popstate", popStateHandler);
|
|
1846
2080
|
});
|
|
1847
2081
|
}
|
|
1848
|
-
// Simple flag to check if NexusProvider is active
|
|
1849
2082
|
isInitializedByReactProvider() {
|
|
1850
2083
|
return !!document.getElementById("__nexus_react_active");
|
|
1851
2084
|
}
|
|
2085
|
+
async identify(userId, traits = {}) {
|
|
2086
|
+
return this.sendIdentityRequest("identify", { user_id: userId, traits });
|
|
2087
|
+
}
|
|
2088
|
+
async group(groupId, traits = {}) {
|
|
2089
|
+
return this.sendIdentityRequest("group", { group_id: groupId, traits });
|
|
2090
|
+
}
|
|
2091
|
+
async alias(newId) {
|
|
2092
|
+
return this.sendIdentityRequest("alias", {
|
|
2093
|
+
previous_id: this.tracker.getSession(),
|
|
2094
|
+
user_id: newId
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
reset(performGdprScrub = false) {
|
|
2098
|
+
const config = this.tracker.config;
|
|
2099
|
+
this.tracker.stop();
|
|
2100
|
+
localStorage.removeItem("nexus_sid");
|
|
2101
|
+
localStorage.removeItem("nexus_vid");
|
|
2102
|
+
safeRemoveItem2("nexus_anon_id");
|
|
2103
|
+
this.tracker.clearIdentity();
|
|
2104
|
+
if (performGdprScrub) {
|
|
2105
|
+
const endpoint = `${config.analyticsUrl}/api/privacy/scrub`;
|
|
2106
|
+
const userId = this.tracker.getVisitorId();
|
|
2107
|
+
fetch(endpoint, {
|
|
2108
|
+
method: "DELETE",
|
|
2109
|
+
headers: {
|
|
2110
|
+
"Content-Type": "application/json",
|
|
2111
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
2112
|
+
},
|
|
2113
|
+
body: JSON.stringify({
|
|
2114
|
+
project_id: config.projectId,
|
|
2115
|
+
user_id: userId
|
|
2116
|
+
})
|
|
2117
|
+
}).catch((err) => console.error("[NexusHub] Privacy scrub failed:", err));
|
|
2118
|
+
}
|
|
2119
|
+
window.location.reload();
|
|
2120
|
+
}
|
|
2121
|
+
track(eventName, properties = {}) {
|
|
2122
|
+
this.tracker.send(
|
|
2123
|
+
"custom_event",
|
|
2124
|
+
{ event_name: eventName, ...properties },
|
|
2125
|
+
eventName
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
trackPurchase(orderData) {
|
|
2129
|
+
const ecommerce = {
|
|
2130
|
+
orderId: orderData.orderId,
|
|
2131
|
+
total: orderData.total,
|
|
2132
|
+
revenue: _nullishCoalesce(orderData.revenue, () => ( orderData.total)),
|
|
2133
|
+
currency: orderData.currency || "USD",
|
|
2134
|
+
products: orderData.products.map((p) => ({
|
|
2135
|
+
productId: p.id,
|
|
2136
|
+
sku: p.sku,
|
|
2137
|
+
name: p.name,
|
|
2138
|
+
price: p.price,
|
|
2139
|
+
quantity: p.quantity
|
|
2140
|
+
}))
|
|
2141
|
+
};
|
|
2142
|
+
this.tracker.send(
|
|
2143
|
+
"purchase",
|
|
2144
|
+
{ order_id: orderData.orderId },
|
|
2145
|
+
"purchase",
|
|
2146
|
+
ecommerce
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
trackError(error, context) {
|
|
2150
|
+
this.tracker.send("error", {
|
|
2151
|
+
message: error.message,
|
|
2152
|
+
stack: error.stack,
|
|
2153
|
+
context,
|
|
2154
|
+
url: typeof window !== "undefined" ? window.location.href : "",
|
|
2155
|
+
type: "manual"
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
async sendIdentityRequest(type, data) {
|
|
2159
|
+
try {
|
|
2160
|
+
const config = this.tracker.config;
|
|
2161
|
+
const endpoint = `${config.analyticsUrl}/api/${type}`;
|
|
2162
|
+
const payload = {
|
|
2163
|
+
projectId: config.projectId,
|
|
2164
|
+
sessionId: this.tracker.getSession(),
|
|
2165
|
+
visitorId: this.tracker.getVisitorId(),
|
|
2166
|
+
anonymousId: this.tracker.getAnonymousId(),
|
|
2167
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2168
|
+
...data
|
|
2169
|
+
};
|
|
2170
|
+
const response = await fetch(endpoint, {
|
|
2171
|
+
method: "POST",
|
|
2172
|
+
headers: {
|
|
2173
|
+
"Content-Type": "application/json",
|
|
2174
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
2175
|
+
},
|
|
2176
|
+
body: JSON.stringify(payload)
|
|
2177
|
+
});
|
|
2178
|
+
return response.ok;
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
console.error(`[NexusHub] ${type} failed:`, error);
|
|
2181
|
+
return false;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
1852
2184
|
getSessionId() {
|
|
1853
2185
|
return this.tracker.getSession();
|
|
1854
2186
|
}
|
|
@@ -1858,24 +2190,34 @@ var AnalyticsEngine = class {
|
|
|
1858
2190
|
if (isFinalShutdown) {
|
|
1859
2191
|
this.tracker.send("session_end", {
|
|
1860
2192
|
session_id: this.tracker.getSession(),
|
|
1861
|
-
duration: this.tracker.getSessionDuration()
|
|
2193
|
+
duration: this.tracker.getSessionDuration(),
|
|
2194
|
+
max_scroll_depth: this.maxScrollDepth / 100
|
|
1862
2195
|
});
|
|
1863
2196
|
}
|
|
1864
2197
|
this.tracker.stop();
|
|
1865
2198
|
this.isInitialized = false;
|
|
1866
2199
|
}
|
|
1867
2200
|
};
|
|
2201
|
+
function getSelector(el, depth = 0) {
|
|
2202
|
+
if (!el || depth > 2) return "";
|
|
2203
|
+
const tag = el.tagName.toLowerCase();
|
|
2204
|
+
const id = el.id ? `#${el.id}` : "";
|
|
2205
|
+
const cls = el.className && typeof el.className === "string" ? `.${el.className.trim().split(/\s+/).slice(0, 2).join(".")}` : "";
|
|
2206
|
+
const self = `${tag}${id}${cls}`;
|
|
2207
|
+
const parent = el.parentElement && depth < 2 ? `${getSelector(el.parentElement, depth + 1)} > ` : "";
|
|
2208
|
+
return `${parent}${self}`;
|
|
2209
|
+
}
|
|
1868
2210
|
|
|
1869
2211
|
// src/client.ts
|
|
1870
2212
|
var NexusClient = class {
|
|
1871
2213
|
constructor(config) {
|
|
1872
2214
|
const fullConfig = getFullConfig(config);
|
|
1873
2215
|
this.config = {
|
|
1874
|
-
debug: _nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
1875
|
-
cacheStrategy: _nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
1876
|
-
revalidateTime: _nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
1877
|
-
timeout: _nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
1878
|
-
retries: _nullishCoalesce(_optionalChain([config, 'optionalAccess',
|
|
2216
|
+
debug: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _40 => _40.debug]), () => ( false)),
|
|
2217
|
+
cacheStrategy: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _41 => _41.cacheStrategy]), () => ( "memory")),
|
|
2218
|
+
revalidateTime: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _42 => _42.revalidateTime]), () => ( 60)),
|
|
2219
|
+
timeout: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _43 => _43.timeout]), () => ( 1e4)),
|
|
2220
|
+
retries: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _44 => _44.retries]), () => ( 3)),
|
|
1879
2221
|
...fullConfig
|
|
1880
2222
|
};
|
|
1881
2223
|
const errors = validateConfig(this.config);
|
|
@@ -1922,6 +2264,8 @@ var createNexusClient = (config) => new NexusClient(config);
|
|
|
1922
2264
|
|
|
1923
2265
|
|
|
1924
2266
|
|
|
2267
|
+
|
|
2268
|
+
|
|
1925
2269
|
var _react = require('react'); var _react2 = _interopRequireDefault(_react);
|
|
1926
2270
|
var _navigation = require('next/navigation');
|
|
1927
2271
|
|
|
@@ -2111,7 +2455,7 @@ async function parseError(res) {
|
|
|
2111
2455
|
code: json.code || "UNKNOWN_ERROR",
|
|
2112
2456
|
message: json.message || "An error occurred during authentication"
|
|
2113
2457
|
};
|
|
2114
|
-
} catch (
|
|
2458
|
+
} catch (e12) {
|
|
2115
2459
|
return {
|
|
2116
2460
|
status: res.status,
|
|
2117
2461
|
code: "NETWORK_ERROR",
|
|
@@ -2123,6 +2467,10 @@ async function parseError(res) {
|
|
|
2123
2467
|
// src/components/NexusProvider.tsx
|
|
2124
2468
|
|
|
2125
2469
|
var NexusContext = _react.createContext.call(void 0, nexus);
|
|
2470
|
+
var NexusLiveFeedContext = _react.createContext.call(void 0, {
|
|
2471
|
+
latestEvent: null,
|
|
2472
|
+
isConnected: false
|
|
2473
|
+
});
|
|
2126
2474
|
function NexusAnalyticsTracker({
|
|
2127
2475
|
disableAnalytics
|
|
2128
2476
|
}) {
|
|
@@ -2141,9 +2489,24 @@ function NexusAnalyticsTracker({
|
|
|
2141
2489
|
var NexusProvider = ({
|
|
2142
2490
|
children,
|
|
2143
2491
|
projectId,
|
|
2144
|
-
disableAnalytics = false
|
|
2492
|
+
disableAnalytics = false,
|
|
2493
|
+
hasConsent = true,
|
|
2494
|
+
// NEW: default true to preserve existing behaviour
|
|
2495
|
+
enableLiveFeed = false,
|
|
2496
|
+
// NEW
|
|
2497
|
+
onLiveEvent
|
|
2498
|
+
// NEW
|
|
2145
2499
|
}) => {
|
|
2146
2500
|
const isInitialized = _react.useRef.call(void 0, false);
|
|
2501
|
+
const socketRef = _react.useRef.call(void 0, null);
|
|
2502
|
+
const [latestEvent, setLatestEvent] = _react.useState.call(void 0,
|
|
2503
|
+
null
|
|
2504
|
+
);
|
|
2505
|
+
const [isConnected, setIsConnected] = _react.useState.call(void 0, false);
|
|
2506
|
+
const liveFeedValue = _react.useMemo.call(void 0,
|
|
2507
|
+
() => ({ latestEvent, isConnected }),
|
|
2508
|
+
[latestEvent, isConnected]
|
|
2509
|
+
);
|
|
2147
2510
|
const config = _react.useMemo.call(void 0, () => {
|
|
2148
2511
|
if (projectId && nexus.getConfig().projectId !== projectId) {
|
|
2149
2512
|
nexus.updateConfig({ projectId });
|
|
@@ -2151,7 +2514,8 @@ var NexusProvider = ({
|
|
|
2151
2514
|
return nexus.getConfig();
|
|
2152
2515
|
}, [projectId]);
|
|
2153
2516
|
_react.useEffect.call(void 0, () => {
|
|
2154
|
-
if (typeof window === "undefined" || disableAnalytics)
|
|
2517
|
+
if (typeof window === "undefined" || disableAnalytics || !hasConsent)
|
|
2518
|
+
return;
|
|
2155
2519
|
if (!isInitialized.current) {
|
|
2156
2520
|
if (!nexus.analytics) {
|
|
2157
2521
|
nexus.analytics = new AnalyticsEngine(nexus.getConfig());
|
|
@@ -2168,11 +2532,53 @@ var NexusProvider = ({
|
|
|
2168
2532
|
isInitialized.current = false;
|
|
2169
2533
|
}
|
|
2170
2534
|
};
|
|
2171
|
-
}, [disableAnalytics]);
|
|
2172
|
-
|
|
2535
|
+
}, [disableAnalytics, hasConsent]);
|
|
2536
|
+
const connectLiveFeed = _react.useCallback.call(void 0, async () => {
|
|
2537
|
+
if (typeof window === "undefined" || !enableLiveFeed) return;
|
|
2538
|
+
try {
|
|
2539
|
+
const { io } = await Promise.resolve().then(() => _interopRequireWildcard(require("socket.io-client")));
|
|
2540
|
+
const cfg = nexus.getConfig();
|
|
2541
|
+
const wsUrl = cfg.apiUrl || "http://localhost:3001";
|
|
2542
|
+
const socket = io(`${wsUrl}/analytics`, {
|
|
2543
|
+
auth: { token: cfg.apiKey },
|
|
2544
|
+
query: { projectId: cfg.projectId },
|
|
2545
|
+
transports: ["websocket"],
|
|
2546
|
+
reconnectionAttempts: 5,
|
|
2547
|
+
reconnectionDelay: 2e3
|
|
2548
|
+
});
|
|
2549
|
+
socket.on("connect", () => {
|
|
2550
|
+
setIsConnected(true);
|
|
2551
|
+
if (cfg.debug) console.log("[NexusHub] \u{1F534} Live feed connected");
|
|
2552
|
+
});
|
|
2553
|
+
socket.on("disconnect", () => {
|
|
2554
|
+
setIsConnected(false);
|
|
2555
|
+
if (cfg.debug) console.log("[NexusHub] Live feed disconnected");
|
|
2556
|
+
});
|
|
2557
|
+
socket.on("live_event", (event) => {
|
|
2558
|
+
setLatestEvent(event);
|
|
2559
|
+
_optionalChain([onLiveEvent, 'optionalCall', _45 => _45(event)]);
|
|
2560
|
+
});
|
|
2561
|
+
socketRef.current = socket;
|
|
2562
|
+
} catch (err) {
|
|
2563
|
+
console.error("[NexusHub] Live feed connection failed:", err);
|
|
2564
|
+
}
|
|
2565
|
+
}, [enableLiveFeed, onLiveEvent]);
|
|
2566
|
+
_react.useEffect.call(void 0, () => {
|
|
2567
|
+
if (enableLiveFeed) {
|
|
2568
|
+
connectLiveFeed();
|
|
2569
|
+
}
|
|
2570
|
+
return () => {
|
|
2571
|
+
if (socketRef.current) {
|
|
2572
|
+
socketRef.current.disconnect();
|
|
2573
|
+
socketRef.current = null;
|
|
2574
|
+
setIsConnected(false);
|
|
2575
|
+
}
|
|
2576
|
+
};
|
|
2577
|
+
}, [enableLiveFeed, connectLiveFeed]);
|
|
2578
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, NexusContext.Provider, { value: nexus, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, NexusLiveFeedContext.Provider, { value: liveFeedValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, AuthProvider, { config, children: [
|
|
2173
2579
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react.Suspense, { fallback: null, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, NexusAnalyticsTracker, { disableAnalytics }) }),
|
|
2174
2580
|
children
|
|
2175
|
-
] }) });
|
|
2581
|
+
] }) }) });
|
|
2176
2582
|
};
|
|
2177
2583
|
var useNexus = () => {
|
|
2178
2584
|
const context = _react2.default.useContext(NexusContext);
|