@adobe/alloy 2.36.0 → 2.36.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/dist/alloy.js CHANGED
@@ -3750,7 +3750,14 @@
3750
3750
  /**
3751
3751
  * Send a network request and returns details about the response.
3752
3752
  */
3753
- return ({ requestId, url, payload, useSendBeacon, headers }) => {
3753
+ return ({
3754
+ requestId,
3755
+ url,
3756
+ payload,
3757
+ useSendBeacon,
3758
+ useKeepalive,
3759
+ headers,
3760
+ }) => {
3754
3761
  // We want to log raw payload and event data rather than
3755
3762
  // our fancy wrapper objects. Calling payload.toJSON() is
3756
3763
  // insufficient to get all the nested raw data, because it's
@@ -3771,53 +3778,55 @@
3771
3778
  // navigator.sendBeacon() has no way to attach custom headers, so a
3772
3779
  // request that needs any (e.g. authenticated Server API requests)
3773
3780
  // must always go through fetch, regardless of useSendBeacon.
3774
- const requestMethod =
3775
- useSendBeacon && !headers ? sendBeaconRequest : sendFetchRequest;
3781
+ const request =
3782
+ useSendBeacon && !headers
3783
+ ? sendBeaconRequest(url, stringifiedPayload)
3784
+ : sendFetchRequest(url, stringifiedPayload, headers, {
3785
+ keepalive: Boolean(useKeepalive),
3786
+ });
3776
3787
 
3777
- return requestMethod(url, stringifiedPayload, headers).then(
3778
- (response) => {
3779
- const requestIsRetryable = isRequestRetryable({
3788
+ return request.then((response) => {
3789
+ const requestIsRetryable = isRequestRetryable({
3790
+ response,
3791
+ retriesAttempted,
3792
+ });
3793
+
3794
+ if (requestIsRetryable) {
3795
+ const requestRetryDelay = getRequestRetryDelay({
3780
3796
  response,
3781
3797
  retriesAttempted,
3782
3798
  });
3799
+ return new Promise((resolve) => {
3800
+ setTimeout(() => {
3801
+ resolve(executeRequest(retriesAttempted + 1));
3802
+ }, requestRetryDelay);
3803
+ });
3804
+ }
3783
3805
 
3784
- if (requestIsRetryable) {
3785
- const requestRetryDelay = getRequestRetryDelay({
3786
- response,
3787
- retriesAttempted,
3788
- });
3789
- return new Promise((resolve) => {
3790
- setTimeout(() => {
3791
- resolve(executeRequest(retriesAttempted + 1));
3792
- }, requestRetryDelay);
3793
- });
3794
- }
3795
-
3796
- let parsedBody;
3806
+ let parsedBody;
3797
3807
 
3798
- try {
3799
- parsedBody = JSON.parse(response.body);
3800
- } catch {
3801
- // Non-JSON. Something went wrong.
3802
- }
3808
+ try {
3809
+ parsedBody = JSON.parse(response.body);
3810
+ } catch {
3811
+ // Non-JSON. Something went wrong.
3812
+ }
3803
3813
 
3804
- logger.logOnNetworkResponse({
3805
- requestId,
3806
- url,
3807
- payload: parsedPayload,
3808
- ...response,
3809
- parsedBody,
3810
- retriesAttempted,
3811
- });
3814
+ logger.logOnNetworkResponse({
3815
+ requestId,
3816
+ url,
3817
+ payload: parsedPayload,
3818
+ ...response,
3819
+ parsedBody,
3820
+ retriesAttempted,
3821
+ });
3812
3822
 
3813
- return {
3814
- statusCode: response.statusCode,
3815
- body: response.body,
3816
- parsedBody,
3817
- getHeader: response.getHeader,
3818
- };
3819
- },
3820
- );
3823
+ return {
3824
+ statusCode: response.statusCode,
3825
+ body: response.body,
3826
+ parsedBody,
3827
+ getHeader: response.getHeader,
3828
+ };
3829
+ });
3821
3830
  };
3822
3831
 
3823
3832
  return executeRequest().catch((error) => {
@@ -5229,6 +5238,15 @@
5229
5238
  },
5230
5239
  logOnNetworkResponse(data) {
5231
5240
  notifyMonitors("onNetworkResponse", data);
5241
+ if (data.sentWithBeacon) {
5242
+ // sendBeacon only tells us the browser queued the request; there is
5243
+ // no server response to report.
5244
+ log(
5245
+ "info",
5246
+ `Request ${data.requestId}: Sent using sendBeacon. No response is available for sendBeacon requests. If the page navigates away, the browser keeps sending the request in the background, but its developer tools may show it as canceled.`,
5247
+ );
5248
+ return;
5249
+ }
5232
5250
  const messagesSuffix =
5233
5251
  data.parsedBody || data.body ? `response body:` : `no response body.`;
5234
5252
  log(
@@ -5335,6 +5353,9 @@
5335
5353
  * @param {RequestPayload} options.payload
5336
5354
  * @param {function({isIdentityEstablished: boolean}): string} options.getAction
5337
5355
  * @param {function({isIdentityEstablished: boolean}): boolean} options.getUseSendBeacon
5356
+ * @param {function(): boolean} [options.getUseKeepalive] Whether a fetch
5357
+ * request should be sent with `keepalive` so it survives a page unload.
5358
+ * Defaults to false.
5338
5359
  * @param {string} [options.datastreamIdOverride]
5339
5360
  * @param {string} [options.edgeSubPath]
5340
5361
  *
@@ -5358,6 +5379,7 @@
5358
5379
  payload,
5359
5380
  getAction,
5360
5381
  getUseSendBeacon,
5382
+ getUseKeepalive = () => false,
5361
5383
  datastreamIdOverride,
5362
5384
  edgeSubPath,
5363
5385
  requestParams = {},
@@ -5383,6 +5405,9 @@
5383
5405
  getUseSendBeacon() {
5384
5406
  return getUseSendBeacon({ isIdentityEstablished });
5385
5407
  },
5408
+ getUseKeepalive() {
5409
+ return getUseKeepalive();
5410
+ },
5386
5411
  getEdgeSubPath() {
5387
5412
  if (edgeSubPath) {
5388
5413
  return edgeSubPath;
@@ -5451,9 +5476,9 @@
5451
5476
  // No error will be thrown and an identity is established. This is good.
5452
5477
  // 3. The document is unloaded and Alloy does not receive the empty response
5453
5478
  // back from the collect endpoint before navigation is completed. In this
5454
- // case, no error is thrown, but no identity was established and it's
5455
- // more likely that the request never makes it to the server because we're
5456
- // using fetch instead of sendBeacon.
5479
+ // case, no error is thrown, but no identity was established. Because the
5480
+ // fetch is sent with `keepalive` (see getUseKeepalive below), the browser
5481
+ // still delivers the request to the server after the page unloads.
5457
5482
  //
5458
5483
  // The second approach seemed preferable.
5459
5484
  return (
@@ -5462,6 +5487,12 @@
5462
5487
  );
5463
5488
  };
5464
5489
 
5490
+ // When the document may be unloading but we use fetch anyway (for example,
5491
+ // before an identity is established), send the fetch with `keepalive` so the
5492
+ // browser doesn't cancel the request when the page navigates away.
5493
+ const getUseKeepalive = () =>
5494
+ dataCollectionRequestPayload.getDocumentMayUnload();
5495
+
5465
5496
  return createRequest({
5466
5497
  payload: dataCollectionRequestPayload,
5467
5498
  getAction({ isIdentityEstablished }) {
@@ -5470,6 +5501,7 @@
5470
5501
  : "interact";
5471
5502
  },
5472
5503
  getUseSendBeacon,
5504
+ getUseKeepalive,
5473
5505
  datastreamIdOverride,
5474
5506
  });
5475
5507
  };
@@ -6335,6 +6367,7 @@
6335
6367
  url,
6336
6368
  payload: outgoingPayload,
6337
6369
  useSendBeacon: request.getUseSendBeacon(),
6370
+ useKeepalive: request.getUseKeepalive(),
6338
6371
  headers,
6339
6372
  });
6340
6373
  })
@@ -6355,6 +6388,7 @@
6355
6388
  url,
6356
6389
  payload,
6357
6390
  useSendBeacon: request.getUseSendBeacon(),
6391
+ useKeepalive: request.getUseKeepalive(),
6358
6392
  });
6359
6393
  }
6360
6394
  return handleRequestFailure(onRequestFailureCallbackAggregator)(error);
@@ -7956,10 +7990,10 @@
7956
7990
  governing permissions and limitations under the License.
7957
7991
  */
7958
7992
 
7959
- // The 2.36.0 keyword will be replace at alloy build time with the package.json version.
7993
+ // The 2.36.1 keyword will be replace at alloy build time with the package.json version.
7960
7994
  // see babel-plugin-version
7961
7995
 
7962
- var libraryVersion = "2.36.0";
7996
+ var libraryVersion = "2.36.1";
7963
7997
 
7964
7998
  /*
7965
7999
  Copyright 2019 Adobe. All rights reserved.
@@ -10446,6 +10480,8 @@
10446
10480
  const TYPE$1 = "type";
10447
10481
  const VALID_EVENT_TYPES = [IAM_EVENT_TYPE, EVENT_TYPE, TYPE$1];
10448
10482
  const VALID_EVENT_IDS = [IAM_ID, ID];
10483
+ const IAM_ACTION = "iam.action";
10484
+ const ACTION = "action";
10449
10485
  const checkForHistoricalMatcher = (eventCount, matcherKey, value) => {
10450
10486
  switch (matcherKey) {
10451
10487
  case MatcherType.GREATER_THAN:
@@ -10481,6 +10517,10 @@
10481
10517
  event[normalizedKeyName] = event[keyName];
10482
10518
  delete event[keyName];
10483
10519
  });
10520
+ if (!isUndefined(event[IAM_ACTION])) {
10521
+ event[ACTION] = event[IAM_ACTION];
10522
+ delete event[IAM_ACTION];
10523
+ }
10484
10524
  return event;
10485
10525
  };
10486
10526
  function queryAndCountAnyEvent(events, context, options, from = 0, to = Infinity) {
@@ -11539,12 +11579,21 @@
11539
11579
  const filteredItems = [];
11540
11580
  proposition.items.forEach((item) => {
11541
11581
  if (item.schema === EVENT_HISTORY_OPERATION) {
11582
+ const { content } = item.data;
11583
+ const event = {
11584
+ eventId: content["iam.id"],
11585
+ eventType: content["iam.eventType"],
11586
+ };
11587
+
11588
+ // Web SDK records interactions with an `action` (from
11589
+ // propositionAction.id), so carry it over to land in the same entry.
11590
+ if (content["iam.action"] !== undefined) {
11591
+ event.action = content["iam.action"];
11592
+ }
11593
+
11542
11594
  result.push({
11543
11595
  operation: item.data.operation,
11544
- event: {
11545
- eventId: item.data.content["iam.id"],
11546
- eventType: item.data.content["iam.eventType"],
11547
- },
11596
+ event,
11548
11597
  });
11549
11598
  } else {
11550
11599
  filteredItems.push(item);
@@ -22117,9 +22166,23 @@
22117
22166
  governing permissions and limitations under the License.
22118
22167
  */
22119
22168
 
22120
- var injectSendFetchRequest = ({ fetch }) => {
22121
- return (url, body, headers) => {
22122
- return fetch(url, {
22169
+ /** @import { NetworkService } from "@adobe/alloy-core/services" */
22170
+
22171
+ /**
22172
+ * @param {Object} params
22173
+ * @param {typeof window.fetch} params.fetch
22174
+ * @param {{ info: (...args: any[]) => void }} params.logger
22175
+ * @returns {NetworkService["sendFetchRequest"]}
22176
+ */
22177
+ var injectSendFetchRequest = ({ fetch, logger }) => {
22178
+ /**
22179
+ * @param {string} url
22180
+ * @param {string} body
22181
+ * @param {Record<string, string> | undefined} headers
22182
+ * @param {boolean} keepalive
22183
+ */
22184
+ const startFetch = (url, body, headers, keepalive) =>
22185
+ fetch(url, {
22123
22186
  method: "POST",
22124
22187
  cache: "no-cache",
22125
22188
  credentials: "include", // To set the cookie header in the request.
@@ -22129,7 +22192,30 @@
22129
22192
  },
22130
22193
  referrerPolicy: "no-referrer-when-downgrade",
22131
22194
  body,
22132
- }).then((response) => {
22195
+ ...(keepalive ? { keepalive: true } : {}),
22196
+ });
22197
+
22198
+ return (url, body, headers, { keepalive = false } = {}) => {
22199
+ let responsePromise = startFetch(url, body, headers, keepalive);
22200
+
22201
+ if (keepalive) {
22202
+ // Browsers reject keepalive requests whose body, together with every
22203
+ // other in-flight keepalive request and sendBeacon on the page, exceeds
22204
+ // 64 KiB. The rejection is a TypeError that looks the same as any other
22205
+ // network error ("Failed to fetch", "NetworkError when attempting to
22206
+ // fetch resource.", "Load failed") and arrives before anything is sent,
22207
+ // so retry once without keepalive. Only fetch() itself is covered here;
22208
+ // a failure while reading the response is not retried because the
22209
+ // server already received the request.
22210
+ responsePromise = responsePromise.catch(() => {
22211
+ logger.info(
22212
+ "Unable to send the request with `keepalive`; falling back to a regular `fetch`.",
22213
+ );
22214
+ return startFetch(url, body, headers, false);
22215
+ });
22216
+ }
22217
+
22218
+ return responsePromise.then((response) => {
22133
22219
  return response.text().then((responseBody) => ({
22134
22220
  statusCode: response.status,
22135
22221
  // We expose headers through a function instead of creating an object
@@ -22166,13 +22252,15 @@
22166
22252
 
22167
22253
  // Using sendBeacon, we technically don't get a response back from
22168
22254
  // the server, but we'll resolve the promise with an object to maintain
22169
- // consistency with other network strategies.
22255
+ // consistency with other network strategies. `sentWithBeacon` marks the
22256
+ // response as synthetic so it isn't logged as a real server response.
22170
22257
  return Promise.resolve({
22171
22258
  statusCode: 204,
22172
22259
  getHeader() {
22173
22260
  return null;
22174
22261
  },
22175
22262
  body: "",
22263
+ sentWithBeacon: true,
22176
22264
  });
22177
22265
  };
22178
22266
  };
@@ -22196,7 +22284,7 @@
22196
22284
  */
22197
22285
  const createBrowserNetworkService = ({ logger }) => {
22198
22286
  const { fetch, navigator } = window;
22199
- const sendFetchRequest = injectSendFetchRequest({ fetch });
22287
+ const sendFetchRequest = injectSendFetchRequest({ fetch, logger });
22200
22288
  const sendBeaconRequest =
22201
22289
  typeof navigator.sendBeacon === "function"
22202
22290
  ? injectSendBeaconRequest({