@adobe/alloy 2.25.0-beta.1 → 2.26.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 = {};
@@ -73,7 +74,18 @@ var _default = ({
73
74
  namespaces: requestedNamespaces
74
75
  } = options;
75
76
  return consent.awaitConsent().then(() => {
76
- return namespaces ? undefined : getIdentity(options);
77
+ if (namespaces) {
78
+ return undefined;
79
+ }
80
+ const ecidFromCookie = decodeKndctrCookie();
81
+ if (ecidFromCookie) {
82
+ if (!namespaces) {
83
+ namespaces = {};
84
+ }
85
+ namespaces[_ecidNamespace.default] = ecidFromCookie;
86
+ return undefined;
87
+ }
88
+ return getIdentity(options);
77
89
  }).then(() => {
78
90
  return {
79
91
  identity: requestedNamespaces.reduce((acc, namespace) => {
@@ -89,7 +101,18 @@ var _default = ({
89
101
  optionsValidator: _appendIdentityToUrlOptionsValidator.default,
90
102
  run: options => {
91
103
  return consent.withConsent().then(() => {
92
- return namespaces ? undefined : getIdentity(options);
104
+ if (namespaces) {
105
+ return undefined;
106
+ }
107
+ const ecidFromCookie = decodeKndctrCookie();
108
+ if (ecidFromCookie) {
109
+ if (!namespaces) {
110
+ namespaces = {};
111
+ }
112
+ namespaces[_ecidNamespace.default] = ecidFromCookie;
113
+ return undefined;
114
+ }
115
+ return getIdentity(options);
93
116
  }).then(() => {
94
117
  return {
95
118
  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";
@@ -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.25.0-beta.1";
17
+ var _default = exports.default = "2.26.0-beta.0";
@@ -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 = {};
@@ -70,7 +71,18 @@ export default ({
70
71
  namespaces: requestedNamespaces
71
72
  } = options;
72
73
  return consent.awaitConsent().then(() => {
73
- return namespaces ? undefined : getIdentity(options);
74
+ if (namespaces) {
75
+ return undefined;
76
+ }
77
+ const ecidFromCookie = decodeKndctrCookie();
78
+ if (ecidFromCookie) {
79
+ if (!namespaces) {
80
+ namespaces = {};
81
+ }
82
+ namespaces[ecidNamespace] = ecidFromCookie;
83
+ return undefined;
84
+ }
85
+ return getIdentity(options);
74
86
  }).then(() => {
75
87
  return {
76
88
  identity: requestedNamespaces.reduce((acc, namespace) => {
@@ -86,7 +98,18 @@ export default ({
86
98
  optionsValidator: appendIdentityToUrlOptionsValidator,
87
99
  run: options => {
88
100
  return consent.withConsent().then(() => {
89
- return namespaces ? undefined : getIdentity(options);
101
+ if (namespaces) {
102
+ return undefined;
103
+ }
104
+ const ecidFromCookie = decodeKndctrCookie();
105
+ if (ecidFromCookie) {
106
+ if (!namespaces) {
107
+ namespaces = {};
108
+ }
109
+ namespaces[ecidNamespace] = ecidFromCookie;
110
+ return undefined;
111
+ }
112
+ return getIdentity(options);
90
113
  }).then(() => {
91
114
  return {
92
115
  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";
@@ -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 {
@@ -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.25.0-beta.1";
16
+ export default "2.26.0-beta.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/alloy",
3
- "version": "2.25.0-beta.1",
3
+ "version": "2.26.0-beta.0",
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.3",
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.0",
74
- "@babel/eslint-parser": "^7.26.5",
75
- "@babel/plugin-transform-template-literals": "^7.25.9",
76
- "@babel/preset-env": "^7.26.0",
77
- "@inquirer/prompts": "^7.2.3",
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",
@@ -83,23 +82,23 @@
83
82
  "css.escape": "^1.5.1",
84
83
  "js-cookie": "3.0.5",
85
84
  "parse-uri": "^1.0.9",
86
- "rollup": "^4.31.0",
87
- "rollup-plugin-license": "^3.5.3",
88
- "uuid": "^11.0.5"
85
+ "rollup": "^4.34.8",
86
+ "rollup-plugin-license": "^3.6.0",
87
+ "uuid": "^11.1.0"
89
88
  },
90
89
  "devDependencies": {
91
- "@adobe/alloy": "^2.25.0-beta.0",
90
+ "@adobe/alloy": "^2.25.0",
92
91
  "@babel/cli": "^7.26.4",
93
- "@babel/plugin-transform-runtime": "^7.25.9",
94
- "@eslint/js": "^9.18.0",
95
- "@octokit/rest": "^21.1.0",
96
- "@vitest/browser": "^3.0.3",
97
- "@vitest/coverage-v8": "^3.0.3",
92
+ "@babel/plugin-transform-runtime": "^7.26.9",
93
+ "@eslint/js": "^9.20.0",
94
+ "@octokit/rest": "^21.1.1",
95
+ "@vitest/browser": "^3.0.6",
96
+ "@vitest/coverage-v8": "^3.0.6",
98
97
  "chalk": "^5.4.1",
99
98
  "concurrently": "^9.1.2",
100
99
  "date-fns": "^4.1.0",
101
100
  "dotenv": "^16.4.7",
102
- "eslint": "^9.18.0",
101
+ "eslint": "^9.20.1",
103
102
  "eslint-config-airbnb-base": "^15.0.0",
104
103
  "eslint-config-prettier": "^10.0.1",
105
104
  "eslint-plugin-ban": "^2.0.0",
@@ -107,32 +106,34 @@
107
106
  "eslint-plugin-prettier": "^5.2.3",
108
107
  "eslint-plugin-testcafe": "^0.2.1",
109
108
  "glob": "^11.0.1",
110
- "globals": "^15.14.0",
109
+ "globals": "^15.15.0",
111
110
  "handlebars": "^4.7.8",
112
- "happy-dom": "^16.7.2",
111
+ "happy-dom": "^17.1.1",
113
112
  "husky": "^9.1.7",
114
- "lint-staged": "^15.4.1",
115
- "playwright": "^1.49.1",
116
- "prettier": "^3.4.2",
113
+ "lint-staged": "^15.4.3",
114
+ "playwright": "^1.50.1",
115
+ "prettier": "^3.5.1",
117
116
  "read-cache": "^1.0.0",
118
117
  "recursive-readdir": "^2.2.3",
119
118
  "request": "^2.88.2",
120
119
  "rimraf": "^6.0.1",
121
120
  "rollup-plugin-glob-import": "^0.5.0",
122
121
  "rollup-plugin-istanbul": "^5.0.0",
123
- "semver": "^7.6.3",
122
+ "semver": "^7.7.1",
124
123
  "staged-git-files": "^1.3.0",
125
124
  "start-server-and-test": "^2.0.10",
126
- "testcafe": "^3.7.1",
125
+ "testcafe": "^3.7.2",
126
+ "testcafe-browser-provider-saucelabs": "^3.0.0",
127
127
  "testcafe-reporter-junit": "^3.0.2",
128
+ "testcafe-reporter-saucelabs": "^3.6.0",
128
129
  "url-exists-nodejs": "^0.2.4",
129
130
  "url-parse": "^1.5.10",
130
- "vitest": "^3.0.3"
131
+ "vitest": "^3.0.6"
131
132
  },
132
133
  "optionalDependencies": {
133
- "@rollup/rollup-linux-x64-gnu": "^4.31.0"
134
+ "@rollup/rollup-linux-x64-gnu": "^4.34.8"
134
135
  },
135
136
  "overrides": {
136
- "eslint": "^9.18.0"
137
+ "eslint": "^9.20.1"
137
138
  }
138
139
  }