@adobe/spacecat-shared-utils 1.33.3 → 1.35.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-utils-v1.35.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.34.0...@adobe/spacecat-shared-utils-v1.35.0) (2025-03-06)
2
+
3
+
4
+ ### Features
5
+
6
+ * tracing fetch timeout ([#657](https://github.com/adobe/spacecat-shared/issues/657)) ([67335e3](https://github.com/adobe/spacecat-shared/commit/67335e32b7cceeb500ca58dc05518876589a108a))
7
+
8
+ # [@adobe/spacecat-shared-utils-v1.34.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.33.3...@adobe/spacecat-shared-utils-v1.34.0) (2025-03-05)
9
+
10
+
11
+ ### Features
12
+
13
+ * Brand client ([#640](https://github.com/adobe/spacecat-shared/issues/640)) ([8d82f45](https://github.com/adobe/spacecat-shared/commit/8d82f45349ea9be2b1359fe5ebebf5a350f52666))
14
+
1
15
  # [@adobe/spacecat-shared-utils-v1.33.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.33.2...@adobe/spacecat-shared-utils-v1.33.3) (2025-03-04)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-utils",
3
- "version": "1.33.3",
3
+ "version": "1.35.0",
4
4
  "description": "Shared modules of the Spacecat Services - utils",
5
5
  "type": "module",
6
6
  "engines": {
package/src/functions.js CHANGED
@@ -15,6 +15,7 @@ import { validate as uuidValidate } from 'uuid';
15
15
  // Precompile regular expressions
16
16
  const REGEX_ISO_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
17
17
  const REGEX_TIME_OFFSET_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}(Z|[+-]\d{2}:\d{2})/;
18
+ const IMS_ORG_ID_REGEX = /[a-z0-9]{24}@AdobeOrg/i;
18
19
 
19
20
  /**
20
21
  * Determines if the given parameter is an array.
@@ -251,6 +252,15 @@ function dateAfterDays(days, dateString) {
251
252
  return currentDate;
252
253
  }
253
254
 
255
+ /**
256
+ * Validates whether the given string is a valid IMS Org ID.
257
+ * @param {string} imsOrgId - The string to validate.
258
+ * @returns {boolean} True if the given string is a valid IMS Org ID, false otherwise.
259
+ */
260
+ function isValidIMSOrgId(imsOrgId) {
261
+ return IMS_ORG_ID_REGEX.test(imsOrgId);
262
+ }
263
+
254
264
  export {
255
265
  arrayEquals,
256
266
  dateAfterDays,
@@ -269,5 +279,6 @@ export {
269
279
  isValidDate,
270
280
  isValidUrl,
271
281
  isValidUUID,
282
+ isValidIMSOrgId,
272
283
  toBoolean,
273
284
  };
package/src/index.d.ts CHANGED
@@ -45,6 +45,8 @@ export function isValidUrl(urlString: string): boolean;
45
45
 
46
46
  export function isValidUUID(uuid: string): boolean;
47
47
 
48
+ export function isValidIMSOrgId(imsOrgId: string): boolean;
49
+
48
50
  export function dateAfterDays(days: number, dateString: string): Date;
49
51
 
50
52
  export function deepEqual(a: unknown, b: unknown): boolean;
package/src/index.js CHANGED
@@ -28,6 +28,7 @@ export {
28
28
  isValidDate,
29
29
  isValidUrl,
30
30
  isValidUUID,
31
+ isValidIMSOrgId,
31
32
  toBoolean,
32
33
  } from './functions.js';
33
34
 
@@ -9,7 +9,7 @@
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 { Request } from '@adobe/fetch';
12
+ import { Request, timeoutSignal, AbortError } from '@adobe/fetch';
13
13
  import AWSXRay from 'aws-xray-sdk';
14
14
 
15
15
  import { fetch as adobeFetch } from './adobe-fetch.js';
@@ -107,17 +107,60 @@ const handleSubSegmentError = (subSegment, request, error) => {
107
107
  subSegment.close(error);
108
108
  };
109
109
 
110
+ /**
111
+ * Creates a timeout error with a consistent format
112
+ * @param {number} timeout - The timeout value in milliseconds
113
+ * @returns {Error} A formatted timeout error
114
+ */
115
+ const createTimeoutError = (timeout) => {
116
+ const timeoutError = new Error(`Request timeout after ${timeout}ms`);
117
+ timeoutError.code = 'ETIMEOUT';
118
+ return timeoutError;
119
+ };
120
+
121
+ /**
122
+ * Performs a fetch request with timeout handling
123
+ * @param {string|Request} resource - The resource to fetch
124
+ * @param {Object} options - Options for the fetch call
125
+ * @param {Object} signal - The timeout signal
126
+ * @returns {Promise<Response>} The fetch response
127
+ */
128
+ const fetchWithTimeout = async (resource, options, signal) => {
129
+ try {
130
+ const fetchOptions = { ...options, signal };
131
+ return await adobeFetch(resource, fetchOptions);
132
+ } catch (error) {
133
+ if (error instanceof AbortError) {
134
+ // Extract timeout from signal (implementation detail, but necessary)
135
+ // eslint-disable-next-line no-underscore-dangle
136
+ const timeout = signal._ms || 10000;
137
+ throw createTimeoutError(timeout);
138
+ }
139
+ throw error;
140
+ } finally {
141
+ signal.clear(); // Clean up the signal
142
+ }
143
+ };
144
+
110
145
  /**
111
146
  * Performs a fetch request and adds AWS X-Ray tracing, including request/response tracking.
112
147
  * @param {string} url - The URL for the request.
113
- * @param {Object} options - Options to be passed to the fetch call.
148
+ * @param {Object} [options] - Optional options to be passed to the fetch call.
149
+ * @param {number} [options.timeout=10000] - Timeout in milliseconds (defaults to 10 seconds).
114
150
  * @returns {Promise<Response>} The response from the fetch request.
115
151
  */
116
152
  export async function tracingFetch(url, options) {
117
153
  const parentSegment = AWSXRay.getSegment();
118
154
 
119
- options = isObject(options) ? options : {};
120
- options.headers = isObject(options.headers) ? options.headers : new Headers();
155
+ options = isObject(options) ? { ...options } : {};
156
+ options.headers = isObject(options.headers) ? options.headers : { };
157
+
158
+ // Set default timeout of 10 seconds if not specified
159
+ const timeout = options.timeout || 10000;
160
+ delete options.timeout; // Remove from options as we'll handle it separately
161
+
162
+ // Create a timeout signal
163
+ const signal = timeoutSignal(timeout);
121
164
 
122
165
  // find user-agent header in headers case insensitively
123
166
  let hasUserAgent = false;
@@ -131,10 +174,12 @@ export async function tracingFetch(url, options) {
131
174
  options.headers['User-Agent'] = SPACECAT_USER_AGENT;
132
175
  }
133
176
 
177
+ // If no parent segment, perform fetch without tracing
134
178
  if (!parentSegment) {
135
- return adobeFetch(url, options);
179
+ return fetchWithTimeout(url, options, signal);
136
180
  }
137
181
 
182
+ // With parent segment, create subsegment and add tracing
138
183
  const request = new Request(url, options);
139
184
  const { hostname } = new URL(request.url);
140
185
  const subSegment = createSubsegment(parentSegment, hostname);
@@ -145,21 +190,22 @@ export async function tracingFetch(url, options) {
145
190
  setTraceHeaders(request, parentSegment, subSegment);
146
191
  }
147
192
 
148
- const capturedAdobeFetch = async () => {
149
- let response = null;
150
- try {
151
- response = await adobeFetch(request);
152
- } catch (e) {
153
- handleSubSegmentError(subSegment, request, e);
154
- throw e;
155
- }
193
+ subSegment.addAnnotation('timeout_ms', timeout);
156
194
 
157
- setSubSegmentFlagsByStatusCode(subSegment, response.status);
195
+ try {
196
+ // Create a new request with the signal
197
+ const requestWithSignal = new Request(request, { signal });
198
+
199
+ // Use the same fetchWithTimeout function but catch errors to handle subsegment
200
+ const response = await fetchWithTimeout(requestWithSignal, { }, signal);
158
201
 
202
+ setSubSegmentFlagsByStatusCode(subSegment, response.status);
159
203
  addFetchRequestDataToSegment(subSegment, request, response);
160
204
  subSegment.close();
161
- return response;
162
- };
163
205
 
164
- return capturedAdobeFetch();
206
+ return response;
207
+ } catch (error) {
208
+ handleSubSegmentError(subSegment, request, error);
209
+ throw error;
210
+ }
165
211
  }