saasco-sdk 0.1.32 → 0.1.34

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
@@ -7,7 +7,7 @@ var psl = require('psl');
7
7
  var uuid$1 = require('@lukeed/uuid');
8
8
  var zod = require('zod');
9
9
 
10
- var version = "0.1.32";
10
+ var version = "0.1.34";
11
11
 
12
12
  const timezones = {
13
13
  'Asia/Barnaul': 'RU',
@@ -566,7 +566,9 @@ function createFacebookPixelIntegration(config) {
566
566
  environments: ['client'],
567
567
  init: () => tslib.__awaiter(this, void 0, void 0, function* () {
568
568
  if (isServer$1) {
569
- throw new Error('Facebook Pixel cannot be initialized on server');
569
+ // On server, Facebook Pixel is a no-op but doesn't throw
570
+ // This allows the integration to be registered but remain inactive
571
+ return;
570
572
  }
571
573
  // Check if Facebook Pixel is already loaded
572
574
  if (window.fbq || window._fbq) {
@@ -624,23 +626,45 @@ function createFacebookPixelIntegration(config) {
624
626
  if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
625
627
  if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
626
628
  if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
627
- window.fbq('track', fbEventName, fbParams);
629
+ window.fbq('track', fbEventName, Object.assign(Object.assign({}, properties), fbParams));
628
630
  } else {
629
631
  window.fbq('track', fbEventName);
630
632
  }
631
633
  },
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);
634
+ identify: (userId, properties, context) => tslib.__awaiter(this, void 0, void 0, function* () {
635
+ try {
636
+ if (typeof window !== 'undefined' && window.fbq) {
637
+ const amValues = coerceMetaConversionsAmValues(Object.assign(Object.assign({}, properties || {}), userId ? {
638
+ external_id: userId
639
+ } : {}));
640
+ const am = {};
641
+ if (amValues.external_id) am['external_id'] = amValues.external_id;
642
+ if (amValues.em) am['em'] = yield sha256Hex(amValues.em);
643
+ if (amValues.fn) am['fn'] = amValues.fn;
644
+ if (amValues.ln) am['ln'] = amValues.ln;
645
+ if (amValues.ph) am['ph'] = amValues.ph;
646
+ if (amValues.ge) am['ge'] = amValues.ge;
647
+ if (amValues.db) am['db'] = amValues.db;
648
+ if (amValues.ct) am['ct'] = amValues.ct;
649
+ if (amValues.st) am['st'] = amValues.st;
650
+ if (amValues.zp) am['zp'] = amValues.zp;
651
+ if (amValues.country) am['country'] = amValues.country;
652
+ if (Object.keys(am).length > 0) {
653
+ window.fbq('init', config.pixelId, am);
654
+ }
655
+ }
656
+ } catch (error) {
657
+ console.error('Error identifying user in Meta Pixel:', error);
639
658
  }
640
- }
659
+ })
641
660
  };
642
661
  }
643
662
  function getFacebookEventName(eventName, eventMapping) {
663
+ // Convert default saasco page view event by default
664
+ if (eventName === 'Page View') {
665
+ const mappedEvent = 'ViewContent';
666
+ return mappedEvent;
667
+ }
644
668
  if (!eventMapping) return eventName;
645
669
  return eventMapping[eventName] || eventName;
646
670
  }
@@ -668,6 +692,146 @@ function trackFacebookEvent(eventName, properties, eventMapping) {
668
692
  window.fbq('track', fbEventName);
669
693
  }
670
694
  }
695
+ function coerceMetaConversionsAmValues(properties) {
696
+ const coerce = [{
697
+ key: 'em',
698
+ coerceFrom: ['email', 'user_email', 'userEmail', 'email_address', 'emailAddress', 'e_mail', 'E_Mail', 'mail', 'Mail', 'contact_email', 'contactEmail', 'primary_email', 'primaryEmail'],
699
+ transform: value => {
700
+ if (typeof value !== 'string') return undefined;
701
+ const normalized = value.trim().toLowerCase();
702
+ return normalized || undefined;
703
+ }
704
+ }, {
705
+ key: 'fn',
706
+ coerceFrom: ['first_name', 'firstName', 'firstname', 'given_name', 'givenName', 'user_first_name', 'userFirstName', 'name_first', 'nameFirst', 'f_name', 'fName'],
707
+ transform: value => {
708
+ if (typeof value !== 'string') return undefined;
709
+ const normalized = value.trim().toLowerCase();
710
+ return normalized || undefined;
711
+ }
712
+ }, {
713
+ key: 'ln',
714
+ coerceFrom: ['last_name', 'lastName', 'lastname', 'surname', 'user_last_name', 'userLastName', 'name_last', 'nameLast', 'l_name', 'lName'],
715
+ transform: value => {
716
+ if (typeof value !== 'string') return undefined;
717
+ const normalized = value.trim().toLowerCase();
718
+ return normalized || undefined;
719
+ }
720
+ }, {
721
+ key: 'ph',
722
+ coerceFrom: ['phone', 'phone_number', 'phoneNumber', 'mobile', 'mobile_number', 'mobileNumber', 'contact_number', 'contactNumber', 'tel', 'telephone'],
723
+ transform: value => {
724
+ if (typeof value !== 'string' && typeof value !== 'number') return undefined;
725
+ const phoneStr = String(value).replace(/\D/g, '');
726
+ return phoneStr || undefined;
727
+ }
728
+ }, {
729
+ key: 'external_id',
730
+ coerceFrom: ['external_id', 'externalId', 'user_id', 'userId', 'id', 'distinctId', 'distinct_id', 'customer_id', 'customerId', 'member_id', 'memberId', 'loyalty_id', 'loyaltyId'],
731
+ transform: value => {
732
+ if (value == null) return undefined;
733
+ return String(value) || undefined;
734
+ }
735
+ }, {
736
+ key: 'ge',
737
+ coerceFrom: ['gender', 'user_gender', 'userGender', 'sex', 'user_sex', 'userSex'],
738
+ transform: value => {
739
+ if (typeof value !== 'string') return undefined;
740
+ const normalized = value.trim().toLowerCase();
741
+ if (normalized === 'female' || normalized === 'f') return 'f';
742
+ if (normalized === 'male' || normalized === 'm') return 'm';
743
+ return undefined;
744
+ }
745
+ }, {
746
+ key: 'db',
747
+ coerceFrom: ['birthday', 'birth_date', 'birthDate', 'date_of_birth', 'dateOfBirth', 'dob', 'Dob', 'birth_day', 'birthDay'],
748
+ transform: value => {
749
+ if (!value) return undefined;
750
+ let dateStr;
751
+ if (value instanceof Date) {
752
+ dateStr = value.toISOString().split('T')[0];
753
+ } else {
754
+ dateStr = String(value);
755
+ }
756
+ const dateMatch = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/);
757
+ if (dateMatch) {
758
+ const [, year, month, day] = dateMatch;
759
+ return `${year}${month}${day}`;
760
+ }
761
+ const numericMatch = dateStr.replace(/\D/g, '');
762
+ if (numericMatch.length === 8) {
763
+ return numericMatch;
764
+ }
765
+ return undefined;
766
+ }
767
+ }, {
768
+ key: 'ct',
769
+ coerceFrom: ['city', 'user_city', 'userCity', 'location_city', 'locationCity', 'address_city', 'addressCity'],
770
+ transform: value => {
771
+ if (typeof value !== 'string') return undefined;
772
+ const normalized = value.trim().toLowerCase().replace(/\s+/g, '');
773
+ return normalized || undefined;
774
+ }
775
+ }, {
776
+ key: 'st',
777
+ coerceFrom: ['state', 'user_state', 'userState', 'province', 'user_province', 'userProvince', 'region', 'user_region', 'userRegion', 'location_state', 'locationState', 'address_state', 'addressState'],
778
+ transform: value => {
779
+ if (typeof value !== 'string') return undefined;
780
+ const normalized = value.trim().toLowerCase();
781
+ if (normalized.length === 2) return normalized;
782
+ return undefined;
783
+ }
784
+ }, {
785
+ key: 'zp',
786
+ coerceFrom: ['zip', 'zipcode', 'zip_code', 'postal_code', 'postalCode', 'postcode', 'user_zip', 'userZip', 'address_zip', 'addressZip'],
787
+ transform: value => {
788
+ if (typeof value !== 'string' && typeof value !== 'number') return undefined;
789
+ return String(value).trim() || undefined;
790
+ }
791
+ }, {
792
+ key: 'country',
793
+ coerceFrom: ['country', 'country_code', 'countryCode', 'user_country', 'userCountry', 'location_country', 'locationCountry', 'address_country', 'addressCountry'],
794
+ transform: value => {
795
+ if (typeof value !== 'string') return undefined;
796
+ const normalized = value.trim().toLowerCase();
797
+ if (normalized.length === 2) return normalized;
798
+ return undefined;
799
+ }
800
+ }];
801
+ const result = {};
802
+ for (const {
803
+ key,
804
+ coerceFrom,
805
+ transform
806
+ } of coerce) {
807
+ const foundValue = findFirstProperty(properties, coerceFrom);
808
+ if (foundValue !== null && transform) {
809
+ const transformedValue = transform(foundValue);
810
+ if (transformedValue) {
811
+ result[key] = transformedValue;
812
+ }
813
+ } else if (foundValue !== null && typeof foundValue === 'string') {
814
+ result[key] = foundValue;
815
+ }
816
+ }
817
+ return result;
818
+ }
819
+ function findFirstProperty(properties, keys) {
820
+ for (const key of keys) {
821
+ if (properties[key] !== undefined) {
822
+ return properties[key];
823
+ }
824
+ }
825
+ return null;
826
+ }
827
+ const toHex = buf => [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('');
828
+ function sha256Hex(input) {
829
+ return tslib.__awaiter(this, void 0, void 0, function* () {
830
+ const enc = new TextEncoder().encode(input);
831
+ const digest = yield crypto.subtle.digest('SHA-256', enc);
832
+ return toHex(digest);
833
+ });
834
+ }
671
835
 
672
836
  /* eslint-disable @typescript-eslint/no-explicit-any */
673
837
  class AnalyticsLogger {
@@ -847,21 +1011,23 @@ class IntegrationManager {
847
1011
  this.deliver(envelope);
848
1012
  }
849
1013
  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);
1014
+ return tslib.__awaiter(this, void 0, void 0, function* () {
1015
+ for (const {
1016
+ integration,
1017
+ status
1018
+ } of this.integrations.values()) {
1019
+ if (status !== 'ready') continue;
1020
+ try {
1021
+ if (evt.type === 'track' && integration.track && evt.name) {
1022
+ yield integration.track(evt.name, evt.properties, evt.context);
1023
+ } else if (evt.type === 'identify' && integration.identify) {
1024
+ yield integration.identify(evt.context.distinctId, evt.properties, evt.context);
1025
+ }
1026
+ } catch (e) {
1027
+ this.logger.error(`deliver error in ${integration.name} (${evt.type})`, e);
860
1028
  }
861
- } catch (e) {
862
- this.logger.error(`deliver error in ${integration.name}`, e);
863
1029
  }
864
- }
1030
+ });
865
1031
  }
866
1032
  /**
867
1033
  * Flush queued events FIFO once at least one integration is ready.
@@ -1156,6 +1322,10 @@ class Saasco {
1156
1322
  });
1157
1323
  // Set initial context
1158
1324
  this.integrationManager.setContext({});
1325
+ // Initialize integrations immediately (works on both client and server)
1326
+ this.initIntegrations().catch(error => {
1327
+ this.logger.log('Error initializing integrations:', error);
1328
+ });
1159
1329
  }
1160
1330
  init() {
1161
1331
  if (this.isInitialized) {
@@ -1171,10 +1341,6 @@ class Saasco {
1171
1341
  this.logger.log('Saasco initialized', this.config);
1172
1342
  if (this.config.debug) this.logger.log('Debug mode active. This will log all events to the console.');
1173
1343
  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
- });
1178
1344
  this.initAutoPageTracking();
1179
1345
  this.isInitialized = true;
1180
1346
  }
@@ -1458,41 +1624,10 @@ const browserContextSchema = zod.z.object({
1458
1624
  $utmAdId: zod.z.string().nullable()
1459
1625
  });
1460
1626
 
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
1627
  exports.IntegrationManager = IntegrationManager;
1492
1628
  exports.Logger = Logger;
1493
1629
  exports.Saasco = Saasco;
1494
1630
  exports.browserContextSchema = browserContextSchema;
1495
- exports.createConsoleIntegration = createConsoleIntegration;
1496
1631
  exports.createFacebookPixelIntegration = createFacebookPixelIntegration;
1497
1632
  exports.getBrowserContext = getBrowserContext;
1498
1633
  exports.initializeFacebookPixel = initializeFacebookPixel;
package/index.esm.js CHANGED
@@ -3,7 +3,7 @@ import { isValid, parse } from 'psl';
3
3
  import { v4 } from '@lukeed/uuid';
4
4
  import { z } from 'zod';
5
5
 
6
- var version = "0.1.32";
6
+ var version = "0.1.34";
7
7
 
8
8
  const timezones = {
9
9
  'Asia/Barnaul': 'RU',
@@ -562,7 +562,9 @@ function createFacebookPixelIntegration(config) {
562
562
  environments: ['client'],
563
563
  init: () => __awaiter(this, void 0, void 0, function* () {
564
564
  if (isServer$1) {
565
- throw new Error('Facebook Pixel cannot be initialized on server');
565
+ // On server, Facebook Pixel is a no-op but doesn't throw
566
+ // This allows the integration to be registered but remain inactive
567
+ return;
566
568
  }
567
569
  // Check if Facebook Pixel is already loaded
568
570
  if (window.fbq || window._fbq) {
@@ -620,23 +622,45 @@ function createFacebookPixelIntegration(config) {
620
622
  if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
621
623
  if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
622
624
  if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
623
- window.fbq('track', fbEventName, fbParams);
625
+ window.fbq('track', fbEventName, Object.assign(Object.assign({}, properties), fbParams));
624
626
  } else {
625
627
  window.fbq('track', fbEventName);
626
628
  }
627
629
  },
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);
630
+ identify: (userId, properties, context) => __awaiter(this, void 0, void 0, function* () {
631
+ try {
632
+ if (typeof window !== 'undefined' && window.fbq) {
633
+ const amValues = coerceMetaConversionsAmValues(Object.assign(Object.assign({}, properties || {}), userId ? {
634
+ external_id: userId
635
+ } : {}));
636
+ const am = {};
637
+ if (amValues.external_id) am['external_id'] = amValues.external_id;
638
+ if (amValues.em) am['em'] = yield sha256Hex(amValues.em);
639
+ if (amValues.fn) am['fn'] = amValues.fn;
640
+ if (amValues.ln) am['ln'] = amValues.ln;
641
+ if (amValues.ph) am['ph'] = amValues.ph;
642
+ if (amValues.ge) am['ge'] = amValues.ge;
643
+ if (amValues.db) am['db'] = amValues.db;
644
+ if (amValues.ct) am['ct'] = amValues.ct;
645
+ if (amValues.st) am['st'] = amValues.st;
646
+ if (amValues.zp) am['zp'] = amValues.zp;
647
+ if (amValues.country) am['country'] = amValues.country;
648
+ if (Object.keys(am).length > 0) {
649
+ window.fbq('init', config.pixelId, am);
650
+ }
651
+ }
652
+ } catch (error) {
653
+ console.error('Error identifying user in Meta Pixel:', error);
635
654
  }
636
- }
655
+ })
637
656
  };
638
657
  }
639
658
  function getFacebookEventName(eventName, eventMapping) {
659
+ // Convert default saasco page view event by default
660
+ if (eventName === 'Page View') {
661
+ const mappedEvent = 'ViewContent';
662
+ return mappedEvent;
663
+ }
640
664
  if (!eventMapping) return eventName;
641
665
  return eventMapping[eventName] || eventName;
642
666
  }
@@ -664,6 +688,146 @@ function trackFacebookEvent(eventName, properties, eventMapping) {
664
688
  window.fbq('track', fbEventName);
665
689
  }
666
690
  }
691
+ function coerceMetaConversionsAmValues(properties) {
692
+ const coerce = [{
693
+ key: 'em',
694
+ coerceFrom: ['email', 'user_email', 'userEmail', 'email_address', 'emailAddress', 'e_mail', 'E_Mail', 'mail', 'Mail', 'contact_email', 'contactEmail', 'primary_email', 'primaryEmail'],
695
+ transform: value => {
696
+ if (typeof value !== 'string') return undefined;
697
+ const normalized = value.trim().toLowerCase();
698
+ return normalized || undefined;
699
+ }
700
+ }, {
701
+ key: 'fn',
702
+ coerceFrom: ['first_name', 'firstName', 'firstname', 'given_name', 'givenName', 'user_first_name', 'userFirstName', 'name_first', 'nameFirst', 'f_name', 'fName'],
703
+ transform: value => {
704
+ if (typeof value !== 'string') return undefined;
705
+ const normalized = value.trim().toLowerCase();
706
+ return normalized || undefined;
707
+ }
708
+ }, {
709
+ key: 'ln',
710
+ coerceFrom: ['last_name', 'lastName', 'lastname', 'surname', 'user_last_name', 'userLastName', 'name_last', 'nameLast', 'l_name', 'lName'],
711
+ transform: value => {
712
+ if (typeof value !== 'string') return undefined;
713
+ const normalized = value.trim().toLowerCase();
714
+ return normalized || undefined;
715
+ }
716
+ }, {
717
+ key: 'ph',
718
+ coerceFrom: ['phone', 'phone_number', 'phoneNumber', 'mobile', 'mobile_number', 'mobileNumber', 'contact_number', 'contactNumber', 'tel', 'telephone'],
719
+ transform: value => {
720
+ if (typeof value !== 'string' && typeof value !== 'number') return undefined;
721
+ const phoneStr = String(value).replace(/\D/g, '');
722
+ return phoneStr || undefined;
723
+ }
724
+ }, {
725
+ key: 'external_id',
726
+ coerceFrom: ['external_id', 'externalId', 'user_id', 'userId', 'id', 'distinctId', 'distinct_id', 'customer_id', 'customerId', 'member_id', 'memberId', 'loyalty_id', 'loyaltyId'],
727
+ transform: value => {
728
+ if (value == null) return undefined;
729
+ return String(value) || undefined;
730
+ }
731
+ }, {
732
+ key: 'ge',
733
+ coerceFrom: ['gender', 'user_gender', 'userGender', 'sex', 'user_sex', 'userSex'],
734
+ transform: value => {
735
+ if (typeof value !== 'string') return undefined;
736
+ const normalized = value.trim().toLowerCase();
737
+ if (normalized === 'female' || normalized === 'f') return 'f';
738
+ if (normalized === 'male' || normalized === 'm') return 'm';
739
+ return undefined;
740
+ }
741
+ }, {
742
+ key: 'db',
743
+ coerceFrom: ['birthday', 'birth_date', 'birthDate', 'date_of_birth', 'dateOfBirth', 'dob', 'Dob', 'birth_day', 'birthDay'],
744
+ transform: value => {
745
+ if (!value) return undefined;
746
+ let dateStr;
747
+ if (value instanceof Date) {
748
+ dateStr = value.toISOString().split('T')[0];
749
+ } else {
750
+ dateStr = String(value);
751
+ }
752
+ const dateMatch = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/);
753
+ if (dateMatch) {
754
+ const [, year, month, day] = dateMatch;
755
+ return `${year}${month}${day}`;
756
+ }
757
+ const numericMatch = dateStr.replace(/\D/g, '');
758
+ if (numericMatch.length === 8) {
759
+ return numericMatch;
760
+ }
761
+ return undefined;
762
+ }
763
+ }, {
764
+ key: 'ct',
765
+ coerceFrom: ['city', 'user_city', 'userCity', 'location_city', 'locationCity', 'address_city', 'addressCity'],
766
+ transform: value => {
767
+ if (typeof value !== 'string') return undefined;
768
+ const normalized = value.trim().toLowerCase().replace(/\s+/g, '');
769
+ return normalized || undefined;
770
+ }
771
+ }, {
772
+ key: 'st',
773
+ coerceFrom: ['state', 'user_state', 'userState', 'province', 'user_province', 'userProvince', 'region', 'user_region', 'userRegion', 'location_state', 'locationState', 'address_state', 'addressState'],
774
+ transform: value => {
775
+ if (typeof value !== 'string') return undefined;
776
+ const normalized = value.trim().toLowerCase();
777
+ if (normalized.length === 2) return normalized;
778
+ return undefined;
779
+ }
780
+ }, {
781
+ key: 'zp',
782
+ coerceFrom: ['zip', 'zipcode', 'zip_code', 'postal_code', 'postalCode', 'postcode', 'user_zip', 'userZip', 'address_zip', 'addressZip'],
783
+ transform: value => {
784
+ if (typeof value !== 'string' && typeof value !== 'number') return undefined;
785
+ return String(value).trim() || undefined;
786
+ }
787
+ }, {
788
+ key: 'country',
789
+ coerceFrom: ['country', 'country_code', 'countryCode', 'user_country', 'userCountry', 'location_country', 'locationCountry', 'address_country', 'addressCountry'],
790
+ transform: value => {
791
+ if (typeof value !== 'string') return undefined;
792
+ const normalized = value.trim().toLowerCase();
793
+ if (normalized.length === 2) return normalized;
794
+ return undefined;
795
+ }
796
+ }];
797
+ const result = {};
798
+ for (const {
799
+ key,
800
+ coerceFrom,
801
+ transform
802
+ } of coerce) {
803
+ const foundValue = findFirstProperty(properties, coerceFrom);
804
+ if (foundValue !== null && transform) {
805
+ const transformedValue = transform(foundValue);
806
+ if (transformedValue) {
807
+ result[key] = transformedValue;
808
+ }
809
+ } else if (foundValue !== null && typeof foundValue === 'string') {
810
+ result[key] = foundValue;
811
+ }
812
+ }
813
+ return result;
814
+ }
815
+ function findFirstProperty(properties, keys) {
816
+ for (const key of keys) {
817
+ if (properties[key] !== undefined) {
818
+ return properties[key];
819
+ }
820
+ }
821
+ return null;
822
+ }
823
+ const toHex = buf => [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('');
824
+ function sha256Hex(input) {
825
+ return __awaiter(this, void 0, void 0, function* () {
826
+ const enc = new TextEncoder().encode(input);
827
+ const digest = yield crypto.subtle.digest('SHA-256', enc);
828
+ return toHex(digest);
829
+ });
830
+ }
667
831
 
668
832
  /* eslint-disable @typescript-eslint/no-explicit-any */
669
833
  class AnalyticsLogger {
@@ -843,21 +1007,23 @@ class IntegrationManager {
843
1007
  this.deliver(envelope);
844
1008
  }
845
1009
  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);
1010
+ return __awaiter(this, void 0, void 0, function* () {
1011
+ for (const {
1012
+ integration,
1013
+ status
1014
+ } of this.integrations.values()) {
1015
+ if (status !== 'ready') continue;
1016
+ try {
1017
+ if (evt.type === 'track' && integration.track && evt.name) {
1018
+ yield integration.track(evt.name, evt.properties, evt.context);
1019
+ } else if (evt.type === 'identify' && integration.identify) {
1020
+ yield integration.identify(evt.context.distinctId, evt.properties, evt.context);
1021
+ }
1022
+ } catch (e) {
1023
+ this.logger.error(`deliver error in ${integration.name} (${evt.type})`, e);
856
1024
  }
857
- } catch (e) {
858
- this.logger.error(`deliver error in ${integration.name}`, e);
859
1025
  }
860
- }
1026
+ });
861
1027
  }
862
1028
  /**
863
1029
  * Flush queued events FIFO once at least one integration is ready.
@@ -1152,6 +1318,10 @@ class Saasco {
1152
1318
  });
1153
1319
  // Set initial context
1154
1320
  this.integrationManager.setContext({});
1321
+ // Initialize integrations immediately (works on both client and server)
1322
+ this.initIntegrations().catch(error => {
1323
+ this.logger.log('Error initializing integrations:', error);
1324
+ });
1155
1325
  }
1156
1326
  init() {
1157
1327
  if (this.isInitialized) {
@@ -1167,10 +1337,6 @@ class Saasco {
1167
1337
  this.logger.log('Saasco initialized', this.config);
1168
1338
  if (this.config.debug) this.logger.log('Debug mode active. This will log all events to the console.');
1169
1339
  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
- });
1174
1340
  this.initAutoPageTracking();
1175
1341
  this.isInitialized = true;
1176
1342
  }
@@ -1454,34 +1620,4 @@ const browserContextSchema = z.object({
1454
1620
  $utmAdId: z.string().nullable()
1455
1621
  });
1456
1622
 
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 };
1623
+ export { IntegrationManager, Logger, Saasco, browserContextSchema, createFacebookPixelIntegration, getBrowserContext, initializeFacebookPixel, timezones, trackFacebookEvent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasco-sdk",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "dependencies": {
5
5
  "tslib": "^2.3.0",
6
6
  "@lukeed/uuid": "^2.0.1",
@@ -25,8 +25,9 @@ type DoRequestResponse = {
25
25
  success: boolean;
26
26
  message: string;
27
27
  };
28
- export type IntegrationConfig = {
28
+ export type IntegrationConfigBase = {
29
29
  type: string;
30
+ config: Record<string, any>;
30
31
  };
31
32
  export type IntegrationsConfig = FacebookPixelIntegrationConfig[];
32
33
  export declare class Saasco {
@@ -1,4 +1,4 @@
1
- import { IntegrationConfig } from '../analytics';
1
+ import { IntegrationConfigBase } from '../analytics';
2
2
  import { Integration } from './integration-manager';
3
3
  declare global {
4
4
  interface Window {
@@ -9,7 +9,7 @@ declare global {
9
9
  export type FacebookEventMapping = {
10
10
  [key: string]: StandardFacebookEvent;
11
11
  };
12
- export type FacebookPixelIntegrationConfig = IntegrationConfig & {
12
+ export type FacebookPixelIntegrationConfig = IntegrationConfigBase & {
13
13
  type: 'facebook-pixel';
14
14
  config: FacebookPixelConfig;
15
15
  };
@@ -1,3 +1,2 @@
1
1
  export * from './integration-manager';
2
2
  export * from './facebook-pixel';
3
- export * from './console-integration';
@@ -34,7 +34,7 @@ export type Integration = {
34
34
  /**
35
35
  * The function to identify a user
36
36
  */
37
- identify?: (userId?: string | null, properties?: Record<string, unknown>, context?: AnalyticsContext) => void;
37
+ identify?: (userId?: string | null, properties?: Record<string, unknown>, context?: AnalyticsContext) => void | Promise<void>;
38
38
  };
39
39
  export type IntegrationStatus = 'idle' | 'loading' | 'ready' | 'error';
40
40
  export type IntegrationState = {
@@ -1,9 +0,0 @@
1
- import { Integration } from './integration-manager';
2
- export type ConsoleIntegrationConfig = {
3
- prefix?: string;
4
- };
5
- /**
6
- * Example integration that works on both client and server
7
- * Simply logs events to console
8
- */
9
- export declare function createConsoleIntegration(config?: ConsoleIntegrationConfig): Integration;