saasco-sdk 0.1.32 → 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 +184 -54
- package/index.esm.js +185 -54
- package/package.json +1 -1
- package/src/lib/analytics.d.ts +2 -1
- package/src/lib/integrations/facebook-pixel.d.ts +2 -2
- package/src/lib/integrations/index.d.ts +0 -1
- package/src/lib/integrations/integration-manager.d.ts +1 -1
- package/src/lib/integrations/console-integration.d.ts +0 -9
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.
|
|
10
|
+
var version = "0.1.33";
|
|
11
11
|
|
|
12
12
|
const timezones = {
|
|
13
13
|
'Asia/Barnaul': 'RU',
|
|
@@ -624,23 +624,42 @@ function createFacebookPixelIntegration(config) {
|
|
|
624
624
|
if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
|
|
625
625
|
if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
|
|
626
626
|
if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
|
|
627
|
-
window.fbq('track', fbEventName, fbParams);
|
|
627
|
+
window.fbq('track', fbEventName, Object.assign(Object.assign({}, properties), fbParams));
|
|
628
628
|
} else {
|
|
629
629
|
window.fbq('track', fbEventName);
|
|
630
630
|
}
|
|
631
631
|
},
|
|
632
|
-
identify: (userId, properties, context) => {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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);
|
|
639
656
|
}
|
|
640
|
-
}
|
|
657
|
+
})
|
|
641
658
|
};
|
|
642
659
|
}
|
|
643
660
|
function getFacebookEventName(eventName, eventMapping) {
|
|
661
|
+
// Convert default saasco page view event by default
|
|
662
|
+
if (eventName === 'Page View') return 'PageView';
|
|
644
663
|
if (!eventMapping) return eventName;
|
|
645
664
|
return eventMapping[eventName] || eventName;
|
|
646
665
|
}
|
|
@@ -668,6 +687,146 @@ function trackFacebookEvent(eventName, properties, eventMapping) {
|
|
|
668
687
|
window.fbq('track', fbEventName);
|
|
669
688
|
}
|
|
670
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
|
+
}
|
|
671
830
|
|
|
672
831
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
673
832
|
class AnalyticsLogger {
|
|
@@ -847,21 +1006,23 @@ class IntegrationManager {
|
|
|
847
1006
|
this.deliver(envelope);
|
|
848
1007
|
}
|
|
849
1008
|
deliver(evt) {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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);
|
|
860
1023
|
}
|
|
861
|
-
} catch (e) {
|
|
862
|
-
this.logger.error(`deliver error in ${integration.name}`, e);
|
|
863
1024
|
}
|
|
864
|
-
}
|
|
1025
|
+
});
|
|
865
1026
|
}
|
|
866
1027
|
/**
|
|
867
1028
|
* Flush queued events FIFO once at least one integration is ready.
|
|
@@ -1458,41 +1619,10 @@ const browserContextSchema = zod.z.object({
|
|
|
1458
1619
|
$utmAdId: zod.z.string().nullable()
|
|
1459
1620
|
});
|
|
1460
1621
|
|
|
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
1622
|
exports.IntegrationManager = IntegrationManager;
|
|
1492
1623
|
exports.Logger = Logger;
|
|
1493
1624
|
exports.Saasco = Saasco;
|
|
1494
1625
|
exports.browserContextSchema = browserContextSchema;
|
|
1495
|
-
exports.createConsoleIntegration = createConsoleIntegration;
|
|
1496
1626
|
exports.createFacebookPixelIntegration = createFacebookPixelIntegration;
|
|
1497
1627
|
exports.getBrowserContext = getBrowserContext;
|
|
1498
1628
|
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.
|
|
6
|
+
var version = "0.1.33";
|
|
7
7
|
|
|
8
8
|
const timezones = {
|
|
9
9
|
'Asia/Barnaul': 'RU',
|
|
@@ -620,23 +620,42 @@ function createFacebookPixelIntegration(config) {
|
|
|
620
620
|
if (properties['value'] !== undefined) fbParams['value'] = properties['value'];
|
|
621
621
|
if (properties['currency'] !== undefined) fbParams['currency'] = properties['currency'];
|
|
622
622
|
if (properties['predicted_ltv'] !== undefined) fbParams['predicted_ltv'] = properties['predicted_ltv'];
|
|
623
|
-
window.fbq('track', fbEventName, fbParams);
|
|
623
|
+
window.fbq('track', fbEventName, Object.assign(Object.assign({}, properties), fbParams));
|
|
624
624
|
} else {
|
|
625
625
|
window.fbq('track', fbEventName);
|
|
626
626
|
}
|
|
627
627
|
},
|
|
628
|
-
identify: (userId, properties, context) => {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
628
|
+
identify: (userId, properties, context) => __awaiter(this, void 0, void 0, function* () {
|
|
629
|
+
try {
|
|
630
|
+
if (typeof window !== 'undefined' && window.fbq) {
|
|
631
|
+
const amValues = coerceMetaConversionsAmValues(Object.assign(Object.assign({}, properties || {}), userId ? {
|
|
632
|
+
external_id: userId
|
|
633
|
+
} : {}));
|
|
634
|
+
const am = {};
|
|
635
|
+
if (amValues.external_id) am['external_id'] = amValues.external_id;
|
|
636
|
+
if (amValues.em) am['em'] = yield sha256Hex(amValues.em);
|
|
637
|
+
if (amValues.fn) am['fn'] = amValues.fn;
|
|
638
|
+
if (amValues.ln) am['ln'] = amValues.ln;
|
|
639
|
+
if (amValues.ph) am['ph'] = amValues.ph;
|
|
640
|
+
if (amValues.ge) am['ge'] = amValues.ge;
|
|
641
|
+
if (amValues.db) am['db'] = amValues.db;
|
|
642
|
+
if (amValues.ct) am['ct'] = amValues.ct;
|
|
643
|
+
if (amValues.st) am['st'] = amValues.st;
|
|
644
|
+
if (amValues.zp) am['zp'] = amValues.zp;
|
|
645
|
+
if (amValues.country) am['country'] = amValues.country;
|
|
646
|
+
if (Object.keys(am).length > 0) {
|
|
647
|
+
window.fbq('init', config.pixelId, am);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
} catch (error) {
|
|
651
|
+
console.error('Error identifying user in Meta Pixel:', error);
|
|
635
652
|
}
|
|
636
|
-
}
|
|
653
|
+
})
|
|
637
654
|
};
|
|
638
655
|
}
|
|
639
656
|
function getFacebookEventName(eventName, eventMapping) {
|
|
657
|
+
// Convert default saasco page view event by default
|
|
658
|
+
if (eventName === 'Page View') return 'PageView';
|
|
640
659
|
if (!eventMapping) return eventName;
|
|
641
660
|
return eventMapping[eventName] || eventName;
|
|
642
661
|
}
|
|
@@ -664,6 +683,146 @@ function trackFacebookEvent(eventName, properties, eventMapping) {
|
|
|
664
683
|
window.fbq('track', fbEventName);
|
|
665
684
|
}
|
|
666
685
|
}
|
|
686
|
+
function coerceMetaConversionsAmValues(properties) {
|
|
687
|
+
const coerce = [{
|
|
688
|
+
key: 'em',
|
|
689
|
+
coerceFrom: ['email', 'user_email', 'userEmail', 'email_address', 'emailAddress', 'e_mail', 'E_Mail', 'mail', 'Mail', 'contact_email', 'contactEmail', 'primary_email', 'primaryEmail'],
|
|
690
|
+
transform: value => {
|
|
691
|
+
if (typeof value !== 'string') return undefined;
|
|
692
|
+
const normalized = value.trim().toLowerCase();
|
|
693
|
+
return normalized || undefined;
|
|
694
|
+
}
|
|
695
|
+
}, {
|
|
696
|
+
key: 'fn',
|
|
697
|
+
coerceFrom: ['first_name', 'firstName', 'firstname', 'given_name', 'givenName', 'user_first_name', 'userFirstName', 'name_first', 'nameFirst', 'f_name', 'fName'],
|
|
698
|
+
transform: value => {
|
|
699
|
+
if (typeof value !== 'string') return undefined;
|
|
700
|
+
const normalized = value.trim().toLowerCase();
|
|
701
|
+
return normalized || undefined;
|
|
702
|
+
}
|
|
703
|
+
}, {
|
|
704
|
+
key: 'ln',
|
|
705
|
+
coerceFrom: ['last_name', 'lastName', 'lastname', 'surname', 'user_last_name', 'userLastName', 'name_last', 'nameLast', 'l_name', 'lName'],
|
|
706
|
+
transform: value => {
|
|
707
|
+
if (typeof value !== 'string') return undefined;
|
|
708
|
+
const normalized = value.trim().toLowerCase();
|
|
709
|
+
return normalized || undefined;
|
|
710
|
+
}
|
|
711
|
+
}, {
|
|
712
|
+
key: 'ph',
|
|
713
|
+
coerceFrom: ['phone', 'phone_number', 'phoneNumber', 'mobile', 'mobile_number', 'mobileNumber', 'contact_number', 'contactNumber', 'tel', 'telephone'],
|
|
714
|
+
transform: value => {
|
|
715
|
+
if (typeof value !== 'string' && typeof value !== 'number') return undefined;
|
|
716
|
+
const phoneStr = String(value).replace(/\D/g, '');
|
|
717
|
+
return phoneStr || undefined;
|
|
718
|
+
}
|
|
719
|
+
}, {
|
|
720
|
+
key: 'external_id',
|
|
721
|
+
coerceFrom: ['external_id', 'externalId', 'user_id', 'userId', 'id', 'distinctId', 'distinct_id', 'customer_id', 'customerId', 'member_id', 'memberId', 'loyalty_id', 'loyaltyId'],
|
|
722
|
+
transform: value => {
|
|
723
|
+
if (value == null) return undefined;
|
|
724
|
+
return String(value) || undefined;
|
|
725
|
+
}
|
|
726
|
+
}, {
|
|
727
|
+
key: 'ge',
|
|
728
|
+
coerceFrom: ['gender', 'user_gender', 'userGender', 'sex', 'user_sex', 'userSex'],
|
|
729
|
+
transform: value => {
|
|
730
|
+
if (typeof value !== 'string') return undefined;
|
|
731
|
+
const normalized = value.trim().toLowerCase();
|
|
732
|
+
if (normalized === 'female' || normalized === 'f') return 'f';
|
|
733
|
+
if (normalized === 'male' || normalized === 'm') return 'm';
|
|
734
|
+
return undefined;
|
|
735
|
+
}
|
|
736
|
+
}, {
|
|
737
|
+
key: 'db',
|
|
738
|
+
coerceFrom: ['birthday', 'birth_date', 'birthDate', 'date_of_birth', 'dateOfBirth', 'dob', 'Dob', 'birth_day', 'birthDay'],
|
|
739
|
+
transform: value => {
|
|
740
|
+
if (!value) return undefined;
|
|
741
|
+
let dateStr;
|
|
742
|
+
if (value instanceof Date) {
|
|
743
|
+
dateStr = value.toISOString().split('T')[0];
|
|
744
|
+
} else {
|
|
745
|
+
dateStr = String(value);
|
|
746
|
+
}
|
|
747
|
+
const dateMatch = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/);
|
|
748
|
+
if (dateMatch) {
|
|
749
|
+
const [, year, month, day] = dateMatch;
|
|
750
|
+
return `${year}${month}${day}`;
|
|
751
|
+
}
|
|
752
|
+
const numericMatch = dateStr.replace(/\D/g, '');
|
|
753
|
+
if (numericMatch.length === 8) {
|
|
754
|
+
return numericMatch;
|
|
755
|
+
}
|
|
756
|
+
return undefined;
|
|
757
|
+
}
|
|
758
|
+
}, {
|
|
759
|
+
key: 'ct',
|
|
760
|
+
coerceFrom: ['city', 'user_city', 'userCity', 'location_city', 'locationCity', 'address_city', 'addressCity'],
|
|
761
|
+
transform: value => {
|
|
762
|
+
if (typeof value !== 'string') return undefined;
|
|
763
|
+
const normalized = value.trim().toLowerCase().replace(/\s+/g, '');
|
|
764
|
+
return normalized || undefined;
|
|
765
|
+
}
|
|
766
|
+
}, {
|
|
767
|
+
key: 'st',
|
|
768
|
+
coerceFrom: ['state', 'user_state', 'userState', 'province', 'user_province', 'userProvince', 'region', 'user_region', 'userRegion', 'location_state', 'locationState', 'address_state', 'addressState'],
|
|
769
|
+
transform: value => {
|
|
770
|
+
if (typeof value !== 'string') return undefined;
|
|
771
|
+
const normalized = value.trim().toLowerCase();
|
|
772
|
+
if (normalized.length === 2) return normalized;
|
|
773
|
+
return undefined;
|
|
774
|
+
}
|
|
775
|
+
}, {
|
|
776
|
+
key: 'zp',
|
|
777
|
+
coerceFrom: ['zip', 'zipcode', 'zip_code', 'postal_code', 'postalCode', 'postcode', 'user_zip', 'userZip', 'address_zip', 'addressZip'],
|
|
778
|
+
transform: value => {
|
|
779
|
+
if (typeof value !== 'string' && typeof value !== 'number') return undefined;
|
|
780
|
+
return String(value).trim() || undefined;
|
|
781
|
+
}
|
|
782
|
+
}, {
|
|
783
|
+
key: 'country',
|
|
784
|
+
coerceFrom: ['country', 'country_code', 'countryCode', 'user_country', 'userCountry', 'location_country', 'locationCountry', 'address_country', 'addressCountry'],
|
|
785
|
+
transform: value => {
|
|
786
|
+
if (typeof value !== 'string') return undefined;
|
|
787
|
+
const normalized = value.trim().toLowerCase();
|
|
788
|
+
if (normalized.length === 2) return normalized;
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
}];
|
|
792
|
+
const result = {};
|
|
793
|
+
for (const {
|
|
794
|
+
key,
|
|
795
|
+
coerceFrom,
|
|
796
|
+
transform
|
|
797
|
+
} of coerce) {
|
|
798
|
+
const foundValue = findFirstProperty(properties, coerceFrom);
|
|
799
|
+
if (foundValue !== null && transform) {
|
|
800
|
+
const transformedValue = transform(foundValue);
|
|
801
|
+
if (transformedValue) {
|
|
802
|
+
result[key] = transformedValue;
|
|
803
|
+
}
|
|
804
|
+
} else if (foundValue !== null && typeof foundValue === 'string') {
|
|
805
|
+
result[key] = foundValue;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return result;
|
|
809
|
+
}
|
|
810
|
+
function findFirstProperty(properties, keys) {
|
|
811
|
+
for (const key of keys) {
|
|
812
|
+
if (properties[key] !== undefined) {
|
|
813
|
+
return properties[key];
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
const toHex = buf => [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
819
|
+
function sha256Hex(input) {
|
|
820
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
821
|
+
const enc = new TextEncoder().encode(input);
|
|
822
|
+
const digest = yield crypto.subtle.digest('SHA-256', enc);
|
|
823
|
+
return toHex(digest);
|
|
824
|
+
});
|
|
825
|
+
}
|
|
667
826
|
|
|
668
827
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
669
828
|
class AnalyticsLogger {
|
|
@@ -843,21 +1002,23 @@ class IntegrationManager {
|
|
|
843
1002
|
this.deliver(envelope);
|
|
844
1003
|
}
|
|
845
1004
|
deliver(evt) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1005
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1006
|
+
for (const {
|
|
1007
|
+
integration,
|
|
1008
|
+
status
|
|
1009
|
+
} of this.integrations.values()) {
|
|
1010
|
+
if (status !== 'ready') continue;
|
|
1011
|
+
try {
|
|
1012
|
+
if (evt.type === 'track' && integration.track && evt.name) {
|
|
1013
|
+
yield integration.track(evt.name, evt.properties, evt.context);
|
|
1014
|
+
} else if (evt.type === 'identify' && integration.identify) {
|
|
1015
|
+
yield integration.identify(evt.context.distinctId, evt.properties, evt.context);
|
|
1016
|
+
}
|
|
1017
|
+
} catch (e) {
|
|
1018
|
+
this.logger.error(`deliver error in ${integration.name} (${evt.type})`, e);
|
|
856
1019
|
}
|
|
857
|
-
} catch (e) {
|
|
858
|
-
this.logger.error(`deliver error in ${integration.name}`, e);
|
|
859
1020
|
}
|
|
860
|
-
}
|
|
1021
|
+
});
|
|
861
1022
|
}
|
|
862
1023
|
/**
|
|
863
1024
|
* Flush queued events FIFO once at least one integration is ready.
|
|
@@ -1454,34 +1615,4 @@ const browserContextSchema = z.object({
|
|
|
1454
1615
|
$utmAdId: z.string().nullable()
|
|
1455
1616
|
});
|
|
1456
1617
|
|
|
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 };
|
|
1618
|
+
export { IntegrationManager, Logger, Saasco, browserContextSchema, createFacebookPixelIntegration, getBrowserContext, initializeFacebookPixel, timezones, trackFacebookEvent };
|
package/package.json
CHANGED
package/src/lib/analytics.d.ts
CHANGED
|
@@ -25,8 +25,9 @@ type DoRequestResponse = {
|
|
|
25
25
|
success: boolean;
|
|
26
26
|
message: string;
|
|
27
27
|
};
|
|
28
|
-
export type
|
|
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 {
|
|
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 =
|
|
12
|
+
export type FacebookPixelIntegrationConfig = IntegrationConfigBase & {
|
|
13
13
|
type: 'facebook-pixel';
|
|
14
14
|
config: FacebookPixelConfig;
|
|
15
15
|
};
|
|
@@ -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;
|