@adobe/alloy 2.34.2-beta.2 → 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.
- package/dist/alloy.js +312 -96
- package/dist/alloy.min.js +1 -1
- package/dist/alloyServiceWorker.js +85 -12
- package/dist/alloyServiceWorker.min.js +1 -1
- package/dist/types/browser/src/components/ActivityCollector/configValidators.d.ts +2 -2
- package/dist/types/browser/src/components/Advertising/configValidators.d.ts +1 -1
- package/dist/types/browser/src/components/Context/index.d.ts +1 -1
- package/dist/types/browser/src/components/Personalization/index.d.ts +1 -1
- package/dist/types/browser/src/components/PushNotifications/helpers/readFromIndexedDb.d.ts +2 -1
- package/dist/types/browser/src/components/PushNotifications/helpers/saveToIndexedDb.d.ts +4 -3
- package/dist/types/browser/src/components/PushNotifications/helpers/serviceWorkerPushListener.d.ts +3 -1
- package/dist/types/browser/src/components/PushNotifications/index.d.ts +1 -1
- package/dist/types/browser/src/components/PushNotifications/request/makeSendPushSubscriptionRequest.d.ts +5 -1
- package/dist/types/browser/src/components/PushNotifications/request/makeSendServiceWorkerTrackingData.d.ts +4 -2
- package/dist/types/browser/src/components/PushNotifications/types.d.ts +28 -0
- package/dist/types/core/src/components/BrandConcierge/configValidators.d.ts +1 -1
- package/dist/types/core/src/components/Consent/configValidators.d.ts +1 -1
- package/dist/types/core/src/components/Consent/validateSetConsentOptions.d.ts +1 -1
- package/dist/types/core/src/components/Identity/configValidators.d.ts +1 -1
- package/dist/types/core/src/components/RulesEngine/index.d.ts +1 -1
- package/dist/types/core/src/components/StreamingMedia/configValidators.d.ts +1 -1
- package/dist/types/core/src/core/config/createCoreConfigs.d.ts +1 -1
- package/dist/types/core/src/utils/bytes.d.ts +1 -1
- package/dist/types/core/src/utils/validateConfigOverride.d.ts +1 -1
- package/dist/types/core/src/utils/validateIdentityMap.d.ts +1 -1
- package/dist/types/core/src/utils/validation/index.d.ts +23 -11
- package/dist/types/core/src/utils/validation/types.d.ts +133 -0
- package/dist/types/core/src/utils/validation/utils.d.ts +4 -3
- package/package.json +2 -2
- package/src/components/PushNotifications/helpers/getPushSubscriptionDetails.js +10 -8
- package/src/components/PushNotifications/helpers/readFromIndexedDb.js +3 -2
- package/src/components/PushNotifications/helpers/saveToIndexedDb.js +7 -2
- package/src/components/PushNotifications/helpers/serviceWorkerPushListener.js +58 -2
- package/src/components/PushNotifications/index.js +7 -6
- package/src/components/PushNotifications/request/makeSendPushSubscriptionRequest.js +20 -5
- package/src/components/PushNotifications/request/makeSendServiceWorkerTrackingData.js +14 -6
- package/src/components/PushNotifications/types.js +15 -0
- 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 {
|
|
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:
|
|
99
|
-
urlSafe: true
|
|
100
|
-
|
|
101
|
-
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<
|
|
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,10 +22,10 @@ import {
|
|
|
21
22
|
import { DB_NAME, DB_VERSION, STORE_NAME, INDEX_KEY } from "./constants.js";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* @param {
|
|
25
|
+
* @param {Partial<PushServiceWorkerConfig>} data
|
|
25
26
|
* @param {Logger} logger
|
|
26
27
|
*
|
|
27
|
-
* @returns {Promise<
|
|
28
|
+
* @returns {Promise<boolean>} Whether the data was successfully saved.
|
|
28
29
|
*/
|
|
29
30
|
export default async function saveToIndexedDB(data, logger) {
|
|
30
31
|
try {
|
|
@@ -58,7 +59,11 @@ export default async function saveToIndexedDB(data, logger) {
|
|
|
58
59
|
"Successfully saved web SDK config to IndexedDB",
|
|
59
60
|
updatedConfigData,
|
|
60
61
|
);
|
|
62
|
+
|
|
63
|
+
return true;
|
|
61
64
|
} catch (error) {
|
|
62
65
|
logger.error("Failed to save config to IndexedDB", { error });
|
|
66
|
+
|
|
67
|
+
return false;
|
|
63
68
|
}
|
|
64
69
|
}
|
|
@@ -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
|
-
|
|
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/
|
|
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
|
*/
|
|
@@ -59,6 +60,14 @@ export default async ({
|
|
|
59
60
|
await identity.awaitIdentity();
|
|
60
61
|
const ecid = identity.getEcidFromCookie();
|
|
61
62
|
|
|
63
|
+
if (!ecid) {
|
|
64
|
+
logger.info(
|
|
65
|
+
"No ECID is available. Not sending push subscription details to the server.",
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
const pushSubscriptionDetails = await getPushSubscriptionDetails({
|
|
63
72
|
vapidPublicKey,
|
|
64
73
|
window,
|
|
@@ -71,7 +80,7 @@ export default async ({
|
|
|
71
80
|
const cacheValue = `${ecid}${serializedPushSubscriptionDetails}`;
|
|
72
81
|
const storedValue = await storage.getItem(SUBSCRIPTION_DETAILS);
|
|
73
82
|
|
|
74
|
-
if (cacheValue === storedValue) {
|
|
83
|
+
if (storedValue && cacheValue === storedValue) {
|
|
75
84
|
logger.info(
|
|
76
85
|
"Subscription details have not changed. Not sending to the server.",
|
|
77
86
|
);
|
|
@@ -79,8 +88,6 @@ export default async ({
|
|
|
79
88
|
return;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
storage.setItem(SUBSCRIPTION_DETAILS, cacheValue);
|
|
83
|
-
|
|
84
91
|
const payload = await createSendPushSubscriptionPayload({
|
|
85
92
|
eventManager,
|
|
86
93
|
ecid,
|
|
@@ -95,5 +102,13 @@ export default async ({
|
|
|
95
102
|
await consent.awaitConsent();
|
|
96
103
|
await sendEdgeNetworkRequest({ request });
|
|
97
104
|
|
|
98
|
-
await saveToIndexedDb({ ecid }, logger);
|
|
105
|
+
const savedToIndexedDb = await saveToIndexedDb({ ecid }, logger);
|
|
106
|
+
|
|
107
|
+
// Only cache the subscription details once the ECID has been persisted, so
|
|
108
|
+
// that a failed send or a failed IndexedDB write does not short-circuit the
|
|
109
|
+
// dedupe check on the next call. This lets a subsequent sendPushSubscription
|
|
110
|
+
// self-heal instead of getting stuck with a cached subscription but no ECID.
|
|
111
|
+
if (savedToIndexedDb) {
|
|
112
|
+
storage.setItem(SUBSCRIPTION_DETAILS, cacheValue);
|
|
113
|
+
}
|
|
99
114
|
};
|
|
@@ -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<
|
|
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:
|
|
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 {
|
|
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:
|
|
99
|
+
ECID: [{ id: validEcid }],
|
|
92
100
|
},
|
|
93
101
|
timestamp: new Date().toISOString(),
|
|
94
102
|
pushNotificationTracking: {
|
|
95
103
|
...customActionData,
|
|
96
104
|
pushProviderMessageID: uuidv4(),
|
|
97
|
-
pushProvider:
|
|
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 = {};
|