saasco-sdk 0.1.25 → 0.1.27
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/index.cjs.js +153 -20
- package/index.esm.js +152 -21
- package/package.json +3 -2
- package/src/index.d.ts +1 -0
- package/src/lib/getBrowserContext.d.ts +1 -1
- package/src/lib/getUTMContext.d.ts +2 -0
- package/src/lib/tracking/types.d.ts +20 -0
package/index.cjs.js
CHANGED
|
@@ -4,9 +4,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var tslib = require('tslib');
|
|
6
6
|
var uuid = require('@lukeed/uuid');
|
|
7
|
+
var psl = require('psl');
|
|
7
8
|
var zod = require('zod');
|
|
8
9
|
|
|
9
|
-
var version = "0.1.
|
|
10
|
+
var version = "0.1.27";
|
|
10
11
|
|
|
11
12
|
const timezones = {
|
|
12
13
|
'Asia/Barnaul': 'RU',
|
|
@@ -439,7 +440,7 @@ const timezones = {
|
|
|
439
440
|
function getBrowserContext() {
|
|
440
441
|
var _a;
|
|
441
442
|
const isBrowser = typeof window !== 'undefined';
|
|
442
|
-
if (!isBrowser)
|
|
443
|
+
if (!isBrowser) throw new Error('getBrowserContext can only be called in browser');
|
|
443
444
|
const customNavigator = navigator;
|
|
444
445
|
const $locale = (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages) && (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages.length) ? customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages[0] : (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.userLanguage) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.language) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.browserLanguage) || 'en';
|
|
445
446
|
// https://caniuse.com/?search=Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
@@ -468,16 +469,108 @@ function getBrowserContext() {
|
|
|
468
469
|
};
|
|
469
470
|
}
|
|
470
471
|
|
|
472
|
+
function getUTMContext() {
|
|
473
|
+
const isBrowser = typeof window !== 'undefined';
|
|
474
|
+
if (!isBrowser) throw new Error('getUTMContext can only be called in browser');
|
|
475
|
+
const params = new URLSearchParams(window.location.search);
|
|
476
|
+
const $utmSource = params.get('utm_source');
|
|
477
|
+
const $utmMedium = params.get('utm_medium');
|
|
478
|
+
const $utmCampaign = params.get('utm_campaign');
|
|
479
|
+
const $utmTerm = params.get('utm_term');
|
|
480
|
+
const $utmContent = params.get('utm_content');
|
|
481
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, $utmSource ? {
|
|
482
|
+
$utmSource
|
|
483
|
+
} : {}), $utmMedium ? {
|
|
484
|
+
$utmMedium
|
|
485
|
+
} : {}), $utmCampaign ? {
|
|
486
|
+
$utmCampaign
|
|
487
|
+
} : {}), $utmTerm ? {
|
|
488
|
+
$utmTerm
|
|
489
|
+
} : {}), $utmContent ? {
|
|
490
|
+
$utmContent
|
|
491
|
+
} : {});
|
|
492
|
+
}
|
|
493
|
+
|
|
471
494
|
const isBrowser = typeof window !== 'undefined';
|
|
472
495
|
const isServer = !isBrowser;
|
|
473
496
|
const PREF = 'saasco-sdk';
|
|
497
|
+
const SESSION_DURATION = 1000 * 60 * 30;
|
|
498
|
+
const USER_DURATION = 1000 * 60 * 60 * 24 * 365;
|
|
474
499
|
const data = {};
|
|
500
|
+
function getRootDomain() {
|
|
501
|
+
if (isServer) return;
|
|
502
|
+
const hostname = window.location.hostname;
|
|
503
|
+
if (!psl.isValid(hostname)) {
|
|
504
|
+
// If not a valid domain, don't set domain (for localhost, IP addresses, etc.)
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
const parsed = psl.parse(hostname);
|
|
508
|
+
if ('domain' in parsed && parsed.domain) {
|
|
509
|
+
return parsed.domain;
|
|
510
|
+
}
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
function setCookie(name, value, ttl) {
|
|
514
|
+
if (isServer) return;
|
|
515
|
+
const expires = new Date(Date.now() + ttl).toUTCString();
|
|
516
|
+
const domain = getRootDomain();
|
|
517
|
+
const cookieString = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${domain ? `; domain=.${domain}` : ''}`;
|
|
518
|
+
document.cookie = cookieString;
|
|
519
|
+
}
|
|
520
|
+
function getCookie(name) {
|
|
521
|
+
if (isServer) return null;
|
|
522
|
+
const nameEQ = name + '=';
|
|
523
|
+
const ca = document.cookie.split(';');
|
|
524
|
+
for (let i = 0; i < ca.length; i++) {
|
|
525
|
+
let c = ca[i];
|
|
526
|
+
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
|
527
|
+
if (c.indexOf(nameEQ) === 0) {
|
|
528
|
+
return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
function deleteCookie(name) {
|
|
534
|
+
if (isServer) return;
|
|
535
|
+
const domain = getRootDomain();
|
|
536
|
+
const cookieString = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/${domain ? `; domain=.${domain}` : ''}`;
|
|
537
|
+
document.cookie = cookieString;
|
|
538
|
+
}
|
|
539
|
+
function migrateFromLocalStorage() {
|
|
540
|
+
if (isServer) return;
|
|
541
|
+
const keysToMigrate = [`${PREF}-session-id`, `${PREF}-anonymous-id`, `${PREF}-user-id`, `${PREF}-session-utm-context`];
|
|
542
|
+
keysToMigrate.forEach(key => {
|
|
543
|
+
try {
|
|
544
|
+
const value = localStorage.getItem(key);
|
|
545
|
+
if (value) {
|
|
546
|
+
// Parse the stored data to get the original TTL
|
|
547
|
+
const item = JSON.parse(value);
|
|
548
|
+
const now = new Date().getTime();
|
|
549
|
+
// Check if the data is still valid
|
|
550
|
+
if (item.expiry > now) {
|
|
551
|
+
// Calculate remaining TTL
|
|
552
|
+
const remainingTtl = item.expiry - now;
|
|
553
|
+
// Store in cookie with remaining TTL
|
|
554
|
+
setCookie(key, value, remainingTtl);
|
|
555
|
+
// Remove from localStorage after successful migration
|
|
556
|
+
localStorage.removeItem(key);
|
|
557
|
+
} else {
|
|
558
|
+
// Data has expired, just remove it
|
|
559
|
+
localStorage.removeItem(key);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
} catch (error) {
|
|
563
|
+
// If there's an error parsing the data, remove the corrupted item
|
|
564
|
+
localStorage.removeItem(key);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
475
568
|
function storeData(key, value, ttl) {
|
|
476
|
-
const item = {
|
|
477
|
-
value,
|
|
478
|
-
expiry: new Date().getTime() + ttl
|
|
479
|
-
};
|
|
480
569
|
if (isServer) {
|
|
570
|
+
const item = {
|
|
571
|
+
value,
|
|
572
|
+
expiry: new Date().getTime() + ttl
|
|
573
|
+
};
|
|
481
574
|
if (value === undefined) {
|
|
482
575
|
delete data[key];
|
|
483
576
|
return;
|
|
@@ -486,8 +579,11 @@ function storeData(key, value, ttl) {
|
|
|
486
579
|
return;
|
|
487
580
|
}
|
|
488
581
|
const fullKey = `${PREF}-${key}`;
|
|
489
|
-
if (value === undefined)
|
|
490
|
-
|
|
582
|
+
if (value === undefined) {
|
|
583
|
+
deleteCookie(fullKey);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
setCookie(fullKey, JSON.stringify(value), ttl);
|
|
491
587
|
}
|
|
492
588
|
function retrieveData(key) {
|
|
493
589
|
if (isServer) {
|
|
@@ -500,17 +596,17 @@ function retrieveData(key) {
|
|
|
500
596
|
return item.value;
|
|
501
597
|
}
|
|
502
598
|
const fullKey = `${PREF}-${key}`;
|
|
503
|
-
const itemStr =
|
|
599
|
+
const itemStr = getCookie(fullKey);
|
|
504
600
|
if (!itemStr) {
|
|
505
601
|
return null;
|
|
506
602
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
603
|
+
try {
|
|
604
|
+
return JSON.parse(itemStr);
|
|
605
|
+
} catch (error) {
|
|
606
|
+
// If JSON parsing fails, remove the corrupted cookie
|
|
607
|
+
deleteCookie(fullKey);
|
|
511
608
|
return null;
|
|
512
609
|
}
|
|
513
|
-
return item.value;
|
|
514
610
|
}
|
|
515
611
|
function getSessionId() {
|
|
516
612
|
return retrieveData(`session-id`);
|
|
@@ -521,7 +617,7 @@ function setSessionId({
|
|
|
521
617
|
reset: false
|
|
522
618
|
}) {
|
|
523
619
|
const sessionId = reset ? uuid.v4() : getSessionId() || uuid.v4();
|
|
524
|
-
storeData(`session-id`, sessionId,
|
|
620
|
+
storeData(`session-id`, sessionId, SESSION_DURATION);
|
|
525
621
|
}
|
|
526
622
|
function getAnonymousId() {
|
|
527
623
|
return retrieveData(`anonymous-id`);
|
|
@@ -532,14 +628,31 @@ function setAnonymousId({
|
|
|
532
628
|
reset: false
|
|
533
629
|
}) {
|
|
534
630
|
const anonymousId = reset ? uuid.v4() : getAnonymousId() || uuid.v4();
|
|
535
|
-
storeData(`anonymous-id`, anonymousId,
|
|
631
|
+
storeData(`anonymous-id`, anonymousId, USER_DURATION);
|
|
536
632
|
return anonymousId;
|
|
537
633
|
}
|
|
538
634
|
function getUserId() {
|
|
539
635
|
return retrieveData(`user-id`);
|
|
540
636
|
}
|
|
541
637
|
function setUserId(userId) {
|
|
542
|
-
storeData(`user-id`, userId,
|
|
638
|
+
storeData(`user-id`, userId, USER_DURATION);
|
|
639
|
+
}
|
|
640
|
+
function getSessionUTMContext() {
|
|
641
|
+
return retrieveData(`session-utm-context`);
|
|
642
|
+
}
|
|
643
|
+
function setSessionUTMContext(utmContext) {
|
|
644
|
+
storeData(`session-utm-context`, utmContext, SESSION_DURATION);
|
|
645
|
+
}
|
|
646
|
+
function handleSessionUTMContext() {
|
|
647
|
+
if (isServer) return null;
|
|
648
|
+
const utmContext = getUTMContext();
|
|
649
|
+
// If we have any utm data its current so we store it
|
|
650
|
+
const utmContextKeys = Object.keys(utmContext);
|
|
651
|
+
if (utmContextKeys.length > 0) {
|
|
652
|
+
setSessionUTMContext(utmContext);
|
|
653
|
+
}
|
|
654
|
+
// This will return the current data, or stored data if we have any
|
|
655
|
+
return utmContext || getSessionUTMContext() || null;
|
|
543
656
|
}
|
|
544
657
|
class Saasco {
|
|
545
658
|
/**
|
|
@@ -579,6 +692,8 @@ class Saasco {
|
|
|
579
692
|
if (isBrowser) {
|
|
580
693
|
window.saasco = this;
|
|
581
694
|
}
|
|
695
|
+
// Migrate existing localStorage data to cookies for cross-subdomain support
|
|
696
|
+
migrateFromLocalStorage();
|
|
582
697
|
this.log('Saasco initialized', this.config);
|
|
583
698
|
if (this.config.debug) this.log('Debug mode active. This will log all events to the console.');
|
|
584
699
|
if (!this.config.enabled) this.log('Analytics is disabled. No requests will be sent to the server.');
|
|
@@ -618,8 +733,15 @@ class Saasco {
|
|
|
618
733
|
const sessionId = (hasAction ? undefined : actionOrPayload.sessionId) || getSessionId();
|
|
619
734
|
const anonymousId = (hasAction ? undefined : actionOrPayload.anonymousId) || getAnonymousId();
|
|
620
735
|
const distinctId = (hasAction ? undefined : actionOrPayload.userId) || getUserId();
|
|
621
|
-
// Only gets browser context if in the browser
|
|
622
|
-
|
|
736
|
+
// Only gets browser and utm context if in the browser
|
|
737
|
+
let browserContext;
|
|
738
|
+
let utmContext;
|
|
739
|
+
if (isBrowser) {
|
|
740
|
+
browserContext = getBrowserContext();
|
|
741
|
+
// handle session utm context and set it if it exists
|
|
742
|
+
const sessionUTMContext = handleSessionUTMContext();
|
|
743
|
+
if (sessionUTMContext) utmContext = sessionUTMContext;
|
|
744
|
+
}
|
|
623
745
|
const data = {
|
|
624
746
|
id: uuid.v4(),
|
|
625
747
|
timestamp: new Date().toISOString(),
|
|
@@ -629,7 +751,7 @@ class Saasco {
|
|
|
629
751
|
anonymousId,
|
|
630
752
|
distinctId,
|
|
631
753
|
projectId: this.config.projectId,
|
|
632
|
-
payload: JSON.stringify(Object.assign(Object.assign({}, browserContext), {
|
|
754
|
+
payload: JSON.stringify(Object.assign(Object.assign(Object.assign({}, browserContext), utmContext), {
|
|
633
755
|
properties: properties || {}
|
|
634
756
|
})),
|
|
635
757
|
source: isBrowser ? 'client' : 'server',
|
|
@@ -712,6 +834,8 @@ class Saasco {
|
|
|
712
834
|
*
|
|
713
835
|
*/
|
|
714
836
|
logout() {
|
|
837
|
+
// Clear UTM data on logout
|
|
838
|
+
setSessionUTMContext(null);
|
|
715
839
|
this.identify(null);
|
|
716
840
|
}
|
|
717
841
|
/**
|
|
@@ -837,8 +961,17 @@ const browserContextSchema = zod.z.object({
|
|
|
837
961
|
$title: zod.z.string(),
|
|
838
962
|
$userAgent: zod.z.string()
|
|
839
963
|
});
|
|
964
|
+
const utmContextSchema = zod.z.object({
|
|
965
|
+
$utmSource: zod.z.string().optional(),
|
|
966
|
+
$utmMedium: zod.z.string().optional(),
|
|
967
|
+
$utmCampaign: zod.z.string().optional(),
|
|
968
|
+
$utmTerm: zod.z.string().optional(),
|
|
969
|
+
$utmContent: zod.z.string().optional()
|
|
970
|
+
});
|
|
840
971
|
|
|
841
972
|
exports.Saasco = Saasco;
|
|
842
973
|
exports.browserContextSchema = browserContextSchema;
|
|
843
974
|
exports.getBrowserContext = getBrowserContext;
|
|
975
|
+
exports.getUTMContext = getUTMContext;
|
|
844
976
|
exports.timezones = timezones;
|
|
977
|
+
exports.utmContextSchema = utmContextSchema;
|
package/index.esm.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
2
|
import { v4 } from '@lukeed/uuid';
|
|
3
|
+
import { isValid, parse } from 'psl';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
|
|
5
|
-
var version = "0.1.
|
|
6
|
+
var version = "0.1.27";
|
|
6
7
|
|
|
7
8
|
const timezones = {
|
|
8
9
|
'Asia/Barnaul': 'RU',
|
|
@@ -435,7 +436,7 @@ const timezones = {
|
|
|
435
436
|
function getBrowserContext() {
|
|
436
437
|
var _a;
|
|
437
438
|
const isBrowser = typeof window !== 'undefined';
|
|
438
|
-
if (!isBrowser)
|
|
439
|
+
if (!isBrowser) throw new Error('getBrowserContext can only be called in browser');
|
|
439
440
|
const customNavigator = navigator;
|
|
440
441
|
const $locale = (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages) && (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages.length) ? customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages[0] : (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.userLanguage) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.language) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.browserLanguage) || 'en';
|
|
441
442
|
// https://caniuse.com/?search=Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
@@ -464,16 +465,108 @@ function getBrowserContext() {
|
|
|
464
465
|
};
|
|
465
466
|
}
|
|
466
467
|
|
|
468
|
+
function getUTMContext() {
|
|
469
|
+
const isBrowser = typeof window !== 'undefined';
|
|
470
|
+
if (!isBrowser) throw new Error('getUTMContext can only be called in browser');
|
|
471
|
+
const params = new URLSearchParams(window.location.search);
|
|
472
|
+
const $utmSource = params.get('utm_source');
|
|
473
|
+
const $utmMedium = params.get('utm_medium');
|
|
474
|
+
const $utmCampaign = params.get('utm_campaign');
|
|
475
|
+
const $utmTerm = params.get('utm_term');
|
|
476
|
+
const $utmContent = params.get('utm_content');
|
|
477
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, $utmSource ? {
|
|
478
|
+
$utmSource
|
|
479
|
+
} : {}), $utmMedium ? {
|
|
480
|
+
$utmMedium
|
|
481
|
+
} : {}), $utmCampaign ? {
|
|
482
|
+
$utmCampaign
|
|
483
|
+
} : {}), $utmTerm ? {
|
|
484
|
+
$utmTerm
|
|
485
|
+
} : {}), $utmContent ? {
|
|
486
|
+
$utmContent
|
|
487
|
+
} : {});
|
|
488
|
+
}
|
|
489
|
+
|
|
467
490
|
const isBrowser = typeof window !== 'undefined';
|
|
468
491
|
const isServer = !isBrowser;
|
|
469
492
|
const PREF = 'saasco-sdk';
|
|
493
|
+
const SESSION_DURATION = 1000 * 60 * 30;
|
|
494
|
+
const USER_DURATION = 1000 * 60 * 60 * 24 * 365;
|
|
470
495
|
const data = {};
|
|
496
|
+
function getRootDomain() {
|
|
497
|
+
if (isServer) return;
|
|
498
|
+
const hostname = window.location.hostname;
|
|
499
|
+
if (!isValid(hostname)) {
|
|
500
|
+
// If not a valid domain, don't set domain (for localhost, IP addresses, etc.)
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
const parsed = parse(hostname);
|
|
504
|
+
if ('domain' in parsed && parsed.domain) {
|
|
505
|
+
return parsed.domain;
|
|
506
|
+
}
|
|
507
|
+
return undefined;
|
|
508
|
+
}
|
|
509
|
+
function setCookie(name, value, ttl) {
|
|
510
|
+
if (isServer) return;
|
|
511
|
+
const expires = new Date(Date.now() + ttl).toUTCString();
|
|
512
|
+
const domain = getRootDomain();
|
|
513
|
+
const cookieString = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/${domain ? `; domain=.${domain}` : ''}`;
|
|
514
|
+
document.cookie = cookieString;
|
|
515
|
+
}
|
|
516
|
+
function getCookie(name) {
|
|
517
|
+
if (isServer) return null;
|
|
518
|
+
const nameEQ = name + '=';
|
|
519
|
+
const ca = document.cookie.split(';');
|
|
520
|
+
for (let i = 0; i < ca.length; i++) {
|
|
521
|
+
let c = ca[i];
|
|
522
|
+
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
|
523
|
+
if (c.indexOf(nameEQ) === 0) {
|
|
524
|
+
return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
function deleteCookie(name) {
|
|
530
|
+
if (isServer) return;
|
|
531
|
+
const domain = getRootDomain();
|
|
532
|
+
const cookieString = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/${domain ? `; domain=.${domain}` : ''}`;
|
|
533
|
+
document.cookie = cookieString;
|
|
534
|
+
}
|
|
535
|
+
function migrateFromLocalStorage() {
|
|
536
|
+
if (isServer) return;
|
|
537
|
+
const keysToMigrate = [`${PREF}-session-id`, `${PREF}-anonymous-id`, `${PREF}-user-id`, `${PREF}-session-utm-context`];
|
|
538
|
+
keysToMigrate.forEach(key => {
|
|
539
|
+
try {
|
|
540
|
+
const value = localStorage.getItem(key);
|
|
541
|
+
if (value) {
|
|
542
|
+
// Parse the stored data to get the original TTL
|
|
543
|
+
const item = JSON.parse(value);
|
|
544
|
+
const now = new Date().getTime();
|
|
545
|
+
// Check if the data is still valid
|
|
546
|
+
if (item.expiry > now) {
|
|
547
|
+
// Calculate remaining TTL
|
|
548
|
+
const remainingTtl = item.expiry - now;
|
|
549
|
+
// Store in cookie with remaining TTL
|
|
550
|
+
setCookie(key, value, remainingTtl);
|
|
551
|
+
// Remove from localStorage after successful migration
|
|
552
|
+
localStorage.removeItem(key);
|
|
553
|
+
} else {
|
|
554
|
+
// Data has expired, just remove it
|
|
555
|
+
localStorage.removeItem(key);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
} catch (error) {
|
|
559
|
+
// If there's an error parsing the data, remove the corrupted item
|
|
560
|
+
localStorage.removeItem(key);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
471
564
|
function storeData(key, value, ttl) {
|
|
472
|
-
const item = {
|
|
473
|
-
value,
|
|
474
|
-
expiry: new Date().getTime() + ttl
|
|
475
|
-
};
|
|
476
565
|
if (isServer) {
|
|
566
|
+
const item = {
|
|
567
|
+
value,
|
|
568
|
+
expiry: new Date().getTime() + ttl
|
|
569
|
+
};
|
|
477
570
|
if (value === undefined) {
|
|
478
571
|
delete data[key];
|
|
479
572
|
return;
|
|
@@ -482,8 +575,11 @@ function storeData(key, value, ttl) {
|
|
|
482
575
|
return;
|
|
483
576
|
}
|
|
484
577
|
const fullKey = `${PREF}-${key}`;
|
|
485
|
-
if (value === undefined)
|
|
486
|
-
|
|
578
|
+
if (value === undefined) {
|
|
579
|
+
deleteCookie(fullKey);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
setCookie(fullKey, JSON.stringify(value), ttl);
|
|
487
583
|
}
|
|
488
584
|
function retrieveData(key) {
|
|
489
585
|
if (isServer) {
|
|
@@ -496,17 +592,17 @@ function retrieveData(key) {
|
|
|
496
592
|
return item.value;
|
|
497
593
|
}
|
|
498
594
|
const fullKey = `${PREF}-${key}`;
|
|
499
|
-
const itemStr =
|
|
595
|
+
const itemStr = getCookie(fullKey);
|
|
500
596
|
if (!itemStr) {
|
|
501
597
|
return null;
|
|
502
598
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
599
|
+
try {
|
|
600
|
+
return JSON.parse(itemStr);
|
|
601
|
+
} catch (error) {
|
|
602
|
+
// If JSON parsing fails, remove the corrupted cookie
|
|
603
|
+
deleteCookie(fullKey);
|
|
507
604
|
return null;
|
|
508
605
|
}
|
|
509
|
-
return item.value;
|
|
510
606
|
}
|
|
511
607
|
function getSessionId() {
|
|
512
608
|
return retrieveData(`session-id`);
|
|
@@ -517,7 +613,7 @@ function setSessionId({
|
|
|
517
613
|
reset: false
|
|
518
614
|
}) {
|
|
519
615
|
const sessionId = reset ? v4() : getSessionId() || v4();
|
|
520
|
-
storeData(`session-id`, sessionId,
|
|
616
|
+
storeData(`session-id`, sessionId, SESSION_DURATION);
|
|
521
617
|
}
|
|
522
618
|
function getAnonymousId() {
|
|
523
619
|
return retrieveData(`anonymous-id`);
|
|
@@ -528,14 +624,31 @@ function setAnonymousId({
|
|
|
528
624
|
reset: false
|
|
529
625
|
}) {
|
|
530
626
|
const anonymousId = reset ? v4() : getAnonymousId() || v4();
|
|
531
|
-
storeData(`anonymous-id`, anonymousId,
|
|
627
|
+
storeData(`anonymous-id`, anonymousId, USER_DURATION);
|
|
532
628
|
return anonymousId;
|
|
533
629
|
}
|
|
534
630
|
function getUserId() {
|
|
535
631
|
return retrieveData(`user-id`);
|
|
536
632
|
}
|
|
537
633
|
function setUserId(userId) {
|
|
538
|
-
storeData(`user-id`, userId,
|
|
634
|
+
storeData(`user-id`, userId, USER_DURATION);
|
|
635
|
+
}
|
|
636
|
+
function getSessionUTMContext() {
|
|
637
|
+
return retrieveData(`session-utm-context`);
|
|
638
|
+
}
|
|
639
|
+
function setSessionUTMContext(utmContext) {
|
|
640
|
+
storeData(`session-utm-context`, utmContext, SESSION_DURATION);
|
|
641
|
+
}
|
|
642
|
+
function handleSessionUTMContext() {
|
|
643
|
+
if (isServer) return null;
|
|
644
|
+
const utmContext = getUTMContext();
|
|
645
|
+
// If we have any utm data its current so we store it
|
|
646
|
+
const utmContextKeys = Object.keys(utmContext);
|
|
647
|
+
if (utmContextKeys.length > 0) {
|
|
648
|
+
setSessionUTMContext(utmContext);
|
|
649
|
+
}
|
|
650
|
+
// This will return the current data, or stored data if we have any
|
|
651
|
+
return utmContext || getSessionUTMContext() || null;
|
|
539
652
|
}
|
|
540
653
|
class Saasco {
|
|
541
654
|
/**
|
|
@@ -575,6 +688,8 @@ class Saasco {
|
|
|
575
688
|
if (isBrowser) {
|
|
576
689
|
window.saasco = this;
|
|
577
690
|
}
|
|
691
|
+
// Migrate existing localStorage data to cookies for cross-subdomain support
|
|
692
|
+
migrateFromLocalStorage();
|
|
578
693
|
this.log('Saasco initialized', this.config);
|
|
579
694
|
if (this.config.debug) this.log('Debug mode active. This will log all events to the console.');
|
|
580
695
|
if (!this.config.enabled) this.log('Analytics is disabled. No requests will be sent to the server.');
|
|
@@ -614,8 +729,15 @@ class Saasco {
|
|
|
614
729
|
const sessionId = (hasAction ? undefined : actionOrPayload.sessionId) || getSessionId();
|
|
615
730
|
const anonymousId = (hasAction ? undefined : actionOrPayload.anonymousId) || getAnonymousId();
|
|
616
731
|
const distinctId = (hasAction ? undefined : actionOrPayload.userId) || getUserId();
|
|
617
|
-
// Only gets browser context if in the browser
|
|
618
|
-
|
|
732
|
+
// Only gets browser and utm context if in the browser
|
|
733
|
+
let browserContext;
|
|
734
|
+
let utmContext;
|
|
735
|
+
if (isBrowser) {
|
|
736
|
+
browserContext = getBrowserContext();
|
|
737
|
+
// handle session utm context and set it if it exists
|
|
738
|
+
const sessionUTMContext = handleSessionUTMContext();
|
|
739
|
+
if (sessionUTMContext) utmContext = sessionUTMContext;
|
|
740
|
+
}
|
|
619
741
|
const data = {
|
|
620
742
|
id: v4(),
|
|
621
743
|
timestamp: new Date().toISOString(),
|
|
@@ -625,7 +747,7 @@ class Saasco {
|
|
|
625
747
|
anonymousId,
|
|
626
748
|
distinctId,
|
|
627
749
|
projectId: this.config.projectId,
|
|
628
|
-
payload: JSON.stringify(Object.assign(Object.assign({}, browserContext), {
|
|
750
|
+
payload: JSON.stringify(Object.assign(Object.assign(Object.assign({}, browserContext), utmContext), {
|
|
629
751
|
properties: properties || {}
|
|
630
752
|
})),
|
|
631
753
|
source: isBrowser ? 'client' : 'server',
|
|
@@ -708,6 +830,8 @@ class Saasco {
|
|
|
708
830
|
*
|
|
709
831
|
*/
|
|
710
832
|
logout() {
|
|
833
|
+
// Clear UTM data on logout
|
|
834
|
+
setSessionUTMContext(null);
|
|
711
835
|
this.identify(null);
|
|
712
836
|
}
|
|
713
837
|
/**
|
|
@@ -833,5 +957,12 @@ const browserContextSchema = z.object({
|
|
|
833
957
|
$title: z.string(),
|
|
834
958
|
$userAgent: z.string()
|
|
835
959
|
});
|
|
960
|
+
const utmContextSchema = z.object({
|
|
961
|
+
$utmSource: z.string().optional(),
|
|
962
|
+
$utmMedium: z.string().optional(),
|
|
963
|
+
$utmCampaign: z.string().optional(),
|
|
964
|
+
$utmTerm: z.string().optional(),
|
|
965
|
+
$utmContent: z.string().optional()
|
|
966
|
+
});
|
|
836
967
|
|
|
837
|
-
export { Saasco, browserContextSchema, getBrowserContext, timezones };
|
|
968
|
+
export { Saasco, browserContextSchema, getBrowserContext, getUTMContext, timezones, utmContextSchema };
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saasco-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"tslib": "^2.3.0",
|
|
6
6
|
"@lukeed/uuid": "^2.0.1",
|
|
7
|
-
"zod": "^3.22.4"
|
|
7
|
+
"zod": "^3.22.4",
|
|
8
|
+
"psl": "^1.9.0"
|
|
8
9
|
},
|
|
9
10
|
"main": "./index.cjs.js",
|
|
10
11
|
"typings": "./src/index.d.ts",
|
package/src/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { BrowserContext } from './tracking';
|
|
2
|
-
export declare function getBrowserContext(): BrowserContext
|
|
2
|
+
export declare function getBrowserContext(): BrowserContext;
|
|
@@ -34,3 +34,23 @@ export declare const browserContextSchema: z.ZodObject<{
|
|
|
34
34
|
$userAgent: string;
|
|
35
35
|
}>;
|
|
36
36
|
export type BrowserContext = z.infer<typeof browserContextSchema>;
|
|
37
|
+
export declare const utmContextSchema: z.ZodObject<{
|
|
38
|
+
$utmSource: z.ZodOptional<z.ZodString>;
|
|
39
|
+
$utmMedium: z.ZodOptional<z.ZodString>;
|
|
40
|
+
$utmCampaign: z.ZodOptional<z.ZodString>;
|
|
41
|
+
$utmTerm: z.ZodOptional<z.ZodString>;
|
|
42
|
+
$utmContent: z.ZodOptional<z.ZodString>;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
$utmSource?: string | undefined;
|
|
45
|
+
$utmMedium?: string | undefined;
|
|
46
|
+
$utmCampaign?: string | undefined;
|
|
47
|
+
$utmTerm?: string | undefined;
|
|
48
|
+
$utmContent?: string | undefined;
|
|
49
|
+
}, {
|
|
50
|
+
$utmSource?: string | undefined;
|
|
51
|
+
$utmMedium?: string | undefined;
|
|
52
|
+
$utmCampaign?: string | undefined;
|
|
53
|
+
$utmTerm?: string | undefined;
|
|
54
|
+
$utmContent?: string | undefined;
|
|
55
|
+
}>;
|
|
56
|
+
export type UTMContext = z.infer<typeof utmContextSchema>;
|