@sanity/client 7.22.0 → 7.23.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/dist/index.browser.cjs +81 -29
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.d.cts +30 -2
- package/dist/index.browser.d.ts +30 -2
- package/dist/index.browser.js +82 -30
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +82 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -2
- package/dist/index.d.ts +30 -2
- package/dist/index.js +83 -31
- package/dist/index.js.map +1 -1
- package/dist/stega.browser.d.cts +30 -2
- package/dist/stega.browser.d.ts +30 -2
- package/dist/stega.d.cts +30 -2
- package/dist/stega.d.ts +30 -2
- package/package.json +2 -2
- package/src/SanityClient.ts +28 -0
- package/src/data/dataMethods.ts +53 -2
- package/src/data/live.ts +95 -24
- package/src/http/errors.ts +15 -5
- package/umd/sanityClient.js +110 -31
- package/umd/sanityClient.min.js +2 -2
package/dist/stega.browser.d.cts
CHANGED
|
@@ -1134,9 +1134,9 @@ declare interface ContentSourceMapValueMapping_2 {
|
|
|
1134
1134
|
|
|
1135
1135
|
/** @public */
|
|
1136
1136
|
export declare class CorsOriginError extends Error {
|
|
1137
|
-
projectId
|
|
1137
|
+
projectId?: string
|
|
1138
1138
|
addOriginUrl?: URL
|
|
1139
|
-
constructor({projectId}
|
|
1139
|
+
constructor({projectId, credentials}?: {projectId?: string; credentials?: boolean})
|
|
1140
1140
|
}
|
|
1141
1141
|
|
|
1142
1142
|
/**
|
|
@@ -3363,6 +3363,20 @@ export declare class ObservableSanityClient {
|
|
|
3363
3363
|
tag?: string
|
|
3364
3364
|
},
|
|
3365
3365
|
): Observable<(SanityDocument<R> | null)[]>
|
|
3366
|
+
/**
|
|
3367
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
3368
|
+
* Returns a set of the IDs that exist.
|
|
3369
|
+
*
|
|
3370
|
+
* @param ids - Document IDs to check
|
|
3371
|
+
* @param options - Request options
|
|
3372
|
+
*/
|
|
3373
|
+
documentsExists(
|
|
3374
|
+
ids: string[],
|
|
3375
|
+
options?: {
|
|
3376
|
+
signal?: AbortSignal
|
|
3377
|
+
tag?: string
|
|
3378
|
+
},
|
|
3379
|
+
): Observable<Set<string>>
|
|
3366
3380
|
/**
|
|
3367
3381
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
3368
3382
|
* Returns an observable that resolves to the created document.
|
|
@@ -5286,6 +5300,20 @@ export declare class SanityClient {
|
|
|
5286
5300
|
tag?: string
|
|
5287
5301
|
},
|
|
5288
5302
|
): Promise<(SanityDocument<R> | null)[]>
|
|
5303
|
+
/**
|
|
5304
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
5305
|
+
* Returns a set of the IDs that exist.
|
|
5306
|
+
*
|
|
5307
|
+
* @param ids - Document IDs to check
|
|
5308
|
+
* @param options - Request options
|
|
5309
|
+
*/
|
|
5310
|
+
documentsExists(
|
|
5311
|
+
ids: string[],
|
|
5312
|
+
options?: {
|
|
5313
|
+
signal?: AbortSignal
|
|
5314
|
+
tag?: string
|
|
5315
|
+
},
|
|
5316
|
+
): Promise<Set<string>>
|
|
5289
5317
|
/**
|
|
5290
5318
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
5291
5319
|
* Returns a promise that resolves to the created document.
|
package/dist/stega.browser.d.ts
CHANGED
|
@@ -1134,9 +1134,9 @@ declare interface ContentSourceMapValueMapping_2 {
|
|
|
1134
1134
|
|
|
1135
1135
|
/** @public */
|
|
1136
1136
|
export declare class CorsOriginError extends Error {
|
|
1137
|
-
projectId
|
|
1137
|
+
projectId?: string
|
|
1138
1138
|
addOriginUrl?: URL
|
|
1139
|
-
constructor({projectId}
|
|
1139
|
+
constructor({projectId, credentials}?: {projectId?: string; credentials?: boolean})
|
|
1140
1140
|
}
|
|
1141
1141
|
|
|
1142
1142
|
/**
|
|
@@ -3363,6 +3363,20 @@ export declare class ObservableSanityClient {
|
|
|
3363
3363
|
tag?: string
|
|
3364
3364
|
},
|
|
3365
3365
|
): Observable<(SanityDocument<R> | null)[]>
|
|
3366
|
+
/**
|
|
3367
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
3368
|
+
* Returns a set of the IDs that exist.
|
|
3369
|
+
*
|
|
3370
|
+
* @param ids - Document IDs to check
|
|
3371
|
+
* @param options - Request options
|
|
3372
|
+
*/
|
|
3373
|
+
documentsExists(
|
|
3374
|
+
ids: string[],
|
|
3375
|
+
options?: {
|
|
3376
|
+
signal?: AbortSignal
|
|
3377
|
+
tag?: string
|
|
3378
|
+
},
|
|
3379
|
+
): Observable<Set<string>>
|
|
3366
3380
|
/**
|
|
3367
3381
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
3368
3382
|
* Returns an observable that resolves to the created document.
|
|
@@ -5286,6 +5300,20 @@ export declare class SanityClient {
|
|
|
5286
5300
|
tag?: string
|
|
5287
5301
|
},
|
|
5288
5302
|
): Promise<(SanityDocument<R> | null)[]>
|
|
5303
|
+
/**
|
|
5304
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
5305
|
+
* Returns a set of the IDs that exist.
|
|
5306
|
+
*
|
|
5307
|
+
* @param ids - Document IDs to check
|
|
5308
|
+
* @param options - Request options
|
|
5309
|
+
*/
|
|
5310
|
+
documentsExists(
|
|
5311
|
+
ids: string[],
|
|
5312
|
+
options?: {
|
|
5313
|
+
signal?: AbortSignal
|
|
5314
|
+
tag?: string
|
|
5315
|
+
},
|
|
5316
|
+
): Promise<Set<string>>
|
|
5289
5317
|
/**
|
|
5290
5318
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
5291
5319
|
* Returns a promise that resolves to the created document.
|
package/dist/stega.d.cts
CHANGED
|
@@ -1134,9 +1134,9 @@ declare interface ContentSourceMapValueMapping_2 {
|
|
|
1134
1134
|
|
|
1135
1135
|
/** @public */
|
|
1136
1136
|
export declare class CorsOriginError extends Error {
|
|
1137
|
-
projectId
|
|
1137
|
+
projectId?: string
|
|
1138
1138
|
addOriginUrl?: URL
|
|
1139
|
-
constructor({projectId}
|
|
1139
|
+
constructor({projectId, credentials}?: {projectId?: string; credentials?: boolean})
|
|
1140
1140
|
}
|
|
1141
1141
|
|
|
1142
1142
|
/**
|
|
@@ -3363,6 +3363,20 @@ export declare class ObservableSanityClient {
|
|
|
3363
3363
|
tag?: string
|
|
3364
3364
|
},
|
|
3365
3365
|
): Observable<(SanityDocument<R> | null)[]>
|
|
3366
|
+
/**
|
|
3367
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
3368
|
+
* Returns a set of the IDs that exist.
|
|
3369
|
+
*
|
|
3370
|
+
* @param ids - Document IDs to check
|
|
3371
|
+
* @param options - Request options
|
|
3372
|
+
*/
|
|
3373
|
+
documentsExists(
|
|
3374
|
+
ids: string[],
|
|
3375
|
+
options?: {
|
|
3376
|
+
signal?: AbortSignal
|
|
3377
|
+
tag?: string
|
|
3378
|
+
},
|
|
3379
|
+
): Observable<Set<string>>
|
|
3366
3380
|
/**
|
|
3367
3381
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
3368
3382
|
* Returns an observable that resolves to the created document.
|
|
@@ -5286,6 +5300,20 @@ export declare class SanityClient {
|
|
|
5286
5300
|
tag?: string
|
|
5287
5301
|
},
|
|
5288
5302
|
): Promise<(SanityDocument<R> | null)[]>
|
|
5303
|
+
/**
|
|
5304
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
5305
|
+
* Returns a set of the IDs that exist.
|
|
5306
|
+
*
|
|
5307
|
+
* @param ids - Document IDs to check
|
|
5308
|
+
* @param options - Request options
|
|
5309
|
+
*/
|
|
5310
|
+
documentsExists(
|
|
5311
|
+
ids: string[],
|
|
5312
|
+
options?: {
|
|
5313
|
+
signal?: AbortSignal
|
|
5314
|
+
tag?: string
|
|
5315
|
+
},
|
|
5316
|
+
): Promise<Set<string>>
|
|
5289
5317
|
/**
|
|
5290
5318
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
5291
5319
|
* Returns a promise that resolves to the created document.
|
package/dist/stega.d.ts
CHANGED
|
@@ -1134,9 +1134,9 @@ declare interface ContentSourceMapValueMapping_2 {
|
|
|
1134
1134
|
|
|
1135
1135
|
/** @public */
|
|
1136
1136
|
export declare class CorsOriginError extends Error {
|
|
1137
|
-
projectId
|
|
1137
|
+
projectId?: string
|
|
1138
1138
|
addOriginUrl?: URL
|
|
1139
|
-
constructor({projectId}
|
|
1139
|
+
constructor({projectId, credentials}?: {projectId?: string; credentials?: boolean})
|
|
1140
1140
|
}
|
|
1141
1141
|
|
|
1142
1142
|
/**
|
|
@@ -3363,6 +3363,20 @@ export declare class ObservableSanityClient {
|
|
|
3363
3363
|
tag?: string
|
|
3364
3364
|
},
|
|
3365
3365
|
): Observable<(SanityDocument<R> | null)[]>
|
|
3366
|
+
/**
|
|
3367
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
3368
|
+
* Returns a set of the IDs that exist.
|
|
3369
|
+
*
|
|
3370
|
+
* @param ids - Document IDs to check
|
|
3371
|
+
* @param options - Request options
|
|
3372
|
+
*/
|
|
3373
|
+
documentsExists(
|
|
3374
|
+
ids: string[],
|
|
3375
|
+
options?: {
|
|
3376
|
+
signal?: AbortSignal
|
|
3377
|
+
tag?: string
|
|
3378
|
+
},
|
|
3379
|
+
): Observable<Set<string>>
|
|
3366
3380
|
/**
|
|
3367
3381
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
3368
3382
|
* Returns an observable that resolves to the created document.
|
|
@@ -5286,6 +5300,20 @@ export declare class SanityClient {
|
|
|
5286
5300
|
tag?: string
|
|
5287
5301
|
},
|
|
5288
5302
|
): Promise<(SanityDocument<R> | null)[]>
|
|
5303
|
+
/**
|
|
5304
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
5305
|
+
* Returns a set of the IDs that exist.
|
|
5306
|
+
*
|
|
5307
|
+
* @param ids - Document IDs to check
|
|
5308
|
+
* @param options - Request options
|
|
5309
|
+
*/
|
|
5310
|
+
documentsExists(
|
|
5311
|
+
ids: string[],
|
|
5312
|
+
options?: {
|
|
5313
|
+
signal?: AbortSignal
|
|
5314
|
+
tag?: string
|
|
5315
|
+
},
|
|
5316
|
+
): Promise<Set<string>>
|
|
5289
5317
|
/**
|
|
5290
5318
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
5291
5319
|
* Returns a promise that resolves to the created document.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/client",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.23.0",
|
|
4
4
|
"description": "Client for retrieving, creating and patching data from Sanity.io",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
},
|
|
131
131
|
"dependencies": {
|
|
132
132
|
"@sanity/eventsource": "^5.0.2",
|
|
133
|
-
"get-it": "^8.
|
|
133
|
+
"get-it": "^8.8.0",
|
|
134
134
|
"nanoid": "^3.3.11",
|
|
135
135
|
"rxjs": "^7.0.0"
|
|
136
136
|
},
|
package/src/SanityClient.ts
CHANGED
|
@@ -324,6 +324,20 @@ export class ObservableSanityClient {
|
|
|
324
324
|
return dataMethods._getDocuments<R>(this, this.#httpRequest, ids, options)
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
329
|
+
* Returns a set of the IDs that exist.
|
|
330
|
+
*
|
|
331
|
+
* @param ids - Document IDs to check
|
|
332
|
+
* @param options - Request options
|
|
333
|
+
*/
|
|
334
|
+
documentsExists(
|
|
335
|
+
ids: string[],
|
|
336
|
+
options?: {signal?: AbortSignal; tag?: string},
|
|
337
|
+
): Observable<Set<string>> {
|
|
338
|
+
return dataMethods._documentsExists(this, this.#httpRequest, ids, options)
|
|
339
|
+
}
|
|
340
|
+
|
|
327
341
|
/**
|
|
328
342
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
329
343
|
* Returns an observable that resolves to the created document.
|
|
@@ -1390,6 +1404,20 @@ export class SanityClient {
|
|
|
1390
1404
|
return lastValueFrom(dataMethods._getDocuments<R>(this, this.#httpRequest, ids, options))
|
|
1391
1405
|
}
|
|
1392
1406
|
|
|
1407
|
+
/**
|
|
1408
|
+
* Convenient and bandwidth efficient method of checking wether a set of document IDs exists.
|
|
1409
|
+
* Returns a set of the IDs that exist.
|
|
1410
|
+
*
|
|
1411
|
+
* @param ids - Document IDs to check
|
|
1412
|
+
* @param options - Request options
|
|
1413
|
+
*/
|
|
1414
|
+
documentsExists(
|
|
1415
|
+
ids: string[],
|
|
1416
|
+
options?: {signal?: AbortSignal; tag?: string},
|
|
1417
|
+
): Promise<Set<string>> {
|
|
1418
|
+
return lastValueFrom(dataMethods._documentsExists(this, this.#httpRequest, ids, options))
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1393
1421
|
/**
|
|
1394
1422
|
* Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database.
|
|
1395
1423
|
* Returns a promise that resolves to the created document.
|
package/src/data/dataMethods.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {getDraftId, getVersionFromId, getVersionId, isDraftId} from '@sanity/client/csm'
|
|
2
|
-
import {from, type MonoTypeOperatorFunction, Observable} from 'rxjs'
|
|
3
|
-
import {combineLatestWith, filter, map} from 'rxjs/operators'
|
|
2
|
+
import {from, type MonoTypeOperatorFunction, Observable, of} from 'rxjs'
|
|
3
|
+
import {combineLatestWith, concatMap, filter, map, reduce} from 'rxjs/operators'
|
|
4
4
|
|
|
5
5
|
import {validateApiPerspective} from '../config'
|
|
6
6
|
import {requestOptions} from '../http/requestOptions'
|
|
@@ -231,6 +231,51 @@ export function _getDocuments<R extends Record<string, Any>>(
|
|
|
231
231
|
)
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
const DOCUMENTS_EXISTS_BATCH_SIZE = 100
|
|
235
|
+
|
|
236
|
+
/** @internal */
|
|
237
|
+
export function _documentsExists(
|
|
238
|
+
client: Client,
|
|
239
|
+
httpRequest: HttpRequest,
|
|
240
|
+
ids: string[],
|
|
241
|
+
opts: {signal?: AbortSignal; tag?: string} = {},
|
|
242
|
+
): Observable<Set<string>> {
|
|
243
|
+
if (ids.length === 0) {
|
|
244
|
+
return of(new Set<string>())
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const batches: string[][] = []
|
|
248
|
+
for (let i = 0; i < ids.length; i += DOCUMENTS_EXISTS_BATCH_SIZE) {
|
|
249
|
+
batches.push(ids.slice(i, i + DOCUMENTS_EXISTS_BATCH_SIZE))
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const fetchBatch = (batchIds: string[]) =>
|
|
253
|
+
_requestObservable<Any>(client, httpRequest, {
|
|
254
|
+
uri: _getDataUrl(client, 'doc', batchIds.map(encodeURIComponent).join(',')),
|
|
255
|
+
tag: opts.tag,
|
|
256
|
+
signal: opts.signal,
|
|
257
|
+
query: {excludeContent: true},
|
|
258
|
+
}).pipe(
|
|
259
|
+
filter(isResponse),
|
|
260
|
+
map((event: Any) => {
|
|
261
|
+
const missing = new Set<string>()
|
|
262
|
+
for (const omitted of event.body.omitted || []) {
|
|
263
|
+
if (omitted.reason !== 'existence') continue
|
|
264
|
+
missing.add(omitted.id)
|
|
265
|
+
}
|
|
266
|
+
return new Set(batchIds.filter((id) => !missing.has(id)))
|
|
267
|
+
}),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return from(batches).pipe(
|
|
271
|
+
concatMap(fetchBatch),
|
|
272
|
+
reduce((acc, set) => {
|
|
273
|
+
for (const id of set) acc.add(id)
|
|
274
|
+
return acc
|
|
275
|
+
}, new Set<string>()),
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
234
279
|
/** @internal */
|
|
235
280
|
export function _getReleaseDocuments<R extends Record<string, Any>>(
|
|
236
281
|
client: ObservableSanityClient | SanityClient,
|
|
@@ -598,6 +643,11 @@ export function _requestObservable<R>(
|
|
|
598
643
|
httpRequest: HttpRequest,
|
|
599
644
|
options: RequestObservableOptions,
|
|
600
645
|
): Observable<HttpRequestEvent<R>> {
|
|
646
|
+
// Capture the call-site error here, where the caller's code is still
|
|
647
|
+
// on the synchronous call stack, and pass it through to get-it so errors
|
|
648
|
+
// include the originating call site rather than just internal plumbing.
|
|
649
|
+
const callSiteStack = new Error()
|
|
650
|
+
|
|
601
651
|
const uri = options.url || (options.uri as string)
|
|
602
652
|
const config = client.config()
|
|
603
653
|
|
|
@@ -667,6 +717,7 @@ export function _requestObservable<R>(
|
|
|
667
717
|
config,
|
|
668
718
|
Object.assign({}, options, {
|
|
669
719
|
url: _getUrl(client, uri, useCdn),
|
|
720
|
+
callSiteStack,
|
|
670
721
|
}),
|
|
671
722
|
) as RequestOptions
|
|
672
723
|
|
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
|
@@ -256,23 +256,33 @@ function sliceWithEllipsis(str: string, max: number) {
|
|
|
256
256
|
|
|
257
257
|
/** @public */
|
|
258
258
|
export class CorsOriginError extends Error {
|
|
259
|
-
projectId
|
|
259
|
+
projectId?: string
|
|
260
260
|
addOriginUrl?: URL
|
|
261
261
|
|
|
262
|
-
constructor({projectId}: {projectId
|
|
262
|
+
constructor({projectId, credentials}: {projectId?: string; credentials?: boolean} = {}) {
|
|
263
263
|
super('CorsOriginError')
|
|
264
264
|
this.name = 'CorsOriginError'
|
|
265
265
|
this.projectId = projectId
|
|
266
266
|
|
|
267
|
-
|
|
268
|
-
|
|
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`)
|
|
269
272
|
const {origin} = location
|
|
270
273
|
url.searchParams.set('cors', 'add')
|
|
271
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
|
+
}
|
|
272
280
|
this.addOriginUrl = url
|
|
273
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`
|
|
274
284
|
} else {
|
|
275
|
-
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.`
|
|
276
286
|
}
|
|
277
287
|
}
|
|
278
288
|
}
|