UNPKG

@automattic/shopping-cart

Version:
153 lines 6.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createShoppingCartManagerClient = void 0; const tslib_1 = require("tslib"); const debug_1 = tslib_1.__importDefault(require("debug")); const cart_functions_1 = require("./cart-functions"); const errors_1 = require("./errors"); const managers_1 = require("./managers"); const shopping_cart_actions_1 = require("./shopping-cart-actions"); const shopping_cart_reducer_1 = require("./shopping-cart-reducer"); const state_based_actions_1 = require("./state-based-actions"); const sync_1 = require("./sync"); const debug = (0, debug_1.default)('shopping-cart:shopping-cart-manager'); /** * Enhance an action dispatcher to return a Promise that will resolve when the * cart next reaches a `valid` CacheStatus. * * This is the dispatcher used for all actions in the ShoppingCartManager's * public API. * * See `createActionPromisesManager` to learn about how the Promises are * tracked. */ function createDispatchAndWaitForValid(dispatch, actionPromises) { return (action) => { return new Promise((resolve, reject) => { actionPromises.add({ resolve, reject }); dispatch(action); }); }; } /** * Return an error if the current state contains one. * * There are two types of things we consider an error in the state: * * 1. A loading error. This gets set when a network request (GET or POST) fails. * 2. Cart errors. These get set by the endpoint for invalid carts. * * If there is a loading error, the cart may be invalid indefinitely. However, * if the cart has errors, typically the cart is valid and can continue to be * used. */ function getErrorFromState(state) { if (state.loadingError) { return new errors_1.CartActionConnectionError(state.loadingError, state.loadingErrorType); } const errorMessages = state.responseCart.messages?.errors ?? []; if (errorMessages.length > 0) { const firstMessage = errorMessages[0]; return new errors_1.CartActionResponseError(firstMessage.message, firstMessage.code); } return undefined; } function createShoppingCartManager(cartKey, getCart, setCart) { let state = (0, shopping_cart_reducer_1.getInitialShoppingCartState)(); const subscriptionManager = (0, managers_1.createSubscriptionManager)(cartKey); const syncManager = (0, sync_1.createCartSyncManager)(cartKey, getCart, setCart); const actionPromises = (0, managers_1.createActionPromisesManager)(); const takeActionsBasedOnState = (0, state_based_actions_1.createTakeActionsBasedOnState)(syncManager); const dispatch = (action) => { debug(`dispatching action for cartKey ${cartKey}`, action.type); const newState = (0, shopping_cart_reducer_1.reducerWithQueue)(state, action); const isStateChanged = newState !== state; state = newState; if (state.cacheStatus === 'error') { debug('cache status is error, so rejecting action promises'); actionPromises.reject(new errors_1.CartActionConnectionError(state.loadingError, state.loadingErrorType)); } if (!(0, shopping_cart_reducer_1.isStatePendingUpdateOrQueuedAction)(state)) { // action promises are resolved even if state hasn't changed so that noop // actions resolve immediately. However, if the state did not change and // there are errors, they have already been reported and we don't want to // reject action promises a second time. const error = getErrorFromState(state); if (error && isStateChanged) { debug('no state updates pending and there is an error, so rejecting action promises'); actionPromises.reject(error); } else { debug('no state updates pending and there are no new errors, so resolving action promises'); actionPromises.resolve(state.responseCart); } } if (isStateChanged) { takeActionsBasedOnState(state, dispatch); subscriptionManager.notifySubscribers(); } }; // `dispatchAndWaitForValid` enhances the action dispatcher to return a // Promise that will resolve when the cart next reaches a `valid` // CacheStatus. This is the dispatcher used for all actions in the // ShoppingCartManager's public API. const dispatchAndWaitForValid = createDispatchAndWaitForValid(dispatch, actionPromises); const actions = (0, shopping_cart_actions_1.createActions)(dispatchAndWaitForValid); let cachedManagerState = (0, managers_1.getShoppingCartManagerState)(state); let lastState = state; const getCachedManagerState = () => { if (lastState !== state) { cachedManagerState = (0, managers_1.getShoppingCartManagerState)(state); lastState = state; } return cachedManagerState; }; let didInitialFetch = false; const initialFetch = () => { // Only perform initialFetch once. If it already ran, we return immediately // with the result. if (didInitialFetch) { const error = getErrorFromState(state); if (error) { debug('initial fetch called but it already ran and the current state has an error'); return Promise.reject(error); } debug('initial fetch called but it already ran; returning without doing anything'); return Promise.resolve(state.lastValidResponseCart); } didInitialFetch = true; // takeActionsBasedOnState on a fresh cart will triger an initial fetch. debug('initial fetch called and has not been called before'); takeActionsBasedOnState(state, dispatch); return new Promise((resolve, reject) => { actionPromises.add({ resolve, reject }); }); }; return { subscribe: subscriptionManager.subscribe, actions, getState: getCachedManagerState, fetchInitialCart: initialFetch, }; } function createShoppingCartManagerClient({ getCart, setCart, }) { const managersByCartKey = new Map(); function forCartKey(cartKey) { if (!cartKey) { return managers_1.noopManager; } let manager = managersByCartKey.get(cartKey); if (typeof manager === 'undefined') { debug(`creating cart manager for "${cartKey}"`); manager = createShoppingCartManager(cartKey, getCart, setCart); managersByCartKey.set(cartKey, manager); } return manager; } return { forCartKey, getCartKeyForSiteSlug: (siteSlug) => (0, cart_functions_1.findCartKeyFromSiteSlug)(siteSlug, getCart), }; } exports.createShoppingCartManagerClient = createShoppingCartManagerClient; //# sourceMappingURL=shopping-cart-manager.js.map