UNPKG

@commercetools-frontend/sdk

Version:
581 lines (556 loc) • 25 kB
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 };