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 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.31";
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.v4() : getSessionId() || uuid.v4();
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.v4() : getAnonymousId() || uuid.v4();
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.v4(),
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.v4()}`;
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.v4(),
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;