@sitecore-jss/sitecore-jss
Version:
This module is provided as a part of Sitecore JavaScript Rendering SDK. It contains the core JSS APIs (layout service) and utilities.
183 lines (182 loc) • 8.9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RestLayoutService = void 0;
const layout_service_1 = require("./layout-service");
const native_fetcher_1 = require("../native-fetcher");
const data_fetcher_1 = require("../data-fetcher");
const debug_1 = __importDefault(require("../debug"));
/**
* Fetch layout data using the Sitecore Layout Service REST API.
* Uses NativeDataFetcher as the default data fetcher (@see NativeDataFetcher).
* @augments LayoutServiceBase
*/
class RestLayoutService extends layout_service_1.LayoutServiceBase {
constructor(serviceConfig) {
super();
this.serviceConfig = serviceConfig;
/**
* Provides fetch options in order to fetch data
* @param {string} [language] language will be applied to `sc_lang` param
* @returns {FetchOptions} fetch options
*/
this.getFetchParams = (language) => {
var _a;
return {
sc_apikey: this.serviceConfig.apiKey,
sc_site: this.serviceConfig.siteName,
sc_lang: language || '',
tracking: (_a = this.serviceConfig.tracking) !== null && _a !== void 0 ? _a : true,
};
};
this.getFetcher = (req, res) => {
return this.serviceConfig.dataFetcherResolver
? this.serviceConfig.dataFetcherResolver(req, res)
: this.getDefaultFetcher(req, res);
};
/**
* Returns a fetcher function pre-configured with headers from the incoming request.
* Provides default @see NativeDataFetcher data fetcher
* @param {IncomingMessage} [req] Request instance
* @param {ServerResponse} [res] Response instance
* @returns default fetcher
*/
this.getDefaultFetcher = (req, res) => {
const config = { debugger: debug_1.default.layout };
let headers;
if (req) {
headers = this.setupReqHeaders(req);
}
const nativeFetcher = new native_fetcher_1.NativeDataFetcher(config);
return (url, data) => __awaiter(this, void 0, void 0, function* () {
const response = yield nativeFetcher.fetch(url, Object.assign(Object.assign({}, data), { headers }));
if (res) {
this.setupResHeaders(res, response);
}
return response;
});
};
}
/**
* Fetch layout data for an item.
* @param {string} itemPath item path to fetch layout data for.
* @param {string} [language] the language to fetch layout data for.
* @param {IncomingMessage} [req] Request instance
* @param {ServerResponse} [res] Response instance
* @returns {Promise<LayoutServiceData>} layout service data
* @throws {Error} the item with the specified path is not found
*/
fetchLayoutData(itemPath, language, req, res) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const querystringParams = this.getFetchParams(language);
debug_1.default.layout('fetching layout data for %s %s %s', itemPath, language, this.serviceConfig.siteName);
const fetcher = this.getFetcher(req, res);
const fetchUrl = this.resolveLayoutServiceUrl('render');
try {
return yield (0, data_fetcher_1.fetchData)(fetchUrl, fetcher, Object.assign({ item: itemPath }, querystringParams));
}
catch (error) {
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
// Aligned with response of GraphQL Layout Service in case if layout is not found.
// When 404 Rest Layout Service returns
// {
// sitecore: {
// context: {
// pageEditing: false,
// language
// },
// route: null
// },
// }
//
return error.response.data;
}
throw error;
}
});
}
/**
* Fetch layout data for a particular placeholder.
* Makes a request to Sitecore Layout Service for the specified placeholder in
* a specific route item. Allows you to retrieve rendered data for individual placeholders instead of entire routes.
* @param {string} placeholderName the name of the placeholder to fetch layout data for.
* @param {string} itemPath the path to the item to fetch layout data for.
* @param {string} [language] the language to fetch data for.
* @param {IncomingMessage} [req] Request instance
* @param {ServerResponse} [res] Response instance
* @returns {Promise<PlaceholderData>} placeholder data
*/
fetchPlaceholderData(placeholderName, itemPath, language, req, res) {
const querystringParams = this.getFetchParams(language);
debug_1.default.layout('fetching placeholder data for %s %s %s %s', placeholderName, itemPath, language, this.serviceConfig.siteName);
const fetcher = this.serviceConfig.dataFetcherResolver
? this.serviceConfig.dataFetcherResolver(req, res)
: this.getDefaultFetcher(req, res);
const fetchUrl = this.resolveLayoutServiceUrl('placeholder');
return (0, data_fetcher_1.fetchData)(fetchUrl, fetcher, Object.assign({ placeholderName, item: itemPath }, querystringParams));
}
/**
* Resolves layout service url
* @param {string} apiType which layout service API to call ('render' or 'placeholder')
* @returns the layout service url
*/
resolveLayoutServiceUrl(apiType) {
const { apiHost = '', configurationName = 'jss' } = this.serviceConfig;
return `${apiHost}/sitecore/api/layout/${apiType}/${configurationName}`;
}
/**
* Creates an HTTP `Headers` object populated with headers from the incoming request.
* @param {IncomingMessage} [req] - The incoming HTTP request, used to extract headers.
* @returns {Headers} - An instance of the `Headers` object populated with the extracted headers.
*/
setupReqHeaders(req) {
const headers = new Headers();
if (req === null || req === void 0 ? void 0 : req.headers) {
// Copy all headers from req.headers
Object.entries(req.headers).forEach(([key, value]) => {
if (value) {
headers.set(key, Array.isArray(value) ? value.join(', ') : value);
}
});
// Add or override specific headers
req.headers.cookie && headers.set('cookie', req.headers.cookie);
req.headers.referer && headers.set('referer', req.headers.referer);
req.headers['user-agent'] && headers.set('user-agent', req.headers['user-agent']);
req.socket.remoteAddress && headers.set('X-Forwarded-For', req.socket.remoteAddress);
}
return headers;
}
/**
* Setup response headers based on response from layout service
* @param {ServerResponse} res Response instance
* @param {NativeDataFetcherResponse<T>} serverRes
* @returns {NativeDataFetcherResponse} response
*/
setupResHeaders(res, serverRes) {
debug_1.default.layout('performing response header passing');
const headers = serverRes.headers;
if (headers instanceof Headers && headers.has('set-cookie')) {
const rawSetCookie = headers.get('set-cookie');
if (rawSetCookie) {
const cookies = rawSetCookie.includes(', ') && rawSetCookie.includes(';')
? rawSetCookie.split(/,(?=\s*\w+=)/)
: [rawSetCookie];
res.setHeader('Set-Cookie', cookies);
}
}
return serverRes;
}
}
exports.RestLayoutService = RestLayoutService;