Skip to content

Commit

Permalink
Use relative URLs for editing internal API calls (#544)
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Brauer <adab@sitecore.net>
  • Loading branch information
ambrauer and Adam Brauer committed Feb 1, 2021
1 parent 2b297f2 commit 2cbfe8c
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 31 deletions.
Expand Up @@ -67,16 +67,13 @@ const mockDataService = (previewData?: EditingPreviewData) => {
};

describe('EditingRenderMiddleware', () => {
const publicUrl = 'http://test.com';
const secret = 'secret1234';

beforeEach(() => {
process.env.PUBLIC_URL = publicUrl;
process.env.JSS_EDITING_SECRET = secret;
});

after(() => {
delete process.env.PUBLIC_URL;
delete process.env.JSS_EDITING_SECRET;
});

Expand Down Expand Up @@ -197,6 +194,38 @@ describe('EditingRenderMiddleware', () => {
expect(res.json).to.have.been.called.once;
});

it('should use custom resolvePageRoute', async () => {
const html = '<html><body>Something amazing</body></html>';
const fetcher = mockFetcher(html);
const dataService = mockDataService();
const query = {} as Query;
query[QUERY_PARAM_EDITING_SECRET] = secret;
const req = mockRequest(EE_BODY, query);
const res = mockResponse();

const expectedPageRoute = `/some/path${EE_PATH}`;
const resolvePageRoute = spy((itemPath: string) => {
return `/some/path${itemPath}`;
});

const middleware = new EditingRenderMiddleware({
dataFetcher: fetcher,
editingDataService: dataService,
resolvePageRoute: resolvePageRoute,
});
const handler = middleware.getHandler();

await handler(req, res);

expect(resolvePageRoute).to.have.been.called.once;
expect(resolvePageRoute).to.have.been.called.with(EE_PATH);
expect(fetcher.get).to.have.been.called.once.and.satisfies((args: any) => {
const spy = args.__spy;
const url = spy.calls[0][0] as string;
return url.startsWith(expectedPageRoute);
});
});

it('should respond with 500 if rendered html empty', async () => {
const fetcher = mockFetcher('');
const dataService = mockDataService();
Expand Down
Expand Up @@ -3,7 +3,7 @@ import { AxiosDataFetcher } from '@sitecore-jss/sitecore-jss';
import { EditingData } from '../sharedTypes/editing-data';
import { EditingDataService, editingDataService } from '../services/editing-data-service';
import { QUERY_PARAM_EDITING_SECRET } from '../services/editing-data-service';
import { getPublicUrl, getJssEditingSecret } from '../utils';
import { getJssEditingSecret } from '../utils';

export interface EditingRenderMiddlewareConfig {
/**
Expand All @@ -21,14 +21,13 @@ export interface EditingRenderMiddlewareConfig {
*/
editingDataService?: EditingDataService;
/**
* Function used to determine route/page URL to render.
* Function used to determine page/route to render.
* This may be necessary for certain custom Next.js routing configurations.
* @param {string} serverUrl The root server URL e.g. 'http://localhost:3000'
* @param {string} itemPath The Sitecore relative item path e.g. '/styleguide'
* @returns {string} The URL to render
* @default `${serverUrl}${itemPath}`
* @returns {string} The route to render
* @default itemPath
*/
resolveRouteUrl?: (serverUrl: string, itemPath: string) => string;
resolvePageRoute?: (itemPath: string) => string;
}

/**
Expand All @@ -38,15 +37,15 @@ export interface EditingRenderMiddlewareConfig {
export class EditingRenderMiddleware {
private editingDataService: EditingDataService;
private dataFetcher: AxiosDataFetcher;
private resolveRouteUrl: (serverUrl: string, itemPath: string) => string;
private resolvePageRoute: (itemPath: string) => string;

/**
* @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
*/
constructor(config?: EditingRenderMiddlewareConfig) {
this.editingDataService = config?.editingDataService ?? editingDataService;
this.dataFetcher = config?.dataFetcher ?? new AxiosDataFetcher();
this.resolveRouteUrl = config?.resolveRouteUrl ?? this.defaultResolveRouteUrl;
this.resolvePageRoute = config?.resolvePageRoute ?? this.defaultResolvePageRoute;
}

/**
Expand Down Expand Up @@ -93,7 +92,7 @@ export class EditingRenderMiddleware {

// Make actual render request for page route, passing on preview cookies.
// Note timestamp effectively disables caching the request in Axios (no amount of cache headers seemed to do it)
const requestUrl = this.resolveRouteUrl(getPublicUrl(), editingData.path);
const requestUrl = this.resolvePageRoute(editingData.path);
const pageRes = await this.dataFetcher.get<string>(`${requestUrl}?timestamp=${Date.now()}`, {
headers: {
Cookie: cookies.join(';'),
Expand All @@ -114,8 +113,8 @@ export class EditingRenderMiddleware {
}
};

private defaultResolveRouteUrl = (serverUrl: string, itemPath: string) => {
return `${serverUrl}${itemPath}`;
private defaultResolvePageRoute = (itemPath: string) => {
return itemPath;
};
}

Expand Down
Expand Up @@ -22,16 +22,13 @@ const mockFetcher = (data?: any) => {
};

describe('EditingDataService', () => {
const publicUrl = 'http://test.com';
const secret = 'secret1234';

beforeEach(() => {
process.env.PUBLIC_URL = publicUrl;
process.env.JSS_EDITING_SECRET = secret;
});

after(() => {
delete process.env.PUBLIC_URL;
delete process.env.JSS_EDITING_SECRET;
});

Expand All @@ -45,7 +42,7 @@ describe('EditingDataService', () => {
path: '/styleguide',
} as EditingData;
const key = '1234key';
const expectedUrl = `${publicUrl}/api/editing/data/${key}?${QUERY_PARAM_EDITING_SECRET}=${secret}`;
const expectedUrl = `/api/editing/data/${key}?${QUERY_PARAM_EDITING_SECRET}=${secret}`;

const fetcher = mockFetcher();

Expand Down Expand Up @@ -73,15 +70,62 @@ describe('EditingDataService', () => {
const previewData2 = await service.setEditingData(data);
expect(previewData1.key).to.not.equal(previewData2.key);
});

it('should use custom apiRoute', async () => {
const data = {
layoutData: { sitecore: { route: { itemId: 'd6ac9d26-9474-51cf-982d-4f8d44951229' } } },
} as EditingData;
const key = '1234key';
const expectedUrl = `/api/some/path/${key}?${QUERY_PARAM_EDITING_SECRET}=${secret}`;

const fetcher = mockFetcher();

const service = new EditingDataService({
dataFetcher: fetcher,
apiRoute: '/api/some/path/[key]',
});
spy.on(service, 'generateKey', () => {
return key;
});

return service.setEditingData(data).then(() => {
expect(fetcher.put).to.have.been.called.once;
expect(fetcher.put).to.have.been.called.with.exactly(expectedUrl, data);
});
});

it('should URI encode secret', async () => {
const superSecret = ';,/?:@&=+$';
process.env.JSS_EDITING_SECRET = superSecret;
const data = {
layoutData: { sitecore: { route: { itemId: 'd6ac9d26-9474-51cf-982d-4f8d44951229' } } },
} as EditingData;
const key = '1234key';
const expectedUrl = `/api/editing/data/${key}?${QUERY_PARAM_EDITING_SECRET}=${encodeURIComponent(
superSecret
)}`;

const fetcher = mockFetcher();

const service = new EditingDataService({ dataFetcher: fetcher });
spy.on(service, 'generateKey', () => {
return key;
});

return service.setEditingData(data).then(() => {
expect(fetcher.put).to.have.been.called.once;
expect(fetcher.put).to.have.been.called.with.exactly(expectedUrl, data);
});
});
});

describe('getEditingData', () => {
it('should invoke GET request', () => {
it('should invoke GET request', async () => {
const data = {
path: '/styleguide',
} as EditingData;
const key = '1234key';
const expectedUrl = `${publicUrl}/api/editing/data/${key}?${QUERY_PARAM_EDITING_SECRET}=${secret}`;
const expectedUrl = `/api/editing/data/${key}?${QUERY_PARAM_EDITING_SECRET}=${secret}`;

const fetcher = mockFetcher(data);

Expand All @@ -90,11 +134,10 @@ describe('EditingDataService', () => {
return key;
});

return service.getEditingData({ key }).then((editingData) => {
expect(editingData).to.equal(data);
expect(fetcher.get).to.have.been.called.once;
expect(fetcher.get).to.have.been.called.with(expectedUrl);
});
const editingData = await service.getEditingData({ key });
expect(editingData).to.equal(data);
expect(fetcher.get).to.have.been.called.once;
expect(fetcher.get).to.have.been.called.with(expectedUrl);
});
});
});
@@ -1,6 +1,6 @@
import { AxiosDataFetcher } from '@sitecore-jss/sitecore-jss';
import { EditingData, EditingPreviewData } from '../sharedTypes/editing-data';
import { getPublicUrl, getJssEditingSecret } from '../utils';
import { getJssEditingSecret } from '../utils';

export const QUERY_PARAM_EDITING_SECRET = 'secret';

Expand Down Expand Up @@ -78,12 +78,10 @@ export class EditingDataService {

protected getUrl(key: string): string {
// Example URL format:
// http://localhost:3000/api/editing/data/52961eea-bafd-5287-a532-a72e36bd8a36-qkb4e3fv5x?secret=1234secret
const publicUrl = getPublicUrl();
// /api/editing/data/52961eea-bafd-5287-a532-a72e36bd8a36-qkb4e3fv5x?secret=1234secret
const apiRoute = this.apiRoute?.replace('[key]', key);
const url = new URL(apiRoute, publicUrl);
url.searchParams.append(QUERY_PARAM_EDITING_SECRET, getJssEditingSecret());
return url.toString();
const secret = getJssEditingSecret();
return `${apiRoute}?${QUERY_PARAM_EDITING_SECRET}=${encodeURIComponent(secret)}`;
}
}

Expand Down

0 comments on commit 2cbfe8c

Please sign in to comment.