@sanity/client 7.21.0 → 7.22.1
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/dist/index.browser.cjs +51 -37
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.d.cts +10 -3
- package/dist/index.browser.d.ts +10 -3
- package/dist/index.browser.js +51 -37
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +52 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +52 -38
- package/dist/index.js.map +1 -1
- package/dist/stega.browser.d.cts +10 -3
- package/dist/stega.browser.d.ts +10 -3
- package/dist/stega.d.cts +10 -3
- package/dist/stega.d.ts +10 -3
- package/package.json +1 -1
- package/src/data/live.ts +95 -24
- package/src/http/errors.ts +56 -17
- package/src/types.ts +1 -0
- package/umd/sanityClient.js +51 -37
- package/umd/sanityClient.min.js +2 -2
package/dist/stega.browser.d.cts
CHANGED
|
@@ -855,6 +855,7 @@ export declare class ClientError extends Error {
|
|
|
855
855
|
response: ErrorProps['response']
|
|
856
856
|
statusCode: ErrorProps['statusCode']
|
|
857
857
|
responseBody: ErrorProps['responseBody']
|
|
858
|
+
traceId: ErrorProps['traceId']
|
|
858
859
|
details: ErrorProps['details']
|
|
859
860
|
constructor(res: Any, context?: HttpContext)
|
|
860
861
|
}
|
|
@@ -1133,9 +1134,9 @@ declare interface ContentSourceMapValueMapping_2 {
|
|
|
1133
1134
|
|
|
1134
1135
|
/** @public */
|
|
1135
1136
|
export declare class CorsOriginError extends Error {
|
|
1136
|
-
projectId
|
|
1137
|
+
projectId?: string
|
|
1137
1138
|
addOriginUrl?: URL
|
|
1138
|
-
constructor({projectId}
|
|
1139
|
+
constructor({projectId, credentials}?: {projectId?: string; credentials?: boolean})
|
|
1139
1140
|
}
|
|
1140
1141
|
|
|
1141
1142
|
/**
|
|
@@ -1515,6 +1516,7 @@ export declare interface ErrorProps {
|
|
|
1515
1516
|
response: Any
|
|
1516
1517
|
statusCode: number
|
|
1517
1518
|
responseBody: Any
|
|
1519
|
+
traceId?: string
|
|
1518
1520
|
details: Any
|
|
1519
1521
|
}
|
|
1520
1522
|
|
|
@@ -1714,7 +1716,11 @@ export declare type FitMode = 'preserve' | 'stretch' | 'crop' | 'smartcrop' | 'p
|
|
|
1714
1716
|
* @returns A formatted error message string.
|
|
1715
1717
|
* @public
|
|
1716
1718
|
*/
|
|
1717
|
-
export declare function formatQueryParseError(
|
|
1719
|
+
export declare function formatQueryParseError(
|
|
1720
|
+
error: QueryParseError,
|
|
1721
|
+
tag?: string | null,
|
|
1722
|
+
traceId?: string,
|
|
1723
|
+
): string
|
|
1718
1724
|
|
|
1719
1725
|
/** @beta */
|
|
1720
1726
|
declare type GenerateAsyncInstruction<T extends Record<string, Any> = Record<string, Any>> = (
|
|
@@ -6061,6 +6067,7 @@ export declare class ServerError extends Error {
|
|
|
6061
6067
|
response: ErrorProps['response']
|
|
6062
6068
|
statusCode: ErrorProps['statusCode']
|
|
6063
6069
|
responseBody: ErrorProps['responseBody']
|
|
6070
|
+
traceId: ErrorProps['traceId']
|
|
6064
6071
|
details: ErrorProps['details']
|
|
6065
6072
|
constructor(res: Any)
|
|
6066
6073
|
}
|
package/dist/stega.browser.d.ts
CHANGED
|
@@ -855,6 +855,7 @@ export declare class ClientError extends Error {
|
|
|
855
855
|
response: ErrorProps['response']
|
|
856
856
|
statusCode: ErrorProps['statusCode']
|
|
857
857
|
responseBody: ErrorProps['responseBody']
|
|
858
|
+
traceId: ErrorProps['traceId']
|
|
858
859
|
details: ErrorProps['details']
|
|
859
860
|
constructor(res: Any, context?: HttpContext)
|
|
860
861
|
}
|
|
@@ -1133,9 +1134,9 @@ declare interface ContentSourceMapValueMapping_2 {
|
|
|
1133
1134
|
|
|
1134
1135
|
/** @public */
|
|
1135
1136
|
export declare class CorsOriginError extends Error {
|
|
1136
|
-
projectId
|
|
1137
|
+
projectId?: string
|
|
1137
1138
|
addOriginUrl?: URL
|
|
1138
|
-
constructor({projectId}
|
|
1139
|
+
constructor({projectId, credentials}?: {projectId?: string; credentials?: boolean})
|
|
1139
1140
|
}
|
|
1140
1141
|
|
|
1141
1142
|
/**
|
|
@@ -1515,6 +1516,7 @@ export declare interface ErrorProps {
|
|
|
1515
1516
|
response: Any
|
|
1516
1517
|
statusCode: number
|
|
1517
1518
|
responseBody: Any
|
|
1519
|
+
traceId?: string
|
|
1518
1520
|
details: Any
|
|
1519
1521
|
}
|
|
1520
1522
|
|
|
@@ -1714,7 +1716,11 @@ export declare type FitMode = 'preserve' | 'stretch' | 'crop' | 'smartcrop' | 'p
|
|
|
1714
1716
|
* @returns A formatted error message string.
|
|
1715
1717
|
* @public
|
|
1716
1718
|
*/
|
|
1717
|
-
export declare function formatQueryParseError(
|
|
1719
|
+
export declare function formatQueryParseError(
|
|
1720
|
+
error: QueryParseError,
|
|
1721
|
+
tag?: string | null,
|
|
1722
|
+
traceId?: string,
|
|
1723
|
+
): string
|
|
1718
1724
|
|
|
1719
1725
|
/** @beta */
|
|
1720
1726
|
declare type GenerateAsyncInstruction<T extends Record<string, Any> = Record<string, Any>> = (
|
|
@@ -6061,6 +6067,7 @@ export declare class ServerError extends Error {
|
|
|
6061
6067
|
response: ErrorProps['response']
|
|
6062
6068
|
statusCode: ErrorProps['statusCode']
|
|
6063
6069
|
responseBody: ErrorProps['responseBody']
|
|
6070
|
+
traceId: ErrorProps['traceId']
|
|
6064
6071
|
details: ErrorProps['details']
|
|
6065
6072
|
constructor(res: Any)
|
|
6066
6073
|
}
|
package/dist/stega.d.cts
CHANGED
|
@@ -855,6 +855,7 @@ export declare class ClientError extends Error {
|
|
|
855
855
|
response: ErrorProps['response']
|
|
856
856
|
statusCode: ErrorProps['statusCode']
|
|
857
857
|
responseBody: ErrorProps['responseBody']
|
|
858
|
+
traceId: ErrorProps['traceId']
|
|
858
859
|
details: ErrorProps['details']
|
|
859
860
|
constructor(res: Any, context?: HttpContext)
|
|
860
861
|
}
|
|
@@ -1133,9 +1134,9 @@ declare interface ContentSourceMapValueMapping_2 {
|
|
|
1133
1134
|
|
|
1134
1135
|
/** @public */
|
|
1135
1136
|
export declare class CorsOriginError extends Error {
|
|
1136
|
-
projectId
|
|
1137
|
+
projectId?: string
|
|
1137
1138
|
addOriginUrl?: URL
|
|
1138
|
-
constructor({projectId}
|
|
1139
|
+
constructor({projectId, credentials}?: {projectId?: string; credentials?: boolean})
|
|
1139
1140
|
}
|
|
1140
1141
|
|
|
1141
1142
|
/**
|
|
@@ -1515,6 +1516,7 @@ export declare interface ErrorProps {
|
|
|
1515
1516
|
response: Any
|
|
1516
1517
|
statusCode: number
|
|
1517
1518
|
responseBody: Any
|
|
1519
|
+
traceId?: string
|
|
1518
1520
|
details: Any
|
|
1519
1521
|
}
|
|
1520
1522
|
|
|
@@ -1714,7 +1716,11 @@ export declare type FitMode = 'preserve' | 'stretch' | 'crop' | 'smartcrop' | 'p
|
|
|
1714
1716
|
* @returns A formatted error message string.
|
|
1715
1717
|
* @public
|
|
1716
1718
|
*/
|
|
1717
|
-
export declare function formatQueryParseError(
|
|
1719
|
+
export declare function formatQueryParseError(
|
|
1720
|
+
error: QueryParseError,
|
|
1721
|
+
tag?: string | null,
|
|
1722
|
+
traceId?: string,
|
|
1723
|
+
): string
|
|
1718
1724
|
|
|
1719
1725
|
/** @beta */
|
|
1720
1726
|
declare type GenerateAsyncInstruction<T extends Record<string, Any> = Record<string, Any>> = (
|
|
@@ -6061,6 +6067,7 @@ export declare class ServerError extends Error {
|
|
|
6061
6067
|
response: ErrorProps['response']
|
|
6062
6068
|
statusCode: ErrorProps['statusCode']
|
|
6063
6069
|
responseBody: ErrorProps['responseBody']
|
|
6070
|
+
traceId: ErrorProps['traceId']
|
|
6064
6071
|
details: ErrorProps['details']
|
|
6065
6072
|
constructor(res: Any)
|
|
6066
6073
|
}
|
package/dist/stega.d.ts
CHANGED
|
@@ -855,6 +855,7 @@ export declare class ClientError extends Error {
|
|
|
855
855
|
response: ErrorProps['response']
|
|
856
856
|
statusCode: ErrorProps['statusCode']
|
|
857
857
|
responseBody: ErrorProps['responseBody']
|
|
858
|
+
traceId: ErrorProps['traceId']
|
|
858
859
|
details: ErrorProps['details']
|
|
859
860
|
constructor(res: Any, context?: HttpContext)
|
|
860
861
|
}
|
|
@@ -1133,9 +1134,9 @@ declare interface ContentSourceMapValueMapping_2 {
|
|
|
1133
1134
|
|
|
1134
1135
|
/** @public */
|
|
1135
1136
|
export declare class CorsOriginError extends Error {
|
|
1136
|
-
projectId
|
|
1137
|
+
projectId?: string
|
|
1137
1138
|
addOriginUrl?: URL
|
|
1138
|
-
constructor({projectId}
|
|
1139
|
+
constructor({projectId, credentials}?: {projectId?: string; credentials?: boolean})
|
|
1139
1140
|
}
|
|
1140
1141
|
|
|
1141
1142
|
/**
|
|
@@ -1515,6 +1516,7 @@ export declare interface ErrorProps {
|
|
|
1515
1516
|
response: Any
|
|
1516
1517
|
statusCode: number
|
|
1517
1518
|
responseBody: Any
|
|
1519
|
+
traceId?: string
|
|
1518
1520
|
details: Any
|
|
1519
1521
|
}
|
|
1520
1522
|
|
|
@@ -1714,7 +1716,11 @@ export declare type FitMode = 'preserve' | 'stretch' | 'crop' | 'smartcrop' | 'p
|
|
|
1714
1716
|
* @returns A formatted error message string.
|
|
1715
1717
|
* @public
|
|
1716
1718
|
*/
|
|
1717
|
-
export declare function formatQueryParseError(
|
|
1719
|
+
export declare function formatQueryParseError(
|
|
1720
|
+
error: QueryParseError,
|
|
1721
|
+
tag?: string | null,
|
|
1722
|
+
traceId?: string,
|
|
1723
|
+
): string
|
|
1718
1724
|
|
|
1719
1725
|
/** @beta */
|
|
1720
1726
|
declare type GenerateAsyncInstruction<T extends Record<string, Any> = Record<string, Any>> = (
|
|
@@ -6061,6 +6067,7 @@ export declare class ServerError extends Error {
|
|
|
6061
6067
|
response: ErrorProps['response']
|
|
6062
6068
|
statusCode: ErrorProps['statusCode']
|
|
6063
6069
|
responseBody: ErrorProps['responseBody']
|
|
6070
|
+
traceId: ErrorProps['traceId']
|
|
6064
6071
|
details: ErrorProps['details']
|
|
6065
6072
|
constructor(res: Any)
|
|
6066
6073
|
}
|
package/package.json
CHANGED
package/src/data/live.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {catchError, mergeMap, Observable, of} from 'rxjs'
|
|
1
|
+
import {catchError, mergeMap, Observable, of, throwError} from 'rxjs'
|
|
2
2
|
import {finalize, map} from 'rxjs/operators'
|
|
3
3
|
|
|
4
4
|
import {CorsOriginError} from '../http/errors'
|
|
@@ -122,16 +122,10 @@ export class LiveClient {
|
|
|
122
122
|
'goaway',
|
|
123
123
|
])
|
|
124
124
|
|
|
125
|
-
const checkCors =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
headers: esOptions.headers,
|
|
130
|
-
}).pipe(
|
|
131
|
-
catchError(() => {
|
|
132
|
-
// If the request fails, then we assume it was due to CORS, and we rethrow a special error that allows special handling in userland
|
|
133
|
-
throw new CorsOriginError({projectId: projectId!})
|
|
134
|
-
}),
|
|
125
|
+
const checkCors = checkCorsObservable(
|
|
126
|
+
new URL(this.#client.getUrl('/check/cors', false)),
|
|
127
|
+
projectId,
|
|
128
|
+
esOptions.withCredentials === true,
|
|
135
129
|
)
|
|
136
130
|
|
|
137
131
|
const observable = events
|
|
@@ -145,6 +139,12 @@ export class LiveClient {
|
|
|
145
139
|
return of(event)
|
|
146
140
|
}),
|
|
147
141
|
catchError((err) => {
|
|
142
|
+
// If a prior `reconnect` already ran the CORS probe and produced a
|
|
143
|
+
// `CorsOriginError`, just rethrow it instead of calling `/check/cors`
|
|
144
|
+
// a second time only to get the same answer.
|
|
145
|
+
if (err instanceof CorsOriginError) {
|
|
146
|
+
return throwError(() => err)
|
|
147
|
+
}
|
|
148
148
|
return checkCors.pipe(
|
|
149
149
|
mergeMap(() => {
|
|
150
150
|
// rethrow the original error if checkCors passed
|
|
@@ -172,21 +172,92 @@ export class LiveClient {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Probes the `/check/cors` endpoint to confirm whether the current origin is
|
|
177
|
+
* allowed by the project's CORS configuration. EventSource failures are opaque,
|
|
178
|
+
* so we use this side-channel purely to tell "the server actively rejected our
|
|
179
|
+
* origin" apart from every other class of failure.
|
|
180
|
+
*
|
|
181
|
+
* Errors with `CorsOriginError` when either:
|
|
182
|
+
*
|
|
183
|
+
* - `requireCredentials` is `true` (the EventSource was about to send
|
|
184
|
+
* credentials) and `/check/cors` reports `result.withCredentials === false`.
|
|
185
|
+
* The credentialed request would fail due to a missing
|
|
186
|
+
* `access-control-allow-credentials` header. The resulting error carries
|
|
187
|
+
* `credentials: true` so its `addOriginUrl` deep-link pre-selects the
|
|
188
|
+
* "Allow credentials" toggle in the Sanity management form.
|
|
189
|
+
* - `/check/cors` reports `result.allowed === false` (origin is not on the
|
|
190
|
+
* project's CORS allow-list). The error carries `credentials: requireCredentials`
|
|
191
|
+
* so the deep-link still pre-selects credentials when the caller needed them.
|
|
192
|
+
*
|
|
193
|
+
* Every other outcome is intentionally treated as "we don't know": the
|
|
194
|
+
* observable emits a single `void` value and then completes, so downstream
|
|
195
|
+
* `mergeMap(() => ...)` consumers can continue. No error is surfaced for any
|
|
196
|
+
* of these cases:
|
|
197
|
+
*
|
|
198
|
+
* - `allowed: true` (with credentials satisfied if required) or an
|
|
199
|
+
* unrecognised body shape: the server did not confirm a CORS rejection.
|
|
200
|
+
* - Non-2xx HTTP response from `/check/cors`: same - no signal either way, and
|
|
201
|
+
* a 5xx on the probe shouldn't poison the EventSource's original error.
|
|
202
|
+
* - `fetch` / network / JSON parse failures: indistinguishable from ordinary
|
|
203
|
+
* connectivity hiccups (offline, DNS, certs, transient outages). Reporting
|
|
204
|
+
* those as CORS errors is exactly the false-positive class this helper
|
|
205
|
+
* exists to prevent.
|
|
206
|
+
* - The subscription was aborted: nothing to emit and nothing to complete.
|
|
207
|
+
*
|
|
208
|
+
* In all of those cases the caller's original underlying error from the
|
|
209
|
+
* EventSource is allowed to propagate unchanged.
|
|
210
|
+
*/
|
|
211
|
+
function checkCorsObservable(
|
|
212
|
+
url: URL,
|
|
213
|
+
projectId: string | undefined,
|
|
214
|
+
requireCredentials: boolean,
|
|
215
|
+
): Observable<void> {
|
|
216
|
+
return new Observable<void>((observer) => {
|
|
177
217
|
const controller = new AbortController()
|
|
178
|
-
const signal = controller
|
|
179
|
-
fetch(url, {
|
|
180
|
-
(response) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
218
|
+
const {signal} = controller
|
|
219
|
+
fetch(url, {method: 'GET', mode: 'cors', credentials: 'omit', signal})
|
|
220
|
+
.then((response) => {
|
|
221
|
+
// Aborted or non-2xx: not a confirmed CORS rejection. Fall through with
|
|
222
|
+
// an undefined body so the next step takes the silent-completion path.
|
|
223
|
+
if (signal.aborted || !response.ok) return
|
|
224
|
+
return response.json() as Promise<{
|
|
225
|
+
result?: {allowed?: boolean; withCredentials?: boolean}
|
|
226
|
+
}>
|
|
227
|
+
})
|
|
228
|
+
.then((body) => {
|
|
229
|
+
if (signal.aborted) return
|
|
230
|
+
// Check the credentialed case first: if the EventSource was about to
|
|
231
|
+
// send credentials but the project's CORS config doesn't permit them,
|
|
232
|
+
// the credentialed request would fail with a missing
|
|
233
|
+
// `access-control-allow-credentials` header. Surface this as a CORS
|
|
234
|
+
// rejection with `credentials: true` so the deep-link pre-selects the
|
|
235
|
+
// "Allow credentials" toggle.
|
|
236
|
+
if (requireCredentials && body?.result?.withCredentials === false) {
|
|
237
|
+
observer.error(new CorsOriginError({projectId, credentials: true}))
|
|
238
|
+
return
|
|
187
239
|
}
|
|
188
|
-
|
|
189
|
-
|
|
240
|
+
// Generic case: the server actively rejected this origin. Propagate
|
|
241
|
+
// `credentials: requireCredentials` so the deep-link still pre-selects
|
|
242
|
+
// credentials when the caller needed them.
|
|
243
|
+
if (body?.result?.allowed === false) {
|
|
244
|
+
observer.error(new CorsOriginError({projectId, credentials: requireCredentials}))
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
// Anything else (allowed + credentials satisfied, unrecognised body)
|
|
248
|
+
// is treated as "not a confirmed CORS rejection" - let the caller's
|
|
249
|
+
// original error surface instead.
|
|
250
|
+
observer.next()
|
|
251
|
+
observer.complete()
|
|
252
|
+
})
|
|
253
|
+
// Fetch/network/JSON parse errors are intentionally ignored - see the
|
|
254
|
+
// helper's docblock for the rationale. We still need to settle the
|
|
255
|
+
// observer so downstream `mergeMap(checkCors, ...)` consumers can proceed.
|
|
256
|
+
.catch(() => {
|
|
257
|
+
if (signal.aborted || observer.closed) return
|
|
258
|
+
observer.next()
|
|
259
|
+
observer.complete()
|
|
260
|
+
})
|
|
190
261
|
return () => controller.abort()
|
|
191
262
|
})
|
|
192
263
|
}
|
package/src/http/errors.ts
CHANGED
|
@@ -64,6 +64,7 @@ export class ClientError extends Error {
|
|
|
64
64
|
response: ErrorProps['response']
|
|
65
65
|
statusCode: ErrorProps['statusCode'] = 400
|
|
66
66
|
responseBody: ErrorProps['responseBody']
|
|
67
|
+
traceId: ErrorProps['traceId']
|
|
67
68
|
details: ErrorProps['details']
|
|
68
69
|
|
|
69
70
|
constructor(res: Any, context?: HttpContext) {
|
|
@@ -78,6 +79,7 @@ export class ServerError extends Error {
|
|
|
78
79
|
response: ErrorProps['response']
|
|
79
80
|
statusCode: ErrorProps['statusCode'] = 500
|
|
80
81
|
responseBody: ErrorProps['responseBody']
|
|
82
|
+
traceId: ErrorProps['traceId']
|
|
81
83
|
details: ErrorProps['details']
|
|
82
84
|
|
|
83
85
|
constructor(res: Any) {
|
|
@@ -93,13 +95,14 @@ function extractErrorProps(res: Any, context?: HttpContext): ErrorProps {
|
|
|
93
95
|
response: res,
|
|
94
96
|
statusCode: res.statusCode,
|
|
95
97
|
responseBody: stringifyBody(body, res),
|
|
98
|
+
traceId: extractTraceId(res),
|
|
96
99
|
message: '',
|
|
97
100
|
details: undefined as Any,
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
// Fall back early if we didn't get a JSON object returned as expected
|
|
101
104
|
if (!isRecord(body)) {
|
|
102
|
-
props.message = httpErrorMessage(res, body)
|
|
105
|
+
props.message = `${httpErrorMessage(res, body)}${formatTraceId(props.traceId)}`
|
|
103
106
|
return props
|
|
104
107
|
}
|
|
105
108
|
|
|
@@ -107,18 +110,18 @@ function extractErrorProps(res: Any, context?: HttpContext): ErrorProps {
|
|
|
107
110
|
|
|
108
111
|
// API/Boom style errors ({statusCode, error, message})
|
|
109
112
|
if (typeof error === 'string' && typeof body.message === 'string') {
|
|
110
|
-
props.message = `${error} - ${body.message}`
|
|
113
|
+
props.message = `${error} - ${body.message}${formatTraceId(props.traceId)}`
|
|
111
114
|
return props
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
// Content Lake errors with a `error` prop being an object
|
|
115
118
|
if (typeof error !== 'object' || error === null) {
|
|
116
119
|
if (typeof error === 'string') {
|
|
117
|
-
props.message = error
|
|
120
|
+
props.message = `${error}${formatTraceId(props.traceId)}`
|
|
118
121
|
} else if (typeof body.message === 'string') {
|
|
119
|
-
props.message = body.message
|
|
122
|
+
props.message = `${body.message}${formatTraceId(props.traceId)}`
|
|
120
123
|
} else {
|
|
121
|
-
props.message = httpErrorMessage(res, body)
|
|
124
|
+
props.message = `${httpErrorMessage(res, body)}${formatTraceId(props.traceId)}`
|
|
122
125
|
}
|
|
123
126
|
return props
|
|
124
127
|
}
|
|
@@ -134,7 +137,7 @@ function extractErrorProps(res: Any, context?: HttpContext): ErrorProps {
|
|
|
134
137
|
if (allItems.length > MAX_ITEMS_IN_ERROR_MESSAGE) {
|
|
135
138
|
itemsStr += `\n...and ${allItems.length - MAX_ITEMS_IN_ERROR_MESSAGE} more`
|
|
136
139
|
}
|
|
137
|
-
props.message = `${error.description}${itemsStr}`
|
|
140
|
+
props.message = `${error.description}${formatTraceId(props.traceId)}${itemsStr}`
|
|
138
141
|
props.details = body.error
|
|
139
142
|
return props
|
|
140
143
|
}
|
|
@@ -142,20 +145,20 @@ function extractErrorProps(res: Any, context?: HttpContext): ErrorProps {
|
|
|
142
145
|
// Query parse errors
|
|
143
146
|
if (isQueryParseError(error)) {
|
|
144
147
|
const tag = context?.options?.query?.tag
|
|
145
|
-
props.message = formatQueryParseError(error, tag)
|
|
148
|
+
props.message = formatQueryParseError(error, tag, props.traceId)
|
|
146
149
|
props.details = body.error
|
|
147
150
|
return props
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
if ('description' in error && typeof error.description === 'string') {
|
|
151
154
|
// Query/database errors ({error: {description, other, arb, props}})
|
|
152
|
-
props.message = error.description
|
|
155
|
+
props.message = `${error.description}${formatTraceId(props.traceId)}`
|
|
153
156
|
props.details = error
|
|
154
157
|
return props
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
// Other, more arbitrary errors
|
|
158
|
-
props.message = httpErrorMessage(res, body)
|
|
161
|
+
props.message = `${httpErrorMessage(res, body)}${formatTraceId(props.traceId)}`
|
|
159
162
|
return props
|
|
160
163
|
}
|
|
161
164
|
|
|
@@ -196,17 +199,22 @@ export function isQueryParseError(error: object): error is QueryParseError {
|
|
|
196
199
|
* @returns A formatted error message string.
|
|
197
200
|
* @public
|
|
198
201
|
*/
|
|
199
|
-
export function formatQueryParseError(
|
|
202
|
+
export function formatQueryParseError(
|
|
203
|
+
error: QueryParseError,
|
|
204
|
+
tag?: string | null,
|
|
205
|
+
traceId?: string,
|
|
206
|
+
) {
|
|
200
207
|
const {query, start, end, description} = error
|
|
208
|
+
const withTraceId = traceId ? `\n(traceId: ${traceId})` : ''
|
|
201
209
|
|
|
202
210
|
if (!query || typeof start === 'undefined') {
|
|
203
|
-
return `GROQ query parse error: ${description}`
|
|
211
|
+
return `GROQ query parse error: ${description}${withTraceId}`
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
const withTag = tag ? `\n\nTag: ${tag}` : ''
|
|
207
215
|
const framed = codeFrame(query, {start, end}, description)
|
|
208
216
|
|
|
209
|
-
return `GROQ query parse error:\n${framed}${withTag}`
|
|
217
|
+
return `GROQ query parse error:\n${framed}${withTag}${withTraceId}`
|
|
210
218
|
}
|
|
211
219
|
|
|
212
220
|
function httpErrorMessage(res: Any, body: unknown) {
|
|
@@ -215,35 +223,66 @@ function httpErrorMessage(res: Any, body: unknown) {
|
|
|
215
223
|
return `${res.method}-request to ${res.url} resulted in HTTP ${res.statusCode}${statusMessage}${details}`
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Extract the traceId from the traceparent header on the response.
|
|
228
|
+
*
|
|
229
|
+
* The traceparent is on the format [version]-[traceId]-[parentId]-[traceFlags], but
|
|
230
|
+
* when debugging end-user issues it's the traceId we need to be able to get hold of
|
|
231
|
+
* the relevant traces.
|
|
232
|
+
*
|
|
233
|
+
* @see https://www.w3.org/TR/trace-context/
|
|
234
|
+
* @returns The traceId for HTTP response
|
|
235
|
+
*/
|
|
236
|
+
function extractTraceId(res: Any): string | undefined {
|
|
237
|
+
const traceparent = res?.headers?.['traceparent']
|
|
238
|
+
if (!traceparent) return
|
|
239
|
+
|
|
240
|
+
return traceparent.split('-')[1]
|
|
241
|
+
}
|
|
242
|
+
|
|
218
243
|
function stringifyBody(body: Any, res: Any) {
|
|
219
244
|
const contentType = (res.headers['content-type'] || '').toLowerCase()
|
|
220
245
|
const isJson = contentType.indexOf('application/json') !== -1
|
|
221
246
|
return isJson ? JSON.stringify(body, null, 2) : body
|
|
222
247
|
}
|
|
223
248
|
|
|
249
|
+
function formatTraceId(traceId: string | undefined): string {
|
|
250
|
+
return traceId ? ` (traceId: ${traceId})` : ''
|
|
251
|
+
}
|
|
252
|
+
|
|
224
253
|
function sliceWithEllipsis(str: string, max: number) {
|
|
225
254
|
return str.length > max ? `${str.slice(0, max)}…` : str
|
|
226
255
|
}
|
|
227
256
|
|
|
228
257
|
/** @public */
|
|
229
258
|
export class CorsOriginError extends Error {
|
|
230
|
-
projectId
|
|
259
|
+
projectId?: string
|
|
231
260
|
addOriginUrl?: URL
|
|
232
261
|
|
|
233
|
-
constructor({projectId}: {projectId
|
|
262
|
+
constructor({projectId, credentials}: {projectId?: string; credentials?: boolean} = {}) {
|
|
234
263
|
super('CorsOriginError')
|
|
235
264
|
this.name = 'CorsOriginError'
|
|
236
265
|
this.projectId = projectId
|
|
237
266
|
|
|
238
|
-
|
|
239
|
-
|
|
267
|
+
// Only build a deep-link when we know which project the user needs to
|
|
268
|
+
// configure - without `projectId` the management URL can't actually route
|
|
269
|
+
// them anywhere useful.
|
|
270
|
+
if (projectId && typeof location !== 'undefined') {
|
|
271
|
+
const url = new URL(`https://sanity.io/manage/project/${projectId}/api`)
|
|
240
272
|
const {origin} = location
|
|
241
273
|
url.searchParams.set('cors', 'add')
|
|
242
274
|
url.searchParams.set('origin', origin)
|
|
275
|
+
if (credentials) {
|
|
276
|
+
// Pre-selects the "Allow credentials (token-based auth)" toggle in
|
|
277
|
+
// the Sanity management CORS form.
|
|
278
|
+
url.searchParams.set('credentials', '')
|
|
279
|
+
}
|
|
243
280
|
this.addOriginUrl = url
|
|
244
281
|
this.message = `The current origin is not allowed to connect to the Live Content API. Add it here: ${url}`
|
|
282
|
+
} else if (projectId) {
|
|
283
|
+
this.message = `The current origin is not allowed to connect to the Live Content API. Change your configuration here: https://sanity.io/manage/project/${projectId}/api`
|
|
245
284
|
} else {
|
|
246
|
-
this.message = `The current origin is not allowed to connect to the Live Content API
|
|
285
|
+
this.message = `The current origin is not allowed to connect to the Live Content API.`
|
|
247
286
|
}
|
|
248
287
|
}
|
|
249
288
|
}
|