@scayle/storefront-cms-contentful 0.2.0-alpha.0 → 1.0.0-alpha.2
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/README.md +3 -12
- package/dist/index.d.mts +8 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +12 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -71,20 +71,11 @@ export const createCMSService = (storefront: StorefrontContext) =>
|
|
|
71
71
|
The `storefront` context is used for the service's own application-level caching.
|
|
72
72
|
|
|
73
73
|
The boilerplate registers `createCMSService(storefront)` as the `cms` slot on the `ServiceRegistry`
|
|
74
|
-
(`src/server/registries.ts`), alongside every other domain. A controller resolves it from there
|
|
75
|
-
`
|
|
76
|
-
then calls `fetchPageData`/`fetchListingPageData` directly with the raw request:
|
|
74
|
+
(`src/server/registries.ts`), alongside every other domain. A controller resolves it from there and
|
|
75
|
+
calls `fetchPageData`/`fetchListingPageData` with `this.request` (the inbound `IncomingRequest`):
|
|
77
76
|
|
|
78
77
|
```ts
|
|
79
|
-
const
|
|
80
|
-
this.services.cms,
|
|
81
|
-
this.ctx.req,
|
|
82
|
-
)
|
|
83
|
-
const pageData = await this.services.cms.fetchPageData(
|
|
84
|
-
slug,
|
|
85
|
-
locale,
|
|
86
|
-
this.ctx.req,
|
|
87
|
-
)
|
|
78
|
+
const pageData = await this.services.cms.fetchPageData(slug, this.request)
|
|
88
79
|
```
|
|
89
80
|
|
|
90
81
|
See `@scayle/storefront`'s CMS module for the `CMSProviderService` contract this package implements.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EntryCollection, EntrySkeletonType } from "contentful";
|
|
2
|
-
import { CMSCspConfig, CMSEditorData, CMSPagePayload, CMSProviderService
|
|
3
|
-
import { StorefrontContext } from "@scayle/storefront/types";
|
|
2
|
+
import { CMSCspConfig, CMSEditorData, CMSPagePayload, CMSProviderService } from "@scayle/storefront/cms";
|
|
3
|
+
import { IncomingRequest, StorefrontContext } from "@scayle/storefront/types";
|
|
4
4
|
/**
|
|
5
5
|
* Configuration for ContentfulCMSService.
|
|
6
6
|
*/
|
|
@@ -53,19 +53,18 @@ declare class ContentfulCMSService implements CMSProviderService {
|
|
|
53
53
|
private getDeliveryClient;
|
|
54
54
|
private getPreviewClient;
|
|
55
55
|
private getClient;
|
|
56
|
-
isEditorMode(request:
|
|
57
|
-
getCMSEditorData(request:
|
|
56
|
+
isEditorMode(request: IncomingRequest): boolean;
|
|
57
|
+
getCMSEditorData(request: IncomingRequest): CMSEditorData | undefined;
|
|
58
58
|
private fetchPage;
|
|
59
59
|
/**
|
|
60
60
|
* Retrieves CMS page data with application-level caching.
|
|
61
61
|
* Draft content requests bypass the cache entirely.
|
|
62
62
|
*
|
|
63
63
|
* @param slug CMS slug
|
|
64
|
-
* @param
|
|
65
|
-
* @param request Incoming request, used to detect editor mode and read editor-specific query params
|
|
64
|
+
* @param request Inbound request, used to detect editor mode and read editor-specific query params
|
|
66
65
|
* @returns Contentful page data or undefined
|
|
67
66
|
*/
|
|
68
|
-
fetchPageData(slug: string,
|
|
67
|
+
fetchPageData(slug: string, request: IncomingRequest): Promise<CMSPagePayload | undefined>;
|
|
69
68
|
private fetchListing;
|
|
70
69
|
/**
|
|
71
70
|
* Retrieves CMS content for a product listing page, with caching.
|
|
@@ -73,11 +72,10 @@ declare class ContentfulCMSService implements CMSProviderService {
|
|
|
73
72
|
* missing PLP content is expected, not an error condition.
|
|
74
73
|
*
|
|
75
74
|
* @param categoryId SCAYLE category ID
|
|
76
|
-
* @param
|
|
77
|
-
* @param request Incoming request, used to detect editor mode and read editor-specific query params
|
|
75
|
+
* @param request Inbound request, used to detect editor mode and read editor-specific query params
|
|
78
76
|
* @returns Contentful listing page data or undefined
|
|
79
77
|
*/
|
|
80
|
-
fetchListingPageData(categoryId: number,
|
|
78
|
+
fetchListingPageData(categoryId: number, request: IncomingRequest): Promise<CMSPagePayload | undefined>;
|
|
81
79
|
/**
|
|
82
80
|
* Builds the Contentful `getEntries` query for a page lookup.
|
|
83
81
|
* Override to query a different content type or field name than the
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/ContentfulCMSService.ts","../src/csp.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/ContentfulCMSService.ts","../src/csp.ts"],"mappings":";;;;;;UA+BiB;;EAEf;;EAEA;;EAEA;;EAEA;;;;;;;;;;;;;;;;;;;;;iBA6Bc,0BAA0B;;;;;;;cAuB7B,gCAAgC;iBAC5B;iBAEA;WAGN,WAAW;mBAEH;mBACA;mBACA;mBACA;mBACA;EAEjB,YAAY,QAAQ,4BAA4B,SAAS;UA0BjD;UAiBA;UAkBA;EAIR,aAAa,SAAS;EAItB,iBAAiB,SAAS,kBAAkB;UAQ9B;;;;;;;;;EAqCd,cACE,cACA,SAAS,kBACR,QAAQ;UAsBG;;;;;;;;;;EA2Bd,qBACE,oBACA,SAAS,kBACR,QAAQ;;;;;;;;;;YAkDD,eACR,cACA,6BACC;;;;;;;;;;YAsBO,kBACR,cACA,6BACC;UAaW;UAUA;;;;;;;;;;YAsCJ,sBACR,UAAU,gBAAgB,oBAC1B,2BACC;;;;;;;cC5YQ,WAAW"}
|
package/dist/index.mjs
CHANGED
|
@@ -3,15 +3,13 @@ import { createClient } from "contentful";
|
|
|
3
3
|
import resolveResponse from "contentful-resolve-response";
|
|
4
4
|
import { CMSContentNotFoundError, extractErrorStatus, wrapClientInit } from "@scayle/storefront/cms";
|
|
5
5
|
import { createLogger } from "@scayle/storefront/shared";
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
};
|
|
6
|
+
const isContentfulPreviewRequest = (request) => Boolean(request.query._editorMode);
|
|
7
|
+
const cspConfig = { directives: {
|
|
8
|
+
"frame-ancestors": "'self' https://app.contentful.com",
|
|
9
|
+
"script-src": "'self' 'unsafe-inline' https://app.contentful.com",
|
|
10
|
+
"worker-src": "'self' blob:",
|
|
11
|
+
"connect-src": "'self' https://cdn.contentful.com https://preview.contentful.com"
|
|
12
|
+
} };
|
|
15
13
|
const log = createLogger("cms");
|
|
16
14
|
const CACHE_TTL_SECONDS = 300;
|
|
17
15
|
function isUnconfiguredLocaleError(error) {
|
|
@@ -61,7 +59,7 @@ var ContentfulCMSService = class ContentfulCMSService {
|
|
|
61
59
|
return useDraftContent ? this.getPreviewClient() : this.getDeliveryClient();
|
|
62
60
|
}
|
|
63
61
|
isEditorMode(request) {
|
|
64
|
-
return
|
|
62
|
+
return isContentfulPreviewRequest(request);
|
|
65
63
|
}
|
|
66
64
|
getCMSEditorData(request) {
|
|
67
65
|
if (!this.isEditorMode(request)) return;
|
|
@@ -85,7 +83,8 @@ var ContentfulCMSService = class ContentfulCMSService {
|
|
|
85
83
|
throw new Error(`Contentful API request failed (slug: "${slug}", locale: "${locale}", content-type: "PageComponent"): ${cause.message}`, { cause });
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
|
-
async fetchPageData(slug,
|
|
86
|
+
async fetchPageData(slug, request) {
|
|
87
|
+
const locale = this.context.country.locale;
|
|
89
88
|
const useDraftContent = this.isEditorMode(request) && this.draftContentEnabled;
|
|
90
89
|
if (useDraftContent) {
|
|
91
90
|
log.debug({
|
|
@@ -107,7 +106,8 @@ var ContentfulCMSService = class ContentfulCMSService {
|
|
|
107
106
|
throw error;
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
|
-
async fetchListingPageData(categoryId,
|
|
109
|
+
async fetchListingPageData(categoryId, request) {
|
|
110
|
+
const locale = this.context.country.locale;
|
|
111
111
|
const useDraftContent = this.isEditorMode(request) && this.draftContentEnabled;
|
|
112
112
|
const fetchListing = async () => {
|
|
113
113
|
try {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/csp.ts","../src/ContentfulCMSService.ts"],"sourcesContent":["import type { CMSCspConfig, CMSRequestLike } from '@scayle/storefront/cms'\n\n/**\n * CSP configuration for the Contentful provider.\n * Allows the Contentful web app to embed the storefront in an iframe\n * and connect to the delivery and preview APIs.\n */\nexport const cspConfig: CMSCspConfig = {\n isPreviewRequest: (req: CMSRequestLike) => Boolean(req.query('_editorMode')),\n directives: {\n // Allows Contentful to embed the storefront in an iframe for live preview.\n // https://www.contentful.com/developers/docs/tutorials/general/live-preview/\n 'frame-ancestors': \"'self' https://app.contentful.com\",\n // Allows Contentful editor scripts and Inertia inline hydration scripts.\n // 'unsafe-inline' is required because Inertia injects inline scripts for\n // SSR page data, and CSP may not include it when no upstream script-src exists.\n 'script-src': \"'self' 'unsafe-inline' https://app.contentful.com\",\n // The @contentful/live-preview SDK creates a Web Worker from a blob URL\n // for processing live update messages. Without worker-src, CSP falls back\n // to script-src which does not allow blob: origins.\n 'worker-src': \"'self' blob:\",\n // Allows the Live Preview SDK to fetch entry data from the Contentful\n // Content Delivery API (cdn) and Content Preview API (preview).\n // https://www.contentful.com/developers/docs/references/content-delivery-api/\n // https://www.contentful.com/developers/docs/references/content-preview-api/\n 'connect-src':\n \"'self' https://cdn.contentful.com https://preview.contentful.com\",\n },\n}\n","import https from 'node:https'\nimport { createClient } from 'contentful'\nimport resolveResponse from 'contentful-resolve-response'\nimport type {\n ContentfulClientApi,\n EntryCollection,\n EntrySkeletonType,\n} from 'contentful'\nimport {\n CMSContentNotFoundError,\n extractErrorStatus,\n wrapClientInit,\n} from '@scayle/storefront/cms'\nimport type {\n CMSCspConfig,\n CMSEditorData,\n CMSPagePayload,\n CMSProviderService,\n CMSRequestLike,\n} from '@scayle/storefront/cms'\nimport { createLogger } from '@scayle/storefront/shared'\nimport type { StorefrontContext } from '@scayle/storefront/types'\nimport { cspConfig } from './csp'\n\nconst log = createLogger('cms')\n\n/**\n * Configuration for ContentfulCMSService.\n */\nexport interface ContentfulCMSServiceConfig {\n /** Contentful delivery access token. */\n accessToken: string\n /** Contentful space ID. */\n space: string\n /** Contentful preview access token for draft content. */\n previewAccessToken: string\n /** Enable draft content access when in editor mode. */\n draftContentEnabled: boolean\n}\n\n/** Cache TTL for published CMS content: 5 minutes. */\nconst CACHE_TTL_SECONDS = 5 * 60\n\ntype ContentfulReadClient =\n | ContentfulClientApi<'WITHOUT_UNRESOLVABLE_LINKS'>\n | ContentfulClientApi<'WITHOUT_LINK_RESOLUTION'>\n\n/**\n * Determines whether a Contentful error indicates an unconfigured locale.\n * Contentful returns a `BadRequest` error with `\"Unknown locale: ...\"` when\n * the requested locale does not exist in the space.\n *\n * @param error Error to inspect\n * @returns true when the error is a locale-related BadRequest\n *\n * @example\n * ```ts\n * try {\n * await client.getEntries({ locale: 'en-DE' })\n * } catch (error) {\n * if (isUnconfiguredLocaleError(error)) {\n * // retry without locale\n * }\n * }\n * ```\n */\nexport function isUnconfiguredLocaleError(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false\n }\n\n if (\n 'name' in error &&\n 'message' in error &&\n error.name === 'BadRequest' &&\n typeof error.message === 'string'\n ) {\n return error.message.includes('Unknown locale')\n }\n\n return false\n}\n\n/**\n * Contentful CMS provider service.\n * Manages two lazy singleton API clients (delivery and preview).\n *\n * @see https://www.contentful.com/developers/docs/references/content-delivery-api/\n */\nexport class ContentfulCMSService implements CMSProviderService {\n private static deliveryClient:\n ContentfulClientApi<'WITHOUT_UNRESOLVABLE_LINKS'> | undefined\n private static previewClient:\n ContentfulClientApi<'WITHOUT_LINK_RESOLUTION'> | undefined\n\n readonly cspConfig: CMSCspConfig = cspConfig\n\n private readonly accessToken: string\n private readonly space: string\n private readonly previewAccessToken: string\n private readonly draftContentEnabled: boolean\n private readonly context: StorefrontContext\n\n constructor(config: ContentfulCMSServiceConfig, context: StorefrontContext) {\n if (!config.accessToken) {\n throw new Error(\n 'Contentful CMS initialization failed: missing or empty access token. Check that CONTENTFUL_CMS_ACCESS_TOKEN is set.',\n )\n }\n\n if (!config.space) {\n throw new Error(\n 'Contentful CMS initialization failed: missing or empty space ID. Check that CONTENTFUL_CMS_SPACE is set.',\n )\n }\n\n if (!config.previewAccessToken) {\n throw new Error(\n 'Contentful CMS initialization failed: missing or empty preview access token. Check that CONTENTFUL_CMS_PREVIEW_ACCESS_TOKEN is set and different from the delivery token (CONTENTFUL_CMS_ACCESS_TOKEN).',\n )\n }\n\n this.accessToken = config.accessToken\n this.space = config.space\n this.previewAccessToken = config.previewAccessToken\n this.draftContentEnabled = config.draftContentEnabled\n this.context = context\n }\n\n private getDeliveryClient(): ContentfulClientApi<'WITHOUT_UNRESOLVABLE_LINKS'> {\n if (ContentfulCMSService.deliveryClient) {\n return ContentfulCMSService.deliveryClient\n }\n\n ContentfulCMSService.deliveryClient = wrapClientInit(\n () =>\n createClient({\n accessToken: this.accessToken,\n space: this.space,\n httpsAgent: new https.Agent({ keepAlive: true }),\n }).withoutUnresolvableLinks,\n 'Contentful delivery client initialization failed. Check that CONTENTFUL_CMS_SPACE is a valid space ID (typically a 20-character alphanumeric string) and CONTENTFUL_CMS_ACCESS_TOKEN has delivery permissions',\n )\n return ContentfulCMSService.deliveryClient\n }\n\n private getPreviewClient(): ContentfulClientApi<'WITHOUT_LINK_RESOLUTION'> {\n if (ContentfulCMSService.previewClient) {\n return ContentfulCMSService.previewClient\n }\n\n ContentfulCMSService.previewClient = wrapClientInit(\n () =>\n createClient({\n accessToken: this.previewAccessToken,\n space: this.space,\n host: 'preview.contentful.com',\n httpsAgent: new https.Agent({ keepAlive: true }),\n }).withoutLinkResolution,\n 'Contentful preview client initialization failed. Check that CONTENTFUL_CMS_SPACE is a valid space ID and CONTENTFUL_CMS_PREVIEW_ACCESS_TOKEN has preview permissions',\n )\n return ContentfulCMSService.previewClient\n }\n\n private getClient(useDraftContent: boolean): ContentfulReadClient {\n return useDraftContent ? this.getPreviewClient() : this.getDeliveryClient()\n }\n\n isEditorMode(request: CMSRequestLike): boolean {\n return Boolean(request.query('_editorMode'))\n }\n\n getCMSEditorData(request: CMSRequestLike): CMSEditorData | undefined {\n if (!this.isEditorMode(request)) {\n return undefined\n }\n\n return {}\n }\n\n private async fetchPage(\n slug: string,\n locale: string,\n useDraftContent: boolean,\n ): Promise<CMSPagePayload | undefined> {\n const client = this.getClient(useDraftContent)\n\n try {\n return await this.fetchPageEntries(client, slug, locale, useDraftContent)\n } catch (error) {\n if (isUnconfiguredLocaleError(error)) {\n return this.fetchPageEntries(client, slug, undefined, useDraftContent)\n }\n\n if (extractErrorStatus(error) === 404) {\n throw new CMSContentNotFoundError('Contentful content not found', {\n cause: error,\n details: { slug, locale, contentType: 'PageComponent' },\n })\n }\n\n const cause = error instanceof Error ? error : new Error(String(error))\n throw new Error(\n `Contentful API request failed (slug: \"${slug}\", locale: \"${locale}\", content-type: \"PageComponent\"): ${cause.message}`,\n { cause },\n )\n }\n }\n\n /**\n * Retrieves CMS page data with application-level caching.\n * Draft content requests bypass the cache entirely.\n *\n * @param slug CMS slug\n * @param locale Storefront locale code\n * @param request Incoming request, used to detect editor mode and read editor-specific query params\n * @returns Contentful page data or undefined\n */\n async fetchPageData(\n slug: string,\n locale: string,\n request: CMSRequestLike,\n ): Promise<CMSPagePayload | undefined> {\n const useDraftContent =\n this.isEditorMode(request) && this.draftContentEnabled\n\n if (useDraftContent) {\n log.debug({\n message: 'Bypassing CMS cache in preview mode',\n slug,\n locale,\n })\n return this.fetchPage(slug, locale, useDraftContent)\n }\n\n return this.context.cache.getOrSet(\n `cms:contentful:page:${slug}:${locale}`,\n // Cast required: StorageValue excludes undefined, but the fetch can return undefined for missing content\n () => this.fetchPage(slug, locale, useDraftContent) as never,\n CACHE_TTL_SECONDS,\n )\n }\n\n private async fetchListing(\n categoryId: number,\n locale: string,\n useDraftContent: boolean,\n ): Promise<CMSPagePayload | undefined> {\n const slug = `c/c-${categoryId}`\n const client = this.getClient(useDraftContent)\n\n try {\n return await this.getListingEntries(client, slug, locale, useDraftContent)\n } catch (error) {\n if (isUnconfiguredLocaleError(error)) {\n return this.getListingEntries(client, slug, undefined, useDraftContent)\n }\n throw error\n }\n }\n\n /**\n * Retrieves CMS content for a product listing page, with caching.\n * Converts a missing entry into `undefined` rather than throwing, since\n * missing PLP content is expected, not an error condition.\n *\n * @param categoryId SCAYLE category ID\n * @param locale Storefront locale code\n * @param request Incoming request, used to detect editor mode and read editor-specific query params\n * @returns Contentful listing page data or undefined\n */\n async fetchListingPageData(\n categoryId: number,\n locale: string,\n request: CMSRequestLike,\n ): Promise<CMSPagePayload | undefined> {\n const useDraftContent =\n this.isEditorMode(request) && this.draftContentEnabled\n\n const fetchListing = async () => {\n try {\n return await this.fetchListing(categoryId, locale, useDraftContent)\n } catch (error) {\n if (error instanceof CMSContentNotFoundError) {\n return undefined\n }\n\n log.error({\n message: 'Failed to fetch CMS listing page data',\n err: error instanceof Error ? error : new Error(String(error)),\n categoryId,\n locale,\n })\n\n throw error\n }\n }\n\n if (useDraftContent) {\n log.debug({\n message: 'Bypassing CMS PLP cache in preview mode',\n categoryId,\n locale,\n })\n return fetchListing()\n }\n\n return this.context.cache.getOrSet(\n `cms:contentful:plp:${categoryId}:${locale}`,\n // Cast required: StorageValue excludes undefined, but the fetch can return undefined for missing content\n () => fetchListing() as never,\n CACHE_TTL_SECONDS,\n )\n }\n\n /**\n * Builds the Contentful `getEntries` query for a page lookup.\n * Override to query a different content type or field name than the\n * default `PageComponent` / `fields.slug[match]` shape.\n *\n * @param slug Page slug\n * @param locale Storefront locale code, or undefined on the no-locale retry\n * @returns Query object passed to `client.getEntries(...)`\n */\n protected buildPageQuery(\n slug: string,\n locale: string | undefined,\n ): Record<string, unknown> {\n const query: Record<string, unknown> = {\n content_type: 'PageComponent',\n 'fields.slug[match]': slug,\n include: 10,\n limit: 1,\n }\n if (locale) {\n query.locale = locale\n }\n return query\n }\n\n /**\n * Builds the Contentful `getEntries` query for a listing-page lookup.\n * Override to query a different content type or field name than the\n * default `productListingPageComponent` / `fields.slug[match]` shape.\n *\n * @param slug Synthetic `c/c-{categoryId}` slug\n * @param locale Storefront locale code, or undefined on the no-locale retry\n * @returns Query object passed to `client.getEntries(...)`\n */\n protected buildListingQuery(\n slug: string,\n locale: string | undefined,\n ): Record<string, unknown> {\n const query: Record<string, unknown> = {\n content_type: 'productListingPageComponent',\n 'fields.slug[match]': slug,\n include: 10,\n limit: 1,\n }\n if (locale) {\n query.locale = locale\n }\n return query\n }\n\n private async fetchPageEntries(\n client: ContentfulReadClient,\n slug: string,\n locale: string | undefined,\n useDraftContent: boolean,\n ): Promise<CMSPagePayload | undefined> {\n const response = await client.getEntries(this.buildPageQuery(slug, locale))\n return this.transformPageResponse(response, useDraftContent)\n }\n\n private async getListingEntries(\n client: ContentfulReadClient,\n slug: string,\n locale: string | undefined,\n useDraftContent: boolean,\n ): Promise<CMSPagePayload | undefined> {\n try {\n const response = await client.getEntries(\n this.buildListingQuery(slug, locale),\n )\n return this.transformPageResponse(response, useDraftContent)\n } catch (error) {\n if (extractErrorStatus(error) === 404) {\n throw new CMSContentNotFoundError(\n 'Contentful listing content not found',\n {\n cause: error,\n details: {\n slug,\n locale,\n contentType: 'productListingPageComponent',\n },\n },\n )\n }\n throw error\n }\n }\n\n /**\n * Transforms a Contentful entry collection into the page payload.\n * Override to change what shape `fetchPageData`/`fetchListingPageData` return,\n * for example to keep additional response fields.\n *\n * @param response Raw Contentful entry collection\n * @param useDraftContent Whether this is a draft content fetch\n * @returns Page payload, or undefined when the collection has no entries\n */\n protected transformPageResponse(\n response: EntryCollection<EntrySkeletonType>,\n useDraftContent: boolean,\n ): CMSPagePayload | undefined {\n const entry = response.items.at(0)\n if (!entry) {\n return undefined\n }\n if (!useDraftContent) {\n return { entry }\n }\n\n const [resolvedEntry] = resolveResponse({\n items: response.items,\n includes: response.includes,\n })\n\n return {\n entry: resolvedEntry ?? entry,\n previewSource: { entry, includes: response.includes },\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/csp.ts","../src/ContentfulCMSService.ts"],"sourcesContent":["import type { CMSCspConfig } from '@scayle/storefront/cms'\nimport type { IncomingRequest } from '@scayle/storefront/types'\n\n/**\n * Checks whether the request is a Contentful Live Preview session.\n *\n * Contentful injects `_editorMode` into the iframe URL; both editor-mode\n * detection and preview CSP use this same predicate.\n *\n * @param request Inbound request\n * @returns True when `_editorMode` is present on the query string\n */\nexport const isContentfulPreviewRequest = (request: IncomingRequest): boolean =>\n Boolean(request.query._editorMode)\n\n/**\n * CSP configuration for the Contentful provider.\n * Allows the Contentful web app to embed the storefront in an iframe\n * and connect to the delivery and preview APIs.\n */\nexport const cspConfig: CMSCspConfig = {\n directives: {\n // Allows Contentful to embed the storefront in an iframe for live preview.\n // https://www.contentful.com/developers/docs/tutorials/general/live-preview/\n 'frame-ancestors': \"'self' https://app.contentful.com\",\n // Allows Contentful editor scripts and Inertia inline hydration scripts.\n // 'unsafe-inline' is required because Inertia injects inline scripts for\n // SSR page data, and CSP may not include it when no upstream script-src exists.\n 'script-src': \"'self' 'unsafe-inline' https://app.contentful.com\",\n // The @contentful/live-preview SDK creates a Web Worker from a blob URL\n // for processing live update messages. Without worker-src, CSP falls back\n // to script-src which does not allow blob: origins.\n 'worker-src': \"'self' blob:\",\n // Allows the Live Preview SDK to fetch entry data from the Contentful\n // Content Delivery API (cdn) and Content Preview API (preview).\n // https://www.contentful.com/developers/docs/references/content-delivery-api/\n // https://www.contentful.com/developers/docs/references/content-preview-api/\n 'connect-src':\n \"'self' https://cdn.contentful.com https://preview.contentful.com\",\n },\n}\n","import https from 'node:https'\nimport { createClient } from 'contentful'\nimport resolveResponse from 'contentful-resolve-response'\nimport type {\n ContentfulClientApi,\n EntryCollection,\n EntrySkeletonType,\n} from 'contentful'\nimport {\n CMSContentNotFoundError,\n extractErrorStatus,\n wrapClientInit,\n} from '@scayle/storefront/cms'\nimport type {\n CMSCspConfig,\n CMSEditorData,\n CMSPagePayload,\n CMSProviderService,\n} from '@scayle/storefront/cms'\nimport { createLogger } from '@scayle/storefront/shared'\nimport type {\n IncomingRequest,\n StorefrontContext,\n} from '@scayle/storefront/types'\nimport { cspConfig, isContentfulPreviewRequest } from './csp'\n\nconst log = createLogger('cms')\n\n/**\n * Configuration for ContentfulCMSService.\n */\nexport interface ContentfulCMSServiceConfig {\n /** Contentful delivery access token. */\n accessToken: string\n /** Contentful space ID. */\n space: string\n /** Contentful preview access token for draft content. */\n previewAccessToken: string\n /** Enable draft content access when in editor mode. */\n draftContentEnabled: boolean\n}\n\n/** Cache TTL for published CMS content: 5 minutes. */\nconst CACHE_TTL_SECONDS = 5 * 60\n\ntype ContentfulReadClient =\n | ContentfulClientApi<'WITHOUT_UNRESOLVABLE_LINKS'>\n | ContentfulClientApi<'WITHOUT_LINK_RESOLUTION'>\n\n/**\n * Determines whether a Contentful error indicates an unconfigured locale.\n * Contentful returns a `BadRequest` error with `\"Unknown locale: ...\"` when\n * the requested locale does not exist in the space.\n *\n * @param error Error to inspect\n * @returns true when the error is a locale-related BadRequest\n *\n * @example\n * ```ts\n * try {\n * await client.getEntries({ locale: 'en-DE' })\n * } catch (error) {\n * if (isUnconfiguredLocaleError(error)) {\n * // retry without locale\n * }\n * }\n * ```\n */\nexport function isUnconfiguredLocaleError(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false\n }\n\n if (\n 'name' in error &&\n 'message' in error &&\n error.name === 'BadRequest' &&\n typeof error.message === 'string'\n ) {\n return error.message.includes('Unknown locale')\n }\n\n return false\n}\n\n/**\n * Contentful CMS provider service.\n * Manages two lazy singleton API clients (delivery and preview).\n *\n * @see https://www.contentful.com/developers/docs/references/content-delivery-api/\n */\nexport class ContentfulCMSService implements CMSProviderService {\n private static deliveryClient:\n ContentfulClientApi<'WITHOUT_UNRESOLVABLE_LINKS'> | undefined\n private static previewClient:\n ContentfulClientApi<'WITHOUT_LINK_RESOLUTION'> | undefined\n\n readonly cspConfig: CMSCspConfig = cspConfig\n\n private readonly accessToken: string\n private readonly space: string\n private readonly previewAccessToken: string\n private readonly draftContentEnabled: boolean\n private readonly context: StorefrontContext\n\n constructor(config: ContentfulCMSServiceConfig, context: StorefrontContext) {\n if (!config.accessToken) {\n throw new Error(\n 'Contentful CMS initialization failed: missing or empty access token. Check that CONTENTFUL_CMS_ACCESS_TOKEN is set.',\n )\n }\n\n if (!config.space) {\n throw new Error(\n 'Contentful CMS initialization failed: missing or empty space ID. Check that CONTENTFUL_CMS_SPACE is set.',\n )\n }\n\n if (!config.previewAccessToken) {\n throw new Error(\n 'Contentful CMS initialization failed: missing or empty preview access token. Check that CONTENTFUL_CMS_PREVIEW_ACCESS_TOKEN is set and different from the delivery token (CONTENTFUL_CMS_ACCESS_TOKEN).',\n )\n }\n\n this.accessToken = config.accessToken\n this.space = config.space\n this.previewAccessToken = config.previewAccessToken\n this.draftContentEnabled = config.draftContentEnabled\n this.context = context\n }\n\n private getDeliveryClient(): ContentfulClientApi<'WITHOUT_UNRESOLVABLE_LINKS'> {\n if (ContentfulCMSService.deliveryClient) {\n return ContentfulCMSService.deliveryClient\n }\n\n ContentfulCMSService.deliveryClient = wrapClientInit(\n () =>\n createClient({\n accessToken: this.accessToken,\n space: this.space,\n httpsAgent: new https.Agent({ keepAlive: true }),\n }).withoutUnresolvableLinks,\n 'Contentful delivery client initialization failed. Check that CONTENTFUL_CMS_SPACE is a valid space ID (typically a 20-character alphanumeric string) and CONTENTFUL_CMS_ACCESS_TOKEN has delivery permissions',\n )\n return ContentfulCMSService.deliveryClient\n }\n\n private getPreviewClient(): ContentfulClientApi<'WITHOUT_LINK_RESOLUTION'> {\n if (ContentfulCMSService.previewClient) {\n return ContentfulCMSService.previewClient\n }\n\n ContentfulCMSService.previewClient = wrapClientInit(\n () =>\n createClient({\n accessToken: this.previewAccessToken,\n space: this.space,\n host: 'preview.contentful.com',\n httpsAgent: new https.Agent({ keepAlive: true }),\n }).withoutLinkResolution,\n 'Contentful preview client initialization failed. Check that CONTENTFUL_CMS_SPACE is a valid space ID and CONTENTFUL_CMS_PREVIEW_ACCESS_TOKEN has preview permissions',\n )\n return ContentfulCMSService.previewClient\n }\n\n private getClient(useDraftContent: boolean): ContentfulReadClient {\n return useDraftContent ? this.getPreviewClient() : this.getDeliveryClient()\n }\n\n isEditorMode(request: IncomingRequest): boolean {\n return isContentfulPreviewRequest(request)\n }\n\n getCMSEditorData(request: IncomingRequest): CMSEditorData | undefined {\n if (!this.isEditorMode(request)) {\n return undefined\n }\n\n return {}\n }\n\n private async fetchPage(\n slug: string,\n locale: string,\n useDraftContent: boolean,\n ): Promise<CMSPagePayload | undefined> {\n const client = this.getClient(useDraftContent)\n\n try {\n return await this.fetchPageEntries(client, slug, locale, useDraftContent)\n } catch (error) {\n if (isUnconfiguredLocaleError(error)) {\n return this.fetchPageEntries(client, slug, undefined, useDraftContent)\n }\n\n if (extractErrorStatus(error) === 404) {\n throw new CMSContentNotFoundError('Contentful content not found', {\n cause: error,\n details: { slug, locale, contentType: 'PageComponent' },\n })\n }\n\n const cause = error instanceof Error ? error : new Error(String(error))\n throw new Error(\n `Contentful API request failed (slug: \"${slug}\", locale: \"${locale}\", content-type: \"PageComponent\"): ${cause.message}`,\n { cause },\n )\n }\n }\n\n /**\n * Retrieves CMS page data with application-level caching.\n * Draft content requests bypass the cache entirely.\n *\n * @param slug CMS slug\n * @param request Inbound request, used to detect editor mode and read editor-specific query params\n * @returns Contentful page data or undefined\n */\n async fetchPageData(\n slug: string,\n request: IncomingRequest,\n ): Promise<CMSPagePayload | undefined> {\n const locale = this.context.country.locale\n const useDraftContent =\n this.isEditorMode(request) && this.draftContentEnabled\n\n if (useDraftContent) {\n log.debug({\n message: 'Bypassing CMS cache in preview mode',\n slug,\n locale,\n })\n return this.fetchPage(slug, locale, useDraftContent)\n }\n\n return this.context.cache.getOrSet(\n `cms:contentful:page:${slug}:${locale}`,\n // Cast required: StorageValue excludes undefined, but the fetch can return undefined for missing content\n () => this.fetchPage(slug, locale, useDraftContent) as never,\n CACHE_TTL_SECONDS,\n )\n }\n\n private async fetchListing(\n categoryId: number,\n locale: string,\n useDraftContent: boolean,\n ): Promise<CMSPagePayload | undefined> {\n const slug = `c/c-${categoryId}`\n const client = this.getClient(useDraftContent)\n\n try {\n return await this.getListingEntries(client, slug, locale, useDraftContent)\n } catch (error) {\n if (isUnconfiguredLocaleError(error)) {\n return this.getListingEntries(client, slug, undefined, useDraftContent)\n }\n throw error\n }\n }\n\n /**\n * Retrieves CMS content for a product listing page, with caching.\n * Converts a missing entry into `undefined` rather than throwing, since\n * missing PLP content is expected, not an error condition.\n *\n * @param categoryId SCAYLE category ID\n * @param request Inbound request, used to detect editor mode and read editor-specific query params\n * @returns Contentful listing page data or undefined\n */\n async fetchListingPageData(\n categoryId: number,\n request: IncomingRequest,\n ): Promise<CMSPagePayload | undefined> {\n const locale = this.context.country.locale\n const useDraftContent =\n this.isEditorMode(request) && this.draftContentEnabled\n\n const fetchListing = async () => {\n try {\n return await this.fetchListing(categoryId, locale, useDraftContent)\n } catch (error) {\n if (error instanceof CMSContentNotFoundError) {\n return undefined\n }\n\n log.error({\n message: 'Failed to fetch CMS listing page data',\n err: error instanceof Error ? error : new Error(String(error)),\n categoryId,\n locale,\n })\n\n throw error\n }\n }\n\n if (useDraftContent) {\n log.debug({\n message: 'Bypassing CMS PLP cache in preview mode',\n categoryId,\n locale,\n })\n return fetchListing()\n }\n\n return this.context.cache.getOrSet(\n `cms:contentful:plp:${categoryId}:${locale}`,\n // Cast required: StorageValue excludes undefined, but the fetch can return undefined for missing content\n () => fetchListing() as never,\n CACHE_TTL_SECONDS,\n )\n }\n\n /**\n * Builds the Contentful `getEntries` query for a page lookup.\n * Override to query a different content type or field name than the\n * default `PageComponent` / `fields.slug[match]` shape.\n *\n * @param slug Page slug\n * @param locale Storefront locale code, or undefined on the no-locale retry\n * @returns Query object passed to `client.getEntries(...)`\n */\n protected buildPageQuery(\n slug: string,\n locale: string | undefined,\n ): Record<string, unknown> {\n const query: Record<string, unknown> = {\n content_type: 'PageComponent',\n 'fields.slug[match]': slug,\n include: 10,\n limit: 1,\n }\n if (locale) {\n query.locale = locale\n }\n return query\n }\n\n /**\n * Builds the Contentful `getEntries` query for a listing-page lookup.\n * Override to query a different content type or field name than the\n * default `productListingPageComponent` / `fields.slug[match]` shape.\n *\n * @param slug Synthetic `c/c-{categoryId}` slug\n * @param locale Storefront locale code, or undefined on the no-locale retry\n * @returns Query object passed to `client.getEntries(...)`\n */\n protected buildListingQuery(\n slug: string,\n locale: string | undefined,\n ): Record<string, unknown> {\n const query: Record<string, unknown> = {\n content_type: 'productListingPageComponent',\n 'fields.slug[match]': slug,\n include: 10,\n limit: 1,\n }\n if (locale) {\n query.locale = locale\n }\n return query\n }\n\n private async fetchPageEntries(\n client: ContentfulReadClient,\n slug: string,\n locale: string | undefined,\n useDraftContent: boolean,\n ): Promise<CMSPagePayload | undefined> {\n const response = await client.getEntries(this.buildPageQuery(slug, locale))\n return this.transformPageResponse(response, useDraftContent)\n }\n\n private async getListingEntries(\n client: ContentfulReadClient,\n slug: string,\n locale: string | undefined,\n useDraftContent: boolean,\n ): Promise<CMSPagePayload | undefined> {\n try {\n const response = await client.getEntries(\n this.buildListingQuery(slug, locale),\n )\n return this.transformPageResponse(response, useDraftContent)\n } catch (error) {\n if (extractErrorStatus(error) === 404) {\n throw new CMSContentNotFoundError(\n 'Contentful listing content not found',\n {\n cause: error,\n details: {\n slug,\n locale,\n contentType: 'productListingPageComponent',\n },\n },\n )\n }\n throw error\n }\n }\n\n /**\n * Transforms a Contentful entry collection into the page payload.\n * Override to change what shape `fetchPageData`/`fetchListingPageData` return,\n * for example to keep additional response fields.\n *\n * @param response Raw Contentful entry collection\n * @param useDraftContent Whether this is a draft content fetch\n * @returns Page payload, or undefined when the collection has no entries\n */\n protected transformPageResponse(\n response: EntryCollection<EntrySkeletonType>,\n useDraftContent: boolean,\n ): CMSPagePayload | undefined {\n const entry = response.items.at(0)\n if (!entry) {\n return undefined\n }\n if (!useDraftContent) {\n return { entry }\n }\n\n const [resolvedEntry] = resolveResponse({\n items: response.items,\n includes: response.includes,\n })\n\n return {\n entry: resolvedEntry ?? entry,\n previewSource: { entry, includes: response.includes },\n }\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scayle/storefront-cms-contentful",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "Contentful CMS provider integration for the SCAYLE Storefront Application V3",
|
|
5
5
|
"author": "SCAYLE Commerce Engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"contentful": "^11.12.7",
|
|
26
26
|
"contentful-resolve-response": "^2.0.1",
|
|
27
|
-
"@scayle/storefront": "1.0.0-alpha.
|
|
27
|
+
"@scayle/storefront": "1.0.0-alpha.4"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@arethetypeswrong/cli": "0.18.5",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"typescript": "6.0.3",
|
|
39
39
|
"obuild": "0.4.38",
|
|
40
40
|
"vitest": "4.1.10",
|
|
41
|
-
"@scayle/eslint-config-storefront": "4.8.3-alpha.
|
|
42
|
-
"@scayle/storefront": "1.0.0-alpha.
|
|
41
|
+
"@scayle/eslint-config-storefront": "4.8.3-alpha.1",
|
|
42
|
+
"@scayle/storefront": "1.0.0-alpha.4",
|
|
43
43
|
"@scayle/vitest-config-storefront": "1.0.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|