@adobe/alloy 2.28.1 → 2.29.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 (80) hide show
  1. package/libEs5/components/Advertising/configValidators.js +26 -0
  2. package/libEs5/components/Advertising/constants/index.js +78 -0
  3. package/libEs5/components/Advertising/createComponent.js +59 -0
  4. package/libEs5/components/Advertising/handlers/clickThroughHandler.js +81 -0
  5. package/libEs5/components/Advertising/handlers/createAdConversionHandler.js +55 -0
  6. package/libEs5/components/Advertising/handlers/onBeforeSendEventHandler.js +69 -0
  7. package/libEs5/components/Advertising/handlers/sendAdConversion.js +67 -0
  8. package/libEs5/components/Advertising/handlers/viewThroughHandler.js +56 -0
  9. package/libEs5/components/Advertising/identities/collectAllIdentities.js +34 -0
  10. package/libEs5/components/Advertising/identities/collectID5Id.js +112 -0
  11. package/libEs5/components/Advertising/identities/collectRampId.js +183 -0
  12. package/libEs5/components/Advertising/identities/collectSurferId.js +146 -0
  13. package/libEs5/components/Advertising/index.js +52 -0
  14. package/libEs5/components/Advertising/utils/advertisingCookieManager.js +80 -0
  15. package/libEs5/components/Advertising/utils/helpers.js +123 -0
  16. package/libEs5/components/DataCollector/validateUserEventOptions.js +5 -1
  17. package/libEs5/constants/consentStatus.js +5 -2
  18. package/libEs5/constants/libraryVersion.js +1 -1
  19. package/libEs5/core/componentCreators.js +8 -1
  20. package/libEs5/utils/dom/index.js +7 -0
  21. package/libEs5/utils/dom/loadScript.js +93 -0
  22. package/libEs6/components/Advertising/configValidators.js +24 -0
  23. package/libEs6/components/Advertising/constants/index.js +75 -0
  24. package/libEs6/components/Advertising/createComponent.js +56 -0
  25. package/libEs6/components/Advertising/handlers/clickThroughHandler.js +79 -0
  26. package/libEs6/components/Advertising/handlers/createAdConversionHandler.js +52 -0
  27. package/libEs6/components/Advertising/handlers/onBeforeSendEventHandler.js +66 -0
  28. package/libEs6/components/Advertising/handlers/sendAdConversion.js +65 -0
  29. package/libEs6/components/Advertising/handlers/viewThroughHandler.js +53 -0
  30. package/libEs6/components/Advertising/identities/collectAllIdentities.js +31 -0
  31. package/libEs6/components/Advertising/identities/collectID5Id.js +108 -0
  32. package/libEs6/components/Advertising/identities/collectRampId.js +179 -0
  33. package/libEs6/components/Advertising/identities/collectSurferId.js +143 -0
  34. package/libEs6/components/Advertising/index.js +49 -0
  35. package/libEs6/components/Advertising/utils/advertisingCookieManager.js +77 -0
  36. package/libEs6/components/Advertising/utils/helpers.js +113 -0
  37. package/libEs6/components/DataCollector/validateUserEventOptions.js +6 -2
  38. package/libEs6/constants/consentStatus.js +4 -1
  39. package/libEs6/constants/libraryVersion.js +1 -1
  40. package/libEs6/core/componentCreators.js +2 -1
  41. package/libEs6/utils/dom/index.js +1 -0
  42. package/libEs6/utils/dom/loadScript.js +90 -0
  43. package/package.json +2 -2
  44. package/types/components/Advertising/configValidators.d.ts +3 -0
  45. package/types/components/Advertising/configValidators.d.ts.map +1 -0
  46. package/types/components/Advertising/constants/index.d.ts +46 -0
  47. package/types/components/Advertising/constants/index.d.ts.map +1 -0
  48. package/types/components/Advertising/createComponent.d.ts +18 -0
  49. package/types/components/Advertising/createComponent.d.ts.map +1 -0
  50. package/types/components/Advertising/handlers/clickThroughHandler.d.ts +22 -0
  51. package/types/components/Advertising/handlers/clickThroughHandler.d.ts.map +1 -0
  52. package/types/components/Advertising/handlers/createAdConversionHandler.d.ts +13 -0
  53. package/types/components/Advertising/handlers/createAdConversionHandler.d.ts.map +1 -0
  54. package/types/components/Advertising/handlers/onBeforeSendEventHandler.d.ts +21 -0
  55. package/types/components/Advertising/handlers/onBeforeSendEventHandler.d.ts.map +1 -0
  56. package/types/components/Advertising/handlers/sendAdConversion.d.ts +10 -0
  57. package/types/components/Advertising/handlers/sendAdConversion.d.ts.map +1 -0
  58. package/types/components/Advertising/handlers/viewThroughHandler.d.ts +9 -0
  59. package/types/components/Advertising/handlers/viewThroughHandler.d.ts.map +1 -0
  60. package/types/components/Advertising/identities/collectAllIdentities.d.ts +7 -0
  61. package/types/components/Advertising/identities/collectAllIdentities.d.ts.map +1 -0
  62. package/types/components/Advertising/identities/collectID5Id.d.ts +3 -0
  63. package/types/components/Advertising/identities/collectID5Id.d.ts.map +1 -0
  64. package/types/components/Advertising/identities/collectRampId.d.ts +3 -0
  65. package/types/components/Advertising/identities/collectRampId.d.ts.map +1 -0
  66. package/types/components/Advertising/identities/collectSurferId.d.ts +3 -0
  67. package/types/components/Advertising/identities/collectSurferId.d.ts.map +1 -0
  68. package/types/components/Advertising/index.d.ts +23 -0
  69. package/types/components/Advertising/index.d.ts.map +1 -0
  70. package/types/components/Advertising/utils/advertisingCookieManager.d.ts +9 -0
  71. package/types/components/Advertising/utils/advertisingCookieManager.d.ts.map +1 -0
  72. package/types/components/Advertising/utils/helpers.d.ts +23 -0
  73. package/types/components/Advertising/utils/helpers.d.ts.map +1 -0
  74. package/types/components/DataCollector/validateUserEventOptions.d.ts.map +1 -1
  75. package/types/constants/consentStatus.d.ts +3 -0
  76. package/types/constants/consentStatus.d.ts.map +1 -1
  77. package/types/core/componentCreators.d.ts +1 -0
  78. package/types/utils/dom/index.d.ts +1 -0
  79. package/types/utils/dom/loadScript.d.ts +17 -0
  80. package/types/utils/dom/loadScript.d.ts.map +1 -0
@@ -0,0 +1,108 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+ import { loadScript } from "../../../utils/dom/index.js";
13
+ import { ID5_SCRIPT_URL } from "../constants/index.js";
14
+ let id5Id = "";
15
+ let inProgressId5Promise = null;
16
+ const SHORT_TIMEOUT_MS = 5000;
17
+ const DEFAULT_TIMEOUT_MS = 30000;
18
+ const initiateID5Call = (partnerId, useShortTimeout, logger) => {
19
+ partnerId = Math.floor(Number(partnerId));
20
+ if (inProgressId5Promise) {
21
+ return inProgressId5Promise;
22
+ }
23
+ const timeoutMs = useShortTimeout ? SHORT_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
24
+ let iD5TimedOut = false;
25
+ const id5ResolutionPromise = new Promise((resolve, reject) => {
26
+ if (!partnerId) {
27
+ logger.error("Missing partner ID");
28
+ reject(new Error("ID5 partner ID is required"));
29
+ return;
30
+ }
31
+ const handleId5 = () => {
32
+ try {
33
+ if (typeof window.ID5 === "undefined") {
34
+ reject(new Error("ID5 object not available after script load"));
35
+ return;
36
+ }
37
+ const id5Instance = window.ID5.init({
38
+ partnerId
39
+ });
40
+ id5Id = id5Instance.getUserId();
41
+ const safeResolve = val => {
42
+ if (!iD5TimedOut) resolve(val);
43
+ };
44
+ if (!id5Id) {
45
+ window.ID5.init({
46
+ partnerId
47
+ }).onAvailable(function firstRetry(retryStatus) {
48
+ id5Id = retryStatus.getUserId();
49
+ if (id5Id) {
50
+ safeResolve(id5Id);
51
+ } else {
52
+ window.ID5.init({
53
+ partnerId
54
+ }).onAvailable(function secondRetry(secondRetryStatus) {
55
+ id5Id = secondRetryStatus.getUserId();
56
+ if (id5Id) {
57
+ safeResolve(id5Id);
58
+ } else {
59
+ logger.error("Failed to get ID5 ID after all retries");
60
+ safeResolve(null);
61
+ }
62
+ });
63
+ }
64
+ }, 1000);
65
+ } else {
66
+ safeResolve(id5Id);
67
+ }
68
+ } catch (error) {
69
+ logger.error("Error during ID5 initialization", error);
70
+ reject(error);
71
+ }
72
+ };
73
+ if (typeof window.ID5 !== "undefined") {
74
+ handleId5();
75
+ } else {
76
+ loadScript(ID5_SCRIPT_URL, {
77
+ onLoad: handleId5,
78
+ onError: error => {
79
+ logger.error("Script loading failed", error);
80
+ reject(error);
81
+ }
82
+ });
83
+ }
84
+ });
85
+ const timeoutPromise = new Promise(resolve => {
86
+ setTimeout(() => {
87
+ iD5TimedOut = true;
88
+ resolve(null);
89
+ }, timeoutMs);
90
+ });
91
+ inProgressId5Promise = Promise.race([id5ResolutionPromise, timeoutPromise]).finally(() => {
92
+ inProgressId5Promise = null;
93
+ });
94
+ return inProgressId5Promise;
95
+ };
96
+ const getID5Id = function getID5Id(logger, partnerId, resolveId5IfNotAvailable = true, useShortTimeout = false) {
97
+ if (id5Id && id5Id !== "") {
98
+ return Promise.resolve(id5Id);
99
+ }
100
+ if (!resolveId5IfNotAvailable) {
101
+ return Promise.resolve(null);
102
+ }
103
+ return initiateID5Call(partnerId, useShortTimeout, logger).then(resolvedId => resolvedId).catch(error => {
104
+ logger.error("Failed to get ID5 ID", error);
105
+ throw error;
106
+ });
107
+ };
108
+ export { getID5Id, initiateID5Call };
@@ -0,0 +1,179 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { loadScript } from "../../../utils/dom/index.js";
14
+ import { RAMP_ID, RAMP_ID_EXPIRES } from "../constants/index.js";
15
+ const RETRY_CONFIG = {
16
+ MAX_COUNT: 15,
17
+ MAX_TIME_MS: 30000,
18
+ DELAY_BASE_MS: 500,
19
+ MAX_DELAY_MS: 5000,
20
+ SHORT_TIMEOUT_MS: 5000
21
+ };
22
+ const state = {
23
+ rampIdEnv: undefined,
24
+ rampIdCallInitiated: false,
25
+ inProgressRampIdPromise: null,
26
+ scriptLoadingInitiated: false,
27
+ envelopeRetrievalInProgress: false
28
+ };
29
+ const processEnvelope = (envelope, resolve, cookieManager, logger) => {
30
+ let parsedEnvelope;
31
+ try {
32
+ parsedEnvelope = JSON.parse(envelope).envelope;
33
+ } catch {
34
+ parsedEnvelope = envelope;
35
+ }
36
+ if (parsedEnvelope && !state.rampIdCallInitiated) {
37
+ state.rampIdCallInitiated = true;
38
+ state.rampIdEnv = parsedEnvelope;
39
+ state.envelopeRetrievalInProgress = false;
40
+ state.inProgressRampIdPromise = null;
41
+ cookieManager.setValue(RAMP_ID, state.rampIdEnv);
42
+ cookieManager.setValue(RAMP_ID_EXPIRES, Date.now() + 48 * 60 * 60 * 1000); //expires in 48 hours
43
+ resolve(state.rampIdEnv);
44
+ } else {
45
+ logger.warn("Invalid RampID envelope received", {
46
+ envelope: parsedEnvelope
47
+ });
48
+ state.envelopeRetrievalInProgress = false;
49
+ }
50
+ };
51
+ const retrieveEnvelopeWithRetries = (resolve, reject, cookieManager, logger) => {
52
+ let retryCount = RETRY_CONFIG.MAX_COUNT;
53
+ let timerMultiplier = 1;
54
+ let totalElapsedTime = 0;
55
+ const tryToRetrieve = () => {
56
+ if (state.rampIdEnv) return;
57
+ if (totalElapsedTime > RETRY_CONFIG.MAX_TIME_MS) {
58
+ logger.error("Maximum retry time exceeded");
59
+ state.envelopeRetrievalInProgress = false;
60
+ reject(new Error("Failed to retrieve RampID - timeout"));
61
+ state.inProgressRampIdPromise = null;
62
+ return;
63
+ }
64
+ if (retryCount === 0) {
65
+ logger.error("Maximum retries exceeded");
66
+ state.envelopeRetrievalInProgress = false;
67
+ reject(new Error("Failed to retrieve RampID after maximum retries"));
68
+ state.inProgressRampIdPromise = null;
69
+ return;
70
+ }
71
+ const delay = Math.min(RETRY_CONFIG.DELAY_BASE_MS * timerMultiplier, RETRY_CONFIG.MAX_DELAY_MS);
72
+ setTimeout(() => {
73
+ totalElapsedTime += delay;
74
+ retryCount -= 1;
75
+ timerMultiplier += 1;
76
+ if (typeof window.ats !== "undefined" && window.ats !== null && !state.rampIdEnv && !state.envelopeRetrievalInProgress) {
77
+ state.envelopeRetrievalInProgress = true;
78
+ window.ats.retrieveEnvelope().then(rampIdResponse => {
79
+ processEnvelope(rampIdResponse, resolve, cookieManager, logger);
80
+ if (!state.rampIdEnv) tryToRetrieve();
81
+ }, () => {
82
+ logger.warn("Failed to retrieve envelope");
83
+ state.envelopeRetrievalInProgress = false;
84
+ tryToRetrieve();
85
+ });
86
+ } else {
87
+ tryToRetrieve();
88
+ }
89
+ }, delay);
90
+ };
91
+ tryToRetrieve();
92
+ };
93
+ const initiateRampIDCall = (rampIdScriptPath, cookieManager, logger, useShortTimeout = false) => {
94
+ if (state.inProgressRampIdPromise) {
95
+ return state.inProgressRampIdPromise;
96
+ }
97
+ const timeoutMs = useShortTimeout ? RETRY_CONFIG.SHORT_TIMEOUT_MS : RETRY_CONFIG.MAX_TIME_MS;
98
+ let timedOut = false;
99
+ const mainPromise = new Promise((resolve, reject) => {
100
+ loadScript(rampIdScriptPath, {
101
+ onLoad: () => {
102
+ if (typeof window.ats !== "undefined" && !state.envelopeRetrievalInProgress) {
103
+ state.envelopeRetrievalInProgress = true;
104
+ window.ats.retrieveEnvelope().then(envelopeResponse => {
105
+ if (envelopeResponse) {
106
+ if (!timedOut) {
107
+ processEnvelope(envelopeResponse, resolve, cookieManager, logger);
108
+ }
109
+ } else {
110
+ state.envelopeRetrievalInProgress = false;
111
+ window.addEventListener("lrEnvelopePresent", () => {
112
+ if (state.envelopeRetrievalInProgress || timedOut) return;
113
+ state.envelopeRetrievalInProgress = true;
114
+ window.ats.retrieveEnvelope().then(envelopeResult => {
115
+ if (!timedOut) {
116
+ processEnvelope(envelopeResult, resolve, cookieManager, logger);
117
+ }
118
+ }, error => {
119
+ if (!timedOut) {
120
+ logger.error("Failed to retrieve envelope after event", error);
121
+ state.envelopeRetrievalInProgress = false;
122
+ reject(error);
123
+ state.inProgressRampIdPromise = null;
124
+ }
125
+ });
126
+ });
127
+ }
128
+ }).catch(error => {
129
+ if (!timedOut) {
130
+ logger.error("Error retrieving envelope", error);
131
+ state.envelopeRetrievalInProgress = false;
132
+ reject(error);
133
+ state.inProgressRampIdPromise = null;
134
+ }
135
+ });
136
+ }
137
+ retrieveEnvelopeWithRetries(val => {
138
+ if (!timedOut) resolve(val);
139
+ }, err => {
140
+ if (!timedOut) reject(err);
141
+ }, cookieManager, logger);
142
+ },
143
+ onError: error => {
144
+ if (!timedOut) {
145
+ reject(error);
146
+ }
147
+ state.inProgressRampIdPromise = null;
148
+ }
149
+ });
150
+ });
151
+ const timeoutPromise = new Promise(resolve => {
152
+ setTimeout(() => {
153
+ timedOut = true;
154
+ resolve(null);
155
+ }, timeoutMs);
156
+ });
157
+ state.inProgressRampIdPromise = Promise.race([mainPromise, timeoutPromise]).finally(() => {
158
+ state.inProgressRampIdPromise = null;
159
+ });
160
+ return state.inProgressRampIdPromise;
161
+ };
162
+ const getRampId = (logger, rampIdScriptPath, cookieManager, resolveRampIdIfNotAvailable = true, useShortTimeout = false) => {
163
+ if (state.rampIdEnv) {
164
+ return Promise.resolve(state.rampIdEnv);
165
+ }
166
+ if (cookieManager) {
167
+ const rampIdFromCookie = cookieManager.getValue(RAMP_ID);
168
+ const rampIdExpires = cookieManager.getValue(RAMP_ID_EXPIRES);
169
+ if (rampIdFromCookie && rampIdExpires && rampIdExpires > Date.now()) {
170
+ state.rampIdEnv = rampIdFromCookie;
171
+ return Promise.resolve(rampIdFromCookie);
172
+ }
173
+ }
174
+ if (!resolveRampIdIfNotAvailable || rampIdScriptPath == null) {
175
+ return Promise.resolve(null);
176
+ }
177
+ return initiateRampIDCall(rampIdScriptPath, cookieManager, logger, useShortTimeout);
178
+ };
179
+ export { getRampId, initiateRampIDCall };
@@ -0,0 +1,143 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed under the Apache License, Version 2.0.
4
+ */
5
+
6
+ import { DISPLAY_CLICK_COOKIE_KEY, SURFER_ID, SURFER_PIXEL_HOST, SURFER_USER_ID, SURFER_TIMEOUT_MS, SURFER_TRUSTED_ORIGIN, SURFER_PARAM_KEY, DISPLAY_CLICK_COOKIE_KEY_EXPIRES } from "../constants/index.js";
7
+ import createNode from "../../../utils/dom/createNode.js";
8
+ import { injectAreThirdPartyCookiesSupportedByDefault } from "../../../utils/index.js";
9
+ let surferId = "";
10
+ let displayClickCookie = "";
11
+ let inProgressSurferPromise = null;
12
+ const addToDom = element => {
13
+ if (document.body) {
14
+ document.body.appendChild(element);
15
+ } else {
16
+ window.addEventListener("load", () => {
17
+ document.body.appendChild(element);
18
+ }, false);
19
+ }
20
+ };
21
+ const getInvisibleIframeElement = url => createNode("iframe", {
22
+ src: url
23
+ }, {
24
+ height: 0,
25
+ width: 0,
26
+ frameBorder: 0,
27
+ style: {
28
+ display: "none"
29
+ }
30
+ }, []);
31
+ const addListener = fn => window.addEventListener("message", fn, false);
32
+ const removeListener = fn => window.removeEventListener("message", fn, false);
33
+ const initiateAdvertisingIdentityCall = () => {
34
+ if (inProgressSurferPromise) {
35
+ return inProgressSurferPromise;
36
+ }
37
+ inProgressSurferPromise = new Promise((resolve, reject) => {
38
+ setTimeout(() => {
39
+ const scheme = document.location.protocol === "https:" ? "https:" : "http:";
40
+ const nestedParams = new URLSearchParams({
41
+ google: "__EFGCK__",
42
+ gsurfer: "__EFGSURFER__",
43
+ imsId: "__EFIMSORGID__",
44
+ is_fb_cookie_synced: "__EFFB__",
45
+ optout: "__EFOPTOUT__",
46
+ throttleCookie: "__EFSYNC__",
47
+ time: "__EFTIME__",
48
+ ev_lcc: "__LCC__"
49
+ });
50
+ const nestedUrl = `${scheme}//www.everestjs.net/static/pixel_details.html#${nestedParams.toString()}`;
51
+ const mainParams = new URLSearchParams({
52
+ ev_gb: "0",
53
+ url: nestedUrl
54
+ });
55
+ const pixelDetailsUrl = `${scheme}//${SURFER_PIXEL_HOST}/${SURFER_USER_ID}/gr?${mainParams.toString()}`;
56
+ const iframeElement = getInvisibleIframeElement(pixelDetailsUrl);
57
+ addToDom(iframeElement);
58
+ const pixelDetailsReceiver = function pixelDetailsReceiver(message) {
59
+ if (!message.origin.includes(SURFER_TRUSTED_ORIGIN)) {
60
+ return;
61
+ }
62
+ try {
63
+ const pixelRedirectUri = message.data;
64
+ const hashIndex = pixelRedirectUri.indexOf("#");
65
+ if (hashIndex === -1) {
66
+ resolve({
67
+ surferId: null,
68
+ displayClickCookie: null
69
+ });
70
+ return;
71
+ }
72
+ const hashParams = new URLSearchParams(pixelRedirectUri.substring(hashIndex + 1));
73
+ let resolvedSurferId;
74
+ let resolvedDisplayClickCookie;
75
+ const surferValue = hashParams.get(SURFER_PARAM_KEY);
76
+ if (surferValue) {
77
+ resolvedSurferId = surferValue;
78
+ }
79
+ const displayClickValue = hashParams.get(DISPLAY_CLICK_COOKIE_KEY);
80
+ if (displayClickValue && displayClickValue !== "__LCC__") {
81
+ resolvedDisplayClickCookie = displayClickValue;
82
+ }
83
+ removeListener(pixelDetailsReceiver);
84
+ if (resolvedSurferId) {
85
+ surferId = resolvedSurferId;
86
+ displayClickCookie = resolvedDisplayClickCookie;
87
+ resolve({
88
+ surferId,
89
+ displayClickCookie
90
+ });
91
+ } else {
92
+ resolve({
93
+ surferId: null,
94
+ displayClickCookie: null
95
+ });
96
+ }
97
+ } catch (err) {
98
+ reject(err);
99
+ } finally {
100
+ inProgressSurferPromise = null;
101
+ }
102
+ };
103
+ addListener(pixelDetailsReceiver);
104
+ }, SURFER_TIMEOUT_MS);
105
+ });
106
+ return inProgressSurferPromise;
107
+ };
108
+ const collectSurferId = function collectSurferId(cookieManager, getBrowser, resolveSurferIdIfNotAvailable = true) {
109
+ if (getBrowser) {
110
+ const areThirdPartyCookiesSupportedByDefault = injectAreThirdPartyCookiesSupportedByDefault({
111
+ getBrowser
112
+ });
113
+ if (!areThirdPartyCookiesSupportedByDefault()) {
114
+ return Promise.resolve(null);
115
+ }
116
+ }
117
+ if (surferId && surferId !== "") {
118
+ return Promise.resolve(surferId);
119
+ }
120
+ if (cookieManager) {
121
+ const cookieSurferId = cookieManager.getValue(SURFER_ID);
122
+ if (cookieSurferId) {
123
+ surferId = cookieSurferId;
124
+ return Promise.resolve(cookieSurferId);
125
+ }
126
+ }
127
+ if (resolveSurferIdIfNotAvailable) {
128
+ return initiateAdvertisingIdentityCall().then(resolvedId => {
129
+ if (cookieManager && resolvedId) {
130
+ if (resolvedId.surferId) {
131
+ cookieManager.setValue(SURFER_ID, resolvedId.surferId);
132
+ }
133
+ if (resolvedId.displayClickCookie) {
134
+ cookieManager.setValue(DISPLAY_CLICK_COOKIE_KEY, resolvedId.displayClickCookie);
135
+ cookieManager.setValue(DISPLAY_CLICK_COOKIE_KEY_EXPIRES, Date.now() + 15 * 60 * 1000);
136
+ }
137
+ }
138
+ return resolvedId.surferId;
139
+ });
140
+ }
141
+ return Promise.resolve(null);
142
+ };
143
+ export default collectSurferId;
@@ -0,0 +1,49 @@
1
+ /*
2
+ Copyright 2023 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import createComponent from "./createComponent.js";
14
+ import configValidators from "./configValidators.js";
15
+ import { createDataCollectionRequest, createDataCollectionRequestPayload } from "../../utils/request/index.js";
16
+ import createAdConversionHandler from "./handlers/createAdConversionHandler.js";
17
+ import createCookieManager from "./utils/advertisingCookieManager.js";
18
+ const createAdvertising = ({
19
+ logger,
20
+ config,
21
+ eventManager,
22
+ sendEdgeNetworkRequest,
23
+ consent,
24
+ getBrowser
25
+ }) => {
26
+ const cookieManager = createCookieManager({
27
+ orgId: config.orgId,
28
+ logger
29
+ });
30
+ const adConversionHandler = createAdConversionHandler({
31
+ eventManager,
32
+ sendEdgeNetworkRequest,
33
+ consent,
34
+ createDataCollectionRequest,
35
+ createDataCollectionRequestPayload,
36
+ logger
37
+ });
38
+ return createComponent({
39
+ logger,
40
+ config,
41
+ eventManager,
42
+ cookieManager,
43
+ adConversionHandler,
44
+ getBrowser
45
+ });
46
+ };
47
+ createAdvertising.namespace = "Advertising";
48
+ createAdvertising.configValidators = configValidators;
49
+ export default createAdvertising;
@@ -0,0 +1,77 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { getNamespacedCookieName, cookieJar } from "../../../utils/index.js";
14
+ import createLoggingCookieJar from "../../../utils/createLoggingCookieJar.js";
15
+ import { ADVERTISING_COOKIE_KEY, DEFAULT_COOKIE_EXPIRATION_MINUTES, DEFAULT_THROTTLE_MINUTES } from "../constants/index.js";
16
+ export default ({
17
+ orgId,
18
+ logger
19
+ }) => {
20
+ const loggingCookieJar = createLoggingCookieJar({
21
+ logger,
22
+ cookieJar
23
+ });
24
+ const getCookieName = (key, useNamespace = true) => useNamespace ? getNamespacedCookieName(orgId, key) : key;
25
+ const getDefaultExpiration = (minutes = DEFAULT_THROTTLE_MINUTES) => new Date(Date.now() + minutes * 60 * 1000);
26
+ const safeJsonParse = value => {
27
+ try {
28
+ if (value?.startsWith("%7B") || value?.startsWith("{")) {
29
+ return JSON.parse(decodeURIComponent(value));
30
+ }
31
+ } catch {
32
+ // pass
33
+ }
34
+ return value;
35
+ };
36
+ const safeJsonStringify = value => typeof value === "object" && value !== null ? encodeURIComponent(JSON.stringify(value)) : value;
37
+ const readCookie = (key, useNamespace = true) => {
38
+ try {
39
+ const name = getCookieName(key, useNamespace);
40
+ const value = loggingCookieJar.get(name);
41
+ return value ? safeJsonParse(value) : null;
42
+ } catch (error) {
43
+ logger.error(`Error reading cookie: ${key}`, error);
44
+ return null;
45
+ }
46
+ };
47
+ const writeCookie = (key, value, options = {}, useNamespace = true) => {
48
+ try {
49
+ const name = getCookieName(key, useNamespace);
50
+ const storedValue = safeJsonStringify(value);
51
+ loggingCookieJar.set(name, storedValue, options);
52
+ return true;
53
+ } catch (error) {
54
+ logger.error(`Error writing cookie: ${key}`, error);
55
+ return false;
56
+ }
57
+ };
58
+ const setValue = (key, value, options = {}) => {
59
+ const existing = readCookie(ADVERTISING_COOKIE_KEY) || {};
60
+ const updated = {
61
+ ...existing,
62
+ [key]: value
63
+ };
64
+ return writeCookie(ADVERTISING_COOKIE_KEY, updated, {
65
+ expires: getDefaultExpiration(DEFAULT_COOKIE_EXPIRATION_MINUTES),
66
+ ...options
67
+ });
68
+ };
69
+ const getValue = key => {
70
+ const data = readCookie(ADVERTISING_COOKIE_KEY) || {};
71
+ return data[key];
72
+ };
73
+ return {
74
+ setValue,
75
+ getValue
76
+ };
77
+ };
@@ -0,0 +1,113 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { SKWCID_PARAM, EFID_PARAM, UNKNOWN_ADVERTISER, LAST_CLICK_COOKIE_KEY, LOG_ID_CONVERSION_SUCCESS, LAST_CONVERSION_TIME_KEY, DISPLAY_CLICK_COOKIE_KEY, AD_CONVERSION_VIEW_EVENT_TYPE, SURFER_ID, ID5_ID, RAMP_ID, DISPLAY_CLICK_COOKIE_KEY_EXPIRES } from "../constants/index.js";
14
+ import { queryString } from "../../../utils/index.js";
15
+ const getUrlParams = () => {
16
+ const parsedParams = queryString.parse(window.location.search);
17
+ return {
18
+ skwcid: parsedParams[SKWCID_PARAM],
19
+ // "s_kwcid"
20
+ efid: parsedParams[EFID_PARAM] // "ef_id"
21
+ };
22
+ };
23
+
24
+ /**
25
+ * Manages a single, ongoing asynchronous operation to prevent redundant calls.
26
+ * @param {string} operationName - A name for the operation for logging purposes.
27
+ * @param {Function} workerFn - A function that returns a Promise to be executed.
28
+ * @returns {Function} A function that, when called, will either start the worker or return the in-progress promise.
29
+ */
30
+ const createManagedAsyncOperation = (operationName, workerFn) => {
31
+ let inProgressPromise = null;
32
+ return (...args) => {
33
+ if (inProgressPromise) {
34
+ return inProgressPromise;
35
+ }
36
+ inProgressPromise = workerFn(...args).finally(() => {
37
+ inProgressPromise = null;
38
+ });
39
+ return inProgressPromise;
40
+ };
41
+ };
42
+
43
+ /**
44
+ * Normalizes advertiser settings - extracts enabled advertiser IDs
45
+ * @param {Object[]} advertiserSettings - Array of advertiserSettings objects with advertiserId and enabled properties
46
+ * @returns {string} Comma-separated string of enabled advertiser IDs
47
+ */
48
+ const normalizeAdvertiser = advertiserSettings => {
49
+ if (!advertiserSettings || !Array.isArray(advertiserSettings)) {
50
+ return UNKNOWN_ADVERTISER;
51
+ }
52
+ return advertiserSettings.filter(item => item && item.enabled === true && item.advertiserId).map(item => item.advertiserId).join(", ");
53
+ };
54
+ const appendAdvertisingIdQueryToEvent = (idsToInclude, event, cookieManager, componentConfig, addEventType = false) => {
55
+ const searchClickData = cookieManager.getValue(LAST_CLICK_COOKIE_KEY);
56
+ let displayClickCookie = null;
57
+ const displayClickCookieExpires = cookieManager.getValue(DISPLAY_CLICK_COOKIE_KEY_EXPIRES);
58
+ if (displayClickCookieExpires && displayClickCookieExpires > Date.now()) {
59
+ displayClickCookie = cookieManager.getValue(DISPLAY_CLICK_COOKIE_KEY);
60
+ }
61
+ const query = {
62
+ advertising: {
63
+ ...(searchClickData?.click_time && {
64
+ lastSearchClick: searchClickData.click_time
65
+ }),
66
+ ...(displayClickCookie && {
67
+ lastDisplayClick: displayClickCookie
68
+ }),
69
+ stitchIds: {
70
+ ...(idsToInclude[SURFER_ID] && {
71
+ surferId: idsToInclude[SURFER_ID]
72
+ }),
73
+ ...(idsToInclude[ID5_ID] && {
74
+ id5: idsToInclude[ID5_ID]
75
+ }),
76
+ ...(idsToInclude[RAMP_ID] && {
77
+ rampIdEnv: idsToInclude[RAMP_ID]
78
+ }),
79
+ ipAddress: "DUMMY_IP_ADDRESS"
80
+ },
81
+ advIds: normalizeAdvertiser(componentConfig.advertiserSettings),
82
+ ...(addEventType && {
83
+ eventType: AD_CONVERSION_VIEW_EVENT_TYPE
84
+ })
85
+ }
86
+ };
87
+ event.mergeQuery(query);
88
+ return event;
89
+ };
90
+ const isAnyIdUnused = (availableIds, conversionCalled) => {
91
+ return Object.entries(availableIds).some(([idType]) => {
92
+ return !conversionCalled[idType];
93
+ });
94
+ };
95
+ const markIdsAsConverted = (idTypes, conversionCalled, cookieManager, logger) => {
96
+ const now = Date.now();
97
+ idTypes.forEach(idType => {
98
+ conversionCalled[idType] = true;
99
+ cookieManager.setValue(`${idType}_last_conversion`, now);
100
+ logger.info(LOG_ID_CONVERSION_SUCCESS.replace("{0}", idType));
101
+ });
102
+ cookieManager.setValue(LAST_CONVERSION_TIME_KEY, now);
103
+ };
104
+ const isThrottled = (idType, cookieManager) => {
105
+ const now = Date.now();
106
+ const THROTTLE_WINDOW = 30 * 60 * 1000; // 30 minutes
107
+ const lastSuccessfulConversion = cookieManager.getValue(`${idType}_last_conversion`);
108
+ return Boolean(lastSuccessfulConversion && now - lastSuccessfulConversion < THROTTLE_WINDOW);
109
+ };
110
+ const shouldThrottle = (idType, cookieManager) => {
111
+ return isThrottled(idType, cookieManager);
112
+ };
113
+ export { getUrlParams, normalizeAdvertiser, createManagedAsyncOperation, appendAdvertisingIdQueryToEvent, isAnyIdUnused, markIdsAsConverted, isThrottled, shouldThrottle };
@@ -10,8 +10,9 @@ OF ANY KIND, either express or implied. See the License for the specific languag
10
10
  governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { string, objectOf, boolean, arrayOf } from "../../utils/validation/index.js";
13
+ import { string, objectOf, boolean, arrayOf, enumOf } from "../../utils/validation/index.js";
14
14
  import { validateConfigOverride, validateIdentityMap } from "../../utils/index.js";
15
+ import { DISABLED, WAIT, AUTO } from "../../constants/consentStatus.js";
15
16
  /**
16
17
  * Verifies user provided event options.
17
18
  * @param {*} options The user event options to validate
@@ -42,7 +43,10 @@ export default ({
42
43
  }),
43
44
  datasetId: string(),
44
45
  mergeId: string(),
45
- edgeConfigOverrides: validateConfigOverride
46
+ edgeConfigOverrides: validateConfigOverride,
47
+ advertising: objectOf({
48
+ handleAdvertisingData: enumOf(DISABLED, WAIT, AUTO).default(DISABLED)
49
+ })
46
50
  }).required().noUnknownFields();
47
51
  return eventOptionsValidator(options);
48
52
  };