saasco-sdk 0.1.31 → 0.1.32

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.32";
11
11
 
12
12
  const timezones = {
13
13
  'Asia/Barnaul': 'RU',
@@ -554,6 +554,385 @@ 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, fbParams);
628
+ } else {
629
+ window.fbq('track', fbEventName);
630
+ }
631
+ },
632
+ identify: (userId, properties, context) => {
633
+ if (!isPixelReady || !window.fbq) return;
634
+ // Facebook Pixel doesn't have explicit identify method
635
+ // We can use advanced matching or track as CompleteRegistration
636
+ if (userId && properties) {
637
+ // Track as registration/identification event
638
+ window.fbq('track', 'CompleteRegistration', properties);
639
+ }
640
+ }
641
+ };
642
+ }
643
+ function getFacebookEventName(eventName, eventMapping) {
644
+ if (!eventMapping) return eventName;
645
+ return eventMapping[eventName] || eventName;
646
+ }
647
+ // Legacy functions for backward compatibility (deprecated)
648
+ function initializeFacebookPixel(pixelId) {
649
+ var _a;
650
+ console.warn('initializeFacebookPixel is deprecated. Use createFacebookPixelIntegration instead.');
651
+ const integration = createFacebookPixelIntegration({
652
+ pixelId
653
+ });
654
+ (_a = integration.init) === null || _a === void 0 ? void 0 : _a.call(integration);
655
+ }
656
+ function trackFacebookEvent(eventName, properties, eventMapping) {
657
+ console.warn('trackFacebookEvent is deprecated. Use IntegrationManager instead.');
658
+ if (isServer$1 || !window.fbq) return;
659
+ const fbEventName = getFacebookEventName(eventName, eventMapping);
660
+ const eventsWithParams = ['Purchase', 'StartTrial', 'Subscribe'];
661
+ if (eventsWithParams.includes(fbEventName) && properties) {
662
+ const fbParams = {};
663
+ if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
664
+ if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
665
+ if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
666
+ window.fbq('track', fbEventName, fbParams);
667
+ } else {
668
+ window.fbq('track', fbEventName);
669
+ }
670
+ }
671
+
672
+ /* eslint-disable @typescript-eslint/no-explicit-any */
673
+ class AnalyticsLogger {
674
+ constructor(config) {
675
+ this.config = config;
676
+ }
677
+ /**
678
+ * Log debug information
679
+ */
680
+ log(...args) {
681
+ if (!this.config.debug) return;
682
+ const icon = '◍';
683
+ const message = `${icon} ${this.config.label}`;
684
+ console.info(`%c ${message}`, `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
685
+ }
686
+ /**
687
+ * Log warning information
688
+ */
689
+ warn(...args) {
690
+ if (!this.config.debug) return;
691
+ const icon = '◍';
692
+ const message = `${icon} ${this.config.label}`;
693
+ console.warn(`%c ${message}`, `background: #ffa500; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
694
+ }
695
+ /**
696
+ * Log error information
697
+ */
698
+ error(...args) {
699
+ const icon = '◍';
700
+ const message = `${icon} ${this.config.label} Error`;
701
+ console.error(`%c ${message}`, 'background: red; color: white; padding-right: 6px; border-radius: 2px;', ...args);
702
+ return {
703
+ success: false,
704
+ message: args.join(' ')
705
+ };
706
+ }
707
+ }
708
+
709
+ /**
710
+ * Generate a collision-resistant UUID
711
+ * Uses lukeed's UUID v4 implementation for consistent, fast UUID generation
712
+ */
713
+ function uuid() {
714
+ return uuid$1.v4();
715
+ }
716
+
717
+ /*
718
+ Minimal analytics integration manager (v0)
719
+ */
720
+ class Logger {
721
+ constructor(label = 'Analytics', debug = false) {
722
+ this.label = label;
723
+ this.debug = debug;
724
+ }
725
+ log(...args) {
726
+ if (this.debug) console.log(`[${this.label}]`, ...args);
727
+ }
728
+ warn(...args) {
729
+ console.warn(`[${this.label}]`, ...args);
730
+ }
731
+ error(...args) {
732
+ console.error(`[${this.label}]`, ...args);
733
+ }
734
+ }
735
+ class IntegrationManager {
736
+ constructor(config = {}) {
737
+ var _a, _b, _c, _d;
738
+ this.context = {};
739
+ this.integrations = new Map();
740
+ this.globalQueue = [];
741
+ this.config = {
742
+ debug: (_a = config.debug) !== null && _a !== void 0 ? _a : false,
743
+ maxQueueSize: (_b = config.maxQueueSize) !== null && _b !== void 0 ? _b : 200,
744
+ maxIntegrationWaitTime: (_c = config.maxIntegrationWaitTime) !== null && _c !== void 0 ? _c : 10000,
745
+ flushInterval: (_d = config.flushInterval) !== null && _d !== void 0 ? _d : 5000
746
+ };
747
+ this.logger = new AnalyticsLogger({
748
+ label: 'Saasco Integrations Debug',
749
+ debug: this.config.debug
750
+ });
751
+ this.initTime = Date.now();
752
+ this.currentEnvironment = typeof window !== 'undefined' ? 'client' : 'server';
753
+ this.setupPeriodicFlushing();
754
+ this.setupUnloadHandler();
755
+ }
756
+ /**
757
+ * Shallow-merge context to keep it simple + predictable in v0
758
+ */
759
+ setContext(next) {
760
+ this.context = Object.assign(Object.assign({}, this.context), next);
761
+ // Only log if we are setting something
762
+ if (Object.keys(this.context).length > 0) this.logger.log('context', this.context);
763
+ }
764
+ /**
765
+ * Register and init an integration. When init resolves, we mark it ready and
766
+ * immediately flush any queued events in FIFO order to *all* ready integrations.
767
+ */
768
+ registerIntegration(integration) {
769
+ return tslib.__awaiter(this, void 0, void 0, function* () {
770
+ if (this.integrations.has(integration.name)) {
771
+ this.logger.warn(`integration already registered: ${integration.name}`);
772
+ return;
773
+ }
774
+ // Check environment compatibility
775
+ const isCompatible = integration.environments.includes(this.currentEnvironment);
776
+ if (!isCompatible) {
777
+ this.logger.warn(`skipping ${integration.name}: requires ${integration.environments.join(' or ')} but running in ${this.currentEnvironment}`);
778
+ return;
779
+ }
780
+ this.logger.log(`registering ${integration.name} (supports: ${integration.environments.join(', ')}, current: ${this.currentEnvironment})`);
781
+ const state = {
782
+ integration,
783
+ status: 'idle'
784
+ };
785
+ this.integrations.set(integration.name, state);
786
+ if (integration.init) {
787
+ try {
788
+ state.status = 'loading';
789
+ this.logger.log(`init ${integration.name}`);
790
+ yield integration.init();
791
+ state.status = 'ready';
792
+ this.logger.log(`ready ${integration.name}`);
793
+ this.flush();
794
+ } catch (e) {
795
+ state.status = 'error';
796
+ this.logger.error(`failed to init ${integration.name}`, e);
797
+ }
798
+ } else {
799
+ state.status = 'ready';
800
+ this.logger.log(`ready (no init) ${integration.name}`);
801
+ this.flush();
802
+ }
803
+ });
804
+ }
805
+ identify(userId, traits) {
806
+ // update context so subsequent track() carries new userId
807
+ this.setContext({
808
+ userId
809
+ });
810
+ this.send({
811
+ type: 'identify',
812
+ name: undefined,
813
+ properties: traits
814
+ });
815
+ }
816
+ track(name, properties) {
817
+ this.send({
818
+ type: 'track',
819
+ name,
820
+ properties
821
+ });
822
+ }
823
+ /**
824
+ * Core send path: if at least one integration is ready -> deliver immediately
825
+ * Else enqueue (bounded FIFO)
826
+ */
827
+ send(partial) {
828
+ const envelope = {
829
+ id: uuid(),
830
+ type: partial.type,
831
+ timestamp: Date.now(),
832
+ name: partial.name,
833
+ properties: partial.properties,
834
+ context: Object.assign({}, this.context)
835
+ };
836
+ // If we're not ready, enqueue
837
+ if (!this.isReady()) {
838
+ if (this.globalQueue.length >= this.config.maxQueueSize) {
839
+ // drop oldest
840
+ this.globalQueue.shift();
841
+ this.logger.warn('queue full → dropped oldest');
842
+ }
843
+ this.globalQueue.push(envelope);
844
+ this.logger.log(`queued (${this.globalQueue.length})`, envelope);
845
+ return;
846
+ }
847
+ this.deliver(envelope);
848
+ }
849
+ deliver(evt) {
850
+ for (const {
851
+ integration,
852
+ status
853
+ } of this.integrations.values()) {
854
+ if (status !== 'ready') continue;
855
+ try {
856
+ if (evt.type === 'track' && integration.track && evt.name) {
857
+ integration.track(evt.name, evt.properties, evt.context);
858
+ } else if (evt.type === 'identify' && integration.identify) {
859
+ integration.identify(evt.context.distinctId, evt.properties, evt.context);
860
+ }
861
+ } catch (e) {
862
+ this.logger.error(`deliver error in ${integration.name}`, e);
863
+ }
864
+ }
865
+ }
866
+ /**
867
+ * Flush queued events FIFO once at least one integration is ready.
868
+ */
869
+ flush() {
870
+ // nothing to flush
871
+ if (this.globalQueue.length === 0) return;
872
+ // If we're not ready don't flush
873
+ if (!this.isReady()) return;
874
+ this.logger.log(`flushing ${this.globalQueue.length} queued events`);
875
+ const toSend = this.globalQueue;
876
+ this.globalQueue = [];
877
+ // deliver all events
878
+ toSend.forEach(evt => this.deliver(evt));
879
+ }
880
+ readyCount() {
881
+ let n = 0;
882
+ for (const s of this.integrations.values()) if (s.status === 'ready') n++;
883
+ return n;
884
+ }
885
+ isReady() {
886
+ // Return if integrations are not ready, or if we haven't waited the max integration wait time
887
+ if (this.readyCount() < this.integrations.size && Date.now() - this.initTime < this.config.maxIntegrationWaitTime) return false;
888
+ return true;
889
+ }
890
+ /**
891
+ * Setup periodic flushing if enabled
892
+ */
893
+ setupPeriodicFlushing() {
894
+ if (this.config.flushInterval <= 0) return;
895
+ this.flushTimer = setInterval(() => {
896
+ if (this.globalQueue.length > 0) {
897
+ this.logger.log('periodic flush triggered');
898
+ this.flush();
899
+ }
900
+ }, this.config.flushInterval);
901
+ this.logger.log(`periodic flushing enabled: ${this.config.flushInterval}ms`);
902
+ }
903
+ /**
904
+ * Setup page unload handler for client environment
905
+ */
906
+ setupUnloadHandler() {
907
+ if (this.currentEnvironment !== 'client' || typeof window === 'undefined') {
908
+ return;
909
+ }
910
+ this.unloadHandler = () => {
911
+ this.logger.log('page unloading, flushing remaining events');
912
+ this.flush();
913
+ };
914
+ // Use both beforeunload and pagehide for better coverage
915
+ window.addEventListener('beforeunload', this.unloadHandler);
916
+ window.addEventListener('pagehide', this.unloadHandler);
917
+ this.logger.log('page unload handlers registered');
918
+ }
919
+ /** Debug helpers */
920
+ getStats() {
921
+ return {
922
+ currentEnvironment: this.currentEnvironment,
923
+ integrations: [...this.integrations.values()].map(s => ({
924
+ name: s.integration.name,
925
+ status: s.status,
926
+ environments: s.integration.environments
927
+ })),
928
+ queueLength: this.globalQueue.length,
929
+ readyCount: this.readyCount(),
930
+ periodicFlushEnabled: this.config.flushInterval > 0,
931
+ flushInterval: this.config.flushInterval
932
+ };
933
+ }
934
+ }
935
+
557
936
  const isBrowser = typeof window !== 'undefined';
558
937
  const isServer = !isBrowser;
559
938
  const PREF = 'saasco-sdk';
@@ -688,8 +1067,9 @@ function setSessionId({
688
1067
  } = {
689
1068
  reset: false
690
1069
  }) {
691
- const sessionId = reset ? uuid.v4() : getSessionId() || uuid.v4();
1070
+ const sessionId = reset ? uuid() : getSessionId() || uuid();
692
1071
  storeData(`session-id`, sessionId, SESSION_DURATION);
1072
+ return sessionId;
693
1073
  }
694
1074
  function getAnonymousId() {
695
1075
  return retrieveData(`anonymous-id`);
@@ -699,7 +1079,7 @@ function setAnonymousId({
699
1079
  } = {
700
1080
  reset: false
701
1081
  }) {
702
- const anonymousId = reset ? uuid.v4() : getAnonymousId() || uuid.v4();
1082
+ const anonymousId = reset ? uuid() : getAnonymousId() || uuid();
703
1083
  storeData(`anonymous-id`, anonymousId, USER_DURATION);
704
1084
  return anonymousId;
705
1085
  }
@@ -746,13 +1126,19 @@ class Saasco {
746
1126
  * @param config.debug Whether to log debug information. Default is false.
747
1127
  * @param config.trackUrlParams Whether to track URL parameters. Default is true.
748
1128
  * @param config.trackHashChanges Whether to track hash changes. Default is true.
1129
+ * @param config.integrations Configuration for third-party integrations like Facebook Pixel.
749
1130
  */
750
1131
  constructor(config) {
751
1132
  this.config = config;
752
1133
  this.lastPageViewPath = '';
753
1134
  this.isInitialized = false;
1135
+ // Initialize logger
1136
+ this.logger = new AnalyticsLogger({
1137
+ debug: !!this.config.debug,
1138
+ label: 'Saasco Debug'
1139
+ });
754
1140
  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.");
1141
+ 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
1142
  return;
757
1143
  }
758
1144
  // default enabled to true
@@ -763,10 +1149,17 @@ class Saasco {
763
1149
  trackQueryParams: true,
764
1150
  trackHash: true
765
1151
  }, this.config.autoPageTracking);
1152
+ // Initialize integration manager
1153
+ this.integrationManager = new IntegrationManager({
1154
+ debug: this.config.debug,
1155
+ maxQueueSize: 1000
1156
+ });
1157
+ // Set initial context
1158
+ this.integrationManager.setContext({});
766
1159
  }
767
1160
  init() {
768
1161
  if (this.isInitialized) {
769
- this.log('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
1162
+ this.logger.log('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
770
1163
  return;
771
1164
  }
772
1165
  // Export Saasco to the window object for easy access and debugging
@@ -775,23 +1168,46 @@ class Saasco {
775
1168
  }
776
1169
  // Migrate existing localStorage data to cookies for cross-subdomain support
777
1170
  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.');
1171
+ this.logger.log('Saasco initialized', this.config);
1172
+ if (this.config.debug) this.logger.log('Debug mode active. This will log all events to the console.');
1173
+ if (!this.config.enabled) this.logger.log('Analytics is disabled. No requests will be sent to the server.');
1174
+ // Initialize integrations asynchronously
1175
+ this.initIntegrations().catch(error => {
1176
+ this.logger.log('Error initializing integrations:', error);
1177
+ });
781
1178
  this.initAutoPageTracking();
782
1179
  this.isInitialized = true;
783
1180
  }
784
1181
  disableDebug() {
785
- this.log('Debug mode deactivated.');
1182
+ this.logger.log('Debug mode deactivated.');
786
1183
  this.config.debug = false;
787
1184
  }
788
1185
  enableDebug() {
789
1186
  this.config.debug = true;
790
- this.log('Debug mode activated.');
1187
+ this.logger.log('Debug mode activated.');
1188
+ }
1189
+ /**
1190
+ * Initialize third-party integrations
1191
+ */
1192
+ initIntegrations() {
1193
+ return tslib.__awaiter(this, void 0, void 0, function* () {
1194
+ if (!this.config.integrations || this.config.integrations.length === 0) return;
1195
+ for (const integrationConfig of this.config.integrations) {
1196
+ try {
1197
+ if (integrationConfig.type === 'facebook-pixel') {
1198
+ this.logger.log('Registering Facebook Pixel integration with ID:', integrationConfig.config.pixelId);
1199
+ const fbIntegration = createFacebookPixelIntegration(integrationConfig.config);
1200
+ yield this.integrationManager.registerIntegration(fbIntegration);
1201
+ }
1202
+ } catch (error) {
1203
+ this.logger.log(`Failed to register ${integrationConfig.type} integration:`, error);
1204
+ }
1205
+ }
1206
+ });
791
1207
  }
792
1208
  track(actionOrPayload, propertiesOrNothing, contextOrNothing) {
793
1209
  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.");
1210
+ 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
1211
  return Promise.resolve(response);
796
1212
  }
797
1213
  setSessionId();
@@ -800,12 +1216,12 @@ class Saasco {
800
1216
  const hasPayload = typeof actionOrPayload === 'object';
801
1217
  // Must do payload on the server
802
1218
  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" } })');
1219
+ 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
1220
  return Promise.resolve(response);
805
1221
  }
806
1222
  // On server events we require a userId or an anonymousId
807
1223
  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.');
1224
+ 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
1225
  return Promise.resolve(response);
810
1226
  }
811
1227
  const action = hasAction ? actionOrPayload : actionOrPayload.event;
@@ -825,7 +1241,7 @@ class Saasco {
825
1241
  properties: properties || {}
826
1242
  });
827
1243
  const data = {
828
- id: uuid.v4(),
1244
+ id: uuid(),
829
1245
  timestamp: new Date().toISOString(),
830
1246
  action,
831
1247
  version,
@@ -837,6 +1253,9 @@ class Saasco {
837
1253
  source: isBrowser ? 'client' : 'server',
838
1254
  context: JSON.stringify(context || {})
839
1255
  };
1256
+ // Send event to integrations
1257
+ this.integrationManager.track(action, properties);
1258
+ // Send event to API
840
1259
  return this.doRequest('events', data);
841
1260
  }
842
1261
  /**
@@ -868,20 +1287,20 @@ class Saasco {
868
1287
  }
869
1288
  identify(distinctIdOrProperties, propertiesOrContext, contextOrNothing) {
870
1289
  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.");
1290
+ 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
1291
  return Promise.resolve(response);
873
1292
  }
874
1293
  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()}`;
1294
+ const distinctId = hasId ? distinctIdOrProperties === null || distinctIdOrProperties === void 0 ? void 0 : distinctIdOrProperties.toString() : `soft_${uuid()}`;
876
1295
  const properties = hasId ? propertiesOrContext : distinctIdOrProperties;
877
1296
  const context = hasId ? contextOrNothing : propertiesOrContext;
878
1297
  // When the user gets identified as null this will reset the users session and anonymous id
879
1298
  // This should only be called when distinctId is null and there is an existing userId.
880
1299
  // This means the user has logged out and we should reset the session and anonymous IDs
881
1300
  const userIdChangedToNull = distinctId === null && !!getUserId();
882
- if (userIdChangedToNull) this.log('User logged out');
1301
+ if (userIdChangedToNull) this.logger.log('User logged out');
883
1302
  const reset = userIdChangedToNull;
884
- setSessionId({
1303
+ const sessionId = setSessionId({
885
1304
  reset
886
1305
  });
887
1306
  const anonymousId = setAnonymousId({
@@ -889,13 +1308,21 @@ class Saasco {
889
1308
  });
890
1309
  // set the distinct Id to the userId
891
1310
  setUserId(distinctId);
1311
+ // Update integration manager context and send identify (IntegrationManager will filter by environment)
1312
+ this.integrationManager.setContext({
1313
+ distinctId,
1314
+ anonymousId,
1315
+ sessionId
1316
+ });
1317
+ this.integrationManager.identify(distinctId, properties);
892
1318
  // No distinctId provided so we don't track the user
893
1319
  if (!distinctId) return Promise.resolve({
894
1320
  success: true,
895
1321
  message: 'No distinctId provided'
896
1322
  });
1323
+ // Send identify to API
897
1324
  const data = {
898
- id: uuid.v4(),
1325
+ id: uuid(),
899
1326
  timestamp: new Date().toISOString(),
900
1327
  projectId: this.config.projectId,
901
1328
  distinctId,
@@ -929,9 +1356,9 @@ class Saasco {
929
1356
  const base = this.config.proxy || 'https://www.saasco.com/api/';
930
1357
  const url = `${base}${path}`;
931
1358
  if (data.action === 'Page View') {
932
- this.log('Page View', window.location.href, data);
1359
+ this.logger.log('Page View', window.location.href, data);
933
1360
  } else {
934
- this.log(data.action || path, data);
1361
+ this.logger.log(data.action || path, data);
935
1362
  }
936
1363
  // If analytics is disabled, don't send the request
937
1364
  if (this.config.enabled === false) return {
@@ -955,7 +1382,7 @@ class Saasco {
955
1382
  message: responseBody.message
956
1383
  };
957
1384
  } catch (error) {
958
- this.error(`\nError Message: "${error.message}"`, `\nPath: "/${path}"`, `\nRequest Data: ${JSON.stringify(data, null, 2)}`);
1385
+ this.logger.error(`\nError Message: "${error.message}"`, `\nPath: "/${path}"`, `\nRequest Data: ${JSON.stringify(data, null, 2)}`);
959
1386
  return {
960
1387
  success: false,
961
1388
  message: error.message
@@ -963,29 +1390,6 @@ class Saasco {
963
1390
  }
964
1391
  });
965
1392
  }
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
1393
  /**
990
1394
  * If autoPageTracking is enabled, this will automatically track page views
991
1395
  * It listens to url changes to track new pages every time the url changes
@@ -999,11 +1403,11 @@ class Saasco {
999
1403
  if (isServer) return console.warn('Saasco auto page tracking is only available in the browser');
1000
1404
  // Prevent intitializing auto page tracking more than once
1001
1405
  if (window.saascoAutoPageTrackingActive) {
1002
- this.log('Auto Page Tracking already enabled');
1406
+ this.logger.log('Auto Page Tracking already enabled');
1003
1407
  return;
1004
1408
  }
1005
1409
  window.saascoAutoPageTrackingActive = true;
1006
- this.log('Auto Page Tracking enabled');
1410
+ this.logger.log('Auto Page Tracking enabled');
1007
1411
  // Track initial page load
1008
1412
  this.page();
1009
1413
  // Listen for hash changes if hash tracking is enabled
@@ -1054,7 +1458,43 @@ const browserContextSchema = zod.z.object({
1054
1458
  $utmAdId: zod.z.string().nullable()
1055
1459
  });
1056
1460
 
1461
+ /**
1462
+ * Example integration that works on both client and server
1463
+ * Simply logs events to console
1464
+ */
1465
+ function createConsoleIntegration(config = {}) {
1466
+ const prefix = config.prefix || '[Analytics]';
1467
+ return {
1468
+ name: 'console',
1469
+ environments: ['client', 'server'],
1470
+ // This integration works on both client and server
1471
+ init: () => {
1472
+ console.log(`${prefix} Console integration initialized`);
1473
+ },
1474
+ track: (eventName, properties, context) => {
1475
+ console.log(`${prefix} Track:`, {
1476
+ event: eventName,
1477
+ properties,
1478
+ context
1479
+ });
1480
+ },
1481
+ identify: (userId, properties, context) => {
1482
+ console.log(`${prefix} Identify:`, {
1483
+ userId,
1484
+ properties,
1485
+ context
1486
+ });
1487
+ }
1488
+ };
1489
+ }
1490
+
1491
+ exports.IntegrationManager = IntegrationManager;
1492
+ exports.Logger = Logger;
1057
1493
  exports.Saasco = Saasco;
1058
1494
  exports.browserContextSchema = browserContextSchema;
1495
+ exports.createConsoleIntegration = createConsoleIntegration;
1496
+ exports.createFacebookPixelIntegration = createFacebookPixelIntegration;
1059
1497
  exports.getBrowserContext = getBrowserContext;
1498
+ exports.initializeFacebookPixel = initializeFacebookPixel;
1060
1499
  exports.timezones = timezones;
1500
+ exports.trackFacebookEvent = trackFacebookEvent;