@adobe/spacecat-shared-utils 1.112.3 → 1.112.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [@adobe/spacecat-shared-utils-v1.112.5](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.112.4...@adobe/spacecat-shared-utils-v1.112.5) (2026-04-10)
2
+
3
+ ### Bug Fixes
4
+
5
+ * byocdn-imperva shows correct config ([#1529](https://github.com/adobe/spacecat-shared/issues/1529)) ([8aaaf1d](https://github.com/adobe/spacecat-shared/commit/8aaaf1df7b5081184f9f6c3824b915f260c0d898))
6
+
7
+ ## [@adobe/spacecat-shared-utils-v1.112.4](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.112.3...@adobe/spacecat-shared-utils-v1.112.4) (2026-04-09)
8
+
9
+ ### Bug Fixes
10
+
11
+ * **utils:** detect Akamai blocking via akamai-cache-status and akamai-grn headers ([#1524](https://github.com/adobe/spacecat-shared/issues/1524)) ([85f93a2](https://github.com/adobe/spacecat-shared/commit/85f93a276be0668a75fa3058102d86e537898a79)), closes [#1523](https://github.com/adobe/spacecat-shared/issues/1523)
12
+
1
13
  ## [@adobe/spacecat-shared-utils-v1.112.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.112.2...@adobe/spacecat-shared-utils-v1.112.3) (2026-04-08)
2
14
 
3
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.112.3",
3
+ "version": "1.112.5",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "exports": {
@@ -83,7 +83,7 @@
83
83
  "sinon-chai": "4.0.1"
84
84
  },
85
85
  "dependencies": {
86
- "@adobe/fetch": "4.2.3",
86
+ "@adobe/fetch": "4.3.0",
87
87
  "@aws-sdk/client-s3": "3.1024.0",
88
88
  "@aws-sdk/client-sqs": "3.1024.0",
89
89
  "@json2csv/plainjs": "7.0.6",
@@ -91,6 +91,7 @@ const CHALLENGE_PATTERNS = {
91
91
  akamai: [
92
92
  /Access Denied.*Akamai/i,
93
93
  /Reference.*Akamai/i,
94
+ /errors\.(edgesuite|edgekey)\.net/i,
94
95
  ],
95
96
  general: [
96
97
  /captcha/i,
@@ -124,7 +125,9 @@ function analyzeResponse(response, html = null) {
124
125
  const hasImperva = () => headers.get('x-iinfo') || headers.get('x-cdn') === 'Incapsula';
125
126
  const hasAkamai = () => headers.get('x-akamai-request-id')
126
127
  || headers.get('x-akamai-session-id')
127
- || headers.get('server')?.includes('AkamaiGHost');
128
+ || headers.get('server')?.includes('AkamaiGHost')
129
+ || headers.get('akamai-cache-status')
130
+ || headers.get('akamai-grn');
128
131
  const hasFastly = () => headers.get('x-served-by')?.startsWith('cache-')
129
132
  || headers.get('fastly-io-info');
130
133
  const hasCloudFront = () => headers.get('x-amz-cf-id')
@@ -133,7 +136,9 @@ function analyzeResponse(response, html = null) {
133
136
 
134
137
  // Check HTML content for challenge page patterns (if HTML provided)
135
138
  const htmlHasChallenge = (patterns) => {
136
- if (!html) return false;
139
+ if (!html) {
140
+ return false;
141
+ }
137
142
  return patterns.some((pattern) => pattern.test(html));
138
143
  };
139
144
 
@@ -40,8 +40,12 @@ function has53CalendarWeeks(year) {
40
40
  }
41
41
 
42
42
  function isValidWeek(week, year) {
43
- if (!Number.isInteger(year) || year < 100 || !Number.isInteger(week) || week < 1) return false;
44
- if (week === 53) return has53CalendarWeeks(year);
43
+ if (!Number.isInteger(year) || year < 100 || !Number.isInteger(week) || week < 1) {
44
+ return false;
45
+ }
46
+ if (week === 53) {
47
+ return has53CalendarWeeks(year);
48
+ }
45
49
  return week <= 52;
46
50
  }
47
51
 
@@ -189,13 +189,12 @@ const CDN_TRANSFORMATIONS = {
189
189
  'byocdn-imperva': (payload) => ({
190
190
  'Log integration mode': 'Push mode',
191
191
  'Delivery method': 'Amazon S3 ARN',
192
- 'Bucket Name': payload.bucketName,
193
192
  Region: payload.region,
194
- Path: payload.allowedPaths?.[0] || '',
193
+ Path: `${payload.bucketName}/${payload.allowedPaths?.[0] || ''}`.replace(/\/$/, ''),
195
194
  'Log types': 'Cloud WAF',
196
195
  'Log level': 'Access logs',
197
196
  Format: 'W3C',
198
- 'Compress logs': 'Yes',
197
+ 'Compress logs': 'No',
199
198
  HelpUrl: 'https://docs-cybersec.thalesgroup.com/bundle/cloud-application-security/page/siem-log-configuration.htm',
200
199
  }),
201
200
  'byocdn-other': (payload) => ({
package/src/formcalc.js CHANGED
@@ -157,7 +157,9 @@ export function getHighPageViewsLowFormCtrMetrics(formVitalsCollection) {
157
157
  }
158
158
 
159
159
  // Skip entry if no valid maxPageviewUrl is found
160
- if (!maxPageviewUrl) return;
160
+ if (!maxPageviewUrl) {
161
+ return;
162
+ }
161
163
 
162
164
  // Calculate `y`: find the CTA with the highest clicks and include the source
163
165
  const y = maxPageviewUrl.CTAs.reduce((maxCta, cta) => {
package/src/functions.js CHANGED
@@ -94,30 +94,48 @@ function isNonEmptyObject(value) {
94
94
  * @return {boolean} True if the objects or arrays are equal, false otherwise.
95
95
  */
96
96
  function deepEqual(x, y) {
97
- if (x === y) return true;
97
+ if (x === y) {
98
+ return true;
99
+ }
98
100
 
99
101
  if (isArray(x) && isArray(y)) {
100
- if (x.length !== y.length) return false;
102
+ if (x.length !== y.length) {
103
+ return false;
104
+ }
101
105
  for (let i = 0; i < x.length; i += 1) {
102
- if (!deepEqual(x[i], y[i])) return false;
106
+ if (!deepEqual(x[i], y[i])) {
107
+ return false;
108
+ }
103
109
  }
104
110
  return true;
105
111
  }
106
112
 
107
- if (!isObject(x) || !isObject(y)) return false;
113
+ if (!isObject(x) || !isObject(y)) {
114
+ return false;
115
+ }
108
116
 
109
- if (x.constructor !== y.constructor) return false;
117
+ if (x.constructor !== y.constructor) {
118
+ return false;
119
+ }
110
120
 
111
- if (x instanceof Date) return x.getTime() === y.getTime();
112
- if (x instanceof RegExp) return x.toString() === y.toString();
121
+ if (x instanceof Date) {
122
+ return x.getTime() === y.getTime();
123
+ }
124
+ if (x instanceof RegExp) {
125
+ return x.toString() === y.toString();
126
+ }
113
127
 
114
128
  const xKeys = Object.keys(x).filter((key) => typeof x[key] !== 'function');
115
129
  const yKeys = Object.keys(y).filter((key) => typeof y[key] !== 'function');
116
130
 
117
- if (xKeys.length !== yKeys.length) return false;
131
+ if (xKeys.length !== yKeys.length) {
132
+ return false;
133
+ }
118
134
 
119
135
  for (const key of xKeys) {
120
- if (!Object.prototype.hasOwnProperty.call(y, key) || !deepEqual(x[key], y[key])) return false;
136
+ if (!Object.prototype.hasOwnProperty.call(y, key) || !deepEqual(x[key], y[key])) {
137
+ return false;
138
+ }
121
139
  }
122
140
 
123
141
  return true;
@@ -90,9 +90,13 @@ export function getCurrentCycle(cycleFormat) {
90
90
  * cycleFormat: string, currentCycle: string }|undefined}
91
91
  */
92
92
  export function getTokenGrantConfig(tokenType) {
93
- if (!hasText(tokenType)) return undefined;
93
+ if (!hasText(tokenType)) {
94
+ return undefined;
95
+ }
94
96
  const entry = TOKEN_GRANT_CONFIG[tokenType];
95
- if (!entry) return undefined;
97
+ if (!entry) {
98
+ return undefined;
99
+ }
96
100
  return { ...entry, currentCycle: getCurrentCycle(entry.cycleFormat) };
97
101
  }
98
102
 
@@ -105,9 +109,13 @@ export function getTokenGrantConfig(tokenType) {
105
109
  * tokenType: string }|undefined}
106
110
  */
107
111
  export function getTokenGrantConfigByOpportunity(opportunityName) {
108
- if (!hasText(opportunityName)) return undefined;
112
+ if (!hasText(opportunityName)) {
113
+ return undefined;
114
+ }
109
115
  const tokenType = getTokenTypeForOpportunity(opportunityName);
110
116
  const config = getTokenGrantConfig(tokenType);
111
- if (!config) return undefined;
117
+ if (!config) {
118
+ return undefined;
119
+ }
112
120
  return { ...config, tokenType };
113
121
  }
@@ -184,7 +184,9 @@ async function resolveCanonicalUrl(urlString, method = 'HEAD') {
184
184
  * @returns {string} The normalized URL
185
185
  */
186
186
  function normalizeUrl(url) {
187
- if (!url || typeof url !== 'string') return url;
187
+ if (!url || typeof url !== 'string') {
188
+ return url;
189
+ }
188
190
  // Trim whitespace from beginning and end
189
191
  let normalized = url.trim();
190
192
  // Handle trailing slashes - normalize multiple trailing slashes to single slash
@@ -207,8 +209,13 @@ function normalizeUrl(url) {
207
209
  * @returns {string} The normalized pathname
208
210
  */
209
211
  function normalizePathname(pathname) {
210
- if (!pathname || typeof pathname !== 'string') return pathname;
211
- if (pathname === '/') return '/';
212
+ /* c8 ignore next 3 */
213
+ if (!pathname || typeof pathname !== 'string') {
214
+ return pathname;
215
+ }
216
+ if (pathname === '/') {
217
+ return '/';
218
+ }
212
219
  return pathname.replace(/\/+$/, '');
213
220
  }
214
221
 
@@ -219,7 +226,9 @@ function normalizePathname(pathname) {
219
226
  * @returns {boolean} True if URL matches any filter URL, false if any URL is invalid
220
227
  */
221
228
  function urlMatchesFilter(url, filterUrls) {
222
- if (!filterUrls || filterUrls.length === 0) return true;
229
+ if (!filterUrls || filterUrls.length === 0) {
230
+ return true;
231
+ }
223
232
  try {
224
233
  // Normalize the input URL
225
234
  const normalizedInputUrl = normalizeUrl(url);
@@ -268,7 +277,9 @@ function hasNonWWWSubdomain(baseUrl) {
268
277
  * @returns {string} - The hostname with the www subdomain toggled.
269
278
  */
270
279
  function toggleWWWHostname(hostname) {
271
- if (hasNonWWWSubdomain(`https://${hostname}`)) return hostname;
280
+ if (hasNonWWWSubdomain(`https://${hostname}`)) {
281
+ return hostname;
282
+ }
272
283
  return hostname.startsWith('www.') ? hostname.replace('www.', '') : `www.${hostname}`;
273
284
  }
274
285