@adobe/alloy 2.25.0 → 2.26.0-beta.1
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/libEs5/components/ActivityCollector/utils/trimQueryFromUrl.js +5 -0
- package/libEs5/components/Identity/createComponent.js +32 -4
- package/libEs5/components/Identity/createDecodeKndctrCookie.js +267 -0
- package/libEs5/components/Identity/index.js +8 -1
- package/libEs5/components/Identity/injectAddQueryStringIdentityToPayload.js +2 -1
- package/libEs5/components/Personalization/handlers/createProcessRedirect.js +7 -0
- package/libEs5/constants/libraryVersion.js +1 -1
- package/libEs5/utils/parseUrl.js +29 -2
- package/libEs6/components/ActivityCollector/utils/trimQueryFromUrl.js +5 -0
- package/libEs6/components/Identity/createComponent.js +32 -4
- package/libEs6/components/Identity/createDecodeKndctrCookie.js +263 -0
- package/libEs6/components/Identity/index.js +8 -1
- package/libEs6/components/Identity/injectAddQueryStringIdentityToPayload.js +2 -1
- package/libEs6/components/Personalization/handlers/createProcessRedirect.js +6 -0
- package/libEs6/constants/libraryVersion.js +1 -1
- package/libEs6/utils/parseUrl.js +28 -1
- package/package.json +34 -35
- package/scripts/alloyBuilder.js +20 -19
|
@@ -12,6 +12,11 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
|
|
|
12
12
|
OF ANY KIND, either express or implied. See the License for the specific language
|
|
13
13
|
governing permissions and limitations under the License.
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* Trims the query from the URL.
|
|
17
|
+
* @param {string} url
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
15
20
|
var _default = url => {
|
|
16
21
|
const questionMarkIndex = url.indexOf("?");
|
|
17
22
|
const hashIndex = url.indexOf("#");
|
|
@@ -25,7 +25,8 @@ var _default = ({
|
|
|
25
25
|
consent,
|
|
26
26
|
appendIdentityToUrl,
|
|
27
27
|
logger,
|
|
28
|
-
getIdentityOptionsValidator
|
|
28
|
+
getIdentityOptionsValidator,
|
|
29
|
+
decodeKndctrCookie
|
|
29
30
|
}) => {
|
|
30
31
|
let namespaces;
|
|
31
32
|
let edge = {};
|
|
@@ -55,7 +56,12 @@ var _default = ({
|
|
|
55
56
|
// https://jira.corp.adobe.com/browse/EXEG-1234
|
|
56
57
|
setLegacyEcid(newNamespaces[_ecidNamespace.default]);
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
if (newNamespaces && Object.keys(newNamespaces).length > 0) {
|
|
60
|
+
namespaces = {
|
|
61
|
+
...namespaces,
|
|
62
|
+
...newNamespaces
|
|
63
|
+
};
|
|
64
|
+
}
|
|
59
65
|
// For sendBeacon requests, getEdge() will return {}, so we are using assign here
|
|
60
66
|
// so that sendBeacon requests don't override the edge info from before.
|
|
61
67
|
edge = {
|
|
@@ -73,7 +79,18 @@ var _default = ({
|
|
|
73
79
|
namespaces: requestedNamespaces
|
|
74
80
|
} = options;
|
|
75
81
|
return consent.awaitConsent().then(() => {
|
|
76
|
-
|
|
82
|
+
if (namespaces) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const ecidFromCookie = decodeKndctrCookie();
|
|
86
|
+
if (ecidFromCookie) {
|
|
87
|
+
if (!namespaces) {
|
|
88
|
+
namespaces = {};
|
|
89
|
+
}
|
|
90
|
+
namespaces[_ecidNamespace.default] = ecidFromCookie;
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
return getIdentity(options);
|
|
77
94
|
}).then(() => {
|
|
78
95
|
return {
|
|
79
96
|
identity: requestedNamespaces.reduce((acc, namespace) => {
|
|
@@ -89,7 +106,18 @@ var _default = ({
|
|
|
89
106
|
optionsValidator: _appendIdentityToUrlOptionsValidator.default,
|
|
90
107
|
run: options => {
|
|
91
108
|
return consent.withConsent().then(() => {
|
|
92
|
-
|
|
109
|
+
if (namespaces) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const ecidFromCookie = decodeKndctrCookie();
|
|
113
|
+
if (ecidFromCookie) {
|
|
114
|
+
if (!namespaces) {
|
|
115
|
+
namespaces = {};
|
|
116
|
+
}
|
|
117
|
+
namespaces[_ecidNamespace.default] = ecidFromCookie;
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return getIdentity(options);
|
|
93
121
|
}).then(() => {
|
|
94
122
|
return {
|
|
95
123
|
url: appendIdentityToUrl(namespaces[_ecidNamespace.default], options.url)
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.default = exports.decodeVarint = void 0;
|
|
4
|
+
var _index = require("../../utils/index.js");
|
|
5
|
+
/*
|
|
6
|
+
Copyright 2024 Adobe. All rights reserved.
|
|
7
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
9
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
12
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
13
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
14
|
+
governing permissions and limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/* eslint-disable no-bitwise */
|
|
17
|
+
|
|
18
|
+
// #region decode protobuf
|
|
19
|
+
|
|
20
|
+
/** Decoding bytes is not something commonly done in vanilla JavaScript work, and as such
|
|
21
|
+
* this file will strive to explain each step of decoding a protobuf in detail.
|
|
22
|
+
* It leans heavily on the protobuf documentation https://protobuf.dev/programming-guides/encoding/,
|
|
23
|
+
* often quoting directly from it without citation.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The kndctr cookie protobuf format
|
|
28
|
+
* From https://git.corp.adobe.com/pages/experience-edge/konductor/#/api/identifying-visitors?id=device-identifiers
|
|
29
|
+
* and https://git.corp.adobe.com/experience-edge/konductor/blob/master/feature-identity/src/main/kotlin/com/adobe/edge/features/identity/data/StoredIdentity.kt#L16
|
|
30
|
+
|
|
31
|
+
* syntax = "proto3";
|
|
32
|
+
*
|
|
33
|
+
* // Device-level identity for Experience Edge
|
|
34
|
+
* message Identity {
|
|
35
|
+
* // The Experience Cloud ID value
|
|
36
|
+
* string ecid = 1;
|
|
37
|
+
*
|
|
38
|
+
* IdentityMetadata metadata = 10;
|
|
39
|
+
*
|
|
40
|
+
* // Used only in the 3rd party domain context.
|
|
41
|
+
* // It stores the UNIX timestamp and some metadata about the last identity sync triggered by Experience Edge.
|
|
42
|
+
* int64 last_sync = 20;
|
|
43
|
+
* int64 sync_hash = 21;
|
|
44
|
+
* int32 id_sync_container_id = 22;
|
|
45
|
+
*
|
|
46
|
+
* // UNIX timestamp when the Identity was last returned in a `state:store` instruction.
|
|
47
|
+
* // The Identity is written at most once every 24h with a large TTL, to ensure it does not expire.
|
|
48
|
+
* int64 write_time = 30;
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* message IdentityMetadata {
|
|
52
|
+
* // UNIX timestamp when this identity was minted.
|
|
53
|
+
* int64 created_at = 1;
|
|
54
|
+
*
|
|
55
|
+
* // Whether or not the identity is random (new) or based on an existing seed.
|
|
56
|
+
* bool is_new = 2;
|
|
57
|
+
*
|
|
58
|
+
* // Type of device for which the identity was generated.
|
|
59
|
+
* // 0 = UNKNOWN, 1 = BROWSER, 2 = MOBILE
|
|
60
|
+
* int32 device_type = 3;
|
|
61
|
+
*
|
|
62
|
+
* // The Experience Edge region in which the identity was minted.
|
|
63
|
+
* string region = 5;
|
|
64
|
+
*
|
|
65
|
+
* // More details on the source of the ECID identity.
|
|
66
|
+
* // Invariant: when `is_new` = true, the source must be set to `RANDOM`.
|
|
67
|
+
* // 0 = RANDOM, 1 = THIRD_PARTY_ID, 2 = FIRST_PARTY_ID, 3 = RECEIVED_IN_REQUEST
|
|
68
|
+
* int32 source = 6;
|
|
69
|
+
* }
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
const ECID_FIELD_NUMBER = 1;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Decodes a varint from a buffer starting at the given offset.
|
|
76
|
+
*
|
|
77
|
+
* Variable-width integers, or varints, are at the core of the wire format. They
|
|
78
|
+
* allow encoding unsigned 64-bit integers using anywhere between one and ten
|
|
79
|
+
* bytes, with small values using fewer bytes.
|
|
80
|
+
*
|
|
81
|
+
* Each byte in the varint has a continuation bit that indicates if the byte
|
|
82
|
+
* that follows it is part of the varint. This is the most significant bit (MSB)
|
|
83
|
+
* of the byte (sometimes also called the sign bit). The lower 7 bits are a
|
|
84
|
+
* payload; the resulting integer is built by appending together the 7-bit
|
|
85
|
+
* payloads of its constituent bytes.
|
|
86
|
+
*
|
|
87
|
+
* 10010110 00000001 // Original inputs.
|
|
88
|
+
* 0010110 0000001 // Drop continuation bits.
|
|
89
|
+
* 0000001 0010110 // Convert to big-endian.
|
|
90
|
+
* 00000010010110 // Concatenate.
|
|
91
|
+
* 128 + 16 + 4 + 2 = 150 // Interpret as an unsigned 64-bit integer.
|
|
92
|
+
*
|
|
93
|
+
* @example decodeVarint(new Uint8Array([0b0, 0b1]), 0) // { value: 1, length: 2 }
|
|
94
|
+
* @example decodeVarint(new Uint8Array([0b10010110, 0b00000001], 0) // { value: 150, length: 2 })
|
|
95
|
+
* @param {Uint8Array} buffer
|
|
96
|
+
* @param {number} offset
|
|
97
|
+
* @returns {{ value: number, length: number }} The value of the varint and the
|
|
98
|
+
* number of bytes it takes up.
|
|
99
|
+
*/
|
|
100
|
+
const decodeVarint = (buffer, offset) => {
|
|
101
|
+
let value = 0;
|
|
102
|
+
let length = 0;
|
|
103
|
+
let byte;
|
|
104
|
+
do {
|
|
105
|
+
if (offset < 0 || offset + length >= buffer.length) {
|
|
106
|
+
throw new Error("Invalid varint: buffer ended unexpectedly");
|
|
107
|
+
}
|
|
108
|
+
byte = buffer[offset + length];
|
|
109
|
+
// Drop the continuation bit (the most significant bit), convert it from
|
|
110
|
+
// little endian to big endian, and add it to the accumulator `value`.
|
|
111
|
+
value |= (byte & 0b01111111) << 7 * length;
|
|
112
|
+
// Increase the length of the varint by one byte.
|
|
113
|
+
length += 1;
|
|
114
|
+
// A varint can be at most 10 bytes long for a 64-bit integer.
|
|
115
|
+
if (length > 10) {
|
|
116
|
+
throw new Error("Invalid varint: too long");
|
|
117
|
+
}
|
|
118
|
+
} while (byte & 0b10000000);
|
|
119
|
+
return {
|
|
120
|
+
value,
|
|
121
|
+
length
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* | ID | Name | Used for |
|
|
127
|
+
* |----|--------|----------------------------------------------------------|
|
|
128
|
+
* | 0 | varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
|
|
129
|
+
* | 1 | I64 | fixed64, sfixed64, double |
|
|
130
|
+
* | 2 | LEN | string, bytes |
|
|
131
|
+
* | 3 | SGROUP | group start (deprecated) |
|
|
132
|
+
* | 4 | EGROUP | group end (deprecated) |
|
|
133
|
+
* | 5 | I32 | fixed32, sfixed32, float |
|
|
134
|
+
*/
|
|
135
|
+
exports.decodeVarint = decodeVarint;
|
|
136
|
+
const WIRE_TYPES = Object.freeze({
|
|
137
|
+
VARINT: 0,
|
|
138
|
+
I64: 1,
|
|
139
|
+
LEN: 2,
|
|
140
|
+
SGROUP: 3,
|
|
141
|
+
EGROUP: 4,
|
|
142
|
+
I32: 5
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Given a protobuf as a Uint8Array and based on the protobuf definition for the
|
|
147
|
+
* kndctr cookie provided at https://git.corp.adobe.com/pages/experience-edge/konductor/#/api/identifying-visitors?id=device-identifiers,
|
|
148
|
+
* this function should return the ECID as a string.
|
|
149
|
+
* The decoding of the protobuf is hand-crafted in order to save on size
|
|
150
|
+
* compared to the full protobuf.js library.
|
|
151
|
+
* @param {Uint8Array} buffer
|
|
152
|
+
* @returns {string}
|
|
153
|
+
*/
|
|
154
|
+
const decodeKndctrProtobuf = buffer => {
|
|
155
|
+
let offset = 0;
|
|
156
|
+
let ecid = null;
|
|
157
|
+
while (offset < buffer.length && !ecid) {
|
|
158
|
+
// A protobuf message is a series of records. Each record is a tag, the length,
|
|
159
|
+
// and the value.
|
|
160
|
+
// A record always starts with the tag. The “tag” of a record is encoded as
|
|
161
|
+
// a varint formed from the field number and the wire type via the formula
|
|
162
|
+
// `(field_number << 3) | wire_type`. In other words, after decoding the
|
|
163
|
+
// varint representing a field, the low 3 bits tell us the wire type, and the rest of the integer tells us the field number.
|
|
164
|
+
// So the first step is to decode the varint
|
|
165
|
+
const {
|
|
166
|
+
value: tag,
|
|
167
|
+
length: tagLength
|
|
168
|
+
} = decodeVarint(buffer, offset);
|
|
169
|
+
offset += tagLength;
|
|
170
|
+
// Next, we get the wire type and the field number.
|
|
171
|
+
// You take the last three bits to get the wire type and then right-shift by
|
|
172
|
+
// three to get the field number.
|
|
173
|
+
const wireType = tag & 0b111;
|
|
174
|
+
const fieldNumber = tag >> 3;
|
|
175
|
+
// We only care about the ECID field, so we will skip any other fields until
|
|
176
|
+
// we find it.
|
|
177
|
+
if (fieldNumber === ECID_FIELD_NUMBER) {
|
|
178
|
+
// The wire type for the ECID field is 2, which means it is a length-delimited field.
|
|
179
|
+
if (wireType === WIRE_TYPES.LEN) {
|
|
180
|
+
// The next varint will tell us the length of the ECID.
|
|
181
|
+
const fieldValueLength = decodeVarint(buffer, offset);
|
|
182
|
+
offset += fieldValueLength.length;
|
|
183
|
+
// The ECID is a UTF-8 encoded string, so we will decode it as such.
|
|
184
|
+
ecid = new TextDecoder().decode(buffer.slice(offset, offset + fieldValueLength.value));
|
|
185
|
+
offset += fieldValueLength.value;
|
|
186
|
+
return ecid;
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
// If we don't care about the field, we skip it.
|
|
190
|
+
// The wire type tells us how to skip the field.
|
|
191
|
+
switch (wireType) {
|
|
192
|
+
case WIRE_TYPES.VARINT:
|
|
193
|
+
// Skip the varint
|
|
194
|
+
offset += decodeVarint(buffer, offset).length;
|
|
195
|
+
break;
|
|
196
|
+
case WIRE_TYPES.I64:
|
|
197
|
+
// Skip the 64-bit integer
|
|
198
|
+
offset += 8;
|
|
199
|
+
break;
|
|
200
|
+
case WIRE_TYPES.LEN:
|
|
201
|
+
{
|
|
202
|
+
// Find the value that represents the length of the vield
|
|
203
|
+
const fieldValueLength = decodeVarint(buffer, offset);
|
|
204
|
+
offset += fieldValueLength.length + fieldValueLength.value;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case WIRE_TYPES.SGROUP:
|
|
208
|
+
// Skip the start group
|
|
209
|
+
break;
|
|
210
|
+
case WIRE_TYPES.EGROUP:
|
|
211
|
+
// Skip the end group
|
|
212
|
+
break;
|
|
213
|
+
case WIRE_TYPES.I32:
|
|
214
|
+
// Skip the 32-bit integer
|
|
215
|
+
offset += 4;
|
|
216
|
+
break;
|
|
217
|
+
default:
|
|
218
|
+
throw new Error("Malformed kndctr cookie. Unknown wire type: " + wireType);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// No ECID was found. Maybe the cookie is malformed, maybe the format was changed.
|
|
224
|
+
throw new Error("No ECID found in cookie.");
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* takes a base64 string of bytes and returns a Uint8Array
|
|
229
|
+
* @param {string} base64
|
|
230
|
+
* @returns {Uint8Array}
|
|
231
|
+
*/
|
|
232
|
+
const base64ToBytes = base64 => {
|
|
233
|
+
const binString = atob(base64);
|
|
234
|
+
return Uint8Array.from(binString, m => m.codePointAt(0));
|
|
235
|
+
};
|
|
236
|
+
// #endregion
|
|
237
|
+
|
|
238
|
+
// #region decode cookie
|
|
239
|
+
var _default = ({
|
|
240
|
+
orgId,
|
|
241
|
+
cookieJar,
|
|
242
|
+
logger
|
|
243
|
+
}) => {
|
|
244
|
+
const kndctrCookieName = (0, _index.getNamespacedCookieName)(orgId, "identity");
|
|
245
|
+
/**
|
|
246
|
+
* Returns the ECID from the kndctr cookie.
|
|
247
|
+
* @returns {string|null}
|
|
248
|
+
*/
|
|
249
|
+
return () => {
|
|
250
|
+
const cookie = cookieJar.get(kndctrCookieName);
|
|
251
|
+
if (!cookie) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const decodedCookie = decodeURIComponent(cookie).replace(/_/g, "/").replace(/-/g, "+");
|
|
256
|
+
// cookie is a base64 encoded byte representation of a Identity protobuf message
|
|
257
|
+
// and we need to get it to a Uint8Array in order to decode it
|
|
258
|
+
|
|
259
|
+
const cookieBytes = base64ToBytes(decodedCookie);
|
|
260
|
+
return decodeKndctrProtobuf(cookieBytes);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
logger.warn("Unable to decode ECID from " + kndctrCookieName + " cookie", error);
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}; // #endregion
|
|
267
|
+
exports.default = _default;
|
|
@@ -22,6 +22,7 @@ var _createIdentityRequest = require("./getIdentity/createIdentityRequest.js");
|
|
|
22
22
|
var _createIdentityRequestPayload = require("./getIdentity/createIdentityRequestPayload.js");
|
|
23
23
|
var _injectAppendIdentityToUrl = require("./appendIdentityToUrl/injectAppendIdentityToUrl.js");
|
|
24
24
|
var _createGetIdentityOptionsValidator = require("./getIdentity/createGetIdentityOptionsValidator.js");
|
|
25
|
+
var _createDecodeKndctrCookie = require("./createDecodeKndctrCookie.js");
|
|
25
26
|
/*
|
|
26
27
|
Copyright 2019 Adobe. All rights reserved.
|
|
27
28
|
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
@@ -121,6 +122,11 @@ const createIdentity = ({
|
|
|
121
122
|
thirdPartyCookiesEnabled,
|
|
122
123
|
areThirdPartyCookiesSupportedByDefault
|
|
123
124
|
});
|
|
125
|
+
const decodeKndctrCookie = (0, _createDecodeKndctrCookie.default)({
|
|
126
|
+
orgId,
|
|
127
|
+
cookieJar: loggingCookieJar,
|
|
128
|
+
logger
|
|
129
|
+
});
|
|
124
130
|
return (0, _createComponent.default)({
|
|
125
131
|
addEcidQueryToPayload,
|
|
126
132
|
addQueryStringIdentityToPayload,
|
|
@@ -133,7 +139,8 @@ const createIdentity = ({
|
|
|
133
139
|
appendIdentityToUrl,
|
|
134
140
|
logger,
|
|
135
141
|
config,
|
|
136
|
-
getIdentityOptionsValidator
|
|
142
|
+
getIdentityOptionsValidator,
|
|
143
|
+
decodeKndctrCookie
|
|
137
144
|
});
|
|
138
145
|
};
|
|
139
146
|
createIdentity.namespace = "Identity";
|
|
@@ -41,7 +41,8 @@ var _default = ({
|
|
|
41
41
|
}
|
|
42
42
|
const properties = queryStringValue.split("|").reduce((memo, keyValue) => {
|
|
43
43
|
const [key, value] = keyValue.split("=");
|
|
44
|
-
memo[key] = value;
|
|
44
|
+
memo[key] = (0, _decodeUriComponentSafely.default)(value);
|
|
45
|
+
memo[key] = memo[key].replace(/[^a-zA-Z0-9@_-]/g, ""); // sanitization
|
|
45
46
|
return memo;
|
|
46
47
|
}, {});
|
|
47
48
|
// We are using MCMID and MCORGID to be compatible with Visitor.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.default = void 0;
|
|
4
|
+
var _index = require("../flicker/index.js");
|
|
4
5
|
/*
|
|
5
6
|
Copyright 2023 Adobe. All rights reserved.
|
|
6
7
|
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
@@ -12,6 +13,8 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
|
|
|
12
13
|
OF ANY KIND, either express or implied. See the License for the specific language
|
|
13
14
|
governing permissions and limitations under the License.
|
|
14
15
|
*/
|
|
16
|
+
|
|
17
|
+
const REDIRECT_HIDING_ELEMENT = "BODY";
|
|
15
18
|
var _default = ({
|
|
16
19
|
logger,
|
|
17
20
|
executeRedirect,
|
|
@@ -25,6 +28,7 @@ var _default = ({
|
|
|
25
28
|
return {};
|
|
26
29
|
}
|
|
27
30
|
const render = () => {
|
|
31
|
+
(0, _index.hideElements)(REDIRECT_HIDING_ELEMENT);
|
|
28
32
|
return collect({
|
|
29
33
|
decisionsMeta: [item.getProposition().getNotification()],
|
|
30
34
|
documentMayUnload: true
|
|
@@ -43,6 +47,9 @@ var _default = ({
|
|
|
43
47
|
// for display notifications from this request, they will never run because this promise will
|
|
44
48
|
// not resolve. This is intentional because we don't want to run bottom of page events if
|
|
45
49
|
// there is a redirect.
|
|
50
|
+
}).catch(error => {
|
|
51
|
+
(0, _index.showElements)(REDIRECT_HIDING_ELEMENT);
|
|
52
|
+
throw error;
|
|
46
53
|
});
|
|
47
54
|
};
|
|
48
55
|
return {
|
|
@@ -14,4 +14,4 @@ governing permissions and limitations under the License.
|
|
|
14
14
|
*/
|
|
15
15
|
// The __VERSION__ keyword will be replace at alloy build time with the package.json version.
|
|
16
16
|
// see babel-plugin-version
|
|
17
|
-
var _default = exports.default = "2.
|
|
17
|
+
var _default = exports.default = "2.26.0-beta.1";
|
package/libEs5/utils/parseUrl.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.default = void 0;
|
|
4
|
-
var _parseUri = require("parse-uri");
|
|
5
4
|
var _isString = require("./isString.js");
|
|
6
5
|
/*
|
|
7
6
|
Copyright 2023 Adobe. All rights reserved.
|
|
@@ -44,11 +43,39 @@ const parseDomainBasic = host => {
|
|
|
44
43
|
}
|
|
45
44
|
return result;
|
|
46
45
|
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {Object} ParseUriResult
|
|
49
|
+
* @property {string} host
|
|
50
|
+
* @property {string} path
|
|
51
|
+
* @property {string} query
|
|
52
|
+
* @property {string} anchor
|
|
53
|
+
*
|
|
54
|
+
* @param {string} url
|
|
55
|
+
* @returns {ParseUriResult}
|
|
56
|
+
*/
|
|
57
|
+
const parseUri = url => {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = new URL(url);
|
|
60
|
+
let path = parsed.pathname;
|
|
61
|
+
if (!url.endsWith("/") && path === "/") {
|
|
62
|
+
path = "";
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
host: parsed.hostname,
|
|
66
|
+
path,
|
|
67
|
+
query: parsed.search.replace(/^\?/, ""),
|
|
68
|
+
anchor: parsed.hash.replace(/^#/, "")
|
|
69
|
+
};
|
|
70
|
+
} catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
47
74
|
const parseUrl = (url, parseDomain = parseDomainBasic) => {
|
|
48
75
|
if (!(0, _isString.default)(url)) {
|
|
49
76
|
url = "";
|
|
50
77
|
}
|
|
51
|
-
const parsed = (
|
|
78
|
+
const parsed = parseUri(url) || {};
|
|
52
79
|
const {
|
|
53
80
|
host = "",
|
|
54
81
|
path = "",
|
|
@@ -10,6 +10,11 @@ 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
|
+
/**
|
|
14
|
+
* Trims the query from the URL.
|
|
15
|
+
* @param {string} url
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
13
18
|
export default url => {
|
|
14
19
|
const questionMarkIndex = url.indexOf("?");
|
|
15
20
|
const hashIndex = url.indexOf("#");
|
|
@@ -22,7 +22,8 @@ export default ({
|
|
|
22
22
|
consent,
|
|
23
23
|
appendIdentityToUrl,
|
|
24
24
|
logger,
|
|
25
|
-
getIdentityOptionsValidator
|
|
25
|
+
getIdentityOptionsValidator,
|
|
26
|
+
decodeKndctrCookie
|
|
26
27
|
}) => {
|
|
27
28
|
let namespaces;
|
|
28
29
|
let edge = {};
|
|
@@ -52,7 +53,12 @@ export default ({
|
|
|
52
53
|
// https://jira.corp.adobe.com/browse/EXEG-1234
|
|
53
54
|
setLegacyEcid(newNamespaces[ecidNamespace]);
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
+
if (newNamespaces && Object.keys(newNamespaces).length > 0) {
|
|
57
|
+
namespaces = {
|
|
58
|
+
...namespaces,
|
|
59
|
+
...newNamespaces
|
|
60
|
+
};
|
|
61
|
+
}
|
|
56
62
|
// For sendBeacon requests, getEdge() will return {}, so we are using assign here
|
|
57
63
|
// so that sendBeacon requests don't override the edge info from before.
|
|
58
64
|
edge = {
|
|
@@ -70,7 +76,18 @@ export default ({
|
|
|
70
76
|
namespaces: requestedNamespaces
|
|
71
77
|
} = options;
|
|
72
78
|
return consent.awaitConsent().then(() => {
|
|
73
|
-
|
|
79
|
+
if (namespaces) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const ecidFromCookie = decodeKndctrCookie();
|
|
83
|
+
if (ecidFromCookie) {
|
|
84
|
+
if (!namespaces) {
|
|
85
|
+
namespaces = {};
|
|
86
|
+
}
|
|
87
|
+
namespaces[ecidNamespace] = ecidFromCookie;
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
return getIdentity(options);
|
|
74
91
|
}).then(() => {
|
|
75
92
|
return {
|
|
76
93
|
identity: requestedNamespaces.reduce((acc, namespace) => {
|
|
@@ -86,7 +103,18 @@ export default ({
|
|
|
86
103
|
optionsValidator: appendIdentityToUrlOptionsValidator,
|
|
87
104
|
run: options => {
|
|
88
105
|
return consent.withConsent().then(() => {
|
|
89
|
-
|
|
106
|
+
if (namespaces) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const ecidFromCookie = decodeKndctrCookie();
|
|
110
|
+
if (ecidFromCookie) {
|
|
111
|
+
if (!namespaces) {
|
|
112
|
+
namespaces = {};
|
|
113
|
+
}
|
|
114
|
+
namespaces[ecidNamespace] = ecidFromCookie;
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return getIdentity(options);
|
|
90
118
|
}).then(() => {
|
|
91
119
|
return {
|
|
92
120
|
url: appendIdentityToUrl(namespaces[ecidNamespace], options.url)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024 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
|
+
/* eslint-disable no-bitwise */
|
|
13
|
+
import { getNamespacedCookieName } from "../../utils/index.js";
|
|
14
|
+
|
|
15
|
+
// #region decode protobuf
|
|
16
|
+
|
|
17
|
+
/** Decoding bytes is not something commonly done in vanilla JavaScript work, and as such
|
|
18
|
+
* this file will strive to explain each step of decoding a protobuf in detail.
|
|
19
|
+
* It leans heavily on the protobuf documentation https://protobuf.dev/programming-guides/encoding/,
|
|
20
|
+
* often quoting directly from it without citation.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The kndctr cookie protobuf format
|
|
25
|
+
* From https://git.corp.adobe.com/pages/experience-edge/konductor/#/api/identifying-visitors?id=device-identifiers
|
|
26
|
+
* and https://git.corp.adobe.com/experience-edge/konductor/blob/master/feature-identity/src/main/kotlin/com/adobe/edge/features/identity/data/StoredIdentity.kt#L16
|
|
27
|
+
|
|
28
|
+
* syntax = "proto3";
|
|
29
|
+
*
|
|
30
|
+
* // Device-level identity for Experience Edge
|
|
31
|
+
* message Identity {
|
|
32
|
+
* // The Experience Cloud ID value
|
|
33
|
+
* string ecid = 1;
|
|
34
|
+
*
|
|
35
|
+
* IdentityMetadata metadata = 10;
|
|
36
|
+
*
|
|
37
|
+
* // Used only in the 3rd party domain context.
|
|
38
|
+
* // It stores the UNIX timestamp and some metadata about the last identity sync triggered by Experience Edge.
|
|
39
|
+
* int64 last_sync = 20;
|
|
40
|
+
* int64 sync_hash = 21;
|
|
41
|
+
* int32 id_sync_container_id = 22;
|
|
42
|
+
*
|
|
43
|
+
* // UNIX timestamp when the Identity was last returned in a `state:store` instruction.
|
|
44
|
+
* // The Identity is written at most once every 24h with a large TTL, to ensure it does not expire.
|
|
45
|
+
* int64 write_time = 30;
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* message IdentityMetadata {
|
|
49
|
+
* // UNIX timestamp when this identity was minted.
|
|
50
|
+
* int64 created_at = 1;
|
|
51
|
+
*
|
|
52
|
+
* // Whether or not the identity is random (new) or based on an existing seed.
|
|
53
|
+
* bool is_new = 2;
|
|
54
|
+
*
|
|
55
|
+
* // Type of device for which the identity was generated.
|
|
56
|
+
* // 0 = UNKNOWN, 1 = BROWSER, 2 = MOBILE
|
|
57
|
+
* int32 device_type = 3;
|
|
58
|
+
*
|
|
59
|
+
* // The Experience Edge region in which the identity was minted.
|
|
60
|
+
* string region = 5;
|
|
61
|
+
*
|
|
62
|
+
* // More details on the source of the ECID identity.
|
|
63
|
+
* // Invariant: when `is_new` = true, the source must be set to `RANDOM`.
|
|
64
|
+
* // 0 = RANDOM, 1 = THIRD_PARTY_ID, 2 = FIRST_PARTY_ID, 3 = RECEIVED_IN_REQUEST
|
|
65
|
+
* int32 source = 6;
|
|
66
|
+
* }
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
const ECID_FIELD_NUMBER = 1;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Decodes a varint from a buffer starting at the given offset.
|
|
73
|
+
*
|
|
74
|
+
* Variable-width integers, or varints, are at the core of the wire format. They
|
|
75
|
+
* allow encoding unsigned 64-bit integers using anywhere between one and ten
|
|
76
|
+
* bytes, with small values using fewer bytes.
|
|
77
|
+
*
|
|
78
|
+
* Each byte in the varint has a continuation bit that indicates if the byte
|
|
79
|
+
* that follows it is part of the varint. This is the most significant bit (MSB)
|
|
80
|
+
* of the byte (sometimes also called the sign bit). The lower 7 bits are a
|
|
81
|
+
* payload; the resulting integer is built by appending together the 7-bit
|
|
82
|
+
* payloads of its constituent bytes.
|
|
83
|
+
*
|
|
84
|
+
* 10010110 00000001 // Original inputs.
|
|
85
|
+
* 0010110 0000001 // Drop continuation bits.
|
|
86
|
+
* 0000001 0010110 // Convert to big-endian.
|
|
87
|
+
* 00000010010110 // Concatenate.
|
|
88
|
+
* 128 + 16 + 4 + 2 = 150 // Interpret as an unsigned 64-bit integer.
|
|
89
|
+
*
|
|
90
|
+
* @example decodeVarint(new Uint8Array([0b0, 0b1]), 0) // { value: 1, length: 2 }
|
|
91
|
+
* @example decodeVarint(new Uint8Array([0b10010110, 0b00000001], 0) // { value: 150, length: 2 })
|
|
92
|
+
* @param {Uint8Array} buffer
|
|
93
|
+
* @param {number} offset
|
|
94
|
+
* @returns {{ value: number, length: number }} The value of the varint and the
|
|
95
|
+
* number of bytes it takes up.
|
|
96
|
+
*/
|
|
97
|
+
export const decodeVarint = (buffer, offset) => {
|
|
98
|
+
let value = 0;
|
|
99
|
+
let length = 0;
|
|
100
|
+
let byte;
|
|
101
|
+
do {
|
|
102
|
+
if (offset < 0 || offset + length >= buffer.length) {
|
|
103
|
+
throw new Error("Invalid varint: buffer ended unexpectedly");
|
|
104
|
+
}
|
|
105
|
+
byte = buffer[offset + length];
|
|
106
|
+
// Drop the continuation bit (the most significant bit), convert it from
|
|
107
|
+
// little endian to big endian, and add it to the accumulator `value`.
|
|
108
|
+
value |= (byte & 0b01111111) << 7 * length;
|
|
109
|
+
// Increase the length of the varint by one byte.
|
|
110
|
+
length += 1;
|
|
111
|
+
// A varint can be at most 10 bytes long for a 64-bit integer.
|
|
112
|
+
if (length > 10) {
|
|
113
|
+
throw new Error("Invalid varint: too long");
|
|
114
|
+
}
|
|
115
|
+
} while (byte & 0b10000000);
|
|
116
|
+
return {
|
|
117
|
+
value,
|
|
118
|
+
length
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* | ID | Name | Used for |
|
|
124
|
+
* |----|--------|----------------------------------------------------------|
|
|
125
|
+
* | 0 | varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
|
|
126
|
+
* | 1 | I64 | fixed64, sfixed64, double |
|
|
127
|
+
* | 2 | LEN | string, bytes |
|
|
128
|
+
* | 3 | SGROUP | group start (deprecated) |
|
|
129
|
+
* | 4 | EGROUP | group end (deprecated) |
|
|
130
|
+
* | 5 | I32 | fixed32, sfixed32, float |
|
|
131
|
+
*/
|
|
132
|
+
const WIRE_TYPES = Object.freeze({
|
|
133
|
+
VARINT: 0,
|
|
134
|
+
I64: 1,
|
|
135
|
+
LEN: 2,
|
|
136
|
+
SGROUP: 3,
|
|
137
|
+
EGROUP: 4,
|
|
138
|
+
I32: 5
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Given a protobuf as a Uint8Array and based on the protobuf definition for the
|
|
143
|
+
* kndctr cookie provided at https://git.corp.adobe.com/pages/experience-edge/konductor/#/api/identifying-visitors?id=device-identifiers,
|
|
144
|
+
* this function should return the ECID as a string.
|
|
145
|
+
* The decoding of the protobuf is hand-crafted in order to save on size
|
|
146
|
+
* compared to the full protobuf.js library.
|
|
147
|
+
* @param {Uint8Array} buffer
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
const decodeKndctrProtobuf = buffer => {
|
|
151
|
+
let offset = 0;
|
|
152
|
+
let ecid = null;
|
|
153
|
+
while (offset < buffer.length && !ecid) {
|
|
154
|
+
// A protobuf message is a series of records. Each record is a tag, the length,
|
|
155
|
+
// and the value.
|
|
156
|
+
// A record always starts with the tag. The “tag” of a record is encoded as
|
|
157
|
+
// a varint formed from the field number and the wire type via the formula
|
|
158
|
+
// `(field_number << 3) | wire_type`. In other words, after decoding the
|
|
159
|
+
// varint representing a field, the low 3 bits tell us the wire type, and the rest of the integer tells us the field number.
|
|
160
|
+
// So the first step is to decode the varint
|
|
161
|
+
const {
|
|
162
|
+
value: tag,
|
|
163
|
+
length: tagLength
|
|
164
|
+
} = decodeVarint(buffer, offset);
|
|
165
|
+
offset += tagLength;
|
|
166
|
+
// Next, we get the wire type and the field number.
|
|
167
|
+
// You take the last three bits to get the wire type and then right-shift by
|
|
168
|
+
// three to get the field number.
|
|
169
|
+
const wireType = tag & 0b111;
|
|
170
|
+
const fieldNumber = tag >> 3;
|
|
171
|
+
// We only care about the ECID field, so we will skip any other fields until
|
|
172
|
+
// we find it.
|
|
173
|
+
if (fieldNumber === ECID_FIELD_NUMBER) {
|
|
174
|
+
// The wire type for the ECID field is 2, which means it is a length-delimited field.
|
|
175
|
+
if (wireType === WIRE_TYPES.LEN) {
|
|
176
|
+
// The next varint will tell us the length of the ECID.
|
|
177
|
+
const fieldValueLength = decodeVarint(buffer, offset);
|
|
178
|
+
offset += fieldValueLength.length;
|
|
179
|
+
// The ECID is a UTF-8 encoded string, so we will decode it as such.
|
|
180
|
+
ecid = new TextDecoder().decode(buffer.slice(offset, offset + fieldValueLength.value));
|
|
181
|
+
offset += fieldValueLength.value;
|
|
182
|
+
return ecid;
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// If we don't care about the field, we skip it.
|
|
186
|
+
// The wire type tells us how to skip the field.
|
|
187
|
+
switch (wireType) {
|
|
188
|
+
case WIRE_TYPES.VARINT:
|
|
189
|
+
// Skip the varint
|
|
190
|
+
offset += decodeVarint(buffer, offset).length;
|
|
191
|
+
break;
|
|
192
|
+
case WIRE_TYPES.I64:
|
|
193
|
+
// Skip the 64-bit integer
|
|
194
|
+
offset += 8;
|
|
195
|
+
break;
|
|
196
|
+
case WIRE_TYPES.LEN:
|
|
197
|
+
{
|
|
198
|
+
// Find the value that represents the length of the vield
|
|
199
|
+
const fieldValueLength = decodeVarint(buffer, offset);
|
|
200
|
+
offset += fieldValueLength.length + fieldValueLength.value;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case WIRE_TYPES.SGROUP:
|
|
204
|
+
// Skip the start group
|
|
205
|
+
break;
|
|
206
|
+
case WIRE_TYPES.EGROUP:
|
|
207
|
+
// Skip the end group
|
|
208
|
+
break;
|
|
209
|
+
case WIRE_TYPES.I32:
|
|
210
|
+
// Skip the 32-bit integer
|
|
211
|
+
offset += 4;
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
throw new Error(`Malformed kndctr cookie. Unknown wire type: ${wireType}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// No ECID was found. Maybe the cookie is malformed, maybe the format was changed.
|
|
220
|
+
throw new Error("No ECID found in cookie.");
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* takes a base64 string of bytes and returns a Uint8Array
|
|
225
|
+
* @param {string} base64
|
|
226
|
+
* @returns {Uint8Array}
|
|
227
|
+
*/
|
|
228
|
+
const base64ToBytes = base64 => {
|
|
229
|
+
const binString = atob(base64);
|
|
230
|
+
return Uint8Array.from(binString, m => m.codePointAt(0));
|
|
231
|
+
};
|
|
232
|
+
// #endregion
|
|
233
|
+
|
|
234
|
+
// #region decode cookie
|
|
235
|
+
export default ({
|
|
236
|
+
orgId,
|
|
237
|
+
cookieJar,
|
|
238
|
+
logger
|
|
239
|
+
}) => {
|
|
240
|
+
const kndctrCookieName = getNamespacedCookieName(orgId, "identity");
|
|
241
|
+
/**
|
|
242
|
+
* Returns the ECID from the kndctr cookie.
|
|
243
|
+
* @returns {string|null}
|
|
244
|
+
*/
|
|
245
|
+
return () => {
|
|
246
|
+
const cookie = cookieJar.get(kndctrCookieName);
|
|
247
|
+
if (!cookie) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const decodedCookie = decodeURIComponent(cookie).replace(/_/g, "/").replace(/-/g, "+");
|
|
252
|
+
// cookie is a base64 encoded byte representation of a Identity protobuf message
|
|
253
|
+
// and we need to get it to a Uint8Array in order to decode it
|
|
254
|
+
|
|
255
|
+
const cookieBytes = base64ToBytes(decodedCookie);
|
|
256
|
+
return decodeKndctrProtobuf(cookieBytes);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
logger.warn(`Unable to decode ECID from ${kndctrCookieName} cookie`, error);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
// #endregion
|
|
@@ -31,6 +31,7 @@ import createIdentityRequest from "./getIdentity/createIdentityRequest.js";
|
|
|
31
31
|
import createIdentityRequestPayload from "./getIdentity/createIdentityRequestPayload.js";
|
|
32
32
|
import injectAppendIdentityToUrl from "./appendIdentityToUrl/injectAppendIdentityToUrl.js";
|
|
33
33
|
import createGetIdentityOptionsValidator from "./getIdentity/createGetIdentityOptionsValidator.js";
|
|
34
|
+
import createGetEcidFromCookie from "./createDecodeKndctrCookie.js";
|
|
34
35
|
const createIdentity = ({
|
|
35
36
|
config,
|
|
36
37
|
logger,
|
|
@@ -118,6 +119,11 @@ const createIdentity = ({
|
|
|
118
119
|
thirdPartyCookiesEnabled,
|
|
119
120
|
areThirdPartyCookiesSupportedByDefault
|
|
120
121
|
});
|
|
122
|
+
const decodeKndctrCookie = createGetEcidFromCookie({
|
|
123
|
+
orgId,
|
|
124
|
+
cookieJar: loggingCookieJar,
|
|
125
|
+
logger
|
|
126
|
+
});
|
|
121
127
|
return createComponent({
|
|
122
128
|
addEcidQueryToPayload,
|
|
123
129
|
addQueryStringIdentityToPayload,
|
|
@@ -130,7 +136,8 @@ const createIdentity = ({
|
|
|
130
136
|
appendIdentityToUrl,
|
|
131
137
|
logger,
|
|
132
138
|
config,
|
|
133
|
-
getIdentityOptionsValidator
|
|
139
|
+
getIdentityOptionsValidator,
|
|
140
|
+
decodeKndctrCookie
|
|
134
141
|
});
|
|
135
142
|
};
|
|
136
143
|
createIdentity.namespace = "Identity";
|
|
@@ -39,7 +39,8 @@ export default ({
|
|
|
39
39
|
}
|
|
40
40
|
const properties = queryStringValue.split("|").reduce((memo, keyValue) => {
|
|
41
41
|
const [key, value] = keyValue.split("=");
|
|
42
|
-
memo[key] = value;
|
|
42
|
+
memo[key] = decodeUriComponentSafely(value);
|
|
43
|
+
memo[key] = memo[key].replace(/[^a-zA-Z0-9@_-]/g, ""); // sanitization
|
|
43
44
|
return memo;
|
|
44
45
|
}, {});
|
|
45
46
|
// We are using MCMID and MCORGID to be compatible with Visitor.
|
|
@@ -9,6 +9,8 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
|
|
|
9
9
|
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
+
import { hideElements, showElements } from "../flicker/index.js";
|
|
13
|
+
const REDIRECT_HIDING_ELEMENT = "BODY";
|
|
12
14
|
export default ({
|
|
13
15
|
logger,
|
|
14
16
|
executeRedirect,
|
|
@@ -22,6 +24,7 @@ export default ({
|
|
|
22
24
|
return {};
|
|
23
25
|
}
|
|
24
26
|
const render = () => {
|
|
27
|
+
hideElements(REDIRECT_HIDING_ELEMENT);
|
|
25
28
|
return collect({
|
|
26
29
|
decisionsMeta: [item.getProposition().getNotification()],
|
|
27
30
|
documentMayUnload: true
|
|
@@ -40,6 +43,9 @@ export default ({
|
|
|
40
43
|
// for display notifications from this request, they will never run because this promise will
|
|
41
44
|
// not resolve. This is intentional because we don't want to run bottom of page events if
|
|
42
45
|
// there is a redirect.
|
|
46
|
+
}).catch(error => {
|
|
47
|
+
showElements(REDIRECT_HIDING_ELEMENT);
|
|
48
|
+
throw error;
|
|
43
49
|
});
|
|
44
50
|
};
|
|
45
51
|
return {
|
package/libEs6/utils/parseUrl.js
CHANGED
|
@@ -9,7 +9,6 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
|
|
|
9
9
|
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
import parseUri from "parse-uri";
|
|
13
12
|
import isString from "./isString.js";
|
|
14
13
|
const parseDomainBasic = host => {
|
|
15
14
|
const result = {};
|
|
@@ -40,6 +39,34 @@ const parseDomainBasic = host => {
|
|
|
40
39
|
}
|
|
41
40
|
return result;
|
|
42
41
|
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {Object} ParseUriResult
|
|
45
|
+
* @property {string} host
|
|
46
|
+
* @property {string} path
|
|
47
|
+
* @property {string} query
|
|
48
|
+
* @property {string} anchor
|
|
49
|
+
*
|
|
50
|
+
* @param {string} url
|
|
51
|
+
* @returns {ParseUriResult}
|
|
52
|
+
*/
|
|
53
|
+
const parseUri = url => {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = new URL(url);
|
|
56
|
+
let path = parsed.pathname;
|
|
57
|
+
if (!url.endsWith("/") && path === "/") {
|
|
58
|
+
path = "";
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
host: parsed.hostname,
|
|
62
|
+
path,
|
|
63
|
+
query: parsed.search.replace(/^\?/, ""),
|
|
64
|
+
anchor: parsed.hash.replace(/^#/, "")
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
43
70
|
const parseUrl = (url, parseDomain = parseDomainBasic) => {
|
|
44
71
|
if (!isString(url)) {
|
|
45
72
|
url = "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/alloy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.0-beta.1",
|
|
4
4
|
"description": "Adobe Experience Platform Web SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "libEs5/index.js",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"lint": "eslint --cache --fix \"*.{js,cjs,mjs,jsx}\" \"{src,test,scripts}/**/*.{js,cjs,mjs,jsx}\"",
|
|
23
23
|
"format": "prettier --write \"*.{html,js,cjs,mjs,jsx}\" \"{sandbox,src,test,scripts}/**/*.{html,js,cjs,mjs,jsx}\"",
|
|
24
24
|
"test": "npm run test:unit && npm run test:scripts",
|
|
25
|
-
"test:unit": "vitest run",
|
|
26
|
-
"test:unit:debug": "vitest --no-file-parallelism --browser.headless=false",
|
|
27
|
-
"test:unit:watch": "vitest",
|
|
28
|
-
"test:unit:coverage": "vitest run --coverage",
|
|
25
|
+
"test:unit": "npx playwright install chromium && vitest run",
|
|
26
|
+
"test:unit:debug": "npx playwright install chromium && vitest --no-file-parallelism --browser.headless=false",
|
|
27
|
+
"test:unit:watch": "npx playwright install chromium && vitest",
|
|
28
|
+
"test:unit:coverage": "npx playwright install chromium && vitest run --coverage",
|
|
29
29
|
"test:functional": "EDGE_BASE_PATH=\"ee-pre-prd\" ALLOY_ENV=\"int\" testcafe chrome",
|
|
30
30
|
"test:functional:custom": "node scripts/helpers/runFunctionalTests.js",
|
|
31
31
|
"test:functional:watch": "EDGE_BASE_PATH=\"ee-pre-prd\" ALLOY_ENV=\"int\" ./scripts/watchFunctionalTests.js --browsers chrome",
|
|
@@ -41,8 +41,7 @@
|
|
|
41
41
|
"prepare": "husky && cd sandbox && npm install",
|
|
42
42
|
"prepack": "rimraf libEs5 libEs6 && babel src -d libEs5 --env-name npmEs5 && babel src -d libEs6 --env-name npmEs6",
|
|
43
43
|
"checkthattestfilesexist": "./scripts/checkThatTestFilesExist.js",
|
|
44
|
-
"add-license": "./scripts/add-license.js"
|
|
45
|
-
"postinstall": "npx playwright install chromium"
|
|
44
|
+
"add-license": "./scripts/add-license.js"
|
|
46
45
|
},
|
|
47
46
|
"lint-staged": {
|
|
48
47
|
"./*.{cjs,mjs,js,jsx}": [
|
|
@@ -65,16 +64,16 @@
|
|
|
65
64
|
"author": "Adobe Inc.",
|
|
66
65
|
"license": "Apache-2.0",
|
|
67
66
|
"dependencies": {
|
|
68
|
-
"@adobe/aep-rules-engine": "^2.0
|
|
67
|
+
"@adobe/aep-rules-engine": "^2.1.0",
|
|
69
68
|
"@adobe/reactor-cookie": "^1.1.0",
|
|
70
69
|
"@adobe/reactor-load-script": "^1.1.1",
|
|
71
70
|
"@adobe/reactor-object-assign": "^2.0.0",
|
|
72
71
|
"@adobe/reactor-query-string": "^2.0.0",
|
|
73
|
-
"@babel/core": "^7.26.
|
|
74
|
-
"@babel/eslint-parser": "^7.26.
|
|
75
|
-
"@babel/plugin-transform-template-literals": "^7.
|
|
76
|
-
"@babel/preset-env": "^7.26.
|
|
77
|
-
"@inquirer/prompts": "^7.2
|
|
72
|
+
"@babel/core": "^7.26.9",
|
|
73
|
+
"@babel/eslint-parser": "^7.26.8",
|
|
74
|
+
"@babel/plugin-transform-template-literals": "^7.26.8",
|
|
75
|
+
"@babel/preset-env": "^7.26.9",
|
|
76
|
+
"@inquirer/prompts": "^7.3.2",
|
|
78
77
|
"@rollup/plugin-babel": "^6.0.4",
|
|
79
78
|
"@rollup/plugin-commonjs": "^28.0.2",
|
|
80
79
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
@@ -82,24 +81,23 @@
|
|
|
82
81
|
"commander": "^13.1.0",
|
|
83
82
|
"css.escape": "^1.5.1",
|
|
84
83
|
"js-cookie": "3.0.5",
|
|
85
|
-
"
|
|
86
|
-
"rollup": "^
|
|
87
|
-
"
|
|
88
|
-
"uuid": "^11.0.5"
|
|
84
|
+
"rollup": "^4.34.8",
|
|
85
|
+
"rollup-plugin-license": "^3.6.0",
|
|
86
|
+
"uuid": "^11.1.0"
|
|
89
87
|
},
|
|
90
88
|
"devDependencies": {
|
|
91
|
-
"@adobe/alloy": "^2.
|
|
89
|
+
"@adobe/alloy": "^2.26.0-beta.0",
|
|
92
90
|
"@babel/cli": "^7.26.4",
|
|
93
|
-
"@babel/plugin-transform-runtime": "^7.
|
|
94
|
-
"@eslint/js": "^9.
|
|
95
|
-
"@octokit/rest": "^21.1.
|
|
96
|
-
"@vitest/browser": "^3.0.
|
|
97
|
-
"@vitest/coverage-v8": "^3.0.
|
|
91
|
+
"@babel/plugin-transform-runtime": "^7.26.9",
|
|
92
|
+
"@eslint/js": "^9.20.0",
|
|
93
|
+
"@octokit/rest": "^21.1.1",
|
|
94
|
+
"@vitest/browser": "^3.0.6",
|
|
95
|
+
"@vitest/coverage-v8": "^3.0.6",
|
|
98
96
|
"chalk": "^5.4.1",
|
|
99
97
|
"concurrently": "^9.1.2",
|
|
100
98
|
"date-fns": "^4.1.0",
|
|
101
99
|
"dotenv": "^16.4.7",
|
|
102
|
-
"eslint": "^9.
|
|
100
|
+
"eslint": "^9.20.1",
|
|
103
101
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
104
102
|
"eslint-config-prettier": "^10.0.1",
|
|
105
103
|
"eslint-plugin-ban": "^2.0.0",
|
|
@@ -107,32 +105,33 @@
|
|
|
107
105
|
"eslint-plugin-prettier": "^5.2.3",
|
|
108
106
|
"eslint-plugin-testcafe": "^0.2.1",
|
|
109
107
|
"glob": "^11.0.1",
|
|
110
|
-
"globals": "^15.
|
|
108
|
+
"globals": "^15.15.0",
|
|
111
109
|
"handlebars": "^4.7.8",
|
|
112
|
-
"happy-dom": "^
|
|
110
|
+
"happy-dom": "^17.1.1",
|
|
113
111
|
"husky": "^9.1.7",
|
|
114
|
-
"lint-staged": "^15.4.
|
|
115
|
-
"playwright": "^1.
|
|
116
|
-
"prettier": "^3.
|
|
112
|
+
"lint-staged": "^15.4.3",
|
|
113
|
+
"playwright": "^1.50.1",
|
|
114
|
+
"prettier": "^3.5.1",
|
|
117
115
|
"read-cache": "^1.0.0",
|
|
118
116
|
"recursive-readdir": "^2.2.3",
|
|
119
|
-
"request": "^2.88.2",
|
|
120
117
|
"rimraf": "^6.0.1",
|
|
121
118
|
"rollup-plugin-glob-import": "^0.5.0",
|
|
122
119
|
"rollup-plugin-istanbul": "^5.0.0",
|
|
123
|
-
"semver": "^7.
|
|
120
|
+
"semver": "^7.7.1",
|
|
124
121
|
"staged-git-files": "^1.3.0",
|
|
125
122
|
"start-server-and-test": "^2.0.10",
|
|
126
|
-
"testcafe": "^3.7.
|
|
123
|
+
"testcafe": "^3.7.2",
|
|
124
|
+
"testcafe-browser-provider-saucelabs": "^3.0.0",
|
|
127
125
|
"testcafe-reporter-junit": "^3.0.2",
|
|
126
|
+
"testcafe-reporter-saucelabs": "^3.6.0",
|
|
128
127
|
"url-exists-nodejs": "^0.2.4",
|
|
129
128
|
"url-parse": "^1.5.10",
|
|
130
|
-
"vitest": "^3.0.
|
|
129
|
+
"vitest": "^3.0.6"
|
|
131
130
|
},
|
|
132
131
|
"optionalDependencies": {
|
|
133
|
-
"@rollup/rollup-linux-x64-gnu": "^4.
|
|
132
|
+
"@rollup/rollup-linux-x64-gnu": "^4.34.8"
|
|
134
133
|
},
|
|
135
134
|
"overrides": {
|
|
136
|
-
"eslint": "^9.
|
|
135
|
+
"eslint": "^9.20.1"
|
|
137
136
|
}
|
|
138
137
|
}
|
package/scripts/alloyBuilder.js
CHANGED
|
@@ -11,27 +11,25 @@ OF ANY KIND, either express or implied. See the License for the specific languag
|
|
|
11
11
|
governing permissions and limitations under the License.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import babel from "@babel/core";
|
|
15
|
+
import { checkbox, input, select } from "@inquirer/prompts";
|
|
16
|
+
import { Command, InvalidOptionArgumentError, Option } from "commander";
|
|
14
17
|
import fs from "fs";
|
|
15
18
|
import path from "path";
|
|
16
19
|
import { rollup } from "rollup";
|
|
17
|
-
import { Command, Option, InvalidOptionArgumentError } from "commander";
|
|
18
|
-
import { input, checkbox, select } from "@inquirer/prompts";
|
|
19
|
-
import { fileURLToPath } from "url";
|
|
20
|
-
import babel from "@babel/core";
|
|
21
20
|
import { buildConfig } from "../rollup.config.js";
|
|
22
21
|
import entryPointGeneratorBabelPlugin from "./helpers/entryPointGeneratorBabelPlugin.js";
|
|
23
|
-
|
|
24
|
-
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
import { getProjectRoot, safePathJoin } from "./helpers/path.js";
|
|
25
23
|
|
|
26
24
|
const packageJsonContent = fs.readFileSync(
|
|
27
|
-
|
|
25
|
+
safePathJoin(getProjectRoot(), "package.json"),
|
|
28
26
|
"utf8",
|
|
29
27
|
);
|
|
30
28
|
const { version } = JSON.parse(packageJsonContent);
|
|
31
29
|
|
|
32
|
-
let sourceRootPath =
|
|
30
|
+
let sourceRootPath = safePathJoin(getProjectRoot(), "src");
|
|
33
31
|
if (!fs.existsSync(sourceRootPath)) {
|
|
34
|
-
sourceRootPath =
|
|
32
|
+
sourceRootPath = safePathJoin(getProjectRoot(), "libEs6");
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
const arrayDifference = (arr1, arr2) => arr1.filter((x) => !arr2.includes(x));
|
|
@@ -44,11 +42,14 @@ const getComponents = (() => {
|
|
|
44
42
|
const components = {};
|
|
45
43
|
[
|
|
46
44
|
{
|
|
47
|
-
filePath:
|
|
45
|
+
filePath: safePathJoin(sourceRootPath, "core/componentCreators.js"),
|
|
48
46
|
key: "optional",
|
|
49
47
|
},
|
|
50
48
|
{
|
|
51
|
-
filePath:
|
|
49
|
+
filePath: safePathJoin(
|
|
50
|
+
sourceRootPath,
|
|
51
|
+
"core/requiredComponentCreators.js",
|
|
52
|
+
),
|
|
52
53
|
key: "required",
|
|
53
54
|
},
|
|
54
55
|
].forEach(({ filePath, key }) => {
|
|
@@ -69,12 +70,12 @@ const getComponents = (() => {
|
|
|
69
70
|
return () => components;
|
|
70
71
|
})();
|
|
71
72
|
|
|
72
|
-
const getDefaultPath = () => {
|
|
73
|
-
return process.cwd();
|
|
74
|
-
};
|
|
75
|
-
|
|
76
73
|
const getOutputFilePath = (argv) => {
|
|
77
|
-
|
|
74
|
+
const outputPath = safePathJoin(
|
|
75
|
+
argv.outputDir,
|
|
76
|
+
`alloy${argv.minify ? ".min" : ""}.js`,
|
|
77
|
+
);
|
|
78
|
+
return outputPath;
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
const getFileSizeInKB = (filePath) => {
|
|
@@ -93,7 +94,7 @@ const generateInputEntryFile = ({
|
|
|
93
94
|
}).code;
|
|
94
95
|
|
|
95
96
|
const destinationDirectory = path.dirname(inputPath);
|
|
96
|
-
const outputPath =
|
|
97
|
+
const outputPath = safePathJoin(destinationDirectory, outputFile);
|
|
97
98
|
|
|
98
99
|
fs.writeFileSync(outputPath, output);
|
|
99
100
|
|
|
@@ -157,10 +158,10 @@ const getMakeBuildCommand = () => {
|
|
|
157
158
|
"-o, --outputDir <dir>",
|
|
158
159
|
"the output directory for the generated build",
|
|
159
160
|
)
|
|
160
|
-
.default(
|
|
161
|
+
.default(getProjectRoot())
|
|
161
162
|
.argParser((value) => {
|
|
162
163
|
if (!path.isAbsolute(value)) {
|
|
163
|
-
value =
|
|
164
|
+
value = safePathJoin(getProjectRoot(), value);
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
try {
|