saasco-sdk 0.1.31 → 0.1.33
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 +618 -48
- package/index.esm.js +614 -49
- package/package.json +1 -1
- package/src/index.d.ts +1 -0
- package/src/lib/analytics.d.ts +14 -8
- package/src/lib/integrations/facebook-pixel.d.ts +26 -0
- package/src/lib/integrations/index.d.ts +2 -0
- package/src/lib/integrations/integration-manager.d.ts +125 -0
- package/src/lib/utils/index.d.ts +2 -0
- package/src/lib/utils/logger.d.ts +23 -0
- package/src/lib/utils/uuid.d.ts +5 -0
package/index.cjs.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var tslib = require('tslib');
|
|
6
|
-
var uuid = require('@lukeed/uuid');
|
|
7
6
|
var psl = require('psl');
|
|
7
|
+
var uuid$1 = require('@lukeed/uuid');
|
|
8
8
|
var zod = require('zod');
|
|
9
9
|
|
|
10
|
-
var version = "0.1.
|
|
10
|
+
var version = "0.1.33";
|
|
11
11
|
|
|
12
12
|
const timezones = {
|
|
13
13
|
'Asia/Barnaul': 'RU',
|
|
@@ -554,6 +554,546 @@ function getBrowserContext() {
|
|
|
554
554
|
};
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
+
const isBrowser$1 = typeof window !== 'undefined';
|
|
558
|
+
const isServer$1 = !isBrowser$1;
|
|
559
|
+
/**
|
|
560
|
+
* Create a Facebook Pixel integration instance
|
|
561
|
+
*/
|
|
562
|
+
function createFacebookPixelIntegration(config) {
|
|
563
|
+
let isPixelReady = false;
|
|
564
|
+
return {
|
|
565
|
+
name: 'facebook-pixel',
|
|
566
|
+
environments: ['client'],
|
|
567
|
+
init: () => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
568
|
+
if (isServer$1) {
|
|
569
|
+
throw new Error('Facebook Pixel cannot be initialized on server');
|
|
570
|
+
}
|
|
571
|
+
// Check if Facebook Pixel is already loaded
|
|
572
|
+
if (window.fbq || window._fbq) {
|
|
573
|
+
console.warn('Facebook Pixel is already initialized');
|
|
574
|
+
isPixelReady = true;
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
// Initialize Facebook Pixel
|
|
578
|
+
return new Promise((resolve, reject) => {
|
|
579
|
+
try {
|
|
580
|
+
(function (f, b, e, v, n, t, s) {
|
|
581
|
+
if (f.fbq) return;
|
|
582
|
+
n = f.fbq = function () {
|
|
583
|
+
n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
|
|
584
|
+
};
|
|
585
|
+
if (!f._fbq) f._fbq = n;
|
|
586
|
+
n.push = n;
|
|
587
|
+
n.loaded = !0;
|
|
588
|
+
n.version = '2.0';
|
|
589
|
+
n.queue = [];
|
|
590
|
+
t = b.createElement(e);
|
|
591
|
+
t.async = !0;
|
|
592
|
+
t.src = v;
|
|
593
|
+
// Add load event listener
|
|
594
|
+
t.onload = () => {
|
|
595
|
+
isPixelReady = true;
|
|
596
|
+
resolve();
|
|
597
|
+
};
|
|
598
|
+
t.onerror = () => {
|
|
599
|
+
reject(new Error('Failed to load Facebook Pixel script'));
|
|
600
|
+
};
|
|
601
|
+
s = b.getElementsByTagName(e)[0];
|
|
602
|
+
s.parentNode.insertBefore(t, s);
|
|
603
|
+
})(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
|
|
604
|
+
// Initialize pixel (but don't track PageView automatically)
|
|
605
|
+
window.fbq('init', config.pixelId);
|
|
606
|
+
// If script loads synchronously, mark as ready
|
|
607
|
+
if (window.fbq && typeof window.fbq === 'function') {
|
|
608
|
+
isPixelReady = true;
|
|
609
|
+
resolve();
|
|
610
|
+
}
|
|
611
|
+
} catch (error) {
|
|
612
|
+
reject(error);
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}),
|
|
616
|
+
track: (eventName, properties, context) => {
|
|
617
|
+
if (!isPixelReady || !window.fbq) return;
|
|
618
|
+
const fbEventName = getFacebookEventName(eventName, config.eventMapping);
|
|
619
|
+
// Standard Facebook events that support parameters
|
|
620
|
+
const eventsWithParams = ['Purchase', 'StartTrial', 'Subscribe'];
|
|
621
|
+
if (eventsWithParams.includes(fbEventName) && properties) {
|
|
622
|
+
// Extract standard Facebook parameters
|
|
623
|
+
const fbParams = {};
|
|
624
|
+
if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
|
|
625
|
+
if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
|
|
626
|
+
if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
|
|
627
|
+
window.fbq('track', fbEventName, Object.assign(Object.assign({}, properties), fbParams));
|
|
628
|
+
} else {
|
|
629
|
+
window.fbq('track', fbEventName);
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
identify: (userId, properties, context) => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
633
|
+
try {
|
|
634
|
+
if (typeof window !== 'undefined' && window.fbq) {
|
|
635
|
+
const amValues = coerceMetaConversionsAmValues(Object.assign(Object.assign({}, properties || {}), userId ? {
|
|
636
|
+
external_id: userId
|
|
637
|
+
} : {}));
|
|
638
|
+
const am = {};
|
|
639
|
+
if (amValues.external_id) am['external_id'] = amValues.external_id;
|
|
640
|
+
if (amValues.em) am['em'] = yield sha256Hex(amValues.em);
|
|
641
|
+
if (amValues.fn) am['fn'] = amValues.fn;
|
|
642
|
+
if (amValues.ln) am['ln'] = amValues.ln;
|
|
643
|
+
if (amValues.ph) am['ph'] = amValues.ph;
|
|
644
|
+
if (amValues.ge) am['ge'] = amValues.ge;
|
|
645
|
+
if (amValues.db) am['db'] = amValues.db;
|
|
646
|
+
if (amValues.ct) am['ct'] = amValues.ct;
|
|
647
|
+
if (amValues.st) am['st'] = amValues.st;
|
|
648
|
+
if (amValues.zp) am['zp'] = amValues.zp;
|
|
649
|
+
if (amValues.country) am['country'] = amValues.country;
|
|
650
|
+
if (Object.keys(am).length > 0) {
|
|
651
|
+
window.fbq('init', config.pixelId, am);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
} catch (error) {
|
|
655
|
+
console.error('Error identifying user in Meta Pixel:', error);
|
|
656
|
+
}
|
|
657
|
+
})
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function getFacebookEventName(eventName, eventMapping) {
|
|
661
|
+
// Convert default saasco page view event by default
|
|
662
|
+
if (eventName === 'Page View') return 'PageView';
|
|
663
|
+
if (!eventMapping) return eventName;
|
|
664
|
+
return eventMapping[eventName] || eventName;
|
|
665
|
+
}
|
|
666
|
+
// Legacy functions for backward compatibility (deprecated)
|
|
667
|
+
function initializeFacebookPixel(pixelId) {
|
|
668
|
+
var _a;
|
|
669
|
+
console.warn('initializeFacebookPixel is deprecated. Use createFacebookPixelIntegration instead.');
|
|
670
|
+
const integration = createFacebookPixelIntegration({
|
|
671
|
+
pixelId
|
|
672
|
+
});
|
|
673
|
+
(_a = integration.init) === null || _a === void 0 ? void 0 : _a.call(integration);
|
|
674
|
+
}
|
|
675
|
+
function trackFacebookEvent(eventName, properties, eventMapping) {
|
|
676
|
+
console.warn('trackFacebookEvent is deprecated. Use IntegrationManager instead.');
|
|
677
|
+
if (isServer$1 || !window.fbq) return;
|
|
678
|
+
const fbEventName = getFacebookEventName(eventName, eventMapping);
|
|
679
|
+
const eventsWithParams = ['Purchase', 'StartTrial', 'Subscribe'];
|
|
680
|
+
if (eventsWithParams.includes(fbEventName) && properties) {
|
|
681
|
+
const fbParams = {};
|
|
682
|
+
if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
|
|
683
|
+
if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
|
|
684
|
+
if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
|
|
685
|
+
window.fbq('track', fbEventName, fbParams);
|
|
686
|
+
} else {
|
|
687
|
+
window.fbq('track', fbEventName);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function coerceMetaConversionsAmValues(properties) {
|
|
691
|
+
const coerce = [{
|
|
692
|
+
key: 'em',
|
|
693
|
+
coerceFrom: ['email', 'user_email', 'userEmail', 'email_address', 'emailAddress', 'e_mail', 'E_Mail', 'mail', 'Mail', 'contact_email', 'contactEmail', 'primary_email', 'primaryEmail'],
|
|
694
|
+
transform: value => {
|
|
695
|
+
if (typeof value !== 'string') return undefined;
|
|
696
|
+
const normalized = value.trim().toLowerCase();
|
|
697
|
+
return normalized || undefined;
|
|
698
|
+
}
|
|
699
|
+
}, {
|
|
700
|
+
key: 'fn',
|
|
701
|
+
coerceFrom: ['first_name', 'firstName', 'firstname', 'given_name', 'givenName', 'user_first_name', 'userFirstName', 'name_first', 'nameFirst', 'f_name', 'fName'],
|
|
702
|
+
transform: value => {
|
|
703
|
+
if (typeof value !== 'string') return undefined;
|
|
704
|
+
const normalized = value.trim().toLowerCase();
|
|
705
|
+
return normalized || undefined;
|
|
706
|
+
}
|
|
707
|
+
}, {
|
|
708
|
+
key: 'ln',
|
|
709
|
+
coerceFrom: ['last_name', 'lastName', 'lastname', 'surname', 'user_last_name', 'userLastName', 'name_last', 'nameLast', 'l_name', 'lName'],
|
|
710
|
+
transform: value => {
|
|
711
|
+
if (typeof value !== 'string') return undefined;
|
|
712
|
+
const normalized = value.trim().toLowerCase();
|
|
713
|
+
return normalized || undefined;
|
|
714
|
+
}
|
|
715
|
+
}, {
|
|
716
|
+
key: 'ph',
|
|
717
|
+
coerceFrom: ['phone', 'phone_number', 'phoneNumber', 'mobile', 'mobile_number', 'mobileNumber', 'contact_number', 'contactNumber', 'tel', 'telephone'],
|
|
718
|
+
transform: value => {
|
|
719
|
+
if (typeof value !== 'string' && typeof value !== 'number') return undefined;
|
|
720
|
+
const phoneStr = String(value).replace(/\D/g, '');
|
|
721
|
+
return phoneStr || undefined;
|
|
722
|
+
}
|
|
723
|
+
}, {
|
|
724
|
+
key: 'external_id',
|
|
725
|
+
coerceFrom: ['external_id', 'externalId', 'user_id', 'userId', 'id', 'distinctId', 'distinct_id', 'customer_id', 'customerId', 'member_id', 'memberId', 'loyalty_id', 'loyaltyId'],
|
|
726
|
+
transform: value => {
|
|
727
|
+
if (value == null) return undefined;
|
|
728
|
+
return String(value) || undefined;
|
|
729
|
+
}
|
|
730
|
+
}, {
|
|
731
|
+
key: 'ge',
|
|
732
|
+
coerceFrom: ['gender', 'user_gender', 'userGender', 'sex', 'user_sex', 'userSex'],
|
|
733
|
+
transform: value => {
|
|
734
|
+
if (typeof value !== 'string') return undefined;
|
|
735
|
+
const normalized = value.trim().toLowerCase();
|
|
736
|
+
if (normalized === 'female' || normalized === 'f') return 'f';
|
|
737
|
+
if (normalized === 'male' || normalized === 'm') return 'm';
|
|
738
|
+
return undefined;
|
|
739
|
+
}
|
|
740
|
+
}, {
|
|
741
|
+
key: 'db',
|
|
742
|
+
coerceFrom: ['birthday', 'birth_date', 'birthDate', 'date_of_birth', 'dateOfBirth', 'dob', 'Dob', 'birth_day', 'birthDay'],
|
|
743
|
+
transform: value => {
|
|
744
|
+
if (!value) return undefined;
|
|
745
|
+
let dateStr;
|
|
746
|
+
if (value instanceof Date) {
|
|
747
|
+
dateStr = value.toISOString().split('T')[0];
|
|
748
|
+
} else {
|
|
749
|
+
dateStr = String(value);
|
|
750
|
+
}
|
|
751
|
+
const dateMatch = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/);
|
|
752
|
+
if (dateMatch) {
|
|
753
|
+
const [, year, month, day] = dateMatch;
|
|
754
|
+
return `${year}${month}${day}`;
|
|
755
|
+
}
|
|
756
|
+
const numericMatch = dateStr.replace(/\D/g, '');
|
|
757
|
+
if (numericMatch.length === 8) {
|
|
758
|
+
return numericMatch;
|
|
759
|
+
}
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
762
|
+
}, {
|
|
763
|
+
key: 'ct',
|
|
764
|
+
coerceFrom: ['city', 'user_city', 'userCity', 'location_city', 'locationCity', 'address_city', 'addressCity'],
|
|
765
|
+
transform: value => {
|
|
766
|
+
if (typeof value !== 'string') return undefined;
|
|
767
|
+
const normalized = value.trim().toLowerCase().replace(/\s+/g, '');
|
|
768
|
+
return normalized || undefined;
|
|
769
|
+
}
|
|
770
|
+
}, {
|
|
771
|
+
key: 'st',
|
|
772
|
+
coerceFrom: ['state', 'user_state', 'userState', 'province', 'user_province', 'userProvince', 'region', 'user_region', 'userRegion', 'location_state', 'locationState', 'address_state', 'addressState'],
|
|
773
|
+
transform: value => {
|
|
774
|
+
if (typeof value !== 'string') return undefined;
|
|
775
|
+
const normalized = value.trim().toLowerCase();
|
|
776
|
+
if (normalized.length === 2) return normalized;
|
|
777
|
+
return undefined;
|
|
778
|
+
}
|
|
779
|
+
}, {
|
|
780
|
+
key: 'zp',
|
|
781
|
+
coerceFrom: ['zip', 'zipcode', 'zip_code', 'postal_code', 'postalCode', 'postcode', 'user_zip', 'userZip', 'address_zip', 'addressZip'],
|
|
782
|
+
transform: value => {
|
|
783
|
+
if (typeof value !== 'string' && typeof value !== 'number') return undefined;
|
|
784
|
+
return String(value).trim() || undefined;
|
|
785
|
+
}
|
|
786
|
+
}, {
|
|
787
|
+
key: 'country',
|
|
788
|
+
coerceFrom: ['country', 'country_code', 'countryCode', 'user_country', 'userCountry', 'location_country', 'locationCountry', 'address_country', 'addressCountry'],
|
|
789
|
+
transform: value => {
|
|
790
|
+
if (typeof value !== 'string') return undefined;
|
|
791
|
+
const normalized = value.trim().toLowerCase();
|
|
792
|
+
if (normalized.length === 2) return normalized;
|
|
793
|
+
return undefined;
|
|
794
|
+
}
|
|
795
|
+
}];
|
|
796
|
+
const result = {};
|
|
797
|
+
for (const {
|
|
798
|
+
key,
|
|
799
|
+
coerceFrom,
|
|
800
|
+
transform
|
|
801
|
+
} of coerce) {
|
|
802
|
+
const foundValue = findFirstProperty(properties, coerceFrom);
|
|
803
|
+
if (foundValue !== null && transform) {
|
|
804
|
+
const transformedValue = transform(foundValue);
|
|
805
|
+
if (transformedValue) {
|
|
806
|
+
result[key] = transformedValue;
|
|
807
|
+
}
|
|
808
|
+
} else if (foundValue !== null && typeof foundValue === 'string') {
|
|
809
|
+
result[key] = foundValue;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
function findFirstProperty(properties, keys) {
|
|
815
|
+
for (const key of keys) {
|
|
816
|
+
if (properties[key] !== undefined) {
|
|
817
|
+
return properties[key];
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
const toHex = buf => [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
823
|
+
function sha256Hex(input) {
|
|
824
|
+
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
825
|
+
const enc = new TextEncoder().encode(input);
|
|
826
|
+
const digest = yield crypto.subtle.digest('SHA-256', enc);
|
|
827
|
+
return toHex(digest);
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
832
|
+
class AnalyticsLogger {
|
|
833
|
+
constructor(config) {
|
|
834
|
+
this.config = config;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Log debug information
|
|
838
|
+
*/
|
|
839
|
+
log(...args) {
|
|
840
|
+
if (!this.config.debug) return;
|
|
841
|
+
const icon = '◍';
|
|
842
|
+
const message = `${icon} ${this.config.label}`;
|
|
843
|
+
console.info(`%c ${message}`, `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Log warning information
|
|
847
|
+
*/
|
|
848
|
+
warn(...args) {
|
|
849
|
+
if (!this.config.debug) return;
|
|
850
|
+
const icon = '◍';
|
|
851
|
+
const message = `${icon} ${this.config.label}`;
|
|
852
|
+
console.warn(`%c ${message}`, `background: #ffa500; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Log error information
|
|
856
|
+
*/
|
|
857
|
+
error(...args) {
|
|
858
|
+
const icon = '◍';
|
|
859
|
+
const message = `${icon} ${this.config.label} Error`;
|
|
860
|
+
console.error(`%c ${message}`, 'background: red; color: white; padding-right: 6px; border-radius: 2px;', ...args);
|
|
861
|
+
return {
|
|
862
|
+
success: false,
|
|
863
|
+
message: args.join(' ')
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Generate a collision-resistant UUID
|
|
870
|
+
* Uses lukeed's UUID v4 implementation for consistent, fast UUID generation
|
|
871
|
+
*/
|
|
872
|
+
function uuid() {
|
|
873
|
+
return uuid$1.v4();
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/*
|
|
877
|
+
Minimal analytics integration manager (v0)
|
|
878
|
+
*/
|
|
879
|
+
class Logger {
|
|
880
|
+
constructor(label = 'Analytics', debug = false) {
|
|
881
|
+
this.label = label;
|
|
882
|
+
this.debug = debug;
|
|
883
|
+
}
|
|
884
|
+
log(...args) {
|
|
885
|
+
if (this.debug) console.log(`[${this.label}]`, ...args);
|
|
886
|
+
}
|
|
887
|
+
warn(...args) {
|
|
888
|
+
console.warn(`[${this.label}]`, ...args);
|
|
889
|
+
}
|
|
890
|
+
error(...args) {
|
|
891
|
+
console.error(`[${this.label}]`, ...args);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
class IntegrationManager {
|
|
895
|
+
constructor(config = {}) {
|
|
896
|
+
var _a, _b, _c, _d;
|
|
897
|
+
this.context = {};
|
|
898
|
+
this.integrations = new Map();
|
|
899
|
+
this.globalQueue = [];
|
|
900
|
+
this.config = {
|
|
901
|
+
debug: (_a = config.debug) !== null && _a !== void 0 ? _a : false,
|
|
902
|
+
maxQueueSize: (_b = config.maxQueueSize) !== null && _b !== void 0 ? _b : 200,
|
|
903
|
+
maxIntegrationWaitTime: (_c = config.maxIntegrationWaitTime) !== null && _c !== void 0 ? _c : 10000,
|
|
904
|
+
flushInterval: (_d = config.flushInterval) !== null && _d !== void 0 ? _d : 5000
|
|
905
|
+
};
|
|
906
|
+
this.logger = new AnalyticsLogger({
|
|
907
|
+
label: 'Saasco Integrations Debug',
|
|
908
|
+
debug: this.config.debug
|
|
909
|
+
});
|
|
910
|
+
this.initTime = Date.now();
|
|
911
|
+
this.currentEnvironment = typeof window !== 'undefined' ? 'client' : 'server';
|
|
912
|
+
this.setupPeriodicFlushing();
|
|
913
|
+
this.setupUnloadHandler();
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Shallow-merge context to keep it simple + predictable in v0
|
|
917
|
+
*/
|
|
918
|
+
setContext(next) {
|
|
919
|
+
this.context = Object.assign(Object.assign({}, this.context), next);
|
|
920
|
+
// Only log if we are setting something
|
|
921
|
+
if (Object.keys(this.context).length > 0) this.logger.log('context', this.context);
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Register and init an integration. When init resolves, we mark it ready and
|
|
925
|
+
* immediately flush any queued events in FIFO order to *all* ready integrations.
|
|
926
|
+
*/
|
|
927
|
+
registerIntegration(integration) {
|
|
928
|
+
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
929
|
+
if (this.integrations.has(integration.name)) {
|
|
930
|
+
this.logger.warn(`integration already registered: ${integration.name}`);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
// Check environment compatibility
|
|
934
|
+
const isCompatible = integration.environments.includes(this.currentEnvironment);
|
|
935
|
+
if (!isCompatible) {
|
|
936
|
+
this.logger.warn(`skipping ${integration.name}: requires ${integration.environments.join(' or ')} but running in ${this.currentEnvironment}`);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
this.logger.log(`registering ${integration.name} (supports: ${integration.environments.join(', ')}, current: ${this.currentEnvironment})`);
|
|
940
|
+
const state = {
|
|
941
|
+
integration,
|
|
942
|
+
status: 'idle'
|
|
943
|
+
};
|
|
944
|
+
this.integrations.set(integration.name, state);
|
|
945
|
+
if (integration.init) {
|
|
946
|
+
try {
|
|
947
|
+
state.status = 'loading';
|
|
948
|
+
this.logger.log(`init ${integration.name}`);
|
|
949
|
+
yield integration.init();
|
|
950
|
+
state.status = 'ready';
|
|
951
|
+
this.logger.log(`ready ${integration.name}`);
|
|
952
|
+
this.flush();
|
|
953
|
+
} catch (e) {
|
|
954
|
+
state.status = 'error';
|
|
955
|
+
this.logger.error(`failed to init ${integration.name}`, e);
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
state.status = 'ready';
|
|
959
|
+
this.logger.log(`ready (no init) ${integration.name}`);
|
|
960
|
+
this.flush();
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
identify(userId, traits) {
|
|
965
|
+
// update context so subsequent track() carries new userId
|
|
966
|
+
this.setContext({
|
|
967
|
+
userId
|
|
968
|
+
});
|
|
969
|
+
this.send({
|
|
970
|
+
type: 'identify',
|
|
971
|
+
name: undefined,
|
|
972
|
+
properties: traits
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
track(name, properties) {
|
|
976
|
+
this.send({
|
|
977
|
+
type: 'track',
|
|
978
|
+
name,
|
|
979
|
+
properties
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Core send path: if at least one integration is ready -> deliver immediately
|
|
984
|
+
* Else enqueue (bounded FIFO)
|
|
985
|
+
*/
|
|
986
|
+
send(partial) {
|
|
987
|
+
const envelope = {
|
|
988
|
+
id: uuid(),
|
|
989
|
+
type: partial.type,
|
|
990
|
+
timestamp: Date.now(),
|
|
991
|
+
name: partial.name,
|
|
992
|
+
properties: partial.properties,
|
|
993
|
+
context: Object.assign({}, this.context)
|
|
994
|
+
};
|
|
995
|
+
// If we're not ready, enqueue
|
|
996
|
+
if (!this.isReady()) {
|
|
997
|
+
if (this.globalQueue.length >= this.config.maxQueueSize) {
|
|
998
|
+
// drop oldest
|
|
999
|
+
this.globalQueue.shift();
|
|
1000
|
+
this.logger.warn('queue full → dropped oldest');
|
|
1001
|
+
}
|
|
1002
|
+
this.globalQueue.push(envelope);
|
|
1003
|
+
this.logger.log(`queued (${this.globalQueue.length})`, envelope);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
this.deliver(envelope);
|
|
1007
|
+
}
|
|
1008
|
+
deliver(evt) {
|
|
1009
|
+
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
1010
|
+
for (const {
|
|
1011
|
+
integration,
|
|
1012
|
+
status
|
|
1013
|
+
} of this.integrations.values()) {
|
|
1014
|
+
if (status !== 'ready') continue;
|
|
1015
|
+
try {
|
|
1016
|
+
if (evt.type === 'track' && integration.track && evt.name) {
|
|
1017
|
+
yield integration.track(evt.name, evt.properties, evt.context);
|
|
1018
|
+
} else if (evt.type === 'identify' && integration.identify) {
|
|
1019
|
+
yield integration.identify(evt.context.distinctId, evt.properties, evt.context);
|
|
1020
|
+
}
|
|
1021
|
+
} catch (e) {
|
|
1022
|
+
this.logger.error(`deliver error in ${integration.name} (${evt.type})`, e);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Flush queued events FIFO once at least one integration is ready.
|
|
1029
|
+
*/
|
|
1030
|
+
flush() {
|
|
1031
|
+
// nothing to flush
|
|
1032
|
+
if (this.globalQueue.length === 0) return;
|
|
1033
|
+
// If we're not ready don't flush
|
|
1034
|
+
if (!this.isReady()) return;
|
|
1035
|
+
this.logger.log(`flushing ${this.globalQueue.length} queued events`);
|
|
1036
|
+
const toSend = this.globalQueue;
|
|
1037
|
+
this.globalQueue = [];
|
|
1038
|
+
// deliver all events
|
|
1039
|
+
toSend.forEach(evt => this.deliver(evt));
|
|
1040
|
+
}
|
|
1041
|
+
readyCount() {
|
|
1042
|
+
let n = 0;
|
|
1043
|
+
for (const s of this.integrations.values()) if (s.status === 'ready') n++;
|
|
1044
|
+
return n;
|
|
1045
|
+
}
|
|
1046
|
+
isReady() {
|
|
1047
|
+
// Return if integrations are not ready, or if we haven't waited the max integration wait time
|
|
1048
|
+
if (this.readyCount() < this.integrations.size && Date.now() - this.initTime < this.config.maxIntegrationWaitTime) return false;
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Setup periodic flushing if enabled
|
|
1053
|
+
*/
|
|
1054
|
+
setupPeriodicFlushing() {
|
|
1055
|
+
if (this.config.flushInterval <= 0) return;
|
|
1056
|
+
this.flushTimer = setInterval(() => {
|
|
1057
|
+
if (this.globalQueue.length > 0) {
|
|
1058
|
+
this.logger.log('periodic flush triggered');
|
|
1059
|
+
this.flush();
|
|
1060
|
+
}
|
|
1061
|
+
}, this.config.flushInterval);
|
|
1062
|
+
this.logger.log(`periodic flushing enabled: ${this.config.flushInterval}ms`);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Setup page unload handler for client environment
|
|
1066
|
+
*/
|
|
1067
|
+
setupUnloadHandler() {
|
|
1068
|
+
if (this.currentEnvironment !== 'client' || typeof window === 'undefined') {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
this.unloadHandler = () => {
|
|
1072
|
+
this.logger.log('page unloading, flushing remaining events');
|
|
1073
|
+
this.flush();
|
|
1074
|
+
};
|
|
1075
|
+
// Use both beforeunload and pagehide for better coverage
|
|
1076
|
+
window.addEventListener('beforeunload', this.unloadHandler);
|
|
1077
|
+
window.addEventListener('pagehide', this.unloadHandler);
|
|
1078
|
+
this.logger.log('page unload handlers registered');
|
|
1079
|
+
}
|
|
1080
|
+
/** Debug helpers */
|
|
1081
|
+
getStats() {
|
|
1082
|
+
return {
|
|
1083
|
+
currentEnvironment: this.currentEnvironment,
|
|
1084
|
+
integrations: [...this.integrations.values()].map(s => ({
|
|
1085
|
+
name: s.integration.name,
|
|
1086
|
+
status: s.status,
|
|
1087
|
+
environments: s.integration.environments
|
|
1088
|
+
})),
|
|
1089
|
+
queueLength: this.globalQueue.length,
|
|
1090
|
+
readyCount: this.readyCount(),
|
|
1091
|
+
periodicFlushEnabled: this.config.flushInterval > 0,
|
|
1092
|
+
flushInterval: this.config.flushInterval
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
557
1097
|
const isBrowser = typeof window !== 'undefined';
|
|
558
1098
|
const isServer = !isBrowser;
|
|
559
1099
|
const PREF = 'saasco-sdk';
|
|
@@ -688,8 +1228,9 @@ function setSessionId({
|
|
|
688
1228
|
} = {
|
|
689
1229
|
reset: false
|
|
690
1230
|
}) {
|
|
691
|
-
const sessionId = reset ? uuid
|
|
1231
|
+
const sessionId = reset ? uuid() : getSessionId() || uuid();
|
|
692
1232
|
storeData(`session-id`, sessionId, SESSION_DURATION);
|
|
1233
|
+
return sessionId;
|
|
693
1234
|
}
|
|
694
1235
|
function getAnonymousId() {
|
|
695
1236
|
return retrieveData(`anonymous-id`);
|
|
@@ -699,7 +1240,7 @@ function setAnonymousId({
|
|
|
699
1240
|
} = {
|
|
700
1241
|
reset: false
|
|
701
1242
|
}) {
|
|
702
|
-
const anonymousId = reset ? uuid
|
|
1243
|
+
const anonymousId = reset ? uuid() : getAnonymousId() || uuid();
|
|
703
1244
|
storeData(`anonymous-id`, anonymousId, USER_DURATION);
|
|
704
1245
|
return anonymousId;
|
|
705
1246
|
}
|
|
@@ -746,13 +1287,19 @@ class Saasco {
|
|
|
746
1287
|
* @param config.debug Whether to log debug information. Default is false.
|
|
747
1288
|
* @param config.trackUrlParams Whether to track URL parameters. Default is true.
|
|
748
1289
|
* @param config.trackHashChanges Whether to track hash changes. Default is true.
|
|
1290
|
+
* @param config.integrations Configuration for third-party integrations like Facebook Pixel.
|
|
749
1291
|
*/
|
|
750
1292
|
constructor(config) {
|
|
751
1293
|
this.config = config;
|
|
752
1294
|
this.lastPageViewPath = '';
|
|
753
1295
|
this.isInitialized = false;
|
|
1296
|
+
// Initialize logger
|
|
1297
|
+
this.logger = new AnalyticsLogger({
|
|
1298
|
+
debug: !!this.config.debug,
|
|
1299
|
+
label: 'Saasco Debug'
|
|
1300
|
+
});
|
|
754
1301
|
if (!config.projectId) {
|
|
755
|
-
this.error("Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
|
|
1302
|
+
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.");
|
|
756
1303
|
return;
|
|
757
1304
|
}
|
|
758
1305
|
// default enabled to true
|
|
@@ -763,10 +1310,17 @@ class Saasco {
|
|
|
763
1310
|
trackQueryParams: true,
|
|
764
1311
|
trackHash: true
|
|
765
1312
|
}, this.config.autoPageTracking);
|
|
1313
|
+
// Initialize integration manager
|
|
1314
|
+
this.integrationManager = new IntegrationManager({
|
|
1315
|
+
debug: this.config.debug,
|
|
1316
|
+
maxQueueSize: 1000
|
|
1317
|
+
});
|
|
1318
|
+
// Set initial context
|
|
1319
|
+
this.integrationManager.setContext({});
|
|
766
1320
|
}
|
|
767
1321
|
init() {
|
|
768
1322
|
if (this.isInitialized) {
|
|
769
|
-
this.log('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
|
|
1323
|
+
this.logger.log('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
|
|
770
1324
|
return;
|
|
771
1325
|
}
|
|
772
1326
|
// Export Saasco to the window object for easy access and debugging
|
|
@@ -775,23 +1329,46 @@ class Saasco {
|
|
|
775
1329
|
}
|
|
776
1330
|
// Migrate existing localStorage data to cookies for cross-subdomain support
|
|
777
1331
|
migrateFromLocalStorage();
|
|
778
|
-
this.log('Saasco initialized', this.config);
|
|
779
|
-
if (this.config.debug) this.log('Debug mode active. This will log all events to the console.');
|
|
780
|
-
if (!this.config.enabled) this.log('Analytics is disabled. No requests will be sent to the server.');
|
|
1332
|
+
this.logger.log('Saasco initialized', this.config);
|
|
1333
|
+
if (this.config.debug) this.logger.log('Debug mode active. This will log all events to the console.');
|
|
1334
|
+
if (!this.config.enabled) this.logger.log('Analytics is disabled. No requests will be sent to the server.');
|
|
1335
|
+
// Initialize integrations asynchronously
|
|
1336
|
+
this.initIntegrations().catch(error => {
|
|
1337
|
+
this.logger.log('Error initializing integrations:', error);
|
|
1338
|
+
});
|
|
781
1339
|
this.initAutoPageTracking();
|
|
782
1340
|
this.isInitialized = true;
|
|
783
1341
|
}
|
|
784
1342
|
disableDebug() {
|
|
785
|
-
this.log('Debug mode deactivated.');
|
|
1343
|
+
this.logger.log('Debug mode deactivated.');
|
|
786
1344
|
this.config.debug = false;
|
|
787
1345
|
}
|
|
788
1346
|
enableDebug() {
|
|
789
1347
|
this.config.debug = true;
|
|
790
|
-
this.log('Debug mode activated.');
|
|
1348
|
+
this.logger.log('Debug mode activated.');
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Initialize third-party integrations
|
|
1352
|
+
*/
|
|
1353
|
+
initIntegrations() {
|
|
1354
|
+
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
1355
|
+
if (!this.config.integrations || this.config.integrations.length === 0) return;
|
|
1356
|
+
for (const integrationConfig of this.config.integrations) {
|
|
1357
|
+
try {
|
|
1358
|
+
if (integrationConfig.type === 'facebook-pixel') {
|
|
1359
|
+
this.logger.log('Registering Facebook Pixel integration with ID:', integrationConfig.config.pixelId);
|
|
1360
|
+
const fbIntegration = createFacebookPixelIntegration(integrationConfig.config);
|
|
1361
|
+
yield this.integrationManager.registerIntegration(fbIntegration);
|
|
1362
|
+
}
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
this.logger.log(`Failed to register ${integrationConfig.type} integration:`, error);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
791
1368
|
}
|
|
792
1369
|
track(actionOrPayload, propertiesOrNothing, contextOrNothing) {
|
|
793
1370
|
if (!this.config.projectId) {
|
|
794
|
-
const response = this.error("Unable to track event. Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
|
|
1371
|
+
const response = this.logger.error("Unable to track event. Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
|
|
795
1372
|
return Promise.resolve(response);
|
|
796
1373
|
}
|
|
797
1374
|
setSessionId();
|
|
@@ -800,12 +1377,12 @@ class Saasco {
|
|
|
800
1377
|
const hasPayload = typeof actionOrPayload === 'object';
|
|
801
1378
|
// Must do payload on the server
|
|
802
1379
|
if (isServer && hasAction) {
|
|
803
|
-
const response = this.error('When calling track from the server you must pass a payload object with a userId in order to track events. For example: track({ event: "Song Played", userId: "user_123", properties: { song: "Song Name" } })');
|
|
1380
|
+
const response = this.logger.error('When calling track from the server you must pass a payload object with a userId in order to track events. For example: track({ event: "Song Played", userId: "user_123", properties: { song: "Song Name" } })');
|
|
804
1381
|
return Promise.resolve(response);
|
|
805
1382
|
}
|
|
806
1383
|
// On server events we require a userId or an anonymousId
|
|
807
1384
|
if (isServer && hasPayload && !actionOrPayload.userId && !actionOrPayload.anonymousId) {
|
|
808
|
-
const response = this.error('When calling track from the server you must pass a payload object with either a userId or an anonymousId in order to track events. For example:\n\ntrack({ event: "Song Played", userId: "user_123", properties: { song: "Song Name" } }). \n\nIf providing an anonymousId this must be provided from the client side otherwise we will not have a user to connect the event to.');
|
|
1385
|
+
const response = this.logger.error('When calling track from the server you must pass a payload object with either a userId or an anonymousId in order to track events. For example:\n\ntrack({ event: "Song Played", userId: "user_123", properties: { song: "Song Name" } }). \n\nIf providing an anonymousId this must be provided from the client side otherwise we will not have a user to connect the event to.');
|
|
809
1386
|
return Promise.resolve(response);
|
|
810
1387
|
}
|
|
811
1388
|
const action = hasAction ? actionOrPayload : actionOrPayload.event;
|
|
@@ -825,7 +1402,7 @@ class Saasco {
|
|
|
825
1402
|
properties: properties || {}
|
|
826
1403
|
});
|
|
827
1404
|
const data = {
|
|
828
|
-
id: uuid
|
|
1405
|
+
id: uuid(),
|
|
829
1406
|
timestamp: new Date().toISOString(),
|
|
830
1407
|
action,
|
|
831
1408
|
version,
|
|
@@ -837,6 +1414,9 @@ class Saasco {
|
|
|
837
1414
|
source: isBrowser ? 'client' : 'server',
|
|
838
1415
|
context: JSON.stringify(context || {})
|
|
839
1416
|
};
|
|
1417
|
+
// Send event to integrations
|
|
1418
|
+
this.integrationManager.track(action, properties);
|
|
1419
|
+
// Send event to API
|
|
840
1420
|
return this.doRequest('events', data);
|
|
841
1421
|
}
|
|
842
1422
|
/**
|
|
@@ -868,20 +1448,20 @@ class Saasco {
|
|
|
868
1448
|
}
|
|
869
1449
|
identify(distinctIdOrProperties, propertiesOrContext, contextOrNothing) {
|
|
870
1450
|
if (!this.config.projectId) {
|
|
871
|
-
const response = this.error("Unable to identify user. Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
|
|
1451
|
+
const response = this.logger.error("Unable to identify user. Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
|
|
872
1452
|
return Promise.resolve(response);
|
|
873
1453
|
}
|
|
874
1454
|
const hasId = typeof distinctIdOrProperties === 'string' || typeof distinctIdOrProperties === 'number' || distinctIdOrProperties === null;
|
|
875
|
-
const distinctId = hasId ? distinctIdOrProperties === null || distinctIdOrProperties === void 0 ? void 0 : distinctIdOrProperties.toString() : `soft_${uuid
|
|
1455
|
+
const distinctId = hasId ? distinctIdOrProperties === null || distinctIdOrProperties === void 0 ? void 0 : distinctIdOrProperties.toString() : `soft_${uuid()}`;
|
|
876
1456
|
const properties = hasId ? propertiesOrContext : distinctIdOrProperties;
|
|
877
1457
|
const context = hasId ? contextOrNothing : propertiesOrContext;
|
|
878
1458
|
// When the user gets identified as null this will reset the users session and anonymous id
|
|
879
1459
|
// This should only be called when distinctId is null and there is an existing userId.
|
|
880
1460
|
// This means the user has logged out and we should reset the session and anonymous IDs
|
|
881
1461
|
const userIdChangedToNull = distinctId === null && !!getUserId();
|
|
882
|
-
if (userIdChangedToNull) this.log('User logged out');
|
|
1462
|
+
if (userIdChangedToNull) this.logger.log('User logged out');
|
|
883
1463
|
const reset = userIdChangedToNull;
|
|
884
|
-
setSessionId({
|
|
1464
|
+
const sessionId = setSessionId({
|
|
885
1465
|
reset
|
|
886
1466
|
});
|
|
887
1467
|
const anonymousId = setAnonymousId({
|
|
@@ -889,13 +1469,21 @@ class Saasco {
|
|
|
889
1469
|
});
|
|
890
1470
|
// set the distinct Id to the userId
|
|
891
1471
|
setUserId(distinctId);
|
|
1472
|
+
// Update integration manager context and send identify (IntegrationManager will filter by environment)
|
|
1473
|
+
this.integrationManager.setContext({
|
|
1474
|
+
distinctId,
|
|
1475
|
+
anonymousId,
|
|
1476
|
+
sessionId
|
|
1477
|
+
});
|
|
1478
|
+
this.integrationManager.identify(distinctId, properties);
|
|
892
1479
|
// No distinctId provided so we don't track the user
|
|
893
1480
|
if (!distinctId) return Promise.resolve({
|
|
894
1481
|
success: true,
|
|
895
1482
|
message: 'No distinctId provided'
|
|
896
1483
|
});
|
|
1484
|
+
// Send identify to API
|
|
897
1485
|
const data = {
|
|
898
|
-
id: uuid
|
|
1486
|
+
id: uuid(),
|
|
899
1487
|
timestamp: new Date().toISOString(),
|
|
900
1488
|
projectId: this.config.projectId,
|
|
901
1489
|
distinctId,
|
|
@@ -929,9 +1517,9 @@ class Saasco {
|
|
|
929
1517
|
const base = this.config.proxy || 'https://www.saasco.com/api/';
|
|
930
1518
|
const url = `${base}${path}`;
|
|
931
1519
|
if (data.action === 'Page View') {
|
|
932
|
-
this.log('Page View', window.location.href, data);
|
|
1520
|
+
this.logger.log('Page View', window.location.href, data);
|
|
933
1521
|
} else {
|
|
934
|
-
this.log(data.action || path, data);
|
|
1522
|
+
this.logger.log(data.action || path, data);
|
|
935
1523
|
}
|
|
936
1524
|
// If analytics is disabled, don't send the request
|
|
937
1525
|
if (this.config.enabled === false) return {
|
|
@@ -955,7 +1543,7 @@ class Saasco {
|
|
|
955
1543
|
message: responseBody.message
|
|
956
1544
|
};
|
|
957
1545
|
} catch (error) {
|
|
958
|
-
this.error(`\nError Message: "${error.message}"`, `\nPath: "/${path}"`, `\nRequest Data: ${JSON.stringify(data, null, 2)}`);
|
|
1546
|
+
this.logger.error(`\nError Message: "${error.message}"`, `\nPath: "/${path}"`, `\nRequest Data: ${JSON.stringify(data, null, 2)}`);
|
|
959
1547
|
return {
|
|
960
1548
|
success: false,
|
|
961
1549
|
message: error.message
|
|
@@ -963,29 +1551,6 @@ class Saasco {
|
|
|
963
1551
|
}
|
|
964
1552
|
});
|
|
965
1553
|
}
|
|
966
|
-
/**
|
|
967
|
-
* @param args Arguments to be logged
|
|
968
|
-
*/
|
|
969
|
-
log(...args) {
|
|
970
|
-
if (!this.config.debug) return;
|
|
971
|
-
const message = '◍ Saasco Debug';
|
|
972
|
-
console.info(
|
|
973
|
-
// Message highlighted for easy finding
|
|
974
|
-
`%c ${message}`, 'background: #eee; color: #000; padding: 2px 4px; border-radius: 2px;', ...args);
|
|
975
|
-
}
|
|
976
|
-
/**
|
|
977
|
-
* @param args Arguments to be logged
|
|
978
|
-
*/
|
|
979
|
-
error(...args) {
|
|
980
|
-
const message = '◍ Saasco Error';
|
|
981
|
-
console.error(
|
|
982
|
-
// Message highlighted for easy finding
|
|
983
|
-
`%c ${message}`, 'background: red; color: white; padding: 2px 4px; border-radius: 2px;', ...args);
|
|
984
|
-
return {
|
|
985
|
-
success: false,
|
|
986
|
-
message: args.join(' ')
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
1554
|
/**
|
|
990
1555
|
* If autoPageTracking is enabled, this will automatically track page views
|
|
991
1556
|
* It listens to url changes to track new pages every time the url changes
|
|
@@ -999,11 +1564,11 @@ class Saasco {
|
|
|
999
1564
|
if (isServer) return console.warn('Saasco auto page tracking is only available in the browser');
|
|
1000
1565
|
// Prevent intitializing auto page tracking more than once
|
|
1001
1566
|
if (window.saascoAutoPageTrackingActive) {
|
|
1002
|
-
this.log('Auto Page Tracking already enabled');
|
|
1567
|
+
this.logger.log('Auto Page Tracking already enabled');
|
|
1003
1568
|
return;
|
|
1004
1569
|
}
|
|
1005
1570
|
window.saascoAutoPageTrackingActive = true;
|
|
1006
|
-
this.log('Auto Page Tracking enabled');
|
|
1571
|
+
this.logger.log('Auto Page Tracking enabled');
|
|
1007
1572
|
// Track initial page load
|
|
1008
1573
|
this.page();
|
|
1009
1574
|
// Listen for hash changes if hash tracking is enabled
|
|
@@ -1054,7 +1619,12 @@ const browserContextSchema = zod.z.object({
|
|
|
1054
1619
|
$utmAdId: zod.z.string().nullable()
|
|
1055
1620
|
});
|
|
1056
1621
|
|
|
1622
|
+
exports.IntegrationManager = IntegrationManager;
|
|
1623
|
+
exports.Logger = Logger;
|
|
1057
1624
|
exports.Saasco = Saasco;
|
|
1058
1625
|
exports.browserContextSchema = browserContextSchema;
|
|
1626
|
+
exports.createFacebookPixelIntegration = createFacebookPixelIntegration;
|
|
1059
1627
|
exports.getBrowserContext = getBrowserContext;
|
|
1628
|
+
exports.initializeFacebookPixel = initializeFacebookPixel;
|
|
1060
1629
|
exports.timezones = timezones;
|
|
1630
|
+
exports.trackFacebookEvent = trackFacebookEvent;
|