@sitecore-jss/sitecore-jss
Advanced tools
Comparing version 22.4.0-canary.9 to 22.4.0-canary.10
"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()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.ResponseError = void 0; | ||
exports.checkStatus = checkStatus; | ||
exports.fetchData = fetchData; | ||
@@ -16,24 +24,11 @@ const utils_1 = require("./utils/utils"); | ||
/** | ||
* @param {HttpResponse<T>} response the response to check | ||
* @throws {ResponseError} if response code is not ok | ||
*/ | ||
function checkStatus(response) { | ||
if (response.status >= 200 && response.status < 300) { | ||
return response; | ||
} | ||
const error = new ResponseError(response.statusText, response); | ||
throw error; | ||
} | ||
/** | ||
* @param {string} url the URL to request; may include query string | ||
* @param {HttpDataFetcher} fetcher the fetcher to use to perform the request | ||
* @param {HttpDataFetcher<T> | NativeDataFetcherFunction<T>} fetcher the fetcher to use to perform the request | ||
* @param {ParsedUrlQueryInput} params the query string parameters to send with the request | ||
*/ | ||
function fetchData(url, fetcher, params = {}) { | ||
return fetcher((0, utils_1.resolveUrl)(url, params)) | ||
.then(checkStatus) | ||
.then((response) => { | ||
// axios auto-parses JSON responses, don't need to JSON.parse | ||
function fetchData(url_1, fetcher_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, fetcher, params = {}) { | ||
const response = yield fetcher((0, utils_1.resolveUrl)(url, params)); | ||
return response.data; | ||
}); | ||
} |
@@ -113,3 +113,3 @@ "use strict"; | ||
// Note we don't have access to raw request/response with graphql-request | ||
// (or nice hooks like we have with Axios), but we should log whatever we have. | ||
// but we should log whatever we have. | ||
this.debug('request: %o', { | ||
@@ -116,0 +116,0 @@ url: this.endpoint, |
@@ -16,3 +16,3 @@ "use strict"; | ||
exports.RestDictionaryService = void 0; | ||
const axios_fetcher_1 = require("../axios-fetcher"); | ||
const native_fetcher_1 = require("../native-fetcher"); | ||
const data_fetcher_1 = require("../data-fetcher"); | ||
@@ -23,3 +23,3 @@ const dictionary_service_1 = require("./dictionary-service"); | ||
* Fetch dictionary data using the Sitecore Dictionary Service REST API. | ||
* Uses Axios as the default data fetcher (@see AxiosDataFetcher). | ||
* Uses NativeDataFetcher as the default data fetcher (@see NativeDataFetcher). | ||
* @augments DictionaryServiceBase | ||
@@ -33,9 +33,9 @@ */ | ||
/** | ||
* Provides default @see AxiosDataFetcher data fetcher | ||
* Provides default @see NativeDataFetcher data fetcher | ||
*/ | ||
get defaultFetcher() { | ||
const dataFetcher = new axios_fetcher_1.AxiosDataFetcher({ | ||
const dataFetcher = new native_fetcher_1.NativeDataFetcher({ | ||
debugger: debug_1.default.dictionary, | ||
// CORS issue: Sitecore provides 'Access-Control-Allow-Origin' as wildcard '*', so we can't include credentials for the dictionary service | ||
withCredentials: false, | ||
credentials: 'omit', | ||
}); | ||
@@ -42,0 +42,0 @@ return (url) => dataFetcher.fetch(url); |
@@ -31,3 +31,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.constants = exports.NativeDataFetcher = exports.ClientError = exports.MemoryCacheClient = exports.AxiosDataFetcher = exports.GraphQLRequestClient = exports.DefaultRetryStrategy = exports.fetchData = exports.enableDebug = exports.debug = void 0; | ||
exports.constants = exports.NativeDataFetcher = exports.ClientError = exports.MemoryCacheClient = exports.GraphQLRequestClient = exports.DefaultRetryStrategy = exports.ResponseError = exports.fetchData = exports.enableDebug = exports.debug = void 0; | ||
const constants = __importStar(require("./constants")); | ||
@@ -40,7 +40,6 @@ exports.constants = constants; | ||
Object.defineProperty(exports, "fetchData", { enumerable: true, get: function () { return data_fetcher_1.fetchData; } }); | ||
Object.defineProperty(exports, "ResponseError", { enumerable: true, get: function () { return data_fetcher_1.ResponseError; } }); | ||
var graphql_request_client_1 = require("./graphql-request-client"); | ||
Object.defineProperty(exports, "DefaultRetryStrategy", { enumerable: true, get: function () { return graphql_request_client_1.DefaultRetryStrategy; } }); | ||
Object.defineProperty(exports, "GraphQLRequestClient", { enumerable: true, get: function () { return graphql_request_client_1.GraphQLRequestClient; } }); | ||
var axios_fetcher_1 = require("./axios-fetcher"); | ||
Object.defineProperty(exports, "AxiosDataFetcher", { enumerable: true, get: function () { return axios_fetcher_1.AxiosDataFetcher; } }); | ||
var cache_client_1 = require("./cache-client"); | ||
@@ -47,0 +46,0 @@ Object.defineProperty(exports, "MemoryCacheClient", { enumerable: true, get: function () { return cache_client_1.MemoryCacheClient; } }); |
"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) { | ||
@@ -8,3 +17,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
const layout_service_1 = require("./layout-service"); | ||
const axios_fetcher_1 = require("../axios-fetcher"); | ||
const native_fetcher_1 = require("../native-fetcher"); | ||
const data_fetcher_1 = require("../data-fetcher"); | ||
@@ -14,3 +23,3 @@ const debug_1 = __importDefault(require("../debug")); | ||
* Fetch layout data using the Sitecore Layout Service REST API. | ||
* Uses Axios as the default data fetcher (@see AxiosDataFetcher). | ||
* Uses NativeDataFetcher as the default data fetcher (@see NativeDataFetcher). | ||
* @augments LayoutServiceBase | ||
@@ -42,3 +51,4 @@ */ | ||
/** | ||
* Provides default @see AxiosDataFetcher data fetcher | ||
* Returns a fetcher function pre-configured with headers from the incoming request. | ||
* Provides default @see NativeDataFetcher data fetcher | ||
* @param {IncomingMessage} [req] Request instance | ||
@@ -49,14 +59,15 @@ * @param {ServerResponse} [res] Response instance | ||
this.getDefaultFetcher = (req, res) => { | ||
const config = { | ||
debugger: debug_1.default.layout, | ||
}; | ||
if (req && res) { | ||
config.onReq = this.setupReqHeaders(req); | ||
config.onRes = this.setupResHeaders(res); | ||
const config = { debugger: debug_1.default.layout }; | ||
let headers; | ||
if (req) { | ||
headers = this.setupReqHeaders(req); | ||
} | ||
const axiosFetcher = new axios_fetcher_1.AxiosDataFetcher(config); | ||
const fetcher = (url, data) => { | ||
return axiosFetcher.fetch(url, data); | ||
}; | ||
return fetcher; | ||
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; | ||
}); | ||
}; | ||
@@ -74,24 +85,29 @@ } | ||
fetchLayoutData(itemPath, language, req, res) { | ||
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'); | ||
return (0, data_fetcher_1.fetchData)(fetchUrl, fetcher, Object.assign({ item: itemPath }, querystringParams)).catch((error) => { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
var _a; | ||
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; | ||
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)); | ||
} | ||
throw error; | ||
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; | ||
} | ||
}); | ||
@@ -129,12 +145,22 @@ } | ||
/** | ||
* Setup request headers | ||
* @param {IncomingMessage} req Request instance | ||
* @returns {AxiosRequestConfig} axios request config | ||
* 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) { | ||
return (reqConfig) => { | ||
debug_1.default.layout('performing request header passing'); | ||
reqConfig.headers.common = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, reqConfig.headers.common), (req.headers.cookie && { cookie: req.headers.cookie })), (req.headers.referer && { referer: req.headers.referer })), (req.headers['user-agent'] && { 'user-agent': req.headers['user-agent'] })), (req.connection.remoteAddress && { 'X-Forwarded-For': req.connection.remoteAddress })); | ||
return reqConfig; | ||
}; | ||
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; | ||
} | ||
@@ -144,13 +170,17 @@ /** | ||
* @param {ServerResponse} res Response instance | ||
* @returns {AxiosResponse} response | ||
* @param {NativeDataFetcherResponse<T>} serverRes | ||
* @returns {NativeDataFetcherResponse} response | ||
*/ | ||
setupResHeaders(res) { | ||
return (serverRes) => { | ||
debug_1.default.layout('performing response header passing'); | ||
serverRes.headers['set-cookie'] && | ||
res.setHeader('set-cookie', serverRes.headers['set-cookie']); | ||
return serverRes; | ||
}; | ||
setupResHeaders(res, serverRes) { | ||
debug_1.default.layout('performing response header passing'); | ||
const headers = serverRes.headers; | ||
if (headers instanceof Headers) { | ||
const setCookieHeader = headers.get('set-cookie'); | ||
if (setCookieHeader) { | ||
res.setHeader('set-cookie', setCookieHeader); | ||
} | ||
} | ||
return serverRes; | ||
} | ||
} | ||
exports.RestLayoutService = RestLayoutService; |
@@ -34,9 +34,9 @@ "use strict"; | ||
/** | ||
* Implements a data fetcher. @see HttpDataFetcher<T> type for implementation details/notes. | ||
* @param {string} url The URL to request; may include query string | ||
* @param {unknown} [data] Optional data to POST with the request. | ||
* @returns {Promise<HttpResponse<T>>} response | ||
* Implements a data fetcher. | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Optional fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
fetch(url, data) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
fetch(url_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, options = {}) { | ||
var _a; | ||
@@ -47,3 +47,3 @@ const _b = this.config, { debugger: debugOverride, fetch: fetchOverride } = _b, init = __rest(_b, ["debugger", "fetch"]); | ||
const debug = debugOverride || debug_1.default.http; | ||
const requestInit = this.getRequestInit(init, data); | ||
const requestInit = this.getRequestInit(Object.assign(Object.assign({}, init), options)); | ||
const fetchWithOptionalTimeout = [fetchImpl(url, requestInit)]; | ||
@@ -54,54 +54,96 @@ if (init.timeout) { | ||
} | ||
// Note a goal here is to provide consistent debug logging and error handling | ||
// as we do in AxiosDataFetcher and GraphQLRequestClient | ||
const { headers: reqHeaders } = requestInit, rest = __rest(requestInit, ["headers"]); | ||
debug('request: %o', Object.assign({ url, headers: this.extractDebugHeaders(reqHeaders) }, rest)); | ||
const response = yield Promise.race(fetchWithOptionalTimeout) | ||
.then((res) => { | ||
var _a; | ||
debug('Request initiated: %o', Object.assign({ url, headers: this.extractDebugHeaders(requestInit.headers) }, requestInit)); | ||
try { | ||
const response = yield Promise.race(fetchWithOptionalTimeout).then((res) => { | ||
var _a; | ||
(_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear(); | ||
return res; | ||
}); | ||
const respData = yield this.parseResponse(response, debug); | ||
if (!response.ok) { | ||
const error = this.createError(response, respData); | ||
debug('Response error: %o', error.response); | ||
throw error; | ||
} | ||
debug('Response in %dms: %o', Date.now() - startTimestamp, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: this.extractDebugHeaders(response.headers), | ||
url: response.url, | ||
data: respData, | ||
}); | ||
return Object.assign(Object.assign({}, response), { data: respData }); | ||
} | ||
catch (error) { | ||
(_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear(); | ||
return res; | ||
}) | ||
.catch((error) => { | ||
var _a; | ||
(_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear(); | ||
debug('request error: %o', error); | ||
debug('Request failed: %o', error); | ||
throw error; | ||
}); | ||
// Note even an error status may send useful json data in response (which we want for logging) | ||
let respData = undefined; | ||
const isJson = (_a = response.headers.get('Content-Type')) === null || _a === void 0 ? void 0 : _a.includes('application/json'); | ||
if (isJson) { | ||
respData = yield response.json().catch((error) => { | ||
debug('response.json() error: %o', error); | ||
}); | ||
} | ||
const debugResponse = { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: this.extractDebugHeaders(response.headers), | ||
url: response.url, | ||
redirected: response.redirected, | ||
data: respData, | ||
}; | ||
if (!response.ok) { | ||
debug('response error: %o', debugResponse); | ||
throw new Error(`HTTP ${response.status} ${response.statusText}`); | ||
} | ||
debug('response in %dms: %o', Date.now() - startTimestamp, debugResponse); | ||
return Object.assign(Object.assign({}, response), { data: respData }); | ||
}); | ||
} | ||
/** | ||
* Perform a GET request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
get(url_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'GET' }, options)); | ||
}); | ||
} | ||
/** | ||
* Perform a POST request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {unknown} body The data to send with the request | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
post(url_1, body_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, body, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'POST', body: JSON.stringify(body) }, options)); | ||
}); | ||
} | ||
/** | ||
* Perform a DELETE request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
delete(url_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'DELETE' }, options)); | ||
}); | ||
} | ||
/** | ||
* Perform a PUT request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {unknown} body The data to send with the request | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
put(url_1, body_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, body, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'PUT', body: JSON.stringify(body) }, options)); | ||
}); | ||
} | ||
/** | ||
* Perform a HEAD request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
head(url, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'HEAD' }, options)); | ||
} | ||
/** | ||
* Determines settings for the request | ||
* @param {RequestInit} init Custom settings for request | ||
* @param {unknown} [data] Optional data to POST with the request | ||
* @returns {RequestInit} The final request settings | ||
*/ | ||
getRequestInit(init = {}, data) { | ||
// This is a focused implementation (GET or POST only using JSON input/output) | ||
// so we are opinionated about method, body, and Content-Type | ||
init.method = data ? 'POST' : 'GET'; | ||
init.body = data ? JSON.stringify(data) : undefined; | ||
getRequestInit(init = {}) { | ||
const headers = new Headers(init.headers); | ||
if (!init.method) { | ||
init.method = init.body ? 'POST' : 'GET'; | ||
} | ||
headers.set('Content-Type', 'application/json'); | ||
@@ -125,3 +167,38 @@ init.headers = headers; | ||
} | ||
/** | ||
* Parses the response data. | ||
* @param {Response} response - The fetch response object. | ||
* @param {Function} debug - The debug logger function. | ||
* @returns {Promise<unknown>} - The parsed response data. | ||
*/ | ||
parseResponse(response, debug) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const contentType = response.headers.get('Content-Type') || ''; | ||
try { | ||
if (contentType.includes('application/json')) { | ||
return yield response.json(); | ||
} | ||
return yield response.text(); | ||
} | ||
catch (error) { | ||
debug('Response parsing error: %o', error); | ||
return undefined; | ||
} | ||
}); | ||
} | ||
/** | ||
* Creates a custom error for fetch failures. | ||
* @param {Response} response - The fetch response object. | ||
* @param {unknown} data - The parsed response data. | ||
* @returns {NativeDataFetcherError} - The constructed error object. | ||
*/ | ||
createError(response, data) { | ||
return Object.assign(Object.assign({}, new Error(`HTTP ${response.status} ${response.statusText}`)), { response: { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: this.extractDebugHeaders(response.headers), | ||
data, | ||
} }); | ||
} | ||
} | ||
exports.NativeDataFetcher = NativeDataFetcher; |
"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()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.checkStatus = checkStatus; | ||
exports.trackEvent = trackEvent; | ||
const utils_1 = require("./../utils"); | ||
class ResponseError extends Error { | ||
constructor(message, response) { | ||
super(message); | ||
Object.setPrototypeOf(this, ResponseError.prototype); | ||
this.response = response; | ||
} | ||
} | ||
/** | ||
* @param {HttpResponse<T>} response response from fetch | ||
* @returns {HttpResponse<T>} response | ||
*/ | ||
function checkStatus(response) { | ||
if (response.status >= 200 && response.status < 300) { | ||
return response; | ||
} | ||
const error = new ResponseError(response.statusText, response); | ||
throw error; | ||
} | ||
/** | ||
* Note: axios needs to use `withCredentials: true` in order for Sitecore cookies to be included in CORS requests | ||
* Note: fetch api needs to use `credentials: include` in order for Sitecore cookies to be included in CORS requests | ||
* which is necessary for analytics and such | ||
@@ -32,8 +22,10 @@ * @param {string} url url to fetch | ||
*/ | ||
function fetchData(url, data, fetcher, params = {}) { | ||
return fetcher((0, utils_1.resolveUrl)(url, params), data) | ||
.then(checkStatus) | ||
.then((response) => { | ||
// axios auto-parses JSON responses, don't need to JSON.parse | ||
return response.data; | ||
function fetchData(url_1, data_1, fetcher_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, data, fetcher, params = {}) { | ||
const requestData = Object.assign(Object.assign({}, (typeof data === 'object' && data !== null ? data : {})), { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json' }, (typeof data === 'object' && data !== null && 'headers' in data | ||
? data.headers | ||
: {})), body: JSON.stringify(data) }); | ||
return fetcher((0, utils_1.resolveUrl)(url, params), requestData).then((response) => { | ||
return response.data; | ||
}); | ||
}); | ||
@@ -40,0 +32,0 @@ } |
@@ -68,7 +68,3 @@ "use strict"; | ||
var _a; | ||
return (error.code === '408' || | ||
error.code === 'ECONNABORTED' || | ||
error.code === 'ETIMEDOUT' || | ||
((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 408 || | ||
error.name === 'AbortError'); | ||
return ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 408 || error.name === 'AbortError'; | ||
}; | ||
@@ -75,0 +71,0 @@ exports.isTimeoutError = isTimeoutError; |
@@ -0,1 +1,10 @@ | ||
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()); | ||
}); | ||
}; | ||
import { resolveUrl } from './utils/utils'; | ||
@@ -10,24 +19,11 @@ export class ResponseError extends Error { | ||
/** | ||
* @param {HttpResponse<T>} response the response to check | ||
* @throws {ResponseError} if response code is not ok | ||
*/ | ||
export function checkStatus(response) { | ||
if (response.status >= 200 && response.status < 300) { | ||
return response; | ||
} | ||
const error = new ResponseError(response.statusText, response); | ||
throw error; | ||
} | ||
/** | ||
* @param {string} url the URL to request; may include query string | ||
* @param {HttpDataFetcher} fetcher the fetcher to use to perform the request | ||
* @param {HttpDataFetcher<T> | NativeDataFetcherFunction<T>} fetcher the fetcher to use to perform the request | ||
* @param {ParsedUrlQueryInput} params the query string parameters to send with the request | ||
*/ | ||
export function fetchData(url, fetcher, params = {}) { | ||
return fetcher(resolveUrl(url, params)) | ||
.then(checkStatus) | ||
.then((response) => { | ||
// axios auto-parses JSON responses, don't need to JSON.parse | ||
export function fetchData(url_1, fetcher_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, fetcher, params = {}) { | ||
const response = yield fetcher(resolveUrl(url, params)); | ||
return response.data; | ||
}); | ||
} |
@@ -106,3 +106,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
// Note we don't have access to raw request/response with graphql-request | ||
// (or nice hooks like we have with Axios), but we should log whatever we have. | ||
// but we should log whatever we have. | ||
this.debug('request: %o', { | ||
@@ -109,0 +109,0 @@ url: this.endpoint, |
@@ -10,3 +10,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
import { AxiosDataFetcher } from '../axios-fetcher'; | ||
import { NativeDataFetcher } from '../native-fetcher'; | ||
import { fetchData } from '../data-fetcher'; | ||
@@ -17,3 +17,3 @@ import { DictionaryServiceBase } from './dictionary-service'; | ||
* Fetch dictionary data using the Sitecore Dictionary Service REST API. | ||
* Uses Axios as the default data fetcher (@see AxiosDataFetcher). | ||
* Uses NativeDataFetcher as the default data fetcher (@see NativeDataFetcher). | ||
* @augments DictionaryServiceBase | ||
@@ -27,9 +27,9 @@ */ | ||
/** | ||
* Provides default @see AxiosDataFetcher data fetcher | ||
* Provides default @see NativeDataFetcher data fetcher | ||
*/ | ||
get defaultFetcher() { | ||
const dataFetcher = new AxiosDataFetcher({ | ||
const dataFetcher = new NativeDataFetcher({ | ||
debugger: debug.dictionary, | ||
// CORS issue: Sitecore provides 'Access-Control-Allow-Origin' as wildcard '*', so we can't include credentials for the dictionary service | ||
withCredentials: false, | ||
credentials: 'omit', | ||
}); | ||
@@ -36,0 +36,0 @@ return (url) => dataFetcher.fetch(url); |
@@ -5,8 +5,7 @@ // NOTE: all imports are now named as to not make breaking changes | ||
export { default as debug, enableDebug } from './debug'; | ||
export { fetchData } from './data-fetcher'; | ||
export { fetchData, ResponseError } from './data-fetcher'; | ||
export { DefaultRetryStrategy, GraphQLRequestClient, } from './graphql-request-client'; | ||
export { AxiosDataFetcher } from './axios-fetcher'; | ||
export { MemoryCacheClient } from './cache-client'; | ||
export { ClientError } from 'graphql-request'; | ||
export { NativeDataFetcher } from './native-fetcher'; | ||
export { NativeDataFetcher, } from './native-fetcher'; | ||
export { constants }; |
@@ -0,3 +1,12 @@ | ||
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()); | ||
}); | ||
}; | ||
import { LayoutServiceBase } from './layout-service'; | ||
import { AxiosDataFetcher } from '../axios-fetcher'; | ||
import { NativeDataFetcher, } from '../native-fetcher'; | ||
import { fetchData } from '../data-fetcher'; | ||
@@ -7,3 +16,3 @@ import debug from '../debug'; | ||
* Fetch layout data using the Sitecore Layout Service REST API. | ||
* Uses Axios as the default data fetcher (@see AxiosDataFetcher). | ||
* Uses NativeDataFetcher as the default data fetcher (@see NativeDataFetcher). | ||
* @augments LayoutServiceBase | ||
@@ -35,3 +44,4 @@ */ | ||
/** | ||
* Provides default @see AxiosDataFetcher data fetcher | ||
* Returns a fetcher function pre-configured with headers from the incoming request. | ||
* Provides default @see NativeDataFetcher data fetcher | ||
* @param {IncomingMessage} [req] Request instance | ||
@@ -42,14 +52,15 @@ * @param {ServerResponse} [res] Response instance | ||
this.getDefaultFetcher = (req, res) => { | ||
const config = { | ||
debugger: debug.layout, | ||
}; | ||
if (req && res) { | ||
config.onReq = this.setupReqHeaders(req); | ||
config.onRes = this.setupResHeaders(res); | ||
const config = { debugger: debug.layout }; | ||
let headers; | ||
if (req) { | ||
headers = this.setupReqHeaders(req); | ||
} | ||
const axiosFetcher = new AxiosDataFetcher(config); | ||
const fetcher = (url, data) => { | ||
return axiosFetcher.fetch(url, data); | ||
}; | ||
return fetcher; | ||
const nativeFetcher = new 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; | ||
}); | ||
}; | ||
@@ -67,24 +78,29 @@ } | ||
fetchLayoutData(itemPath, language, req, res) { | ||
const querystringParams = this.getFetchParams(language); | ||
debug.layout('fetching layout data for %s %s %s', itemPath, language, this.serviceConfig.siteName); | ||
const fetcher = this.getFetcher(req, res); | ||
const fetchUrl = this.resolveLayoutServiceUrl('render'); | ||
return fetchData(fetchUrl, fetcher, Object.assign({ item: itemPath }, querystringParams)).catch((error) => { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
var _a; | ||
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; | ||
const querystringParams = this.getFetchParams(language); | ||
debug.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 fetchData(fetchUrl, fetcher, Object.assign({ item: itemPath }, querystringParams)); | ||
} | ||
throw error; | ||
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; | ||
} | ||
}); | ||
@@ -122,12 +138,22 @@ } | ||
/** | ||
* Setup request headers | ||
* @param {IncomingMessage} req Request instance | ||
* @returns {AxiosRequestConfig} axios request config | ||
* 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) { | ||
return (reqConfig) => { | ||
debug.layout('performing request header passing'); | ||
reqConfig.headers.common = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, reqConfig.headers.common), (req.headers.cookie && { cookie: req.headers.cookie })), (req.headers.referer && { referer: req.headers.referer })), (req.headers['user-agent'] && { 'user-agent': req.headers['user-agent'] })), (req.connection.remoteAddress && { 'X-Forwarded-For': req.connection.remoteAddress })); | ||
return reqConfig; | ||
}; | ||
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; | ||
} | ||
@@ -137,12 +163,16 @@ /** | ||
* @param {ServerResponse} res Response instance | ||
* @returns {AxiosResponse} response | ||
* @param {NativeDataFetcherResponse<T>} serverRes | ||
* @returns {NativeDataFetcherResponse} response | ||
*/ | ||
setupResHeaders(res) { | ||
return (serverRes) => { | ||
debug.layout('performing response header passing'); | ||
serverRes.headers['set-cookie'] && | ||
res.setHeader('set-cookie', serverRes.headers['set-cookie']); | ||
return serverRes; | ||
}; | ||
setupResHeaders(res, serverRes) { | ||
debug.layout('performing response header passing'); | ||
const headers = serverRes.headers; | ||
if (headers instanceof Headers) { | ||
const setCookieHeader = headers.get('set-cookie'); | ||
if (setCookieHeader) { | ||
res.setHeader('set-cookie', setCookieHeader); | ||
} | ||
} | ||
return serverRes; | ||
} | ||
} |
@@ -28,9 +28,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
/** | ||
* Implements a data fetcher. @see HttpDataFetcher<T> type for implementation details/notes. | ||
* @param {string} url The URL to request; may include query string | ||
* @param {unknown} [data] Optional data to POST with the request. | ||
* @returns {Promise<HttpResponse<T>>} response | ||
* Implements a data fetcher. | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Optional fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
fetch(url, data) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
fetch(url_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, options = {}) { | ||
var _a; | ||
@@ -41,3 +41,3 @@ const _b = this.config, { debugger: debugOverride, fetch: fetchOverride } = _b, init = __rest(_b, ["debugger", "fetch"]); | ||
const debug = debugOverride || debuggers.http; | ||
const requestInit = this.getRequestInit(init, data); | ||
const requestInit = this.getRequestInit(Object.assign(Object.assign({}, init), options)); | ||
const fetchWithOptionalTimeout = [fetchImpl(url, requestInit)]; | ||
@@ -48,54 +48,96 @@ if (init.timeout) { | ||
} | ||
// Note a goal here is to provide consistent debug logging and error handling | ||
// as we do in AxiosDataFetcher and GraphQLRequestClient | ||
const { headers: reqHeaders } = requestInit, rest = __rest(requestInit, ["headers"]); | ||
debug('request: %o', Object.assign({ url, headers: this.extractDebugHeaders(reqHeaders) }, rest)); | ||
const response = yield Promise.race(fetchWithOptionalTimeout) | ||
.then((res) => { | ||
var _a; | ||
debug('Request initiated: %o', Object.assign({ url, headers: this.extractDebugHeaders(requestInit.headers) }, requestInit)); | ||
try { | ||
const response = yield Promise.race(fetchWithOptionalTimeout).then((res) => { | ||
var _a; | ||
(_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear(); | ||
return res; | ||
}); | ||
const respData = yield this.parseResponse(response, debug); | ||
if (!response.ok) { | ||
const error = this.createError(response, respData); | ||
debug('Response error: %o', error.response); | ||
throw error; | ||
} | ||
debug('Response in %dms: %o', Date.now() - startTimestamp, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: this.extractDebugHeaders(response.headers), | ||
url: response.url, | ||
data: respData, | ||
}); | ||
return Object.assign(Object.assign({}, response), { data: respData }); | ||
} | ||
catch (error) { | ||
(_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear(); | ||
return res; | ||
}) | ||
.catch((error) => { | ||
var _a; | ||
(_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear(); | ||
debug('request error: %o', error); | ||
debug('Request failed: %o', error); | ||
throw error; | ||
}); | ||
// Note even an error status may send useful json data in response (which we want for logging) | ||
let respData = undefined; | ||
const isJson = (_a = response.headers.get('Content-Type')) === null || _a === void 0 ? void 0 : _a.includes('application/json'); | ||
if (isJson) { | ||
respData = yield response.json().catch((error) => { | ||
debug('response.json() error: %o', error); | ||
}); | ||
} | ||
const debugResponse = { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: this.extractDebugHeaders(response.headers), | ||
url: response.url, | ||
redirected: response.redirected, | ||
data: respData, | ||
}; | ||
if (!response.ok) { | ||
debug('response error: %o', debugResponse); | ||
throw new Error(`HTTP ${response.status} ${response.statusText}`); | ||
} | ||
debug('response in %dms: %o', Date.now() - startTimestamp, debugResponse); | ||
return Object.assign(Object.assign({}, response), { data: respData }); | ||
}); | ||
} | ||
/** | ||
* Perform a GET request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
get(url_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'GET' }, options)); | ||
}); | ||
} | ||
/** | ||
* Perform a POST request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {unknown} body The data to send with the request | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
post(url_1, body_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, body, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'POST', body: JSON.stringify(body) }, options)); | ||
}); | ||
} | ||
/** | ||
* Perform a DELETE request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
delete(url_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'DELETE' }, options)); | ||
}); | ||
} | ||
/** | ||
* Perform a PUT request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {unknown} body The data to send with the request | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
put(url_1, body_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, body, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'PUT', body: JSON.stringify(body) }, options)); | ||
}); | ||
} | ||
/** | ||
* Perform a HEAD request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
head(url, options = {}) { | ||
return this.fetch(url, Object.assign({ method: 'HEAD' }, options)); | ||
} | ||
/** | ||
* Determines settings for the request | ||
* @param {RequestInit} init Custom settings for request | ||
* @param {unknown} [data] Optional data to POST with the request | ||
* @returns {RequestInit} The final request settings | ||
*/ | ||
getRequestInit(init = {}, data) { | ||
// This is a focused implementation (GET or POST only using JSON input/output) | ||
// so we are opinionated about method, body, and Content-Type | ||
init.method = data ? 'POST' : 'GET'; | ||
init.body = data ? JSON.stringify(data) : undefined; | ||
getRequestInit(init = {}) { | ||
const headers = new Headers(init.headers); | ||
if (!init.method) { | ||
init.method = init.body ? 'POST' : 'GET'; | ||
} | ||
headers.set('Content-Type', 'application/json'); | ||
@@ -119,2 +161,37 @@ init.headers = headers; | ||
} | ||
/** | ||
* Parses the response data. | ||
* @param {Response} response - The fetch response object. | ||
* @param {Function} debug - The debug logger function. | ||
* @returns {Promise<unknown>} - The parsed response data. | ||
*/ | ||
parseResponse(response, debug) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const contentType = response.headers.get('Content-Type') || ''; | ||
try { | ||
if (contentType.includes('application/json')) { | ||
return yield response.json(); | ||
} | ||
return yield response.text(); | ||
} | ||
catch (error) { | ||
debug('Response parsing error: %o', error); | ||
return undefined; | ||
} | ||
}); | ||
} | ||
/** | ||
* Creates a custom error for fetch failures. | ||
* @param {Response} response - The fetch response object. | ||
* @param {unknown} data - The parsed response data. | ||
* @returns {NativeDataFetcherError} - The constructed error object. | ||
*/ | ||
createError(response, data) { | ||
return Object.assign(Object.assign({}, new Error(`HTTP ${response.status} ${response.statusText}`)), { response: { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: this.extractDebugHeaders(response.headers), | ||
data, | ||
} }); | ||
} | ||
} |
@@ -0,22 +1,13 @@ | ||
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()); | ||
}); | ||
}; | ||
import { isServer, resolveUrl } from './../utils'; | ||
class ResponseError extends Error { | ||
constructor(message, response) { | ||
super(message); | ||
Object.setPrototypeOf(this, ResponseError.prototype); | ||
this.response = response; | ||
} | ||
} | ||
/** | ||
* @param {HttpResponse<T>} response response from fetch | ||
* @returns {HttpResponse<T>} response | ||
*/ | ||
export function checkStatus(response) { | ||
if (response.status >= 200 && response.status < 300) { | ||
return response; | ||
} | ||
const error = new ResponseError(response.statusText, response); | ||
throw error; | ||
} | ||
/** | ||
* Note: axios needs to use `withCredentials: true` in order for Sitecore cookies to be included in CORS requests | ||
* Note: fetch api needs to use `credentials: include` in order for Sitecore cookies to be included in CORS requests | ||
* which is necessary for analytics and such | ||
@@ -28,8 +19,10 @@ * @param {string} url url to fetch | ||
*/ | ||
function fetchData(url, data, fetcher, params = {}) { | ||
return fetcher(resolveUrl(url, params), data) | ||
.then(checkStatus) | ||
.then((response) => { | ||
// axios auto-parses JSON responses, don't need to JSON.parse | ||
return response.data; | ||
function fetchData(url_1, data_1, fetcher_1) { | ||
return __awaiter(this, arguments, void 0, function* (url, data, fetcher, params = {}) { | ||
const requestData = Object.assign(Object.assign({}, (typeof data === 'object' && data !== null ? data : {})), { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json' }, (typeof data === 'object' && data !== null && 'headers' in data | ||
? data.headers | ||
: {})), body: JSON.stringify(data) }); | ||
return fetcher(resolveUrl(url, params), requestData).then((response) => { | ||
return response.data; | ||
}); | ||
}); | ||
@@ -36,0 +29,0 @@ } |
@@ -60,7 +60,3 @@ import isServer from './is-server'; | ||
var _a; | ||
return (error.code === '408' || | ||
error.code === 'ECONNABORTED' || | ||
error.code === 'ETIMEDOUT' || | ||
((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 408 || | ||
error.name === 'AbortError'); | ||
return ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 408 || error.name === 'AbortError'; | ||
}; | ||
@@ -67,0 +63,0 @@ /** |
{ | ||
"name": "@sitecore-jss/sitecore-jss", | ||
"version": "22.4.0-canary.9", | ||
"version": "22.4.0-canary.10", | ||
"main": "dist/cjs/index.js", | ||
@@ -50,3 +50,3 @@ "module": "dist/esm/index.js", | ||
"mocha": "^10.2.0", | ||
"nock": "^13.0.5", | ||
"nock": "14.0.0-beta.7", | ||
"nyc": "^15.1.0", | ||
@@ -59,3 +59,2 @@ "sinon": "^17.0.1", | ||
"dependencies": { | ||
"axios": "^0.21.1", | ||
"chalk": "^4.1.0", | ||
@@ -71,3 +70,3 @@ "debug": "^4.3.1", | ||
"types": "types/index.d.ts", | ||
"gitHead": "5d94e2017b872ca4c798162ff0392da0628ce69d", | ||
"gitHead": "1220b71aa4c565d5f8dffde56f362543d6fda3f7", | ||
"files": [ | ||
@@ -74,0 +73,0 @@ "dist", |
@@ -0,1 +1,2 @@ | ||
import { NativeDataFetcherFunction } from './native-fetcher'; | ||
import { ParsedUrlQueryInput } from 'querystring'; | ||
@@ -16,3 +17,3 @@ /** | ||
* Describes functions that fetch data asynchronously (i.e. from an API endpoint). | ||
* This interface conforms to Axios' public API, but is adaptable to other HTTP libraries and | ||
* This interface conforms to 'fetch' public API, but is adaptable to other HTTP libraries and | ||
* fetch polyfills. | ||
@@ -30,11 +31,6 @@ * The interface implementation must: | ||
/** | ||
* @param {HttpResponse<T>} response the response to check | ||
* @throws {ResponseError} if response code is not ok | ||
*/ | ||
export declare function checkStatus<T>(response: HttpResponse<T>): HttpResponse<T>; | ||
/** | ||
* @param {string} url the URL to request; may include query string | ||
* @param {HttpDataFetcher} fetcher the fetcher to use to perform the request | ||
* @param {HttpDataFetcher<T> | NativeDataFetcherFunction<T>} fetcher the fetcher to use to perform the request | ||
* @param {ParsedUrlQueryInput} params the query string parameters to send with the request | ||
*/ | ||
export declare function fetchData<T>(url: string, fetcher: HttpDataFetcher<T>, params?: ParsedUrlQueryInput): Promise<T>; | ||
export declare function fetchData<T>(url: string, fetcher: HttpDataFetcher<T> | NativeDataFetcherFunction<T>, params?: ParsedUrlQueryInput): Promise<T>; |
@@ -31,3 +31,3 @@ import { HttpDataFetcher } from '../data-fetcher'; | ||
* Fetch dictionary data using the Sitecore Dictionary Service REST API. | ||
* Uses Axios as the default data fetcher (@see AxiosDataFetcher). | ||
* Uses NativeDataFetcher as the default data fetcher (@see NativeDataFetcher). | ||
* @augments DictionaryServiceBase | ||
@@ -39,3 +39,3 @@ */ | ||
/** | ||
* Provides default @see AxiosDataFetcher data fetcher | ||
* Provides default @see NativeDataFetcher data fetcher | ||
*/ | ||
@@ -42,0 +42,0 @@ get defaultFetcher(): HttpDataFetcher<RestDictionaryServiceData>; |
import * as constants from './constants'; | ||
export { default as debug, Debugger, enableDebug } from './debug'; | ||
export { HttpDataFetcher, HttpResponse, fetchData } from './data-fetcher'; | ||
export { HttpDataFetcher, HttpResponse, fetchData, ResponseError } from './data-fetcher'; | ||
export { RetryStrategy, DefaultRetryStrategy, GraphQLClient, GraphQLRequestClient, GraphQLRequestClientConfig, GraphQLRequestClientFactory, GraphQLRequestClientFactoryConfig, } from './graphql-request-client'; | ||
export { AxiosDataFetcher, AxiosDataFetcherConfig } from './axios-fetcher'; | ||
export { CacheClient, CacheOptions, MemoryCacheClient } from './cache-client'; | ||
export { AxiosResponse } from 'axios'; | ||
export { ClientError } from 'graphql-request'; | ||
export { NativeDataFetcher, NativeDataFetcherConfig } from './native-fetcher'; | ||
export { NativeDataFetcher, NativeDataFetcherError, NativeDataFetcherConfig, NativeDataFetcherResponse, } from './native-fetcher'; | ||
export { HTMLLink } from './models'; | ||
export { constants }; |
@@ -1,5 +0,5 @@ | ||
import { AxiosRequestConfig, AxiosResponse } from 'axios'; | ||
import { IncomingMessage, ServerResponse } from 'http'; | ||
import { LayoutServiceBase } from './layout-service'; | ||
import { PlaceholderData, LayoutServiceData } from './models'; | ||
import { NativeDataFetcherFunction, NativeDataFetcherResponse } from '../native-fetcher'; | ||
import { HttpDataFetcher } from '../data-fetcher'; | ||
@@ -47,6 +47,6 @@ interface FetchParams { | ||
*/ | ||
export type DataFetcherResolver = <T>(req?: IncomingMessage, res?: ServerResponse) => HttpDataFetcher<T>; | ||
export type DataFetcherResolver = <T>(req?: IncomingMessage, res?: ServerResponse) => HttpDataFetcher<T> | NativeDataFetcherFunction<T>; | ||
/** | ||
* Fetch layout data using the Sitecore Layout Service REST API. | ||
* Uses Axios as the default data fetcher (@see AxiosDataFetcher). | ||
* Uses NativeDataFetcher as the default data fetcher (@see NativeDataFetcher). | ||
* @augments LayoutServiceBase | ||
@@ -85,3 +85,3 @@ */ | ||
protected getFetchParams: (language?: string) => FetchParams; | ||
protected getFetcher: (req?: IncomingMessage, res?: ServerResponse) => HttpDataFetcher<LayoutServiceData>; | ||
protected getFetcher: (req?: IncomingMessage, res?: ServerResponse) => HttpDataFetcher<LayoutServiceData> | NativeDataFetcherFunction<LayoutServiceData>; | ||
/** | ||
@@ -94,3 +94,4 @@ * Resolves layout service url | ||
/** | ||
* Provides default @see AxiosDataFetcher data fetcher | ||
* Returns a fetcher function pre-configured with headers from the incoming request. | ||
* Provides default @see NativeDataFetcher data fetcher | ||
* @param {IncomingMessage} [req] Request instance | ||
@@ -100,16 +101,17 @@ * @param {ServerResponse} [res] Response instance | ||
*/ | ||
protected getDefaultFetcher: <T>(req?: IncomingMessage, res?: ServerResponse) => (url: string, data?: unknown) => Promise<AxiosResponse<T>>; | ||
protected getDefaultFetcher: <T>(req?: IncomingMessage, res?: ServerResponse) => (url: string, data?: RequestInit) => Promise<NativeDataFetcherResponse<T>>; | ||
/** | ||
* Setup request headers | ||
* @param {IncomingMessage} req Request instance | ||
* @returns {AxiosRequestConfig} axios request config | ||
* 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. | ||
*/ | ||
protected setupReqHeaders(req: IncomingMessage): (reqConfig: AxiosRequestConfig) => AxiosRequestConfig; | ||
protected setupReqHeaders(req?: IncomingMessage): Headers; | ||
/** | ||
* Setup response headers based on response from layout service | ||
* @param {ServerResponse} res Response instance | ||
* @returns {AxiosResponse} response | ||
* @param {NativeDataFetcherResponse<T>} serverRes | ||
* @returns {NativeDataFetcherResponse} response | ||
*/ | ||
protected setupResHeaders(res: ServerResponse): (serverRes: AxiosResponse) => AxiosResponse<any>; | ||
protected setupResHeaders<T>(res: ServerResponse, serverRes: NativeDataFetcherResponse<T>): NativeDataFetcherResponse<T>; | ||
} | ||
export {}; |
@@ -1,2 +0,1 @@ | ||
import { HttpResponse } from './data-fetcher'; | ||
import { Debugger } from './debug'; | ||
@@ -17,2 +16,29 @@ type NativeDataFetcherOptions = { | ||
}; | ||
/** | ||
* Response data for an HTTP request sent to an API | ||
* @template T the type of data model requested | ||
*/ | ||
export interface NativeDataFetcherResponse<T> { | ||
/** HTTP status code of the response (i.e. 200, 404) */ | ||
status: number; | ||
/** HTTP status text of the response (i.e. 'OK', 'Bad Request') */ | ||
statusText: string; | ||
/** Response content */ | ||
data: T; | ||
/** Response headers */ | ||
headers?: HeadersInit; | ||
} | ||
/** | ||
* Native fetcher error type to include response text and status | ||
*/ | ||
export type NativeDataFetcherError = Error & { | ||
response: NativeDataFetcherResponse<unknown>; | ||
}; | ||
/** | ||
* A function that fetches data from a given URL and returns a `NativeDataFetcherResponse`. | ||
* @param {string} url The URL to request (can include query string parameters). | ||
* @param {unknown} [data] Optional data to send with the request (e.g., for POST or PUT requests). | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} A promise that resolves to a `NativeDataFetcherResponse<T>`, | ||
*/ | ||
export type NativeDataFetcherFunction<T> = (url: string, data?: RequestInit) => Promise<NativeDataFetcherResponse<T>>; | ||
export type NativeDataFetcherConfig = NativeDataFetcherOptions & RequestInit; | ||
@@ -24,15 +50,51 @@ export declare class NativeDataFetcher { | ||
/** | ||
* Implements a data fetcher. @see HttpDataFetcher<T> type for implementation details/notes. | ||
* @param {string} url The URL to request; may include query string | ||
* @param {unknown} [data] Optional data to POST with the request. | ||
* @returns {Promise<HttpResponse<T>>} response | ||
* Implements a data fetcher. | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Optional fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
fetch<T>(url: string, data?: unknown): Promise<HttpResponse<T>>; | ||
fetch<T>(url: string, options?: RequestInit): Promise<NativeDataFetcherResponse<T>>; | ||
/** | ||
* Perform a GET request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
get<T>(url: string, options?: RequestInit): Promise<NativeDataFetcherResponse<T>>; | ||
/** | ||
* Perform a POST request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {unknown} body The data to send with the request | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
post<T>(url: string, body: unknown, options?: RequestInit): Promise<NativeDataFetcherResponse<T>>; | ||
/** | ||
* Perform a DELETE request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
delete<T>(url: string, options?: RequestInit): Promise<NativeDataFetcherResponse<T>>; | ||
/** | ||
* Perform a PUT request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {unknown} body The data to send with the request | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
put<T>(url: string, body: unknown, options?: RequestInit): Promise<NativeDataFetcherResponse<T>>; | ||
/** | ||
* Perform a HEAD request | ||
* @param {string} url The URL to request (may include query string) | ||
* @param {RequestInit} [options] Fetch options | ||
* @returns {Promise<NativeDataFetcherResponse<T>>} response | ||
*/ | ||
head<T>(url: string, options?: RequestInit): Promise<NativeDataFetcherResponse<T>>; | ||
/** | ||
* Determines settings for the request | ||
* @param {RequestInit} init Custom settings for request | ||
* @param {unknown} [data] Optional data to POST with the request | ||
* @returns {RequestInit} The final request settings | ||
*/ | ||
protected getRequestInit(init?: RequestInit, data?: unknown): RequestInit; | ||
protected getRequestInit(init?: RequestInit): RequestInit; | ||
/** | ||
@@ -46,3 +108,17 @@ * Safely extract all headers for debug logging | ||
}; | ||
/** | ||
* Parses the response data. | ||
* @param {Response} response - The fetch response object. | ||
* @param {Function} debug - The debug logger function. | ||
* @returns {Promise<unknown>} - The parsed response data. | ||
*/ | ||
private parseResponse; | ||
/** | ||
* Creates a custom error for fetch failures. | ||
* @param {Response} response - The fetch response object. | ||
* @param {unknown} data - The parsed response data. | ||
* @returns {NativeDataFetcherError} - The constructed error object. | ||
*/ | ||
private createError; | ||
} | ||
export {}; |
import { CampaignInstance, EventInstance, GoalInstance, OutcomeInstance, PageViewInstance } from './dataModels'; | ||
import { TrackingRequestOptions } from './trackingRequestOptions'; | ||
import { HttpResponse } from './../data-fetcher'; | ||
/** | ||
* @param {HttpResponse<T>} response response from fetch | ||
* @returns {HttpResponse<T>} response | ||
*/ | ||
export declare function checkStatus<T>(response: HttpResponse<T>): HttpResponse<T>; | ||
/** | ||
* Makes a request to Sitecore Layout Service for the specified route item path. | ||
@@ -11,0 +5,0 @@ * @param {Array<EventInstance | GoalInstance | OutcomeInstance | CampaignInstance | PageViewInstance>} events events to send |
7
441180
180
9664
- Removedaxios@^0.21.1
- Removedaxios@0.21.4(transitive)
- Removedfollow-redirects@1.15.9(transitive)