@ohos-ports/s-ui-segment-wrapper 0.0.0-stage → 5.4.0-beta.0
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/README.md +269 -2
- package/lib/config.js +20 -0
- package/lib/events.js +3 -0
- package/lib/index.js +82 -0
- package/lib/middlewares/destination/optimizelySiteAttribute.js +17 -0
- package/lib/middlewares/source/campaignContext.js +10 -0
- package/lib/middlewares/source/defaultContextProperties.js +11 -0
- package/lib/middlewares/source/optimizelyUserId.js +15 -0
- package/lib/middlewares/source/pageData.js +42 -0
- package/lib/middlewares/source/pageReferrer.js +102 -0
- package/lib/middlewares/source/userScreenInfo.js +12 -0
- package/lib/middlewares/source/userTraits.js +48 -0
- package/lib/repositories/googleRepository.js +272 -0
- package/lib/repositories/xandrRepository.js +42 -0
- package/lib/segmentWrapper.js +364 -0
- package/lib/tcf.js +230 -0
- package/lib/universalId.js +48 -0
- package/lib/utils/checkAnonymousId.js +9 -0
- package/lib/utils/cookies.js +50 -0
- package/lib/utils/ga4Detection.js +18 -0
- package/lib/utils/hashEmail.js +24 -0
- package/lib/utils/patchAnalytics.js +36 -0
- package/lib/utils/storage.js +17 -0
- package/package.json +39 -4
- package/src/config.js +25 -0
- package/src/events.js +3 -0
- package/src/index.js +96 -0
- package/src/middlewares/destination/optimizelySiteAttribute.js +22 -0
- package/src/middlewares/source/campaignContext.js +12 -0
- package/src/middlewares/source/defaultContextProperties.js +15 -0
- package/src/middlewares/source/optimizelyUserId.js +20 -0
- package/src/middlewares/source/pageData.js +45 -0
- package/src/middlewares/source/pageReferrer.js +125 -0
- package/src/middlewares/source/userScreenInfo.js +11 -0
- package/src/middlewares/source/userTraits.js +41 -0
- package/src/repositories/googleRepository.js +251 -0
- package/src/repositories/xandrRepository.js +48 -0
- package/src/segmentWrapper.js +338 -0
- package/src/tcf.js +263 -0
- package/src/universalId.js +51 -0
- package/src/utils/checkAnonymousId.js +10 -0
- package/src/utils/cookies.js +56 -0
- package/src/utils/ga4Detection.js +22 -0
- package/src/utils/hashEmail.js +33 -0
- package/src/utils/patchAnalytics.js +46 -0
- package/src/utils/storage.js +18 -0
- package/src-umd/index.js +8 -0
- package/umd/index.js +1 -0
package/lib/tcf.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import _extends from "@swc/helpers/src/_extends.mjs";
|
|
2
|
+
import { getConfig, isClient, setConfig } from "./config.js";
|
|
3
|
+
import analytics from "./segmentWrapper.js";
|
|
4
|
+
/**
|
|
5
|
+
* Cookie to extract the already saved consents for the user
|
|
6
|
+
* @type {string}
|
|
7
|
+
*/ var TCF_COOKIE_KEY = "borosTcf";
|
|
8
|
+
/**
|
|
9
|
+
* TCF Api Version to use
|
|
10
|
+
* @type {number}
|
|
11
|
+
*/ var TCF_API_VERSION = 2;
|
|
12
|
+
/**
|
|
13
|
+
* Default properties to send with every TCF tracking event
|
|
14
|
+
*/ var TCF_TRACK_PROPERTIES = {
|
|
15
|
+
channel: "GDPR"
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* List of purpose ids needed to be able to track with all info
|
|
19
|
+
* @type {Array<number>}
|
|
20
|
+
*/ var NEEDED_PURPOSES = {
|
|
21
|
+
analytics: [
|
|
22
|
+
1,
|
|
23
|
+
8,
|
|
24
|
+
10
|
|
25
|
+
],
|
|
26
|
+
advertising: [
|
|
27
|
+
3
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* TCF events
|
|
32
|
+
*/ var TCF_EVENTS = {
|
|
33
|
+
// Event that determines that the tcData has been loaded
|
|
34
|
+
LOADED: "tcloaded",
|
|
35
|
+
// Event that determines that the user has performed an action
|
|
36
|
+
USER_ACTION_COMPLETE: "useractioncomplete"
|
|
37
|
+
};
|
|
38
|
+
export var CMP_TRACK_EVENT = "CMP Submitted";
|
|
39
|
+
/**
|
|
40
|
+
* State of user according to GDPR regarding tracking
|
|
41
|
+
*/ export var USER_GDPR = {
|
|
42
|
+
ACCEPTED: "accepted",
|
|
43
|
+
DECLINED: "declined",
|
|
44
|
+
UNKNOWN: "unknown"
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Define the user GDPR consents state. This value will be updated with new values
|
|
48
|
+
* when the consents of the users changes.
|
|
49
|
+
*/ var gdprState = {
|
|
50
|
+
listeners: [],
|
|
51
|
+
value: undefined,
|
|
52
|
+
addListener: function(callback) {
|
|
53
|
+
return gdprState.listeners.push(callback);
|
|
54
|
+
},
|
|
55
|
+
get: function() {
|
|
56
|
+
return gdprState.value;
|
|
57
|
+
},
|
|
58
|
+
set: function(value) {
|
|
59
|
+
var ref = value || {}, analytics = ref.analytics, advertising = ref.advertising;
|
|
60
|
+
gdprState.value = {
|
|
61
|
+
analytics: analytics,
|
|
62
|
+
advertising: advertising
|
|
63
|
+
};
|
|
64
|
+
gdprState.listeners.forEach(function(fn) {
|
|
65
|
+
return fn(value);
|
|
66
|
+
});
|
|
67
|
+
gdprState.listeners = [];
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Read cookie by using a key
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/ function readCookie(key) {
|
|
74
|
+
var re = new RegExp(key + "=([^;]+)");
|
|
75
|
+
var value = re.exec(document.cookie);
|
|
76
|
+
return value !== null ? unescape(value[1]) : null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if we're on client and tcfApi is available on window object
|
|
80
|
+
* @returns {Boolean}
|
|
81
|
+
*/ var checkTcfIsAvailable = function() {
|
|
82
|
+
// if we're on the client, check we haven't already initialized it
|
|
83
|
+
if (getConfig("initialized")) return false;
|
|
84
|
+
// if we're on client, check if we have the tcfapi available
|
|
85
|
+
var isTcfApiAvailable = !!window.__tcfapi;
|
|
86
|
+
!isTcfApiAvailable && console.warn("[tcfTracking] window.__tcfapi is not available on client and TCF won't be tracked.");
|
|
87
|
+
return isTcfApiAvailable;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Check from a list of consents if user has accepted being tracked
|
|
91
|
+
* @param {{[purposeId: string]: boolean}} userConsents
|
|
92
|
+
* @returns {Boolean}
|
|
93
|
+
*/ var checkHasUserConsentedAnalytics = function(userConsents) {
|
|
94
|
+
return NEEDED_PURPOSES.analytics.every(function(purposeId) {
|
|
95
|
+
return userConsents["" + purposeId];
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
var checkHasUserConsentedAdvertising = function(userConsents) {
|
|
99
|
+
return NEEDED_PURPOSES.advertising.every(function(purposeId) {
|
|
100
|
+
return userConsents["" + purposeId];
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Track a specific TCF event
|
|
105
|
+
* @param {object} params
|
|
106
|
+
* @param {string} params.eventId Event ID to be sent with the TCF Tracking
|
|
107
|
+
* @param {string} params.gdprPrivacy Send a string telling if the gdpr has been accepted or reject
|
|
108
|
+
* @return {Promise}
|
|
109
|
+
*/ var trackTcf = function(param) {
|
|
110
|
+
var eventId = param.eventId, gdprPrivacy = param.gdprPrivacy;
|
|
111
|
+
return analytics.track(CMP_TRACK_EVENT, _extends({}, TCF_TRACK_PROPERTIES, getConfig("tcfTrackDefaultProperties")), {
|
|
112
|
+
gdpr_privacy: gdprPrivacy.analytics,
|
|
113
|
+
gdpr_privacy_advertising: gdprPrivacy.advertising
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Get if we have user consents
|
|
118
|
+
* @return {Promise<Object>}
|
|
119
|
+
*/ export var getGdprPrivacyValue = function() {
|
|
120
|
+
// try to get the actual gdprPrivacyValue and just return it
|
|
121
|
+
var gdprPrivacyValue = gdprState.get();
|
|
122
|
+
if (gdprPrivacyValue !== undefined) return Promise.resolve(gdprPrivacyValue);
|
|
123
|
+
// // if we don't have a gdprPrivacyValue, then subscribe to it until we have a value
|
|
124
|
+
return new Promise(function(resolve) {
|
|
125
|
+
gdprState.addListener(function(gdprPrivacyValue) {
|
|
126
|
+
return resolve(gdprPrivacyValue);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Check if gdprPrivacyValue is accepted
|
|
132
|
+
* @return {boolean}
|
|
133
|
+
*/ export var checkAnalyticsGdprIsAccepted = function(gdprPrivacyValue) {
|
|
134
|
+
return gdprPrivacyValue.analytics === USER_GDPR.ACCEPTED;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Set gdprState according to list of purpose consents
|
|
138
|
+
* @returns {string}
|
|
139
|
+
*/ var setGdprStateBy = function(purposeConsents) {
|
|
140
|
+
var hasAnalyticsConsents = checkHasUserConsentedAnalytics(purposeConsents);
|
|
141
|
+
var hasAdvertisingConsents = checkHasUserConsentedAdvertising(purposeConsents);
|
|
142
|
+
// get the state key according to the gdprPrivacyValue
|
|
143
|
+
var gdprAnalyticsStateKey = hasAnalyticsConsents ? USER_GDPR.ACCEPTED : USER_GDPR.DECLINED;
|
|
144
|
+
var gdprAdvertisingStateKey = hasAdvertisingConsents ? USER_GDPR.ACCEPTED : USER_GDPR.DECLINED;
|
|
145
|
+
// update gdprState with the new value for user gdpr
|
|
146
|
+
gdprState.set({
|
|
147
|
+
analytics: gdprAnalyticsStateKey,
|
|
148
|
+
advertising: gdprAdvertisingStateKey
|
|
149
|
+
});
|
|
150
|
+
// return the new gdprState
|
|
151
|
+
return {
|
|
152
|
+
analytics: gdprAnalyticsStateKey,
|
|
153
|
+
advertising: gdprAdvertisingStateKey
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Read and decode the tcf cookie
|
|
158
|
+
*/ var getConsentsFromCookie = function() {
|
|
159
|
+
var cookieValue = readCookie(TCF_COOKIE_KEY);
|
|
160
|
+
if (!cookieValue) return;
|
|
161
|
+
try {
|
|
162
|
+
var purpose = JSON.parse(window.atob(cookieValue)).purpose;
|
|
163
|
+
var consents = purpose.consents;
|
|
164
|
+
return consents;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error(e);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Set the correct initial gdprState based on the consents cookie string
|
|
171
|
+
*/ var initializeGdprState = function() {
|
|
172
|
+
var consents = getConsentsFromCookie();
|
|
173
|
+
if (consents) setGdprStateBy(consents);
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Sets global isFirstVisit flag based on the consents cookie string
|
|
177
|
+
*/ var initializeIsFirstVisit = function() {
|
|
178
|
+
var consents = getConsentsFromCookie();
|
|
179
|
+
setConfig("isFirstVisit", !consents);
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Init TCF Tracking User Consents with Segment
|
|
183
|
+
*/ export default function initTcfTracking() {
|
|
184
|
+
// first check if we're on server, as this doesn't work on server-side
|
|
185
|
+
if (!isClient) return;
|
|
186
|
+
// read the cookie and put the correct usergdprValue and isFirstVisit flag before listening events
|
|
187
|
+
initializeGdprState();
|
|
188
|
+
initializeIsFirstVisit();
|
|
189
|
+
// do some checks before initializing tcf tracking as we do that only if available
|
|
190
|
+
if (!checkTcfIsAvailable()) {
|
|
191
|
+
var ref;
|
|
192
|
+
// if we don't have a gdpr state and tcf is not available
|
|
193
|
+
// we should assume we don't known if we have consents
|
|
194
|
+
var analyticsGdprState = (ref = gdprState.get()) == null ? void 0 : ref.analytics;
|
|
195
|
+
if (analyticsGdprState === undefined) gdprState.set({
|
|
196
|
+
analytics: USER_GDPR.UNKNOWN,
|
|
197
|
+
advertising: USER_GDPR.UNKNOWN
|
|
198
|
+
});
|
|
199
|
+
// and we stop executing as we can't track tcf
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// add a flag to the segmentWrapper config to know it's already initialized
|
|
203
|
+
setConfig("initialized", true);
|
|
204
|
+
// listen events from tcf api
|
|
205
|
+
window.__tcfapi("addEventListener", TCF_API_VERSION, function(param, success) {
|
|
206
|
+
var eventStatus = param.eventStatus, purpose = param.purpose;
|
|
207
|
+
if (!success) return Promise.resolve();
|
|
208
|
+
// if we've already user consents or the user is accepting or declining now
|
|
209
|
+
// we change the state of the GDPR to use in our tracking
|
|
210
|
+
if (eventStatus === TCF_EVENTS.USER_ACTION_COMPLETE || eventStatus === TCF_EVENTS.LOADED) {
|
|
211
|
+
var consents = purpose.consents;
|
|
212
|
+
var gdprStateKey = setGdprStateBy(consents);
|
|
213
|
+
// if it's a user action, then we will track it
|
|
214
|
+
if (eventStatus === TCF_EVENTS.USER_ACTION_COMPLETE) {
|
|
215
|
+
// extract the eventId and gdprPrivacy string to send with the track
|
|
216
|
+
var analyticsStateKey = gdprStateKey.analytics, advertisingStateKey = gdprStateKey.advertising;
|
|
217
|
+
var gdprPrivacy = {
|
|
218
|
+
analytics: analyticsStateKey,
|
|
219
|
+
advertising: advertisingStateKey
|
|
220
|
+
};
|
|
221
|
+
// temporary during Didomi migration to avoid fake programmatically generated events where the users doesn't really interact
|
|
222
|
+
var MIGRATION_DIDOMI_SEGMENT_WRAPPER_FLAG = "didomi-migration";
|
|
223
|
+
var isDidomiMigration = isClient && window.sessionStorage.getItem(MIGRATION_DIDOMI_SEGMENT_WRAPPER_FLAG);
|
|
224
|
+
return !isDidomiMigration && trackTcf({
|
|
225
|
+
gdprPrivacy: gdprPrivacy
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { dispatchEvent } from "@s-ui/js/lib/events/index.js";
|
|
2
|
+
import { createUniversalId } from "./utils/hashEmail.js";
|
|
3
|
+
import { getConfig, isClient, setConfig } from "./config.js";
|
|
4
|
+
var USER_DATA_READY_EVENT = "USER_DATA_READY";
|
|
5
|
+
export var getUniversalIdFromConfig = function() {
|
|
6
|
+
return getConfig("universalId");
|
|
7
|
+
};
|
|
8
|
+
export var getUniversalId = function() {
|
|
9
|
+
// 1. Try to get universalId from config
|
|
10
|
+
var universalId = getUniversalIdFromConfig();
|
|
11
|
+
if (universalId) {
|
|
12
|
+
setUniversalIdInitialized();
|
|
13
|
+
return universalId;
|
|
14
|
+
}
|
|
15
|
+
// 2. If not available, then we use the email and hash it
|
|
16
|
+
var userEmail = getConfig("userEmail");
|
|
17
|
+
if (userEmail) {
|
|
18
|
+
universalId = createUniversalId(userEmail);
|
|
19
|
+
setUniversalId(universalId);
|
|
20
|
+
setUniversalIdInitialized();
|
|
21
|
+
return universalId;
|
|
22
|
+
}
|
|
23
|
+
// 3. We don't have user email, so we don't have universalId
|
|
24
|
+
// but we've tried, so we set it as initialized
|
|
25
|
+
setUniversalIdInitialized();
|
|
26
|
+
};
|
|
27
|
+
export var getUserDataAndNotify = function() {
|
|
28
|
+
if (!isClient) return;
|
|
29
|
+
var universalId = getUniversalId();
|
|
30
|
+
var userEmail = getConfig("userEmail");
|
|
31
|
+
dispatchEvent({
|
|
32
|
+
eventName: USER_DATA_READY_EVENT,
|
|
33
|
+
detail: {
|
|
34
|
+
universalId: universalId,
|
|
35
|
+
userEmail: userEmail
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
universalId: universalId,
|
|
40
|
+
userEmail: userEmail
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export var setUniversalIdInitialized = function() {
|
|
44
|
+
setConfig("universalIdInitialized", true);
|
|
45
|
+
};
|
|
46
|
+
export var setUniversalId = function(universalId) {
|
|
47
|
+
return setConfig("universalId", universalId);
|
|
48
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export var checkAnonymousId = function() {
|
|
2
|
+
var ref, ref1;
|
|
3
|
+
var ref2;
|
|
4
|
+
var SEGMENT_ID_USER_WITHOUT_CONSENTS = (ref2 = (ref = window.__SEGMENT_WRAPPER) == null ? void 0 : ref.SEGMENT_ID_USER_WITHOUT_CONSENTS) != null ? ref2 : "anonymous_user";
|
|
5
|
+
var anonymousId = window.analytics.user == null ? void 0 : (ref1 = window.analytics.user()) == null ? void 0 : ref1.anonymousId();
|
|
6
|
+
if (anonymousId === SEGMENT_ID_USER_WITHOUT_CONSENTS) {
|
|
7
|
+
window.analytics.setAnonymousId(null);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import _create_for_of_iterator_helper_loose from "@swc/helpers/src/_create_for_of_iterator_helper_loose.mjs";
|
|
2
|
+
export function readCookie(cookieName) {
|
|
3
|
+
var re = new RegExp(cookieName + "=([^;]+)");
|
|
4
|
+
var value = re.exec(document.cookie);
|
|
5
|
+
return value !== null ? unescape(value[1]) : null;
|
|
6
|
+
}
|
|
7
|
+
var ONE_YEAR = 31536000;
|
|
8
|
+
var DEFAULT_PATH = "/";
|
|
9
|
+
var DEFAULT_SAME_SITE = "Lax";
|
|
10
|
+
export function saveCookie(cookieName, data, param) {
|
|
11
|
+
var ref = param === void 0 ? {} : param, _maxAge = ref.maxAge, maxAge = _maxAge === void 0 ? ONE_YEAR : _maxAge, _path = ref.path, path = _path === void 0 ? DEFAULT_PATH : _path, _reduceDomain = ref.reduceDomain, reduceDomain = _reduceDomain === void 0 ? true : _reduceDomain, _sameSite = ref.sameSite, sameSite = _sameSite === void 0 ? DEFAULT_SAME_SITE : _sameSite;
|
|
12
|
+
var domain = reduceDomain ? toCookieDomain() : window.location.hostname;
|
|
13
|
+
var cookieValue = [
|
|
14
|
+
cookieName + "=" + data,
|
|
15
|
+
"domain=" + domain,
|
|
16
|
+
"path=" + path,
|
|
17
|
+
"max-age=" + maxAge,
|
|
18
|
+
"SameSite=" + sameSite
|
|
19
|
+
].join(";");
|
|
20
|
+
document.cookie = cookieValue;
|
|
21
|
+
}
|
|
22
|
+
export function removeCookie(cookieName, param) {
|
|
23
|
+
var ref = param === void 0 ? {} : param, _path = ref.path, path = _path === void 0 ? DEFAULT_PATH : _path, _reduceDomain = ref.reduceDomain, reduceDomain = _reduceDomain === void 0 ? true : _reduceDomain, _sameSite = ref.sameSite, sameSite = _sameSite === void 0 ? DEFAULT_SAME_SITE : _sameSite;
|
|
24
|
+
var domain = reduceDomain ? toCookieDomain() : window.location.hostname;
|
|
25
|
+
document.cookie = cookieName + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=" + path + "; domain=" + domain;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reduces the domain to main domain name
|
|
29
|
+
* Examples:
|
|
30
|
+
* - www.mywebpage.es -> .mywebpage.es
|
|
31
|
+
* - www.my.newwebpage.net -> .newwebpage.net
|
|
32
|
+
* - www.mywebpage.co.uk -> .mywebpage.co.uk
|
|
33
|
+
*
|
|
34
|
+
* @param {String} domain
|
|
35
|
+
* @returns {String} dot + main domain
|
|
36
|
+
*/ var toCookieDomain = function(domain) {
|
|
37
|
+
if (domain === void 0) domain = window.location.hostname || "";
|
|
38
|
+
var DOT = ".";
|
|
39
|
+
var hostDomainParts = domain.split(DOT).reverse();
|
|
40
|
+
if (hostDomainParts.length === 1) {
|
|
41
|
+
return hostDomainParts[0];
|
|
42
|
+
}
|
|
43
|
+
var cookieDomainParts = [];
|
|
44
|
+
for(var _iterator = _create_for_of_iterator_helper_loose(hostDomainParts), _step; !(_step = _iterator()).done;){
|
|
45
|
+
var part = _step.value;
|
|
46
|
+
cookieDomainParts.push(part);
|
|
47
|
+
if (part.length > 3) break;
|
|
48
|
+
}
|
|
49
|
+
return "" + DOT + cookieDomainParts.reverse().join(DOT);
|
|
50
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getConfig } from "../config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check if Google Analytics 4 Web destination is enabled in Segment
|
|
4
|
+
* Simple detection based on googleAnalyticsMeasurementId presence:
|
|
5
|
+
* - If googleAnalyticsMeasurementId is configured → LEGACY mode (manual GA4)
|
|
6
|
+
* - If NOT configured → NEW mode (Segment destination handles GA4)
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/ export var isGA4DestinationEnabled = function() {
|
|
9
|
+
var googleAnalyticsMeasurementId = getConfig("googleAnalyticsMeasurementId");
|
|
10
|
+
// If no measurement ID configured, assume they're using Segment destination
|
|
11
|
+
return !googleAnalyticsMeasurementId;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Reset the cached detection (useful for testing)
|
|
15
|
+
* @deprecated No longer needed as detection is now stateless
|
|
16
|
+
*/ export var resetGA4DetectionCache = function() {
|
|
17
|
+
// No-op: detection is now stateless, no cache to reset
|
|
18
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import md5 from "tiny-hashes/md5/md5.js";
|
|
2
|
+
import sha256 from "tiny-hashes/sha256/sha256.js";
|
|
3
|
+
var PLUS_AND_DOT = /\.|\+.*$/g;
|
|
4
|
+
/**
|
|
5
|
+
* Normalize email by lowering case and extracting + and . symbols for gmail
|
|
6
|
+
* @param {string} email
|
|
7
|
+
* @return {string} Normalized email. If not valid, returns and empty string
|
|
8
|
+
*/ export function normalizeEmail(email) {
|
|
9
|
+
if (typeof email !== "string") return "";
|
|
10
|
+
email = email.toLowerCase();
|
|
11
|
+
var emailParts = email.split(/@/);
|
|
12
|
+
if (emailParts.length !== 2) return "";
|
|
13
|
+
var username = emailParts[0], domain = emailParts[1];
|
|
14
|
+
username = username.replace(PLUS_AND_DOT, "");
|
|
15
|
+
return username + "@" + domain;
|
|
16
|
+
}
|
|
17
|
+
export function createUniversalId(email) {
|
|
18
|
+
var normalizedEmail = normalizeEmail(email);
|
|
19
|
+
return normalizedEmail ? sha256(normalizedEmail) : "";
|
|
20
|
+
}
|
|
21
|
+
export function hashEmail(email) {
|
|
22
|
+
var normalizedEmail = normalizeEmail(email);
|
|
23
|
+
return normalizedEmail ? md5(normalizedEmail) : "";
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import _extends from "@swc/helpers/src/_extends.mjs";
|
|
2
|
+
import { isClient } from "../config.js";
|
|
3
|
+
import { decorateContextWithNeededData, getDefaultProperties } from "../segmentWrapper.js";
|
|
4
|
+
var MPI_PATCH_FIELD = "__mpi_patch";
|
|
5
|
+
function monkeyPatchAnalyticsTrack() {
|
|
6
|
+
var _analytics = window.analytics, originalTrack = _analytics.track;
|
|
7
|
+
window.analytics.track = function() {
|
|
8
|
+
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
9
|
+
args[_key] = arguments[_key];
|
|
10
|
+
}
|
|
11
|
+
var event = args[0], properties = args[1], contextFromArgs = args[2], fn = args[3];
|
|
12
|
+
var newProperties = _extends({}, getDefaultProperties(), properties);
|
|
13
|
+
decorateContextWithNeededData({
|
|
14
|
+
context: contextFromArgs,
|
|
15
|
+
event: event,
|
|
16
|
+
properties: newProperties
|
|
17
|
+
}).then(function(param) {
|
|
18
|
+
var newContext = param.context, decoratedProperties = param.properties;
|
|
19
|
+
originalTrack.call(window.analytics, event, decoratedProperties, _extends({}, newContext, {
|
|
20
|
+
context: {
|
|
21
|
+
integrations: _extends({}, newContext.integrations)
|
|
22
|
+
}
|
|
23
|
+
}), fn);
|
|
24
|
+
});
|
|
25
|
+
return window.analytics;
|
|
26
|
+
};
|
|
27
|
+
// add a flag to the patched analytics so we don't patch this twice
|
|
28
|
+
window.analytics[MPI_PATCH_FIELD] = true;
|
|
29
|
+
}
|
|
30
|
+
if (isClient) {
|
|
31
|
+
if (!window.analytics) {
|
|
32
|
+
console.warn("Segment Analytics is not loaded so patch is not applied.");
|
|
33
|
+
} else if (!window.analytics[MPI_PATCH_FIELD]) {
|
|
34
|
+
window.analytics.initialized ? monkeyPatchAnalyticsTrack() : window.analytics.ready(monkeyPatchAnalyticsTrack);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* Use this module to store and retrieve data from localStorage or sessionStorage
|
|
4
|
+
* @param {object} params
|
|
5
|
+
* @param {string} params.key
|
|
6
|
+
* @param {'getItem'|'removeItem'|'setItem'=} params.method
|
|
7
|
+
* @param {'localStorage'|'sessionStorage'=} params.type
|
|
8
|
+
* @param {string=} params.value
|
|
9
|
+
* @returns {string|void}
|
|
10
|
+
*/ export var storage = function(param) {
|
|
11
|
+
var _type = param.type, type = _type === void 0 ? "localStorage" : _type, _method = param.method, method = _method === void 0 ? "getItem" : _method, key = param.key, value = param.value;
|
|
12
|
+
try {
|
|
13
|
+
return window[type][method](key, value);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.error(e);
|
|
16
|
+
}
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ohos-ports/s-ui-segment-wrapper",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "5.4.0-beta.0",
|
|
4
|
+
"description": "Abstraction layer on top of the Segment library.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"lib": "sui-js-compiler",
|
|
9
|
+
"postlib": "npm run set:version",
|
|
10
|
+
"prepublishOnly": "npm run umd && npm run lib",
|
|
11
|
+
"set:version": "sed -i.bak \"s/process\\.env\\.VERSION/\\\"$npm_package_version\\\"/g\" lib/segmentWrapper.js && rm lib/segmentWrapper.js.bak",
|
|
12
|
+
"test": "npm run test:client",
|
|
13
|
+
"test:client": "VERSION=0.0.0 sui-test browser --src-pattern=src/index.js -H",
|
|
14
|
+
"test:client:watch": "VERSION=0.0.0 npm run test:client -- --watch",
|
|
15
|
+
"test:umd": "npm run umd && npx servor ./umd",
|
|
16
|
+
"umd": "VERSION=$npm_package_version sui-bundler lib src-umd/index.js -o umd/ -p --root"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@s-ui/js": "2",
|
|
20
|
+
"@swc/helpers": "^0.5.15",
|
|
21
|
+
"tiny-hashes": "1.0.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@s-ui/bundler": "9",
|
|
25
|
+
"@s-ui/js-compiler": "1",
|
|
26
|
+
"@s-ui/test": "8"
|
|
27
|
+
},
|
|
28
|
+
"config": {
|
|
29
|
+
"sui-bundler": {
|
|
30
|
+
"env": [
|
|
31
|
+
"VERSION"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"type": "module",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/ohos-ports/ohos-ports.git",
|
|
39
|
+
"directory": "ports/s-ui-segment-wrapper/5.4.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const MPI_CONFIG_KEY = '__mpi'
|
|
2
|
+
|
|
3
|
+
export const isClient = typeof window !== 'undefined'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the Segment Wrapper config from window
|
|
7
|
+
* @param {string=} key Key config to extract. If not provided, all the config will be returned
|
|
8
|
+
* @return {any} Config value or all the config if not key provided
|
|
9
|
+
*/
|
|
10
|
+
export const getConfig = key => {
|
|
11
|
+
const config = window?.[MPI_CONFIG_KEY]?.segmentWrapper || {}
|
|
12
|
+
|
|
13
|
+
return key ? config[key] : config
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Set a config value to the Segment Wrapper config
|
|
18
|
+
* @param {string} key Config key to update
|
|
19
|
+
* @param {boolean|string|number|object} value Value to set on the config key
|
|
20
|
+
*/
|
|
21
|
+
export const setConfig = (key, value) => {
|
|
22
|
+
window[MPI_CONFIG_KEY] = window[MPI_CONFIG_KEY] || {}
|
|
23
|
+
window[MPI_CONFIG_KEY].segmentWrapper = window[MPI_CONFIG_KEY].segmentWrapper || {}
|
|
24
|
+
window[MPI_CONFIG_KEY].segmentWrapper[key] = value
|
|
25
|
+
}
|
package/src/events.js
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import './utils/patchAnalytics.js'
|
|
2
|
+
|
|
3
|
+
import {campaignContext} from './middlewares/source/campaignContext.js'
|
|
4
|
+
import {pageData} from './middlewares/source/pageData.js'
|
|
5
|
+
import {defaultContextProperties} from './middlewares/source/defaultContextProperties.js'
|
|
6
|
+
import {pageReferrer} from './middlewares/source/pageReferrer.js'
|
|
7
|
+
import {userScreenInfo} from './middlewares/source/userScreenInfo.js'
|
|
8
|
+
import {userTraits} from './middlewares/source/userTraits.js'
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_DATA_LAYER_NAME,
|
|
11
|
+
getCampaignDetails,
|
|
12
|
+
loadGoogleAnalytics,
|
|
13
|
+
sendGoogleConsents
|
|
14
|
+
} from './repositories/googleRepository.js'
|
|
15
|
+
import {checkAnonymousId} from './utils/checkAnonymousId.js'
|
|
16
|
+
import {isGA4DestinationEnabled} from './utils/ga4Detection.js'
|
|
17
|
+
import {getConfig, isClient} from './config.js'
|
|
18
|
+
import analytics from './segmentWrapper.js'
|
|
19
|
+
import initTcfTracking from './tcf.js'
|
|
20
|
+
import {getUserDataAndNotify} from './universalId.js'
|
|
21
|
+
|
|
22
|
+
// Initialize TCF Tracking with Segment
|
|
23
|
+
initTcfTracking()
|
|
24
|
+
|
|
25
|
+
// Generate UniversalId if available
|
|
26
|
+
try {
|
|
27
|
+
getUserDataAndNotify()
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(`[segment-wrapper] UniversalID couldn't be initialized`) // eslint-disable-line
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Initialize middlewares
|
|
33
|
+
const addMiddlewares = () => {
|
|
34
|
+
const experimentalPageDataMiddleware = getConfig('experimentalPageDataMiddleware')
|
|
35
|
+
|
|
36
|
+
window.analytics.addSourceMiddleware(userTraits)
|
|
37
|
+
window.analytics.addSourceMiddleware(defaultContextProperties)
|
|
38
|
+
window.analytics.addSourceMiddleware(campaignContext)
|
|
39
|
+
window.analytics.addSourceMiddleware(userScreenInfo)
|
|
40
|
+
window.analytics.addSourceMiddleware(pageReferrer)
|
|
41
|
+
|
|
42
|
+
if (experimentalPageDataMiddleware) {
|
|
43
|
+
window.analytics.addSourceMiddleware(pageData)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isClient && window.analytics) {
|
|
48
|
+
// Check GA4 mode synchronously to avoid delaying initialization
|
|
49
|
+
const useSegmentGA4Destination = isGA4DestinationEnabled()
|
|
50
|
+
|
|
51
|
+
// Only initialize manual GA4 if destination is not enabled (legacy mode)
|
|
52
|
+
// This runs synchronously to ensure GA4 is ready for first events
|
|
53
|
+
if (!useSegmentGA4Destination) {
|
|
54
|
+
// Legacy behavior: Initialize Google Analytics manually
|
|
55
|
+
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')
|
|
56
|
+
const dataLayerName = getConfig('googleAnalyticsDataLayer') || DEFAULT_DATA_LAYER_NAME
|
|
57
|
+
const needsConsentManagement = getConfig('googleAnalyticsConsentManagement')
|
|
58
|
+
|
|
59
|
+
if (googleAnalyticsMeasurementId) {
|
|
60
|
+
const googleAnalyticsConfig = getConfig('googleAnalyticsConfig')
|
|
61
|
+
|
|
62
|
+
window[dataLayerName] = window[dataLayerName] || []
|
|
63
|
+
window.gtag =
|
|
64
|
+
window.gtag ||
|
|
65
|
+
function gtag() {
|
|
66
|
+
window[dataLayerName].push(arguments)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
window.gtag('js', new Date())
|
|
70
|
+
if (needsConsentManagement) sendGoogleConsents()
|
|
71
|
+
window.gtag('config', googleAnalyticsMeasurementId, {
|
|
72
|
+
cookie_prefix: 'segment',
|
|
73
|
+
send_page_view: false,
|
|
74
|
+
...googleAnalyticsConfig,
|
|
75
|
+
...getCampaignDetails()
|
|
76
|
+
})
|
|
77
|
+
loadGoogleAnalytics().catch(error => {
|
|
78
|
+
console.error(error)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
window.analytics.ready(checkAnonymousId)
|
|
84
|
+
window.analytics.addSourceMiddleware ? addMiddlewares() : window.analytics.ready(addMiddlewares)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default analytics
|
|
88
|
+
export {getGoogleClientId, getGoogleSessionId} from './repositories/googleRepository.js'
|
|
89
|
+
export {getUniversalId} from './universalId.js'
|
|
90
|
+
export {EVENTS} from './events.js'
|
|
91
|
+
|
|
92
|
+
// Deprecated Adobe Analytics functions - kept for backwards compatibility
|
|
93
|
+
// @deprecated Adobe Analytics integration has been removed
|
|
94
|
+
export const getAdobeVisitorData = () => Promise.resolve({trackingServer: '', version: ''})
|
|
95
|
+
// @deprecated Adobe Analytics integration has been removed
|
|
96
|
+
export const getAdobeMCVisitorID = () => Promise.resolve('')
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const optimizelySiteAttributeMiddleware = ({payload, next}) => {
|
|
2
|
+
const {
|
|
3
|
+
obj: {integrations, context}
|
|
4
|
+
} = payload
|
|
5
|
+
|
|
6
|
+
if (!context.site) {
|
|
7
|
+
return next(payload)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
payload.obj.integrations = {
|
|
11
|
+
...integrations,
|
|
12
|
+
Optimizely: {
|
|
13
|
+
...integrations.Optimizely,
|
|
14
|
+
attributes: {
|
|
15
|
+
site: context.site,
|
|
16
|
+
...integrations.Optimizely?.attributes
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
next(payload)
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {getCampaignDetails} from '../../repositories/googleRepository.js'
|
|
2
|
+
|
|
3
|
+
export const campaignContext = ({payload, next}) => {
|
|
4
|
+
const campaignDetails = getCampaignDetails({needsTransformation: false})
|
|
5
|
+
|
|
6
|
+
payload.obj.context = {
|
|
7
|
+
...payload.obj.context,
|
|
8
|
+
...campaignDetails
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
next(payload)
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {getConfig} from '../../config.js'
|
|
2
|
+
|
|
3
|
+
const DEFAULT_CONTEXT = {platform: 'web'}
|
|
4
|
+
|
|
5
|
+
export const defaultContextProperties = ({payload, next}) => {
|
|
6
|
+
const defaultContext = getConfig('defaultContext') || {}
|
|
7
|
+
|
|
8
|
+
payload.obj.context = {
|
|
9
|
+
...DEFAULT_CONTEXT,
|
|
10
|
+
...defaultContext,
|
|
11
|
+
...payload.obj.context
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
next(payload)
|
|
15
|
+
}
|