@automattic/shopping-cart
Version:
A library to use the WordPress.com shopping cart.
153 lines • 6.92 kB
JavaScript
;
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