@dotcms/client 26.9.29-1 → 26.9.29-1-next.2806

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/index.cjs.js CHANGED
@@ -4,7 +4,7 @@ var consola = require('consola');
4
4
  var types = require('@dotcms/types');
5
5
  var internal = require('./internal.cjs.js');
6
6
 
7
- const SDK_VERSION = "26.9.29-1";
7
+ const SDK_VERSION = "26.9.29-1-next.2806";
8
8
 
9
9
  const DOTCMS_VERSION_HEADER = 'x-dotcms-version';
10
10
  const DOTCMS_MIN_SDK_HEADER = 'x-dotcms-min-sdk';
@@ -96,8 +96,9 @@ const checkSdkCompatibility = (headers, ownVersion) => {
96
96
  * - JSON response parsing
97
97
  * - HTTP error response parsing and conversion to DotHttpError
98
98
  * - Network error handling and wrapping
99
- * - Diagnosing misconfiguration: a response in a format other than JSON, or a request
100
- * that was redirected to another origin, is named as such in the error message
99
+ * - Diagnosing misconfiguration: a response in a format other than JSON, a request that was
100
+ * redirected to another origin, or one that got no response at all is named as such in the
101
+ * error message, leading with the dotcmsUrl fix where the SDK can tell what it is
101
102
  *
102
103
  * Every SDK caller talks to a dotCMS JSON endpoint, so a successful response with a
103
104
  * non-JSON content type is rejected rather than returned.
@@ -167,16 +168,14 @@ class FetchHttpClient extends types.BaseHttpClient {
167
168
  catch {
168
169
  errorBody = response.statusText;
169
170
  }
170
- // The status alone hides the cause, so name what else went wrong: the redirect
171
- // first (it is usually the root cause), then the unexpected format.
172
- const hints = [
173
- describeCrossOriginRedirect(url, response),
174
- contentType && !isJson
175
- ? `The response was '${contentType}', not JSON, so it may not have come from the dotCMS API: a proxy, a load balancer or another server can answer this way.`
176
- : undefined
177
- ].filter(Boolean);
171
+ // The status alone hides the cause, so name what else went wrong. A redirect to
172
+ // another origin explains the failure on its own (and any HTML the far side
173
+ // answered with), so it replaces the status line; the format is the fallback.
178
174
  const status = `HTTP ${response.status}: ${response.statusText}`;
179
- const message = hints.length ? `${status}. ${hints.join(' ')}` : status;
175
+ const message = describeCrossOriginRedirect(url, response) ??
176
+ (contentType && !isJson
177
+ ? `${status}. The response was '${contentType}', not JSON, so it may not have come from the dotCMS API: a proxy, a load balancer or another server can answer this way.`
178
+ : status);
180
179
  throw this.createHttpError(response.status, response.statusText, toPlainHeaders(response.headers), errorBody, message);
181
180
  }
182
181
  if (isJson) {
@@ -194,16 +193,13 @@ class FetchHttpClient extends types.BaseHttpClient {
194
193
  catch {
195
194
  body = undefined;
196
195
  }
197
- const crossOriginRedirect = describeCrossOriginRedirect(url, response);
198
- const sameOriginRedirect = !crossOriginRedirect && response.redirected && response.url
196
+ // A redirect to another origin is the whole story; the format is the only
197
+ // signal otherwise (a same-origin login page, a proxy's HTML).
198
+ const sameOriginRedirect = response.redirected && response.url
199
199
  ? ` after a redirect to '${response.url}'`
200
200
  : '';
201
- const message = [
202
- `Expected a JSON response from '${url}' but received '${contentType}' (HTTP ${response.status})${sameOriginRedirect}, which is not JSON. dotCMS API endpoints answer in JSON, so something other than the dotCMS API answered (a login page, a proxy or another server): check that dotcmsUrl points at your dotCMS instance.`,
203
- crossOriginRedirect
204
- ]
205
- .filter(Boolean)
206
- .join(' ');
201
+ const message = describeCrossOriginRedirect(url, response) ??
202
+ `Expected a JSON response from '${url}' but received '${contentType}' (HTTP ${response.status})${sameOriginRedirect}, which is not JSON. dotCMS API endpoints answer in JSON, so something other than the dotCMS API answered (a login page, a proxy or another server): check that dotcmsUrl points at your dotCMS instance.`;
207
203
  // Reported as 502 Bad Gateway, not the real 2xx: callers forward error.status
208
204
  // as their own response status, and an error must never read as success. The
209
205
  // real status is in the message and the body in data.
@@ -213,9 +209,12 @@ class FetchHttpClient extends types.BaseHttpClient {
213
209
  return response;
214
210
  }
215
211
  catch (error) {
216
- // Handle network errors (fetch throws TypeError for network issues)
217
- if (error instanceof TypeError) {
218
- throw this.createNetworkError(error);
212
+ // fetch rejects with a TypeError when no response arrives at all. Match on the name
213
+ // too: Node's fetch creates it in its own realm, which fails instanceof inside a VM
214
+ // context (Vitest's vmForks pool, for one).
215
+ if (error instanceof TypeError ||
216
+ error?.name === 'TypeError') {
217
+ throw this.createHttpError(0, 'Network Error', undefined, error, describeNetworkError(url, error));
219
218
  }
220
219
  throw error;
221
220
  }
@@ -230,23 +229,62 @@ class FetchHttpClient extends types.BaseHttpClient {
230
229
  * request: fetch drops the Authorization header on a cross-origin redirect, and a 301, 302 or
231
230
  * 303 turns a POST into a GET without its body.
232
231
  *
232
+ * The message leads with the fix. When only the origin changed (same path and query), the
233
+ * new origin is the value dotcmsUrl should have. When the path changed too, the far side is
234
+ * something else (an SSO login page, say), so its origin is not a dotcmsUrl to recommend.
235
+ *
233
236
  * @param requestUrl - The URL the SDK asked for.
234
237
  * @param response - The response fetch ended on after following redirects.
235
- * @returns A sentence naming both URLs and `dotcmsUrl`, or undefined.
238
+ * @returns The fix plus one sentence of why, or undefined.
236
239
  */
237
240
  function describeCrossOriginRedirect(requestUrl, response) {
238
241
  if (!response.redirected || !response.url) {
239
242
  return undefined;
240
243
  }
244
+ let from;
245
+ let to;
241
246
  try {
242
- if (new URL(requestUrl).origin === new URL(response.url).origin) {
243
- return undefined;
244
- }
247
+ from = new URL(requestUrl);
248
+ to = new URL(response.url);
245
249
  }
246
250
  catch {
247
251
  return undefined;
248
252
  }
249
- return `The request to '${requestUrl}' was redirected to '${response.url}', a different origin. Check that dotcmsUrl uses the exact scheme and host your dotCMS instance serves: a cross-origin redirect drops the Authorization header, and a 301, 302 or 303 turns a POST into a GET without its body.`;
253
+ if (from.origin === to.origin) {
254
+ return undefined;
255
+ }
256
+ const fix = from.pathname + from.search === to.pathname + to.search
257
+ ? `dotcmsUrl is '${from.origin}' but the server redirected to '${to.origin}'. Set dotcmsUrl to '${to.origin}'.`
258
+ : `dotcmsUrl is '${from.origin}' but the server redirected '${requestUrl}' to '${response.url}'. Set dotcmsUrl to the URL your dotCMS instance answers on directly.`;
259
+ return `${fix} A redirect to another origin drops the Authorization header and can turn a POST into a GET (HTTP ${response.status} after the redirect).`;
260
+ }
261
+ /**
262
+ * Explains a request that got no response at all: the full URL, the reason, and where to look.
263
+ *
264
+ * Node puts the system error in `cause` (`ENOTFOUND`, `ECONNREFUSED`, a TLS code, ...), so its
265
+ * code and the host are named. A browser gives only "Failed to fetch" and keeps the reason in
266
+ * the console, since a CORS rejection, a blocked redirect and a DNS failure all look the same
267
+ * to the page; the message lists those causes and points there.
268
+ *
269
+ * @param url - The URL the SDK asked for.
270
+ * @param error - The TypeError fetch rejected with.
271
+ * @returns The message for the network DotHttpError.
272
+ */
273
+ function describeNetworkError(url, error) {
274
+ const code = error.cause?.code;
275
+ let host = url;
276
+ try {
277
+ host = new URL(url).host;
278
+ }
279
+ catch {
280
+ // A relative URL: keep it as given.
281
+ }
282
+ const reason = typeof code === 'string' ? `${code} ${host}` : error.message;
283
+ const summary = `Couldn't reach '${url}' (${reason}).`;
284
+ if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
285
+ return `${summary} Check dotcmsUrl: the scheme may be wrong (the browser blocks a redirect from http to https), the host may not resolve, or dotCMS may not allow this origin (CORS). The browser console shows the exact reason.`;
286
+ }
287
+ return `${summary} Check the scheme, host and port in dotcmsUrl, and that dotCMS is reachable at that address.`;
250
288
  }
251
289
  /**
252
290
  * Copies fetch Headers into a plain object, the shape createHttpError expects.
package/index.esm.js CHANGED
@@ -2,7 +2,7 @@ import { consola } from 'consola';
2
2
  import { BaseHttpClient, DISTANCE_FUNCTIONS, DotHttpError, DotErrorAISearch, DotErrorContent, DotErrorNavigation, UVE_MODE, DotErrorPage } from '@dotcms/types';
3
3
  import { graphqlToPageEntity } from './internal.esm.js';
4
4
 
5
- const SDK_VERSION = "26.9.29-1";
5
+ const SDK_VERSION = "26.9.29-1-next.2806";
6
6
 
7
7
  const DOTCMS_VERSION_HEADER = 'x-dotcms-version';
8
8
  const DOTCMS_MIN_SDK_HEADER = 'x-dotcms-min-sdk';
@@ -94,8 +94,9 @@ const checkSdkCompatibility = (headers, ownVersion) => {
94
94
  * - JSON response parsing
95
95
  * - HTTP error response parsing and conversion to DotHttpError
96
96
  * - Network error handling and wrapping
97
- * - Diagnosing misconfiguration: a response in a format other than JSON, or a request
98
- * that was redirected to another origin, is named as such in the error message
97
+ * - Diagnosing misconfiguration: a response in a format other than JSON, a request that was
98
+ * redirected to another origin, or one that got no response at all is named as such in the
99
+ * error message, leading with the dotcmsUrl fix where the SDK can tell what it is
99
100
  *
100
101
  * Every SDK caller talks to a dotCMS JSON endpoint, so a successful response with a
101
102
  * non-JSON content type is rejected rather than returned.
@@ -165,16 +166,14 @@ class FetchHttpClient extends BaseHttpClient {
165
166
  catch {
166
167
  errorBody = response.statusText;
167
168
  }
168
- // The status alone hides the cause, so name what else went wrong: the redirect
169
- // first (it is usually the root cause), then the unexpected format.
170
- const hints = [
171
- describeCrossOriginRedirect(url, response),
172
- contentType && !isJson
173
- ? `The response was '${contentType}', not JSON, so it may not have come from the dotCMS API: a proxy, a load balancer or another server can answer this way.`
174
- : undefined
175
- ].filter(Boolean);
169
+ // The status alone hides the cause, so name what else went wrong. A redirect to
170
+ // another origin explains the failure on its own (and any HTML the far side
171
+ // answered with), so it replaces the status line; the format is the fallback.
176
172
  const status = `HTTP ${response.status}: ${response.statusText}`;
177
- const message = hints.length ? `${status}. ${hints.join(' ')}` : status;
173
+ const message = describeCrossOriginRedirect(url, response) ??
174
+ (contentType && !isJson
175
+ ? `${status}. The response was '${contentType}', not JSON, so it may not have come from the dotCMS API: a proxy, a load balancer or another server can answer this way.`
176
+ : status);
178
177
  throw this.createHttpError(response.status, response.statusText, toPlainHeaders(response.headers), errorBody, message);
179
178
  }
180
179
  if (isJson) {
@@ -192,16 +191,13 @@ class FetchHttpClient extends BaseHttpClient {
192
191
  catch {
193
192
  body = undefined;
194
193
  }
195
- const crossOriginRedirect = describeCrossOriginRedirect(url, response);
196
- const sameOriginRedirect = !crossOriginRedirect && response.redirected && response.url
194
+ // A redirect to another origin is the whole story; the format is the only
195
+ // signal otherwise (a same-origin login page, a proxy's HTML).
196
+ const sameOriginRedirect = response.redirected && response.url
197
197
  ? ` after a redirect to '${response.url}'`
198
198
  : '';
199
- const message = [
200
- `Expected a JSON response from '${url}' but received '${contentType}' (HTTP ${response.status})${sameOriginRedirect}, which is not JSON. dotCMS API endpoints answer in JSON, so something other than the dotCMS API answered (a login page, a proxy or another server): check that dotcmsUrl points at your dotCMS instance.`,
201
- crossOriginRedirect
202
- ]
203
- .filter(Boolean)
204
- .join(' ');
199
+ const message = describeCrossOriginRedirect(url, response) ??
200
+ `Expected a JSON response from '${url}' but received '${contentType}' (HTTP ${response.status})${sameOriginRedirect}, which is not JSON. dotCMS API endpoints answer in JSON, so something other than the dotCMS API answered (a login page, a proxy or another server): check that dotcmsUrl points at your dotCMS instance.`;
205
201
  // Reported as 502 Bad Gateway, not the real 2xx: callers forward error.status
206
202
  // as their own response status, and an error must never read as success. The
207
203
  // real status is in the message and the body in data.
@@ -211,9 +207,12 @@ class FetchHttpClient extends BaseHttpClient {
211
207
  return response;
212
208
  }
213
209
  catch (error) {
214
- // Handle network errors (fetch throws TypeError for network issues)
215
- if (error instanceof TypeError) {
216
- throw this.createNetworkError(error);
210
+ // fetch rejects with a TypeError when no response arrives at all. Match on the name
211
+ // too: Node's fetch creates it in its own realm, which fails instanceof inside a VM
212
+ // context (Vitest's vmForks pool, for one).
213
+ if (error instanceof TypeError ||
214
+ error?.name === 'TypeError') {
215
+ throw this.createHttpError(0, 'Network Error', undefined, error, describeNetworkError(url, error));
217
216
  }
218
217
  throw error;
219
218
  }
@@ -228,23 +227,62 @@ class FetchHttpClient extends BaseHttpClient {
228
227
  * request: fetch drops the Authorization header on a cross-origin redirect, and a 301, 302 or
229
228
  * 303 turns a POST into a GET without its body.
230
229
  *
230
+ * The message leads with the fix. When only the origin changed (same path and query), the
231
+ * new origin is the value dotcmsUrl should have. When the path changed too, the far side is
232
+ * something else (an SSO login page, say), so its origin is not a dotcmsUrl to recommend.
233
+ *
231
234
  * @param requestUrl - The URL the SDK asked for.
232
235
  * @param response - The response fetch ended on after following redirects.
233
- * @returns A sentence naming both URLs and `dotcmsUrl`, or undefined.
236
+ * @returns The fix plus one sentence of why, or undefined.
234
237
  */
235
238
  function describeCrossOriginRedirect(requestUrl, response) {
236
239
  if (!response.redirected || !response.url) {
237
240
  return undefined;
238
241
  }
242
+ let from;
243
+ let to;
239
244
  try {
240
- if (new URL(requestUrl).origin === new URL(response.url).origin) {
241
- return undefined;
242
- }
245
+ from = new URL(requestUrl);
246
+ to = new URL(response.url);
243
247
  }
244
248
  catch {
245
249
  return undefined;
246
250
  }
247
- return `The request to '${requestUrl}' was redirected to '${response.url}', a different origin. Check that dotcmsUrl uses the exact scheme and host your dotCMS instance serves: a cross-origin redirect drops the Authorization header, and a 301, 302 or 303 turns a POST into a GET without its body.`;
251
+ if (from.origin === to.origin) {
252
+ return undefined;
253
+ }
254
+ const fix = from.pathname + from.search === to.pathname + to.search
255
+ ? `dotcmsUrl is '${from.origin}' but the server redirected to '${to.origin}'. Set dotcmsUrl to '${to.origin}'.`
256
+ : `dotcmsUrl is '${from.origin}' but the server redirected '${requestUrl}' to '${response.url}'. Set dotcmsUrl to the URL your dotCMS instance answers on directly.`;
257
+ return `${fix} A redirect to another origin drops the Authorization header and can turn a POST into a GET (HTTP ${response.status} after the redirect).`;
258
+ }
259
+ /**
260
+ * Explains a request that got no response at all: the full URL, the reason, and where to look.
261
+ *
262
+ * Node puts the system error in `cause` (`ENOTFOUND`, `ECONNREFUSED`, a TLS code, ...), so its
263
+ * code and the host are named. A browser gives only "Failed to fetch" and keeps the reason in
264
+ * the console, since a CORS rejection, a blocked redirect and a DNS failure all look the same
265
+ * to the page; the message lists those causes and points there.
266
+ *
267
+ * @param url - The URL the SDK asked for.
268
+ * @param error - The TypeError fetch rejected with.
269
+ * @returns The message for the network DotHttpError.
270
+ */
271
+ function describeNetworkError(url, error) {
272
+ const code = error.cause?.code;
273
+ let host = url;
274
+ try {
275
+ host = new URL(url).host;
276
+ }
277
+ catch {
278
+ // A relative URL: keep it as given.
279
+ }
280
+ const reason = typeof code === 'string' ? `${code} ${host}` : error.message;
281
+ const summary = `Couldn't reach '${url}' (${reason}).`;
282
+ if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
283
+ return `${summary} Check dotcmsUrl: the scheme may be wrong (the browser blocks a redirect from http to https), the host may not resolve, or dotCMS may not allow this origin (CORS). The browser console shows the exact reason.`;
284
+ }
285
+ return `${summary} Check the scheme, host and port in dotcmsUrl, and that dotCMS is reachable at that address.`;
248
286
  }
249
287
  /**
250
288
  * Copies fetch Headers into a plain object, the shape createHttpError expects.
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@dotcms/client",
3
- "version": "26.9.29-1",
3
+ "version": "26.9.29-1-next.2806",
4
4
  "description": "Official JavaScript library for interacting with DotCMS REST APIs.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/dotCMS/core.git#main"
8
8
  },
9
9
  "peerDependencies": {
10
- "@dotcms/types": "26.9.29-1"
10
+ "@dotcms/types": "26.9.29-1-next.2806"
11
11
  },
12
12
  "dependencies": {
13
13
  "consola": "^3.4.2"
14
14
  },
15
15
  "devDependencies": {
16
- "@dotcms/types": "26.9.29-1"
16
+ "@dotcms/types": "26.9.29-1-next.2806"
17
17
  },
18
18
  "keywords": [
19
19
  "dotCMS",
@@ -7,8 +7,9 @@ import { BaseHttpClient, DotRequestOptions } from '@dotcms/types';
7
7
  * - JSON response parsing
8
8
  * - HTTP error response parsing and conversion to DotHttpError
9
9
  * - Network error handling and wrapping
10
- * - Diagnosing misconfiguration: a response in a format other than JSON, or a request
11
- * that was redirected to another origin, is named as such in the error message
10
+ * - Diagnosing misconfiguration: a response in a format other than JSON, a request that was
11
+ * redirected to another origin, or one that got no response at all is named as such in the
12
+ * error message, leading with the dotcmsUrl fix where the SDK can tell what it is
12
13
  *
13
14
  * Every SDK caller talks to a dotCMS JSON endpoint, so a successful response with a
14
15
  * non-JSON content type is rejected rather than returned.