@pageviews/tracker 0.2.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,6 +37,8 @@ track('signup', { props: { plan: 'pro' } })
37
37
  identify({ userId: 'user-123' })
38
38
  ```
39
39
 
40
+ `track()` and `identify()` are safe to call before `init()` has finished, or even before it has been called: up to 100 calls are queued and sent, in order, as soon as tracking starts. Awaiting `init()` is optional.
41
+
40
42
  Your site ID is available in the Pageviews dashboard under **Settings → Tracking**.
41
43
 
42
44
  ## Framework Examples
@@ -36,7 +36,14 @@ function sendBeaconEvent(endpoint, payload, siteId) {
36
36
 
37
37
  // src/core/utils.ts
38
38
  const ALLOWED_QUERY_PARAMS = new Set([
39
+ // Attribution
39
40
  'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'ref',
41
+ // Pagination
42
+ 'page', 'p', 'offset', 'limit', 'per_page',
43
+ // Sorting & filtering
44
+ 'sort', 'order', 'orderby', 'order_by', 'filter', 'category', 'cat', 'tag', 'type',
45
+ // Content identifiers
46
+ 'id', 'v', 'tab', 'view', 'lang', 'locale', 'hl',
40
47
  ]);
41
48
  function sanitizeUrl(url) {
42
49
  try {
@@ -469,6 +476,23 @@ const CACHE_KEY = 'pageviews_config';
469
476
  const CACHE_TTL = 1000 * 60 * 60; // 1 hour
470
477
  let instance = null;
471
478
  let initialized = false;
479
+ /** Bumped by destroy(), so a config fetch that resolves afterwards does not start tracking. */
480
+ let generation = 0;
481
+ const MAX_QUEUED_CALLS = 100;
482
+ /** track()/identify() calls made before init() has finished, replayed once tracking starts. */
483
+ let queued = [];
484
+ function enqueue(call) {
485
+ if (queued.length < MAX_QUEUED_CALLS)
486
+ queued.push(call);
487
+ }
488
+ function replayQueued() {
489
+ if (!instance)
490
+ return;
491
+ const calls = queued;
492
+ queued = [];
493
+ for (const call of calls)
494
+ call(instance.tracker);
495
+ }
472
496
  function getCachedConfig(siteId) {
473
497
  try {
474
498
  const raw = localStorage.getItem(`${CACHE_KEY}_${siteId}`);
@@ -499,6 +523,7 @@ async function fetchConfig(endpoint, siteId) {
499
523
  }
500
524
  function startTracking(config) {
501
525
  instance = initTracking(config);
526
+ replayQueued();
502
527
  }
503
528
  async function init(options) {
504
529
  if (initialized)
@@ -506,6 +531,7 @@ async function init(options) {
506
531
  initialized = true;
507
532
  const ingestEndpoint = (options.endpoint || 'https://data.pageviews.ai') + '/api/event';
508
533
  const cfgEndpoint = options.configEndpoint || 'https://pageviews.ai';
534
+ const current = generation;
509
535
  const cached = getCachedConfig(options.siteId);
510
536
  if (cached) {
511
537
  startTracking(Object.assign(Object.assign({}, cached), { siteId: options.siteId, endpoint: ingestEndpoint }));
@@ -515,26 +541,46 @@ async function init(options) {
515
541
  }).catch(() => { });
516
542
  return;
517
543
  }
518
- const configData = await fetchConfig(cfgEndpoint, options.siteId);
544
+ let configData;
545
+ try {
546
+ configData = await fetchConfig(cfgEndpoint, options.siteId);
547
+ }
548
+ catch (error) {
549
+ if (current === generation) {
550
+ initialized = false;
551
+ queued = [];
552
+ }
553
+ throw error;
554
+ }
555
+ if (current !== generation)
556
+ return;
519
557
  setCachedConfig(options.siteId, configData);
520
558
  startTracking(Object.assign(Object.assign({}, configData), { siteId: options.siteId, endpoint: ingestEndpoint }));
521
559
  }
560
+ /** Sends a custom event. Calls made before init() has finished are queued and sent once it has. */
522
561
  function track(eventName, options) {
523
- if (!instance)
524
- throw new Error('pageviews.track() requires init() to be called first');
562
+ if (!instance) {
563
+ enqueue((tracker) => tracker.track(eventName, options));
564
+ return;
565
+ }
525
566
  instance.tracker.track(eventName, options);
526
567
  }
568
+ /** Identifies the visitor. Calls made before init() has finished are queued and sent once it has. */
527
569
  function identify(data) {
528
- if (!instance)
529
- throw new Error('pageviews.identify() requires init() to be called first');
570
+ if (!instance) {
571
+ enqueue((tracker) => tracker.identify(data));
572
+ return;
573
+ }
530
574
  instance.tracker.identify(data);
531
575
  }
532
576
  function destroy() {
577
+ generation++;
578
+ queued = [];
579
+ initialized = false;
533
580
  if (!instance)
534
581
  return;
535
582
  instance.destroy();
536
583
  instance = null;
537
- initialized = false;
538
584
  }
539
585
 
540
586
  exports.destroy = destroy;
@@ -38,7 +38,9 @@ interface InitOptions {
38
38
  configEndpoint?: string;
39
39
  }
40
40
  declare function init(options: InitOptions): Promise<void>;
41
+ /** Sends a custom event. Calls made before init() has finished are queued and sent once it has. */
41
42
  declare function track(eventName: string, options?: TrackOptions): void;
43
+ /** Identifies the visitor. Calls made before init() has finished are queued and sent once it has. */
42
44
  declare function identify(data: IdentifyData): void;
43
45
  declare function destroy(): void;
44
46
 
@@ -34,7 +34,14 @@ function sendBeaconEvent(endpoint, payload, siteId) {
34
34
 
35
35
  // src/core/utils.ts
36
36
  const ALLOWED_QUERY_PARAMS = new Set([
37
+ // Attribution
37
38
  'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'ref',
39
+ // Pagination
40
+ 'page', 'p', 'offset', 'limit', 'per_page',
41
+ // Sorting & filtering
42
+ 'sort', 'order', 'orderby', 'order_by', 'filter', 'category', 'cat', 'tag', 'type',
43
+ // Content identifiers
44
+ 'id', 'v', 'tab', 'view', 'lang', 'locale', 'hl',
38
45
  ]);
39
46
  function sanitizeUrl(url) {
40
47
  try {
@@ -467,6 +474,23 @@ const CACHE_KEY = 'pageviews_config';
467
474
  const CACHE_TTL = 1000 * 60 * 60; // 1 hour
468
475
  let instance = null;
469
476
  let initialized = false;
477
+ /** Bumped by destroy(), so a config fetch that resolves afterwards does not start tracking. */
478
+ let generation = 0;
479
+ const MAX_QUEUED_CALLS = 100;
480
+ /** track()/identify() calls made before init() has finished, replayed once tracking starts. */
481
+ let queued = [];
482
+ function enqueue(call) {
483
+ if (queued.length < MAX_QUEUED_CALLS)
484
+ queued.push(call);
485
+ }
486
+ function replayQueued() {
487
+ if (!instance)
488
+ return;
489
+ const calls = queued;
490
+ queued = [];
491
+ for (const call of calls)
492
+ call(instance.tracker);
493
+ }
470
494
  function getCachedConfig(siteId) {
471
495
  try {
472
496
  const raw = localStorage.getItem(`${CACHE_KEY}_${siteId}`);
@@ -497,6 +521,7 @@ async function fetchConfig(endpoint, siteId) {
497
521
  }
498
522
  function startTracking(config) {
499
523
  instance = initTracking(config);
524
+ replayQueued();
500
525
  }
501
526
  async function init(options) {
502
527
  if (initialized)
@@ -504,6 +529,7 @@ async function init(options) {
504
529
  initialized = true;
505
530
  const ingestEndpoint = (options.endpoint || 'https://data.pageviews.ai') + '/api/event';
506
531
  const cfgEndpoint = options.configEndpoint || 'https://pageviews.ai';
532
+ const current = generation;
507
533
  const cached = getCachedConfig(options.siteId);
508
534
  if (cached) {
509
535
  startTracking(Object.assign(Object.assign({}, cached), { siteId: options.siteId, endpoint: ingestEndpoint }));
@@ -513,26 +539,46 @@ async function init(options) {
513
539
  }).catch(() => { });
514
540
  return;
515
541
  }
516
- const configData = await fetchConfig(cfgEndpoint, options.siteId);
542
+ let configData;
543
+ try {
544
+ configData = await fetchConfig(cfgEndpoint, options.siteId);
545
+ }
546
+ catch (error) {
547
+ if (current === generation) {
548
+ initialized = false;
549
+ queued = [];
550
+ }
551
+ throw error;
552
+ }
553
+ if (current !== generation)
554
+ return;
517
555
  setCachedConfig(options.siteId, configData);
518
556
  startTracking(Object.assign(Object.assign({}, configData), { siteId: options.siteId, endpoint: ingestEndpoint }));
519
557
  }
558
+ /** Sends a custom event. Calls made before init() has finished are queued and sent once it has. */
520
559
  function track(eventName, options) {
521
- if (!instance)
522
- throw new Error('pageviews.track() requires init() to be called first');
560
+ if (!instance) {
561
+ enqueue((tracker) => tracker.track(eventName, options));
562
+ return;
563
+ }
523
564
  instance.tracker.track(eventName, options);
524
565
  }
566
+ /** Identifies the visitor. Calls made before init() has finished are queued and sent once it has. */
525
567
  function identify(data) {
526
- if (!instance)
527
- throw new Error('pageviews.identify() requires init() to be called first');
568
+ if (!instance) {
569
+ enqueue((tracker) => tracker.identify(data));
570
+ return;
571
+ }
528
572
  instance.tracker.identify(data);
529
573
  }
530
574
  function destroy() {
575
+ generation++;
576
+ queued = [];
577
+ initialized = false;
531
578
  if (!instance)
532
579
  return;
533
580
  instance.destroy();
534
581
  instance = null;
535
- initialized = false;
536
582
  }
537
583
 
538
584
  export { destroy, identify, init, track };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pageviews/tracker",
3
- "version": "0.2.4",
3
+ "version": "0.4.1",
4
4
  "description": "Lightweight, cookie-free analytics tracker for PageViews.ai",
5
5
  "author": "Pageviews <hello@pageviews.ai>",
6
6
  "homepage": "https://pageviews.ai",