@dotcms/client 26.9.29-1 → 26.9.29-1-next.2808
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 +66 -28
- package/index.esm.js +66 -28
- package/package.json +3 -3
- package/src/lib/client/adapters/fetch-http-client.d.ts +3 -2
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.2808";
|
|
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,
|
|
100
|
-
*
|
|
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
|
|
171
|
-
//
|
|
172
|
-
|
|
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 =
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
247
|
+
from = new URL(requestUrl);
|
|
248
|
+
to = new URL(response.url);
|
|
245
249
|
}
|
|
246
250
|
catch {
|
|
247
251
|
return undefined;
|
|
248
252
|
}
|
|
249
|
-
|
|
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.2808";
|
|
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,
|
|
98
|
-
*
|
|
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
|
|
169
|
-
//
|
|
170
|
-
|
|
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 =
|
|
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
|
-
|
|
196
|
-
|
|
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
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
245
|
+
from = new URL(requestUrl);
|
|
246
|
+
to = new URL(response.url);
|
|
243
247
|
}
|
|
244
248
|
catch {
|
|
245
249
|
return undefined;
|
|
246
250
|
}
|
|
247
|
-
|
|
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.2808",
|
|
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.2808"
|
|
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.2808"
|
|
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,
|
|
11
|
-
*
|
|
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.
|