@commercetools-frontend/sdk
Version:
Tools for declarative fetching
581 lines (556 loc) • 25 kB
JavaScript
import _Object$keys from '@babel/runtime-corejs3/core-js-stable/object/keys';
import _Object$getOwnPropertySymbols from '@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols';
import _filterInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/filter';
import _Object$getOwnPropertyDescriptor from '@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptor';
import _forEachInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/for-each';
import _Object$getOwnPropertyDescriptors from '@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptors';
import _Object$defineProperties from '@babel/runtime-corejs3/core-js-stable/object/define-properties';
import _Object$defineProperty from '@babel/runtime-corejs3/core-js-stable/object/define-property';
import _slicedToArray from '@babel/runtime-corejs3/helpers/esm/slicedToArray';
import _defineProperty from '@babel/runtime-corejs3/helpers/esm/defineProperty';
import _reduceInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/reduce';
import _Object$entries from '@babel/runtime-corejs3/core-js-stable/object/entries';
import omitEmpty from 'omit-empty-es';
import _Reflect$construct from '@babel/runtime-corejs3/core-js-stable/reflect/construct';
import _classCallCheck from '@babel/runtime-corejs3/helpers/esm/classCallCheck';
import _createClass from '@babel/runtime-corejs3/helpers/esm/createClass';
import _inherits from '@babel/runtime-corejs3/helpers/esm/inherits';
import _possibleConstructorReturn from '@babel/runtime-corejs3/helpers/esm/possibleConstructorReturn';
import _getPrototypeOf from '@babel/runtime-corejs3/helpers/esm/getPrototypeOf';
import _pt from 'prop-types';
import { Component } from 'react';
import { deepEqual } from 'fast-equals';
import { connect, useDispatch } from 'react-redux';
import { createRequestBuilder } from '@commercetools/api-request-builder';
import { SHOW_LOADING, STATUS_CODES, HIDE_LOADING } from '@commercetools-frontend/constants';
import _URL from '@babel/runtime-corejs3/core-js-stable/url';
import { decode } from 'qss';
import _globalThis from '@babel/runtime-corejs3/core-js/global-this';
import { Buffer } from 'buffer';
import createHttpUserAgent from '@commercetools/http-user-agent';
import { createClient as createClient$1 } from '@commercetools/sdk-client';
import { createCorrelationIdMiddleware as createCorrelationIdMiddleware$1 } from '@commercetools/sdk-middleware-correlation-id';
import { createHttpMiddleware } from '@commercetools/sdk-middleware-http';
function ownKeys$3(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = _filterInstanceProperty(o).call(o, function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$3(e) { for (var r = 1; r < arguments.length; r++) { var _context2, _context3; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context2 = ownKeys$3(Object(t), !0)).call(_context2, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context3 = ownKeys$3(Object(t))).call(_context3, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
function get(payload) {
return {
type: 'SDK',
payload: _objectSpread$3(_objectSpread$3({}, payload), {}, {
method: 'GET'
})
};
}
// contrary to the other methods this does not bear the exact name of the HTTP-verb
// because `delete` is a reserved keyword in ECMAScript
function del(payload) {
return {
type: 'SDK',
payload: _objectSpread$3(_objectSpread$3({}, payload), {}, {
method: 'DELETE'
})
};
}
function head(payload) {
return {
type: 'SDK',
payload: _objectSpread$3(_objectSpread$3({}, payload), {}, {
method: 'HEAD'
})
};
}
function post(payload) {
return {
type: 'SDK',
payload: _objectSpread$3(_objectSpread$3({}, payload), {}, {
method: 'POST'
})
};
}
const enhancePayloadForForwardToProxy = payload => {
var _context;
const headers = payload.headers ?? {};
const exchangeTokenClaims = [];
if (payload.includeUserPermissions) {
exchangeTokenClaims.push('permissions');
}
return {
uri: '/proxy/forward-to',
mcApiProxyTarget: undefined,
headers: omitEmpty(_objectSpread$3(_objectSpread$3({}, _reduceInstanceProperty(_context = _Object$entries(headers)).call(_context, (customForwardHeaders, _ref) => {
let _ref2 = _slicedToArray(_ref, 2),
headerName = _ref2[0],
headerValue = _ref2[1];
return _objectSpread$3(_objectSpread$3({}, customForwardHeaders), {}, {
// Prefix headers so that the MC API can allow and forward them.
[`x-forward-header-${headerName}`]: headerValue
});
}, {})), {}, {
'Accept-version': 'v2',
'X-Forward-To': payload.uri,
'X-Forward-To-Audience-Policy': payload.audiencePolicy || 'forward-url-full-path',
'X-Forward-To-Claims': exchangeTokenClaims.join(' ')
}))
};
};
const forwardTo = {
get(payload) {
return {
type: 'SDK',
payload: _objectSpread$3(_objectSpread$3({}, payload), {}, {
method: 'GET'
}, enhancePayloadForForwardToProxy(payload))
};
},
del(payload) {
return {
type: 'SDK',
payload: _objectSpread$3(_objectSpread$3({}, payload), {}, {
method: 'DELETE'
}, enhancePayloadForForwardToProxy(payload))
};
},
head(payload) {
return {
type: 'SDK',
payload: _objectSpread$3(_objectSpread$3({}, payload), {}, {
method: 'HEAD'
}, enhancePayloadForForwardToProxy(payload))
};
},
post(payload) {
return {
type: 'SDK',
payload: _objectSpread$3(_objectSpread$3({}, payload), {}, {
method: 'POST'
}, enhancePayloadForForwardToProxy(payload))
};
}
};
var index = /*#__PURE__*/Object.freeze({
__proto__: null,
get: get,
del: del,
head: head,
post: post,
forwardTo: forwardTo
});
function _createSuper(t) { var r = _isNativeReflectConstruct(); return function () { var e, o = _getPrototypeOf(t); if (r) { var s = _getPrototypeOf(this).constructor; e = _Reflect$construct(o, arguments, s); } else e = o.apply(this, arguments); return _possibleConstructorReturn(this, e); }; }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); }
const defaultProps = {
actionCreatorArgs: [],
shouldRefetch: (prevArgs, nextArgs) => !deepEqual(prevArgs, nextArgs)
};
let SdkGet = /*#__PURE__*/function (_Component) {
_inherits(SdkGet, _Component);
var _super = _createSuper(SdkGet);
function SdkGet() {
var _this;
_classCallCheck(this, SdkGet);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _super.call(this, ...args);
_this.state = {
// We want the component to have a loading state by default, so we
// keep track of whether the first request has completed.
// We can't use requestsInFlight only as that would lead to a flash of
// the loading state until the first request starts in componentDidMount.
isWaitingForCompletionOfFirstRequest: true,
requestsInFlight: 0,
result: undefined,
error: undefined
};
_this.isComponentMounted = false;
_this.changeRequestsInFlight = delta => {
if (_this.isComponentMounted) _this.setState(prevState => ({
requestsInFlight: prevState.requestsInFlight + delta
}));
};
_this.fetch = _ref => {
let dispatch = _ref.dispatch,
actionCreator = _ref.actionCreator,
actionCreatorArgs = _ref.actionCreatorArgs,
onSuccess = _ref.onSuccess,
onError = _ref.onError;
_this.changeRequestsInFlight(1);
return dispatch(actionCreator(...(actionCreatorArgs ?? defaultProps.actionCreatorArgs))).then(result => {
_this.changeRequestsInFlight(-1);
if (_this.isComponentMounted) _this.setState({
error: undefined,
result
});
if (onSuccess) onSuccess(result);
return result;
}, error => {
_this.changeRequestsInFlight(-1);
if (_this.isComponentMounted) _this.setState({
error,
result: undefined
});
if (onError) onError(error);else SdkGet.errorHandler(error);
});
};
_this.refresh = () => _this.fetch({
dispatch: _this.props.dispatch,
actionCreator: _this.props.actionCreator,
actionCreatorArgs: _this.props.actionCreatorArgs,
onSuccess: _this.props.onSuccess,
onError: _this.props.onError
});
return _this;
}
_createClass(SdkGet, [{
key: "componentDidMount",
value: function componentDidMount() {
this.isComponentMounted = true;
this.fetch({
dispatch: this.props.dispatch,
actionCreator: this.props.actionCreator,
actionCreatorArgs: this.props.actionCreatorArgs,
onSuccess: this.props.onSuccess,
onError: this.props.onError
}).then(result => {
if (this.isComponentMounted) this.setState({
isWaitingForCompletionOfFirstRequest: false
});
return result;
}, error => {
if (this.isComponentMounted) this.setState({
isWaitingForCompletionOfFirstRequest: false
});
throw error;
});
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps) {
const shouldRefetch = this.props.shouldRefetch ?? defaultProps.shouldRefetch;
if (shouldRefetch(prevProps.actionCreatorArgs ?? defaultProps.actionCreatorArgs, this.props.actionCreatorArgs ?? defaultProps.actionCreatorArgs)) {
this.fetch({
dispatch: this.props.dispatch,
actionCreator: this.props.actionCreator,
actionCreatorArgs: this.props.actionCreatorArgs,
onSuccess: this.props.onSuccess,
onError: this.props.onError
});
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.isComponentMounted = false;
}
}, {
key: "render",
value: function render() {
return this.props.render({
isLoading: this.state.requestsInFlight > 0 || this.state.isWaitingForCompletionOfFirstRequest,
refresh: this.refresh,
result: this.state.result,
error: this.state.error
});
}
}]);
return SdkGet;
}(Component);
SdkGet.displayName = 'SdkGet';
SdkGet.errorHandler = error => {
throw error;
};
SdkGet.propTypes = process.env.NODE_ENV !== "production" ? {
dispatch: _pt.func.isRequired,
actionCreator: _pt.func.isRequired,
actionCreatorArgs: _pt.arrayOf(_pt.any),
shouldRefetch: _pt.func,
onSuccess: _pt.func,
onError: _pt.func,
render: _pt.func.isRequired
} : {};
const mapDispatchToProps = dispatch => ({
dispatch
});
var SdkGet$1 = connect(null, mapDispatchToProps)(SdkGet);
// NOTE: This string will be replaced on build time with the package version.
var version = "24.2.1";
/* eslint-disable no-console */
const isLoggerEnabled = () => {
if (process.env.DEBUG === 'true') return true;
if (process.env.NODE_ENV === 'development') return true;
const queryParams = new _URL(window.location.href);
if (process.env.NODE_ENV === 'production' && queryParams.searchParams.get('debug') === 'true') return true;
return false;
};
const logger = {
groupCollapsed: function () {
return isLoggerEnabled() && console.groupCollapsed(...arguments);
},
groupEnd: () => isLoggerEnabled() && console.groupEnd(),
info: function () {
return isLoggerEnabled() && console.info(...arguments);
},
log: function () {
return isLoggerEnabled() && console.log(...arguments);
},
error: function () {
return isLoggerEnabled() && console.error(...arguments);
},
warn: function () {
return isLoggerEnabled() && console.warn(...arguments);
}
};
function ownKeys$2(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = _filterInstanceProperty(o).call(o, function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$2(e) { for (var r = 1; r < arguments.length; r++) { var _context, _context2; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context = ownKeys$2(Object(t), !0)).call(_context, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context2 = ownKeys$2(Object(t))).call(_context2, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
const parseUri = uri => {
const parser = document.createElement('a');
parser.href = uri;
return {
pathname: parser.pathname,
search: decode(parser.search.substring(1))
};
};
const isPostAction = action => action.payload.method === 'POST';
const logRequest = _ref => {
let method = _ref.method,
request = _ref.request,
response = _ref.response,
error = _ref.error,
action = _ref.action;
const uriParts = parseUri(request.uri);
const groupName = `%c${method} %c${uriParts.pathname}`;
logger.groupCollapsed(groupName, `color: ${error ? 'red' : 'black'}; font-weight: bold;`, 'color: gray; font-weight: lighter;');
logger.log('%caction', 'color: cadetblue; font-weight: bold;', action);
logger.log('%crequest', `color: cornflowerblue; font-weight: bold;`, _objectSpread$2({
headers: request.headers,
uri: request.uri,
params: uriParts.search
}, isPostAction(action) ? {
body: action.payload.payload
} : {}));
if (response) logger.log('%cresponse', `color: green; font-weight: bold;`, response);
if (error) logger.log('%cerror', `color: red; font-weight: bold;`, error);
logger.groupEnd();
};
const mcHostnameRegex = /^mc(-(\d){4,})?\.(.*)$/;
const mcPreviewHostnameRegex = /^.*\.mc-preview\.(.*)$/;
const getMcOriginTld = host => {
if (host.match(mcPreviewHostnameRegex)) {
return host.replace(mcPreviewHostnameRegex, '$1');
}
return host.replace(mcHostnameRegex, '$3');
};
const getMcApiUrlFromOrigin = actualWindow => {
const url = new _URL(actualWindow.origin);
const originTld = getMcOriginTld(url.host);
return `${url.protocol}//mc-api.${originTld}`;
};
const parseAsBoolean = value => value === true || value === 'true';
function getMcApiUrl() {
let actualWindow = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window;
const isServedByProxy = parseAsBoolean(actualWindow.app.servedByProxy);
/**
* Prefer using the origin URL for the MC API based on the origin value
* of the browser's `window.location`.
* This ensures that the application always uses the correct URL associated
* with that environment, instead of relying on the config value.
*/
if (isServedByProxy) {
return getMcApiUrlFromOrigin(actualWindow);
}
return actualWindow.app.mcApiUrl;
}
function ownKeys$1(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = _filterInstanceProperty(o).call(o, function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var _context, _context2; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context = ownKeys$1(Object(t), !0)).call(_context, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context2 = ownKeys$1(Object(t))).call(_context2, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
// This is currently required by @commercetools/sdk-middleware-http package
_globalThis.Buffer = Buffer;
const userAgent = createHttpUserAgent({
name: '@commercetools/sdk-client',
libraryName: [typeof window !== 'undefined' ? window.app.applicationName : 'unknown-application-name', 'sdk'].join('/'),
libraryVersion: version,
contactUrl: 'https://git.io/fjuyC',
// points to the appkit repo issues
contactEmail: 'mc@commercetools.com'
});
const customUserAgentMiddleware = next => (request, response) => {
const requestWithCustomUserAgent = _objectSpread$1(_objectSpread$1({}, request), {}, {
headers: _objectSpread$1(_objectSpread$1({}, request.headers), {}, {
'X-User-Agent': userAgent
})
});
next(requestWithCustomUserAgent, response);
};
// NOTE we should not use these directly but rather have them passed in from
// the application
const httpMiddleware = createHttpMiddleware({
host: getMcApiUrl(),
includeResponseHeaders: true,
credentialsMode: 'include',
fetch
});
const createCorrelationIdMiddleware = _ref => {
let getCorrelationId = _ref.getCorrelationId;
return createCorrelationIdMiddleware$1({
generate: getCorrelationId
});
};
const createClient = _ref2 => {
let getCorrelationId = _ref2.getCorrelationId;
return createClient$1({
middlewares: [createCorrelationIdMiddleware({
getCorrelationId
}), customUserAgentMiddleware, httpMiddleware]
});
};
function ownKeys(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = _filterInstanceProperty(o).call(o, function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var _context2, _context3; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context2 = ownKeys(Object(t), !0)).call(_context2, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context3 = ownKeys(Object(t))).call(_context3, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
const isSdkActionForUri = actionPayload => actionPayload.uri !== undefined;
// https://github.com/commercetools/nodejs/blob/master/packages/api-request-builder/src/default-services.js#L200:L200
const ORDER_EDIT_SERVICE = 'orderEdits';
const actionToUri = (action, projectKey) => {
if (isSdkActionForUri(action.payload)) return action.payload.uri;
// Validate that `projectKey` exists
if (!projectKey) {
throw new Error(`Expected projectKey to be defined for action service "${action.payload.service}" (method "${action.payload.method}")`);
}
const requestBuilder = createRequestBuilder({
projectKey
});
// NOTE it's weird that we have to access this from the request builder.
// Shouldn't it just be a part of the object we parse?
// NOTE shouldn't requestBuilder be called requestUriBuilder instead?
const service = requestBuilder[action.payload.service];
if (action.payload.options) service.parse(action.payload.options);
return service.build({
// given `service=orderEdits` and given `applyOrderEditTo`, we build an apply endpoint
// given `service=orderEdits` and no `applyOrderEditTo`, we build an update endpoint
// https://docs.commercetools.com/api/projects/order-edits
applyOrderEdit: action.payload.service === ORDER_EDIT_SERVICE && typeof action.payload.options?.applyOrderEditTo === 'string',
// at this stage, the `projectKey` should be available already.
withProjectKey: true
});
};
// Force TS cast of generic action to TNotificationAction
const isSdkAction = action => action.type === 'SDK';
const isSdkError = error => error.statusCode !== undefined;
function createSdkMiddleware(_ref) {
let getCorrelationId = _ref.getCorrelationId,
getProjectKey = _ref.getProjectKey,
getAdditionalHeaders = _ref.getAdditionalHeaders;
const client = createClient({
getCorrelationId
});
const middleware = _ref2 => {
let dispatch = _ref2.dispatch;
return next => action => {
var _context;
if (!isSdkAction(action)) {
return next(action);
}
const projectKey = getProjectKey();
const uri = _filterInstanceProperty(_context = [action.payload.mcApiProxyTarget && `/proxy/${action.payload.mcApiProxyTarget}`, actionToUri(action, projectKey)]).call(_context, Boolean).join('');
// This `requestName` is never really used.
//
// We keep track of requests which are in progress in the `loading` state of
// the application. The `loading` state is an array of strings
// (which are correlation Ids, action types or request names).
// This is just done so that debugging is easier.
//
// It's easier to debug
// loading: ['PRODUCTS_FETCHED', 'sdk.fetch(/product-projection-search)']
// than to debug
// loading: 2
const requestName = `sdk.${action.payload.method.toLowerCase()}(${uri})`;
// NOTE here the middleware is aware of the application
// Instead we should probably convert to a middleware factory
// and provide hooks for `onFetch`, `onResult` and `onError
dispatch({
type: SHOW_LOADING,
payload: requestName
});
// NOTE the promise returned by the client resolves to a custom format
// it will contain { statusCode, headers, body }
// NOTE This retry logic could be moved to an sdk client middleware,
// but the client's middleware system is not capable of that right now
// https://github.com/commercetools/merchant-center-frontend/pull/3304
// https://github.com/commercetools/nodejs/issues/390
const sendRequest = function () {
let _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
shouldRenewToken = _ref3.shouldRenewToken;
const additionalHeaders = getAdditionalHeaders();
const headers = _objectSpread(_objectSpread(_objectSpread(_objectSpread({
Accept: 'application/json'
}, action.payload.headers || {}), shouldRenewToken ? {
'X-Force-Token': 'true'
} : {}), projectKey && {
'X-Project-Key': projectKey
}), additionalHeaders ?? {});
const body = action.payload.method === 'POST' ? action.payload.payload : undefined;
return client.execute({
uri,
method: action.payload.method,
headers,
body
}).then(result => {
if (process.env.NODE_ENV === 'development') logRequest({
method: action.payload.method,
request: {
headers,
uri
},
response: result.body,
action
});
return result;
}, error => {
if (process.env.NODE_ENV === 'development') logRequest({
method: action.payload.method,
request: {
headers,
uri
},
error,
action
});
throw error;
});
};
return sendRequest().catch(error => {
// in case of 401 error, try again with a new token
// https://github.com/commercetools/merchant-center-backend/blob/master/docs/AUTHENTICATION.md#problems-due-to-oauth-token-caching
if (isSdkError(error) && error.statusCode === STATUS_CODES.UNAUTHORIZED) {
return sendRequest({
shouldRenewToken: true
});
}
throw error;
}).then(result => {
dispatch({
type: HIDE_LOADING,
payload: requestName
});
// The promise returned by "fetch" will reject when the request fails,
// but only in certain cases. See "Checking that the fetch was successful"
// in https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
// The SDK already handles this case for us.
return result.body;
}, error => {
dispatch({
type: HIDE_LOADING,
payload: requestName
});
throw error;
});
};
};
return middleware;
}
// Wraps `dispatch` and cast the return type to a Promise, as the middleware
// returns a Promise.
function useAsyncDispatch() {
const dispatch = useDispatch();
return dispatch;
}
const Sdk = {
Get: SdkGet$1
};
export { Sdk, index as actions, createSdkMiddleware as createMiddleware, useAsyncDispatch, version };