@nexushub/client 0.2.6 → 0.2.8

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 CHANGED
@@ -349,18 +349,21 @@ var RateLimiter = class {
349
349
  this.config = config;
350
350
  }
351
351
  async checkLimit() {
352
- const now = Date.now();
353
- const windowStart = now - this.config.timeWindow;
354
- this.requests = this.requests.filter((time) => time > windowStart);
355
- if (this.requests.length >= this.config.maxRequests) {
352
+ while (true) {
353
+ const now = Date.now();
354
+ const windowStart = now - this.config.timeWindow;
355
+ this.requests = this.requests.filter((time) => time > windowStart);
356
+ if (this.requests.length < this.config.maxRequests) {
357
+ this.requests.push(now);
358
+ return;
359
+ }
356
360
  const oldestRequest = this.requests[0];
357
- const waitTime = windowStart + this.config.timeWindow - now;
361
+ const waitTime = oldestRequest + this.config.timeWindow - now;
358
362
  if (waitTime > 0) {
359
363
  await new Promise((resolve) => setTimeout(resolve, waitTime));
360
- return this.checkLimit();
364
+ } else {
361
365
  }
362
366
  }
363
- this.requests.push(now);
364
367
  }
365
368
  getStats() {
366
369
  const now = Date.now();
@@ -368,21 +371,15 @@ var RateLimiter = class {
368
371
  const currentRequests = this.requests.filter(
369
372
  (time) => time > windowStart
370
373
  ).length;
371
- return {
372
- currentRequests,
373
- limit: this.config.maxRequests
374
- };
374
+ return { currentRequests, limit: this.config.maxRequests };
375
375
  }
376
376
  };
377
377
  var ExponentialBackoff = class {
378
378
  constructor(config) {
379
- this.config = {
380
- jitter: true,
381
- ...config
382
- };
379
+ this.config = { jitter: true, ...config };
383
380
  }
384
381
  async execute(fn, onRetry) {
385
- let lastError;
382
+ let lastError = new Error("Unknown error");
386
383
  for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
387
384
  try {
388
385
  return await fn();
@@ -391,13 +388,9 @@ var ExponentialBackoff = class {
391
388
  if (this.isClientError(error) && !this.isRateLimitError(error)) {
392
389
  throw error;
393
390
  }
394
- if (attempt === this.config.maxRetries) {
395
- break;
396
- }
391
+ if (attempt === this.config.maxRetries) break;
397
392
  const delay = this.calculateDelay(attempt);
398
- if (onRetry) {
399
- onRetry(attempt + 1, delay, error);
400
- }
393
+ if (onRetry) onRetry(attempt + 1, delay, error);
401
394
  await new Promise((resolve) => setTimeout(resolve, delay));
402
395
  }
403
396
  }
@@ -429,8 +422,7 @@ var CircuitBreaker = class {
429
422
  // 30 seconds
430
423
  isOpen() {
431
424
  if (this.state === 1 /* OPEN */) {
432
- const now = Date.now();
433
- if (now - this.lastFailureTime > this.resetTimeout) {
425
+ if (Date.now() - this.lastFailureTime > this.resetTimeout) {
434
426
  this.state = 2 /* HALF_OPEN */;
435
427
  return false;
436
428
  }
@@ -449,14 +441,11 @@ var CircuitBreaker = class {
449
441
  this.state = 1 /* OPEN */;
450
442
  if (process.env.NODE_ENV === "development") {
451
443
  console.warn(
452
- `[NexusHub] \u{1F50C} Circuit Breaker OPEN. Pausing network requests.`
444
+ "[NexusHub] \u{1F50C} Circuit Breaker OPEN. Pausing network requests."
453
445
  );
454
446
  }
455
447
  }
456
448
  }
457
- isIgnorableError(error) {
458
- return _optionalChain([error, 'optionalAccess', _13 => _13.status]) === 401 || _optionalChain([error, 'optionalAccess', _14 => _14.status]) === 404;
459
- }
460
449
  };
461
450
  var RequestBatcher = class {
462
451
  constructor(batchWindow = 10, maxBatchSize = 20) {
@@ -489,13 +478,9 @@ var RequestBatcher = class {
489
478
  this.batch = [];
490
479
  try {
491
480
  const result = await request();
492
- currentBatch.forEach((item) => {
493
- item.resolve(result);
494
- });
481
+ currentBatch.forEach((item) => item.resolve(result));
495
482
  } catch (error) {
496
- currentBatch.forEach((item) => {
497
- item.reject(error);
498
- });
483
+ currentBatch.forEach((item) => item.reject(error));
499
484
  } finally {
500
485
  this.processing = false;
501
486
  if (this.batch.length > 0) {
@@ -534,10 +519,10 @@ function buildQueryString(query) {
534
519
  if (query.sort) params.append("sort", query.sort);
535
520
  if (query.order) params.append("order", query.order);
536
521
  if (query.search) params.append("search", query.search);
537
- if (_optionalChain([query, 'access', _15 => _15.include, 'optionalAccess', _16 => _16.length])) {
522
+ if (_optionalChain([query, 'access', _13 => _13.include, 'optionalAccess', _14 => _14.length])) {
538
523
  params.append("include", query.include.join(","));
539
524
  }
540
- if (_optionalChain([query, 'access', _17 => _17.fields, 'optionalAccess', _18 => _18.length])) {
525
+ if (_optionalChain([query, 'access', _15 => _15.fields, 'optionalAccess', _16 => _16.length])) {
541
526
  params.append("fields", query.fields.join(","));
542
527
  }
543
528
  if (query.filter) {
@@ -819,7 +804,7 @@ var ContentEngine = class {
819
804
  }
820
805
  return this.requestBatcher.schedule(cacheKey, async () => {
821
806
  const params = new URLSearchParams();
822
- if (_optionalChain([options, 'access', _19 => _19.include, 'optionalAccess', _20 => _20.length])) {
807
+ if (_optionalChain([options, 'access', _17 => _17.include, 'optionalAccess', _18 => _18.length])) {
823
808
  params.append("include", options.include.join(","));
824
809
  }
825
810
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}/${itemId}?${params}`;
@@ -852,10 +837,10 @@ var ContentEngine = class {
852
837
  q: query,
853
838
  limit: (options.limit || 20).toString()
854
839
  });
855
- if (_optionalChain([options, 'access', _21 => _21.collections, 'optionalAccess', _22 => _22.length])) {
840
+ if (_optionalChain([options, 'access', _19 => _19.collections, 'optionalAccess', _20 => _20.length])) {
856
841
  params.append("collections", options.collections.join(","));
857
842
  }
858
- if (_optionalChain([options, 'access', _23 => _23.fields, 'optionalAccess', _24 => _24.length])) {
843
+ if (_optionalChain([options, 'access', _21 => _21.fields, 'optionalAccess', _22 => _22.length])) {
859
844
  params.append("fields", options.fields.join(","));
860
845
  }
861
846
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/search?${params}`;
@@ -916,7 +901,7 @@ var ContentEngine = class {
916
901
  }
917
902
  };
918
903
  eventSource.onerror = () => {
919
- _optionalChain([eventSource, 'optionalAccess', _25 => _25.close, 'call', _26 => _26()]);
904
+ _optionalChain([eventSource, 'optionalAccess', _23 => _23.close, 'call', _24 => _24()]);
920
905
  if (isClosed) return;
921
906
  const timeout = Math.min(1e3 * Math.pow(2, retryCount), 1e4);
922
907
  retryCount++;
@@ -929,7 +914,7 @@ var ContentEngine = class {
929
914
  connect();
930
915
  return () => {
931
916
  isClosed = true;
932
- _optionalChain([eventSource, 'optionalAccess', _27 => _27.close, 'call', _28 => _28()]);
917
+ _optionalChain([eventSource, 'optionalAccess', _25 => _25.close, 'call', _26 => _26()]);
933
918
  };
934
919
  }
935
920
  // --- CACHE MANAGEMENT ---
@@ -1255,23 +1240,25 @@ var ContentEngine = class {
1255
1240
  };
1256
1241
 
1257
1242
  // src/analytics/fingerprint.ts
1243
+ var cachedEntropy = null;
1258
1244
  var getDeviceEntropy = async () => {
1245
+ if (cachedEntropy) return cachedEntropy;
1259
1246
  if (typeof window === "undefined") return {};
1260
1247
  const nav = window.navigator;
1261
- return {
1248
+ cachedEntropy = {
1262
1249
  screen_resolution: `${window.screen.width}x${window.screen.height}`,
1263
1250
  color_depth: window.screen.colorDepth,
1264
1251
  pixel_ratio: window.devicePixelRatio || 1,
1265
1252
  hardware_concurrency: nav.hardwareConcurrency,
1266
1253
  device_memory: nav.deviceMemory,
1267
- // Only available in Chrome/Edge
1254
+ // Chrome/Edge only
1268
1255
  timezone_offset: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
1269
1256
  platform: nav.platform,
1270
1257
  language: nav.language,
1271
1258
  touch_support: "ontouchstart" in window || nav.maxTouchPoints > 0,
1272
- // Optional: Calculate Canvas Fingerprint for high-security modes
1273
1259
  canvas_hash: await generateCanvasHash()
1274
1260
  };
1261
+ return cachedEntropy;
1275
1262
  };
1276
1263
  var generateCanvasHash = async () => {
1277
1264
  try {
@@ -1389,6 +1376,7 @@ var initVitals = (tracker) => {
1389
1376
  var DB_NAME = "NexusHub_Analytics";
1390
1377
  var STORE_NAME = "events_queue";
1391
1378
  var DB_VERSION = 2;
1379
+ var MAX_QUEUE_SIZE = 500;
1392
1380
  var EventStorage = class {
1393
1381
  constructor() {
1394
1382
  this.db = null;
@@ -1398,7 +1386,7 @@ var EventStorage = class {
1398
1386
  if (typeof window === "undefined" || !window.indexedDB) {
1399
1387
  return Promise.resolve();
1400
1388
  }
1401
- return new Promise((resolve, reject) => {
1389
+ return new Promise((resolve) => {
1402
1390
  const request = indexedDB.open(DB_NAME, DB_VERSION);
1403
1391
  request.onerror = () => {
1404
1392
  console.warn(
@@ -1423,11 +1411,19 @@ var EventStorage = class {
1423
1411
  });
1424
1412
  }
1425
1413
  /**
1426
- * Add an event to the persistent queue
1414
+ * Add an event to the persistent queue.
1415
+ * Evicts the oldest entry if the queue is already at MAX_QUEUE_SIZE.
1427
1416
  */
1428
1417
  async enqueue(payload) {
1429
1418
  await this.isReady;
1430
1419
  if (!this.db) return;
1420
+ const currentCount = await this.count();
1421
+ if (currentCount >= MAX_QUEUE_SIZE) {
1422
+ const oldest = await this.peek(1);
1423
+ if (oldest.length > 0 && oldest[0].id !== void 0) {
1424
+ await this.remove([oldest[0].id]);
1425
+ }
1426
+ }
1431
1427
  return new Promise((resolve, reject) => {
1432
1428
  const transaction = this.db.transaction([STORE_NAME], "readwrite");
1433
1429
  const store = transaction.objectStore(STORE_NAME);
@@ -1441,7 +1437,7 @@ var EventStorage = class {
1441
1437
  });
1442
1438
  }
1443
1439
  /**
1444
- * Get a batch of oldest events
1440
+ * Get a batch of the oldest events without removing them.
1445
1441
  */
1446
1442
  async peek(limit = 20) {
1447
1443
  await this.isReady;
@@ -1455,7 +1451,7 @@ var EventStorage = class {
1455
1451
  });
1456
1452
  }
1457
1453
  /**
1458
- * Remove events after successful upload
1454
+ * Remove events by ID after a successful upload.
1459
1455
  */
1460
1456
  async remove(ids) {
1461
1457
  await this.isReady;
@@ -1463,17 +1459,16 @@ var EventStorage = class {
1463
1459
  return new Promise((resolve, reject) => {
1464
1460
  const transaction = this.db.transaction([STORE_NAME], "readwrite");
1465
1461
  const store = transaction.objectStore(STORE_NAME);
1466
- let processed = 0;
1467
- let errors = 0;
1468
1462
  transaction.oncomplete = () => resolve();
1469
1463
  transaction.onerror = () => reject(transaction.error);
1464
+ transaction.onabort = () => reject(new Error("Delete transaction aborted"));
1470
1465
  ids.forEach((id) => {
1471
- const req = store.delete(id);
1466
+ store.delete(id);
1472
1467
  });
1473
1468
  });
1474
1469
  }
1475
1470
  /**
1476
- * Count pending events
1471
+ * Count pending events in the queue.
1477
1472
  */
1478
1473
  async count() {
1479
1474
  await this.isReady;
@@ -1491,14 +1486,11 @@ var eventStorage = new EventStorage();
1491
1486
 
1492
1487
  // src/analytics/tracker.ts
1493
1488
  var Tracker = class {
1494
- // 🆕
1495
1489
  constructor(config) {
1496
1490
  this.sessionId = "";
1497
1491
  this.visitorId = "";
1498
- // FIX: Add this property
1499
1492
  this.isFlushing = false;
1500
1493
  this.config = config;
1501
- const baseUrl = this.config.apiUrl.replace("/v1", "").replace(/\/$/, "");
1502
1494
  this.endpoint = `${this.config.analyticsUrl}/api/collect`;
1503
1495
  this.circuitBreaker = new CircuitBreaker();
1504
1496
  this.sessionStart = Date.now();
@@ -1510,11 +1502,13 @@ var Tracker = class {
1510
1502
  async initSession() {
1511
1503
  this.visitorId = await getVisitorId();
1512
1504
  let sid = localStorage.getItem("nexus_sid");
1513
- let lastActivity = localStorage.getItem("nexus_last_active");
1505
+ const lastActivity = localStorage.getItem("nexus_last_active");
1514
1506
  const now = Date.now();
1515
1507
  const SESSION_TIMEOUT = 30 * 60 * 1e3;
1516
- if (!sid || !lastActivity || now - parseInt(lastActivity) > SESSION_TIMEOUT) {
1517
- sid = `sess_${Math.random().toString(36).substring(2, 9)}_${now}`;
1508
+ const isExpired = !sid || !lastActivity || now - parseInt(lastActivity, 10) > SESSION_TIMEOUT;
1509
+ if (isExpired) {
1510
+ const uuid = crypto.randomUUID().replace(/-/g, "").substring(0, 16);
1511
+ sid = `sess_${uuid}_${now}`;
1518
1512
  localStorage.setItem("nexus_sid", sid);
1519
1513
  this.sessionStart = now;
1520
1514
  }
@@ -1543,7 +1537,6 @@ var Tracker = class {
1543
1537
  context: {
1544
1538
  device: {
1545
1539
  hardwareConcurrency: entropy.hardware_concurrency,
1546
- // Match Rust sub-struct too if needed
1547
1540
  deviceMemory: entropy.device_memory,
1548
1541
  pixelRatio: entropy.pixel_ratio,
1549
1542
  canvasFingerprint: entropy.canvas_hash,
@@ -1567,9 +1560,7 @@ var Tracker = class {
1567
1560
  }
1568
1561
  async flushQueue(useBeacon = false) {
1569
1562
  if (this.isFlushing) return;
1570
- if (this.circuitBreaker.isOpen()) {
1571
- return;
1572
- }
1563
+ if (this.circuitBreaker.isOpen()) return;
1573
1564
  this.isFlushing = true;
1574
1565
  try {
1575
1566
  const storedEvents = await eventStorage.peek(20);
@@ -1619,11 +1610,9 @@ var Tracker = class {
1619
1610
  getSession() {
1620
1611
  return this.sessionId;
1621
1612
  }
1622
- // FIX: Add this method to satisfy AnalyticsEngine
1623
1613
  getSessionDuration() {
1624
1614
  return Date.now() - this.sessionStart;
1625
1615
  }
1626
- // FIX: Add this method to satisfy AnalyticsEngine
1627
1616
  stop() {
1628
1617
  if (this.flushInterval) {
1629
1618
  clearInterval(this.flushInterval);
@@ -1648,7 +1637,9 @@ var AnalyticsEngine = class {
1648
1637
  this.setupFormTracking();
1649
1638
  this.setupRouteTracking();
1650
1639
  this.setupShareTracking();
1651
- console.log("[NexusHub] \u{1F680} Analytics Engine Started");
1640
+ if (process.env.NODE_ENV === "development") {
1641
+ console.log("[NexusHub] \u{1F680} Analytics Engine Started");
1642
+ }
1652
1643
  }
1653
1644
  pageView() {
1654
1645
  if (typeof window === "undefined") return;
@@ -1659,7 +1650,7 @@ var AnalyticsEngine = class {
1659
1650
  timezone_offset: (/* @__PURE__ */ new Date()).getTimezoneOffset()
1660
1651
  });
1661
1652
  }
1662
- // 🆕 PRIVATE: Track "Dark Social" Intent
1653
+ // --- SOCIAL / DARK SOCIAL TRACKING ---
1663
1654
  setupShareTracking() {
1664
1655
  if (typeof window === "undefined") return;
1665
1656
  const copyHandler = () => {
@@ -1668,58 +1659,38 @@ var AnalyticsEngine = class {
1668
1659
  url: window.location.href
1669
1660
  });
1670
1661
  };
1671
- if (navigator.share) {
1672
- const originalShare = navigator.share;
1662
+ window.addEventListener("copy", copyHandler, { passive: true });
1663
+ this.cleanupFns.push(() => window.removeEventListener("copy", copyHandler));
1664
+ if (typeof navigator !== "undefined" && navigator.share) {
1665
+ const originalShare = navigator.share.bind(navigator);
1673
1666
  navigator.share = (data) => {
1674
1667
  this.tracker.send("social_share", {
1675
1668
  method: "native_share_menu",
1676
- url: _optionalChain([data, 'optionalAccess', _29 => _29.url]) || window.location.href,
1677
- title: _optionalChain([data, 'optionalAccess', _30 => _30.title])
1669
+ url: _optionalChain([data, 'optionalAccess', _27 => _27.url]) || window.location.href,
1670
+ title: _optionalChain([data, 'optionalAccess', _28 => _28.title])
1678
1671
  });
1679
- return originalShare.apply(navigator, [data]);
1672
+ return originalShare(data);
1680
1673
  };
1674
+ this.cleanupFns.push(() => {
1675
+ navigator.share = originalShare;
1676
+ });
1681
1677
  }
1682
- window.addEventListener("copy", copyHandler, { passive: true });
1683
- this.cleanupFns.push(() => window.removeEventListener("copy", copyHandler));
1684
1678
  }
1685
- // --- IDENTITY & B2B (The Missing Pieces) ---
1686
- /**
1687
- * 1. IDENTIFY: Link anonymous session to a User ID
1688
- */
1679
+ // --- IDENTITY & B2B ---
1689
1680
  async identify(userId, traits = {}) {
1690
- return this.sendIdentityRequest("identify", {
1691
- user_id: userId,
1692
- traits
1693
- });
1681
+ return this.sendIdentityRequest("identify", { user_id: userId, traits });
1694
1682
  }
1695
- /**
1696
- * 2. GROUP: Link the current user to a Company/Organization (B2B)
1697
- * Required for the /api/group Rust route.
1698
- */
1699
1683
  async group(groupId, traits = {}) {
1700
- return this.sendIdentityRequest("group", {
1701
- group_id: groupId,
1702
- // The Rust backend handles looking up the user from the session if not provided,
1703
- // but passing the user_id if known is safer.
1704
- traits
1705
- });
1684
+ return this.sendIdentityRequest("group", { group_id: groupId, traits });
1706
1685
  }
1707
- /**
1708
- * 3. ALIAS: Merge two identities (e.g. "Guest_123" -> "User_99")
1709
- * Required for the /api/alias Rust route.
1710
- */
1711
1686
  async alias(newId) {
1712
1687
  return this.sendIdentityRequest("alias", {
1713
1688
  previous_id: this.tracker.getSession(),
1714
- // Or the old visitor_id
1715
1689
  user_id: newId
1716
1690
  });
1717
1691
  }
1718
- /**
1719
- * 4. RESET: Clear local data and (optionally) request GDPR scrub
1720
- */
1721
1692
  reset(performGdprScrub = false) {
1722
- const config = this.tracker["config"];
1693
+ const config = this.tracker.config;
1723
1694
  this.tracker.stop();
1724
1695
  localStorage.removeItem("nexus_sid");
1725
1696
  localStorage.removeItem("nexus_vid");
@@ -1763,10 +1734,10 @@ var AnalyticsEngine = class {
1763
1734
  url: typeof window !== "undefined" ? window.location.href : ""
1764
1735
  });
1765
1736
  }
1766
- // --- INTERNAL HELPER ---
1737
+ // --- INTERNAL ---
1767
1738
  async sendIdentityRequest(type, data) {
1768
1739
  try {
1769
- const config = this.tracker["config"];
1740
+ const config = this.tracker.config;
1770
1741
  const endpoint = `${config.analyticsUrl}/api/${type}`;
1771
1742
  const payload = {
1772
1743
  projectId: config.projectId,
@@ -1775,18 +1746,15 @@ var AnalyticsEngine = class {
1775
1746
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1776
1747
  ...data
1777
1748
  };
1778
- if (type === "group" && !payload.user_id) {
1779
- }
1780
1749
  const response = await fetch(endpoint, {
1781
1750
  method: "POST",
1782
1751
  headers: {
1783
1752
  "Content-Type": "application/json",
1784
- Authorization: `Bearer ${this.tracker["config"].apiKey}`
1753
+ Authorization: `Bearer ${config.apiKey}`
1785
1754
  },
1786
1755
  body: JSON.stringify(payload)
1787
1756
  });
1788
1757
  if (response.ok) {
1789
- this.tracker.send(type, data);
1790
1758
  return true;
1791
1759
  }
1792
1760
  return false;
@@ -1795,7 +1763,6 @@ var AnalyticsEngine = class {
1795
1763
  return false;
1796
1764
  }
1797
1765
  }
1798
- // --- PRIVATE EVENT LISTENERS (Keep existing setupClickTracking, setupFormTracking, setupRouteTracking) ---
1799
1766
  setupClickTracking() {
1800
1767
  if (typeof window === "undefined") return;
1801
1768
  const clickHandler = (e) => {
@@ -1805,7 +1772,7 @@ var AnalyticsEngine = class {
1805
1772
  this.tracker.send("click", {
1806
1773
  element_type: "link",
1807
1774
  href: link.href,
1808
- text: _optionalChain([link, 'access', _31 => _31.innerText, 'optionalAccess', _32 => _32.substring, 'call', _33 => _33(0, 50)]),
1775
+ text: _optionalChain([link, 'access', _29 => _29.innerText, 'optionalAccess', _30 => _30.substring, 'call', _31 => _31(0, 50)]),
1809
1776
  id: link.id,
1810
1777
  classes: link.className,
1811
1778
  dataset: { ...link.dataset }
@@ -1815,7 +1782,7 @@ var AnalyticsEngine = class {
1815
1782
  if (button) {
1816
1783
  this.tracker.send("click", {
1817
1784
  element_type: "button",
1818
- text: _optionalChain([button, 'access', _34 => _34.innerText, 'optionalAccess', _35 => _35.substring, 'call', _36 => _36(0, 50)]),
1785
+ text: _optionalChain([button, 'access', _32 => _32.innerText, 'optionalAccess', _33 => _33.substring, 'call', _34 => _34(0, 50)]),
1819
1786
  id: button.id,
1820
1787
  classes: button.className,
1821
1788
  coordinates: { x: e.clientX, y: e.clientY }
@@ -1848,21 +1815,22 @@ var AnalyticsEngine = class {
1848
1815
  setupRouteTracking() {
1849
1816
  if (typeof window === "undefined" || typeof window.history === "undefined")
1850
1817
  return;
1851
- const originalPushState = history.pushState;
1852
- const originalReplaceState = history.replaceState;
1818
+ const originalPushState = history.pushState.bind(history);
1819
+ const originalReplaceState = history.replaceState.bind(history);
1853
1820
  history.pushState = (...args) => {
1854
- originalPushState.apply(history, args);
1821
+ originalPushState(...args);
1855
1822
  this.pageView();
1856
1823
  };
1857
1824
  history.replaceState = (...args) => {
1858
- originalReplaceState.apply(history, args);
1859
- this.pageView();
1825
+ originalReplaceState(...args);
1860
1826
  };
1861
1827
  const popStateHandler = () => this.pageView();
1862
1828
  window.addEventListener("popstate", popStateHandler);
1863
- this.cleanupFns.push(
1864
- () => window.removeEventListener("popstate", popStateHandler)
1865
- );
1829
+ this.cleanupFns.push(() => {
1830
+ history.pushState = originalPushState;
1831
+ history.replaceState = originalReplaceState;
1832
+ window.removeEventListener("popstate", popStateHandler);
1833
+ });
1866
1834
  }
1867
1835
  getSessionId() {
1868
1836
  return this.tracker.getSession();
@@ -1881,44 +1849,60 @@ var AnalyticsEngine = class {
1881
1849
 
1882
1850
  // src/client.ts
1883
1851
  var NexusClient = class {
1884
- // Make this public/accessible
1885
1852
  constructor(config) {
1886
1853
  const fullConfig = getFullConfig(config);
1887
1854
  this.config = {
1888
- debug: _optionalChain([config, 'optionalAccess', _37 => _37.debug]) || false,
1889
- cacheStrategy: _optionalChain([config, 'optionalAccess', _38 => _38.cacheStrategy]) || "memory",
1890
- revalidateTime: _optionalChain([config, 'optionalAccess', _39 => _39.revalidateTime]) || 60,
1891
- timeout: _optionalChain([config, 'optionalAccess', _40 => _40.timeout]) || 1e4,
1892
- retries: _optionalChain([config, 'optionalAccess', _41 => _41.retries]) || 3,
1855
+ debug: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _35 => _35.debug]), () => ( false)),
1856
+ cacheStrategy: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _36 => _36.cacheStrategy]), () => ( "memory")),
1857
+ revalidateTime: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _37 => _37.revalidateTime]), () => ( 60)),
1858
+ timeout: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _38 => _38.timeout]), () => ( 1e4)),
1859
+ retries: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _39 => _39.retries]), () => ( 3)),
1893
1860
  ...fullConfig
1894
1861
  };
1895
- this.content = new ContentEngine(this.config);
1896
- if (typeof window !== "undefined") {
1897
- this.analytics = new AnalyticsEngine(this.config);
1898
- this.analytics.start();
1899
- }
1900
1862
  const errors = validateConfig(this.config);
1901
1863
  if (errors.length > 0) {
1902
1864
  console.warn("\u26A0\uFE0F NexusHub: Configuration issues:", errors.join(", "));
1903
1865
  if (!this.config.projectId) {
1904
- console.warn("\u26A0\uFE0F NexusHub: No Project ID found. Content fetching will fail.");
1866
+ console.warn("\u26A0\uFE0F NexusHub: No Project ID found. Tracking will fail.");
1905
1867
  }
1906
1868
  }
1907
1869
  this.content = new ContentEngine(this.config);
1870
+ if (typeof window !== "undefined") {
1871
+ this.analytics = new AnalyticsEngine(this.config);
1872
+ this.analytics.start();
1873
+ }
1908
1874
  }
1909
- // Helper alias for cleaner code
1875
+ /**
1876
+ * Helper alias for cleaner content fetching.
1877
+ */
1910
1878
  getPage(slug, options) {
1911
1879
  return this.content.getPage(slug, options);
1912
1880
  }
1913
- // Get current config (readonly)
1881
+ /**
1882
+ * Returns a readonly snapshot of the current config.
1883
+ */
1914
1884
  getConfig() {
1915
1885
  return { ...this.config };
1916
1886
  }
1887
+ /**
1888
+ * Updates specific config fields at runtime.
1889
+ * Replaces the previous pattern of `(nexus as any).config.projectId = x`
1890
+ * which bypassed TypeScript and mutated internal state unsafely.
1891
+ */
1892
+ updateConfig(updates) {
1893
+ this.config = { ...this.config, ...updates };
1894
+ }
1917
1895
  };
1918
1896
  var nexus = new NexusClient();
1919
1897
  var createNexusClient = (config) => new NexusClient(config);
1920
1898
 
1921
1899
  // src/components/NexusProvider.tsx
1900
+
1901
+
1902
+
1903
+
1904
+
1905
+
1922
1906
  var _react = require('react'); var _react2 = _interopRequireDefault(_react);
1923
1907
  var _navigation = require('next/navigation');
1924
1908
 
@@ -2120,18 +2104,28 @@ async function parseError(res) {
2120
2104
  // src/components/NexusProvider.tsx
2121
2105
 
2122
2106
  var NexusContext = _react.createContext.call(void 0, nexus);
2107
+ function NexusAnalyticsTracker({
2108
+ disableAnalytics
2109
+ }) {
2110
+ const pathname = _navigation.usePathname.call(void 0, );
2111
+ const searchParams = _navigation.useSearchParams.call(void 0, );
2112
+ const searchParamsString = searchParams.toString();
2113
+ _react.useEffect.call(void 0, () => {
2114
+ if (nexus.analytics && !disableAnalytics) {
2115
+ nexus.analytics.pageView();
2116
+ }
2117
+ }, [pathname, searchParamsString, disableAnalytics]);
2118
+ return null;
2119
+ }
2123
2120
  var NexusProvider = ({
2124
2121
  children,
2125
2122
  projectId,
2126
2123
  disableAnalytics = false
2127
2124
  }) => {
2128
- const pathname = _navigation.usePathname.call(void 0, );
2129
- const searchParams = _navigation.useSearchParams.call(void 0, );
2130
2125
  const isInitialized = _react.useRef.call(void 0, false);
2131
2126
  const config = _react.useMemo.call(void 0, () => {
2132
- const currentConfig = nexus.getConfig();
2133
- if (projectId && currentConfig.projectId !== projectId) {
2134
- nexus.config.projectId = projectId;
2127
+ if (projectId && nexus.getConfig().projectId !== projectId) {
2128
+ nexus.updateConfig({ projectId });
2135
2129
  }
2136
2130
  return nexus.getConfig();
2137
2131
  }, [projectId]);
@@ -2154,12 +2148,17 @@ var NexusProvider = ({
2154
2148
  }
2155
2149
  };
2156
2150
  }, [disableAnalytics]);
2157
- _react.useEffect.call(void 0, () => {
2158
- if (nexus.analytics && !disableAnalytics) {
2159
- nexus.analytics.pageView();
2160
- }
2161
- }, [pathname, searchParams, disableAnalytics]);
2162
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, NexusContext.Provider, { value: nexus, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, AuthProvider, { config, children }) });
2151
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, NexusContext.Provider, { value: nexus, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, AuthProvider, { config, children: [
2152
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react.Suspense, { fallback: null, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, NexusAnalyticsTracker, { disableAnalytics }) }),
2153
+ children
2154
+ ] }) });
2155
+ };
2156
+ var useNexus = () => {
2157
+ const context = _react2.default.useContext(NexusContext);
2158
+ if (!context) {
2159
+ throw new Error("useNexus must be used within a NexusProvider");
2160
+ }
2161
+ return context;
2163
2162
  };
2164
2163
 
2165
2164
  // src/index.ts
@@ -2186,4 +2185,5 @@ var VERSION = "0.0.1";
2186
2185
 
2187
2186
 
2188
2187
 
2189
- exports.AnalyticsEngine = AnalyticsEngine; exports.AuthProvider = AuthProvider; exports.BrowserCache = BrowserCache; exports.CacheTags = CacheTags; exports.ContentEngine = ContentEngine; exports.DEFAULT_ANALYTICS_URL = DEFAULT_ANALYTICS_URL; exports.DEFAULT_API_URL = DEFAULT_API_URL; exports.LOCAL_NEST_URL = LOCAL_NEST_URL; exports.LOCAL_RUST_URL = LOCAL_RUST_URL; exports.LocalCache = LocalCacheProxy; exports.MemoryCache = MemoryCache; exports.NexusClient = NexusClient; exports.NexusProvider = NexusProvider; exports.VERSION = VERSION; exports.createNexusClient = createNexusClient; exports.getEnvConfig = getEnvConfig; exports.getFullConfig = getFullConfig; exports.mergeConfigs = mergeConfigs; exports.nexus = nexus; exports.useNexusAuth = useNexusAuth; exports.validateConfig = validateConfig;
2188
+
2189
+ exports.AnalyticsEngine = AnalyticsEngine; exports.AuthProvider = AuthProvider; exports.BrowserCache = BrowserCache; exports.CacheTags = CacheTags; exports.ContentEngine = ContentEngine; exports.DEFAULT_ANALYTICS_URL = DEFAULT_ANALYTICS_URL; exports.DEFAULT_API_URL = DEFAULT_API_URL; exports.LOCAL_NEST_URL = LOCAL_NEST_URL; exports.LOCAL_RUST_URL = LOCAL_RUST_URL; exports.LocalCache = LocalCacheProxy; exports.MemoryCache = MemoryCache; exports.NexusClient = NexusClient; exports.NexusProvider = NexusProvider; exports.VERSION = VERSION; exports.createNexusClient = createNexusClient; exports.getEnvConfig = getEnvConfig; exports.getFullConfig = getFullConfig; exports.mergeConfigs = mergeConfigs; exports.nexus = nexus; exports.useNexus = useNexus; exports.useNexusAuth = useNexusAuth; exports.validateConfig = validateConfig;