@adobe/alloy 2.29.0-beta.4 → 2.29.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 (141) hide show
  1. package/libEs5/components/ActivityCollector/utils/dom/findClickableElement.js +4 -0
  2. package/libEs5/components/Advertising/handlers/clickThroughHandler.js +2 -1
  3. package/libEs5/components/Advertising/handlers/onBeforeSendEventHandler.js +8 -3
  4. package/libEs5/components/Advertising/handlers/viewThroughHandler.js +2 -1
  5. package/libEs5/components/Advertising/identities/collectID5Id.js +1 -1
  6. package/libEs5/components/Advertising/identities/collectRampId.js +1 -1
  7. package/libEs5/components/Consent/createConsentRequestPayload.js +14 -3
  8. package/libEs5/components/Consent/types.js +18 -0
  9. package/libEs5/components/Personalization/dom-actions/action.js +7 -4
  10. package/libEs5/components/Personalization/dom-actions/initDomActionsModules.js +10 -10
  11. package/libEs5/components/PushNotifications/helpers/getPushSubscriptionDetails.js +116 -0
  12. package/libEs5/components/PushNotifications/index.js +96 -0
  13. package/libEs5/components/PushNotifications/request/createSendPushSubscriptionPayload.js +62 -0
  14. package/libEs5/components/PushNotifications/request/createSendPushSubscriptionRequest.js +35 -0
  15. package/libEs5/components/PushNotifications/request/makeSendPushSubscriptionRequest.js +88 -0
  16. package/libEs5/components/PushNotifications/types.js +14 -0
  17. package/libEs5/constants/libraryVersion.js +1 -1
  18. package/libEs5/core/componentCreators.js +8 -1
  19. package/libEs5/core/consent/createConsent.js +10 -0
  20. package/libEs5/core/consent/createConsentStateMachine.js +36 -0
  21. package/libEs5/core/consent/types.js +24 -0
  22. package/libEs5/core/edgeNetwork/injectSendEdgeNetworkRequest.js +20 -0
  23. package/libEs5/core/edgeNetwork/types.js +10 -0
  24. package/libEs5/core/identity/createIdentity.js +36 -0
  25. package/libEs5/core/identity/types.js +15 -0
  26. package/libEs5/core/injectCreateResponse.js +9 -7
  27. package/libEs5/core/types.js +101 -11
  28. package/libEs5/utils/bytes.js +12 -1
  29. package/libEs5/utils/createLoggingCookieJar.js +14 -1
  30. package/libEs5/utils/createMerger.js +5 -4
  31. package/libEs5/utils/injectStorage.js +19 -0
  32. package/libEs5/utils/request/createDataCollectionRequestPayload.js +16 -8
  33. package/libEs5/utils/request/createRequest.js +28 -1
  34. package/libEs5/utils/request/createRequestPayload.js +68 -4
  35. package/libEs5/utils/request/types.js +52 -0
  36. package/libEs5/utils/types.js +39 -0
  37. package/libEs6/components/ActivityCollector/utils/dom/findClickableElement.js +4 -0
  38. package/libEs6/components/Advertising/handlers/clickThroughHandler.js +2 -1
  39. package/libEs6/components/Advertising/handlers/onBeforeSendEventHandler.js +8 -3
  40. package/libEs6/components/Advertising/handlers/viewThroughHandler.js +2 -1
  41. package/libEs6/components/Advertising/identities/collectID5Id.js +1 -1
  42. package/libEs6/components/Advertising/identities/collectRampId.js +1 -1
  43. package/libEs6/components/Consent/createConsentRequestPayload.js +16 -3
  44. package/libEs6/components/Consent/types.js +15 -0
  45. package/libEs6/components/Personalization/dom-actions/action.js +7 -4
  46. package/libEs6/components/Personalization/dom-actions/initDomActionsModules.js +10 -10
  47. package/libEs6/components/PushNotifications/helpers/getPushSubscriptionDetails.js +114 -0
  48. package/libEs6/components/PushNotifications/index.js +93 -0
  49. package/libEs6/components/PushNotifications/request/createSendPushSubscriptionPayload.js +61 -0
  50. package/libEs6/components/PushNotifications/request/createSendPushSubscriptionRequest.js +34 -0
  51. package/libEs6/components/PushNotifications/request/makeSendPushSubscriptionRequest.js +84 -0
  52. package/libEs6/components/PushNotifications/types.js +11 -0
  53. package/libEs6/constants/libraryVersion.js +1 -1
  54. package/libEs6/core/componentCreators.js +2 -1
  55. package/libEs6/core/consent/createConsent.js +12 -0
  56. package/libEs6/core/consent/createConsentStateMachine.js +36 -0
  57. package/libEs6/core/consent/types.js +21 -0
  58. package/libEs6/core/edgeNetwork/injectSendEdgeNetworkRequest.js +20 -0
  59. package/libEs6/core/edgeNetwork/types.js +7 -0
  60. package/libEs6/core/identity/createIdentity.js +38 -0
  61. package/libEs6/core/identity/types.js +12 -0
  62. package/libEs6/core/injectCreateResponse.js +11 -7
  63. package/libEs6/core/types.js +101 -11
  64. package/libEs6/utils/bytes.js +12 -1
  65. package/libEs6/utils/createLoggingCookieJar.js +15 -1
  66. package/libEs6/utils/createMerger.js +5 -4
  67. package/libEs6/utils/injectStorage.js +20 -0
  68. package/libEs6/utils/request/createDataCollectionRequestPayload.js +19 -8
  69. package/libEs6/utils/request/createRequest.js +29 -1
  70. package/libEs6/utils/request/createRequestPayload.js +67 -4
  71. package/libEs6/utils/request/types.js +49 -0
  72. package/libEs6/utils/types.js +36 -0
  73. package/package.json +30 -30
  74. package/types/components/ActivityCollector/utils/dom/findClickableElement.d.ts.map +1 -1
  75. package/types/components/Advertising/handlers/onBeforeSendEventHandler.d.ts.map +1 -1
  76. package/types/components/Advertising/handlers/viewThroughHandler.d.ts.map +1 -1
  77. package/types/components/Consent/createConsentRequest.d.ts +1 -11
  78. package/types/components/Consent/createConsentRequest.d.ts.map +1 -1
  79. package/types/components/Consent/createConsentRequestPayload.d.ts +2 -9
  80. package/types/components/Consent/createConsentRequestPayload.d.ts.map +1 -1
  81. package/types/components/Consent/types.d.ts +28 -0
  82. package/types/components/Consent/types.d.ts.map +1 -0
  83. package/types/components/Identity/getIdentity/createIdentityRequest.d.ts +1 -11
  84. package/types/components/Identity/getIdentity/createIdentityRequest.d.ts.map +1 -1
  85. package/types/components/Identity/getIdentity/createIdentityRequestPayload.d.ts +1 -9
  86. package/types/components/Identity/getIdentity/createIdentityRequestPayload.d.ts.map +1 -1
  87. package/types/components/Personalization/dom-actions/action.d.ts +1 -1
  88. package/types/components/Personalization/dom-actions/action.d.ts.map +1 -1
  89. package/types/components/PushNotifications/helpers/getPushSubscriptionDetails.d.ts +30 -0
  90. package/types/components/PushNotifications/helpers/getPushSubscriptionDetails.d.ts.map +1 -0
  91. package/types/components/PushNotifications/index.d.ts +43 -0
  92. package/types/components/PushNotifications/index.d.ts.map +1 -0
  93. package/types/components/PushNotifications/request/createSendPushSubscriptionPayload.d.ts +10 -0
  94. package/types/components/PushNotifications/request/createSendPushSubscriptionPayload.d.ts.map +1 -0
  95. package/types/components/PushNotifications/request/createSendPushSubscriptionRequest.d.ts +7 -0
  96. package/types/components/PushNotifications/request/createSendPushSubscriptionRequest.d.ts.map +1 -0
  97. package/types/components/PushNotifications/request/makeSendPushSubscriptionRequest.d.ts +20 -0
  98. package/types/components/PushNotifications/request/makeSendPushSubscriptionRequest.d.ts.map +1 -0
  99. package/types/components/PushNotifications/types.d.ts +23 -0
  100. package/types/components/PushNotifications/types.d.ts.map +1 -0
  101. package/types/components/StreamingMedia/createMediaRequest.d.ts +1 -11
  102. package/types/components/StreamingMedia/createMediaRequest.d.ts.map +1 -1
  103. package/types/core/componentCreators.d.ts +1 -0
  104. package/types/core/consent/createConsent.d.ts +4 -10
  105. package/types/core/consent/createConsent.d.ts.map +1 -1
  106. package/types/core/consent/createConsentStateMachine.d.ts +4 -12
  107. package/types/core/consent/createConsentStateMachine.d.ts.map +1 -1
  108. package/types/core/consent/types.d.ts +42 -0
  109. package/types/core/consent/types.d.ts.map +1 -0
  110. package/types/core/edgeNetwork/injectSendEdgeNetworkRequest.d.ts +15 -13
  111. package/types/core/edgeNetwork/injectSendEdgeNetworkRequest.d.ts.map +1 -1
  112. package/types/core/edgeNetwork/types.d.ts +12 -0
  113. package/types/core/edgeNetwork/types.d.ts.map +1 -0
  114. package/types/core/identity/createIdentity.d.ts +9 -9
  115. package/types/core/identity/createIdentity.d.ts.map +1 -1
  116. package/types/core/identity/types.d.ts +23 -0
  117. package/types/core/identity/types.d.ts.map +1 -0
  118. package/types/core/injectCreateResponse.d.ts +3 -27
  119. package/types/core/injectCreateResponse.d.ts.map +1 -1
  120. package/types/core/types.d.ts +209 -22
  121. package/types/core/types.d.ts.map +1 -1
  122. package/types/utils/bytes.d.ts +3 -1
  123. package/types/utils/bytes.d.ts.map +1 -1
  124. package/types/utils/createLoggingCookieJar.d.ts +5 -3
  125. package/types/utils/createLoggingCookieJar.d.ts.map +1 -1
  126. package/types/utils/createMerger.d.ts +1 -1
  127. package/types/utils/createMerger.d.ts.map +1 -1
  128. package/types/utils/injectStorage.d.ts +2 -40
  129. package/types/utils/injectStorage.d.ts.map +1 -1
  130. package/types/utils/request/createDataCollectionRequest.d.ts +1 -11
  131. package/types/utils/request/createDataCollectionRequest.d.ts.map +1 -1
  132. package/types/utils/request/createDataCollectionRequestPayload.d.ts +2 -9
  133. package/types/utils/request/createDataCollectionRequestPayload.d.ts.map +1 -1
  134. package/types/utils/request/createRequest.d.ts +13 -11
  135. package/types/utils/request/createRequest.d.ts.map +1 -1
  136. package/types/utils/request/createRequestPayload.d.ts +7 -9
  137. package/types/utils/request/createRequestPayload.d.ts.map +1 -1
  138. package/types/utils/request/types.d.ts +91 -0
  139. package/types/utils/request/types.d.ts.map +1 -0
  140. package/types/utils/types.d.ts +91 -0
  141. package/types/utils/types.d.ts.map +1 -0
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ exports.Types = void 0;
4
+ /**
5
+ * @typedef {Object} Storage
6
+ * @property {function(string): string|null} getItem
7
+ * @property {function(string, string): void} setItem
8
+ * @property {function(): void} clear
9
+ */
10
+
11
+ /**
12
+ * @typedef {Object} CookieAttributes
13
+ * @property {number|Date} [expires] - Cookie expiration (number of days or Date object)
14
+ * @property {string} [path] - Cookie path (default: "/")
15
+ * @property {string} [domain] - Cookie domain
16
+ * @property {boolean} [secure] - Requires HTTPS transmission
17
+ * @property {"strict"|"lax"|"none"} [sameSite] - SameSite attribute
18
+ */
19
+
20
+ /**
21
+ * @typedef {Object} CookieConverter
22
+ * @property {function(string, string): string} [read] - Custom decoder function
23
+ * @property {function(string, string): string} [write] - Custom encoder function
24
+ */
25
+
26
+ /**
27
+ * @typedef {Object} CookieJar
28
+ * @property {function(string): string|undefined} get - Get cookie value by name, or get all cookies as object if no name provided
29
+ * @property {function(): Object<string, string>} get - Get all cookies as key-value object when called with no arguments
30
+ * @property {function(string, string, CookieAttributes=): string|undefined} set - Set cookie with optional attributes
31
+ * @property {function(string, CookieAttributes=): void} remove - Remove cookie with optional attributes (must match set attributes)
32
+ * @property {function(CookieConverter): CookieJar} withConverter - Create new instance with custom encoding/decoding
33
+ */
34
+
35
+ /**
36
+ * @typedef {function(string): { session: Storage, persistent: Storage }} StorageCreator
37
+ */
38
+
39
+ const Types = exports.Types = {};
@@ -17,6 +17,10 @@ import isButtonSubmitElement from "./isButtonSubmitElement.js";
17
17
  export default element => {
18
18
  let node = element;
19
19
  while (node) {
20
+ // Stop looking when BODY is reached
21
+ if (node.nodeName && node.nodeName === "BODY") {
22
+ break;
23
+ }
20
24
  if (isSupportedAnchorElement(node) || elementHasClickHandler(node) || isInputSubmitElement(node) || isButtonSubmitElement(node)) {
21
25
  return node;
22
26
  }
@@ -62,7 +62,8 @@ export default async function handleClickThrough({
62
62
  }
63
63
  }
64
64
  },
65
- eventType: AD_CONVERSION_CLICK_EVENT_TYPE
65
+ eventType: AD_CONVERSION_CLICK_EVENT_TYPE,
66
+ timestamp: new Date().toISOString()
66
67
  };
67
68
  event.setUserXdm(xdm);
68
69
  cookieManager.setValue(LAST_CONVERSION_TIME_KEY);
@@ -10,11 +10,12 @@ import { getRampId } from "../identities/collectRampId.js";
10
10
  import { appendAdvertisingIdQueryToEvent, getUrlParams, isThrottled } from "../utils/helpers.js";
11
11
  import { SURFER_ID, ID5_ID, RAMP_ID } from "../constants/index.js";
12
12
  import { AUTO, WAIT } from "../../../constants/consentStatus.js";
13
+ import { CHROME } from "../../../constants/browser.js";
13
14
  const isAdvertisingDisabled = advertising => {
14
- return ![AUTO, WAIT].includes(advertising?.handleAdvertisingData);
15
+ return ![AUTO, WAIT].includes(advertising?.handleAdvertisingData?.toLowerCase());
15
16
  };
16
17
  const waitForAdvertisingId = advertising => {
17
- return advertising?.handleAdvertisingData === WAIT;
18
+ return advertising?.handleAdvertisingData?.toLowerCase() === WAIT;
18
19
  };
19
20
 
20
21
  /**
@@ -44,7 +45,11 @@ export default async function handleOnBeforeSendEvent({
44
45
  if (isAdvertisingDisabled(advertising) || isClickThru || isThrottled(SURFER_ID, cookieManager) && isThrottled(ID5_ID, cookieManager) && isThrottled(RAMP_ID, cookieManager)) return;
45
46
  try {
46
47
  const useShortTimeout = waitForAdvertisingId(advertising);
47
- const [surferIdResult, id5IdResult, rampIdResult] = await Promise.allSettled([collectSurferId(cookieManager, getBrowser, useShortTimeout), getID5Id(logger, componentConfig.id5PartnerId, useShortTimeout, useShortTimeout), getRampId(logger, componentConfig.rampIdJSPath, cookieManager, useShortTimeout, useShortTimeout)]);
48
+ let rampIdPromise = null;
49
+ if (!getBrowser || getBrowser() !== CHROME) {
50
+ rampIdPromise = getRampId(logger, componentConfig.rampIdJSPath, cookieManager, useShortTimeout, useShortTimeout);
51
+ }
52
+ const [surferIdResult, id5IdResult, rampIdResult] = await Promise.allSettled([collectSurferId(cookieManager, getBrowser, useShortTimeout), getID5Id(logger, componentConfig.id5PartnerId, useShortTimeout, useShortTimeout), rampIdPromise]);
48
53
  const availableIds = {};
49
54
  if (surferIdResult.status === "fulfilled" && surferIdResult.value && !isThrottled(SURFER_ID, cookieManager)) {
50
55
  availableIds.surferId = surferIdResult.value;
@@ -23,7 +23,8 @@ export default async function handleViewThrough({
23
23
  try {
24
24
  const event = appendAdvertisingIdQueryToEvent(resolvedIds, eventManager.createEvent(), cookieManager, componentConfig, true);
25
25
  const xdm = {
26
- eventType: AD_CONVERSION_VIEW_EVENT_TYPE
26
+ eventType: AD_CONVERSION_VIEW_EVENT_TYPE,
27
+ timestamp: new Date().toISOString()
27
28
  };
28
29
  event.setUserXdm(xdm);
29
30
  const result = await adConversionHandler.trackAdConversion({
@@ -13,7 +13,7 @@ import { loadScript } from "../../../utils/dom/index.js";
13
13
  import { ID5_SCRIPT_URL } from "../constants/index.js";
14
14
  let id5Id = "";
15
15
  let inProgressId5Promise = null;
16
- const SHORT_TIMEOUT_MS = 5000;
16
+ const SHORT_TIMEOUT_MS = 2000;
17
17
  const DEFAULT_TIMEOUT_MS = 30000;
18
18
  const initiateID5Call = (partnerId, useShortTimeout, logger) => {
19
19
  partnerId = Math.floor(Number(partnerId));
@@ -17,7 +17,7 @@ const RETRY_CONFIG = {
17
17
  MAX_TIME_MS: 30000,
18
18
  DELAY_BASE_MS: 500,
19
19
  MAX_DELAY_MS: 5000,
20
- SHORT_TIMEOUT_MS: 5000
20
+ SHORT_TIMEOUT_MS: 2000
21
21
  };
22
22
  const state = {
23
23
  rampIdEnv: undefined,
@@ -10,10 +10,18 @@ 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 { ConsentRequestPayload } from './types.js' */
14
+
13
15
  import { createRequestPayload } from "../../utils/request/index.js";
16
+
17
+ /**
18
+ * @function
19
+ *
20
+ * @returns {ConsentRequestPayload}
21
+ */
14
22
  export default () => {
15
23
  const content = {};
16
- const payload = createRequestPayload({
24
+ const requestPayload = createRequestPayload({
17
25
  content,
18
26
  addIdentity: (namespaceCode, identity) => {
19
27
  content.identityMap = content.identityMap || {};
@@ -24,8 +32,13 @@ export default () => {
24
32
  return (content.identityMap && content.identityMap[namespaceCode]) !== undefined;
25
33
  }
26
34
  });
27
- payload.setConsent = consent => {
28
- content.consent = consent;
35
+
36
+ /** @type {ConsentRequestPayload} */
37
+ const payload = {
38
+ ...requestPayload,
39
+ setConsent: consent => {
40
+ content.consent = consent;
41
+ }
29
42
  };
30
43
  return payload;
31
44
  };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Request payload object with methods for merging different types of data.
3
+ *
4
+ * @typedef {Object} ConsentRequestPayload
5
+ * @property {Function} mergeMeta
6
+ * @property {Function} mergeState
7
+ * @property {Function} mergeQuery
8
+ * @property {Function} mergeConfigOverride
9
+ * @property {Function} addIdentity
10
+ * @property {Function} hasIdentity
11
+ * @property {Function} toJSON
12
+ * @property {Function} setConsent
13
+ */
14
+
15
+ export const Types = {};
@@ -40,9 +40,10 @@ const renderContent = ({
40
40
  content,
41
41
  decorateProposition,
42
42
  renderFunc,
43
- renderStatusHandler
43
+ renderStatusHandler,
44
+ alwaysRender
44
45
  }) => {
45
- const executions = containers.filter(renderStatusHandler.shouldRender).map(async container => {
46
+ const executions = containers.filter(element => alwaysRender || renderStatusHandler.shouldRender(element)).map(async container => {
46
47
  await renderFunc(container, content, decorateProposition);
47
48
  renderStatusHandler.markAsRendered(container);
48
49
  });
@@ -53,8 +54,9 @@ const renderContent = ({
53
54
  * Creates an action function that renders content into a container element.
54
55
  *
55
56
  * @param {Function} renderFunc - The function that performs the rendering.
57
+ * @param {boolean} alwaysRender - Whether to always render the content, even if it has already been rendered.
56
58
  */
57
- export const createAction = renderFunc => {
59
+ export const createAction = (renderFunc, alwaysRender = false) => {
58
60
  /**
59
61
  * Renders content into a container element.
60
62
  *
@@ -76,7 +78,8 @@ export const createAction = renderFunc => {
76
78
  content,
77
79
  decorateProposition,
78
80
  renderFunc,
79
- renderStatusHandler
81
+ renderStatusHandler,
82
+ alwaysRender
80
83
  });
81
84
  } finally {
82
85
  showElements(prehidingSelector);
@@ -45,21 +45,21 @@ export const DOM_ACTION_CLICK = "click";
45
45
  export const DOM_ACTION_COLLECT_INTERACTIONS = "collectInteractions";
46
46
  export default () => {
47
47
  return {
48
- [DOM_ACTION_SET_HTML]: createAction(setHtml),
48
+ [DOM_ACTION_SET_HTML]: createAction(setHtml, true),
49
49
  [DOM_ACTION_CUSTOM_CODE]: createAction(prependHtml),
50
- [DOM_ACTION_SET_TEXT]: createAction(setText),
51
- [DOM_ACTION_SET_ATTRIBUTE]: createAction(setAttributes),
52
- [DOM_ACTION_SET_IMAGE_SOURCE]: createAction(swapImage),
53
- [DOM_ACTION_SET_STYLE]: createAction(setStyles),
54
- [DOM_ACTION_MOVE]: createAction(move),
55
- [DOM_ACTION_RESIZE]: createAction(resize),
50
+ [DOM_ACTION_SET_TEXT]: createAction(setText, true),
51
+ [DOM_ACTION_SET_ATTRIBUTE]: createAction(setAttributes, true),
52
+ [DOM_ACTION_SET_IMAGE_SOURCE]: createAction(swapImage, true),
53
+ [DOM_ACTION_SET_STYLE]: createAction(setStyles, true),
54
+ [DOM_ACTION_MOVE]: createAction(move, true),
55
+ [DOM_ACTION_RESIZE]: createAction(resize, true),
56
56
  [DOM_ACTION_REARRANGE]: createAction(rearrangeChildren),
57
- [DOM_ACTION_REMOVE]: createAction(removeNode),
57
+ [DOM_ACTION_REMOVE]: createAction(removeNode, true),
58
58
  [DOM_ACTION_INSERT_AFTER]: createAction(insertHtmlAfter),
59
59
  [DOM_ACTION_INSERT_BEFORE]: createAction(insertHtmlBefore),
60
- [DOM_ACTION_REPLACE_HTML]: createAction(replaceHtml),
60
+ [DOM_ACTION_REPLACE_HTML]: createAction(replaceHtml, true),
61
61
  [DOM_ACTION_PREPEND_HTML]: createAction(prependHtml),
62
62
  [DOM_ACTION_APPEND_HTML]: createAction(appendHtml),
63
- [DOM_ACTION_COLLECT_INTERACTIONS]: createAction(collectInteractions)
63
+ [DOM_ACTION_COLLECT_INTERACTIONS]: createAction(collectInteractions, true)
64
64
  };
65
65
  };
@@ -0,0 +1,114 @@
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 { PushSubscription } from '../types.js' */
14
+
15
+ import { base64ToBytes, bytesToBase64 } from "../../../utils/index.js";
16
+
17
+ /**
18
+ * Gets push subscription details for the current browser.
19
+ *
20
+ * @async
21
+ * @function
22
+ *
23
+ * @param {object} options
24
+ * @param {string} options.vapidPublicKey - The VAPID public key in base64 format used for push notification authentication.
25
+ * @param {Window} options.window
26
+ *
27
+ * @returns {Promise<PushSubscription>} A promise that resolves to an object containing the push subscription details.
28
+
29
+ * @throws {Error} Throws an error if service workers are not supported in the browser.
30
+ * @throws {Error} Throws an error if user didn't approve push notifications for the domain.
31
+ * @throws {Error} Throws an error if push notifications are not supported in the browser.
32
+ * @throws {Error} Throws an error if no VAPID public key was provided.
33
+ *
34
+ * @example
35
+ * // Get subscription details with VAPID key
36
+ * const vapidKey = "BEl62iUYgUivxIkv69yViEuiBIa40HI5hmjHbKPlXO...";
37
+ * const subscription = await getPushSubscriptionDetails(vapidKey);
38
+ * console.log(subscription.endpoint); // "https://fcm.googleapis.com/fcm/send/..."
39
+ */
40
+ const getPushSubscriptionDetails = async ({
41
+ vapidPublicKey,
42
+ window
43
+ }) => {
44
+ if (!("serviceWorker" in window.navigator)) {
45
+ throw new Error("Service workers are not supported in this browser.");
46
+ }
47
+ if (!("PushManager" in window) || !("Notification" in window)) {
48
+ throw new Error("Push notifications are not supported in this browser.");
49
+ }
50
+
51
+ /** @type {object} */
52
+ const notification = window.Notification;
53
+ if (notification.permission !== "granted") {
54
+ throw new Error("The user has not given permission to send push notifications.");
55
+ }
56
+ const serviceWorkerRegistration =
57
+ // eslint-disable-next-line compat/compat
58
+ await window.navigator.serviceWorker.getRegistration();
59
+ if (!serviceWorkerRegistration) {
60
+ throw new Error("No service worker registration was found.");
61
+ }
62
+
63
+ // Even `applicationServerKey` is not required in the spec, some browsers like Chrome are requiring it.
64
+ if (!vapidPublicKey) {
65
+ throw new Error("No VAPID public key was provided.");
66
+ }
67
+ const subscriptionOptions = {
68
+ userVisibleOnly: true,
69
+ applicationServerKey: base64ToBytes(vapidPublicKey)
70
+ };
71
+
72
+ // Push subscription handling strategy:
73
+ //
74
+ // 1. We always call subscribe() to either get the current subscription or create a new one.
75
+ // - If called with the same VAPID key as an existing subscription, it returns that subscription
76
+ // - If called with a different VAPID key when a subscription exists, it throws an error
77
+ //
78
+ // 2. Error handling approach:
79
+ // - Browser error messages vary and don't clearly indicate VAPID key conflicts
80
+ // - When subscribe() fails, we assume it's likely due to a VAPID key mismatch
81
+ // - We attempt recovery by unsubscribing the existing subscription and retrying
82
+ // - If the retry also fails, we re-throw the original error
83
+ //
84
+ // This strategy ensures we can handle both new subscriptions and VAPID key changes
85
+ // while gracefully falling back to error reporting when recovery isn't possible.
86
+ try {
87
+ const subscription = await serviceWorkerRegistration.pushManager.subscribe(subscriptionOptions);
88
+ return {
89
+ endpoint: subscription.endpoint,
90
+ keys: {
91
+ p256dh: bytesToBase64(new Uint8Array(subscription.getKey("p256dh")), {
92
+ urlSafe: true
93
+ }),
94
+ auth: bytesToBase64(new Uint8Array(subscription.getKey("auth")), {
95
+ urlSafe: true
96
+ })
97
+ }
98
+ };
99
+ } catch (e) {
100
+ const subscription = await serviceWorkerRegistration.pushManager.getSubscription();
101
+ if (!subscription) {
102
+ throw e;
103
+ }
104
+ const unsubscribeResult = await subscription.unsubscribe();
105
+ if (!unsubscribeResult) {
106
+ throw e;
107
+ }
108
+ return getPushSubscriptionDetails({
109
+ vapidPublicKey,
110
+ window
111
+ });
112
+ }
113
+ };
114
+ export default getPushSubscriptionDetails;
@@ -0,0 +1,93 @@
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 { StorageCreator } from '../../utils/types.js' */
14
+ /** @import { EventManager, Logger } from '../../core/types.js' */
15
+ /** @import { IdentityManager } from '../../core/identity/types.js' */
16
+ /** @import { ConsentManager } from '../../core/consent/types.js' */
17
+ /** @import { EdgeRequestExecutor } from '../../core/edgeNetwork/types.js' */
18
+
19
+ import { objectOf, string } from "../../utils/validation/index.js";
20
+ import { sanitizeOrgIdForCookieName } from "../../utils/index.js";
21
+ import makeSendPushSubscriptionRequest from "./request/makeSendPushSubscriptionRequest.js";
22
+ const isComponentConfigured = ({
23
+ orgId,
24
+ pushNotifications: {
25
+ vapidPublicKey
26
+ } = {
27
+ vapidPublicKey: undefined
28
+ }
29
+ }) => orgId && vapidPublicKey;
30
+
31
+ /**
32
+ * @function
33
+ *
34
+ * @param {Object} options
35
+ * @param {{ orgId: string, pushNotifications: { vapidPublicKey: string }}} options.config
36
+ * @param {StorageCreator} options.createNamespacedStorage
37
+ * @param {EventManager} options.eventManager
38
+ * @param {Logger} options.logger
39
+ * @param {ConsentManager} options.consent
40
+ * @param {IdentityManager} options.identity
41
+ * @param {EdgeRequestExecutor} options.sendEdgeNetworkRequest
42
+ * @returns {{ commands: { sendPushSubscription: object } }}
43
+ */
44
+ const createPushNotifications = ({
45
+ createNamespacedStorage,
46
+ eventManager,
47
+ config,
48
+ logger,
49
+ consent,
50
+ identity,
51
+ sendEdgeNetworkRequest
52
+ }) => {
53
+ return {
54
+ commands: {
55
+ sendPushSubscription: {
56
+ run: async () => {
57
+ if (!isComponentConfigured(config)) {
58
+ throw new Error("Push notifications module is not configured. No VAPID public key was provided.");
59
+ }
60
+ const {
61
+ orgId,
62
+ pushNotifications: {
63
+ vapidPublicKey
64
+ } = {
65
+ vapidPublicKey: undefined
66
+ }
67
+ } = config || {};
68
+ const storage = createNamespacedStorage(`${sanitizeOrgIdForCookieName(orgId)}.pushNotifications.`);
69
+ return makeSendPushSubscriptionRequest({
70
+ config: {
71
+ vapidPublicKey
72
+ },
73
+ storage: storage.persistent,
74
+ logger,
75
+ sendEdgeNetworkRequest,
76
+ consent,
77
+ eventManager,
78
+ identity,
79
+ window
80
+ });
81
+ },
82
+ optionsValidator: objectOf({}).noUnknownFields()
83
+ }
84
+ }
85
+ };
86
+ };
87
+ createPushNotifications.namespace = "Push Notifications";
88
+ createPushNotifications.configValidators = objectOf({
89
+ pushNotifications: objectOf({
90
+ vapidPublicKey: string().required()
91
+ }).noUnknownFields()
92
+ });
93
+ export default createPushNotifications;
@@ -0,0 +1,61 @@
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 { EventManager } from "../../../core/types.js" */
14
+ /** @import { DataCollectionRequestPayload } from "../../../utils/request/types.js" */
15
+
16
+ import createDataCollectionRequestPayload from "../../../utils/request/createDataCollectionRequestPayload.js";
17
+
18
+ /**
19
+ * Creates a data collection request payload for sending push subscription details to Adobe Experience Platform.
20
+ *
21
+ * This function constructs an event containing push notification subscription information,
22
+ * including the subscription details, platform information, and identity data. The event
23
+ * is then packaged into a data collection request payload for transmission to the edge network.
24
+ *
25
+ * @async
26
+ * @function
27
+ *
28
+ * @param {Object} options
29
+ * @param {string} options.ecid
30
+ * @param {EventManager} options.eventManager
31
+ * @param {string} options.serializedPushSubscriptionDetails
32
+ * @param {Window} options.window
33
+ *
34
+ * @returns {Promise<DataCollectionRequestPayload>}
35
+ */
36
+ export default async ({
37
+ ecid,
38
+ eventManager,
39
+ serializedPushSubscriptionDetails,
40
+ window
41
+ }) => {
42
+ const event = eventManager.createEvent();
43
+ event.setUserData({
44
+ pushNotificationDetails: [{
45
+ appID: window.location.host,
46
+ token: serializedPushSubscriptionDetails,
47
+ platform: "web",
48
+ denylisted: false,
49
+ identity: {
50
+ namespace: {
51
+ code: "ECID"
52
+ },
53
+ id: ecid
54
+ }
55
+ }]
56
+ });
57
+ event.finalize();
58
+ const payload = createDataCollectionRequestPayload();
59
+ payload.addEvent(event);
60
+ return payload;
61
+ };
@@ -0,0 +1,34 @@
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 { DataCollectionRequestPayload, Request } from '../../../utils/request/types.js' */
14
+
15
+ import { createRequest } from "../../../utils/request/index.js";
16
+
17
+ /**
18
+ * @function
19
+ *
20
+ * @param {{ payload: DataCollectionRequestPayload }} options
21
+ *
22
+ * @returns {Request}
23
+ */
24
+ export default ({
25
+ payload
26
+ }) => createRequest({
27
+ payload,
28
+ getAction() {
29
+ return "interact";
30
+ },
31
+ getUseSendBeacon() {
32
+ return false;
33
+ }
34
+ });
@@ -0,0 +1,84 @@
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 { Storage } from '../../../utils/types.js' */
14
+ /** @import { EventManager, Logger } from '../../../core/types.js' */
15
+ /** @import { IdentityManager } from '../../../core/identity/types.js' */
16
+ /** @import { ConsentManager } from '../../../core/consent/types.js' */
17
+ /** @import { EdgeRequestExecutor } from '../../../core/edgeNetwork/types.js' */
18
+
19
+ import { sortObjectKeysRecursively } from "../../../utils/index.js";
20
+ import getPushSubscriptionDetails from "../helpers/getPushSubscriptionDetails.js";
21
+ import createSendPushSubscriptionRequest from "./createSendPushSubscriptionRequest.js";
22
+ import createSendPushSubscriptionPayload from "./createSendPushSubscriptionPayload.js";
23
+ const SUBSCRIPTION_DETAILS = "subscriptionDetails";
24
+
25
+ /**
26
+ * Retrieves and returns push subscription details with sorted object keys.
27
+ *
28
+ * This function gets the push subscription details using the provided VAPID public key
29
+ * and returns the details with all object keys sorted recursively for consistent output.
30
+ *
31
+ * @async
32
+ * @function
33
+ *
34
+ * @param {Object} options
35
+ * @param {{vapidPublicKey: string}} options.config
36
+ * @param {Storage} options.storage
37
+ * @param {Logger} options.logger
38
+ * @param {EventManager} options.eventManager
39
+ * @param {IdentityManager} options.identity
40
+ * @param {EdgeRequestExecutor} options.sendEdgeNetworkRequest
41
+ * @param {ConsentManager} options.consent
42
+ * @param {Window} options.window
43
+ *
44
+ * @returns {Promise<void>}
45
+ */
46
+ export default async ({
47
+ config: {
48
+ vapidPublicKey
49
+ },
50
+ storage,
51
+ logger,
52
+ sendEdgeNetworkRequest,
53
+ consent,
54
+ eventManager,
55
+ identity,
56
+ window
57
+ }) => {
58
+ await identity.awaitIdentity();
59
+ const ecid = identity.getEcidFromCookie();
60
+ const pushSubscriptionDetails = await getPushSubscriptionDetails({
61
+ vapidPublicKey,
62
+ window
63
+ });
64
+ const serializedPushSubscriptionDetails = JSON.stringify(sortObjectKeysRecursively(pushSubscriptionDetails));
65
+ const cacheValue = `${ecid}${serializedPushSubscriptionDetails}`;
66
+ if (cacheValue === storage.getItem(SUBSCRIPTION_DETAILS)) {
67
+ logger.info("Subscription details have not changed. Not sending to the server.");
68
+ return;
69
+ }
70
+ storage.setItem(SUBSCRIPTION_DETAILS, cacheValue);
71
+ const payload = await createSendPushSubscriptionPayload({
72
+ eventManager,
73
+ ecid,
74
+ serializedPushSubscriptionDetails,
75
+ window
76
+ });
77
+ const request = createSendPushSubscriptionRequest({
78
+ payload
79
+ });
80
+ await consent.awaitConsent();
81
+ await sendEdgeNetworkRequest({
82
+ request
83
+ });
84
+ };
@@ -0,0 +1,11 @@
1
+ /** @import { Identity } from '../../utils/request/types.js' */
2
+
3
+ /**
4
+ * @typedef {Object} PushSubscription
5
+ * @property {string} endpoint - The push service endpoint URL
6
+ * @property {Object} keys - The subscription keys object
7
+ * @property {string|null} keys.p256dh - The P-256 ECDH public key as an ArrayBuffer, or null if not available
8
+ * @property {string|null} keys.auth - The authentication secret as an ArrayBuffer, or null if not available
9
+ */
10
+
11
+ export const Types = {};
@@ -13,4 +13,4 @@ governing permissions and limitations under the License.
13
13
  // The __VERSION__ keyword will be replace at alloy build time with the package.json version.
14
14
  // see babel-plugin-version
15
15
 
16
- export default "2.29.0-beta.4";
16
+ export default "2.29.0";
@@ -22,4 +22,5 @@ export { default as mediaAnalyticsBridge } from "../components/MediaAnalyticsBri
22
22
  export { default as personalization } from "../components/Personalization/index.js";
23
23
  export { default as rulesEngine } from "../components/RulesEngine/index.js";
24
24
  export { default as streamingMedia } from "../components/StreamingMedia/index.js";
25
- export { default as advertising } from "../components/Advertising/index.js";
25
+ export { default as advertising } from "../components/Advertising/index.js";
26
+ export { default as pushNotifications } from "../components/PushNotifications/index.js";