@prismicio/next 2.3.1-pr.147.05dde81 → 2.3.1-pr.147.286b2d0
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/PrismicPreviewClient.cjs +34 -13
- package/dist/PrismicPreviewClient.cjs.map +1 -1
- package/dist/PrismicPreviewClient.js +34 -13
- package/dist/PrismicPreviewClient.js.map +1 -1
- package/dist/getPreviewRef.cjs +2 -2
- package/dist/getPreviewRef.cjs.map +1 -1
- package/dist/getPreviewRef.d.cts.map +1 -1
- package/dist/getPreviewRef.d.ts.map +1 -1
- package/dist/getPreviewRef.js +2 -2
- package/dist/getPreviewRef.js.map +1 -1
- package/dist/lib/expiredPreviewCookies.cjs +5 -0
- package/dist/lib/expiredPreviewCookies.cjs.map +1 -1
- package/dist/lib/expiredPreviewCookies.js +5 -1
- package/dist/lib/expiredPreviewCookies.js.map +1 -1
- package/dist/lib/previewRefFromCookie.cjs +24 -0
- package/dist/lib/previewRefFromCookie.cjs.map +1 -0
- package/dist/lib/previewRefFromCookie.js +24 -0
- package/dist/lib/previewRefFromCookie.js.map +1 -0
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/dist/pages/PrismicPreview.cjs +36 -12
- package/dist/pages/PrismicPreview.cjs.map +1 -1
- package/dist/pages/PrismicPreview.js +36 -12
- package/dist/pages/PrismicPreview.js.map +1 -1
- package/dist/pages/exitPreview.cjs +5 -1
- package/dist/pages/exitPreview.cjs.map +1 -1
- package/dist/pages/exitPreview.d.cts.map +1 -1
- package/dist/pages/exitPreview.d.ts.map +1 -1
- package/dist/pages/exitPreview.js +6 -2
- package/dist/pages/exitPreview.js.map +1 -1
- package/dist/pages/setPreviewData.cjs +8 -2
- package/dist/pages/setPreviewData.cjs.map +1 -1
- package/dist/pages/setPreviewData.d.cts.map +1 -1
- package/dist/pages/setPreviewData.d.ts.map +1 -1
- package/dist/pages/setPreviewData.js +8 -2
- package/dist/pages/setPreviewData.js.map +1 -1
- package/dist/redirectToPreviewURL.cjs +8 -0
- package/dist/redirectToPreviewURL.cjs.map +1 -1
- package/dist/redirectToPreviewURL.js +8 -0
- package/dist/redirectToPreviewURL.js.map +1 -1
- package/package.json +1 -1
- package/src/PrismicPreviewClient.tsx +36 -5
- package/src/getPreviewRef.ts +6 -6
- package/src/lib/expiredPreviewCookies.ts +5 -0
- package/src/lib/previewRefFromCookie.ts +33 -0
- package/src/pages/PrismicPreview.tsx +37 -10
- package/src/pages/exitPreview.ts +12 -2
- package/src/pages/setPreviewData.ts +19 -2
- package/src/redirectToPreviewURL.ts +12 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrismicPreview.js","names":["cookie","prismicCookie"],"sources":["../../src/pages/PrismicPreview.tsx"],"sourcesContent":["import { getToolbarSrc, cookie as prismicCookie } from \"@prismicio/client\"\nimport { useRouter } from \"next/router\"\nimport Script from \"next/script\"\nimport type { FC } from \"react\"\nimport { type ReactNode, useEffect } from \"react\"\n\n/** Props for `<PrismicPreview>`. */\nexport type PrismicPreviewProps = {\n\t/**\n\t * The name of your Prismic repository. A Prismic Toolbar will be registered using this\n\t * repository.\n\t */\n\trepositoryName: string\n\n\t/**\n\t * The URL of your app's Prismic preview endpoint (default: `/api/preview`). This URL will be\n\t * fetched on preview update events.\n\t */\n\tupdatePreviewURL?: string\n\n\t/**\n\t * The URL of your app's exit preview endpoint (default: `/api/exit-preview`). This URL will be\n\t * fetched on preview exit events.\n\t */\n\texitPreviewURL?: string\n\n\t/** Children to render adjacent to the Prismic Toolbar. */\n\tchildren?: ReactNode\n}\n\n/**\n * React component that sets up Prismic Previews using the Prismic Toolbar. When the Prismic Toolbar\n * send events to the browser, such as on preview updates and exiting, this component will\n * automatically refresh the page with the changes.\n *\n * This component can be wrapped around your app or added anywhere in your app's tree. It must be\n * rendered on every page.\n */\nexport const PrismicPreview: FC<PrismicPreviewProps> = (props) => {\n\tconst {\n\t\trepositoryName,\n\t\tupdatePreviewURL = \"/api/preview\",\n\t\texitPreviewURL = \"/api/exit-preview\",\n\t\tchildren,\n\t} = props\n\n\tconst router = useRouter()\n\n\tconst toolbarSrc = getToolbarSrc(repositoryName)\n\n\tuseEffect(() => {\n\t\tconst controller = new AbortController()\n\n\t\twindow.addEventListener(\"prismicPreviewUpdate\", onUpdate, {\n\t\t\tsignal: controller.signal,\n\t\t})\n\t\twindow.addEventListener(\"prismicPreviewEnd\", onEnd, {\n\t\t\tsignal: controller.signal,\n\t\t})\n\n\t\t// Start the preview for preview share links. Previews from\n\t\t// share links do not go to the `updatePreviewURL` like a normal\n\t\t// preview.\n\t\t//\n\t\t// We check that the current URL is a descendant of the base\n\t\t// path to prevent infinite refrehes.\n\t\tif (\n\t\t\twindow.location.href.startsWith(window.location.origin + router.basePath) &&\n\t\t\tgetPreviewCookieRepositoryName() === repositoryName &&\n\t\t\t!router.isPreview\n\t\t) {\n\t\t\tstart()\n\t\t}\n\n\t\tfunction onEnd(event: Event) {\n\t\t\tevent.preventDefault()\n\t\t\tfetch(router.basePath + exitPreviewURL, { signal: controller.signal })\n\t\t\t\t.then((res) => {\n\t\t\t\t\tif (!res.ok) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t`[<PrismicPreview>] Failed to exit Preview Mode using the \"${exitPreviewURL}\" API endpoint. Does it exist?`,\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\trefresh()\n\t\t\t\t})\n\t\t\t\t.catch(() => {})\n\t\t}\n\n\t\tfunction onUpdate(event: Event) {\n\t\t\tevent.preventDefault()\n\t\t\tstart()\n\t\t}\n\n\t\tfunction start() {\n\t\t\t// We check `opaqueredirect` because we don't care if\n\t\t\t// the redirect was successful or not. As long as it\n\t\t\t// redirects, we know the endpoint exists and at least\n\t\t\t// attempted to set preview data.\n\t\t\
|
|
1
|
+
{"version":3,"file":"PrismicPreview.js","names":["cookie","prismicCookie"],"sources":["../../src/pages/PrismicPreview.tsx"],"sourcesContent":["import { getToolbarSrc, cookie as prismicCookie } from \"@prismicio/client\"\nimport { useRouter } from \"next/router\"\nimport Script from \"next/script\"\nimport type { FC } from \"react\"\nimport { type ReactNode, useEffect } from \"react\"\n\n/** Props for `<PrismicPreview>`. */\nexport type PrismicPreviewProps = {\n\t/**\n\t * The name of your Prismic repository. A Prismic Toolbar will be registered using this\n\t * repository.\n\t */\n\trepositoryName: string\n\n\t/**\n\t * The URL of your app's Prismic preview endpoint (default: `/api/preview`). This URL will be\n\t * fetched on preview update events.\n\t */\n\tupdatePreviewURL?: string\n\n\t/**\n\t * The URL of your app's exit preview endpoint (default: `/api/exit-preview`). This URL will be\n\t * fetched on preview exit events.\n\t */\n\texitPreviewURL?: string\n\n\t/** Children to render adjacent to the Prismic Toolbar. */\n\tchildren?: ReactNode\n}\n\n/**\n * React component that sets up Prismic Previews using the Prismic Toolbar. When the Prismic Toolbar\n * send events to the browser, such as on preview updates and exiting, this component will\n * automatically refresh the page with the changes.\n *\n * This component can be wrapped around your app or added anywhere in your app's tree. It must be\n * rendered on every page.\n */\nexport const PrismicPreview: FC<PrismicPreviewProps> = (props) => {\n\tconst {\n\t\trepositoryName,\n\t\tupdatePreviewURL = \"/api/preview\",\n\t\texitPreviewURL = \"/api/exit-preview\",\n\t\tchildren,\n\t} = props\n\n\tconst router = useRouter()\n\n\tconst toolbarSrc = getToolbarSrc(repositoryName)\n\n\tuseEffect(() => {\n\t\tconst controller = new AbortController()\n\t\tlet starting: Promise<void> | undefined\n\t\tlet startController: AbortController | undefined\n\t\tlet updateRequested = false\n\t\tlet ending = false\n\n\t\twindow.addEventListener(\"prismicPreviewUpdate\", onUpdate, {\n\t\t\tsignal: controller.signal,\n\t\t})\n\t\twindow.addEventListener(\"prismicPreviewEnd\", onEnd, {\n\t\t\tsignal: controller.signal,\n\t\t})\n\n\t\t// Start the preview for preview share links. Previews from\n\t\t// share links do not go to the `updatePreviewURL` like a normal\n\t\t// preview.\n\t\t//\n\t\t// We check that the current URL is a descendant of the base\n\t\t// path to prevent infinite refrehes.\n\t\tif (\n\t\t\twindow.location.href.startsWith(window.location.origin + router.basePath) &&\n\t\t\tgetPreviewCookieRepositoryName() === repositoryName &&\n\t\t\t!router.isPreview\n\t\t) {\n\t\t\tstart()\n\t\t}\n\n\t\tfunction onEnd(event: Event) {\n\t\t\tevent.preventDefault()\n\t\t\tif (ending) return\n\t\t\tending = true\n\t\t\tupdateRequested = false\n\t\t\tstartController?.abort()\n\t\t\tfetch(router.basePath + exitPreviewURL, { signal: controller.signal })\n\t\t\t\t.then((res) => {\n\t\t\t\t\tif (!res.ok) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t`[<PrismicPreview>] Failed to exit Preview Mode using the \"${exitPreviewURL}\" API endpoint. Does it exist?`,\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\trefresh()\n\t\t\t\t})\n\t\t\t\t.catch(() => {})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tending = false\n\t\t\t\t})\n\t\t}\n\n\t\tfunction onUpdate(event: Event) {\n\t\t\tevent.preventDefault()\n\t\t\tif (ending) return\n\t\t\tstart()\n\t\t}\n\n\t\tfunction start() {\n\t\t\tupdateRequested = true\n\t\t\tif (starting) return\n\t\t\tconst requestController = new AbortController()\n\t\t\tstartController = requestController\n\n\t\t\t// We check `opaqueredirect` because we don't care if\n\t\t\t// the redirect was successful or not. As long as it\n\t\t\t// redirects, we know the endpoint exists and at least\n\t\t\t// attempted to set preview data.\n\t\t\tstarting = (async () => {\n\t\t\t\tdo {\n\t\t\t\t\tupdateRequested = false\n\t\t\t\t\tconst res = await fetch(router.basePath + updatePreviewURL, {\n\t\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t\t\tsignal: requestController.signal,\n\t\t\t\t\t})\n\t\t\t\t\tif (res.type !== \"opaqueredirect\") {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t`[<PrismicPreview>] Failed to start or update the preview using \"${updatePreviewURL}\". Does it exist?`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t// Preview Data captures the cookie when the request starts.\n\t\t\t\t\t// If edits arrived meanwhile, store the latest ref before rendering.\n\t\t\t\t} while (updateRequested && !requestController.signal.aborted)\n\t\t\t\tif (!requestController.signal.aborted) refresh()\n\t\t\t})()\n\t\t\t\t.catch(() => {})\n\t\t\t\t.finally(() => {\n\t\t\t\t\tstarting = undefined\n\t\t\t\t\tstartController = undefined\n\t\t\t\t})\n\t\t}\n\n\t\tfunction refresh() {\n\t\t\trouter.replace(router.asPath, undefined, { scroll: false })\n\t\t}\n\n\t\treturn () => {\n\t\t\tcontroller.abort()\n\t\t\tstartController?.abort()\n\t\t}\n\t}, [exitPreviewURL, updatePreviewURL, repositoryName, router])\n\n\treturn (\n\t\t<>\n\t\t\t{children}\n\t\t\t<Script src={toolbarSrc} strategy=\"lazyOnload\" />\n\t\t</>\n\t)\n}\n\nfunction getPreviewCookieRepositoryName() {\n\tconst cookie = window.document.cookie\n\t\t.split(\"; \")\n\t\t.find((row) => row.startsWith(`${prismicCookie.preview}=`))\n\t\t?.split(\"=\")[1]\n\n\treturn (decodeURIComponent(cookie ?? \"\").match(/\"([^\"]+)\\.prismic\\.io\"/) || [])[1]\n}\n"],"mappings":";;;;;;;;;;;;;;AAsCA,MAAa,kBAA2C,UAAU;CACjE,MAAM,EACL,gBACA,mBAAmB,gBACnB,iBAAiB,qBACjB,aACG;CAEJ,MAAM,SAAS,WAAW;CAE1B,MAAM,aAAa,cAAc,eAAe;AAEhD,iBAAgB;EACf,MAAM,aAAa,IAAI,iBAAiB;EACxC,IAAI;EACJ,IAAI;EACJ,IAAI,kBAAkB;EACtB,IAAI,SAAS;AAEb,SAAO,iBAAiB,wBAAwB,UAAU,EACzD,QAAQ,WAAW,QACnB,CAAC;AACF,SAAO,iBAAiB,qBAAqB,OAAO,EACnD,QAAQ,WAAW,QACnB,CAAC;AAQF,MACC,OAAO,SAAS,KAAK,WAAW,OAAO,SAAS,SAAS,OAAO,SAAS,IACzE,gCAAgC,KAAK,kBACrC,CAAC,OAAO,UAER,QAAO;EAGR,SAAS,MAAM,OAAc;AAC5B,SAAM,gBAAgB;AACtB,OAAI,OAAQ;AACZ,YAAS;AACT,qBAAkB;AAClB,oBAAiB,OAAO;AACxB,SAAM,OAAO,WAAW,gBAAgB,EAAE,QAAQ,WAAW,QAAQ,CAAC,CACpE,MAAM,QAAQ;AACd,QAAI,CAAC,IAAI,IAAI;AACZ,aAAQ,MACP,6DAA6D,eAAe,gCAC5E;AAED;;AAGD,aAAS;KACR,CACD,YAAY,GAAG,CACf,cAAc;AACd,aAAS;KACR;;EAGJ,SAAS,SAAS,OAAc;AAC/B,SAAM,gBAAgB;AACtB,OAAI,OAAQ;AACZ,UAAO;;EAGR,SAAS,QAAQ;AAChB,qBAAkB;AAClB,OAAI,SAAU;GACd,MAAM,oBAAoB,IAAI,iBAAiB;AAC/C,qBAAkB;AAMlB,eAAY,YAAY;AACvB,OAAG;AACF,uBAAkB;AAKlB,UAJY,MAAM,MAAM,OAAO,WAAW,kBAAkB;MAC3D,UAAU;MACV,QAAQ,kBAAkB;MAC1B,CAAC,EACM,SAAS,kBAAkB;AAClC,cAAQ,MACP,mEAAmE,iBAAiB,mBACpF;AACD;;aAIO,mBAAmB,CAAC,kBAAkB,OAAO;AACtD,QAAI,CAAC,kBAAkB,OAAO,QAAS,UAAS;OAC7C,CACF,YAAY,GAAG,CACf,cAAc;AACd,eAAW,KAAA;AACX,sBAAkB,KAAA;KACjB;;EAGJ,SAAS,UAAU;AAClB,UAAO,QAAQ,OAAO,QAAQ,KAAA,GAAW,EAAE,QAAQ,OAAO,CAAC;;AAG5D,eAAa;AACZ,cAAW,OAAO;AAClB,oBAAiB,OAAO;;IAEvB;EAAC;EAAgB;EAAkB;EAAgB;EAAO,CAAC;AAE9D,QACC,qBAAA,UAAA,EAAA,UAAA,CACE,UACD,oBAAC,QAAD;EAAQ,KAAK;EAAY,UAAS;EAAe,CAAA,CAC/C,EAAA,CAAA;;AAIL,SAAS,iCAAiC;CACzC,MAAMA,WAAS,OAAO,SAAS,OAC7B,MAAM,KAAK,CACX,MAAM,QAAQ,IAAI,WAAW,GAAGC,OAAc,QAAQ,GAAG,CAAC,EACzD,MAAM,IAAI,CAAC;AAEd,SAAQ,mBAAmBD,YAAU,GAAG,CAAC,MAAM,yBAAyB,IAAI,EAAE,EAAE"}
|
|
@@ -18,7 +18,11 @@ const require_expiredPreviewCookies = require("../lib/expiredPreviewCookies.cjs"
|
|
|
18
18
|
function exitPreview(config) {
|
|
19
19
|
config.res.clearPreviewData();
|
|
20
20
|
const existingSetCookie = config.res.getHeader("Set-Cookie");
|
|
21
|
-
const expiredPreviewCookies =
|
|
21
|
+
const expiredPreviewCookies = [
|
|
22
|
+
...require_expiredPreviewCookies.expiredPreviewCookieHeaders(),
|
|
23
|
+
...require_expiredPreviewCookies.expiredDraftModeCookieHeaders(),
|
|
24
|
+
...require_expiredPreviewCookies.expiredPreviewDataCookieHeaders()
|
|
25
|
+
];
|
|
22
26
|
config.res.setHeader("Set-Cookie", existingSetCookie == null ? expiredPreviewCookies : [...(Array.isArray(existingSetCookie) ? existingSetCookie : [existingSetCookie]).map(String), ...expiredPreviewCookies]);
|
|
23
27
|
config.res.setHeader("Cache-Control", "no-store");
|
|
24
28
|
config.res.json({ success: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exitPreview.cjs","names":["expiredPreviewCookieHeaders"],"sources":["../../src/pages/exitPreview.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"exitPreview.cjs","names":["expiredPreviewCookieHeaders","expiredDraftModeCookieHeaders","expiredPreviewDataCookieHeaders"],"sources":["../../src/pages/exitPreview.ts"],"sourcesContent":["import {\n\texpiredDraftModeCookieHeaders,\n\texpiredPreviewCookieHeaders,\n\texpiredPreviewDataCookieHeaders,\n} from \"../lib/expiredPreviewCookies\"\nimport type { NextApiRequestLike, NextApiResponseLike } from \"./types\"\n\n/** Configuration for `exitPreview()`. */\nexport type ExitPreviewAPIRouteConfig = {\n\t/**\n\t * The `req` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\treq?: NextApiRequestLike\n\n\t/**\n\t * The `res` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\tres: NextApiResponseLike\n}\n\n/**\n * Ends a Prismic preview session within a Next.js app. This function should be used in an API\n * route.\n *\n * @example\n * \t;```typescript\n * \t// src/pages/api/exit-preview.js\n *\n * \timport { exitPreview } from \"@prismicio/next\"\n *\n * \texport default function handler(_req, res) {\n * \t\treturn exitPreview({ res })\n * \t}\n * \t```\n */\nexport function exitPreview(config: ExitPreviewAPIRouteConfig): void {\n\tconfig.res.clearPreviewData()\n\n\t// `clearPreviewData()` only clears Next.js preview cookies. Expire both\n\t// leftover `io.prismic.preview` shapes (toolbar Lax, not Secure; and the\n\t// iframe SameSite=None; Secure write). If either survives,\n\t// `<PrismicPreview>` calls `start()` → `/api/preview` and preview returns.\n\t// Next also emits Lax expiries in development, so explicitly clear the\n\t// Secure Next preview cookies written by `setPreviewData()`.\n\t// Do not use `cookies().set` — that is the App Router helper. Do not\n\t// replace the existing Set-Cookie header from `clearPreviewData()`.\n\tconst existingSetCookie = config.res.getHeader(\"Set-Cookie\")\n\tconst expiredPreviewCookies = [\n\t\t...expiredPreviewCookieHeaders(),\n\t\t...expiredDraftModeCookieHeaders(),\n\t\t...expiredPreviewDataCookieHeaders(),\n\t]\n\tconfig.res.setHeader(\n\t\t\"Set-Cookie\",\n\t\texistingSetCookie == null\n\t\t\t? expiredPreviewCookies\n\t\t\t: [\n\t\t\t\t\t...(Array.isArray(existingSetCookie) ? existingSetCookie : [existingSetCookie]).map(\n\t\t\t\t\t\tString,\n\t\t\t\t\t),\n\t\t\t\t\t...expiredPreviewCookies,\n\t\t\t\t],\n\t)\n\n\t// `Cache-Control` header is used to prevent CDN-level caching.\n\tconfig.res.setHeader(\"Cache-Control\", \"no-store\")\n\n\tconfig.res.json({ success: true })\n\n\treturn\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,SAAgB,YAAY,QAAyC;AACpE,QAAO,IAAI,kBAAkB;CAU7B,MAAM,oBAAoB,OAAO,IAAI,UAAU,aAAa;CAC5D,MAAM,wBAAwB;EAC7B,GAAGA,8BAAAA,6BAA6B;EAChC,GAAGC,8BAAAA,+BAA+B;EAClC,GAAGC,8BAAAA,iCAAiC;EACpC;AACD,QAAO,IAAI,UACV,cACA,qBAAqB,OAClB,wBACA,CACA,IAAI,MAAM,QAAQ,kBAAkB,GAAG,oBAAoB,CAAC,kBAAkB,EAAE,IAC/E,OACA,EACD,GAAG,sBACH,CACH;AAGD,QAAO,IAAI,UAAU,iBAAiB,WAAW;AAEjD,QAAO,IAAI,KAAK,EAAE,SAAS,MAAM,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exitPreview.d.cts","names":[],"sources":["../../src/pages/exitPreview.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"exitPreview.d.cts","names":[],"sources":["../../src/pages/exitPreview.ts"],"mappings":";;;;KAQY,yBAAA;EAAA;;;;;EAMX,GAAA,GAAM,kBAAA;EAON;;;;AAkBD;EAlBC,GAAA,EAAK,mBAAA;AAAA;;;;;;;;;;;;;;;;iBAkBU,WAAA,CAAY,MAAA,EAAQ,yBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exitPreview.d.ts","names":[],"sources":["../../src/pages/exitPreview.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"exitPreview.d.ts","names":[],"sources":["../../src/pages/exitPreview.ts"],"mappings":";;;;KAQY,yBAAA;EAAA;;;;;EAMX,GAAA,GAAM,kBAAA;EAON;;;;AAkBD;EAlBC,GAAA,EAAK,mBAAA;AAAA;;;;;;;;;;;;;;;;iBAkBU,WAAA,CAAY,MAAA,EAAQ,yBAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expiredPreviewCookieHeaders } from "../lib/expiredPreviewCookies.js";
|
|
1
|
+
import { expiredDraftModeCookieHeaders, expiredPreviewCookieHeaders, expiredPreviewDataCookieHeaders } from "../lib/expiredPreviewCookies.js";
|
|
2
2
|
//#region src/pages/exitPreview.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ends a Prismic preview session within a Next.js app. This function should be used in an API
|
|
@@ -18,7 +18,11 @@ import { expiredPreviewCookieHeaders } from "../lib/expiredPreviewCookies.js";
|
|
|
18
18
|
function exitPreview(config) {
|
|
19
19
|
config.res.clearPreviewData();
|
|
20
20
|
const existingSetCookie = config.res.getHeader("Set-Cookie");
|
|
21
|
-
const expiredPreviewCookies =
|
|
21
|
+
const expiredPreviewCookies = [
|
|
22
|
+
...expiredPreviewCookieHeaders(),
|
|
23
|
+
...expiredDraftModeCookieHeaders(),
|
|
24
|
+
...expiredPreviewDataCookieHeaders()
|
|
25
|
+
];
|
|
22
26
|
config.res.setHeader("Set-Cookie", existingSetCookie == null ? expiredPreviewCookies : [...(Array.isArray(existingSetCookie) ? existingSetCookie : [existingSetCookie]).map(String), ...expiredPreviewCookies]);
|
|
23
27
|
config.res.setHeader("Cache-Control", "no-store");
|
|
24
28
|
config.res.json({ success: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exitPreview.js","names":[],"sources":["../../src/pages/exitPreview.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"exitPreview.js","names":[],"sources":["../../src/pages/exitPreview.ts"],"sourcesContent":["import {\n\texpiredDraftModeCookieHeaders,\n\texpiredPreviewCookieHeaders,\n\texpiredPreviewDataCookieHeaders,\n} from \"../lib/expiredPreviewCookies\"\nimport type { NextApiRequestLike, NextApiResponseLike } from \"./types\"\n\n/** Configuration for `exitPreview()`. */\nexport type ExitPreviewAPIRouteConfig = {\n\t/**\n\t * The `req` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\treq?: NextApiRequestLike\n\n\t/**\n\t * The `res` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\tres: NextApiResponseLike\n}\n\n/**\n * Ends a Prismic preview session within a Next.js app. This function should be used in an API\n * route.\n *\n * @example\n * \t;```typescript\n * \t// src/pages/api/exit-preview.js\n *\n * \timport { exitPreview } from \"@prismicio/next\"\n *\n * \texport default function handler(_req, res) {\n * \t\treturn exitPreview({ res })\n * \t}\n * \t```\n */\nexport function exitPreview(config: ExitPreviewAPIRouteConfig): void {\n\tconfig.res.clearPreviewData()\n\n\t// `clearPreviewData()` only clears Next.js preview cookies. Expire both\n\t// leftover `io.prismic.preview` shapes (toolbar Lax, not Secure; and the\n\t// iframe SameSite=None; Secure write). If either survives,\n\t// `<PrismicPreview>` calls `start()` → `/api/preview` and preview returns.\n\t// Next also emits Lax expiries in development, so explicitly clear the\n\t// Secure Next preview cookies written by `setPreviewData()`.\n\t// Do not use `cookies().set` — that is the App Router helper. Do not\n\t// replace the existing Set-Cookie header from `clearPreviewData()`.\n\tconst existingSetCookie = config.res.getHeader(\"Set-Cookie\")\n\tconst expiredPreviewCookies = [\n\t\t...expiredPreviewCookieHeaders(),\n\t\t...expiredDraftModeCookieHeaders(),\n\t\t...expiredPreviewDataCookieHeaders(),\n\t]\n\tconfig.res.setHeader(\n\t\t\"Set-Cookie\",\n\t\texistingSetCookie == null\n\t\t\t? expiredPreviewCookies\n\t\t\t: [\n\t\t\t\t\t...(Array.isArray(existingSetCookie) ? existingSetCookie : [existingSetCookie]).map(\n\t\t\t\t\t\tString,\n\t\t\t\t\t),\n\t\t\t\t\t...expiredPreviewCookies,\n\t\t\t\t],\n\t)\n\n\t// `Cache-Control` header is used to prevent CDN-level caching.\n\tconfig.res.setHeader(\"Cache-Control\", \"no-store\")\n\n\tconfig.res.json({ success: true })\n\n\treturn\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,SAAgB,YAAY,QAAyC;AACpE,QAAO,IAAI,kBAAkB;CAU7B,MAAM,oBAAoB,OAAO,IAAI,UAAU,aAAa;CAC5D,MAAM,wBAAwB;EAC7B,GAAG,6BAA6B;EAChC,GAAG,+BAA+B;EAClC,GAAG,iCAAiC;EACpC;AACD,QAAO,IAAI,UACV,cACA,qBAAqB,OAClB,wBACA,CACA,IAAI,MAAM,QAAQ,kBAAkB,GAAG,oBAAoB,CAAC,kBAAkB,EAAE,IAC/E,OACA,EACD,GAAG,sBACH,CACH;AAGD,QAAO,IAAI,UAAU,iBAAiB,WAAW;AAEjD,QAAO,IAAI,KAAK,EAAE,SAAS,MAAM,CAAC"}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
require("../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_previewRefFromCookie = require("../lib/previewRefFromCookie.cjs");
|
|
2
3
|
let _prismicio_client = require("@prismicio/client");
|
|
3
4
|
//#region src/pages/setPreviewData.ts
|
|
4
5
|
/** Set Prismic preview data for Next.js's Preview Mode. */
|
|
5
6
|
function setPreviewData({ req, res }) {
|
|
6
|
-
const
|
|
7
|
-
|
|
7
|
+
const previewCookie = req.cookies[_prismicio_client.cookie.preview];
|
|
8
|
+
const ref = req.query.token || previewCookie && require_previewRefFromCookie.previewRefFromCookie(previewCookie);
|
|
9
|
+
if (ref) {
|
|
10
|
+
res.setPreviewData({ ref });
|
|
11
|
+
const setCookie = res.getHeader("Set-Cookie");
|
|
12
|
+
if (typeof setCookie === "string" || Array.isArray(setCookie)) res.setHeader("Set-Cookie", (typeof setCookie === "string" ? [setCookie] : setCookie).map((header) => /^__(?:prerender_bypass|next_preview_data)=/.test(header) ? `${header.replace(/;\s*(?:Secure|SameSite=[^;]*)(?=;|$)/gi, "")}; SameSite=None; Secure` : header));
|
|
13
|
+
}
|
|
8
14
|
}
|
|
9
15
|
//#endregion
|
|
10
16
|
exports.setPreviewData = setPreviewData;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setPreviewData.cjs","names":["cookie"],"sources":["../../src/pages/setPreviewData.ts"],"sourcesContent":["import { cookie } from \"@prismicio/client\"\n\nimport type { NextApiRequestLike, NextApiResponseLike } from \"./types\"\n\n/** Configuration for `setPreviewData`. */\nexport type SetPreviewDataConfig = {\n\t/**\n\t * The `req` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\treq: NextApiRequestLike\n\n\t/**\n\t * The `res` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\tres: NextApiResponseLike\n}\n\n/** Set Prismic preview data for Next.js's Preview Mode. */\nexport function setPreviewData({ req, res }: SetPreviewDataConfig): void {\n\
|
|
1
|
+
{"version":3,"file":"setPreviewData.cjs","names":["cookie","previewRefFromCookie"],"sources":["../../src/pages/setPreviewData.ts"],"sourcesContent":["import { cookie } from \"@prismicio/client\"\n\nimport { previewRefFromCookie } from \"../lib/previewRefFromCookie\"\nimport type { NextApiRequestLike, NextApiResponseLike } from \"./types\"\n\n/** Configuration for `setPreviewData`. */\nexport type SetPreviewDataConfig = {\n\t/**\n\t * The `req` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\treq: NextApiRequestLike\n\n\t/**\n\t * The `res` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\tres: NextApiResponseLike\n}\n\n/** Set Prismic preview data for Next.js's Preview Mode. */\nexport function setPreviewData({ req, res }: SetPreviewDataConfig): void {\n\t// Only unwrap the cookie. Query tokens, including arrays, stay as-is.\n\tconst previewCookie = req.cookies[cookie.preview]\n\tconst ref = req.query.token || (previewCookie && previewRefFromCookie(previewCookie))\n\tif (ref) {\n\t\tres.setPreviewData({ ref })\n\n\t\t// Next.js uses Lax preview cookies in development. Preserve its\n\t\t// signed/encrypted values and all other attributes, but allow both\n\t\t// cookies to reach the site inside the editor's cross-site iframe.\n\t\tconst setCookie = res.getHeader(\"Set-Cookie\")\n\t\tif (typeof setCookie === \"string\" || Array.isArray(setCookie)) {\n\t\t\tres.setHeader(\n\t\t\t\t\"Set-Cookie\",\n\t\t\t\t(typeof setCookie === \"string\" ? [setCookie] : setCookie).map((header) =>\n\t\t\t\t\t/^__(?:prerender_bypass|next_preview_data)=/.test(header)\n\t\t\t\t\t\t? `${header.replace(/;\\s*(?:Secure|SameSite=[^;]*)(?=;|$)/gi, \"\")}; SameSite=None; Secure`\n\t\t\t\t\t\t: header,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n}\n"],"mappings":";;;;;AAuBA,SAAgB,eAAe,EAAE,KAAK,OAAmC;CAExE,MAAM,gBAAgB,IAAI,QAAQA,kBAAAA,OAAO;CACzC,MAAM,MAAM,IAAI,MAAM,SAAU,iBAAiBC,6BAAAA,qBAAqB,cAAc;AACpF,KAAI,KAAK;AACR,MAAI,eAAe,EAAE,KAAK,CAAC;EAK3B,MAAM,YAAY,IAAI,UAAU,aAAa;AAC7C,MAAI,OAAO,cAAc,YAAY,MAAM,QAAQ,UAAU,CAC5D,KAAI,UACH,eACC,OAAO,cAAc,WAAW,CAAC,UAAU,GAAG,WAAW,KAAK,WAC9D,6CAA6C,KAAK,OAAO,GACtD,GAAG,OAAO,QAAQ,0CAA0C,GAAG,CAAC,2BAChE,OACH,CACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setPreviewData.d.cts","names":[],"sources":["../../src/pages/setPreviewData.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"setPreviewData.d.cts","names":[],"sources":["../../src/pages/setPreviewData.ts"],"mappings":";;;;KAMY,oBAAA;EAAA;;;;;EAMX,GAAA,EAAK,kBAAA;EAOL;;;;AAID;EAJC,GAAA,EAAK,mBAAA;AAAA;;iBAIU,cAAA,CAAA;EAAiB,GAAA;EAAK;AAAA,GAAO,oBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setPreviewData.d.ts","names":[],"sources":["../../src/pages/setPreviewData.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"setPreviewData.d.ts","names":[],"sources":["../../src/pages/setPreviewData.ts"],"mappings":";;;;KAMY,oBAAA;EAAA;;;;;EAMX,GAAA,EAAK,kBAAA;EAOL;;;;AAID;EAJC,GAAA,EAAK,mBAAA;AAAA;;iBAIU,cAAA,CAAA;EAAiB,GAAA;EAAK;AAAA,GAAO,oBAAA"}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { previewRefFromCookie } from "../lib/previewRefFromCookie.js";
|
|
1
2
|
import { cookie } from "@prismicio/client";
|
|
2
3
|
//#region src/pages/setPreviewData.ts
|
|
3
4
|
/** Set Prismic preview data for Next.js's Preview Mode. */
|
|
4
5
|
function setPreviewData({ req, res }) {
|
|
5
|
-
const
|
|
6
|
-
|
|
6
|
+
const previewCookie = req.cookies[cookie.preview];
|
|
7
|
+
const ref = req.query.token || previewCookie && previewRefFromCookie(previewCookie);
|
|
8
|
+
if (ref) {
|
|
9
|
+
res.setPreviewData({ ref });
|
|
10
|
+
const setCookie = res.getHeader("Set-Cookie");
|
|
11
|
+
if (typeof setCookie === "string" || Array.isArray(setCookie)) res.setHeader("Set-Cookie", (typeof setCookie === "string" ? [setCookie] : setCookie).map((header) => /^__(?:prerender_bypass|next_preview_data)=/.test(header) ? `${header.replace(/;\s*(?:Secure|SameSite=[^;]*)(?=;|$)/gi, "")}; SameSite=None; Secure` : header));
|
|
12
|
+
}
|
|
7
13
|
}
|
|
8
14
|
//#endregion
|
|
9
15
|
export { setPreviewData };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setPreviewData.js","names":[],"sources":["../../src/pages/setPreviewData.ts"],"sourcesContent":["import { cookie } from \"@prismicio/client\"\n\nimport type { NextApiRequestLike, NextApiResponseLike } from \"./types\"\n\n/** Configuration for `setPreviewData`. */\nexport type SetPreviewDataConfig = {\n\t/**\n\t * The `req` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\treq: NextApiRequestLike\n\n\t/**\n\t * The `res` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\tres: NextApiResponseLike\n}\n\n/** Set Prismic preview data for Next.js's Preview Mode. */\nexport function setPreviewData({ req, res }: SetPreviewDataConfig): void {\n\
|
|
1
|
+
{"version":3,"file":"setPreviewData.js","names":[],"sources":["../../src/pages/setPreviewData.ts"],"sourcesContent":["import { cookie } from \"@prismicio/client\"\n\nimport { previewRefFromCookie } from \"../lib/previewRefFromCookie\"\nimport type { NextApiRequestLike, NextApiResponseLike } from \"./types\"\n\n/** Configuration for `setPreviewData`. */\nexport type SetPreviewDataConfig = {\n\t/**\n\t * The `req` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\treq: NextApiRequestLike\n\n\t/**\n\t * The `res` object from a Next.js API route.\n\t *\n\t * @see Next.js API route docs: \\<https://nextjs.org/docs/api-routes/introduction\\>\n\t */\n\tres: NextApiResponseLike\n}\n\n/** Set Prismic preview data for Next.js's Preview Mode. */\nexport function setPreviewData({ req, res }: SetPreviewDataConfig): void {\n\t// Only unwrap the cookie. Query tokens, including arrays, stay as-is.\n\tconst previewCookie = req.cookies[cookie.preview]\n\tconst ref = req.query.token || (previewCookie && previewRefFromCookie(previewCookie))\n\tif (ref) {\n\t\tres.setPreviewData({ ref })\n\n\t\t// Next.js uses Lax preview cookies in development. Preserve its\n\t\t// signed/encrypted values and all other attributes, but allow both\n\t\t// cookies to reach the site inside the editor's cross-site iframe.\n\t\tconst setCookie = res.getHeader(\"Set-Cookie\")\n\t\tif (typeof setCookie === \"string\" || Array.isArray(setCookie)) {\n\t\t\tres.setHeader(\n\t\t\t\t\"Set-Cookie\",\n\t\t\t\t(typeof setCookie === \"string\" ? [setCookie] : setCookie).map((header) =>\n\t\t\t\t\t/^__(?:prerender_bypass|next_preview_data)=/.test(header)\n\t\t\t\t\t\t? `${header.replace(/;\\s*(?:Secure|SameSite=[^;]*)(?=;|$)/gi, \"\")}; SameSite=None; Secure`\n\t\t\t\t\t\t: header,\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n}\n"],"mappings":";;;;AAuBA,SAAgB,eAAe,EAAE,KAAK,OAAmC;CAExE,MAAM,gBAAgB,IAAI,QAAQ,OAAO;CACzC,MAAM,MAAM,IAAI,MAAM,SAAU,iBAAiB,qBAAqB,cAAc;AACpF,KAAI,KAAK;AACR,MAAI,eAAe,EAAE,KAAK,CAAC;EAK3B,MAAM,YAAY,IAAI,UAAU,aAAa;AAC7C,MAAI,OAAO,cAAc,YAAY,MAAM,QAAQ,UAAU,CAC5D,KAAI,UACH,eACC,OAAO,cAAc,WAAW,CAAC,UAAU,GAAG,WAAW,KAAK,WAC9D,6CAA6C,KAAK,OAAO,GACtD,GAAG,OAAO,QAAQ,0CAA0C,GAAG,CAAC,2BAChE,OACH,CACD"}
|
|
@@ -20,6 +20,14 @@ async function redirectToPreviewURL(config) {
|
|
|
20
20
|
linkResolver
|
|
21
21
|
});
|
|
22
22
|
(await draftMode()).enable();
|
|
23
|
+
const cookieJar = await cookies();
|
|
24
|
+
const draftCookie = cookieJar.get("__prerender_bypass");
|
|
25
|
+
if (draftCookie) cookieJar.set(draftCookie.name, draftCookie.value, {
|
|
26
|
+
path: "/",
|
|
27
|
+
sameSite: "none",
|
|
28
|
+
secure: true,
|
|
29
|
+
httpOnly: true
|
|
30
|
+
});
|
|
23
31
|
return redirect(previewURL);
|
|
24
32
|
}
|
|
25
33
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirectToPreviewURL.cjs","names":["prismicCookie"],"sources":["../src/redirectToPreviewURL.ts"],"sourcesContent":["import { cookie as prismicCookie, type Client, type LinkResolverFunction } from \"@prismicio/client\"\n\nimport type { NextRequestLike } from \"./types\"\n\nexport type RedirectToPreviewURLConfig = {\n\t/** The Prismic client configured for the preview session's repository. */\n\t// `Pick` is used to use the smallest possible subset of\n\t// `prismic.Client`. Doing this reduces the surface area for breaking\n\t// type changes.\n\tclient: Pick<Client, \"resolvePreviewURL\">\n\n\t/**\n\t * The `request` object from a Next.js Route Handler.\n\t *\n\t * @see Next.js Route Handler docs: \\<https://nextjs.org/docs/app/building-your-application/routing/route-handlers\\>\n\t */\n\trequest: NextRequestLike\n\n\t/**\n\t * A Link Resolver used to resolve the previewed document's URL.\n\t *\n\t * @see To learn more about Link Resolver: {@link https://prismic.io/docs/core-concepts/link-resolver-route-resolver}\n\t */\n\tlinkResolver?: LinkResolverFunction\n\n\t/**\n\t * The default redirect URL if a URL cannot be determined for the previewed document.\n\t *\n\t * **Note**: If you `next.config.js` file contains a `basePath`, the `defaultURL` option must\n\t * _not_ include it. Instead, provide the `basePath` property using the `basePath` option.\n\t */\n\tdefaultURL?: string\n}\n\nexport async function redirectToPreviewURL(config: RedirectToPreviewURLConfig): Promise<never> {\n\tconst { client, request, linkResolver, defaultURL = \"/\" } = config\n\n\t// Need this to avoid the following Next.js build-time error:\n\t// You're importing a component that needs next/headers. That only works\n\t// in a Server Component which is not supported in the pages/ directory.\n\tconst { cookies, draftMode } = await import(\"next/headers\")\n\n\t// Dynamic so Node.js can load this module: the bare specifier (kept by\n\t// tsdown.config.ts for Next.js's server alias) is not resolvable by Node.js.\n\tconst { redirect } = await import(\"next/navigation\")\n\n\tconst documentID = request.nextUrl.searchParams.get(\"documentId\") ?? undefined\n\n\t// Set the initial preview cookie. Setting the cookie here is necessary\n\t// to support unpublished previews. Without setting it here, the page\n\t// will try to render without the preview cookie, leading to a\n\t// PrismicNotFound error.\n\t//\n\t// Path=/; SameSite=None; Secure so the cookie is sent when the site is\n\t// embedded in a cross-site iframe (e.g. the Platform editor). Chrome\n\t// defaults omitted SameSite to Lax and strips the cookie from those\n\t// requests. Do not set HttpOnly: the toolbar reads `document.cookie`.\n\tconst previewToken = request.nextUrl.searchParams.get(\"token\") ?? undefined\n\tif (previewToken) {\n\t\tconst cookieJar = await cookies()\n\t\tcookieJar.set(prismicCookie.preview, previewToken, {\n\t\t\tpath: \"/\",\n\t\t\tsameSite: \"none\",\n\t\t\tsecure: true,\n\t\t\thttpOnly: false,\n\t\t})\n\t}\n\n\tconst previewURL = await client.resolvePreviewURL({\n\t\tdocumentID,\n\t\tpreviewToken,\n\t\tdefaultURL,\n\t\tlinkResolver,\n\t})\n\n\t;(await draftMode()).enable()\n\n\treturn redirect(previewURL)\n}\n"],"mappings":";;;AAkCA,eAAsB,qBAAqB,QAAoD;CAC9F,MAAM,EAAE,QAAQ,SAAS,cAAc,aAAa,QAAQ;CAK5D,MAAM,EAAE,SAAS,cAAc,MAAM,OAAO;CAI5C,MAAM,EAAE,aAAa,MAAM,OAAO;CAElC,MAAM,aAAa,QAAQ,QAAQ,aAAa,IAAI,aAAa,IAAI,KAAA;CAWrE,MAAM,eAAe,QAAQ,QAAQ,aAAa,IAAI,QAAQ,IAAI,KAAA;AAClE,KAAI,aAEH,EADkB,MAAM,SAAS,EACvB,IAAIA,kBAAAA,OAAc,SAAS,cAAc;EAClD,MAAM;EACN,UAAU;EACV,QAAQ;EACR,UAAU;EACV,CAAC;CAGH,MAAM,aAAa,MAAM,OAAO,kBAAkB;EACjD;EACA;EACA;EACA;EACA,CAAC;AAED,EAAC,MAAM,WAAW,EAAE,QAAQ;
|
|
1
|
+
{"version":3,"file":"redirectToPreviewURL.cjs","names":["prismicCookie"],"sources":["../src/redirectToPreviewURL.ts"],"sourcesContent":["import { cookie as prismicCookie, type Client, type LinkResolverFunction } from \"@prismicio/client\"\n\nimport type { NextRequestLike } from \"./types\"\n\nexport type RedirectToPreviewURLConfig = {\n\t/** The Prismic client configured for the preview session's repository. */\n\t// `Pick` is used to use the smallest possible subset of\n\t// `prismic.Client`. Doing this reduces the surface area for breaking\n\t// type changes.\n\tclient: Pick<Client, \"resolvePreviewURL\">\n\n\t/**\n\t * The `request` object from a Next.js Route Handler.\n\t *\n\t * @see Next.js Route Handler docs: \\<https://nextjs.org/docs/app/building-your-application/routing/route-handlers\\>\n\t */\n\trequest: NextRequestLike\n\n\t/**\n\t * A Link Resolver used to resolve the previewed document's URL.\n\t *\n\t * @see To learn more about Link Resolver: {@link https://prismic.io/docs/core-concepts/link-resolver-route-resolver}\n\t */\n\tlinkResolver?: LinkResolverFunction\n\n\t/**\n\t * The default redirect URL if a URL cannot be determined for the previewed document.\n\t *\n\t * **Note**: If you `next.config.js` file contains a `basePath`, the `defaultURL` option must\n\t * _not_ include it. Instead, provide the `basePath` property using the `basePath` option.\n\t */\n\tdefaultURL?: string\n}\n\nexport async function redirectToPreviewURL(config: RedirectToPreviewURLConfig): Promise<never> {\n\tconst { client, request, linkResolver, defaultURL = \"/\" } = config\n\n\t// Need this to avoid the following Next.js build-time error:\n\t// You're importing a component that needs next/headers. That only works\n\t// in a Server Component which is not supported in the pages/ directory.\n\tconst { cookies, draftMode } = await import(\"next/headers\")\n\n\t// Dynamic so Node.js can load this module: the bare specifier (kept by\n\t// tsdown.config.ts for Next.js's server alias) is not resolvable by Node.js.\n\tconst { redirect } = await import(\"next/navigation\")\n\n\tconst documentID = request.nextUrl.searchParams.get(\"documentId\") ?? undefined\n\n\t// Set the initial preview cookie. Setting the cookie here is necessary\n\t// to support unpublished previews. Without setting it here, the page\n\t// will try to render without the preview cookie, leading to a\n\t// PrismicNotFound error.\n\t//\n\t// Path=/; SameSite=None; Secure so the cookie is sent when the site is\n\t// embedded in a cross-site iframe (e.g. the Platform editor). Chrome\n\t// defaults omitted SameSite to Lax and strips the cookie from those\n\t// requests. Do not set HttpOnly: the toolbar reads `document.cookie`.\n\tconst previewToken = request.nextUrl.searchParams.get(\"token\") ?? undefined\n\tif (previewToken) {\n\t\tconst cookieJar = await cookies()\n\t\tcookieJar.set(prismicCookie.preview, previewToken, {\n\t\t\tpath: \"/\",\n\t\t\tsameSite: \"none\",\n\t\t\tsecure: true,\n\t\t\thttpOnly: false,\n\t\t})\n\t}\n\n\tconst previewURL = await client.resolvePreviewURL({\n\t\tdocumentID,\n\t\tpreviewToken,\n\t\tdefaultURL,\n\t\tlinkResolver,\n\t})\n\n\t;(await draftMode()).enable()\n\t// Next.js uses SameSite=Lax for Draft Mode in development. Keep its\n\t// generated value, but allow the editor iframe to send this cookie too.\n\tconst cookieJar = await cookies()\n\tconst draftCookie = cookieJar.get(\"__prerender_bypass\")\n\tif (draftCookie) {\n\t\tcookieJar.set(draftCookie.name, draftCookie.value, {\n\t\t\tpath: \"/\",\n\t\t\tsameSite: \"none\",\n\t\t\tsecure: true,\n\t\t\thttpOnly: true,\n\t\t})\n\t}\n\n\treturn redirect(previewURL)\n}\n"],"mappings":";;;AAkCA,eAAsB,qBAAqB,QAAoD;CAC9F,MAAM,EAAE,QAAQ,SAAS,cAAc,aAAa,QAAQ;CAK5D,MAAM,EAAE,SAAS,cAAc,MAAM,OAAO;CAI5C,MAAM,EAAE,aAAa,MAAM,OAAO;CAElC,MAAM,aAAa,QAAQ,QAAQ,aAAa,IAAI,aAAa,IAAI,KAAA;CAWrE,MAAM,eAAe,QAAQ,QAAQ,aAAa,IAAI,QAAQ,IAAI,KAAA;AAClE,KAAI,aAEH,EADkB,MAAM,SAAS,EACvB,IAAIA,kBAAAA,OAAc,SAAS,cAAc;EAClD,MAAM;EACN,UAAU;EACV,QAAQ;EACR,UAAU;EACV,CAAC;CAGH,MAAM,aAAa,MAAM,OAAO,kBAAkB;EACjD;EACA;EACA;EACA;EACA,CAAC;AAED,EAAC,MAAM,WAAW,EAAE,QAAQ;CAG7B,MAAM,YAAY,MAAM,SAAS;CACjC,MAAM,cAAc,UAAU,IAAI,qBAAqB;AACvD,KAAI,YACH,WAAU,IAAI,YAAY,MAAM,YAAY,OAAO;EAClD,MAAM;EACN,UAAU;EACV,QAAQ;EACR,UAAU;EACV,CAAC;AAGH,QAAO,SAAS,WAAW"}
|
|
@@ -19,6 +19,14 @@ async function redirectToPreviewURL(config) {
|
|
|
19
19
|
linkResolver
|
|
20
20
|
});
|
|
21
21
|
(await draftMode()).enable();
|
|
22
|
+
const cookieJar = await cookies();
|
|
23
|
+
const draftCookie = cookieJar.get("__prerender_bypass");
|
|
24
|
+
if (draftCookie) cookieJar.set(draftCookie.name, draftCookie.value, {
|
|
25
|
+
path: "/",
|
|
26
|
+
sameSite: "none",
|
|
27
|
+
secure: true,
|
|
28
|
+
httpOnly: true
|
|
29
|
+
});
|
|
22
30
|
return redirect(previewURL);
|
|
23
31
|
}
|
|
24
32
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redirectToPreviewURL.js","names":["prismicCookie"],"sources":["../src/redirectToPreviewURL.ts"],"sourcesContent":["import { cookie as prismicCookie, type Client, type LinkResolverFunction } from \"@prismicio/client\"\n\nimport type { NextRequestLike } from \"./types\"\n\nexport type RedirectToPreviewURLConfig = {\n\t/** The Prismic client configured for the preview session's repository. */\n\t// `Pick` is used to use the smallest possible subset of\n\t// `prismic.Client`. Doing this reduces the surface area for breaking\n\t// type changes.\n\tclient: Pick<Client, \"resolvePreviewURL\">\n\n\t/**\n\t * The `request` object from a Next.js Route Handler.\n\t *\n\t * @see Next.js Route Handler docs: \\<https://nextjs.org/docs/app/building-your-application/routing/route-handlers\\>\n\t */\n\trequest: NextRequestLike\n\n\t/**\n\t * A Link Resolver used to resolve the previewed document's URL.\n\t *\n\t * @see To learn more about Link Resolver: {@link https://prismic.io/docs/core-concepts/link-resolver-route-resolver}\n\t */\n\tlinkResolver?: LinkResolverFunction\n\n\t/**\n\t * The default redirect URL if a URL cannot be determined for the previewed document.\n\t *\n\t * **Note**: If you `next.config.js` file contains a `basePath`, the `defaultURL` option must\n\t * _not_ include it. Instead, provide the `basePath` property using the `basePath` option.\n\t */\n\tdefaultURL?: string\n}\n\nexport async function redirectToPreviewURL(config: RedirectToPreviewURLConfig): Promise<never> {\n\tconst { client, request, linkResolver, defaultURL = \"/\" } = config\n\n\t// Need this to avoid the following Next.js build-time error:\n\t// You're importing a component that needs next/headers. That only works\n\t// in a Server Component which is not supported in the pages/ directory.\n\tconst { cookies, draftMode } = await import(\"next/headers\")\n\n\t// Dynamic so Node.js can load this module: the bare specifier (kept by\n\t// tsdown.config.ts for Next.js's server alias) is not resolvable by Node.js.\n\tconst { redirect } = await import(\"next/navigation\")\n\n\tconst documentID = request.nextUrl.searchParams.get(\"documentId\") ?? undefined\n\n\t// Set the initial preview cookie. Setting the cookie here is necessary\n\t// to support unpublished previews. Without setting it here, the page\n\t// will try to render without the preview cookie, leading to a\n\t// PrismicNotFound error.\n\t//\n\t// Path=/; SameSite=None; Secure so the cookie is sent when the site is\n\t// embedded in a cross-site iframe (e.g. the Platform editor). Chrome\n\t// defaults omitted SameSite to Lax and strips the cookie from those\n\t// requests. Do not set HttpOnly: the toolbar reads `document.cookie`.\n\tconst previewToken = request.nextUrl.searchParams.get(\"token\") ?? undefined\n\tif (previewToken) {\n\t\tconst cookieJar = await cookies()\n\t\tcookieJar.set(prismicCookie.preview, previewToken, {\n\t\t\tpath: \"/\",\n\t\t\tsameSite: \"none\",\n\t\t\tsecure: true,\n\t\t\thttpOnly: false,\n\t\t})\n\t}\n\n\tconst previewURL = await client.resolvePreviewURL({\n\t\tdocumentID,\n\t\tpreviewToken,\n\t\tdefaultURL,\n\t\tlinkResolver,\n\t})\n\n\t;(await draftMode()).enable()\n\n\treturn redirect(previewURL)\n}\n"],"mappings":";;AAkCA,eAAsB,qBAAqB,QAAoD;CAC9F,MAAM,EAAE,QAAQ,SAAS,cAAc,aAAa,QAAQ;CAK5D,MAAM,EAAE,SAAS,cAAc,MAAM,OAAO;CAI5C,MAAM,EAAE,aAAa,MAAM,OAAO;CAElC,MAAM,aAAa,QAAQ,QAAQ,aAAa,IAAI,aAAa,IAAI,KAAA;CAWrE,MAAM,eAAe,QAAQ,QAAQ,aAAa,IAAI,QAAQ,IAAI,KAAA;AAClE,KAAI,aAEH,EADkB,MAAM,SAAS,EACvB,IAAIA,OAAc,SAAS,cAAc;EAClD,MAAM;EACN,UAAU;EACV,QAAQ;EACR,UAAU;EACV,CAAC;CAGH,MAAM,aAAa,MAAM,OAAO,kBAAkB;EACjD;EACA;EACA;EACA;EACA,CAAC;AAED,EAAC,MAAM,WAAW,EAAE,QAAQ;
|
|
1
|
+
{"version":3,"file":"redirectToPreviewURL.js","names":["prismicCookie"],"sources":["../src/redirectToPreviewURL.ts"],"sourcesContent":["import { cookie as prismicCookie, type Client, type LinkResolverFunction } from \"@prismicio/client\"\n\nimport type { NextRequestLike } from \"./types\"\n\nexport type RedirectToPreviewURLConfig = {\n\t/** The Prismic client configured for the preview session's repository. */\n\t// `Pick` is used to use the smallest possible subset of\n\t// `prismic.Client`. Doing this reduces the surface area for breaking\n\t// type changes.\n\tclient: Pick<Client, \"resolvePreviewURL\">\n\n\t/**\n\t * The `request` object from a Next.js Route Handler.\n\t *\n\t * @see Next.js Route Handler docs: \\<https://nextjs.org/docs/app/building-your-application/routing/route-handlers\\>\n\t */\n\trequest: NextRequestLike\n\n\t/**\n\t * A Link Resolver used to resolve the previewed document's URL.\n\t *\n\t * @see To learn more about Link Resolver: {@link https://prismic.io/docs/core-concepts/link-resolver-route-resolver}\n\t */\n\tlinkResolver?: LinkResolverFunction\n\n\t/**\n\t * The default redirect URL if a URL cannot be determined for the previewed document.\n\t *\n\t * **Note**: If you `next.config.js` file contains a `basePath`, the `defaultURL` option must\n\t * _not_ include it. Instead, provide the `basePath` property using the `basePath` option.\n\t */\n\tdefaultURL?: string\n}\n\nexport async function redirectToPreviewURL(config: RedirectToPreviewURLConfig): Promise<never> {\n\tconst { client, request, linkResolver, defaultURL = \"/\" } = config\n\n\t// Need this to avoid the following Next.js build-time error:\n\t// You're importing a component that needs next/headers. That only works\n\t// in a Server Component which is not supported in the pages/ directory.\n\tconst { cookies, draftMode } = await import(\"next/headers\")\n\n\t// Dynamic so Node.js can load this module: the bare specifier (kept by\n\t// tsdown.config.ts for Next.js's server alias) is not resolvable by Node.js.\n\tconst { redirect } = await import(\"next/navigation\")\n\n\tconst documentID = request.nextUrl.searchParams.get(\"documentId\") ?? undefined\n\n\t// Set the initial preview cookie. Setting the cookie here is necessary\n\t// to support unpublished previews. Without setting it here, the page\n\t// will try to render without the preview cookie, leading to a\n\t// PrismicNotFound error.\n\t//\n\t// Path=/; SameSite=None; Secure so the cookie is sent when the site is\n\t// embedded in a cross-site iframe (e.g. the Platform editor). Chrome\n\t// defaults omitted SameSite to Lax and strips the cookie from those\n\t// requests. Do not set HttpOnly: the toolbar reads `document.cookie`.\n\tconst previewToken = request.nextUrl.searchParams.get(\"token\") ?? undefined\n\tif (previewToken) {\n\t\tconst cookieJar = await cookies()\n\t\tcookieJar.set(prismicCookie.preview, previewToken, {\n\t\t\tpath: \"/\",\n\t\t\tsameSite: \"none\",\n\t\t\tsecure: true,\n\t\t\thttpOnly: false,\n\t\t})\n\t}\n\n\tconst previewURL = await client.resolvePreviewURL({\n\t\tdocumentID,\n\t\tpreviewToken,\n\t\tdefaultURL,\n\t\tlinkResolver,\n\t})\n\n\t;(await draftMode()).enable()\n\t// Next.js uses SameSite=Lax for Draft Mode in development. Keep its\n\t// generated value, but allow the editor iframe to send this cookie too.\n\tconst cookieJar = await cookies()\n\tconst draftCookie = cookieJar.get(\"__prerender_bypass\")\n\tif (draftCookie) {\n\t\tcookieJar.set(draftCookie.name, draftCookie.value, {\n\t\t\tpath: \"/\",\n\t\t\tsameSite: \"none\",\n\t\t\tsecure: true,\n\t\t\thttpOnly: true,\n\t\t})\n\t}\n\n\treturn redirect(previewURL)\n}\n"],"mappings":";;AAkCA,eAAsB,qBAAqB,QAAoD;CAC9F,MAAM,EAAE,QAAQ,SAAS,cAAc,aAAa,QAAQ;CAK5D,MAAM,EAAE,SAAS,cAAc,MAAM,OAAO;CAI5C,MAAM,EAAE,aAAa,MAAM,OAAO;CAElC,MAAM,aAAa,QAAQ,QAAQ,aAAa,IAAI,aAAa,IAAI,KAAA;CAWrE,MAAM,eAAe,QAAQ,QAAQ,aAAa,IAAI,QAAQ,IAAI,KAAA;AAClE,KAAI,aAEH,EADkB,MAAM,SAAS,EACvB,IAAIA,OAAc,SAAS,cAAc;EAClD,MAAM;EACN,UAAU;EACV,QAAQ;EACR,UAAU;EACV,CAAC;CAGH,MAAM,aAAa,MAAM,OAAO,kBAAkB;EACjD;EACA;EACA;EACA;EACA,CAAC;AAED,EAAC,MAAM,WAAW,EAAE,QAAQ;CAG7B,MAAM,YAAY,MAAM,SAAS;CACjC,MAAM,cAAc,UAAU,IAAI,qBAAqB;AACvD,KAAI,YACH,WAAU,IAAI,YAAY,MAAM,YAAY,OAAO;EAClD,MAAM;EACN,UAAU;EACV,QAAQ;EACR,UAAU;EACV,CAAC;AAGH,QAAO,SAAS,WAAW"}
|
package/package.json
CHANGED
|
@@ -24,6 +24,9 @@ export const PrismicPreviewClient: FC<PrismicPreviewClientProps> = (props) => {
|
|
|
24
24
|
|
|
25
25
|
useEffect(() => {
|
|
26
26
|
const controller = new AbortController()
|
|
27
|
+
let starting: Promise<void> | undefined
|
|
28
|
+
let startController: AbortController | undefined
|
|
29
|
+
let ending = false
|
|
27
30
|
|
|
28
31
|
window.addEventListener("prismicPreviewUpdate", onUpdate, {
|
|
29
32
|
signal: controller.signal,
|
|
@@ -42,14 +45,21 @@ export const PrismicPreviewClient: FC<PrismicPreviewClientProps> = (props) => {
|
|
|
42
45
|
// share links do not go to the `updatePreviewURL` like a normal
|
|
43
46
|
// preview.
|
|
44
47
|
if (hasCookieForRepository && !isDraftMode) {
|
|
48
|
+
start()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function start() {
|
|
52
|
+
if (starting) return
|
|
53
|
+
startController = new AbortController()
|
|
54
|
+
|
|
45
55
|
// We check `opaqueredirect` because we don't care if
|
|
46
56
|
// the redirect was successful or not. As long as it
|
|
47
57
|
// redirects, we know the endpoint exists and draft mode
|
|
48
58
|
// is active.
|
|
49
|
-
globalThis
|
|
59
|
+
starting = globalThis
|
|
50
60
|
.fetch(updatePreviewURL, {
|
|
51
61
|
redirect: "manual",
|
|
52
|
-
signal:
|
|
62
|
+
signal: startController.signal,
|
|
53
63
|
})
|
|
54
64
|
.then((res) => {
|
|
55
65
|
if (res.type !== "opaqueredirect") {
|
|
@@ -60,20 +70,35 @@ export const PrismicPreviewClient: FC<PrismicPreviewClientProps> = (props) => {
|
|
|
60
70
|
return
|
|
61
71
|
}
|
|
62
72
|
|
|
63
|
-
refresh
|
|
73
|
+
// A soft refresh does not reset Next.js's not-found boundary
|
|
74
|
+
// when an unpublished page enters Draft Mode at the same URL.
|
|
75
|
+
// Only this initial activation navigates; active previews stay soft.
|
|
76
|
+
window.location.reload()
|
|
64
77
|
})
|
|
65
78
|
.catch(() => {
|
|
66
79
|
// noop
|
|
67
80
|
})
|
|
81
|
+
.finally(() => {
|
|
82
|
+
starting = undefined
|
|
83
|
+
startController = undefined
|
|
84
|
+
})
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
function onUpdate(event: Event) {
|
|
71
88
|
event.preventDefault()
|
|
72
|
-
|
|
89
|
+
if (ending) return
|
|
90
|
+
if (isDraftMode) {
|
|
91
|
+
refresh()
|
|
92
|
+
} else {
|
|
93
|
+
start()
|
|
94
|
+
}
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
function onEnd(event: Event) {
|
|
76
98
|
event.preventDefault()
|
|
99
|
+
if (ending) return
|
|
100
|
+
ending = true
|
|
101
|
+
startController?.abort()
|
|
77
102
|
globalThis
|
|
78
103
|
.fetch(exitPreviewURL, { signal: controller.signal })
|
|
79
104
|
.then((res) => {
|
|
@@ -90,9 +115,15 @@ export const PrismicPreviewClient: FC<PrismicPreviewClientProps> = (props) => {
|
|
|
90
115
|
.catch(() => {
|
|
91
116
|
// noop
|
|
92
117
|
})
|
|
118
|
+
.finally(() => {
|
|
119
|
+
ending = false
|
|
120
|
+
})
|
|
93
121
|
}
|
|
94
122
|
|
|
95
|
-
return () =>
|
|
123
|
+
return () => {
|
|
124
|
+
controller.abort()
|
|
125
|
+
startController?.abort()
|
|
126
|
+
}
|
|
96
127
|
}, [repositoryName, isDraftMode, updatePreviewURL, exitPreviewURL, refresh])
|
|
97
128
|
|
|
98
129
|
return null
|
package/src/getPreviewRef.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cookie as prismicCookie } from "@prismicio/client"
|
|
2
2
|
|
|
3
|
+
import { previewRefFromCookie } from "./lib/previewRefFromCookie"
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Reads the Prismic preview ref for the current request when an active preview session exists.
|
|
5
7
|
*
|
|
@@ -42,10 +44,8 @@ export async function getPreviewRef(): Promise<string | undefined> {
|
|
|
42
44
|
return
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return cookie
|
|
47
|
+
// Draft Mode gates preview reads. The cookie may be a raw token or the
|
|
48
|
+
// toolbar jar (`{ "<repo>.prismic.io": { preview } }`). Only `preview` is a
|
|
49
|
+
// Content API ref; the jar string is not.
|
|
50
|
+
return previewRefFromCookie(cookie)
|
|
51
51
|
}
|
|
@@ -30,3 +30,8 @@ export function expiredDraftModeCookieHeaders(): string[] {
|
|
|
30
30
|
`${name}=; Path=/; ${expires}; SameSite=None; Secure; HttpOnly`,
|
|
31
31
|
]
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/** Expire Pages Router's signed preview data in development and production. */
|
|
35
|
+
export function expiredPreviewDataCookieHeaders(): string[] {
|
|
36
|
+
return expiredPreviewCookieHeaders("__next_preview_data").map((header) => `${header}; HttpOnly`)
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unwraps `io.prismic.preview` to a Content API ref.
|
|
3
|
+
*
|
|
4
|
+
* After toolbar `PreviewCookie.sync` / `upsertPreviewForDomain`, the cookie is
|
|
5
|
+
* `{ [domain]: { preview: string } }`. Returning that jar makes `enableAutoPreviews`
|
|
6
|
+
* / Cache Components send a non-ref (ParsingError / 404). A jar with more than one
|
|
7
|
+
* repository `preview` is ambiguous and is ignored.
|
|
8
|
+
*/
|
|
9
|
+
export function previewRefFromCookie(cookie: string): string | undefined {
|
|
10
|
+
try {
|
|
11
|
+
const parsed: unknown = JSON.parse(cookie)
|
|
12
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const previewRefs: string[] = []
|
|
17
|
+
for (const value of Object.values(parsed)) {
|
|
18
|
+
if (
|
|
19
|
+
value !== null &&
|
|
20
|
+
typeof value === "object" &&
|
|
21
|
+
!Array.isArray(value) &&
|
|
22
|
+
"preview" in value &&
|
|
23
|
+
typeof value.preview === "string"
|
|
24
|
+
) {
|
|
25
|
+
previewRefs.push(value.preview)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return previewRefs.length === 1 ? previewRefs[0] : undefined
|
|
30
|
+
} catch {
|
|
31
|
+
return cookie
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -50,6 +50,10 @@ export const PrismicPreview: FC<PrismicPreviewProps> = (props) => {
|
|
|
50
50
|
|
|
51
51
|
useEffect(() => {
|
|
52
52
|
const controller = new AbortController()
|
|
53
|
+
let starting: Promise<void> | undefined
|
|
54
|
+
let startController: AbortController | undefined
|
|
55
|
+
let updateRequested = false
|
|
56
|
+
let ending = false
|
|
53
57
|
|
|
54
58
|
window.addEventListener("prismicPreviewUpdate", onUpdate, {
|
|
55
59
|
signal: controller.signal,
|
|
@@ -74,6 +78,10 @@ export const PrismicPreview: FC<PrismicPreviewProps> = (props) => {
|
|
|
74
78
|
|
|
75
79
|
function onEnd(event: Event) {
|
|
76
80
|
event.preventDefault()
|
|
81
|
+
if (ending) return
|
|
82
|
+
ending = true
|
|
83
|
+
updateRequested = false
|
|
84
|
+
startController?.abort()
|
|
77
85
|
fetch(router.basePath + exitPreviewURL, { signal: controller.signal })
|
|
78
86
|
.then((res) => {
|
|
79
87
|
if (!res.ok) {
|
|
@@ -87,41 +95,60 @@ export const PrismicPreview: FC<PrismicPreviewProps> = (props) => {
|
|
|
87
95
|
refresh()
|
|
88
96
|
})
|
|
89
97
|
.catch(() => {})
|
|
98
|
+
.finally(() => {
|
|
99
|
+
ending = false
|
|
100
|
+
})
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
function onUpdate(event: Event) {
|
|
93
104
|
event.preventDefault()
|
|
105
|
+
if (ending) return
|
|
94
106
|
start()
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
function start() {
|
|
110
|
+
updateRequested = true
|
|
111
|
+
if (starting) return
|
|
112
|
+
const requestController = new AbortController()
|
|
113
|
+
startController = requestController
|
|
114
|
+
|
|
98
115
|
// We check `opaqueredirect` because we don't care if
|
|
99
116
|
// the redirect was successful or not. As long as it
|
|
100
117
|
// redirects, we know the endpoint exists and at least
|
|
101
118
|
// attempted to set preview data.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
starting = (async () => {
|
|
120
|
+
do {
|
|
121
|
+
updateRequested = false
|
|
122
|
+
const res = await fetch(router.basePath + updatePreviewURL, {
|
|
123
|
+
redirect: "manual",
|
|
124
|
+
signal: requestController.signal,
|
|
125
|
+
})
|
|
107
126
|
if (res.type !== "opaqueredirect") {
|
|
108
127
|
console.error(
|
|
109
128
|
`[<PrismicPreview>] Failed to start or update the preview using "${updatePreviewURL}". Does it exist?`,
|
|
110
129
|
)
|
|
111
|
-
|
|
112
130
|
return
|
|
113
131
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
})
|
|
132
|
+
// Preview Data captures the cookie when the request starts.
|
|
133
|
+
// If edits arrived meanwhile, store the latest ref before rendering.
|
|
134
|
+
} while (updateRequested && !requestController.signal.aborted)
|
|
135
|
+
if (!requestController.signal.aborted) refresh()
|
|
136
|
+
})()
|
|
117
137
|
.catch(() => {})
|
|
138
|
+
.finally(() => {
|
|
139
|
+
starting = undefined
|
|
140
|
+
startController = undefined
|
|
141
|
+
})
|
|
118
142
|
}
|
|
119
143
|
|
|
120
144
|
function refresh() {
|
|
121
145
|
router.replace(router.asPath, undefined, { scroll: false })
|
|
122
146
|
}
|
|
123
147
|
|
|
124
|
-
return () =>
|
|
148
|
+
return () => {
|
|
149
|
+
controller.abort()
|
|
150
|
+
startController?.abort()
|
|
151
|
+
}
|
|
125
152
|
}, [exitPreviewURL, updatePreviewURL, repositoryName, router])
|
|
126
153
|
|
|
127
154
|
return (
|
package/src/pages/exitPreview.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
expiredDraftModeCookieHeaders,
|
|
3
|
+
expiredPreviewCookieHeaders,
|
|
4
|
+
expiredPreviewDataCookieHeaders,
|
|
5
|
+
} from "../lib/expiredPreviewCookies"
|
|
2
6
|
import type { NextApiRequestLike, NextApiResponseLike } from "./types"
|
|
3
7
|
|
|
4
8
|
/** Configuration for `exitPreview()`. */
|
|
@@ -40,10 +44,16 @@ export function exitPreview(config: ExitPreviewAPIRouteConfig): void {
|
|
|
40
44
|
// leftover `io.prismic.preview` shapes (toolbar Lax, not Secure; and the
|
|
41
45
|
// iframe SameSite=None; Secure write). If either survives,
|
|
42
46
|
// `<PrismicPreview>` calls `start()` → `/api/preview` and preview returns.
|
|
47
|
+
// Next also emits Lax expiries in development, so explicitly clear the
|
|
48
|
+
// Secure Next preview cookies written by `setPreviewData()`.
|
|
43
49
|
// Do not use `cookies().set` — that is the App Router helper. Do not
|
|
44
50
|
// replace the existing Set-Cookie header from `clearPreviewData()`.
|
|
45
51
|
const existingSetCookie = config.res.getHeader("Set-Cookie")
|
|
46
|
-
const expiredPreviewCookies =
|
|
52
|
+
const expiredPreviewCookies = [
|
|
53
|
+
...expiredPreviewCookieHeaders(),
|
|
54
|
+
...expiredDraftModeCookieHeaders(),
|
|
55
|
+
...expiredPreviewDataCookieHeaders(),
|
|
56
|
+
]
|
|
47
57
|
config.res.setHeader(
|
|
48
58
|
"Set-Cookie",
|
|
49
59
|
existingSetCookie == null
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cookie } from "@prismicio/client"
|
|
2
2
|
|
|
3
|
+
import { previewRefFromCookie } from "../lib/previewRefFromCookie"
|
|
3
4
|
import type { NextApiRequestLike, NextApiResponseLike } from "./types"
|
|
4
5
|
|
|
5
6
|
/** Configuration for `setPreviewData`. */
|
|
@@ -21,9 +22,25 @@ export type SetPreviewDataConfig = {
|
|
|
21
22
|
|
|
22
23
|
/** Set Prismic preview data for Next.js's Preview Mode. */
|
|
23
24
|
export function setPreviewData({ req, res }: SetPreviewDataConfig): void {
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// Only unwrap the cookie. Query tokens, including arrays, stay as-is.
|
|
26
|
+
const previewCookie = req.cookies[cookie.preview]
|
|
27
|
+
const ref = req.query.token || (previewCookie && previewRefFromCookie(previewCookie))
|
|
26
28
|
if (ref) {
|
|
27
29
|
res.setPreviewData({ ref })
|
|
30
|
+
|
|
31
|
+
// Next.js uses Lax preview cookies in development. Preserve its
|
|
32
|
+
// signed/encrypted values and all other attributes, but allow both
|
|
33
|
+
// cookies to reach the site inside the editor's cross-site iframe.
|
|
34
|
+
const setCookie = res.getHeader("Set-Cookie")
|
|
35
|
+
if (typeof setCookie === "string" || Array.isArray(setCookie)) {
|
|
36
|
+
res.setHeader(
|
|
37
|
+
"Set-Cookie",
|
|
38
|
+
(typeof setCookie === "string" ? [setCookie] : setCookie).map((header) =>
|
|
39
|
+
/^__(?:prerender_bypass|next_preview_data)=/.test(header)
|
|
40
|
+
? `${header.replace(/;\s*(?:Secure|SameSite=[^;]*)(?=;|$)/gi, "")}; SameSite=None; Secure`
|
|
41
|
+
: header,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
}
|
|
28
45
|
}
|
|
29
46
|
}
|
|
@@ -74,6 +74,18 @@ export async function redirectToPreviewURL(config: RedirectToPreviewURLConfig):
|
|
|
74
74
|
})
|
|
75
75
|
|
|
76
76
|
;(await draftMode()).enable()
|
|
77
|
+
// Next.js uses SameSite=Lax for Draft Mode in development. Keep its
|
|
78
|
+
// generated value, but allow the editor iframe to send this cookie too.
|
|
79
|
+
const cookieJar = await cookies()
|
|
80
|
+
const draftCookie = cookieJar.get("__prerender_bypass")
|
|
81
|
+
if (draftCookie) {
|
|
82
|
+
cookieJar.set(draftCookie.name, draftCookie.value, {
|
|
83
|
+
path: "/",
|
|
84
|
+
sameSite: "none",
|
|
85
|
+
secure: true,
|
|
86
|
+
httpOnly: true,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
77
89
|
|
|
78
90
|
return redirect(previewURL)
|
|
79
91
|
}
|