@adobe/alloy 2.34.2-beta.3 → 2.35.0-beta.4

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 (38) hide show
  1. package/dist/alloy.js +286 -90
  2. package/dist/alloy.min.js +1 -1
  3. package/dist/alloyServiceWorker.js +85 -12
  4. package/dist/alloyServiceWorker.min.js +1 -1
  5. package/dist/types/browser/src/components/ActivityCollector/configValidators.d.ts +2 -2
  6. package/dist/types/browser/src/components/Advertising/configValidators.d.ts +1 -1
  7. package/dist/types/browser/src/components/Context/index.d.ts +1 -1
  8. package/dist/types/browser/src/components/Personalization/index.d.ts +1 -1
  9. package/dist/types/browser/src/components/PushNotifications/helpers/readFromIndexedDb.d.ts +2 -1
  10. package/dist/types/browser/src/components/PushNotifications/helpers/saveToIndexedDb.d.ts +3 -2
  11. package/dist/types/browser/src/components/PushNotifications/helpers/serviceWorkerPushListener.d.ts +3 -1
  12. package/dist/types/browser/src/components/PushNotifications/index.d.ts +1 -1
  13. package/dist/types/browser/src/components/PushNotifications/request/makeSendPushSubscriptionRequest.d.ts +5 -1
  14. package/dist/types/browser/src/components/PushNotifications/request/makeSendServiceWorkerTrackingData.d.ts +4 -2
  15. package/dist/types/browser/src/components/PushNotifications/types.d.ts +28 -0
  16. package/dist/types/core/src/components/BrandConcierge/configValidators.d.ts +1 -1
  17. package/dist/types/core/src/components/Consent/configValidators.d.ts +1 -1
  18. package/dist/types/core/src/components/Consent/validateSetConsentOptions.d.ts +1 -1
  19. package/dist/types/core/src/components/Identity/configValidators.d.ts +1 -1
  20. package/dist/types/core/src/components/RulesEngine/index.d.ts +1 -1
  21. package/dist/types/core/src/components/StreamingMedia/configValidators.d.ts +1 -1
  22. package/dist/types/core/src/core/config/createCoreConfigs.d.ts +1 -1
  23. package/dist/types/core/src/utils/bytes.d.ts +1 -1
  24. package/dist/types/core/src/utils/validateConfigOverride.d.ts +1 -1
  25. package/dist/types/core/src/utils/validateIdentityMap.d.ts +1 -1
  26. package/dist/types/core/src/utils/validation/index.d.ts +23 -11
  27. package/dist/types/core/src/utils/validation/types.d.ts +133 -0
  28. package/dist/types/core/src/utils/validation/utils.d.ts +4 -3
  29. package/package.json +2 -2
  30. package/src/components/PushNotifications/helpers/getPushSubscriptionDetails.js +10 -8
  31. package/src/components/PushNotifications/helpers/readFromIndexedDb.js +3 -2
  32. package/src/components/PushNotifications/helpers/saveToIndexedDb.js +2 -1
  33. package/src/components/PushNotifications/helpers/serviceWorkerPushListener.js +58 -2
  34. package/src/components/PushNotifications/index.js +7 -6
  35. package/src/components/PushNotifications/request/makeSendPushSubscriptionRequest.js +2 -1
  36. package/src/components/PushNotifications/request/makeSendServiceWorkerTrackingData.js +14 -6
  37. package/src/components/PushNotifications/types.js +15 -0
  38. package/src/createEventListeners.js +1 -0
@@ -46,8 +46,7 @@ const getPushSubscriptionDetails = async ({ vapidPublicKey, window }) => {
46
46
  throw new Error("Push notifications are not supported in this browser.");
47
47
  }
48
48
 
49
- /** @type {object} */
50
- const notification = window.Notification;
49
+ const notification = /** @type {typeof Notification} */ (window.Notification);
51
50
  if (notification.permission !== "granted") {
52
51
  throw new Error(
53
52
  "The user has not given permission to send push notifications.",
@@ -92,15 +91,18 @@ const getPushSubscriptionDetails = async ({ vapidPublicKey, window }) => {
92
91
  subscriptionOptions,
93
92
  );
94
93
 
94
+ const p256dhKey = subscription.getKey("p256dh");
95
+ const authKey = subscription.getKey("auth");
96
+
95
97
  return {
96
98
  endpoint: subscription.endpoint,
97
99
  keys: {
98
- p256dh: bytesToBase64(new Uint8Array(subscription.getKey("p256dh")), {
99
- urlSafe: true,
100
- }),
101
- auth: bytesToBase64(new Uint8Array(subscription.getKey("auth")), {
102
- urlSafe: true,
103
- }),
100
+ p256dh: p256dhKey
101
+ ? bytesToBase64(new Uint8Array(p256dhKey), { urlSafe: true })
102
+ : null,
103
+ auth: authKey
104
+ ? bytesToBase64(new Uint8Array(authKey), { urlSafe: true })
105
+ : null,
104
106
  },
105
107
  };
106
108
  } catch (e) {
@@ -10,7 +10,7 @@ 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 { ServiceWorkerLogger } from '../types.js' */
13
+ /** @import { ServiceWorkerLogger, PushServiceWorkerConfig } from '../types.js' */
14
14
 
15
15
  import {
16
16
  getFromIndexedDbStore,
@@ -20,7 +20,7 @@ import { DB_NAME, DB_VERSION, STORE_NAME, INDEX_KEY } from "./constants.js";
20
20
 
21
21
  /**
22
22
  * @param {ServiceWorkerLogger} logger
23
- * @returns {Promise<Object|undefined>}
23
+ * @returns {Promise<PushServiceWorkerConfig|undefined>}
24
24
  * @throws {Error}
25
25
  */
26
26
  export default async (logger) => {
@@ -46,5 +46,6 @@ export default async (logger) => {
46
46
  return existingConfigData;
47
47
  } catch (error) {
48
48
  logger.error("Failed to read data from IndexedDB", { error });
49
+ throw error;
49
50
  }
50
51
  };
@@ -11,6 +11,7 @@ governing permissions and limitations under the License.
11
11
  */
12
12
 
13
13
  /** @import { Logger } from '@adobe/alloy-core/core/types.js' */
14
+ /** @import { PushServiceWorkerConfig } from '../types.js' */
14
15
 
15
16
  import {
16
17
  openIndexedDb,
@@ -21,7 +22,7 @@ import {
21
22
  import { DB_NAME, DB_VERSION, STORE_NAME, INDEX_KEY } from "./constants.js";
22
23
 
23
24
  /**
24
- * @param {Object} data
25
+ * @param {Partial<PushServiceWorkerConfig>} data
25
26
  * @param {Logger} logger
26
27
  *
27
28
  * @returns {Promise<boolean>} Whether the data was successfully saved.
@@ -14,7 +14,53 @@ governing permissions and limitations under the License.
14
14
  /// <reference lib="webworker" />
15
15
 
16
16
  /** @import { PushNotificationData } from '../types.js' */
17
- /** @import { ServiceWorkerLogger } from '../types.js' */
17
+ /** @import { ServiceWorkerLogger, PushServiceWorkerConfig } from '../types.js' */
18
+
19
+ import ecidNamespace from "@adobe/alloy-core/constants/ecidNamespace.js";
20
+
21
+ /**
22
+ * Determines whether a notification targeted at a specific identity should be
23
+ * shown on this browser. When the payload contains an `identity` with the ECID
24
+ * namespace, the notification is only shown if the ECID in the payload matches
25
+ * the ECID stored in IndexedDB for this browser. When there is no identity, or
26
+ * the namespace is not ECID, the notification is always shown.
27
+ *
28
+ * @param {Object} options
29
+ * @param {PushNotificationData["web"]} options.webData
30
+ * @param {(logger: ServiceWorkerLogger) => Promise<PushServiceWorkerConfig|undefined>} options.readFromIndexedDb
31
+ * @param {ServiceWorkerLogger} options.logger
32
+ * @returns {Promise<boolean>}
33
+ */
34
+ const shouldShowNotification = async ({
35
+ webData,
36
+ readFromIndexedDb,
37
+ logger,
38
+ }) => {
39
+ const { identity } = webData;
40
+
41
+ if (!identity || identity.namespace?.toUpperCase() !== ecidNamespace) {
42
+ return true;
43
+ }
44
+
45
+ let storedConfig;
46
+ try {
47
+ storedConfig = await readFromIndexedDb(logger);
48
+ } catch (error) {
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ logger.error(`Unable to read the stored ECID for this browser. ${message}`);
51
+ return true;
52
+ }
53
+ const storedEcid = storedConfig?.ecid;
54
+
55
+ if (identity.id !== storedEcid) {
56
+ logger.info(
57
+ "Suppressing push notification: payload ECID does not match the ECID stored for this browser.",
58
+ );
59
+ return false;
60
+ }
61
+
62
+ return true;
63
+ };
18
64
 
19
65
  /**
20
66
  * @async
@@ -24,9 +70,10 @@ governing permissions and limitations under the License.
24
70
  * @param {ServiceWorkerGlobalScope} options.sw
25
71
  * @param {PushEvent} options.event
26
72
  * @param {ServiceWorkerLogger} options.logger
73
+ * @param {(logger: ServiceWorkerLogger) => Promise<PushServiceWorkerConfig|undefined>} options.readFromIndexedDb
27
74
  * @returns {Promise<void>}
28
75
  */
29
- export default async ({ sw, event, logger }) => {
76
+ export default async ({ sw, event, logger, readFromIndexedDb }) => {
30
77
  if (!event.data) {
31
78
  return;
32
79
  }
@@ -45,6 +92,15 @@ export default async ({ sw, event, logger }) => {
45
92
  return;
46
93
  }
47
94
 
95
+ const showNotification = await shouldShowNotification({
96
+ webData,
97
+ readFromIndexedDb,
98
+ logger,
99
+ });
100
+ if (!showNotification) {
101
+ return;
102
+ }
103
+
48
104
  /** @type {Record<string, unknown>} */
49
105
  const notificationOptions = {
50
106
  data: webData,
@@ -21,6 +21,10 @@ import { sanitizeOrgIdForCookieName } from "@adobe/alloy-core/utils";
21
21
  import makeSendPushSubscriptionRequest from "./request/makeSendPushSubscriptionRequest.js";
22
22
  import saveToIndexedDb from "./helpers/saveToIndexedDb.js";
23
23
 
24
+ /**
25
+ * @param {{ orgId?: string, pushNotifications?: { vapidPublicKey?: string, appId?: string, trackingDatasetId?: string } }} config
26
+ * @returns {boolean}
27
+ */
24
28
  const isComponentConfigured = ({
25
29
  orgId,
26
30
  pushNotifications: { vapidPublicKey, appId, trackingDatasetId } = {
@@ -28,7 +32,7 @@ const isComponentConfigured = ({
28
32
  appId: undefined,
29
33
  trackingDatasetId: undefined,
30
34
  },
31
- }) => orgId && vapidPublicKey && appId && trackingDatasetId;
35
+ }) => Boolean(orgId && vapidPublicKey && appId && trackingDatasetId);
32
36
 
33
37
  /**
34
38
  * @function
@@ -86,11 +90,8 @@ const createPushNotifications = ({
86
90
 
87
91
  const {
88
92
  orgId,
89
- pushNotifications: { vapidPublicKey, appId } = {
90
- vapidPublicKey: undefined,
91
- appId: undefined,
92
- },
93
- } = config || {};
93
+ pushNotifications: { vapidPublicKey, appId },
94
+ } = config;
94
95
 
95
96
  const storage = platformServices.storage.createNamespacedStorage(
96
97
  `${sanitizeOrgIdForCookieName(orgId)}.pushNotifications.`,
@@ -10,7 +10,7 @@ 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 { Storage } from '@adobe/alloy-core/utils/types.js' */
13
+ /** @import { Storage } from '@adobe/alloy-core/services' */
14
14
  /** @import { EventManager, Logger } from '@adobe/alloy-core/core/types.js' */
15
15
  /** @import { IdentityManager } from '@adobe/alloy-core/core/identity/types.js' */
16
16
  /** @import { ConsentManager } from '@adobe/alloy-core/core/consent/types.js' */
@@ -42,6 +42,7 @@ const SUBSCRIPTION_DETAILS = "subscriptionDetails";
42
42
  * @param {EdgeRequestExecutor} options.sendEdgeNetworkRequest
43
43
  * @param {ConsentManager} options.consent
44
44
  * @param {Window} options.window
45
+ * @param {typeof getPushSubscriptionDetailsDefault} [options.getPushSubscriptionDetails]
45
46
  *
46
47
  * @returns {Promise<void>}
47
48
  */
@@ -12,14 +12,16 @@ governing permissions and limitations under the License.
12
12
 
13
13
  /** @import { ServiceWorkerLogger } from '../types.js' */
14
14
  /** @import { TrackingDataPayload } from '../types.js' */
15
+ /** @import { PushServiceWorkerConfig } from '../types.js' */
16
+ /** @import { XdmTrackingContext } from '../types.js' */
15
17
 
16
18
  /**
17
19
  * @param {Object} dependencies
18
- * @param {(logger: ServiceWorkerLogger) => Promise<Object>} dependencies.readFromIndexedDb
20
+ * @param {(logger: ServiceWorkerLogger) => Promise<PushServiceWorkerConfig>} dependencies.readFromIndexedDb
19
21
  * @param {() => string} dependencies.uuidv4
20
22
  * @param {ServiceWorkerLogger} dependencies.logger
21
23
  * @param {(url: string, options: object) => Promise<Response>} dependencies.fetch
22
- * @returns {(options: { xdm: Object, actionLabel?: string, applicationLaunches?: number }) => Promise<boolean>}
24
+ * @returns {(options: { xdm: XdmTrackingContext, actionLabel?: string, applicationLaunches?: number }) => Promise<boolean>}
23
25
  */
24
26
  export const createMakeSendServiceWorkerTrackingData = ({
25
27
  readFromIndexedDb,
@@ -31,7 +33,7 @@ export const createMakeSendServiceWorkerTrackingData = ({
31
33
  * @async
32
34
  * @function
33
35
  * @param {Object} options
34
- * @param {Object} options.xdm
36
+ * @param {XdmTrackingContext} options.xdm
35
37
  * @param {string} [options.actionLabel]
36
38
  * @param {number} [options.applicationLaunches=0]
37
39
  *
@@ -50,6 +52,7 @@ export const createMakeSendServiceWorkerTrackingData = ({
50
52
  };
51
53
  }
52
54
 
55
+ /** @type {{ name: keyof PushServiceWorkerConfig, errorField: string }[]} */
53
56
  const requiredFields = [
54
57
  { name: "browser", errorField: "Browser" },
55
58
  { name: "ecid", errorField: "ECID" },
@@ -82,19 +85,24 @@ export const createMakeSendServiceWorkerTrackingData = ({
82
85
 
83
86
  const url = `https://${edgeDomain}/${edgeBasePath}/v1/interact?configId=${datastreamId}&requestId=${uuidv4()}`;
84
87
 
88
+ // The requiredFields loop above guarantees these are defined strings.
89
+ const validEcid = /** @type {string} */ (ecid);
90
+ const validBrowser = /** @type {string} */ (browser);
91
+ const validDatasetId = /** @type {string} */ (datasetId);
92
+
85
93
  /** @type {TrackingDataPayload} */
86
94
  const payload = {
87
95
  events: [
88
96
  {
89
97
  xdm: {
90
98
  identityMap: {
91
- ECID: [{ id: ecid }],
99
+ ECID: [{ id: validEcid }],
92
100
  },
93
101
  timestamp: new Date().toISOString(),
94
102
  pushNotificationTracking: {
95
103
  ...customActionData,
96
104
  pushProviderMessageID: uuidv4(),
97
- pushProvider: browser.toLowerCase(),
105
+ pushProvider: validBrowser.toLowerCase(),
98
106
  },
99
107
  application: {
100
108
  launches: {
@@ -121,7 +129,7 @@ export const createMakeSendServiceWorkerTrackingData = ({
121
129
  },
122
130
  meta: {
123
131
  collect: {
124
- datasetId,
132
+ datasetId: validDatasetId,
125
133
  },
126
134
  },
127
135
  },
@@ -56,6 +56,9 @@ governing permissions and limitations under the License.
56
56
  * @property {string} web.title
57
57
  * @property {string} web.body
58
58
  * @property {string|null} web.media
59
+ * @property {Object} [web.identity]
60
+ * @property {string} [web.identity.id]
61
+ * @property {string} [web.identity.namespace]
59
62
  * @property {Object} web.interaction
60
63
  * @property {string} web.interaction.type
61
64
  * @property {string|null} web.interaction.uri
@@ -101,4 +104,16 @@ governing permissions and limitations under the License.
101
104
  * @property {(...args: any[]) => void} error
102
105
  */
103
106
 
107
+ /**
108
+ * @typedef {Object} PushServiceWorkerConfig
109
+ * @property {string} [ecid]
110
+ * @property {string} [browser]
111
+ * @property {string} [edgeDomain]
112
+ * @property {string} [edgeBasePath]
113
+ * @property {string} [datastreamId]
114
+ * @property {string} [datasetId]
115
+ * @property {string} [id]
116
+ * @property {number} [timestamp]
117
+ */
118
+
104
119
  export const Types = {};
@@ -50,6 +50,7 @@ const createEventListeners = ({ platform, sw }) => {
50
50
  event,
51
51
  sw,
52
52
  logger: platform.logger,
53
+ readFromIndexedDb,
53
54
  });
54
55
  },
55
56
  /**