diff --git a/src/createClient.ts b/src/createClient.ts index 18febe10..ebb99507 100644 --- a/src/createClient.ts +++ b/src/createClient.ts @@ -18,9 +18,11 @@ import type { PrismicDocument } from "./types/value/document"; import { ForbiddenError } from "./errors/ForbiddenError"; import { NotFoundError } from "./errors/NotFoundError"; import { ParsingError } from "./errors/ParsingError"; +import { PreviewTokenExpiredError } from "./errors/PreviewTokenExpired"; import { PrismicError } from "./errors/PrismicError"; import { RefExpiredError } from "./errors/RefExpiredError"; import { RefNotFoundError } from "./errors/RefNotFoundError"; +import { RepositoryNotFoundError } from "./errors/RepositoryNotFoundError"; import { LinkResolverFunction, asLink } from "./helpers/asLink"; @@ -1911,16 +1913,28 @@ export class Client { // Not Found // - Incorrect repository name (this response has an empty body) // - Ref does not exist + // - Preview token is expired case 404: { - if (res.json && res.json.type === "api_notfound_error") { + if (res.json === undefined) { + throw new RepositoryNotFoundError( + `Prismic repository not found. Check that "${this.endpoint}" is pointing to the correct repository.`, + url, + undefined, + ); + } + + if (res.json.type === "api_notfound_error") { throw new RefNotFoundError(res.json.message, url, res.json); } - throw new NotFoundError( - `Prismic repository not found. Check that "${this.endpoint}" is pointing to the correct repository.`, - url, - undefined, // res.json is empty - ); + if ( + res.json.type === "api_security_error" && + /preview token.*expired/i.test(res.json.message) + ) { + throw new PreviewTokenExpiredError(res.json.message, url, res.json); + } + + throw new NotFoundError(res.json.message, url, res.json); } // Gone diff --git a/src/errors/PreviewTokenExpired.ts b/src/errors/PreviewTokenExpired.ts new file mode 100644 index 00000000..799ed97b --- /dev/null +++ b/src/errors/PreviewTokenExpired.ts @@ -0,0 +1,12 @@ +import { ForbiddenError } from "./ForbiddenError"; + +type PreviewTokenExpiredErrorAPIResponse = { + type: "api_security_error"; + message: string; +}; + +// This error extends `ForbiddenError` for backwards compatibility. +// TODO: Extend this error from `PrismicError` in v8. +export class PreviewTokenExpiredError< + TResponse = PreviewTokenExpiredErrorAPIResponse, +> extends ForbiddenError {} diff --git a/src/errors/RepositoryNotFoundError.ts b/src/errors/RepositoryNotFoundError.ts new file mode 100644 index 00000000..60d1af44 --- /dev/null +++ b/src/errors/RepositoryNotFoundError.ts @@ -0,0 +1,5 @@ +import { NotFoundError } from "./NotFoundError"; + +export class RepositoryNotFoundError< + TResponse = undefined, +> extends NotFoundError {} diff --git a/src/index.ts b/src/index.ts index ad63443c..d6e25b1f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -103,7 +103,9 @@ export { ForbiddenError } from "./errors/ForbiddenError"; export { NotFoundError } from "./errors/NotFoundError"; export { RefNotFoundError } from "./errors/RefNotFoundError"; export { RefExpiredError } from "./errors/RefExpiredError"; +export { PreviewTokenExpiredError } from "./errors/PreviewTokenExpired"; export { ParsingError } from "./errors/ParsingError"; +export { RepositoryNotFoundError } from "./errors/RepositoryNotFoundError"; //============================================================================= // Types - Types representing Prismic content, models, and API payloads. diff --git a/test/client.test.ts b/test/client.test.ts index a6dbb975..c6307040 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -669,7 +669,7 @@ it("throws PrismicError if response is not JSON", async (ctx) => { await expect(() => client.get()).rejects.toThrowError(prismic.PrismicError); }); -it("throws NotFoundError if repository does not exist", async (ctx) => { +it("throws RepositoryNotFoundError if repository does not exist", async (ctx) => { const client = createTestClient(); ctx.server.use( @@ -681,7 +681,9 @@ it("throws NotFoundError if repository does not exist", async (ctx) => { await expect(() => client.get()).rejects.toThrowError( /repository not found/i, ); - await expect(() => client.get()).rejects.toThrowError(prismic.NotFoundError); + await expect(() => client.get()).rejects.toThrowError( + prismic.RepositoryNotFoundError, + ); }); it("throws RefNotFoundError if ref does not exist", async (ctx) => { @@ -738,6 +740,60 @@ it("throws RefExpiredError if ref is expired", async (ctx) => { ); }); +it("throws PreviewTokenExpiredError if preview token is expired", async (ctx) => { + const queryResponse = { + type: "api_security_error", + message: "This preview token has expired", + oauth_initiate: "https://qwerty.prismic.io/auth", + oauth_token: "https://qwerty.prismic.io/auth/token", + }; + + mockPrismicRestAPIV2({ ctx }); + + const client = createTestClient(); + + const queryEndpoint = new URL( + "./documents/search", + `${client.endpoint}/`, + ).toString(); + + ctx.server.use( + msw.rest.get(queryEndpoint, (_req, res, ctx) => { + return res(ctx.status(404), ctx.json(queryResponse)); + }), + ); + + await expect(() => client.get()).rejects.toThrowError(queryResponse.message); + await expect(() => client.get()).rejects.toThrowError( + prismic.PreviewTokenExpiredError, + ); +}); + +it("throws NotFoundError if the 404 error is unknown", async (ctx) => { + const queryResponse = { + type: "unknown_type", + message: "message", + }; + + mockPrismicRestAPIV2({ ctx }); + + const client = createTestClient(); + + const queryEndpoint = new URL( + "./documents/search", + `${client.endpoint}/`, + ).toString(); + + ctx.server.use( + msw.rest.get(queryEndpoint, (_req, res, ctx) => { + return res(ctx.status(404), ctx.json(queryResponse)); + }), + ); + + await expect(() => client.get()).rejects.toThrowError(queryResponse.message); + await expect(() => client.get()).rejects.toThrowError(prismic.NotFoundError); +}); + it("retries after `retry-after` milliseconds if response code is 429", async (ctx) => { const retryAfter = 200; // ms /**