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.esm.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { __awaiter } from 'tslib';
2
- import { v4 } from '@lukeed/uuid';
3
2
  import { isValid, parse } from 'psl';
3
+ import { v4 } from '@lukeed/uuid';
4
4
  import { z } from 'zod';
5
5
 
6
- var version = "0.1.31";
6
+ var version = "0.1.32";
7
7
 
8
8
  const timezones = {
9
9
  'Asia/Barnaul': 'RU',
@@ -550,6 +550,385 @@ function getBrowserContext() {
550
550
  };
551
551
  }
552
552
 
553
+ const isBrowser$1 = typeof window !== 'undefined';
554
+ const isServer$1 = !isBrowser$1;
555
+ /**
556
+ * Create a Facebook Pixel integration instance
557
+ */
558
+ function createFacebookPixelIntegration(config) {
559
+ let isPixelReady = false;
560
+ return {
561
+ name: 'facebook-pixel',
562
+ environments: ['client'],
563
+ init: () => __awaiter(this, void 0, void 0, function* () {
564
+ if (isServer$1) {
565
+ throw new Error('Facebook Pixel cannot be initialized on server');
566
+ }
567
+ // Check if Facebook Pixel is already loaded
568
+ if (window.fbq || window._fbq) {
569
+ console.warn('Facebook Pixel is already initialized');
570
+ isPixelReady = true;
571
+ return;
572
+ }
573
+ // Initialize Facebook Pixel
574
+ return new Promise((resolve, reject) => {
575
+ try {
576
+ (function (f, b, e, v, n, t, s) {
577
+ if (f.fbq) return;
578
+ n = f.fbq = function () {
579
+ n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
580
+ };
581
+ if (!f._fbq) f._fbq = n;
582
+ n.push = n;
583
+ n.loaded = !0;
584
+ n.version = '2.0';
585
+ n.queue = [];
586
+ t = b.createElement(e);
587
+ t.async = !0;
588
+ t.src = v;
589
+ // Add load event listener
590
+ t.onload = () => {
591
+ isPixelReady = true;
592
+ resolve();
593
+ };
594
+ t.onerror = () => {
595
+ reject(new Error('Failed to load Facebook Pixel script'));
596
+ };
597
+ s = b.getElementsByTagName(e)[0];
598
+ s.parentNode.insertBefore(t, s);
599
+ })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
600
+ // Initialize pixel (but don't track PageView automatically)
601
+ window.fbq('init', config.pixelId);
602
+ // If script loads synchronously, mark as ready
603
+ if (window.fbq && typeof window.fbq === 'function') {
604
+ isPixelReady = true;
605
+ resolve();
606
+ }
607
+ } catch (error) {
608
+ reject(error);
609
+ }
610
+ });
611
+ }),
612
+ track: (eventName, properties, context) => {
613
+ if (!isPixelReady || !window.fbq) return;
614
+ const fbEventName = getFacebookEventName(eventName, config.eventMapping);
615
+ // Standard Facebook events that support parameters
616
+ const eventsWithParams = ['Purchase', 'StartTrial', 'Subscribe'];
617
+ if (eventsWithParams.includes(fbEventName) && properties) {
618
+ // Extract standard Facebook parameters
619
+ const fbParams = {};
620
+ if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
621
+ if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
622
+ if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
623
+ window.fbq('track', fbEventName, fbParams);
624
+ } else {
625
+ window.fbq('track', fbEventName);
626
+ }
627
+ },
628
+ identify: (userId, properties, context) => {
629
+ if (!isPixelReady || !window.fbq) return;
630
+ // Facebook Pixel doesn't have explicit identify method
631
+ // We can use advanced matching or track as CompleteRegistration
632
+ if (userId && properties) {
633
+ // Track as registration/identification event
634
+ window.fbq('track', 'CompleteRegistration', properties);
635
+ }
636
+ }
637
+ };
638
+ }
639
+ function getFacebookEventName(eventName, eventMapping) {
640
+ if (!eventMapping) return eventName;
641
+ return eventMapping[eventName] || eventName;
642
+ }
643
+ // Legacy functions for backward compatibility (deprecated)
644
+ function initializeFacebookPixel(pixelId) {
645
+ var _a;
646
+ console.warn('initializeFacebookPixel is deprecated. Use createFacebookPixelIntegration instead.');
647
+ const integration = createFacebookPixelIntegration({
648
+ pixelId
649
+ });
650
+ (_a = integration.init) === null || _a === void 0 ? void 0 : _a.call(integration);
651
+ }
652
+ function trackFacebookEvent(eventName, properties, eventMapping) {
653
+ console.warn('trackFacebookEvent is deprecated. Use IntegrationManager instead.');
654
+ if (isServer$1 || !window.fbq) return;
655
+ const fbEventName = getFacebookEventName(eventName, eventMapping);
656
+ const eventsWithParams = ['Purchase', 'StartTrial', 'Subscribe'];
657
+ if (eventsWithParams.includes(fbEventName) && properties) {
658
+ const fbParams = {};
659
+ if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
660
+ if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
661
+ if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
662
+ window.fbq('track', fbEventName, fbParams);
663
+ } else {
664
+ window.fbq('track', fbEventName);
665
+ }
666
+ }
667
+
668
+ /* eslint-disable @typescript-eslint/no-explicit-any */
669
+ class AnalyticsLogger {
670
+ constructor(config) {
671
+ this.config = config;
672
+ }
673
+ /**
674
+ * Log debug information
675
+ */
676
+ log(...args) {
677
+ if (!this.config.debug) return;
678
+ const icon = '◍';
679
+ const message = `${icon} ${this.config.label}`;
680
+ console.info(`%c ${message}`, `background: #eee; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
681
+ }
682
+ /**
683
+ * Log warning information
684
+ */
685
+ warn(...args) {
686
+ if (!this.config.debug) return;
687
+ const icon = '◍';
688
+ const message = `${icon} ${this.config.label}`;
689
+ console.warn(`%c ${message}`, `background: #ffa500; color: #000; padding-right: 6px; border-radius: 2px;`, ...args);
690
+ }
691
+ /**
692
+ * Log error information
693
+ */
694
+ error(...args) {
695
+ const icon = '◍';
696
+ const message = `${icon} ${this.config.label} Error`;
697
+ console.error(`%c ${message}`, 'background: red; color: white; padding-right: 6px; border-radius: 2px;', ...args);
698
+ return {
699
+ success: false,
700
+ message: args.join(' ')
701
+ };
702
+ }
703
+ }
704
+
705
+ /**
706
+ * Generate a collision-resistant UUID
707
+ * Uses lukeed's UUID v4 implementation for consistent, fast UUID generation
708
+ */
709
+ function uuid() {
710
+ return v4();
711
+ }
712
+
713
+ /*
714
+ Minimal analytics integration manager (v0)
715
+ */
716
+ class Logger {
717
+ constructor(label = 'Analytics', debug = false) {
718
+ this.label = label;
719
+ this.debug = debug;
720
+ }
721
+ log(...args) {
722
+ if (this.debug) console.log(`[${this.label}]`, ...args);
723
+ }
724
+ warn(...args) {
725
+ console.warn(`[${this.label}]`, ...args);
726
+ }
727
+ error(...args) {
728
+ console.error(`[${this.label}]`, ...args);
729
+ }
730
+ }
731
+ class IntegrationManager {
732
+ constructor(config = {}) {
733
+ var _a, _b, _c, _d;
734
+ this.context = {};
735
+ this.integrations = new Map();
736
+ this.globalQueue = [];
737
+ this.config = {
738
+ debug: (_a = config.debug) !== null && _a !== void 0 ? _a : false,
739
+ maxQueueSize: (_b = config.maxQueueSize) !== null && _b !== void 0 ? _b : 200,
740
+ maxIntegrationWaitTime: (_c = config.maxIntegrationWaitTime) !== null && _c !== void 0 ? _c : 10000,
741
+ flushInterval: (_d = config.flushInterval) !== null && _d !== void 0 ? _d : 5000
742
+ };
743
+ this.logger = new AnalyticsLogger({
744
+ label: 'Saasco Integrations Debug',
745
+ debug: this.config.debug
746
+ });
747
+ this.initTime = Date.now();
748
+ this.currentEnvironment = typeof window !== 'undefined' ? 'client' : 'server';
749
+ this.setupPeriodicFlushing();
750
+ this.setupUnloadHandler();
751
+ }
752
+ /**
753
+ * Shallow-merge context to keep it simple + predictable in v0
754
+ */
755
+ setContext(next) {
756
+ this.context = Object.assign(Object.assign({}, this.context), next);
757
+ // Only log if we are setting something
758
+ if (Object.keys(this.context).length > 0) this.logger.log('context', this.context);
759
+ }
760
+ /**
761
+ * Register and init an integration. When init resolves, we mark it ready and
762
+ * immediately flush any queued events in FIFO order to *all* ready integrations.
763
+ */
764
+ registerIntegration(integration) {
765
+ return __awaiter(this, void 0, void 0, function* () {
766
+ if (this.integrations.has(integration.name)) {
767
+ this.logger.warn(`integration already registered: ${integration.name}`);
768
+ return;
769
+ }
770
+ // Check environment compatibility
771
+ const isCompatible = integration.environments.includes(this.currentEnvironment);
772
+ if (!isCompatible) {
773
+ this.logger.warn(`skipping ${integration.name}: requires ${integration.environments.join(' or ')} but running in ${this.currentEnvironment}`);
774
+ return;
775
+ }
776
+ this.logger.log(`registering ${integration.name} (supports: ${integration.environments.join(', ')}, current: ${this.currentEnvironment})`);
777
+ const state = {
778
+ integration,
779
+ status: 'idle'
780
+ };
781
+ this.integrations.set(integration.name, state);
782
+ if (integration.init) {
783
+ try {
784
+ state.status = 'loading';
785
+ this.logger.log(`init ${integration.name}`);
786
+ yield integration.init();
787
+ state.status = 'ready';
788
+ this.logger.log(`ready ${integration.name}`);
789
+ this.flush();
790
+ } catch (e) {
791
+ state.status = 'error';
792
+ this.logger.error(`failed to init ${integration.name}`, e);
793
+ }
794
+ } else {
795
+ state.status = 'ready';
796
+ this.logger.log(`ready (no init) ${integration.name}`);
797
+ this.flush();
798
+ }
799
+ });
800
+ }
801
+ identify(userId, traits) {
802
+ // update context so subsequent track() carries new userId
803
+ this.setContext({
804
+ userId
805
+ });
806
+ this.send({
807
+ type: 'identify',
808
+ name: undefined,
809
+ properties: traits
810
+ });
811
+ }
812
+ track(name, properties) {
813
+ this.send({
814
+ type: 'track',
815
+ name,
816
+ properties
817
+ });
818
+ }
819
+ /**
820
+ * Core send path: if at least one integration is ready -> deliver immediately
821
+ * Else enqueue (bounded FIFO)
822
+ */
823
+ send(partial) {
824
+ const envelope = {
825
+ id: uuid(),
826
+ type: partial.type,
827
+ timestamp: Date.now(),
828
+ name: partial.name,
829
+ properties: partial.properties,
830
+ context: Object.assign({}, this.context)
831
+ };
832
+ // If we're not ready, enqueue
833
+ if (!this.isReady()) {
834
+ if (this.globalQueue.length >= this.config.maxQueueSize) {
835
+ // drop oldest
836
+ this.globalQueue.shift();
837
+ this.logger.warn('queue full → dropped oldest');
838
+ }
839
+ this.globalQueue.push(envelope);
840
+ this.logger.log(`queued (${this.globalQueue.length})`, envelope);
841
+ return;
842
+ }
843
+ this.deliver(envelope);
844
+ }
845
+ deliver(evt) {
846
+ for (const {
847
+ integration,
848
+ status
849
+ } of this.integrations.values()) {
850
+ if (status !== 'ready') continue;
851
+ try {
852
+ if (evt.type === 'track' && integration.track && evt.name) {
853
+ integration.track(evt.name, evt.properties, evt.context);
854
+ } else if (evt.type === 'identify' && integration.identify) {
855
+ integration.identify(evt.context.distinctId, evt.properties, evt.context);
856
+ }
857
+ } catch (e) {
858
+ this.logger.error(`deliver error in ${integration.name}`, e);
859
+ }
860
+ }
861
+ }
862
+ /**
863
+ * Flush queued events FIFO once at least one integration is ready.
864
+ */
865
+ flush() {
866
+ // nothing to flush
867
+ if (this.globalQueue.length === 0) return;
868
+ // If we're not ready don't flush
869
+ if (!this.isReady()) return;
870
+ this.logger.log(`flushing ${this.globalQueue.length} queued events`);
871
+ const toSend = this.globalQueue;
872
+ this.globalQueue = [];
873
+ // deliver all events
874
+ toSend.forEach(evt => this.deliver(evt));
875
+ }
876
+ readyCount() {
877
+ let n = 0;
878
+ for (const s of this.integrations.values()) if (s.status === 'ready') n++;
879
+ return n;
880
+ }
881
+ isReady() {
882
+ // Return if integrations are not ready, or if we haven't waited the max integration wait time
883
+ if (this.readyCount() < this.integrations.size && Date.now() - this.initTime < this.config.maxIntegrationWaitTime) return false;
884
+ return true;
885
+ }
886
+ /**
887
+ * Setup periodic flushing if enabled
888
+ */
889
+ setupPeriodicFlushing() {
890
+ if (this.config.flushInterval <= 0) return;
891
+ this.flushTimer = setInterval(() => {
892
+ if (this.globalQueue.length > 0) {
893
+ this.logger.log('periodic flush triggered');
894
+ this.flush();
895
+ }
896
+ }, this.config.flushInterval);
897
+ this.logger.log(`periodic flushing enabled: ${this.config.flushInterval}ms`);
898
+ }
899
+ /**
900
+ * Setup page unload handler for client environment
901
+ */
902
+ setupUnloadHandler() {
903
+ if (this.currentEnvironment !== 'client' || typeof window === 'undefined') {
904
+ return;
905
+ }
906
+ this.unloadHandler = () => {
907
+ this.logger.log('page unloading, flushing remaining events');
908
+ this.flush();
909
+ };
910
+ // Use both beforeunload and pagehide for better coverage
911
+ window.addEventListener('beforeunload', this.unloadHandler);
912
+ window.addEventListener('pagehide', this.unloadHandler);
913
+ this.logger.log('page unload handlers registered');
914
+ }
915
+ /** Debug helpers */
916
+ getStats() {
917
+ return {
918
+ currentEnvironment: this.currentEnvironment,
919
+ integrations: [...this.integrations.values()].map(s => ({
920
+ name: s.integration.name,
921
+ status: s.status,
922
+ environments: s.integration.environments
923
+ })),
924
+ queueLength: this.globalQueue.length,
925
+ readyCount: this.readyCount(),
926
+ periodicFlushEnabled: this.config.flushInterval > 0,
927
+ flushInterval: this.config.flushInterval
928
+ };
929
+ }
930
+ }
931
+
553
932
  const isBrowser = typeof window !== 'undefined';
554
933
  const isServer = !isBrowser;
555
934
  const PREF = 'saasco-sdk';
@@ -684,8 +1063,9 @@ function setSessionId({
684
1063
  } = {
685
1064
  reset: false
686
1065
  }) {
687
- const sessionId = reset ? v4() : getSessionId() || v4();
1066
+ const sessionId = reset ? uuid() : getSessionId() || uuid();
688
1067
  storeData(`session-id`, sessionId, SESSION_DURATION);
1068
+ return sessionId;
689
1069
  }
690
1070
  function getAnonymousId() {
691
1071
  return retrieveData(`anonymous-id`);
@@ -695,7 +1075,7 @@ function setAnonymousId({
695
1075
  } = {
696
1076
  reset: false
697
1077
  }) {
698
- const anonymousId = reset ? v4() : getAnonymousId() || v4();
1078
+ const anonymousId = reset ? uuid() : getAnonymousId() || uuid();
699
1079
  storeData(`anonymous-id`, anonymousId, USER_DURATION);
700
1080
  return anonymousId;
701
1081
  }
@@ -742,13 +1122,19 @@ class Saasco {
742
1122
  * @param config.debug Whether to log debug information. Default is false.
743
1123
  * @param config.trackUrlParams Whether to track URL parameters. Default is true.
744
1124
  * @param config.trackHashChanges Whether to track hash changes. Default is true.
1125
+ * @param config.integrations Configuration for third-party integrations like Facebook Pixel.
745
1126
  */
746
1127
  constructor(config) {
747
1128
  this.config = config;
748
1129
  this.lastPageViewPath = '';
749
1130
  this.isInitialized = false;
1131
+ // Initialize logger
1132
+ this.logger = new AnalyticsLogger({
1133
+ debug: !!this.config.debug,
1134
+ label: 'Saasco Debug'
1135
+ });
750
1136
  if (!config.projectId) {
751
- this.error("Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
1137
+ 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.");
752
1138
  return;
753
1139
  }
754
1140
  // default enabled to true
@@ -759,10 +1145,17 @@ class Saasco {
759
1145
  trackQueryParams: true,
760
1146
  trackHash: true
761
1147
  }, this.config.autoPageTracking);
1148
+ // Initialize integration manager
1149
+ this.integrationManager = new IntegrationManager({
1150
+ debug: this.config.debug,
1151
+ maxQueueSize: 1000
1152
+ });
1153
+ // Set initial context
1154
+ this.integrationManager.setContext({});
762
1155
  }
763
1156
  init() {
764
1157
  if (this.isInitialized) {
765
- this.log('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
1158
+ this.logger.log('Saasco is already initialized. Please check your code to ensure that init() is not being called multiple times.');
766
1159
  return;
767
1160
  }
768
1161
  // Export Saasco to the window object for easy access and debugging
@@ -771,23 +1164,46 @@ class Saasco {
771
1164
  }
772
1165
  // Migrate existing localStorage data to cookies for cross-subdomain support
773
1166
  migrateFromLocalStorage();
774
- this.log('Saasco initialized', this.config);
775
- if (this.config.debug) this.log('Debug mode active. This will log all events to the console.');
776
- if (!this.config.enabled) this.log('Analytics is disabled. No requests will be sent to the server.');
1167
+ this.logger.log('Saasco initialized', this.config);
1168
+ if (this.config.debug) this.logger.log('Debug mode active. This will log all events to the console.');
1169
+ if (!this.config.enabled) this.logger.log('Analytics is disabled. No requests will be sent to the server.');
1170
+ // Initialize integrations asynchronously
1171
+ this.initIntegrations().catch(error => {
1172
+ this.logger.log('Error initializing integrations:', error);
1173
+ });
777
1174
  this.initAutoPageTracking();
778
1175
  this.isInitialized = true;
779
1176
  }
780
1177
  disableDebug() {
781
- this.log('Debug mode deactivated.');
1178
+ this.logger.log('Debug mode deactivated.');
782
1179
  this.config.debug = false;
783
1180
  }
784
1181
  enableDebug() {
785
1182
  this.config.debug = true;
786
- this.log('Debug mode activated.');
1183
+ this.logger.log('Debug mode activated.');
1184
+ }
1185
+ /**
1186
+ * Initialize third-party integrations
1187
+ */
1188
+ initIntegrations() {
1189
+ return __awaiter(this, void 0, void 0, function* () {
1190
+ if (!this.config.integrations || this.config.integrations.length === 0) return;
1191
+ for (const integrationConfig of this.config.integrations) {
1192
+ try {
1193
+ if (integrationConfig.type === 'facebook-pixel') {
1194
+ this.logger.log('Registering Facebook Pixel integration with ID:', integrationConfig.config.pixelId);
1195
+ const fbIntegration = createFacebookPixelIntegration(integrationConfig.config);
1196
+ yield this.integrationManager.registerIntegration(fbIntegration);
1197
+ }
1198
+ } catch (error) {
1199
+ this.logger.log(`Failed to register ${integrationConfig.type} integration:`, error);
1200
+ }
1201
+ }
1202
+ });
787
1203
  }
788
1204
  track(actionOrPayload, propertiesOrNothing, contextOrNothing) {
789
1205
  if (!this.config.projectId) {
790
- 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.");
1206
+ 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.");
791
1207
  return Promise.resolve(response);
792
1208
  }
793
1209
  setSessionId();
@@ -796,12 +1212,12 @@ class Saasco {
796
1212
  const hasPayload = typeof actionOrPayload === 'object';
797
1213
  // Must do payload on the server
798
1214
  if (isServer && hasAction) {
799
- 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" } })');
1215
+ 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" } })');
800
1216
  return Promise.resolve(response);
801
1217
  }
802
1218
  // On server events we require a userId or an anonymousId
803
1219
  if (isServer && hasPayload && !actionOrPayload.userId && !actionOrPayload.anonymousId) {
804
- 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.');
1220
+ 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.');
805
1221
  return Promise.resolve(response);
806
1222
  }
807
1223
  const action = hasAction ? actionOrPayload : actionOrPayload.event;
@@ -821,7 +1237,7 @@ class Saasco {
821
1237
  properties: properties || {}
822
1238
  });
823
1239
  const data = {
824
- id: v4(),
1240
+ id: uuid(),
825
1241
  timestamp: new Date().toISOString(),
826
1242
  action,
827
1243
  version,
@@ -833,6 +1249,9 @@ class Saasco {
833
1249
  source: isBrowser ? 'client' : 'server',
834
1250
  context: JSON.stringify(context || {})
835
1251
  };
1252
+ // Send event to integrations
1253
+ this.integrationManager.track(action, properties);
1254
+ // Send event to API
836
1255
  return this.doRequest('events', data);
837
1256
  }
838
1257
  /**
@@ -864,20 +1283,20 @@ class Saasco {
864
1283
  }
865
1284
  identify(distinctIdOrProperties, propertiesOrContext, contextOrNothing) {
866
1285
  if (!this.config.projectId) {
867
- 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.");
1286
+ 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.");
868
1287
  return Promise.resolve(response);
869
1288
  }
870
1289
  const hasId = typeof distinctIdOrProperties === 'string' || typeof distinctIdOrProperties === 'number' || distinctIdOrProperties === null;
871
- const distinctId = hasId ? distinctIdOrProperties === null || distinctIdOrProperties === void 0 ? void 0 : distinctIdOrProperties.toString() : `soft_${v4()}`;
1290
+ const distinctId = hasId ? distinctIdOrProperties === null || distinctIdOrProperties === void 0 ? void 0 : distinctIdOrProperties.toString() : `soft_${uuid()}`;
872
1291
  const properties = hasId ? propertiesOrContext : distinctIdOrProperties;
873
1292
  const context = hasId ? contextOrNothing : propertiesOrContext;
874
1293
  // When the user gets identified as null this will reset the users session and anonymous id
875
1294
  // This should only be called when distinctId is null and there is an existing userId.
876
1295
  // This means the user has logged out and we should reset the session and anonymous IDs
877
1296
  const userIdChangedToNull = distinctId === null && !!getUserId();
878
- if (userIdChangedToNull) this.log('User logged out');
1297
+ if (userIdChangedToNull) this.logger.log('User logged out');
879
1298
  const reset = userIdChangedToNull;
880
- setSessionId({
1299
+ const sessionId = setSessionId({
881
1300
  reset
882
1301
  });
883
1302
  const anonymousId = setAnonymousId({
@@ -885,13 +1304,21 @@ class Saasco {
885
1304
  });
886
1305
  // set the distinct Id to the userId
887
1306
  setUserId(distinctId);
1307
+ // Update integration manager context and send identify (IntegrationManager will filter by environment)
1308
+ this.integrationManager.setContext({
1309
+ distinctId,
1310
+ anonymousId,
1311
+ sessionId
1312
+ });
1313
+ this.integrationManager.identify(distinctId, properties);
888
1314
  // No distinctId provided so we don't track the user
889
1315
  if (!distinctId) return Promise.resolve({
890
1316
  success: true,
891
1317
  message: 'No distinctId provided'
892
1318
  });
1319
+ // Send identify to API
893
1320
  const data = {
894
- id: v4(),
1321
+ id: uuid(),
895
1322
  timestamp: new Date().toISOString(),
896
1323
  projectId: this.config.projectId,
897
1324
  distinctId,
@@ -925,9 +1352,9 @@ class Saasco {
925
1352
  const base = this.config.proxy || 'https://www.saasco.com/api/';
926
1353
  const url = `${base}${path}`;
927
1354
  if (data.action === 'Page View') {
928
- this.log('Page View', window.location.href, data);
1355
+ this.logger.log('Page View', window.location.href, data);
929
1356
  } else {
930
- this.log(data.action || path, data);
1357
+ this.logger.log(data.action || path, data);
931
1358
  }
932
1359
  // If analytics is disabled, don't send the request
933
1360
  if (this.config.enabled === false) return {
@@ -951,7 +1378,7 @@ class Saasco {
951
1378
  message: responseBody.message
952
1379
  };
953
1380
  } catch (error) {
954
- this.error(`\nError Message: "${error.message}"`, `\nPath: "/${path}"`, `\nRequest Data: ${JSON.stringify(data, null, 2)}`);
1381
+ this.logger.error(`\nError Message: "${error.message}"`, `\nPath: "/${path}"`, `\nRequest Data: ${JSON.stringify(data, null, 2)}`);
955
1382
  return {
956
1383
  success: false,
957
1384
  message: error.message
@@ -959,29 +1386,6 @@ class Saasco {
959
1386
  }
960
1387
  });
961
1388
  }
962
- /**
963
- * @param args Arguments to be logged
964
- */
965
- log(...args) {
966
- if (!this.config.debug) return;
967
- const message = '◍ Saasco Debug';
968
- console.info(
969
- // Message highlighted for easy finding
970
- `%c ${message}`, 'background: #eee; color: #000; padding: 2px 4px; border-radius: 2px;', ...args);
971
- }
972
- /**
973
- * @param args Arguments to be logged
974
- */
975
- error(...args) {
976
- const message = '◍ Saasco Error';
977
- console.error(
978
- // Message highlighted for easy finding
979
- `%c ${message}`, 'background: red; color: white; padding: 2px 4px; border-radius: 2px;', ...args);
980
- return {
981
- success: false,
982
- message: args.join(' ')
983
- };
984
- }
985
1389
  /**
986
1390
  * If autoPageTracking is enabled, this will automatically track page views
987
1391
  * It listens to url changes to track new pages every time the url changes
@@ -995,11 +1399,11 @@ class Saasco {
995
1399
  if (isServer) return console.warn('Saasco auto page tracking is only available in the browser');
996
1400
  // Prevent intitializing auto page tracking more than once
997
1401
  if (window.saascoAutoPageTrackingActive) {
998
- this.log('Auto Page Tracking already enabled');
1402
+ this.logger.log('Auto Page Tracking already enabled');
999
1403
  return;
1000
1404
  }
1001
1405
  window.saascoAutoPageTrackingActive = true;
1002
- this.log('Auto Page Tracking enabled');
1406
+ this.logger.log('Auto Page Tracking enabled');
1003
1407
  // Track initial page load
1004
1408
  this.page();
1005
1409
  // Listen for hash changes if hash tracking is enabled
@@ -1050,4 +1454,34 @@ const browserContextSchema = z.object({
1050
1454
  $utmAdId: z.string().nullable()
1051
1455
  });
1052
1456
 
1053
- export { Saasco, browserContextSchema, getBrowserContext, timezones };
1457
+ /**
1458
+ * Example integration that works on both client and server
1459
+ * Simply logs events to console
1460
+ */
1461
+ function createConsoleIntegration(config = {}) {
1462
+ const prefix = config.prefix || '[Analytics]';
1463
+ return {
1464
+ name: 'console',
1465
+ environments: ['client', 'server'],
1466
+ // This integration works on both client and server
1467
+ init: () => {
1468
+ console.log(`${prefix} Console integration initialized`);
1469
+ },
1470
+ track: (eventName, properties, context) => {
1471
+ console.log(`${prefix} Track:`, {
1472
+ event: eventName,
1473
+ properties,
1474
+ context
1475
+ });
1476
+ },
1477
+ identify: (userId, properties, context) => {
1478
+ console.log(`${prefix} Identify:`, {
1479
+ userId,
1480
+ properties,
1481
+ context
1482
+ });
1483
+ }
1484
+ };
1485
+ }
1486
+
1487
+ export { IntegrationManager, Logger, Saasco, browserContextSchema, createConsoleIntegration, createFacebookPixelIntegration, getBrowserContext, initializeFacebookPixel, timezones, trackFacebookEvent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasco-sdk",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "dependencies": {
5
5
  "tslib": "^2.3.0",
6
6
  "@lukeed/uuid": "^2.0.1",