@esri/telemetry-amazon 6.0.0-beta.0 → 6.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +26 -9
  2. package/dist/esm/auth.js +22 -31
  3. package/dist/esm/auth.js.map +1 -1
  4. package/dist/esm/create-event-log.js +15 -15
  5. package/dist/esm/create-event-log.js.map +1 -1
  6. package/dist/esm/create-event-log.test.js +50 -8
  7. package/dist/esm/create-event-log.test.js.map +1 -1
  8. package/dist/esm/create-page-view-log.js +1 -6
  9. package/dist/esm/create-page-view-log.js.map +1 -1
  10. package/dist/esm/format-telemetry-attributes.js +8 -4
  11. package/dist/esm/format-telemetry-attributes.js.map +1 -1
  12. package/dist/esm/index.js +43 -19
  13. package/dist/esm/index.js.map +1 -1
  14. package/dist/esm/index.test.js +106 -160
  15. package/dist/esm/index.test.js.map +1 -1
  16. package/dist/esm/plugins/esri.js +419 -0
  17. package/dist/esm/plugins/esri.js.map +1 -0
  18. package/dist/esm/plugins/esri.test.js +556 -0
  19. package/dist/esm/plugins/esri.test.js.map +1 -0
  20. package/dist/esm/plugins/utils/browser.js +97 -0
  21. package/dist/esm/plugins/utils/browser.js.map +1 -0
  22. package/dist/esm/plugins/utils/browser.test.js +130 -0
  23. package/dist/esm/plugins/utils/browser.test.js.map +1 -0
  24. package/dist/esm/plugins/utils/os.js +140 -0
  25. package/dist/esm/plugins/utils/os.js.map +1 -0
  26. package/dist/esm/plugins/utils/os.test.js +54 -0
  27. package/dist/esm/plugins/utils/os.test.js.map +1 -0
  28. package/dist/esm/plugins/utils/shared.js +74 -0
  29. package/dist/esm/plugins/utils/shared.js.map +1 -0
  30. package/dist/esm/types.js +97 -1
  31. package/dist/esm/types.js.map +1 -1
  32. package/dist/node/auth.js +22 -31
  33. package/dist/node/auth.js.map +1 -1
  34. package/dist/node/create-event-log.js +15 -15
  35. package/dist/node/create-event-log.js.map +1 -1
  36. package/dist/node/create-event-log.test.js +50 -8
  37. package/dist/node/create-event-log.test.js.map +1 -1
  38. package/dist/node/create-page-view-log.js +1 -6
  39. package/dist/node/create-page-view-log.js.map +1 -1
  40. package/dist/node/format-telemetry-attributes.js +8 -4
  41. package/dist/node/format-telemetry-attributes.js.map +1 -1
  42. package/dist/node/index.js +43 -19
  43. package/dist/node/index.js.map +1 -1
  44. package/dist/node/index.test.js +106 -160
  45. package/dist/node/index.test.js.map +1 -1
  46. package/dist/node/plugins/esri.js +423 -0
  47. package/dist/node/plugins/esri.js.map +1 -0
  48. package/dist/node/plugins/esri.test.js +559 -0
  49. package/dist/node/plugins/esri.test.js.map +1 -0
  50. package/dist/node/plugins/utils/browser.js +101 -0
  51. package/dist/node/plugins/utils/browser.js.map +1 -0
  52. package/dist/node/plugins/utils/browser.test.js +132 -0
  53. package/dist/node/plugins/utils/browser.test.js.map +1 -0
  54. package/dist/node/plugins/utils/os.js +144 -0
  55. package/dist/node/plugins/utils/os.js.map +1 -0
  56. package/dist/node/plugins/utils/os.test.js +56 -0
  57. package/dist/node/plugins/utils/os.test.js.map +1 -0
  58. package/dist/node/plugins/utils/shared.js +83 -0
  59. package/dist/node/plugins/utils/shared.js.map +1 -0
  60. package/dist/node/types.js +98 -0
  61. package/dist/node/types.js.map +1 -1
  62. package/dist/types/create-event-log.d.ts +2 -2
  63. package/dist/types/create-page-view-log.d.ts +1 -1
  64. package/dist/types/format-telemetry-attributes.d.ts +3 -3
  65. package/dist/types/index.d.ts +14 -7
  66. package/dist/types/plugins/esri.d.ts +6 -0
  67. package/dist/types/plugins/esri.test.d.ts +1 -0
  68. package/dist/types/plugins/utils/browser.d.ts +12 -0
  69. package/dist/types/plugins/utils/browser.test.d.ts +1 -0
  70. package/dist/types/plugins/utils/os.d.ts +5 -0
  71. package/dist/types/plugins/utils/os.test.d.ts +1 -0
  72. package/dist/types/plugins/utils/shared.d.ts +12 -0
  73. package/dist/types/types.d.ts +114 -19
  74. package/dist/umd/telemetry-amazon.js +1253 -141
  75. package/dist/umd/telemetry-amazon.js.map +1 -1
  76. package/dist/umd/telemetry-amazon.min.js +1253 -141
  77. package/dist/umd/telemetry-amazon.min.js.map +1 -1
  78. package/package.json +4 -3
@@ -8,115 +8,86 @@
8
8
 
9
9
  var awsPinpointPlugin__default = /*#__PURE__*/_interopDefaultLegacy(awsPinpointPlugin);
10
10
 
11
- const storage = {
12
- storage: {},
13
- memory: true,
14
- get(key) {
15
- let stored;
16
- try {
17
- stored =
18
- (window.localStorage && window.localStorage.getItem(key)) ||
19
- this.storage[key];
20
- }
21
- catch (e) {
22
- stored = this.storage[key];
23
- }
24
- if (stored) {
25
- try {
26
- return JSON.parse(stored);
27
- }
28
- catch (e) {
29
- return undefined;
30
- }
31
- }
32
- else {
33
- return undefined;
34
- }
35
- },
36
- set(key, value) {
37
- // handle Safari private mode (setItem is not allowed)
38
- const valueToString = JSON.stringify(value);
39
- try {
40
- window.localStorage.setItem(key, valueToString);
41
- }
42
- catch (e) {
43
- if (!this.memory) {
44
- console.error('setting local storage failed, falling back to in-memory storage');
45
- this.memory = true;
46
- }
47
- this.storage[key] = value;
48
- }
49
- },
50
- delete(key) {
51
- try {
52
- window.localStorage.removeItem(key);
53
- }
54
- catch (e) {
55
- if (!this.memory) {
56
- console.error('setting local storage failed, falling back to in-memory storage');
57
- this.memory = true;
58
- }
59
- delete this.storage[key];
60
- }
61
- },
62
- };
11
+ const AMAZON_EVENT_ATTRIBUTE_KEYS = [
12
+ 'action',
13
+ 'attribute',
14
+ 'category',
15
+ 'datasetId',
16
+ 'details',
17
+ 'error',
18
+ 'facetValue',
19
+ 'hostname',
20
+ 'json',
21
+ 'label',
22
+ 'lastLogin',
23
+ 'objectId',
24
+ 'org',
25
+ 'pageUrl',
26
+ 'path',
27
+ 'pageName',
28
+ 'previousPageName',
29
+ 'previousPageUrl',
30
+ 'referrer',
31
+ 'searchQuery',
32
+ 'serviceQuery',
33
+ 'statusCode',
34
+ 'user',
35
+ 'userSince',
36
+ 'urlRequested',
37
+ 'internalUser',
38
+ 'accountType',
39
+ 'workflowId',
40
+ 'browserWebCapabilities',
41
+ /**
42
+ * Original plan was to union this in as `AmazonDimensionKey`, but it's strictly defined in the schema that there are 20 dimensions
43
+ * so a little non-DRY is fine for readability's sake
44
+ */
45
+ 'dimension1',
46
+ 'dimension2',
47
+ 'dimension3',
48
+ 'dimension4',
49
+ 'dimension5',
50
+ 'dimension6',
51
+ 'dimension7',
52
+ 'dimension8',
53
+ 'dimension9',
54
+ 'dimension10',
55
+ 'dimension11',
56
+ 'dimension12',
57
+ 'dimension13',
58
+ 'dimension14',
59
+ 'dimension15',
60
+ 'dimension16',
61
+ 'dimension17',
62
+ 'dimension18',
63
+ 'dimension19',
64
+ 'dimension20',
65
+ /**
66
+ * NOTE: `eventType` is NOT an attribute, but instead a core property on the telemetry payload. Many apps have been treating it
67
+ * as an attribute though, so it's included here for backwards compatibility. Internally it is re-mapped to `eventType`, where all other attributes
68
+ * are mapped to `a_attributeName` in the telemetry payload. This is a known issue and will be addressed in a future release.
69
+ */
70
+ 'eventType',
71
+ /**
72
+ * NOTE: `pathname` is NOT an attribute, nor an official property at all. It is internally mapped to `path`, but is used interchangeably by consuming apps.
73
+ * It's included here for backwards compatibility, but should not be used in new telemetry events. If `path` is set, this will never be used.
74
+ */
75
+ 'pathname',
76
+ ];
63
77
 
64
- const COGNITO_KEY = 'TELEMETRY_COGNITO_CREDENTIALS';
65
- function getCredentials(IdentityPoolId, options = {}) {
66
- const fipsSubdomainSuffix = options.fips === true ? '-fips' : '';
67
- const COGNITO_URL = `https://cognito-identity${fipsSubdomainSuffix}.us-east-1.amazonaws.com/`;
68
- let cached = storage.get(COGNITO_KEY);
69
- if (cached && Date.now() / 1000 < cached.Expiration)
70
- return Promise.resolve(cached);
71
- const fetchOptions = {
72
- method: 'POST',
73
- headers: {
74
- 'Content-type': 'application/x-amz-json-1.1',
75
- 'X-Amz-Target': 'AWSCognitoIdentityService.GetId',
76
- },
77
- body: JSON.stringify({ IdentityPoolId }),
78
- };
79
- return fetch(COGNITO_URL, fetchOptions)
80
- .then((response) => {
81
- if (!response.ok) {
82
- throw new Error(response.statusText);
83
- }
84
- return response.json();
85
- })
86
- .then((response) => {
87
- const { IdentityId } = response;
88
- const options = {
89
- method: 'POST',
90
- headers: {
91
- 'Content-type': 'application/x-amz-json-1.1',
92
- 'X-Amz-Target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
93
- },
94
- body: JSON.stringify({ IdentityId }),
95
- };
96
- return fetch(COGNITO_URL, options);
97
- })
98
- .then((response) => {
99
- if (!response.ok) {
100
- throw new Error(response.statusText);
101
- }
102
- return response.json();
103
- })
104
- .then(({ Credentials }) => {
105
- storage.set(COGNITO_KEY, Credentials);
106
- return Credentials;
107
- });
78
+ function isAmazonEventAttributeKey(key) {
79
+ return AMAZON_EVENT_ATTRIBUTE_KEYS.includes(key);
108
80
  }
109
-
110
- function formatTelemetryAttributes({ telemetryData, dimensionLookup = {}, excludeKeys = [], }) {
81
+ function formatTelemetryAttributes({ telemetryData, dimensionLookup = {}, }) {
111
82
  return Object.keys(telemetryData)
112
83
  .filter((key) => {
113
- return !excludeKeys.includes(key);
84
+ return isAmazonEventAttributeKey(key);
114
85
  })
115
86
  .map((key) => {
116
87
  if (dimensionLookup[key]) {
117
88
  return {
118
89
  key: getCustomDimensionKey(dimensionLookup, key),
119
- value: telemetryData[key],
90
+ value: getValue(telemetryData, key),
120
91
  };
121
92
  }
122
93
  return {
@@ -133,7 +104,7 @@
133
104
  if (key === 'json') {
134
105
  return data[key] ? JSON.stringify(data[key]) : 'null';
135
106
  }
136
- return data[key] === undefined ? 'null' : data[key].toString();
107
+ return data[key] === undefined ? 'null' : String(data[key]);
137
108
  }
138
109
  function getCustomDimensionKey(lookup, key) {
139
110
  return `dimension${lookup[key]}`;
@@ -167,31 +138,30 @@
167
138
  }
168
139
 
169
140
  function createEventLog({ event = {}, dimensionLookup = {}, metricLookup = {}, }) {
170
- var _a, _b;
141
+ var _a, _b, _c;
142
+ const acceptedAttributeKeys = new Set(AMAZON_EVENT_ATTRIBUTE_KEYS);
171
143
  const { pathname: eventPathname, eventType = 'other' } = event;
172
144
  const metrics = formatTelemetryMetrics(event, metricLookup);
173
- const eventAttributes = formatTelemetryAttributes({
174
- telemetryData: Object.assign({ eventType }, event),
145
+ const formattedEventAttributes = formatTelemetryAttributes({
146
+ telemetryData: Object.assign({}, event),
175
147
  dimensionLookup,
176
- excludeKeys: [
177
- 'workflow',
178
- 'pathname',
179
- ...Object.keys(metrics),
180
- ...Object.keys(metricLookup),
181
- ],
182
148
  });
183
- const telemetryPayload = Object.assign(Object.assign({ name: eventType }, eventAttributes), metrics);
149
+ const eventAttributes = Object.fromEntries(Object.entries(formattedEventAttributes).filter(([key]) => {
150
+ return acceptedAttributeKeys.has(key);
151
+ }));
152
+ const telemetryPayload = Object.assign(Object.assign(Object.assign({}, eventAttributes), metrics), { name: eventType });
184
153
  // Set browser properties if available
185
- if (typeof document !== 'undefined' && document.referrer) {
154
+ if (typeof document !== 'undefined' &&
155
+ document.referrer &&
156
+ !telemetryPayload.referrer) {
186
157
  telemetryPayload.referrer = document.referrer;
187
158
  }
188
159
  if (typeof window !== 'undefined' && !telemetryPayload.hostname) {
189
160
  telemetryPayload.hostname = (_a = window.location) === null || _a === void 0 ? void 0 : _a.hostname;
190
161
  }
191
- telemetryPayload.path =
192
- typeof window !== 'undefined' && window.location.pathname
193
- ? (_b = window.location) === null || _b === void 0 ? void 0 : _b.pathname
194
- : eventPathname;
162
+ (_b = telemetryPayload.path) !== null && _b !== void 0 ? _b : (telemetryPayload.path = typeof window !== 'undefined' && window.location.pathname
163
+ ? (_c = window.location) === null || _c === void 0 ? void 0 : _c.pathname
164
+ : eventPathname);
195
165
  return telemetryPayload;
196
166
  }
197
167
 
@@ -200,16 +170,11 @@
200
170
  const attributes = formatTelemetryAttributes({
201
171
  telemetryData: options,
202
172
  dimensionLookup,
203
- excludeKeys: [
204
- 'workflow',
205
- ...Object.keys(metrics),
206
- ...Object.keys(metricLookup),
207
- ],
208
173
  });
209
174
  const { referrer, title } = document || {};
210
175
  const { hostname, pathname } = window && window.location ? window.location : {};
211
176
  return Object.assign(Object.assign({ name: 'pageView', referrer,
212
- hostname, path: page || pathname, pageUrl: page || pathname, pageName: title, previousPageUrl: previousPage.pageUrl, previousPageName: previousPage.pageName }, attributes), metrics);
177
+ hostname, path: page || pathname, pageUrl: page || pathname, pageName: title, previousPageUrl: previousPage === null || previousPage === void 0 ? void 0 : previousPage.pageUrl, previousPageName: previousPage === null || previousPage === void 0 ? void 0 : previousPage.pageName }, attributes), metrics);
213
178
  }
214
179
 
215
180
  var dlv_umd = {exports: {}};
@@ -288,6 +253,96 @@
288
253
  return De(_objectSpread2(_objectSpread2({}, defaultSettings), opts));
289
254
  }
290
255
 
256
+ const storage = {
257
+ storage: {},
258
+ memory: true,
259
+ get(key) {
260
+ let stored;
261
+ try {
262
+ stored =
263
+ (window.localStorage && window.localStorage.getItem(key)) ||
264
+ this.storage[key];
265
+ }
266
+ catch (e) {
267
+ stored = this.storage[key];
268
+ }
269
+ if (stored) {
270
+ try {
271
+ return JSON.parse(stored);
272
+ }
273
+ catch (e) {
274
+ return undefined;
275
+ }
276
+ }
277
+ else {
278
+ return undefined;
279
+ }
280
+ },
281
+ set(key, value) {
282
+ // handle Safari private mode (setItem is not allowed)
283
+ const valueToString = JSON.stringify(value);
284
+ try {
285
+ window.localStorage.setItem(key, valueToString);
286
+ }
287
+ catch (e) {
288
+ if (!this.memory) {
289
+ console.error('setting local storage failed, falling back to in-memory storage');
290
+ this.memory = true;
291
+ }
292
+ this.storage[key] = value;
293
+ }
294
+ },
295
+ delete(key) {
296
+ try {
297
+ window.localStorage.removeItem(key);
298
+ }
299
+ catch (e) {
300
+ if (!this.memory) {
301
+ console.error('setting local storage failed, falling back to in-memory storage');
302
+ this.memory = true;
303
+ }
304
+ delete this.storage[key];
305
+ }
306
+ },
307
+ };
308
+
309
+ const COGNITO_KEY = 'TELEMETRY_COGNITO_CREDENTIALS';
310
+ async function getCredentials(IdentityPoolId, options = {}) {
311
+ const fipsSubdomainSuffix = options.fips === true ? '-fips' : '';
312
+ const COGNITO_URL = `https://cognito-identity${fipsSubdomainSuffix}.us-east-1.amazonaws.com/`;
313
+ let cached = storage.get(COGNITO_KEY);
314
+ if (cached && Date.now() / 1000 < cached.Expiration)
315
+ return cached;
316
+ const fetchOptions = {
317
+ method: 'POST',
318
+ headers: {
319
+ 'Content-type': 'application/x-amz-json-1.1',
320
+ 'X-Amz-Target': 'AWSCognitoIdentityService.GetId',
321
+ },
322
+ body: JSON.stringify({ IdentityPoolId }),
323
+ };
324
+ const getIdResponse = await fetch(COGNITO_URL, fetchOptions);
325
+ if (!getIdResponse.ok) {
326
+ throw new Error(getIdResponse.statusText);
327
+ }
328
+ const { IdentityId } = await getIdResponse.json();
329
+ const credentialsOptions = {
330
+ method: 'POST',
331
+ headers: {
332
+ 'Content-type': 'application/x-amz-json-1.1',
333
+ 'X-Amz-Target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
334
+ },
335
+ body: JSON.stringify({ IdentityId }),
336
+ };
337
+ const getCredentialsResponse = await fetch(COGNITO_URL, credentialsOptions);
338
+ if (!getCredentialsResponse.ok) {
339
+ throw new Error(getCredentialsResponse.statusText);
340
+ }
341
+ const { Credentials } = await getCredentialsResponse.json();
342
+ storage.set(COGNITO_KEY, Credentials);
343
+ return Credentials;
344
+ }
345
+
291
346
  /******************************************************************************
292
347
  Copyright (c) Microsoft Corporation.
293
348
 
@@ -1259,6 +1314,26 @@
1259
1314
  function bboxToString(extent) {
1260
1315
  return extent.join(",");
1261
1316
  }
1317
+ /**
1318
+ * Returns the full thumbnail URL for an item.
1319
+ *
1320
+ * @param item - The item to decorate
1321
+ * @param portal - The portal URL
1322
+ * @param token - Optional token to append if the item is private
1323
+ * @returns The item with decorated thumbnail
1324
+ */
1325
+ function decorateThumbnail(item, portal, token) {
1326
+ if (!item)
1327
+ return item;
1328
+ let thumbnailUrl = null;
1329
+ if (typeof item.thumbnail === "string") {
1330
+ thumbnailUrl = `${portal}/content/items/${item.id}/info/${item.thumbnail}`;
1331
+ if (thumbnailUrl && item.access !== "public" && token) {
1332
+ thumbnailUrl += `?${token}`;
1333
+ }
1334
+ }
1335
+ return Object.assign(Object.assign({}, item), (thumbnailUrl ? { thumbnailUrl } : {}));
1336
+ }
1262
1337
 
1263
1338
  /* Copyright (c) 2018 Environmental Systems Research Institute, Inc.
1264
1339
  * Apache-2.0 */
@@ -1295,6 +1370,16 @@
1295
1370
  if (requestOptions.params.extent && isBBox(requestOptions.params.extent)) {
1296
1371
  requestOptions.params.extent = bboxToString(requestOptions.params.extent);
1297
1372
  }
1373
+ const portal = getPortalUrl(requestOptions);
1374
+ const decoratedThumbnail = decorateThumbnail(Object.assign(Object.assign({}, requestOptions.item), { access: requestOptions.item.access }), portal);
1375
+ const paramsThumbnailUrl = requestOptions.params.thumbnailUrl;
1376
+ if (typeof decoratedThumbnail.thumbnailUrl === "string" &&
1377
+ typeof paramsThumbnailUrl === "string") {
1378
+ if (decoratedThumbnail.thumbnailUrl.split("?")[0] ===
1379
+ paramsThumbnailUrl.split("?")[0]) {
1380
+ delete requestOptions.params.thumbnailUrl;
1381
+ }
1382
+ }
1298
1383
  return request(url, requestOptions);
1299
1384
  });
1300
1385
  }
@@ -1321,7 +1406,22 @@
1321
1406
  const url = getItemBaseUrl(id, requestOptions);
1322
1407
  // default to a GET request
1323
1408
  const options = Object.assign({ httpMethod: "GET" }, requestOptions);
1324
- return request(url, options);
1409
+ return request(url, options).then(async (item) => {
1410
+ const portal = (requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.portal) || getPortalUrl(requestOptions);
1411
+ let token;
1412
+ // if authentication is provided and it’s an authentication manager and the item is not public then request a token for secure access
1413
+ if ((requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.authentication) &&
1414
+ typeof requestOptions.authentication !== "string" &&
1415
+ item.access !== "public") {
1416
+ try {
1417
+ token = await requestOptions.authentication.getToken(url);
1418
+ }
1419
+ catch (e) {
1420
+ // Ignore token acquisition errors because `getToken()` can fail for some authentication types, but we still want to return the item info when possible since the browser may still provide thumbnail access via an auth cookie.
1421
+ }
1422
+ }
1423
+ return decorateThumbnail(item, portal, token);
1424
+ });
1325
1425
  }
1326
1426
  /**
1327
1427
  * Get the fully qualified base URL to the REST end point for an item.
@@ -1376,29 +1476,1039 @@
1376
1476
  return { access };
1377
1477
  }
1378
1478
 
1479
+ /**
1480
+ * @license MIT <https://opensource.org/licenses/MIT>
1481
+ * @copyright Michael Hart 2024
1482
+ */
1483
+ const encoder = new TextEncoder();
1484
+ const HOST_SERVICES = {
1485
+ appstream2: 'appstream',
1486
+ cloudhsmv2: 'cloudhsm',
1487
+ email: 'ses',
1488
+ marketplace: 'aws-marketplace',
1489
+ mobile: 'AWSMobileHubService',
1490
+ pinpoint: 'mobiletargeting',
1491
+ queue: 'sqs',
1492
+ 'git-codecommit': 'codecommit',
1493
+ 'mturk-requester-sandbox': 'mturk-requester',
1494
+ 'personalize-runtime': 'personalize',
1495
+ };
1496
+ const UNSIGNABLE_HEADERS = new Set([
1497
+ 'authorization',
1498
+ 'content-type',
1499
+ 'content-length',
1500
+ 'user-agent',
1501
+ 'presigned-expires',
1502
+ 'expect',
1503
+ 'x-amzn-trace-id',
1504
+ 'range',
1505
+ 'connection',
1506
+ ]);
1507
+ class AwsClient {
1508
+ constructor({ accessKeyId, secretAccessKey, sessionToken, service, region, cache, retries, initRetryMs }) {
1509
+ if (accessKeyId == null) throw new TypeError('accessKeyId is a required option')
1510
+ if (secretAccessKey == null) throw new TypeError('secretAccessKey is a required option')
1511
+ this.accessKeyId = accessKeyId;
1512
+ this.secretAccessKey = secretAccessKey;
1513
+ this.sessionToken = sessionToken;
1514
+ this.service = service;
1515
+ this.region = region;
1516
+ this.cache = cache || new Map();
1517
+ this.retries = retries != null ? retries : 10;
1518
+ this.initRetryMs = initRetryMs || 50;
1519
+ }
1520
+ async sign(input, init) {
1521
+ if (input instanceof Request) {
1522
+ const { method, url, headers, body } = input;
1523
+ init = Object.assign({ method, url, headers }, init);
1524
+ if (init.body == null && headers.has('Content-Type')) {
1525
+ init.body = body != null && headers.has('X-Amz-Content-Sha256') ? body : await input.clone().arrayBuffer();
1526
+ }
1527
+ input = url;
1528
+ }
1529
+ const signer = new AwsV4Signer(Object.assign({ url: input.toString() }, init, this, init && init.aws));
1530
+ const signed = Object.assign({}, init, await signer.sign());
1531
+ delete signed.aws;
1532
+ try {
1533
+ return new Request(signed.url.toString(), signed)
1534
+ } catch (e) {
1535
+ if (e instanceof TypeError) {
1536
+ return new Request(signed.url.toString(), Object.assign({ duplex: 'half' }, signed))
1537
+ }
1538
+ throw e
1539
+ }
1540
+ }
1541
+ async fetch(input, init) {
1542
+ for (let i = 0; i <= this.retries; i++) {
1543
+ const fetched = fetch(await this.sign(input, init));
1544
+ if (i === this.retries) {
1545
+ return fetched
1546
+ }
1547
+ const res = await fetched;
1548
+ if (res.status < 500 && res.status !== 429) {
1549
+ return res
1550
+ }
1551
+ await new Promise(resolve => setTimeout(resolve, Math.random() * this.initRetryMs * Math.pow(2, i)));
1552
+ }
1553
+ throw new Error('An unknown error occurred, ensure retries is not negative')
1554
+ }
1555
+ }
1556
+ class AwsV4Signer {
1557
+ constructor({ method, url, headers, body, accessKeyId, secretAccessKey, sessionToken, service, region, cache, datetime, signQuery, appendSessionToken, allHeaders, singleEncode }) {
1558
+ if (url == null) throw new TypeError('url is a required option')
1559
+ if (accessKeyId == null) throw new TypeError('accessKeyId is a required option')
1560
+ if (secretAccessKey == null) throw new TypeError('secretAccessKey is a required option')
1561
+ this.method = method || (body ? 'POST' : 'GET');
1562
+ this.url = new URL(url);
1563
+ this.headers = new Headers(headers || {});
1564
+ this.body = body;
1565
+ this.accessKeyId = accessKeyId;
1566
+ this.secretAccessKey = secretAccessKey;
1567
+ this.sessionToken = sessionToken;
1568
+ let guessedService, guessedRegion;
1569
+ if (!service || !region) {
1570
+ [guessedService, guessedRegion] = guessServiceRegion(this.url, this.headers);
1571
+ }
1572
+ this.service = service || guessedService || '';
1573
+ this.region = region || guessedRegion || 'us-east-1';
1574
+ this.cache = cache || new Map();
1575
+ this.datetime = datetime || new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
1576
+ this.signQuery = signQuery;
1577
+ this.appendSessionToken = appendSessionToken || this.service === 'iotdevicegateway';
1578
+ this.headers.delete('Host');
1579
+ if (this.service === 's3' && !this.signQuery && !this.headers.has('X-Amz-Content-Sha256')) {
1580
+ this.headers.set('X-Amz-Content-Sha256', 'UNSIGNED-PAYLOAD');
1581
+ }
1582
+ const params = this.signQuery ? this.url.searchParams : this.headers;
1583
+ params.set('X-Amz-Date', this.datetime);
1584
+ if (this.sessionToken && !this.appendSessionToken) {
1585
+ params.set('X-Amz-Security-Token', this.sessionToken);
1586
+ }
1587
+ this.signableHeaders = ['host', ...this.headers.keys()]
1588
+ .filter(header => allHeaders || !UNSIGNABLE_HEADERS.has(header))
1589
+ .sort();
1590
+ this.signedHeaders = this.signableHeaders.join(';');
1591
+ this.canonicalHeaders = this.signableHeaders
1592
+ .map(header => header + ':' + (header === 'host' ? this.url.host : (this.headers.get(header) || '').replace(/\s+/g, ' ')))
1593
+ .join('\n');
1594
+ this.credentialString = [this.datetime.slice(0, 8), this.region, this.service, 'aws4_request'].join('/');
1595
+ if (this.signQuery) {
1596
+ if (this.service === 's3' && !params.has('X-Amz-Expires')) {
1597
+ params.set('X-Amz-Expires', '86400');
1598
+ }
1599
+ params.set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256');
1600
+ params.set('X-Amz-Credential', this.accessKeyId + '/' + this.credentialString);
1601
+ params.set('X-Amz-SignedHeaders', this.signedHeaders);
1602
+ }
1603
+ if (this.service === 's3') {
1604
+ try {
1605
+ this.encodedPath = decodeURIComponent(this.url.pathname.replace(/\+/g, ' '));
1606
+ } catch (e) {
1607
+ this.encodedPath = this.url.pathname;
1608
+ }
1609
+ } else {
1610
+ this.encodedPath = this.url.pathname.replace(/\/+/g, '/');
1611
+ }
1612
+ if (!singleEncode) {
1613
+ this.encodedPath = encodeURIComponent(this.encodedPath).replace(/%2F/g, '/');
1614
+ }
1615
+ this.encodedPath = encodeRfc3986(this.encodedPath);
1616
+ const seenKeys = new Set();
1617
+ this.encodedSearch = [...this.url.searchParams]
1618
+ .filter(([k]) => {
1619
+ if (!k) return false
1620
+ if (this.service === 's3') {
1621
+ if (seenKeys.has(k)) return false
1622
+ seenKeys.add(k);
1623
+ }
1624
+ return true
1625
+ })
1626
+ .map(pair => pair.map(p => encodeRfc3986(encodeURIComponent(p))))
1627
+ .sort(([k1, v1], [k2, v2]) => k1 < k2 ? -1 : k1 > k2 ? 1 : v1 < v2 ? -1 : v1 > v2 ? 1 : 0)
1628
+ .map(pair => pair.join('='))
1629
+ .join('&');
1630
+ }
1631
+ async sign() {
1632
+ if (this.signQuery) {
1633
+ this.url.searchParams.set('X-Amz-Signature', await this.signature());
1634
+ if (this.sessionToken && this.appendSessionToken) {
1635
+ this.url.searchParams.set('X-Amz-Security-Token', this.sessionToken);
1636
+ }
1637
+ } else {
1638
+ this.headers.set('Authorization', await this.authHeader());
1639
+ }
1640
+ return {
1641
+ method: this.method,
1642
+ url: this.url,
1643
+ headers: this.headers,
1644
+ body: this.body,
1645
+ }
1646
+ }
1647
+ async authHeader() {
1648
+ return [
1649
+ 'AWS4-HMAC-SHA256 Credential=' + this.accessKeyId + '/' + this.credentialString,
1650
+ 'SignedHeaders=' + this.signedHeaders,
1651
+ 'Signature=' + (await this.signature()),
1652
+ ].join(', ')
1653
+ }
1654
+ async signature() {
1655
+ const date = this.datetime.slice(0, 8);
1656
+ const cacheKey = [this.secretAccessKey, date, this.region, this.service].join();
1657
+ let kCredentials = this.cache.get(cacheKey);
1658
+ if (!kCredentials) {
1659
+ const kDate = await hmac('AWS4' + this.secretAccessKey, date);
1660
+ const kRegion = await hmac(kDate, this.region);
1661
+ const kService = await hmac(kRegion, this.service);
1662
+ kCredentials = await hmac(kService, 'aws4_request');
1663
+ this.cache.set(cacheKey, kCredentials);
1664
+ }
1665
+ return buf2hex(await hmac(kCredentials, await this.stringToSign()))
1666
+ }
1667
+ async stringToSign() {
1668
+ return [
1669
+ 'AWS4-HMAC-SHA256',
1670
+ this.datetime,
1671
+ this.credentialString,
1672
+ buf2hex(await hash(await this.canonicalString())),
1673
+ ].join('\n')
1674
+ }
1675
+ async canonicalString() {
1676
+ return [
1677
+ this.method.toUpperCase(),
1678
+ this.encodedPath,
1679
+ this.encodedSearch,
1680
+ this.canonicalHeaders + '\n',
1681
+ this.signedHeaders,
1682
+ await this.hexBodyHash(),
1683
+ ].join('\n')
1684
+ }
1685
+ async hexBodyHash() {
1686
+ let hashHeader = this.headers.get('X-Amz-Content-Sha256') || (this.service === 's3' && this.signQuery ? 'UNSIGNED-PAYLOAD' : null);
1687
+ if (hashHeader == null) {
1688
+ if (this.body && typeof this.body !== 'string' && !('byteLength' in this.body)) {
1689
+ throw new Error('body must be a string, ArrayBuffer or ArrayBufferView, unless you include the X-Amz-Content-Sha256 header')
1690
+ }
1691
+ hashHeader = buf2hex(await hash(this.body || ''));
1692
+ }
1693
+ return hashHeader
1694
+ }
1695
+ }
1696
+ async function hmac(key, string) {
1697
+ const cryptoKey = await crypto.subtle.importKey(
1698
+ 'raw',
1699
+ typeof key === 'string' ? encoder.encode(key) : key,
1700
+ { name: 'HMAC', hash: { name: 'SHA-256' } },
1701
+ false,
1702
+ ['sign'],
1703
+ );
1704
+ return crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(string))
1705
+ }
1706
+ async function hash(content) {
1707
+ return crypto.subtle.digest('SHA-256', typeof content === 'string' ? encoder.encode(content) : content)
1708
+ }
1709
+ const HEX_CHARS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
1710
+ function buf2hex(arrayBuffer) {
1711
+ const buffer = new Uint8Array(arrayBuffer);
1712
+ let out = '';
1713
+ for (let idx = 0; idx < buffer.length; idx++) {
1714
+ const n = buffer[idx];
1715
+ out += HEX_CHARS[(n >>> 4) & 0xF];
1716
+ out += HEX_CHARS[n & 0xF];
1717
+ }
1718
+ return out
1719
+ }
1720
+ function encodeRfc3986(urlEncodedStr) {
1721
+ return urlEncodedStr.replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase())
1722
+ }
1723
+ function guessServiceRegion(url, headers) {
1724
+ const { hostname, pathname } = url;
1725
+ if (hostname.endsWith('.on.aws')) {
1726
+ const match = hostname.match(/^[^.]{1,63}\.lambda-url\.([^.]{1,63})\.on\.aws$/);
1727
+ return match != null ? ['lambda', match[1] || ''] : ['', '']
1728
+ }
1729
+ if (hostname.endsWith('.r2.cloudflarestorage.com')) {
1730
+ return ['s3', 'auto']
1731
+ }
1732
+ if (hostname.endsWith('.backblazeb2.com')) {
1733
+ const match = hostname.match(/^(?:[^.]{1,63}\.)?s3\.([^.]{1,63})\.backblazeb2\.com$/);
1734
+ return match != null ? ['s3', match[1] || ''] : ['', '']
1735
+ }
1736
+ const match = hostname.replace('dualstack.', '').match(/([^.]{1,63})\.(?:([^.]{0,63})\.)?amazonaws\.com(?:\.cn)?$/);
1737
+ let service = (match && match[1]) || '';
1738
+ let region = match && match[2];
1739
+ if (region === 'us-gov') {
1740
+ region = 'us-gov-west-1';
1741
+ } else if (region === 's3' || region === 's3-accelerate') {
1742
+ region = 'us-east-1';
1743
+ service = 's3';
1744
+ } else if (service === 'iot') {
1745
+ if (hostname.startsWith('iot.')) {
1746
+ service = 'execute-api';
1747
+ } else if (hostname.startsWith('data.jobs.iot.')) {
1748
+ service = 'iot-jobs-data';
1749
+ } else {
1750
+ service = pathname === '/mqtt' ? 'iotdevicegateway' : 'iotdata';
1751
+ }
1752
+ } else if (service === 'autoscaling') {
1753
+ const targetPrefix = (headers.get('X-Amz-Target') || '').split('.')[0];
1754
+ if (targetPrefix === 'AnyScaleFrontendService') {
1755
+ service = 'application-autoscaling';
1756
+ } else if (targetPrefix === 'AnyScaleScalingPlannerFrontendService') {
1757
+ service = 'autoscaling-plans';
1758
+ }
1759
+ } else if (region == null && service.startsWith('s3-')) {
1760
+ region = service.slice(3).replace(/^fips-|^external-1/, '');
1761
+ service = 's3';
1762
+ } else if (service.endsWith('-fips')) {
1763
+ service = service.slice(0, -5);
1764
+ } else if (region && /-\d$/.test(service) && !/-\d$/.test(region)) {
1765
+ [service, region] = [region, service];
1766
+ }
1767
+ return [HOST_SERVICES[service] || service, region || '']
1768
+ }
1769
+
1770
+ const LINUX_DISTRO_TOKENS = [
1771
+ 'ubuntu',
1772
+ 'debian',
1773
+ 'opensuse',
1774
+ 'suse',
1775
+ 'gentoo',
1776
+ 'arch linux',
1777
+ 'arch',
1778
+ 'fedora',
1779
+ 'centos',
1780
+ 'red hat',
1781
+ 'rhel',
1782
+ 'rocky linux',
1783
+ 'rocky',
1784
+ 'almalinux',
1785
+ 'alma',
1786
+ 'amazon linux',
1787
+ 'amazonlinux',
1788
+ 'amzn',
1789
+ 'manjaro',
1790
+ 'linux mint',
1791
+ 'mint',
1792
+ 'pop!_os',
1793
+ 'pop_os',
1794
+ 'kali',
1795
+ 'alpine',
1796
+ 'nixos',
1797
+ 'linux',
1798
+ ];
1799
+ function escapeRegExp(value) {
1800
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1801
+ }
1802
+ const LINUX_DISTRO_TOKEN_SOURCE = [...LINUX_DISTRO_TOKENS]
1803
+ .sort((a, b) => b.length - a.length)
1804
+ .map((token) => escapeRegExp(token))
1805
+ .join('|');
1806
+ const LINUX_DISTRO_TOKEN_REGEX = new RegExp(`\\b(${LINUX_DISTRO_TOKEN_SOURCE})\\b`, 'i');
1807
+ const LINUX_DISTRO_VERSION_REGEX = new RegExp(`\\b(?:${LINUX_DISTRO_TOKEN_SOURCE})[\\s\\/_-]*([\\d\\.]+)`, 'i');
1808
+ const LINUX_DISTRO_NAME_MAP = {
1809
+ ubuntu: 'Ubuntu',
1810
+ debian: 'Debian',
1811
+ opensuse: 'openSUSE',
1812
+ suse: 'SUSE',
1813
+ gentoo: 'Gentoo',
1814
+ arch: 'Arch Linux',
1815
+ 'arch linux': 'Arch Linux',
1816
+ fedora: 'Fedora',
1817
+ centos: 'CentOS',
1818
+ 'red hat': 'Red Hat',
1819
+ rhel: 'Red Hat',
1820
+ rocky: 'Rocky Linux',
1821
+ 'rocky linux': 'Rocky Linux',
1822
+ alma: 'AlmaLinux',
1823
+ almalinux: 'AlmaLinux',
1824
+ 'amazon linux': 'Amazon Linux',
1825
+ amazonlinux: 'Amazon Linux',
1826
+ amzn: 'Amazon Linux',
1827
+ manjaro: 'Manjaro',
1828
+ 'linux mint': 'Linux Mint',
1829
+ mint: 'Linux Mint',
1830
+ 'pop!_os': 'Pop!_OS',
1831
+ pop_os: 'Pop!_OS',
1832
+ kali: 'Kali Linux',
1833
+ alpine: 'Alpine Linux',
1834
+ nixos: 'NixOS',
1835
+ linux: 'Linux',
1836
+ };
1837
+ function resolveLinuxDistroName(userAgent) {
1838
+ const token = (userAgent.match(LINUX_DISTRO_TOKEN_REGEX) || [])[1];
1839
+ if (!token) {
1840
+ return undefined;
1841
+ }
1842
+ return LINUX_DISTRO_NAME_MAP[token.toLowerCase()];
1843
+ }
1844
+ function extractVersion(userAgent, versionRegex) {
1845
+ if (!versionRegex) {
1846
+ return undefined;
1847
+ }
1848
+ const version = (userAgent.match(versionRegex) || [])[1];
1849
+ if (!version) {
1850
+ return undefined;
1851
+ }
1852
+ return version.replace(/_/g, '.');
1853
+ }
1854
+ function parseOS(userAgent) {
1855
+ if (!userAgent) {
1856
+ return {};
1857
+ }
1858
+ const normalizedAgent = userAgent.toLowerCase();
1859
+ const osMatchers = [
1860
+ {
1861
+ name: 'Windows Phone',
1862
+ regex: /windows phone|windows mobile/i,
1863
+ versionRegex: /windows (?:phone(?: os)?|mobile)[\s\/]*([\d\.]+)/i,
1864
+ },
1865
+ {
1866
+ name: 'Windows',
1867
+ regex: /windows nt|windows\s\d+/i,
1868
+ versionRegex: /windows nt[\s\/]*([\d\.]+)/i,
1869
+ },
1870
+ {
1871
+ name: 'iOS',
1872
+ regex: /iphone|ipad|ipod|cpu (?:iphone )?os /i,
1873
+ versionRegex: /os\s([\d_\.]+)/i,
1874
+ },
1875
+ {
1876
+ name: 'Android',
1877
+ regex: /android/i,
1878
+ versionRegex: /android[\s\/]*([\d\.]+)/i,
1879
+ },
1880
+ {
1881
+ name: 'ChromeOS',
1882
+ regex: /\bcros\b/i,
1883
+ versionRegex: /\bcros\b[^\)]*?([\d]+(?:\.[\d]+)+)/i,
1884
+ },
1885
+ {
1886
+ name: 'MacOS',
1887
+ regex: /mac os x|macos|macintosh|macintel|darwin/i,
1888
+ versionRegex: /(?:mac os x|os x|macos)[\s\/]*([\d_\.]+)/i,
1889
+ },
1890
+ {
1891
+ name: 'Linux',
1892
+ regex: LINUX_DISTRO_TOKEN_REGEX,
1893
+ versionRegex: LINUX_DISTRO_VERSION_REGEX,
1894
+ },
1895
+ ];
1896
+ const matched = osMatchers.find((entry) => entry.regex.test(normalizedAgent));
1897
+ if (!matched) {
1898
+ return {};
1899
+ }
1900
+ const version = extractVersion(normalizedAgent, matched.versionRegex);
1901
+ const name = matched.name === 'Linux'
1902
+ ? resolveLinuxDistroName(normalizedAgent) || 'Linux'
1903
+ : matched.name;
1904
+ return {
1905
+ name,
1906
+ version,
1907
+ };
1908
+ }
1909
+
1910
+ function getNavigatorSafe() {
1911
+ if (typeof window === 'undefined' || !window.navigator) {
1912
+ return undefined;
1913
+ }
1914
+ return window.navigator;
1915
+ }
1916
+ function getLanguageInfo(nav) {
1917
+ if (!nav) {
1918
+ return '';
1919
+ }
1920
+ const lang = nav.language ||
1921
+ nav.browserLanguage ||
1922
+ (Array.isArray(nav.languages) && nav.languages.length > 0
1923
+ ? nav.languages[0]
1924
+ : 'en_US') ||
1925
+ '';
1926
+ return String(lang).toLowerCase().replace('-', '_');
1927
+ }
1928
+ function getBrowserTimezone() {
1929
+ const tzMatch = /\(([A-Za-z\s].*)\)/.exec(new Date().toString());
1930
+ return tzMatch ? tzMatch[1] || '' : '';
1931
+ }
1932
+ function parseBrowserType(userAgent) {
1933
+ const operaMatch = /.+(Opera[\s[A-Z]*|OPR[\sA-Z]*)\/([0-9\.]+).*/i.exec(userAgent);
1934
+ if (operaMatch) {
1935
+ return {
1936
+ type: operaMatch[1],
1937
+ version: operaMatch[2],
1938
+ };
1939
+ }
1940
+ const edgeOrTridentMatch = /.+(Trident|Edge)\/([0-9\.]+).*/i.exec(userAgent);
1941
+ if (edgeOrTridentMatch) {
1942
+ return {
1943
+ type: edgeOrTridentMatch[1],
1944
+ version: edgeOrTridentMatch[2],
1945
+ };
1946
+ }
1947
+ const headlessMatch = /(headlesschrome)(?:\/([\w\.]+)| )/i.exec(userAgent);
1948
+ if (headlessMatch) {
1949
+ return {
1950
+ type: headlessMatch[1],
1951
+ version: headlessMatch[2],
1952
+ };
1953
+ }
1954
+ const browserMatch = /.+(Chrome|Firefox|FxiOS)\/([0-9\.]+).*/i.exec(userAgent);
1955
+ if (browserMatch) {
1956
+ return {
1957
+ type: browserMatch[1],
1958
+ version: browserMatch[2],
1959
+ };
1960
+ }
1961
+ const safariMatch = /.+(Safari)\/([0-9\.]+).*/i.exec(userAgent);
1962
+ if (safariMatch) {
1963
+ return {
1964
+ type: safariMatch[1],
1965
+ version: safariMatch[2],
1966
+ };
1967
+ }
1968
+ const webkitMatch = /.+(AppleWebKit)\/([0-9\.]+).*/i.exec(userAgent);
1969
+ if (webkitMatch) {
1970
+ return {
1971
+ type: webkitMatch[1],
1972
+ version: webkitMatch[2],
1973
+ };
1974
+ }
1975
+ const anyMatch = /.*([A-Z]+)\/([0-9\.]+).*/i.exec(userAgent);
1976
+ const fallback = anyMatch || ['', 'NA', '0.0.0'];
1977
+ return {
1978
+ type: fallback[1],
1979
+ version: fallback[2],
1980
+ };
1981
+ }
1982
+ function getBrowserClientInfo() {
1983
+ const nav = getNavigatorSafe();
1984
+ if (!nav) {
1985
+ return {};
1986
+ }
1987
+ const userAgent = nav.userAgent || '';
1988
+ const browser = parseBrowserType(userAgent);
1989
+ const isBrave = (nav.brave && nav.brave.isBrave) || false;
1990
+ const type = isBrave ? 'Brave' : browser.type;
1991
+ const os = parseOS(userAgent);
1992
+ const vendorToken = (nav.vendor || '').split(' ').filter(Boolean)[0];
1993
+ const make = type === 'Brave' ? type : vendorToken || nav.product || '';
1994
+ return {
1995
+ language: getLanguageInfo(nav),
1996
+ make,
1997
+ model: type,
1998
+ version: browser.version,
1999
+ name: [type, browser.version].filter(Boolean).join('/'),
2000
+ timezone: getBrowserTimezone(),
2001
+ platform: nav.platform,
2002
+ os,
2003
+ };
2004
+ }
2005
+
2006
+ const PINPOINT_ENDPOINT_KEY = 'TELEMETRY_PINPOINT_ENDPOINT_ID';
2007
+ const LOG_PREFIX = '[telemetry-amazon]';
2008
+ function isDebugEnabled() {
2009
+ var _a;
2010
+ const processEnv = typeof globalThis !== 'undefined'
2011
+ ? (_a = globalThis.process) === null || _a === void 0 ? void 0 : _a.env
2012
+ : undefined;
2013
+ return ((processEnv === null || processEnv === void 0 ? void 0 : processEnv.TELEMETRY_AMAZON_DEBUG) === 'true' ||
2014
+ (processEnv === null || processEnv === void 0 ? void 0 : processEnv.NODE_ENV) === 'development');
2015
+ }
2016
+ function debugLog(message, details) {
2017
+ if (!isDebugEnabled() || typeof console === 'undefined') {
2018
+ return;
2019
+ }
2020
+ if (details && typeof console.debug === 'function') {
2021
+ console.debug(`${LOG_PREFIX} ${message}`, details);
2022
+ return;
2023
+ }
2024
+ if (typeof console.debug === 'function') {
2025
+ console.debug(`${LOG_PREFIX} ${message}`);
2026
+ }
2027
+ }
2028
+ function warnLog(message, details) {
2029
+ if (typeof console === 'undefined' || typeof console.warn !== 'function') {
2030
+ return;
2031
+ }
2032
+ if (details) {
2033
+ console.warn(`${LOG_PREFIX} ${message}`, details);
2034
+ return;
2035
+ }
2036
+ console.warn(`${LOG_PREFIX} ${message}`);
2037
+ }
2038
+ function getEndpointId() {
2039
+ const cached = storage.get(PINPOINT_ENDPOINT_KEY);
2040
+ if (cached && typeof cached === 'object' && cached.endpointId) {
2041
+ return cached.endpointId;
2042
+ }
2043
+ if (typeof cached === 'string') {
2044
+ return cached;
2045
+ }
2046
+ const generated = `telemetry-${Date.now()}-${Math.random()
2047
+ .toString(16)
2048
+ .slice(2)}`;
2049
+ storage.set(PINPOINT_ENDPOINT_KEY, { endpointId: generated });
2050
+ return generated;
2051
+ }
2052
+
2053
+ const ESRI_SIGNING_SERVICE = 'execute-api';
2054
+ const ESRI_SIGNING_REGION = 'us-east-1';
2055
+ const ESRI_ENDPOINT_URL = (appId) => `https://tedev.arcgis.com/v1/apps/${appId}/events`;
2056
+ const SESSION_START_EVENT = '_session.start';
2057
+ const SESSION_STOP_EVENT = '_session.stop';
2058
+ const DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
2059
+ const DEFAULT_DEBOUNCE_MS = 5000;
2060
+ /** Converts a string to a number, guarded against invalid values */
2061
+ function getNumber(value, fallback = 0) {
2062
+ const numeric = Number(value);
2063
+ return Number.isFinite(numeric) ? numeric : fallback;
2064
+ }
2065
+ /** Splits an EsriBatchEventPayload into attributes and metrics */
2066
+ function splitEventData(payload) {
2067
+ const attributes = {};
2068
+ const metrics = {};
2069
+ const excludedKeys = new Set([
2070
+ 'name',
2071
+ 'appTitle',
2072
+ 'appVersion',
2073
+ 'clientSdkVersion',
2074
+ 'deviceMake',
2075
+ 'deviceModel',
2076
+ 'city',
2077
+ 'country',
2078
+ 'lat',
2079
+ 'lon',
2080
+ ]);
2081
+ Object.entries(payload || {}).forEach(([key, value]) => {
2082
+ if (excludedKeys.has(key) || value === undefined || value === null) {
2083
+ return;
2084
+ }
2085
+ if (typeof value === 'number' && Number.isFinite(value)) {
2086
+ metrics[key] = value;
2087
+ return;
2088
+ }
2089
+ if (typeof value === 'string' || typeof value === 'boolean') {
2090
+ attributes[key] = String(value);
2091
+ return;
2092
+ }
2093
+ if (Array.isArray(value) || typeof value === 'object') {
2094
+ attributes[key] = JSON.stringify(value);
2095
+ }
2096
+ });
2097
+ return {
2098
+ attributes,
2099
+ metrics,
2100
+ };
2101
+ }
2102
+ /** Constructs an event payload in the shape expected by the endpoint. */
2103
+ function buildBatchEventPayload({ queuedEvents, trackerName, appName, appVersion, }) {
2104
+ var _a, _b, _c, _d, _e, _f;
2105
+ const endpointEvent = queuedEvents[queuedEvents.length - 1] ||
2106
+ {
2107
+ payload: {},
2108
+ browserInfo: {},
2109
+ };
2110
+ const payload = endpointEvent.payload || {};
2111
+ const browserInfo = endpointEvent.browserInfo || {};
2112
+ const endpointId = getEndpointId();
2113
+ const location = {};
2114
+ if (payload === null || payload === void 0 ? void 0 : payload.city) {
2115
+ location.City = payload.city;
2116
+ }
2117
+ if (payload === null || payload === void 0 ? void 0 : payload.country) {
2118
+ location.Country = payload.country;
2119
+ }
2120
+ if ((payload === null || payload === void 0 ? void 0 : payload.lat) !== undefined && (payload === null || payload === void 0 ? void 0 : payload.lat) !== null) {
2121
+ location.Lat = getNumber(payload.lat);
2122
+ }
2123
+ if ((payload === null || payload === void 0 ? void 0 : payload.lon) !== undefined && (payload === null || payload === void 0 ? void 0 : payload.lon) !== null) {
2124
+ location.Lon = getNumber(payload.lon);
2125
+ }
2126
+ return {
2127
+ BatchEvent: {
2128
+ Endpoint: {
2129
+ Id: endpointId,
2130
+ Device: Object.assign({ Locale: (browserInfo === null || browserInfo === void 0 ? void 0 : browserInfo.language) || '', Make: (payload === null || payload === void 0 ? void 0 : payload.deviceMake) || (browserInfo === null || browserInfo === void 0 ? void 0 : browserInfo.make) || '', Model: (payload === null || payload === void 0 ? void 0 : payload.deviceModel) || (browserInfo === null || browserInfo === void 0 ? void 0 : browserInfo.model) || '', ModelVersion: (browserInfo === null || browserInfo === void 0 ? void 0 : browserInfo.version) || '', Platform: ((_a = browserInfo === null || browserInfo === void 0 ? void 0 : browserInfo.os) === null || _a === void 0 ? void 0 : _a.name) || (browserInfo === null || browserInfo === void 0 ? void 0 : browserInfo.platform) || '' }, (((_b = browserInfo === null || browserInfo === void 0 ? void 0 : browserInfo.os) === null || _b === void 0 ? void 0 : _b.version)
2131
+ ? { PlatformVersion: browserInfo.os.version }
2132
+ : {})),
2133
+ Location: location,
2134
+ Application: {
2135
+ AppTitle: (_d = (_c = payload === null || payload === void 0 ? void 0 : payload.appTitle) !== null && _c !== void 0 ? _c : appName) !== null && _d !== void 0 ? _d : trackerName,
2136
+ AppVersion: (_e = payload === null || payload === void 0 ? void 0 : payload.appVersion) !== null && _e !== void 0 ? _e : appVersion,
2137
+ ClientSdkVersion: (_f = payload === null || payload === void 0 ? void 0 : payload.clientSdkVersion) !== null && _f !== void 0 ? _f : trackerName,
2138
+ },
2139
+ },
2140
+ Events: queuedEvents.map((queuedEvent) => {
2141
+ const timestamp = queuedEvent.timestamp;
2142
+ const { attributes, metrics } = splitEventData(queuedEvent.payload);
2143
+ const sessionContext = queuedEvent.session ||
2144
+ {
2145
+ id: `${queuedEvent.unixMs}-${Math.random().toString(16).slice(2)}`,
2146
+ startTimestamp: timestamp,
2147
+ startUnixMs: queuedEvent.unixMs,
2148
+ };
2149
+ const isStopEvent = queuedEvent.eventName === SESSION_STOP_EVENT;
2150
+ return {
2151
+ EventType: queuedEvent.eventName,
2152
+ Timestamp: timestamp,
2153
+ Session: {
2154
+ Id: sessionContext.id,
2155
+ StartTimestamp: sessionContext.startTimestamp,
2156
+ EndTimestamp: timestamp,
2157
+ Duration: isStopEvent
2158
+ ? Math.max(0, queuedEvent.unixMs - sessionContext.startUnixMs)
2159
+ : 0,
2160
+ },
2161
+ Attributes: attributes,
2162
+ Metrics: metrics,
2163
+ };
2164
+ }),
2165
+ },
2166
+ };
2167
+ }
2168
+ function createEsriPlugin({ trackerName, appName, appId, appVersion, userPoolID, fips, debounceMs = DEFAULT_DEBOUNCE_MS, }) {
2169
+ const url = ESRI_ENDPOINT_URL(appId);
2170
+ let isDisabled = false;
2171
+ let hasWarned = false;
2172
+ let hasSessionStarted = false;
2173
+ let sessionStopSent = false;
2174
+ let activeSession;
2175
+ let idleTimerId;
2176
+ let debounceTimerId;
2177
+ let lifecycleListenersAttached = false;
2178
+ const removeLifecycleListeners = [];
2179
+ let pendingEvents = [];
2180
+ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
2181
+ const warnOnce = (message, details) => {
2182
+ if (hasWarned) {
2183
+ return;
2184
+ }
2185
+ hasWarned = true;
2186
+ warnLog(message, details);
2187
+ };
2188
+ debugLog('createEsriPlugin initialized', {
2189
+ trackerName,
2190
+ hasUrl: Boolean(url),
2191
+ });
2192
+ if (!url) {
2193
+ isDisabled = true;
2194
+ warnOnce('Esri telemetry disabled due to missing internal endpoint URL', {
2195
+ trackerName,
2196
+ });
2197
+ }
2198
+ if (!userPoolID) {
2199
+ isDisabled = true;
2200
+ warnOnce('Esri telemetry disabled due to missing userPoolID', {
2201
+ trackerName,
2202
+ });
2203
+ }
2204
+ if (typeof fetch !== 'function') {
2205
+ isDisabled = true;
2206
+ warnOnce('Esri telemetry disabled because fetch is unavailable', {
2207
+ trackerName,
2208
+ });
2209
+ }
2210
+ const clearIdleTimer = () => {
2211
+ if (!idleTimerId) {
2212
+ return;
2213
+ }
2214
+ clearTimeout(idleTimerId);
2215
+ idleTimerId = undefined;
2216
+ };
2217
+ const clearDebounceTimer = () => {
2218
+ if (!debounceTimerId) {
2219
+ return;
2220
+ }
2221
+ clearTimeout(debounceTimerId);
2222
+ debounceTimerId = undefined;
2223
+ };
2224
+ const clearPendingEvents = () => {
2225
+ pendingEvents = [];
2226
+ };
2227
+ const enqueueEvent = (event) => {
2228
+ pendingEvents.push(event);
2229
+ };
2230
+ const dequeuePendingEvents = () => {
2231
+ if (pendingEvents.length === 0) {
2232
+ return [];
2233
+ }
2234
+ const queuedEvents = pendingEvents;
2235
+ pendingEvents = [];
2236
+ return queuedEvents;
2237
+ };
2238
+ const sendQueuedEvents = (queuedEvents) => {
2239
+ var _a;
2240
+ if (queuedEvents.length === 0) {
2241
+ return;
2242
+ }
2243
+ if (isDisabled || !url || !userPoolID) {
2244
+ debugLog('Skipping esri track call', {
2245
+ eventName: (_a = queuedEvents[queuedEvents.length - 1]) === null || _a === void 0 ? void 0 : _a.eventName,
2246
+ reason: 'plugin-disabled',
2247
+ });
2248
+ return;
2249
+ }
2250
+ debugLog('Sending esri endpoint batch event', {
2251
+ trackerName,
2252
+ url,
2253
+ eventCount: queuedEvents.length,
2254
+ });
2255
+ void (async () => {
2256
+ var _a;
2257
+ try {
2258
+ const credentials = await getCredentials(userPoolID, { fips });
2259
+ const accessKeyId = (credentials === null || credentials === void 0 ? void 0 : credentials.AccessKeyId) || (credentials === null || credentials === void 0 ? void 0 : credentials.accessKeyId);
2260
+ const secretAccessKey = (credentials === null || credentials === void 0 ? void 0 : credentials.SecretKey) || (credentials === null || credentials === void 0 ? void 0 : credentials.secretAccessKey);
2261
+ const sessionToken = (credentials === null || credentials === void 0 ? void 0 : credentials.SessionToken) || (credentials === null || credentials === void 0 ? void 0 : credentials.sessionToken);
2262
+ if (!accessKeyId || !secretAccessKey) {
2263
+ throw new Error('Signed endpoint credentials are missing required key fields.');
2264
+ }
2265
+ const client = new AwsClient({
2266
+ accessKeyId,
2267
+ secretAccessKey,
2268
+ sessionToken,
2269
+ service: ESRI_SIGNING_SERVICE,
2270
+ region: ESRI_SIGNING_REGION,
2271
+ });
2272
+ await client.fetch(url, {
2273
+ method: 'POST',
2274
+ headers: {
2275
+ 'Content-Type': 'application/json',
2276
+ },
2277
+ body: JSON.stringify(buildBatchEventPayload({
2278
+ queuedEvents,
2279
+ trackerName,
2280
+ appName,
2281
+ appVersion,
2282
+ })),
2283
+ });
2284
+ debugLog('Endpoint event sent', {
2285
+ trackerName,
2286
+ eventCount: queuedEvents.length,
2287
+ });
2288
+ }
2289
+ catch (error) {
2290
+ isDisabled = true;
2291
+ clearIdleTimer();
2292
+ clearDebounceTimer();
2293
+ clearPendingEvents();
2294
+ removeAllLifecycleListeners();
2295
+ warnOnce('Esri telemetry disabled after endpoint track failure', {
2296
+ eventName: (_a = queuedEvents[queuedEvents.length - 1]) === null || _a === void 0 ? void 0 : _a.eventName,
2297
+ trackerName,
2298
+ message: (error === null || error === void 0 ? void 0 : error.message) || String(error),
2299
+ });
2300
+ }
2301
+ })();
2302
+ };
2303
+ const flushPendingEvents = (eventToAppend) => {
2304
+ clearDebounceTimer();
2305
+ const queuedEvents = dequeuePendingEvents();
2306
+ if (eventToAppend) {
2307
+ queuedEvents.push(eventToAppend);
2308
+ }
2309
+ sendQueuedEvents(queuedEvents);
2310
+ };
2311
+ const scheduleDebounceFlush = () => {
2312
+ clearDebounceTimer();
2313
+ debounceTimerId = setTimeout(() => {
2314
+ flushPendingEvents();
2315
+ }, debounceMs);
2316
+ };
2317
+ const removeAllLifecycleListeners = () => {
2318
+ removeLifecycleListeners.splice(0).forEach((removeListener) => {
2319
+ removeListener();
2320
+ });
2321
+ lifecycleListenersAttached = false;
2322
+ };
2323
+ const createSession = () => {
2324
+ const now = Date.now();
2325
+ return {
2326
+ id: `${now}-${Math.random().toString(16).slice(2)}`,
2327
+ startTimestamp: new Date(now).toISOString(),
2328
+ startUnixMs: now,
2329
+ };
2330
+ };
2331
+ const sendEvent = (eventName, payload, sessionOverride, options, forceImmediate = false) => {
2332
+ if (isDisabled || !url || !userPoolID) {
2333
+ debugLog('Skipping esri track call', {
2334
+ eventName,
2335
+ reason: 'plugin-disabled',
2336
+ });
2337
+ return false;
2338
+ }
2339
+ debugLog('Queueing esri endpoint event', {
2340
+ eventName,
2341
+ trackerName,
2342
+ immediate: Boolean((options === null || options === void 0 ? void 0 : options.immediate) || forceImmediate),
2343
+ });
2344
+ const queuedEvent = {
2345
+ eventName,
2346
+ payload: payload,
2347
+ session: sessionOverride || activeSession,
2348
+ browserInfo: getBrowserClientInfo(),
2349
+ timestamp: new Date().toISOString(),
2350
+ unixMs: Date.now(),
2351
+ };
2352
+ if ((options === null || options === void 0 ? void 0 : options.immediate) || forceImmediate) {
2353
+ flushPendingEvents(queuedEvent);
2354
+ }
2355
+ else {
2356
+ enqueueEvent(queuedEvent);
2357
+ scheduleDebounceFlush();
2358
+ }
2359
+ return true;
2360
+ };
2361
+ const sendSessionLifecycleEvent = (eventName, reason, sessionOverride) => {
2362
+ return sendEvent(eventName, {
2363
+ reason,
2364
+ }, sessionOverride, undefined, true);
2365
+ };
2366
+ const stopSession = (reason) => {
2367
+ if (!hasSessionStarted || sessionStopSent) {
2368
+ return false;
2369
+ }
2370
+ const sessionToStop = activeSession;
2371
+ const didStop = sendSessionLifecycleEvent(SESSION_STOP_EVENT, reason, sessionToStop);
2372
+ if (didStop) {
2373
+ sessionStopSent = true;
2374
+ hasSessionStarted = false;
2375
+ clearIdleTimer();
2376
+ activeSession = undefined;
2377
+ }
2378
+ return didStop;
2379
+ };
2380
+ const startSession = (reason) => {
2381
+ if (hasSessionStarted) {
2382
+ return false;
2383
+ }
2384
+ activeSession = activeSession || createSession();
2385
+ const sessionToStart = activeSession;
2386
+ const didStart = sendSessionLifecycleEvent(SESSION_START_EVENT, reason, sessionToStart);
2387
+ if (didStart) {
2388
+ hasSessionStarted = true;
2389
+ sessionStopSent = false;
2390
+ }
2391
+ else {
2392
+ activeSession = undefined;
2393
+ }
2394
+ return didStart;
2395
+ };
2396
+ const resetIdleTimer = () => {
2397
+ if (!isBrowser || isDisabled || !hasSessionStarted) {
2398
+ return;
2399
+ }
2400
+ clearIdleTimer();
2401
+ idleTimerId = setTimeout(() => {
2402
+ stopSession('idle');
2403
+ }, DEFAULT_IDLE_TIMEOUT_MS);
2404
+ };
2405
+ const handleActivity = () => {
2406
+ if (isDisabled) {
2407
+ return;
2408
+ }
2409
+ if (!hasSessionStarted) {
2410
+ startSession('wakeup');
2411
+ }
2412
+ resetIdleTimer();
2413
+ };
2414
+ const addLifecycleListener = (target, eventName, listener) => {
2415
+ target.addEventListener(eventName, listener);
2416
+ removeLifecycleListeners.push(() => {
2417
+ target.removeEventListener(eventName, listener);
2418
+ });
2419
+ };
2420
+ const attachLifecycleListeners = () => {
2421
+ if (!isBrowser || lifecycleListenersAttached || isDisabled) {
2422
+ return;
2423
+ }
2424
+ const activityEvents = ['pointerdown', 'keydown', 'scroll', 'focus'];
2425
+ activityEvents.forEach((eventName) => {
2426
+ addLifecycleListener(window, eventName, handleActivity);
2427
+ });
2428
+ addLifecycleListener(window, 'beforeunload', () => {
2429
+ stopSession('beforeunload');
2430
+ });
2431
+ lifecycleListenersAttached = true;
2432
+ };
2433
+ return {
2434
+ track: (eventName, payload, options) => {
2435
+ attachLifecycleListeners();
2436
+ const isSessionStopEvent = eventName === SESSION_STOP_EVENT;
2437
+ if (!hasSessionStarted &&
2438
+ eventName !== SESSION_START_EVENT &&
2439
+ !isSessionStopEvent) {
2440
+ startSession('init');
2441
+ }
2442
+ if (eventName === SESSION_START_EVENT) {
2443
+ activeSession = activeSession || createSession();
2444
+ hasSessionStarted = true;
2445
+ sessionStopSent = false;
2446
+ }
2447
+ if (isSessionStopEvent) {
2448
+ sessionStopSent = true;
2449
+ hasSessionStarted = false;
2450
+ clearIdleTimer();
2451
+ }
2452
+ if (hasSessionStarted && !activeSession && !isSessionStopEvent) {
2453
+ activeSession = createSession();
2454
+ }
2455
+ if (hasSessionStarted && !isSessionStopEvent) {
2456
+ resetIdleTimer();
2457
+ }
2458
+ const sessionForEvent = activeSession;
2459
+ const didSend = sendEvent(eventName, payload, sessionForEvent, options, eventName === SESSION_START_EVENT || eventName === SESSION_STOP_EVENT);
2460
+ if (isSessionStopEvent && didSend) {
2461
+ activeSession = undefined;
2462
+ }
2463
+ return didSend;
2464
+ },
2465
+ };
2466
+ }
2467
+
2468
+ // October 31, 2026
2469
+ const PINPOINT_RETIREMENT_CUTOFF_UTC_MS = Date.parse('2026-10-31T00:00:00.000Z');
1379
2470
  /**
1380
2471
  * Amazon Telemetry
1381
- * Supports Amazon Pinpoint
2472
+ * Supports Pinpoint and Esri endpoint modes
1382
2473
  */
1383
2474
  class Amazon {
1384
2475
  constructor(options) {
1385
2476
  this.name = 'amazon';
2477
+ this.previousPage = {};
1386
2478
  this.isInitialized = false;
1387
- const { app = {}, fips, userPoolID } = options;
1388
- this.analytics = analyticsLib({
1389
- app: app.name,
1390
- version: app.version,
1391
- plugins: [
1392
- awsPinpointPlugin__default["default"]({
1393
- fips,
1394
- pinpointAppId: app.id,
1395
- getCredentials: () => {
1396
- return getCredentials(userPoolID, { fips });
1397
- },
1398
- }),
1399
- ],
1400
- });
2479
+ this.appConfig = {};
2480
+ this.isPinpointRetired = (nowMs = Date.now()) => nowMs >= PINPOINT_RETIREMENT_CUTOFF_UTC_MS;
2481
+ // default to `esri` mode if not specified
2482
+ const { app = {}, fips, userPoolID, mode = 'esri' } = options;
1401
2483
  Object.assign(this, options);
2484
+ this.appConfig = app;
2485
+ this.userPoolIDConfig = userPoolID;
2486
+ this.fipsConfig = fips;
2487
+ /** @deprecated Pinpoint is retiring on October 31, 2026. After this point all traffic will be routed to the in-house endpoint */
2488
+ if (mode === 'pinpoint' && !this.isPinpointRetired()) {
2489
+ this.mode = 'pinpoint';
2490
+ this.analytics = analyticsLib({
2491
+ app: app.name,
2492
+ version: app.version,
2493
+ plugins: [
2494
+ awsPinpointPlugin__default["default"]({
2495
+ fips,
2496
+ pinpointAppId: app.id,
2497
+ getCredentials: () => getCredentials(userPoolID, { fips }),
2498
+ }),
2499
+ ],
2500
+ });
2501
+ return;
2502
+ }
2503
+ this.mode = 'esri';
2504
+ this.analytics = createEsriPlugin({
2505
+ trackerName: this.name,
2506
+ appName: this.appConfig.name,
2507
+ appVersion: this.appConfig.version,
2508
+ appId: this.appConfig.id,
2509
+ userPoolID: this.userPoolIDConfig,
2510
+ fips: this.fipsConfig,
2511
+ });
1402
2512
  }
1403
2513
  init() {
1404
2514
  // if already initialized, return
@@ -1429,16 +2539,18 @@
1429
2539
  });
1430
2540
  const { pageUrl, pageName } = telemetryPayload;
1431
2541
  this.previousPage = { pageUrl, pageName };
1432
- this.analytics.track('pageView', telemetryPayload);
2542
+ const result = this.analytics.track('pageView', telemetryPayload);
2543
+ return typeof result === 'boolean' ? result : true;
1433
2544
  }
1434
- logEvent(event = {}) {
2545
+ logEvent(event = {}, trackOptions) {
1435
2546
  const telemetryPayload = createEventLog({
1436
2547
  event,
1437
2548
  dimensionLookup: this.dimensions,
1438
2549
  metricLookup: this.metrics,
1439
2550
  });
1440
2551
  const { name } = telemetryPayload;
1441
- this.analytics.track(name, telemetryPayload);
2552
+ const result = this.analytics.track(name, telemetryPayload, trackOptions);
2553
+ return typeof result === 'boolean' ? result : true;
1442
2554
  }
1443
2555
  }
1444
2556