From 6d45b224e2c847faafe586305ca7711ed04e87ca Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 18 May 2022 17:04:41 -1000 Subject: [PATCH 1/8] feat: accept Request object containing a `Headers` object when using `enableAutoPreviewsFromReq()` --- src/client.ts | 23 ++++++++++++++++------- src/lib/getCookie.ts | 8 ++++---- src/types.ts | 11 ++++++++--- test/client.test.ts | 26 +++++++++++++++++++++++++- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/client.ts b/src/client.ts index 8d755db8..774ab1f6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1492,15 +1492,24 @@ export class Client { */ private async getResolvedRefString(params?: FetchParams): Promise { if (this.refState.autoPreviewsEnabled) { - let previewRef: string | undefined = undefined; + let previewRef: string | undefined; + + let cookieJar: string | null | undefined; if (globalThis.document?.cookie) { - previewRef = getCookie(cookie.preview, globalThis.document.cookie); - } else if (this.refState.httpRequest?.headers?.cookie) { - previewRef = getCookie( - cookie.preview, - this.refState.httpRequest.headers.cookie, - ); + cookieJar = globalThis.document.cookie; + } else if (this.refState.httpRequest?.headers) { + if ("get" in this.refState.httpRequest?.headers) { + // Web API Headers + cookieJar = this.refState.httpRequest.headers.get("cookie"); + } else { + // Express-style headers + cookieJar = this.refState.httpRequest.headers.cookie; + } + } + + if (cookieJar) { + previewRef = getCookie(cookie.preview, cookieJar); } if (previewRef) { diff --git a/src/lib/getCookie.ts b/src/lib/getCookie.ts index 756d41a6..30aea6e9 100644 --- a/src/lib/getCookie.ts +++ b/src/lib/getCookie.ts @@ -47,12 +47,12 @@ const getAll = (cookieStore: string): { [name: string]: string } => /** * Returns the value of a cookie from a given cookie store. * - * @param Name - Of the cookie. - * @param cookieStore - The stringified cookie store from which to read the cookie. + * @param name - Of the cookie. + * @param cookieJar - The stringified cookie store from which to read the cookie. * * @returns The value of the cookie, if it exists. */ export const getCookie = ( name: string, - cookieStore: string, -): string | undefined => getAll(cookieStore)[name]; + cookieJar: string, +): string | undefined => getAll(cookieJar)[name]; diff --git a/src/types.ts b/src/types.ts index 52037316..1161407b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -69,9 +69,14 @@ export interface ResponseLike { * Prismic preview support. */ export interface HttpRequestLike { - headers?: { - cookie?: string; - }; + headers?: // Web API Headers + | { + get(name: string): string | null; + } + // Express-style headers (pre-parsed) + | { + cookie?: string; + }; query?: Record; } diff --git a/test/client.test.ts b/test/client.test.ts index f88790fb..8ee8d7d2 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -2,7 +2,7 @@ import test from "ava"; import * as msw from "msw"; import * as mswNode from "msw/node"; import * as sinon from "sinon"; -import { Response } from "node-fetch"; +import { Response, Headers } from "node-fetch"; import { createMockQueryHandler } from "./__testutils__/createMockQueryHandler"; import { createMockRepositoryHandler } from "./__testutils__/createMockRepositoryHandler"; @@ -261,6 +261,30 @@ test.serial("uses req preview ref if available", async (t) => { t.deepEqual(res, queryResponse); }); +test.serial("supports req with Web APIs", async (t) => { + const previewRef = "previewRef"; + const headers = new Headers(); + headers.set("cookie", `io.prismic.preview=${previewRef}`); + const req = { + headers, + }; + + const queryResponse = createQueryResponse(); + + server.use( + createMockRepositoryHandler(t), + createMockQueryHandler(t, [queryResponse], undefined, { + ref: previewRef, + }), + ); + + const client = createTestClient(t); + client.enableAutoPreviewsFromReq(req); + const res = await client.get(); + + t.deepEqual(res, queryResponse); +}); + test.serial( "does not use preview ref if auto previews are disabled", async (t) => { From 98d0b760e1ccb1470207fb41e1b7f52c67c9cc75 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 18 May 2022 17:30:36 -1000 Subject: [PATCH 2/8] feat: support a Web API Request object --- src/client.ts | 26 +++++++++++------- src/types.ts | 36 +++++++++++++++++-------- test/client-resolvePreviewUrl.test.ts | 39 +++++++++++++++++++++++++++ test/client.test.ts | 1 + 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/client.ts b/src/client.ts index 774ab1f6..df330b3e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1207,19 +1207,27 @@ export class Client { async resolvePreviewURL( args: ResolvePreviewArgs & FetchParams, ): Promise { - let documentID = args.documentID; - let previewToken = args.previewToken; + let documentID: string | undefined | null = args.documentID; + let previewToken: string | undefined | null = args.previewToken; if (typeof globalThis.location !== "undefined") { const searchParams = new URLSearchParams(globalThis.location.search); - documentID = documentID || searchParams.get("documentId") || undefined; - previewToken = previewToken || searchParams.get("token") || undefined; - } else if (this.refState.httpRequest?.query) { - documentID = - documentID || (this.refState.httpRequest.query.documentId as string); - previewToken = - previewToken || (this.refState.httpRequest.query.token as string); + documentID = documentID || searchParams.get("documentId"); + previewToken = previewToken || searchParams.get("token"); + } else if (this.refState.httpRequest) { + if ("url" in this.refState.httpRequest) { + const searchParams = new URL(this.refState.httpRequest.url) + .searchParams; + + documentID = documentID || searchParams.get("documentId"); + previewToken = previewToken || searchParams.get("token"); + } else { + documentID = + documentID || (this.refState.httpRequest.query?.documentId as string); + previewToken = + previewToken || (this.refState.httpRequest.query?.token as string); + } } if (documentID != null && previewToken != null) { diff --git a/src/types.ts b/src/types.ts index 1161407b..1f8735a4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,21 +64,35 @@ export interface ResponseLike { json(): Promise; } +/** + * The minimum required properties to treat as a Express-style Request for + * automatic Prismic preview support. + */ +type HttpRequestLikeExpress = { + headers?: { + cookie?: string; + }; + query?: Record; +}; + +/** + * The minimum required properties to treat as a Web API Request for automatic + * Prismic preview support. + * + * @see http://developer.mozilla.org/en-US/docs/Web/API/Request + */ +type HttpRequestLikeWebAPI = { + headers: { + get(name: string): string | null; + }; + url: string; +}; + /** * The minimum required properties to treat as an HTTP Request for automatic * Prismic preview support. */ -export interface HttpRequestLike { - headers?: // Web API Headers - | { - get(name: string): string | null; - } - // Express-style headers (pre-parsed) - | { - cookie?: string; - }; - query?: Record; -} +export type HttpRequestLike = HttpRequestLikeExpress | HttpRequestLikeWebAPI; /** * An `orderings` parameter that orders the results by the specified field. diff --git a/test/client-resolvePreviewUrl.test.ts b/test/client-resolvePreviewUrl.test.ts index f5ef0a58..aaaa8d46 100644 --- a/test/client-resolvePreviewUrl.test.ts +++ b/test/client-resolvePreviewUrl.test.ts @@ -1,5 +1,6 @@ import test from "ava"; import * as mswNode from "msw/node"; +import { Headers } from "node-fetch"; import AbortController from "abort-controller"; import { createDocument } from "./__testutils__/createDocument"; @@ -76,6 +77,44 @@ test.serial("resolves a preview url using a server req object", async (t) => { t.is(res, `/${document.uid}`); }); +test.serial( + "resolves a preview url using a Web API-based server req object", + async (t) => { + const document = createDocument(); + const queryResponse = createQueryResponse([document]); + + const documentId = document.id; + const previewToken = "previewToken"; + + const headers = new Headers(); + const url = new URL("https://example.com"); + url.searchParams.set("documentId", documentId); + url.searchParams.set("token", previewToken); + const req = { + headers, + url: url.toString(), + }; + + server.use( + createMockRepositoryHandler(t), + createMockQueryHandler(t, [queryResponse], undefined, { + ref: previewToken, + q: `[[at(document.id, "${documentId}")]]`, + lang: "*", + }), + ); + + const client = createTestClient(t); + client.enableAutoPreviewsFromReq(req); + const res = await client.resolvePreviewURL({ + linkResolver: (document) => `/${document.uid}`, + defaultURL: "defaultURL", + }); + + t.is(res, `/${document.uid}`); + }, +); + test.serial( "allows providing an explicit documentId and previewToken", async (t) => { diff --git a/test/client.test.ts b/test/client.test.ts index 8ee8d7d2..f0f82566 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -267,6 +267,7 @@ test.serial("supports req with Web APIs", async (t) => { headers.set("cookie", `io.prismic.preview=${previewRef}`); const req = { headers, + url: "https://example.com", }; const queryResponse = createQueryResponse(); From 9f1a42505f63566f5d7097867c739b5b5c726994 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 18 May 2022 17:54:52 -1000 Subject: [PATCH 3/8] refactor: types --- src/client.ts | 2 +- src/types.ts | 49 ++++++++++++++++++++++++------------------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/client.ts b/src/client.ts index df330b3e..962993b7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1216,7 +1216,7 @@ export class Client { documentID = documentID || searchParams.get("documentId"); previewToken = previewToken || searchParams.get("token"); } else if (this.refState.httpRequest) { - if ("url" in this.refState.httpRequest) { + if (this.refState.httpRequest.url) { const searchParams = new URL(this.refState.httpRequest.url) .searchParams; diff --git a/src/types.ts b/src/types.ts index 1f8735a4..6df0c3fe 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,35 +64,34 @@ export interface ResponseLike { json(): Promise; } -/** - * The minimum required properties to treat as a Express-style Request for - * automatic Prismic preview support. - */ -type HttpRequestLikeExpress = { - headers?: { - cookie?: string; - }; - query?: Record; -}; - -/** - * The minimum required properties to treat as a Web API Request for automatic - * Prismic preview support. - * - * @see http://developer.mozilla.org/en-US/docs/Web/API/Request - */ -type HttpRequestLikeWebAPI = { - headers: { - get(name: string): string | null; - }; - url: string; -}; - /** * The minimum required properties to treat as an HTTP Request for automatic * Prismic preview support. */ -export type HttpRequestLike = HttpRequestLikeExpress | HttpRequestLikeWebAPI; +export type HttpRequestLike = + | /** + * Web API Request + * + * @see http://developer.mozilla.org/en-US/docs/Web/API/Request + */ + { + headers?: { + get(name: string): string | null; + }; + url?: string; + query?: never; + } + + /** + * Express-style Request + */ + | { + headers?: { + cookie?: string; + }; + query?: Record; + url?: never; + }; /** * An `orderings` parameter that orders the results by the specified field. From fe3cb82012068bf6971a50210f40715f186c612b Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 25 May 2022 13:12:19 -1000 Subject: [PATCH 4/8] fix: stricter Web API Headers detection --- src/client.ts | 7 +++++-- test/client.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index 962993b7..811e0bc6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1507,10 +1507,13 @@ export class Client { if (globalThis.document?.cookie) { cookieJar = globalThis.document.cookie; } else if (this.refState.httpRequest?.headers) { - if ("get" in this.refState.httpRequest?.headers) { + if ( + "get" in this.refState.httpRequest?.headers && + typeof this.refState.httpRequest.headers.get === "function" + ) { // Web API Headers cookieJar = this.refState.httpRequest.headers.get("cookie"); - } else { + } else if ("cookie" in this.refState.httpRequest.headers) { // Express-style headers cookieJar = this.refState.httpRequest.headers.cookie; } diff --git a/test/client.test.ts b/test/client.test.ts index f0f82566..887fdaf6 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -286,6 +286,28 @@ test.serial("supports req with Web APIs", async (t) => { t.deepEqual(res, queryResponse); }); +test.serial("ignores req without cookies", async (t) => { + const req = { + headers: {}, + }; + + const repositoryResponse = createRepositoryResponse(); + const queryResponse = createQueryResponse(); + + server.use( + createMockRepositoryHandler(t, repositoryResponse), + createMockQueryHandler(t, [queryResponse], undefined, { + ref: getMasterRef(repositoryResponse), + }), + ); + + const client = createTestClient(t); + client.enableAutoPreviewsFromReq(req); + const res = await client.get(); + + t.deepEqual(res, queryResponse); +}); + test.serial( "does not use preview ref if auto previews are disabled", async (t) => { From 6a0fa54a633c49985dba3d54d3caff30e7fb97cb Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 25 May 2022 13:22:04 -1000 Subject: [PATCH 5/8] test: increase test coverage --- test/client-resolvePreviewUrl.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/client-resolvePreviewUrl.test.ts b/test/client-resolvePreviewUrl.test.ts index aaaa8d46..090f882e 100644 --- a/test/client-resolvePreviewUrl.test.ts +++ b/test/client-resolvePreviewUrl.test.ts @@ -178,7 +178,7 @@ test.serial( "returns defaultURL if req does not contain preview params in server req object", async (t) => { const defaultURL = "defaultURL"; - const req = { query: {} }; + const req = {}; const client = createTestClient(t); client.enableAutoPreviewsFromReq(req); From be4c9b2077d06e5832f3cc96902a0b0449ee9c39 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 25 May 2022 13:29:16 -1000 Subject: [PATCH 6/8] test: check functionality against Express-style req with a Web API Headers-like shape --- test/client.test.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/client.test.ts b/test/client.test.ts index 887fdaf6..f6b72a8f 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -286,6 +286,34 @@ test.serial("supports req with Web APIs", async (t) => { t.deepEqual(res, queryResponse); }); +test.serial( + "correctly treats Express-style req with a Web API Headers-like shape", + async (t) => { + const previewRef = "previewRef"; + const req = { + headers: { + get: "the get property also exists in the Web API Headers API", + cookie: `io.prismic.preview=${previewRef}`, + }, + }; + + const queryResponse = createQueryResponse(); + + server.use( + createMockRepositoryHandler(t), + createMockQueryHandler(t, [queryResponse], undefined, { + ref: previewRef, + }), + ); + + const client = createTestClient(t); + client.enableAutoPreviewsFromReq(req); + const res = await client.get(); + + t.deepEqual(res, queryResponse); + }, +); + test.serial("ignores req without cookies", async (t) => { const req = { headers: {}, From 430b26b35868c84b955f1386e88f828c7fe6e149 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 25 May 2022 13:49:37 -1000 Subject: [PATCH 7/8] test: remove previously added test --- test/client.test.ts | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/test/client.test.ts b/test/client.test.ts index f6b72a8f..887fdaf6 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -286,34 +286,6 @@ test.serial("supports req with Web APIs", async (t) => { t.deepEqual(res, queryResponse); }); -test.serial( - "correctly treats Express-style req with a Web API Headers-like shape", - async (t) => { - const previewRef = "previewRef"; - const req = { - headers: { - get: "the get property also exists in the Web API Headers API", - cookie: `io.prismic.preview=${previewRef}`, - }, - }; - - const queryResponse = createQueryResponse(); - - server.use( - createMockRepositoryHandler(t), - createMockQueryHandler(t, [queryResponse], undefined, { - ref: previewRef, - }), - ); - - const client = createTestClient(t); - client.enableAutoPreviewsFromReq(req); - const res = await client.get(); - - t.deepEqual(res, queryResponse); - }, -); - test.serial("ignores req without cookies", async (t) => { const req = { headers: {}, From e6c7a4e0215ddce1ed537c75e349d589d782e479 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Wed, 25 May 2022 13:49:50 -1000 Subject: [PATCH 8/8] refactor: remove unnecessary optional chain --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 811e0bc6..fadb3d52 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1508,7 +1508,7 @@ export class Client { cookieJar = globalThis.document.cookie; } else if (this.refState.httpRequest?.headers) { if ( - "get" in this.refState.httpRequest?.headers && + "get" in this.refState.httpRequest.headers && typeof this.refState.httpRequest.headers.get === "function" ) { // Web API Headers