@capacms/sdk 1.0.0-next.6 → 1.0.0-next.8
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 +55 -1
- package/README.md +192 -44
- package/dist/config.d.ts +0 -35
- package/dist/config.js +47 -1
- package/dist/next/client.d.ts +9 -8
- package/dist/next/client.js +19 -4
- package/dist/next/field-names.js +1 -1
- package/dist/next/graphql/introspection.d.ts +1 -1
- package/dist/next/graphql/introspection.js +1 -1
- package/dist/next/graphql/plan.js +2 -2
- package/dist/next/key-family.d.ts +9 -12
- package/dist/next/key-family.js +11 -19
- package/dist/nextjs/index.d.ts +182 -33
- package/dist/nextjs/index.js +244 -29
- package/package.json +2 -6
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
* key-family.ts — which keys `@capacms/sdk/next` takes, and for what.
|
|
3
3
|
*
|
|
4
4
|
* A `cap_` key is the `/api/` key: scoped, hashed at rest, and the only kind
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* the draft clients take to read drafts. Every other key is legacy, which is
|
|
6
|
+
* the API's rule (`apps/api/src/api-next/auth/key-format.ts`): the `pk_` and
|
|
7
|
+
* `sk_` keys, and the unprefixed keys older tenants were minted, all reach
|
|
8
|
+
* `/api/` with the grants their permission gives. So every read call takes
|
|
9
|
+
* them, `preview()` included: `GET /api/preview` asks for `instance:read`,
|
|
10
|
+
* which every legacy permission grants, and refuses a token minted for another
|
|
11
|
+
* tenant. The first client built with one warns once per process, naming the
|
|
12
|
+
* key to mint.
|
|
11
13
|
*
|
|
12
14
|
* The `cap_` prefix is the whole rule, case-sensitive, as on the API:
|
|
13
15
|
* `CAP_live_…` is not a key Capa mints, so it is looked up as a legacy key.
|
|
14
|
-
* `@
|
|
16
|
+
* `@capacms/mcp` applies the same rule, and both packages are tested against one
|
|
15
17
|
* vector file, `test/fixtures/key-family.json`.
|
|
16
18
|
*/
|
|
17
19
|
export type KeyFamily = "cap" | "legacy";
|
|
@@ -19,11 +21,6 @@ export type KeyFamily = "cap" | "legacy";
|
|
|
19
21
|
export declare function keyFamily(apiKey: unknown): KeyFamily | null;
|
|
20
22
|
/** Warn about a legacy key once per process: a site builds a client per request. */
|
|
21
23
|
export declare function warnLegacyKeyOnce(apiKey: string): void;
|
|
22
|
-
/**
|
|
23
|
-
* Throws for a legacy key: verifying a preview token takes a `cap_` key. A
|
|
24
|
-
* value that is no key at all is `resolveNextConfig`'s to refuse, by name.
|
|
25
|
-
*/
|
|
26
|
-
export declare function requirePreviewKey(apiKey: string): void;
|
|
27
24
|
/**
|
|
28
25
|
* Throws for a legacy key: a draft read takes a `cap_` key. `holder` names
|
|
29
26
|
* where the key came from (`CAPA_DRAFT_KEY`, `the draft config`), so the
|
package/dist/next/key-family.js
CHANGED
|
@@ -3,22 +3,23 @@
|
|
|
3
3
|
* key-family.ts — which keys `@capacms/sdk/next` takes, and for what.
|
|
4
4
|
*
|
|
5
5
|
* A `cap_` key is the `/api/` key: scoped, hashed at rest, and the only kind
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* the draft clients take to read drafts. Every other key is legacy, which is
|
|
7
|
+
* the API's rule (`apps/api/src/api-next/auth/key-format.ts`): the `pk_` and
|
|
8
|
+
* `sk_` keys, and the unprefixed keys older tenants were minted, all reach
|
|
9
|
+
* `/api/` with the grants their permission gives. So every read call takes
|
|
10
|
+
* them, `preview()` included: `GET /api/preview` asks for `instance:read`,
|
|
11
|
+
* which every legacy permission grants, and refuses a token minted for another
|
|
12
|
+
* tenant. The first client built with one warns once per process, naming the
|
|
13
|
+
* key to mint.
|
|
12
14
|
*
|
|
13
15
|
* The `cap_` prefix is the whole rule, case-sensitive, as on the API:
|
|
14
16
|
* `CAP_live_…` is not a key Capa mints, so it is looked up as a legacy key.
|
|
15
|
-
* `@
|
|
17
|
+
* `@capacms/mcp` applies the same rule, and both packages are tested against one
|
|
16
18
|
* vector file, `test/fixtures/key-family.json`.
|
|
17
19
|
*/
|
|
18
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
21
|
exports.keyFamily = keyFamily;
|
|
20
22
|
exports.warnLegacyKeyOnce = warnLegacyKeyOnce;
|
|
21
|
-
exports.requirePreviewKey = requirePreviewKey;
|
|
22
23
|
exports.requireDraftKey = requireDraftKey;
|
|
23
24
|
exports.__resetKeyWarningForTests = __resetKeyWarningForTests;
|
|
24
25
|
const LEGACY_PREFIX = /^(pk|sk)_/;
|
|
@@ -46,17 +47,8 @@ function warnLegacyKeyOnce(apiKey) {
|
|
|
46
47
|
if (warned)
|
|
47
48
|
return;
|
|
48
49
|
warned = true;
|
|
49
|
-
console.warn(`@capacms/sdk/next: this client reads with a ${legacyKind(apiKey)}. Reads work as they do with a cap_ key. ` +
|
|
50
|
-
`Draft
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Throws for a legacy key: verifying a preview token takes a `cap_` key. A
|
|
54
|
-
* value that is no key at all is `resolveNextConfig`'s to refuse, by name.
|
|
55
|
-
*/
|
|
56
|
-
function requirePreviewKey(apiKey) {
|
|
57
|
-
if (!isLegacy(apiKey))
|
|
58
|
-
return;
|
|
59
|
-
throw new TypeError(`@capacms/sdk/next: preview needs a cap_ key, and this client holds a ${legacyKind(apiKey)}. Mint a cap_ key ${MINT}`);
|
|
50
|
+
console.warn(`@capacms/sdk/next: this client reads with a ${legacyKind(apiKey)}. Reads and preview links work as they do with a cap_ key. ` +
|
|
51
|
+
`Draft reads need a cap_ key: mint one ${MINT}`);
|
|
60
52
|
}
|
|
61
53
|
/**
|
|
62
54
|
* Throws for a legacy key: a draft read takes a `cap_` key. `holder` names
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -13,11 +13,12 @@ export declare function withCache(fetchImpl: typeof fetch, options: CacheOptions
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function modelTag(namespace: string): string;
|
|
15
15
|
/**
|
|
16
|
-
* The tag `graphql()`
|
|
17
|
-
* `revalidateFromWebhook` revalidates on every content
|
|
18
|
-
* never stale after a publish, at the price of
|
|
19
|
-
* Name the models it reads with
|
|
20
|
-
* when one of those changes.
|
|
16
|
+
* The tag `graphql()` keeps a read under when it gives a `revalidate` and no
|
|
17
|
+
* `tags`, and that `revalidateFromWebhook` revalidates on every content
|
|
18
|
+
* change: such a page is never stale after a publish, at the price of
|
|
19
|
+
* refreshing on any publish. Name the models it reads with
|
|
20
|
+
* `tagsFor({ namespace })` to refresh it only when one of those changes. A
|
|
21
|
+
* read with neither `tags` nor `revalidate` is not kept at all.
|
|
21
22
|
*/
|
|
22
23
|
export declare const GRAPHQL_TAG = "capa:graphql";
|
|
23
24
|
/**
|
|
@@ -110,6 +111,10 @@ export type { PreviewClaim };
|
|
|
110
111
|
* clickable. It never switches the site to draft data.
|
|
111
112
|
*/
|
|
112
113
|
export declare const EDIT_PARAM = "capa-edit";
|
|
114
|
+
/** The query parameter a Capa preview link carries: `?capa-preview=<token>`. */
|
|
115
|
+
export declare const PREVIEW_PARAM = "capa-preview";
|
|
116
|
+
/** The query parameter of the editor's Published view: `?capa-view=published`. */
|
|
117
|
+
export declare const VIEW_PARAM = "capa-view";
|
|
113
118
|
/**
|
|
114
119
|
* The request header `resolveEditRequest` sets once a `capa-edit` token has
|
|
115
120
|
* been verified, for `editMode()` to read. Any copy a browser sent is removed
|
|
@@ -156,6 +161,8 @@ export interface EditRequest {
|
|
|
156
161
|
headers: Headers;
|
|
157
162
|
/** `EDIT_CACHE_CONTROL` in edit mode, otherwise null. Set it on the response. */
|
|
158
163
|
cacheControl: string | null;
|
|
164
|
+
/** `DRAFT_ROBOTS_TAG` in edit mode, otherwise null. Set it on the response as `X-Robots-Tag`. */
|
|
165
|
+
robotsTag: string | null;
|
|
159
166
|
}
|
|
160
167
|
/**
|
|
161
168
|
* The middleware half of edit mode.
|
|
@@ -164,6 +171,7 @@ export interface EditRequest {
|
|
|
164
171
|
* const edit = await resolveEditRequest(request, publishedClient());
|
|
165
172
|
* const response = NextResponse.next({ request: { headers: edit.headers } });
|
|
166
173
|
* if (edit.cacheControl) response.headers.set("Cache-Control", edit.cacheControl);
|
|
174
|
+
* if (edit.robotsTag) response.headers.set("X-Robots-Tag", edit.robotsTag);
|
|
167
175
|
* return response;
|
|
168
176
|
* }
|
|
169
177
|
*
|
|
@@ -219,10 +227,12 @@ export declare function getPublishedClient<Q = UntypedQuery>(overrides?: Partial
|
|
|
219
227
|
* `CapaQuery` (from `capa-codegen --graphql`) types the builder, as with
|
|
220
228
|
* `createClient`; leave it out for an untyped client. `config` wins over env.
|
|
221
229
|
*
|
|
222
|
-
* Its
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
230
|
+
* Its REST reads are sent as `createClient` sends them, which Next does not
|
|
231
|
+
* keep. Its GraphQL reads, a document or the builder, are sent the same way
|
|
232
|
+
* unless a call gives `tags` or `revalidate`, so a publish shows up on both
|
|
233
|
+
* alike. Given either, a read is kept in Next's data cache exactly as
|
|
234
|
+
* `graphql()` keeps it: a published read with no errors, never a draft or an
|
|
235
|
+
* edit-mode page.
|
|
226
236
|
*/
|
|
227
237
|
export declare function getCapaClient<Q = UntypedQuery>(input: {
|
|
228
238
|
draftMode: DraftModeFn;
|
|
@@ -234,14 +244,16 @@ export declare function getCapaClient<Q = UntypedQuery>(input: {
|
|
|
234
244
|
/** Where a GraphQL read in Next keeps its answer: Next's data cache, as `graphql()` and `getCapaClient` keep it. */
|
|
235
245
|
export interface NextCacheOptions extends GraphQLCallOptions {
|
|
236
246
|
/**
|
|
237
|
-
* Next.js cache tags
|
|
238
|
-
* with the list `capa-codegen --graphql` writes beside
|
|
239
|
-
* it with every model the query reads, which
|
|
240
|
-
* revalidates on a publish.
|
|
241
|
-
* which every publish
|
|
247
|
+
* Next.js cache tags to keep this read under. `tagsFor({ namespace:
|
|
248
|
+
* <Name>Models })`, with the list `capa-codegen --graphql` writes beside
|
|
249
|
+
* each document, tags it with every model the query reads, which
|
|
250
|
+
* `revalidateFromWebhook` revalidates on a publish. Given a `revalidate`
|
|
251
|
+
* and no tags, the read is kept under `GRAPHQL_TAG`, which every publish
|
|
252
|
+
* revalidates. Given neither, the read is not kept: it is sent as a REST
|
|
253
|
+
* read is, on every render.
|
|
242
254
|
*/
|
|
243
255
|
tags?: string[];
|
|
244
|
-
/** Seconds to cache,
|
|
256
|
+
/** Seconds to cache, `false` to cache until a tag is revalidated, or 0 not to cache. */
|
|
245
257
|
revalidate?: number | false;
|
|
246
258
|
}
|
|
247
259
|
export interface NextGraphQLOptions extends NextCacheOptions {
|
|
@@ -261,9 +273,10 @@ export interface NextGraphQLOptions extends NextCacheOptions {
|
|
|
261
273
|
config?: Partial<CapaNextConfig>;
|
|
262
274
|
/**
|
|
263
275
|
* Next's `unstable_cache`, from `next/cache`. Leave it out: `graphql()`
|
|
264
|
-
* loads Next's own. A published read
|
|
265
|
-
* it answered with no `errors`, under `tags`
|
|
266
|
-
* with no `revalidate` until one of its tags is
|
|
276
|
+
* loads Next's own. A published read given `tags` or a `revalidate` is kept
|
|
277
|
+
* in Next's data cache only when it answered with no `errors`, under `tags`
|
|
278
|
+
* for `revalidate` seconds, or with no `revalidate` until one of its tags is
|
|
279
|
+
* revalidated. Next's fetch
|
|
267
280
|
* cache is not used for it: that cache keeps any 200, and a GraphQL error
|
|
268
281
|
* is a 200 (a root field that timed out), and in Next 15 it keeps nothing
|
|
269
282
|
* without a `revalidate`, tags or not. Pass it only to supply another.
|
|
@@ -293,12 +306,15 @@ export type UnstableCache = <T extends (...args: any[]) => Promise<any>>(cb: T,
|
|
|
293
306
|
* variables are typed from it, as they are for a `<Name>Document` it writes,
|
|
294
307
|
* and `LatestModels` lists the models it reads.
|
|
295
308
|
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
309
|
+
* With no `tags` and no `revalidate` a read is sent as the REST reads are,
|
|
310
|
+
* and Next keeps nothing: the page shows a publish on its next render, as a
|
|
311
|
+
* page reading by REST does. Given either, a published read is kept in Next's
|
|
312
|
+
* data cache (`unstable_cache`) when it answered with no errors: under `tags`,
|
|
313
|
+
* for `revalidate` seconds, or with no `revalidate` until
|
|
314
|
+
* `revalidateFromWebhook` revalidates one of its tags on a publish. It goes
|
|
315
|
+
* as a GET, which the CDN and the API also cache for the published key; a
|
|
316
|
+
* document too long for a GET URL goes as a POST, which only Next's data
|
|
317
|
+
* cache keeps. Under draft mode the read uses `CAPA_DRAFT_KEY`, a
|
|
302
318
|
* `cap_` key, and bypasses the cache (the API answers a development key's GET
|
|
303
319
|
* `no-store`), the same split `getCapaClient` makes for REST, and in edit mode
|
|
304
320
|
* each entry that selects `id` and `model` is marked for `capaAttrs`, uncached.
|
|
@@ -314,29 +330,149 @@ export declare function graphql<D extends keyof CapaDocuments>(document: D, ...r
|
|
|
314
330
|
export declare function graphql<TData = Record<string, unknown>, TVariables = Record<string, unknown>, D extends string = string>(document: NotARecordedDocument<D, TypedDocument<TData, TVariables> | D>, ...rest: VariablesThenOptions<TVariables, NextGraphQLOptions>): Promise<GraphQLResult<TData>>;
|
|
315
331
|
/** Only a path on this site: never `//elsewhere.example` or a full URL. */
|
|
316
332
|
export declare function safeSitePath(value: string | null | undefined): string;
|
|
333
|
+
/** The Capa admin's origin: what frames a draft in the editor, unless a site names another. */
|
|
334
|
+
export declare const CAPA_ADMIN_ORIGIN = "https://app.capacms.com";
|
|
335
|
+
/** `X-Robots-Tag` on every draft and edit-mode response: a draft is never indexed, nor its links followed. */
|
|
336
|
+
export declare const DRAFT_ROBOTS_TAG = "noindex, nofollow";
|
|
337
|
+
/** How long the draft cookie lasts, in seconds: one hour, as a preview token does. */
|
|
338
|
+
export declare const DRAFT_COOKIE_MAX_AGE = 3600;
|
|
339
|
+
/**
|
|
340
|
+
* `frame-ancestors 'self' <origins>`, the CSP directive that lets the Capa
|
|
341
|
+
* editor frame a draft and nothing else frame it. Each origin is a scheme and
|
|
342
|
+
* a host, with a port when it has one, and is written as the URL parser
|
|
343
|
+
* normalises it, so no `;` or quote can reach the policy. Anything else, a
|
|
344
|
+
* path or a query included, throws a `TypeError`.
|
|
345
|
+
*/
|
|
346
|
+
export declare function frameAncestors(adminOrigins?: readonly string[]): string;
|
|
347
|
+
/**
|
|
348
|
+
* The headers every draft response carries: `X-Robots-Tag: noindex, nofollow`,
|
|
349
|
+
* and `Content-Security-Policy: frame-ancestors 'self' https://app.capacms.com`
|
|
350
|
+
* (or the `adminOrigins` given). Throws for an origin that is not one.
|
|
351
|
+
*/
|
|
352
|
+
export declare function draftHeaders(options?: {
|
|
353
|
+
adminOrigins?: readonly string[];
|
|
354
|
+
}): Record<string, string>;
|
|
355
|
+
/** One rule of `next.config`'s `headers()`, in the shape Next takes. */
|
|
356
|
+
export interface NextHeaderRule {
|
|
357
|
+
source: string;
|
|
358
|
+
has: Array<{
|
|
359
|
+
type: "cookie" | "query";
|
|
360
|
+
key: string;
|
|
361
|
+
}>;
|
|
362
|
+
headers: Array<{
|
|
363
|
+
key: string;
|
|
364
|
+
value: string;
|
|
365
|
+
}>;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Rules for `next.config`'s `headers()` that send `draftHeaders` on draft
|
|
369
|
+
* responses only:
|
|
370
|
+
*
|
|
371
|
+
* // next.config.mjs
|
|
372
|
+
* import { capaHeaders } from "@capacms/sdk/nextjs";
|
|
373
|
+
* export default { async headers() { return [...capaHeaders()]; } };
|
|
374
|
+
*
|
|
375
|
+
* A rule matches a request carrying the draft cookie, or a `capa-preview`,
|
|
376
|
+
* `capa-edit` or `capa-view` query. A visitor's request carries none of
|
|
377
|
+
* them, so its response, cached or not, is exactly what it was. Next checks
|
|
378
|
+
* the cookie by name, not by value, so a forged cookie only adds these
|
|
379
|
+
* headers to the forger's own response.
|
|
380
|
+
*
|
|
381
|
+
* A site that sends its own `frame-ancestors` or `X-Frame-Options` must leave
|
|
382
|
+
* them off draft responses (`missing: [{ type: "cookie", key: DRAFT_COOKIE }]`
|
|
383
|
+
* on its rule): a browser applies every CSP it is sent, so the strictest wins.
|
|
384
|
+
*/
|
|
385
|
+
export declare function capaHeaders(options?: {
|
|
386
|
+
adminOrigins?: readonly string[];
|
|
387
|
+
}): NextHeaderRule[];
|
|
388
|
+
/** The part of Next's `cookies()` store (`next/headers`) the draft-cookie helpers use. */
|
|
389
|
+
export interface DraftCookieJar {
|
|
390
|
+
get(name: string): {
|
|
391
|
+
value: string;
|
|
392
|
+
} | undefined;
|
|
393
|
+
set(cookie: {
|
|
394
|
+
name: string;
|
|
395
|
+
value: string;
|
|
396
|
+
path: string;
|
|
397
|
+
httpOnly: boolean;
|
|
398
|
+
secure: boolean;
|
|
399
|
+
sameSite: "none";
|
|
400
|
+
partitioned: boolean;
|
|
401
|
+
maxAge?: number;
|
|
402
|
+
expires?: Date;
|
|
403
|
+
}): unknown;
|
|
404
|
+
}
|
|
405
|
+
/** Next's `cookies` from `next/headers`, or anything shaped like it. */
|
|
406
|
+
export type CookiesFn = () => DraftCookieJar | Promise<DraftCookieJar>;
|
|
407
|
+
/**
|
|
408
|
+
* Re-set the cookie `draftMode().enable()` just set, so the Capa editor can
|
|
409
|
+
* use it: `HttpOnly; Secure; SameSite=None; Partitioned; Path=/` and
|
|
410
|
+
* `Max-Age` one hour (`maxAge` seconds). Next sets it with no `Max-Age`, so
|
|
411
|
+
* it unlocks every draft on the site until the browser closes, and without
|
|
412
|
+
* `Partitioned`, which Safari 26.2 and later need to send a cookie into a
|
|
413
|
+
* cross-site frame. Call it after `enable()`, in the same route handler.
|
|
414
|
+
* Resolves false, and sets nothing, when there is no draft cookie to re-set.
|
|
415
|
+
*/
|
|
416
|
+
export declare function frameDraftCookie(cookies: CookiesFn, options?: {
|
|
417
|
+
maxAge?: number;
|
|
418
|
+
}): Promise<boolean>;
|
|
419
|
+
/**
|
|
420
|
+
* Delete the draft cookie `frameDraftCookie` set. A partitioned cookie is
|
|
421
|
+
* deleted only by a `Set-Cookie` that is partitioned too, which
|
|
422
|
+
* `draftMode().disable()` is not. Call it after `disable()`; it replaces
|
|
423
|
+
* `disable()`'s own deletion, since a response sets one cookie per name. A
|
|
424
|
+
* draft cookie set without `Partitioned`, before a site used
|
|
425
|
+
* `frameDraftCookie`, ends when the browser closes, as it always did.
|
|
426
|
+
*/
|
|
427
|
+
export declare function clearDraftCookie(cookies: CookiesFn): Promise<void>;
|
|
317
428
|
/**
|
|
318
429
|
* `app/api/capa/preview/route.ts`:
|
|
319
430
|
*
|
|
320
|
-
* import { draftMode } from "next/headers";
|
|
321
|
-
*
|
|
322
|
-
* export const GET = createPreviewRoute({ draftMode, redirect });
|
|
431
|
+
* import { cookies, draftMode } from "next/headers";
|
|
432
|
+
* export const GET = createPreviewRoute({ draftMode, cookies });
|
|
323
433
|
*
|
|
324
434
|
* Checks the token with Capa (the site never holds the signing key), turns
|
|
325
435
|
* draft mode on and lands on the entry's page. A bad or expired token lands on
|
|
326
436
|
* the page without draft mode and `?preview=expired`; Capa unreachable gives
|
|
327
|
-
* `?preview=unavailable`.
|
|
437
|
+
* `?preview=unavailable`. The token is checked with any key the site holds,
|
|
438
|
+
* its legacy key included (`getPublishedClient`, from `CAPA_KEY`).
|
|
439
|
+
*
|
|
440
|
+
* Given `cookies`, the draft cookie is re-set by `frameDraftCookie`: one hour,
|
|
441
|
+
* and sent inside the editor's frame. With no `redirect`, the route answers
|
|
442
|
+
* with its own 307, marked `X-Robots-Tag: noindex, nofollow`,
|
|
443
|
+
* `Referrer-Policy: no-referrer` (the token is in the URL),
|
|
444
|
+
* `Cache-Control: private, no-store` and the editor's `frame-ancestors`.
|
|
445
|
+
* Given Next's `redirect`, it is called instead, as before, and the redirect
|
|
446
|
+
* carries none of those.
|
|
328
447
|
*/
|
|
329
448
|
export declare function createPreviewRoute(input: {
|
|
330
449
|
draftMode: DraftModeFn;
|
|
331
|
-
|
|
450
|
+
/** Next's `cookies`. Given, the draft cookie lasts `maxAge` seconds and works in the editor's frame. */
|
|
451
|
+
cookies?: CookiesFn;
|
|
452
|
+
/** Next's `redirect`. Leave it out for a redirect that carries the draft headers. */
|
|
453
|
+
redirect?: (url: string) => never | void;
|
|
332
454
|
client?: () => Pick<CapaNextClient, "preview">;
|
|
333
|
-
/** Runs after draft mode is enabled, before the redirect (cookie tweaks). */
|
|
455
|
+
/** Runs after draft mode is enabled and the cookie re-set, before the redirect (cookie tweaks). */
|
|
334
456
|
onEnable?: () => void | Promise<void>;
|
|
457
|
+
/** The origins allowed to frame a draft. `[CAPA_ADMIN_ORIGIN]` when left out. */
|
|
458
|
+
adminOrigins?: readonly string[];
|
|
459
|
+
/** Seconds the draft cookie lasts, given `cookies`. `DRAFT_COOKIE_MAX_AGE` (one hour) when left out. */
|
|
460
|
+
maxAge?: number;
|
|
335
461
|
}): (request: Request) => Promise<Response | void>;
|
|
336
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* `app/api/capa/exit/route.ts`:
|
|
464
|
+
*
|
|
465
|
+
* import { cookies, draftMode } from "next/headers";
|
|
466
|
+
* export const GET = exitPreviewRoute({ draftMode, cookies });
|
|
467
|
+
*
|
|
468
|
+
* Turns draft mode off and lands on `?path=`. Given `cookies`, the framed
|
|
469
|
+
* cookie `createPreviewRoute` set is deleted too. With no `redirect`, the route
|
|
470
|
+
* answers with its own 307, marked noindex and never cached.
|
|
471
|
+
*/
|
|
337
472
|
export declare function exitPreviewRoute(input: {
|
|
338
473
|
draftMode: DraftModeFn;
|
|
339
|
-
|
|
474
|
+
cookies?: CookiesFn;
|
|
475
|
+
redirect?: (url: string) => never | void;
|
|
340
476
|
}): (request: Request) => Promise<Response | void>;
|
|
341
477
|
interface MiddlewareRequest {
|
|
342
478
|
url: string;
|
|
@@ -372,7 +508,20 @@ interface NextResponseLike {
|
|
|
372
508
|
* - `?capa-view=published` renders without the draft cookie, for the editor's
|
|
373
509
|
* Published view;
|
|
374
510
|
* - `?capa-edit=<token>` turns edit mode on (ids and overlay, published data);
|
|
375
|
-
* - every edit-mode response is `private, no-store
|
|
511
|
+
* - every edit-mode response is `private, no-store`, and it and every request
|
|
512
|
+
* carrying `capa-edit` or `capa-view` is `X-Robots-Tag: noindex, nofollow`.
|
|
513
|
+
*
|
|
514
|
+
* Give it a `matcher` so a visitor's request never runs it (Next reads
|
|
515
|
+
* `config` from the file itself, so it is written out there):
|
|
516
|
+
*
|
|
517
|
+
* export const config = {
|
|
518
|
+
* matcher: [
|
|
519
|
+
* { source: "/:path*", has: [{ type: "query", key: "capa-preview" }] },
|
|
520
|
+
* { source: "/:path*", has: [{ type: "query", key: "capa-edit" }] },
|
|
521
|
+
* { source: "/:path*", has: [{ type: "query", key: "capa-view" }] },
|
|
522
|
+
* { source: "/:path*", has: [{ type: "cookie", key: "__prerender_bypass" }] },
|
|
523
|
+
* ],
|
|
524
|
+
* };
|
|
376
525
|
*/
|
|
377
526
|
export declare function capaMiddleware(input: {
|
|
378
527
|
NextResponse: NextResponseLike;
|