@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.
Files changed (48) hide show
  1. package/README.md +269 -2
  2. package/lib/config.js +20 -0
  3. package/lib/events.js +3 -0
  4. package/lib/index.js +82 -0
  5. package/lib/middlewares/destination/optimizelySiteAttribute.js +17 -0
  6. package/lib/middlewares/source/campaignContext.js +10 -0
  7. package/lib/middlewares/source/defaultContextProperties.js +11 -0
  8. package/lib/middlewares/source/optimizelyUserId.js +15 -0
  9. package/lib/middlewares/source/pageData.js +42 -0
  10. package/lib/middlewares/source/pageReferrer.js +102 -0
  11. package/lib/middlewares/source/userScreenInfo.js +12 -0
  12. package/lib/middlewares/source/userTraits.js +48 -0
  13. package/lib/repositories/googleRepository.js +272 -0
  14. package/lib/repositories/xandrRepository.js +42 -0
  15. package/lib/segmentWrapper.js +364 -0
  16. package/lib/tcf.js +230 -0
  17. package/lib/universalId.js +48 -0
  18. package/lib/utils/checkAnonymousId.js +9 -0
  19. package/lib/utils/cookies.js +50 -0
  20. package/lib/utils/ga4Detection.js +18 -0
  21. package/lib/utils/hashEmail.js +24 -0
  22. package/lib/utils/patchAnalytics.js +36 -0
  23. package/lib/utils/storage.js +17 -0
  24. package/package.json +39 -4
  25. package/src/config.js +25 -0
  26. package/src/events.js +3 -0
  27. package/src/index.js +96 -0
  28. package/src/middlewares/destination/optimizelySiteAttribute.js +22 -0
  29. package/src/middlewares/source/campaignContext.js +12 -0
  30. package/src/middlewares/source/defaultContextProperties.js +15 -0
  31. package/src/middlewares/source/optimizelyUserId.js +20 -0
  32. package/src/middlewares/source/pageData.js +45 -0
  33. package/src/middlewares/source/pageReferrer.js +125 -0
  34. package/src/middlewares/source/userScreenInfo.js +11 -0
  35. package/src/middlewares/source/userTraits.js +41 -0
  36. package/src/repositories/googleRepository.js +251 -0
  37. package/src/repositories/xandrRepository.js +48 -0
  38. package/src/segmentWrapper.js +338 -0
  39. package/src/tcf.js +263 -0
  40. package/src/universalId.js +51 -0
  41. package/src/utils/checkAnonymousId.js +10 -0
  42. package/src/utils/cookies.js +56 -0
  43. package/src/utils/ga4Detection.js +22 -0
  44. package/src/utils/hashEmail.js +33 -0
  45. package/src/utils/patchAnalytics.js +46 -0
  46. package/src/utils/storage.js +18 -0
  47. package/src-umd/index.js +8 -0
  48. 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": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
@@ -0,0 +1,3 @@
1
+ export const EVENTS = {
2
+ GA4_INIT_EVENT_SENT: 'ga4InitEventSent'
3
+ }
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
+ }