saasco-sdk 0.1.43 → 0.1.45
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 +372 -115
- package/index.esm.js +371 -115
- package/package.json +1 -1
- package/src/lib/analytics.d.ts +17 -1
- package/src/lib/integrations/facebook-pixel.d.ts +1 -1
- package/src/lib/integrations/index.d.ts +1 -0
- package/src/lib/integrations/integration-manager.d.ts +5 -11
- package/src/lib/integrations/pinterest-tag.d.ts +37 -0
- package/src/lib/integrations/tiktok-pixel.d.ts +1 -1
- package/src/lib/utils/getIntegrationLoggerLevel.d.ts +8 -0
- package/src/lib/utils/index.d.ts +1 -0
- package/src/lib/utils/logger.d.ts +9 -2
package/index.esm.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
2
|
import { isValid, parse } from 'psl';
|
|
3
|
-
import { sha256 } from 'js-sha256';
|
|
4
3
|
import { v4 } from '@lukeed/uuid';
|
|
4
|
+
import { sha256 } from 'js-sha256';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
|
-
var version = "0.1.
|
|
7
|
+
var version = "0.1.45";
|
|
8
8
|
|
|
9
9
|
const timezones = {
|
|
10
10
|
'Asia/Barnaul': 'RU',
|
|
@@ -551,26 +551,117 @@ function getBrowserContext() {
|
|
|
551
551
|
};
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
-
|
|
555
|
-
|
|
554
|
+
var LogLevel;
|
|
555
|
+
(function (LogLevel) {
|
|
556
|
+
LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
|
|
557
|
+
LogLevel[LogLevel["WARN"] = 1] = "WARN";
|
|
558
|
+
LogLevel[LogLevel["INFO"] = 2] = "INFO";
|
|
559
|
+
LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
|
|
560
|
+
})(LogLevel || (LogLevel = {}));
|
|
561
|
+
class AnalyticsLogger {
|
|
562
|
+
constructor(config) {
|
|
563
|
+
this.config = config;
|
|
564
|
+
}
|
|
565
|
+
debug(...args) {
|
|
566
|
+
if (this.config.level < LogLevel.DEBUG) return;
|
|
567
|
+
const icon = '◍';
|
|
568
|
+
const message = `${icon} ${this.config.label}`;
|
|
569
|
+
console.debug(`%c ${message}`, `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Log debug information
|
|
573
|
+
*/
|
|
574
|
+
info(...args) {
|
|
575
|
+
if (this.config.level < LogLevel.INFO) return;
|
|
576
|
+
const icon = '◍';
|
|
577
|
+
const message = `${icon} ${this.config.label}`;
|
|
578
|
+
console.info(`%c ${message}`, `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Log warning information
|
|
582
|
+
*/
|
|
583
|
+
warn(...args) {
|
|
584
|
+
if (this.config.level < LogLevel.WARN) return;
|
|
585
|
+
const icon = '◍';
|
|
586
|
+
const message = `${icon} ${this.config.label}`;
|
|
587
|
+
console.warn(`%c ${message}`, `background: #ffa500; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Log error information
|
|
591
|
+
*/
|
|
592
|
+
error(...args) {
|
|
593
|
+
const icon = '◍';
|
|
594
|
+
const message = `${icon} ${this.config.label} Error`;
|
|
595
|
+
console.error(`%c ${message}`, 'background: red; color: white; padding-right: 6px; border-radius: 2px;', ...args);
|
|
596
|
+
return {
|
|
597
|
+
success: false,
|
|
598
|
+
message: args.join(' ')
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const isBrowser$4 = typeof window !== 'undefined';
|
|
604
|
+
const isServer$4 = !isBrowser$4;
|
|
605
|
+
/**
|
|
606
|
+
* Get the logger level for a specific integration
|
|
607
|
+
* Supports URL parameters like saasco-debug-{integration-type}=true
|
|
608
|
+
* If debug is true or URL param is true, sets level to DEBUG (3), otherwise WARN
|
|
609
|
+
*/
|
|
610
|
+
function getIntegrationLoggerLevel(integrationName, debug) {
|
|
611
|
+
// Check URL parameters for debug overrides (browser only)
|
|
612
|
+
let urlDebug = false;
|
|
613
|
+
if (!isServer$4) {
|
|
614
|
+
try {
|
|
615
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
616
|
+
const paramName = `saasco-debug-${integrationName}`;
|
|
617
|
+
const paramValue = urlParams.get(paramName);
|
|
618
|
+
if (paramValue === 'true') {
|
|
619
|
+
urlDebug = true;
|
|
620
|
+
}
|
|
621
|
+
} catch (error) {
|
|
622
|
+
// Ignore URL parsing errors
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
// URL parameters override config, config overrides default (WARN)
|
|
626
|
+
return urlDebug || debug ? LogLevel.DEBUG : LogLevel.WARN;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Generate a collision-resistant UUID
|
|
631
|
+
* Uses lukeed's UUID v4 implementation for consistent, fast UUID generation
|
|
632
|
+
*/
|
|
633
|
+
function uuid() {
|
|
634
|
+
return v4();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const isBrowser$3 = typeof window !== 'undefined';
|
|
638
|
+
const isServer$3 = !isBrowser$3;
|
|
556
639
|
const standardFacebookEvents = ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'CompleteRegistration', 'Contact', 'CustomizeProduct', 'Donate', 'FindLocation', 'InitiateCheckout', 'Lead', 'Purchase', 'Schedule', 'Search', 'StartTrial', 'SubmitApplication', 'Subscribe', 'ViewContent', 'PageView'];
|
|
557
640
|
/**
|
|
558
641
|
* Create a Facebook Pixel integration instance
|
|
559
642
|
*/
|
|
560
|
-
function createFacebookPixelIntegration(config) {
|
|
643
|
+
function createFacebookPixelIntegration(config, debug) {
|
|
561
644
|
let isPixelReady = false;
|
|
645
|
+
// Create integration logger
|
|
646
|
+
const integrationLoggerLevel = getIntegrationLoggerLevel('facebook-pixel', debug);
|
|
647
|
+
const logger = new AnalyticsLogger({
|
|
648
|
+
level: integrationLoggerLevel,
|
|
649
|
+
label: 'Saasco facebook-pixel'
|
|
650
|
+
});
|
|
562
651
|
return {
|
|
563
652
|
name: 'facebook-pixel',
|
|
564
653
|
environments: ['client'],
|
|
565
654
|
init: () => __awaiter(this, void 0, void 0, function* () {
|
|
566
|
-
if (isServer$
|
|
567
|
-
|
|
568
|
-
// This allows the integration to be registered but remain inactive
|
|
655
|
+
if (isServer$3) {
|
|
656
|
+
logger.debug('Facebook Pixel init skipped on server');
|
|
569
657
|
return;
|
|
570
658
|
}
|
|
659
|
+
logger.info('Initializing Facebook Pixel', {
|
|
660
|
+
pixelId: config.pixelId
|
|
661
|
+
});
|
|
571
662
|
// Check if Facebook Pixel is already loaded
|
|
572
663
|
if (window.fbq || window._fbq) {
|
|
573
|
-
|
|
664
|
+
logger.warn('Facebook Pixel is already initialized');
|
|
574
665
|
isPixelReady = true;
|
|
575
666
|
return;
|
|
576
667
|
}
|
|
@@ -593,9 +684,11 @@ function createFacebookPixelIntegration(config) {
|
|
|
593
684
|
// Add load event listener
|
|
594
685
|
t.onload = () => {
|
|
595
686
|
isPixelReady = true;
|
|
687
|
+
logger.info('Facebook Pixel script loaded successfully');
|
|
596
688
|
resolve();
|
|
597
689
|
};
|
|
598
690
|
t.onerror = () => {
|
|
691
|
+
logger.error('Failed to load Facebook Pixel script');
|
|
599
692
|
reject(new Error('Failed to load Facebook Pixel script'));
|
|
600
693
|
};
|
|
601
694
|
s = b.getElementsByTagName(e)[0];
|
|
@@ -606,33 +699,56 @@ function createFacebookPixelIntegration(config) {
|
|
|
606
699
|
// Allow duplicate PageView events
|
|
607
700
|
window.fbq.allowDuplicatePageViews = true;
|
|
608
701
|
if (config.automaticConfiguration === false) {
|
|
702
|
+
logger.debug('Disabling automatic configuration');
|
|
609
703
|
window.fbq('set', 'autoConfig', false, config.pixelId);
|
|
610
704
|
}
|
|
611
705
|
// Initialize pixel
|
|
706
|
+
logger.debug('Initializing Facebook Pixel with ID', config.pixelId);
|
|
612
707
|
window.fbq('init', config.pixelId);
|
|
613
708
|
// If script loads synchronously, mark as ready
|
|
614
709
|
if (window.fbq && typeof window.fbq === 'function') {
|
|
615
710
|
isPixelReady = true;
|
|
711
|
+
logger.info('Facebook Pixel initialized synchronously');
|
|
616
712
|
resolve();
|
|
617
713
|
}
|
|
618
714
|
} catch (error) {
|
|
715
|
+
logger.error('Error during Facebook Pixel initialization', error);
|
|
619
716
|
reject(error);
|
|
620
717
|
}
|
|
621
718
|
});
|
|
622
719
|
}),
|
|
623
720
|
track: (eventName, properties, context) => {
|
|
624
|
-
if (!isPixelReady || !window.fbq)
|
|
721
|
+
if (!isPixelReady || !window.fbq) {
|
|
722
|
+
logger.warn('Facebook Pixel not ready, skipping track event', {
|
|
723
|
+
eventName
|
|
724
|
+
});
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
625
727
|
const fbEventName = getFacebookEventName(eventName, config.eventMapping);
|
|
626
728
|
const eventsWithParams = ['Purchase', 'StartTrial', 'Subscribe'];
|
|
627
729
|
const isStandardEvent = standardFacebookEvents.includes(fbEventName);
|
|
628
730
|
const trackType = isStandardEvent ? 'track' : 'trackCustom';
|
|
731
|
+
logger.debug('Tracking Facebook Pixel event', {
|
|
732
|
+
originalEvent: eventName,
|
|
733
|
+
fbEventName,
|
|
734
|
+
trackType,
|
|
735
|
+
isStandardEvent,
|
|
736
|
+
properties
|
|
737
|
+
});
|
|
629
738
|
if (eventsWithParams.includes(fbEventName) && properties) {
|
|
630
739
|
const fbParams = {};
|
|
631
740
|
if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
|
|
632
741
|
if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
|
|
633
742
|
if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
|
|
743
|
+
logger.info('Tracking Facebook Pixel event with parameters', {
|
|
744
|
+
event: fbEventName,
|
|
745
|
+
params: fbParams
|
|
746
|
+
});
|
|
634
747
|
window.fbq(trackType, fbEventName, fbParams);
|
|
635
748
|
} else {
|
|
749
|
+
logger.info('Tracking Facebook Pixel event', {
|
|
750
|
+
event: fbEventName
|
|
751
|
+
});
|
|
636
752
|
window.fbq(trackType, fbEventName);
|
|
637
753
|
}
|
|
638
754
|
},
|
|
@@ -819,6 +935,150 @@ function findFirstProperty$1(properties, keys) {
|
|
|
819
935
|
return null;
|
|
820
936
|
}
|
|
821
937
|
|
|
938
|
+
const isBrowser$2 = typeof window !== 'undefined';
|
|
939
|
+
const isServer$2 = !isBrowser$2;
|
|
940
|
+
/**
|
|
941
|
+
* Standard Pinterest events
|
|
942
|
+
* Reference: https://www.pinterest.com/_/_/help/business/article/event-code
|
|
943
|
+
*/
|
|
944
|
+
const standardPinterestEvents = ['checkout', 'addtocart', 'pagevisit', 'signup', 'watchvideo', 'lead', 'search', 'viewcategory', 'custom', 'addpaymentinfo', 'addtowishlist', 'initiatecheckout', 'subscribe', 'viewcontent'];
|
|
945
|
+
/**
|
|
946
|
+
* Create a Pinterest Tag integration instance
|
|
947
|
+
* Documentation: https://help.pinterest.com/en/business/article/install-the-pinterest-tag
|
|
948
|
+
*/
|
|
949
|
+
function createPinterestTagIntegration(config, debug) {
|
|
950
|
+
let isTagReady = false;
|
|
951
|
+
// Create integration logger
|
|
952
|
+
const integrationLoggerLevel = getIntegrationLoggerLevel('pinterest-tag', debug);
|
|
953
|
+
const logger = new AnalyticsLogger({
|
|
954
|
+
level: integrationLoggerLevel,
|
|
955
|
+
label: 'Saasco Pinterest Tag'
|
|
956
|
+
});
|
|
957
|
+
return {
|
|
958
|
+
name: 'pinterest-tag',
|
|
959
|
+
environments: ['client'],
|
|
960
|
+
init: context => __awaiter(this, void 0, void 0, function* () {
|
|
961
|
+
if (isServer$2) {
|
|
962
|
+
logger.debug('Pinterest Tag init skipped on server');
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
logger.info('Initializing Pinterest Tag', {
|
|
966
|
+
tagId: config.tagId
|
|
967
|
+
});
|
|
968
|
+
// Check if Pinterest Tag is already loaded
|
|
969
|
+
if (window.pintrk || window._pintrk) {
|
|
970
|
+
logger.warn('Pinterest Tag is already initialized');
|
|
971
|
+
isTagReady = true;
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
// Initialize Pinterest Tag
|
|
975
|
+
return new Promise((resolve, reject) => {
|
|
976
|
+
try {
|
|
977
|
+
// Pinterest Tag initialization script
|
|
978
|
+
// Reference: https://help.pinterest.com/en/business/article/install-the-base-code
|
|
979
|
+
(function (e) {
|
|
980
|
+
if (!window.pintrk) {
|
|
981
|
+
window.pintrk = function () {
|
|
982
|
+
window.pintrk.queue.push(Array.prototype.slice.call(arguments));
|
|
983
|
+
};
|
|
984
|
+
const n = window.pintrk;
|
|
985
|
+
n.queue = [];
|
|
986
|
+
n.version = '3.0';
|
|
987
|
+
const t = document.createElement('script');
|
|
988
|
+
t.async = true;
|
|
989
|
+
t.src = e;
|
|
990
|
+
// Add load event listener
|
|
991
|
+
t.onload = () => {
|
|
992
|
+
// base script is loaded; now (re)load tag + fire page via runtime
|
|
993
|
+
window.pintrk('load', config.tagId);
|
|
994
|
+
// initialize the tag
|
|
995
|
+
window.pintrk('page');
|
|
996
|
+
// delay isTagReady to ensure the tag is loaded before sending events
|
|
997
|
+
setTimeout(() => {
|
|
998
|
+
logger.debug('Pinterest Tag initialized');
|
|
999
|
+
isTagReady = true;
|
|
1000
|
+
resolve();
|
|
1001
|
+
}, 500);
|
|
1002
|
+
};
|
|
1003
|
+
t.onerror = () => {
|
|
1004
|
+
logger.error('Failed to load Pinterest Tag script');
|
|
1005
|
+
reject(new Error('Failed to load Pinterest Tag script'));
|
|
1006
|
+
};
|
|
1007
|
+
const r = document.getElementsByTagName('script')[0];
|
|
1008
|
+
r.parentNode.insertBefore(t, r);
|
|
1009
|
+
}
|
|
1010
|
+
})('https://s.pinimg.com/ct/core.js');
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
logger.error('Failed to initialize Pinterest Tag', error);
|
|
1013
|
+
reject(error);
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
}),
|
|
1017
|
+
track: (eventName, properties) => {
|
|
1018
|
+
if (!isTagReady || !window.pintrk) return;
|
|
1019
|
+
const pinterestEventName = getPinterestEventName(eventName, config.eventMapping);
|
|
1020
|
+
const eventData = buildEventData(properties);
|
|
1021
|
+
logger.debug('Tracking event', {
|
|
1022
|
+
eventName,
|
|
1023
|
+
pinterestEventName,
|
|
1024
|
+
eventData
|
|
1025
|
+
});
|
|
1026
|
+
// Track the event
|
|
1027
|
+
window.pintrk('track', pinterestEventName, eventData);
|
|
1028
|
+
},
|
|
1029
|
+
identify: (userId, properties, context) => {
|
|
1030
|
+
// Identify not supported with pinterest tag
|
|
1031
|
+
logger.debug('Identify not supported with pinterest tag', {
|
|
1032
|
+
userId,
|
|
1033
|
+
properties,
|
|
1034
|
+
context
|
|
1035
|
+
});
|
|
1036
|
+
window.pintrk('set', {
|
|
1037
|
+
external_id: userId
|
|
1038
|
+
});
|
|
1039
|
+
window.pintrk('set', {
|
|
1040
|
+
em: properties === null || properties === void 0 ? void 0 : properties['email']
|
|
1041
|
+
});
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function getPinterestEventName(eventName, eventMapping) {
|
|
1047
|
+
// Convert default saasco Page View event by default
|
|
1048
|
+
if (eventName === 'Page View') return 'pagevisit';
|
|
1049
|
+
if (!eventMapping) return eventName.toLowerCase();
|
|
1050
|
+
return eventMapping[eventName] || eventName.toLowerCase();
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Build event data for Pinterest tracking
|
|
1054
|
+
* Reference: https://www.pinterest.com/_/_/help/business/article/event-code
|
|
1055
|
+
*/
|
|
1056
|
+
function buildEventData(properties) {
|
|
1057
|
+
if (!properties) return {};
|
|
1058
|
+
const eventData = {};
|
|
1059
|
+
// Map common properties to Pinterest event data
|
|
1060
|
+
if (properties['event_id']) eventData['event_id'] = properties['event_id'];
|
|
1061
|
+
if (properties['value']) eventData['value'] = properties['value'];
|
|
1062
|
+
if (properties['currency']) eventData['currency'] = properties['currency'];
|
|
1063
|
+
if (properties['order_quantity']) eventData['order_quantity'] = properties['order_quantity'];
|
|
1064
|
+
if (properties['order_id']) eventData['order_id'] = properties['order_id'];
|
|
1065
|
+
if (properties['promo_code']) eventData['promo_code'] = properties['promo_code'];
|
|
1066
|
+
if (properties['property']) eventData['property'] = properties['property'];
|
|
1067
|
+
if (properties['search_query']) eventData['search_query'] = properties['search_query'];
|
|
1068
|
+
if (properties['video_title']) eventData['video_title'] = properties['video_title'];
|
|
1069
|
+
if (properties['lead_type']) eventData['lead_type'] = properties['lead_type'];
|
|
1070
|
+
if (properties['product_category']) eventData['product_category'] = properties['product_category'];
|
|
1071
|
+
// Handle line items for e-commerce tracking
|
|
1072
|
+
if (properties['line_items'] && Array.isArray(properties['line_items'])) {
|
|
1073
|
+
eventData['line_items'] = properties['line_items'];
|
|
1074
|
+
}
|
|
1075
|
+
// Generate event_id if not provided
|
|
1076
|
+
if (!eventData['event_id']) {
|
|
1077
|
+
eventData['event_id'] = uuid();
|
|
1078
|
+
}
|
|
1079
|
+
return eventData;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
822
1082
|
/**
|
|
823
1083
|
* TikTok Pixel Integration
|
|
824
1084
|
*
|
|
@@ -845,20 +1105,28 @@ const standardTikTokEvents = ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'A
|
|
|
845
1105
|
/**
|
|
846
1106
|
* Create a TikTok Pixel integration instance
|
|
847
1107
|
*/
|
|
848
|
-
function createTikTokPixelIntegration(config) {
|
|
1108
|
+
function createTikTokPixelIntegration(config, debug) {
|
|
849
1109
|
let isPixelReady = false;
|
|
1110
|
+
// Create integration logger
|
|
1111
|
+
const integrationLoggerLevel = getIntegrationLoggerLevel('tiktok-pixel', debug);
|
|
1112
|
+
const logger = new AnalyticsLogger({
|
|
1113
|
+
level: integrationLoggerLevel,
|
|
1114
|
+
label: 'Saasco tiktok-pixel'
|
|
1115
|
+
});
|
|
850
1116
|
return {
|
|
851
1117
|
name: 'tiktok-pixel',
|
|
852
1118
|
environments: ['client'],
|
|
853
1119
|
init: () => __awaiter(this, void 0, void 0, function* () {
|
|
854
1120
|
if (isServer$1) {
|
|
855
|
-
|
|
856
|
-
// This allows the integration to be registered but remain inactive
|
|
1121
|
+
logger.debug('TikTok Pixel init skipped on server');
|
|
857
1122
|
return;
|
|
858
1123
|
}
|
|
1124
|
+
logger.info('Initializing TikTok Pixel', {
|
|
1125
|
+
pixelId: config.pixelId
|
|
1126
|
+
});
|
|
859
1127
|
// Check if TikTok Pixel is already loaded
|
|
860
1128
|
if (window.ttq) {
|
|
861
|
-
|
|
1129
|
+
logger.warn('TikTok Pixel is already initialized');
|
|
862
1130
|
isPixelReady = true;
|
|
863
1131
|
return;
|
|
864
1132
|
}
|
|
@@ -909,7 +1177,6 @@ function createTikTokPixelIntegration(config) {
|
|
|
909
1177
|
ttq.load(config.pixelId, config.testMode ? {
|
|
910
1178
|
test_mode: true
|
|
911
1179
|
} : undefined);
|
|
912
|
-
ttq.page();
|
|
913
1180
|
// If script loads synchronously, mark as ready
|
|
914
1181
|
if (window.ttq && typeof window.ttq === 'function') {
|
|
915
1182
|
isPixelReady = true;
|
|
@@ -1084,69 +1351,9 @@ function mapTikTokEventParameters(eventName, properties) {
|
|
|
1084
1351
|
return Object.keys(params).length > 0 ? params : null;
|
|
1085
1352
|
}
|
|
1086
1353
|
|
|
1087
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1088
|
-
class AnalyticsLogger {
|
|
1089
|
-
constructor(config) {
|
|
1090
|
-
this.config = config;
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Log debug information
|
|
1094
|
-
*/
|
|
1095
|
-
log(...args) {
|
|
1096
|
-
if (!this.config.debug) return;
|
|
1097
|
-
const icon = '◍';
|
|
1098
|
-
const message = `${icon} ${this.config.label}`;
|
|
1099
|
-
console.info(`%c ${message}`, `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
|
|
1100
|
-
}
|
|
1101
|
-
/**
|
|
1102
|
-
* Log warning information
|
|
1103
|
-
*/
|
|
1104
|
-
warn(...args) {
|
|
1105
|
-
if (!this.config.debug) return;
|
|
1106
|
-
const icon = '◍';
|
|
1107
|
-
const message = `${icon} ${this.config.label}`;
|
|
1108
|
-
console.warn(`%c ${message}`, `background: #ffa500; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
|
|
1109
|
-
}
|
|
1110
|
-
/**
|
|
1111
|
-
* Log error information
|
|
1112
|
-
*/
|
|
1113
|
-
error(...args) {
|
|
1114
|
-
const icon = '◍';
|
|
1115
|
-
const message = `${icon} ${this.config.label} Error`;
|
|
1116
|
-
console.error(`%c ${message}`, 'background: red; color: white; padding-right: 6px; border-radius: 2px;', ...args);
|
|
1117
|
-
return {
|
|
1118
|
-
success: false,
|
|
1119
|
-
message: args.join(' ')
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
/**
|
|
1125
|
-
* Generate a collision-resistant UUID
|
|
1126
|
-
* Uses lukeed's UUID v4 implementation for consistent, fast UUID generation
|
|
1127
|
-
*/
|
|
1128
|
-
function uuid() {
|
|
1129
|
-
return v4();
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
1354
|
/*
|
|
1133
1355
|
Minimal analytics integration manager (v0)
|
|
1134
1356
|
*/
|
|
1135
|
-
class Logger {
|
|
1136
|
-
constructor(label = 'Analytics', debug = false) {
|
|
1137
|
-
this.label = label;
|
|
1138
|
-
this.debug = debug;
|
|
1139
|
-
}
|
|
1140
|
-
log(...args) {
|
|
1141
|
-
if (this.debug) console.log(`[${this.label}]`, ...args);
|
|
1142
|
-
}
|
|
1143
|
-
warn(...args) {
|
|
1144
|
-
console.warn(`[${this.label}]`, ...args);
|
|
1145
|
-
}
|
|
1146
|
-
error(...args) {
|
|
1147
|
-
console.error(`[${this.label}]`, ...args);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
1357
|
class IntegrationManager {
|
|
1151
1358
|
constructor(config = {}) {
|
|
1152
1359
|
var _a, _b, _c, _d;
|
|
@@ -1154,14 +1361,14 @@ class IntegrationManager {
|
|
|
1154
1361
|
this.integrations = new Map();
|
|
1155
1362
|
this.globalQueue = [];
|
|
1156
1363
|
this.config = {
|
|
1157
|
-
|
|
1364
|
+
loggerLevel: (_a = config.loggerLevel) !== null && _a !== void 0 ? _a : LogLevel.ERROR,
|
|
1158
1365
|
maxQueueSize: (_b = config.maxQueueSize) !== null && _b !== void 0 ? _b : 200,
|
|
1159
1366
|
maxIntegrationWaitTime: (_c = config.maxIntegrationWaitTime) !== null && _c !== void 0 ? _c : 10000,
|
|
1160
1367
|
flushInterval: (_d = config.flushInterval) !== null && _d !== void 0 ? _d : 5000
|
|
1161
1368
|
};
|
|
1162
1369
|
this.logger = new AnalyticsLogger({
|
|
1163
1370
|
label: 'Saasco Integrations Debug',
|
|
1164
|
-
|
|
1371
|
+
level: this.config.loggerLevel
|
|
1165
1372
|
});
|
|
1166
1373
|
this.initTime = Date.now();
|
|
1167
1374
|
this.currentEnvironment = typeof window !== 'undefined' ? 'client' : 'server';
|
|
@@ -1174,7 +1381,7 @@ class IntegrationManager {
|
|
|
1174
1381
|
setContext(next) {
|
|
1175
1382
|
this.context = Object.assign(Object.assign({}, this.context), next);
|
|
1176
1383
|
// Only log if we are setting something
|
|
1177
|
-
if (Object.keys(this.context).length > 0) this.logger.
|
|
1384
|
+
if (Object.keys(this.context).length > 0) this.logger.info('setContext', this.context);
|
|
1178
1385
|
}
|
|
1179
1386
|
/**
|
|
1180
1387
|
* Register and init an integration. When init resolves, we mark it ready and
|
|
@@ -1182,17 +1389,18 @@ class IntegrationManager {
|
|
|
1182
1389
|
*/
|
|
1183
1390
|
registerIntegration(integration) {
|
|
1184
1391
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1392
|
+
this.logger.debug('Register integrations', this.integrations);
|
|
1185
1393
|
if (this.integrations.has(integration.name)) {
|
|
1186
|
-
this.logger.warn(`
|
|
1394
|
+
this.logger.warn(`Integration already registered: ${integration.name}`);
|
|
1187
1395
|
return;
|
|
1188
1396
|
}
|
|
1189
1397
|
// Check environment compatibility
|
|
1190
1398
|
const isCompatible = integration.environments.includes(this.currentEnvironment);
|
|
1191
1399
|
if (!isCompatible) {
|
|
1192
|
-
this.logger.warn(`
|
|
1400
|
+
this.logger.warn(`Skipping ${integration.name}: requires ${integration.environments.join(' or ')} but running in ${this.currentEnvironment}`);
|
|
1193
1401
|
return;
|
|
1194
1402
|
}
|
|
1195
|
-
this.logger.
|
|
1403
|
+
this.logger.debug(`Registering ${integration.name}`);
|
|
1196
1404
|
const state = {
|
|
1197
1405
|
integration,
|
|
1198
1406
|
status: 'idle'
|
|
@@ -1201,17 +1409,21 @@ class IntegrationManager {
|
|
|
1201
1409
|
if (integration.init) {
|
|
1202
1410
|
try {
|
|
1203
1411
|
state.status = 'loading';
|
|
1204
|
-
|
|
1412
|
+
const startTime = Date.now();
|
|
1413
|
+
this.logger.debug(`Initializing ${integration.name}`);
|
|
1414
|
+
yield integration.init(this.context);
|
|
1205
1415
|
state.status = 'ready';
|
|
1206
|
-
|
|
1416
|
+
const endTime = Date.now();
|
|
1417
|
+
const duration = endTime - startTime;
|
|
1418
|
+
this.logger.debug(`Integration ${integration.name} ready in ${duration}ms`);
|
|
1207
1419
|
this.flush();
|
|
1208
1420
|
} catch (e) {
|
|
1209
1421
|
state.status = 'error';
|
|
1210
|
-
this.logger.error(`
|
|
1422
|
+
this.logger.error(`Failed to init ${integration.name}`, e);
|
|
1211
1423
|
}
|
|
1212
1424
|
} else {
|
|
1213
1425
|
state.status = 'ready';
|
|
1214
|
-
this.logger.
|
|
1426
|
+
this.logger.info(`Integration ${integration.name} ready (no init)`);
|
|
1215
1427
|
this.flush();
|
|
1216
1428
|
}
|
|
1217
1429
|
});
|
|
@@ -1252,10 +1464,10 @@ class IntegrationManager {
|
|
|
1252
1464
|
if (this.globalQueue.length >= this.config.maxQueueSize) {
|
|
1253
1465
|
// drop oldest
|
|
1254
1466
|
this.globalQueue.shift();
|
|
1255
|
-
this.logger.warn('
|
|
1467
|
+
this.logger.warn('Queue full → dropped oldest');
|
|
1256
1468
|
}
|
|
1257
1469
|
this.globalQueue.push(envelope);
|
|
1258
|
-
this.logger.
|
|
1470
|
+
this.logger.debug(`Queued (${this.globalQueue.length})`, envelope);
|
|
1259
1471
|
return;
|
|
1260
1472
|
}
|
|
1261
1473
|
this.deliver(envelope);
|
|
@@ -1269,8 +1481,17 @@ class IntegrationManager {
|
|
|
1269
1481
|
if (status !== 'ready') continue;
|
|
1270
1482
|
try {
|
|
1271
1483
|
if (evt.type === 'track' && integration.track && evt.name) {
|
|
1484
|
+
this.logger.debug(`Track (${integration.name}) ${evt.name}`, {
|
|
1485
|
+
properties: evt.properties,
|
|
1486
|
+
context: evt.context
|
|
1487
|
+
});
|
|
1272
1488
|
yield integration.track(evt.name, evt.properties, evt.context);
|
|
1273
1489
|
} else if (evt.type === 'identify' && integration.identify) {
|
|
1490
|
+
this.logger.debug(`Identify (${integration.name})`, {
|
|
1491
|
+
userId: evt.context.distinctId,
|
|
1492
|
+
properties: evt.properties,
|
|
1493
|
+
context: evt.context
|
|
1494
|
+
});
|
|
1274
1495
|
yield integration.identify(evt.context.distinctId, evt.properties, evt.context);
|
|
1275
1496
|
}
|
|
1276
1497
|
} catch (e) {
|
|
@@ -1287,7 +1508,7 @@ class IntegrationManager {
|
|
|
1287
1508
|
if (this.globalQueue.length === 0) return;
|
|
1288
1509
|
// If we're not ready don't flush
|
|
1289
1510
|
if (!this.isReady()) return;
|
|
1290
|
-
this.logger.
|
|
1511
|
+
this.logger.debug(`flushing ${this.globalQueue.length} queued events`);
|
|
1291
1512
|
const toSend = this.globalQueue;
|
|
1292
1513
|
this.globalQueue = [];
|
|
1293
1514
|
// deliver all events
|
|
@@ -1299,8 +1520,14 @@ class IntegrationManager {
|
|
|
1299
1520
|
return n;
|
|
1300
1521
|
}
|
|
1301
1522
|
isReady() {
|
|
1523
|
+
const timeElapsed = Date.now() - this.initTime;
|
|
1302
1524
|
// Return if integrations are not ready, or if we haven't waited the max integration wait time
|
|
1303
|
-
if (this.readyCount() < this.integrations.size &&
|
|
1525
|
+
if (this.readyCount() < this.integrations.size && timeElapsed < this.config.maxIntegrationWaitTime) {
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
if (this.readyCount() < this.integrations.size) {
|
|
1529
|
+
this.logger.info(`proceeding with ${this.readyCount()}/${this.integrations.size} integrations ready after ${timeElapsed}ms wait`);
|
|
1530
|
+
}
|
|
1304
1531
|
return true;
|
|
1305
1532
|
}
|
|
1306
1533
|
/**
|
|
@@ -1322,7 +1549,7 @@ class IntegrationManager {
|
|
|
1322
1549
|
return;
|
|
1323
1550
|
}
|
|
1324
1551
|
this.unloadHandler = () => {
|
|
1325
|
-
this.logger.
|
|
1552
|
+
this.logger.info('page unloading, flushing remaining events');
|
|
1326
1553
|
this.flush();
|
|
1327
1554
|
};
|
|
1328
1555
|
// Use both beforeunload and pagehide for better coverage
|
|
@@ -1341,7 +1568,8 @@ class IntegrationManager {
|
|
|
1341
1568
|
queueLength: this.globalQueue.length,
|
|
1342
1569
|
readyCount: this.readyCount(),
|
|
1343
1570
|
periodicFlushEnabled: this.config.flushInterval > 0,
|
|
1344
|
-
flushInterval: this.config.flushInterval
|
|
1571
|
+
flushInterval: this.config.flushInterval,
|
|
1572
|
+
context: this.context
|
|
1345
1573
|
};
|
|
1346
1574
|
}
|
|
1347
1575
|
}
|
|
@@ -1545,11 +1773,13 @@ class Saasco {
|
|
|
1545
1773
|
this.config = config;
|
|
1546
1774
|
this.lastPageViewPath = '';
|
|
1547
1775
|
this.isInitialized = false;
|
|
1776
|
+
const loggerLevel = getLoggerLevel(this.config);
|
|
1548
1777
|
// Initialize logger
|
|
1549
1778
|
this.logger = new AnalyticsLogger({
|
|
1550
|
-
|
|
1779
|
+
level: loggerLevel,
|
|
1551
1780
|
label: 'Saasco Debug'
|
|
1552
1781
|
});
|
|
1782
|
+
this.logger.info('Saasco started');
|
|
1553
1783
|
if (!config.projectId) {
|
|
1554
1784
|
this.logger.error("Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
|
|
1555
1785
|
return;
|
|
@@ -1564,19 +1794,19 @@ class Saasco {
|
|
|
1564
1794
|
}, this.config.autoPageTracking);
|
|
1565
1795
|
// Initialize integration manager
|
|
1566
1796
|
this.integrationManager = new IntegrationManager({
|
|
1567
|
-
|
|
1797
|
+
loggerLevel,
|
|
1568
1798
|
maxQueueSize: 1000
|
|
1569
1799
|
});
|
|
1570
1800
|
// Set initial context
|
|
1571
1801
|
this.integrationManager.setContext({});
|
|
1572
1802
|
// Initialize integrations immediately (works on both client and server)
|
|
1573
1803
|
this.initIntegrations().catch(error => {
|
|
1574
|
-
this.logger.
|
|
1804
|
+
this.logger.info('Error initializing integrations:', error);
|
|
1575
1805
|
});
|
|
1576
1806
|
}
|
|
1577
1807
|
init() {
|
|
1578
1808
|
if (this.isInitialized) {
|
|
1579
|
-
this.logger.
|
|
1809
|
+
this.logger.info('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
|
|
1580
1810
|
return;
|
|
1581
1811
|
}
|
|
1582
1812
|
// Export Saasco to the window object for easy access and debugging
|
|
@@ -1585,43 +1815,48 @@ class Saasco {
|
|
|
1585
1815
|
}
|
|
1586
1816
|
// Migrate existing localStorage data to cookies for cross-subdomain support
|
|
1587
1817
|
migrateFromLocalStorage();
|
|
1588
|
-
this.logger.
|
|
1589
|
-
if (this.config.debug) this.logger.
|
|
1590
|
-
if (!this.config.enabled) this.logger.
|
|
1818
|
+
this.logger.info('Saasco initialized', this.config);
|
|
1819
|
+
if (this.config.debug) this.logger.info('Debug mode active. This will log all events to the console.');
|
|
1820
|
+
if (!this.config.enabled) this.logger.info(`Analytics is disabled. No requests will be sent to the server and no integrations will be initialized. ${this.config.debug ? 'Debug mode is active and will still log information.' : ''}`);
|
|
1591
1821
|
this.initAutoPageTracking();
|
|
1592
1822
|
this.isInitialized = true;
|
|
1593
1823
|
}
|
|
1594
1824
|
disableDebug() {
|
|
1595
|
-
this.logger.
|
|
1825
|
+
this.logger.info('Debug mode deactivated.');
|
|
1596
1826
|
this.config.debug = false;
|
|
1597
1827
|
}
|
|
1598
1828
|
enableDebug() {
|
|
1599
1829
|
this.config.debug = true;
|
|
1600
|
-
this.logger.
|
|
1830
|
+
this.logger.info('Debug mode activated.');
|
|
1601
1831
|
}
|
|
1602
1832
|
/**
|
|
1603
1833
|
* Initialize third-party integrations
|
|
1604
1834
|
*/
|
|
1605
1835
|
initIntegrations() {
|
|
1606
1836
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1837
|
+
this.logger.info('Initialize integrations');
|
|
1607
1838
|
if (!this.config.integrations || this.config.integrations.length === 0) return;
|
|
1608
1839
|
if (!this.config.enabled) {
|
|
1609
|
-
this.logger.
|
|
1840
|
+
this.logger.info('Analytics is disabled. Skipping integration initialization.');
|
|
1610
1841
|
return;
|
|
1611
1842
|
}
|
|
1612
1843
|
for (const integrationConfig of this.config.integrations) {
|
|
1613
1844
|
try {
|
|
1614
1845
|
if (integrationConfig.type === 'facebook-pixel') {
|
|
1615
|
-
this.logger.
|
|
1616
|
-
const fbIntegration = createFacebookPixelIntegration(integrationConfig.config);
|
|
1846
|
+
this.logger.info('Registering Facebook Pixel integration with ID:', integrationConfig.config.pixelId);
|
|
1847
|
+
const fbIntegration = createFacebookPixelIntegration(integrationConfig.config, integrationConfig.debug);
|
|
1617
1848
|
yield this.integrationManager.registerIntegration(fbIntegration);
|
|
1849
|
+
} else if (integrationConfig.type === 'pinterest-tag') {
|
|
1850
|
+
this.logger.info('Registering Pinterest Tag integration with ID:', integrationConfig.config.tagId);
|
|
1851
|
+
const pinterestIntegration = createPinterestTagIntegration(integrationConfig.config, integrationConfig.debug);
|
|
1852
|
+
yield this.integrationManager.registerIntegration(pinterestIntegration);
|
|
1618
1853
|
} else if (integrationConfig.type === 'tiktok-pixel') {
|
|
1619
|
-
this.logger.
|
|
1620
|
-
const tiktokIntegration = createTikTokPixelIntegration(integrationConfig.config);
|
|
1854
|
+
this.logger.info('Registering TikTok Pixel integration with ID:', integrationConfig.config.pixelId);
|
|
1855
|
+
const tiktokIntegration = createTikTokPixelIntegration(integrationConfig.config, integrationConfig.debug);
|
|
1621
1856
|
yield this.integrationManager.registerIntegration(tiktokIntegration);
|
|
1622
1857
|
}
|
|
1623
1858
|
} catch (error) {
|
|
1624
|
-
this.logger.
|
|
1859
|
+
this.logger.info(`Failed to register ${integrationConfig.type} integration:`, error);
|
|
1625
1860
|
}
|
|
1626
1861
|
}
|
|
1627
1862
|
});
|
|
@@ -1675,9 +1910,9 @@ class Saasco {
|
|
|
1675
1910
|
context: JSON.stringify(context || {})
|
|
1676
1911
|
};
|
|
1677
1912
|
if (data.action === 'Page View') {
|
|
1678
|
-
this.logger.
|
|
1913
|
+
this.logger.info('Page View', window.location.href, data);
|
|
1679
1914
|
} else {
|
|
1680
|
-
this.logger.
|
|
1915
|
+
this.logger.info('track', data);
|
|
1681
1916
|
}
|
|
1682
1917
|
if (!this.config.enabled) {
|
|
1683
1918
|
return Promise.resolve({
|
|
@@ -1730,7 +1965,7 @@ class Saasco {
|
|
|
1730
1965
|
// This should only be called when distinctId is null and there is an existing userId.
|
|
1731
1966
|
// This means the user has logged out and we should reset the session and anonymous IDs
|
|
1732
1967
|
const userIdChangedToNull = distinctId === null && !!getUserId();
|
|
1733
|
-
if (userIdChangedToNull) this.logger.
|
|
1968
|
+
if (userIdChangedToNull) this.logger.info('User logged out');
|
|
1734
1969
|
const reset = userIdChangedToNull;
|
|
1735
1970
|
const sessionId = setSessionId({
|
|
1736
1971
|
reset
|
|
@@ -1741,11 +1976,11 @@ class Saasco {
|
|
|
1741
1976
|
// set the distinct Id to the userId
|
|
1742
1977
|
setUserId(distinctId);
|
|
1743
1978
|
// Update integration manager context and send identify (IntegrationManager will filter by environment)
|
|
1744
|
-
this.integrationManager.setContext({
|
|
1979
|
+
this.integrationManager.setContext(Object.assign({
|
|
1745
1980
|
distinctId,
|
|
1746
1981
|
anonymousId,
|
|
1747
1982
|
sessionId
|
|
1748
|
-
});
|
|
1983
|
+
}, properties));
|
|
1749
1984
|
// No distinctId provided so we don't track the user
|
|
1750
1985
|
if (!distinctId) return Promise.resolve({
|
|
1751
1986
|
success: true,
|
|
@@ -1762,7 +1997,7 @@ class Saasco {
|
|
|
1762
1997
|
payload: properties || {},
|
|
1763
1998
|
context: context || {}
|
|
1764
1999
|
};
|
|
1765
|
-
this.logger.
|
|
2000
|
+
this.logger.info('identify', data);
|
|
1766
2001
|
// Only send identify to integrations if analytics is enabled
|
|
1767
2002
|
if (!this.config.enabled) {
|
|
1768
2003
|
return Promise.resolve({
|
|
@@ -1838,11 +2073,11 @@ class Saasco {
|
|
|
1838
2073
|
if (isServer) return console.warn('Saasco auto page tracking is only available in the browser');
|
|
1839
2074
|
// Prevent intitializing auto page tracking more than once
|
|
1840
2075
|
if (window.saascoAutoPageTrackingActive) {
|
|
1841
|
-
this.logger.
|
|
2076
|
+
this.logger.info('Auto Page Tracking already enabled');
|
|
1842
2077
|
return;
|
|
1843
2078
|
}
|
|
1844
2079
|
window.saascoAutoPageTrackingActive = true;
|
|
1845
|
-
this.logger.
|
|
2080
|
+
this.logger.info('Auto Page Tracking enabled');
|
|
1846
2081
|
// Track initial page load
|
|
1847
2082
|
this.page();
|
|
1848
2083
|
// Listen for hash changes if hash tracking is enabled
|
|
@@ -1866,6 +2101,27 @@ class Saasco {
|
|
|
1866
2101
|
return returnValue;
|
|
1867
2102
|
};
|
|
1868
2103
|
}
|
|
2104
|
+
getIntegrationsStats() {
|
|
2105
|
+
return this.integrationManager.getStats();
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
function getLoggerLevel(config) {
|
|
2109
|
+
// Check URL parameters for debug overrides (browser only)
|
|
2110
|
+
let urlDebug = false;
|
|
2111
|
+
let urlDebugVerbose = false;
|
|
2112
|
+
if (!isServer) {
|
|
2113
|
+
try {
|
|
2114
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
2115
|
+
urlDebug = urlParams.get('saasco-debug') === 'true';
|
|
2116
|
+
urlDebugVerbose = urlParams.get('saasco-debug-verbose') === 'true';
|
|
2117
|
+
} catch (error) {
|
|
2118
|
+
// Ignore URL parsing errors
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
// URL parameters override config
|
|
2122
|
+
const effectiveDebug = urlDebug || config.debug;
|
|
2123
|
+
const effectiveDebugVerbose = urlDebugVerbose || config.debugVerbose;
|
|
2124
|
+
return !!effectiveDebugVerbose ? LogLevel.DEBUG : !!effectiveDebug ? LogLevel.INFO : LogLevel.ERROR;
|
|
1869
2125
|
}
|
|
1870
2126
|
|
|
1871
2127
|
const browserContextSchema = z.object({
|
|
@@ -1893,4 +2149,4 @@ const browserContextSchema = z.object({
|
|
|
1893
2149
|
$utmAdId: z.string().nullable()
|
|
1894
2150
|
});
|
|
1895
2151
|
|
|
1896
|
-
export { IntegrationManager,
|
|
2152
|
+
export { IntegrationManager, Saasco, browserContextSchema, createFacebookPixelIntegration, createPinterestTagIntegration, createTikTokPixelIntegration, getBrowserContext, standardFacebookEvents, standardPinterestEvents, standardTikTokEvents, timezones };
|