-
Notifications
You must be signed in to change notification settings - Fork 277
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Next.js] [BYOC] Component Builder integration endpoint (#1729)
* [Next.js] [BYOC] Component Builder integration endpoint * Update * CHANGELOG * Updated NextConfig * Addressing the pr review comments * cleanup * Update * Cleanup * Removed image whitelisting * Added /feaas-render rewrite
- Loading branch information
1 parent
7905a71
commit c4ccad3
Showing
11 changed files
with
482 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/middleware.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { NextRequest, NextFetchEvent } from 'next/server'; | ||
import middleware from 'lib/middleware'; | ||
|
||
// eslint-disable-next-line | ||
export default async function (req: NextRequest, ev: NextFetchEvent) { | ||
return middleware(req, ev); | ||
} | ||
|
||
export const config = { | ||
/* | ||
* Match all paths except for: | ||
* 1. /api routes | ||
* 2. /_next (Next.js internals) | ||
* 3. /sitecore/api (Sitecore API routes) | ||
* 4. /- (Sitecore media) | ||
* 5. /healthz (Health check) | ||
* 6. /feaas-render (FEaaS render) | ||
* 7. all root files inside /public | ||
*/ | ||
matcher: ['/', '/((?!api/|_next/|feaas-render|healthz|sitecore/api/|-/|favicon.ico|sc_logo.svg).*)'], | ||
}; |
16 changes: 16 additions & 0 deletions
16
...es/create-sitecore-jss/src/templates/nextjs-xmcloud/src/pages/api/editing/feaas/render.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { FEAASRenderMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/editing'; | ||
|
||
/** | ||
* This Next.js API route is used to handle GET requests from Sitecore Component Builder. | ||
* | ||
* The `FEAASRenderMiddleware` will: | ||
* 1. Enable Next.js Preview Mode. | ||
* 2. Redirect the request to the /feaas/render page. | ||
* - If "feaasSrc" query parameter is provided, the page will render a FEAAS component. | ||
* - The page provides all the registered FEAAS components. | ||
*/ | ||
|
||
// Wire up the FEAASRenderMiddleware handler | ||
const handler = new FEAASRenderMiddleware().getHandler(); | ||
|
||
export default handler; |
32 changes: 32 additions & 0 deletions
32
packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/pages/feaas/render.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { GetServerSideProps } from 'next'; | ||
import BYOC from 'src/byoc'; | ||
import * as FEAAS from '@sitecore-feaas/clientside/react'; | ||
|
||
/** | ||
* The FEAASRender page is responsible for: | ||
* - Rendering the FEAAS component if the "feaasSrc" is provided. | ||
* - Rendering all the registered components. | ||
* - The page is rendered only if it's requested by the api route (/api/editing/feaas/render) using the preview mode. | ||
*/ | ||
const FEAASRender = ({ feaasSrc }: { feaasSrc: string }): JSX.Element => { | ||
return ( | ||
<> | ||
{/** Render the component if the "feaasSrc" is provided */} | ||
{feaasSrc && <FEAAS.Component src={feaasSrc} />} | ||
{/** Render all the registered components */} | ||
<BYOC /> | ||
</> | ||
); | ||
}; | ||
|
||
export const getServerSideProps: GetServerSideProps = async (context) => { | ||
return { | ||
props: { | ||
feaasSrc: context.query.feaasSrc || null, | ||
}, | ||
// Don't show the page if it's not requested by the api route using the preview mode | ||
notFound: !context.preview, | ||
}; | ||
}; | ||
|
||
export default FEAASRender; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
packages/sitecore-jss-nextjs/src/editing/feaas-render-middleware.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
/* eslint-disable quotes */ | ||
/* eslint-disable no-unused-expressions */ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { expect, use } from 'chai'; | ||
import { NextApiRequest, NextApiResponse } from 'next'; | ||
import { | ||
QUERY_PARAM_EDITING_SECRET, | ||
QUERY_PARAM_PROTECTION_BYPASS_SITECORE, | ||
QUERY_PARAM_PROTECTION_BYPASS_VERCEL, | ||
} from './constants'; | ||
import { FEAASRenderMiddleware } from './feaas-render-middleware'; | ||
import { spy } from 'sinon'; | ||
import sinonChai from 'sinon-chai'; | ||
|
||
use(sinonChai); | ||
|
||
type Query = { | ||
[key: string]: string; | ||
}; | ||
|
||
const mockRequest = (query?: Query, method?: string, host?: string) => { | ||
return { | ||
body: {}, | ||
method: method ?? 'GET', | ||
query: query ?? {}, | ||
headers: { host: host ?? 'localhost:3000' }, | ||
} as NextApiRequest; | ||
}; | ||
|
||
const mockResponse = () => { | ||
const res = {} as NextApiResponse; | ||
res.status = spy(() => { | ||
return res; | ||
}); | ||
res.send = spy(() => { | ||
return res; | ||
}); | ||
res.setPreviewData = spy(() => { | ||
return res; | ||
}); | ||
res.setHeader = spy(() => { | ||
return res; | ||
}); | ||
res.redirect = spy(() => { | ||
return res; | ||
}); | ||
|
||
return res; | ||
}; | ||
|
||
describe('FEAASRenderMiddleware', () => { | ||
const secret = 'secret1234'; | ||
|
||
beforeEach(() => { | ||
process.env.JSS_EDITING_SECRET = secret; | ||
}); | ||
|
||
after(() => { | ||
delete process.env.JSS_EDITING_SECRET; | ||
}); | ||
|
||
it('should handle request', async () => { | ||
const query = {} as Query; | ||
query[QUERY_PARAM_EDITING_SECRET] = secret; | ||
|
||
const req = mockRequest(query); | ||
const res = mockResponse(); | ||
|
||
const middleware = new FEAASRenderMiddleware(); | ||
const handler = middleware.getHandler(); | ||
|
||
await handler(req, res); | ||
|
||
expect(res.setPreviewData, 'set preview mode w/ data').to.have.been.calledWith({}); | ||
expect(res.redirect).to.have.been.calledOnce; | ||
expect(res.redirect).to.have.been.calledWith('/feaas/render'); | ||
}); | ||
|
||
it('should handle request when feaasSrc query parameter is present', async () => { | ||
const query = { | ||
feaasSrc: 'https://feaas.blob.core.windows.net/components/xxx/xyz/responsive/staged', | ||
} as Query; | ||
query[QUERY_PARAM_EDITING_SECRET] = secret; | ||
|
||
const req = mockRequest(query); | ||
const res = mockResponse(); | ||
|
||
const middleware = new FEAASRenderMiddleware(); | ||
const handler = middleware.getHandler(); | ||
|
||
await handler(req, res); | ||
|
||
expect(res.setPreviewData, 'set preview mode w/ data').to.have.been.calledWith({}); | ||
expect(res.redirect).to.have.been.calledOnce; | ||
expect(res.redirect).to.have.been.calledWith( | ||
'/feaas/render?feaasSrc=https%3A%2F%2Ffeaas.blob.core.windows.net%2Fcomponents%2Fxxx%2Fxyz%2Fresponsive%2Fstaged' | ||
); | ||
}); | ||
|
||
it('should throw error', async () => { | ||
const query = {} as Query; | ||
query[QUERY_PARAM_EDITING_SECRET] = secret; | ||
|
||
const req = mockRequest(query); | ||
const res = mockResponse(); | ||
|
||
res.setPreviewData = spy(() => { | ||
throw new Error('Test Error'); | ||
}); | ||
|
||
const middleware = new FEAASRenderMiddleware(); | ||
const handler = middleware.getHandler(); | ||
|
||
await handler(req, res); | ||
|
||
expect(res.setPreviewData, 'set preview mode w/ data').to.have.been.calledWith({}); | ||
expect(res.status).to.have.been.calledOnce; | ||
expect(res.status).to.have.been.calledWith(500); | ||
expect(res.send).to.have.been.calledOnce; | ||
expect(res.send).to.have.been.calledWith('<html><body>Error: Test Error</body></html>'); | ||
}); | ||
|
||
it('should respondWith 405 for unsupported method', async () => { | ||
const req = mockRequest({}, 'POST'); | ||
const res = mockResponse(); | ||
|
||
const middleware = new FEAASRenderMiddleware(); | ||
const handler = middleware.getHandler(); | ||
|
||
await handler(req, res); | ||
|
||
expect(res.setHeader).to.have.been.calledWithExactly('Allow', 'GET'); | ||
expect(res.status).to.have.been.calledOnce; | ||
expect(res.status).to.have.been.calledWith(405); | ||
expect(res.send).to.have.been.calledOnce; | ||
expect(res.send).to.have.been.calledWith( | ||
"<html><body>Invalid request method 'POST'</body></html>" | ||
); | ||
}); | ||
|
||
it('should respond with 401 for missing secret', async () => { | ||
const query = {} as Query; | ||
const req = mockRequest(query); | ||
const res = mockResponse(); | ||
|
||
const middleware = new FEAASRenderMiddleware(); | ||
const handler = middleware.getHandler(); | ||
|
||
await handler(req, res); | ||
|
||
expect(res.status).to.have.been.calledOnce; | ||
expect(res.status).to.have.been.calledWith(401); | ||
expect(res.send).to.have.been.calledOnce; | ||
expect(res.send).to.have.been.calledWith('<html><body>Missing or invalid secret</body></html>'); | ||
}); | ||
|
||
it('should respond with 401 for invalid secret', async () => { | ||
const query = {} as Query; | ||
query[QUERY_PARAM_EDITING_SECRET] = 'nope'; | ||
const req = mockRequest(query); | ||
const res = mockResponse(); | ||
|
||
const middleware = new FEAASRenderMiddleware(); | ||
const handler = middleware.getHandler(); | ||
|
||
await handler(req, res); | ||
|
||
expect(res.status).to.have.been.calledOnce; | ||
expect(res.status).to.have.been.calledWith(401); | ||
expect(res.send).to.have.been.calledOnce; | ||
expect(res.send).to.have.been.calledWith('<html><body>Missing or invalid secret</body></html>'); | ||
}); | ||
|
||
it('should use custom pageUrl', async () => { | ||
const query = {} as Query; | ||
query[QUERY_PARAM_EDITING_SECRET] = secret; | ||
const req = mockRequest(query); | ||
const res = mockResponse(); | ||
|
||
const pageUrl = '/some/path/feaas/render'; | ||
|
||
const middleware = new FEAASRenderMiddleware({ | ||
pageUrl, | ||
}); | ||
const handler = middleware.getHandler(); | ||
|
||
await handler(req, res); | ||
|
||
expect(res.redirect).to.have.been.calledOnce; | ||
expect(res.redirect).to.have.been.calledWith(pageUrl); | ||
}); | ||
|
||
it('should pass along protection bypass query parameters', async () => { | ||
const query = {} as Query; | ||
const bypassTokenSitecore = 'token1234Sitecore'; | ||
const bypassTokenVercel = 'token1234Vercel'; | ||
query[QUERY_PARAM_EDITING_SECRET] = secret; | ||
query[QUERY_PARAM_PROTECTION_BYPASS_SITECORE] = bypassTokenSitecore; | ||
query[QUERY_PARAM_PROTECTION_BYPASS_VERCEL] = bypassTokenVercel; | ||
const previewData = {}; | ||
|
||
const req = mockRequest(query); | ||
const res = mockResponse(); | ||
|
||
const middleware = new FEAASRenderMiddleware(); | ||
const handler = middleware.getHandler(); | ||
|
||
await handler(req, res); | ||
|
||
expect(res.setPreviewData, 'set preview mode w/ data').to.have.been.calledWith(previewData); | ||
expect(res.redirect).to.have.been.calledOnce; | ||
expect(res.redirect).to.have.been.calledWith( | ||
'/feaas/render?x-sitecore-protection-bypass=token1234Sitecore&x-vercel-protection-bypass=token1234Vercel' | ||
); | ||
}); | ||
}); |
Oops, something went wrong.