@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 +143 -143
- package/dist/index.d.cts +29 -22
- package/dist/index.d.ts +29 -22
- package/dist/index.js +133 -133
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -349,18 +349,21 @@ var RateLimiter = class {
|
|
|
349
349
|
this.config = config;
|
|
350
350
|
}
|
|
351
351
|
async checkLimit() {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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 =
|
|
361
|
+
const waitTime = oldestRequest + this.config.timeWindow - now;
|
|
358
362
|
if (waitTime > 0) {
|
|
359
363
|
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
360
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1505
|
+
const lastActivity = localStorage.getItem("nexus_last_active");
|
|
1514
1506
|
const now = Date.now();
|
|
1515
1507
|
const SESSION_TIMEOUT = 30 * 60 * 1e3;
|
|
1516
|
-
|
|
1517
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1672
|
-
|
|
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',
|
|
1677
|
-
title: _optionalChain([data, 'optionalAccess',
|
|
1669
|
+
url: _optionalChain([data, 'optionalAccess', _27 => _27.url]) || window.location.href,
|
|
1670
|
+
title: _optionalChain([data, 'optionalAccess', _28 => _28.title])
|
|
1678
1671
|
});
|
|
1679
|
-
return originalShare
|
|
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
|
|
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
|
|
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
|
|
1737
|
+
// --- INTERNAL ---
|
|
1767
1738
|
async sendIdentityRequest(type, data) {
|
|
1768
1739
|
try {
|
|
1769
|
-
const config = this.tracker
|
|
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 ${
|
|
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',
|
|
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',
|
|
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
|
|
1821
|
+
originalPushState(...args);
|
|
1855
1822
|
this.pageView();
|
|
1856
1823
|
};
|
|
1857
1824
|
history.replaceState = (...args) => {
|
|
1858
|
-
originalReplaceState
|
|
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
|
-
|
|
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',
|
|
1889
|
-
cacheStrategy: _optionalChain([config, 'optionalAccess',
|
|
1890
|
-
revalidateTime: _optionalChain([config, 'optionalAccess',
|
|
1891
|
-
timeout: _optionalChain([config, 'optionalAccess',
|
|
1892
|
-
retries: _optionalChain([config, 'optionalAccess',
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2133
|
-
|
|
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
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
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
|
-
|
|
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;
|