All files dispatchInterceptor.js

100% Statements 54/54
100% Branches 44/44
100% Functions 9/9
100% Lines 52/52
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 1401x   1x 22x 1x   21x 1x   20x 1x   19x 19x   19x         1x 1x 1x 1x   1x   1x           57x 57x 57x 57x 57x 40x 39x 37x 21x 21x           21x     1x   20x 10x           56x 46x     9x 9x 3x               9x 16x       37x   46x 1x                     10x 1x               1x 57x             57x 57x 33x               28x     5x     57x     1x                    
const typeToInterceptor = new Map();
 
const addInterceptor = (interceptType, interceptor, interceptInThunks = false) => {
  if (typeof interceptType !== "string" || interceptType === "") {
    throw new Error("interceptType must be a non-empty string");
  }
  if (typeToInterceptor.has(interceptType)) {
    throw new Error("A dispatch interceptor of type '" + interceptType + "' is already registered");
  }
  if (typeof interceptor !== "function") {
    throw new Error("interceptor of type '" + interceptType + "' must be a synchronous callback, but got '" + typeof interceptor + "'");
  }
  typeToInterceptor.set(interceptType, {interceptor, interceptInThunks});
  return {
    removeInterceptor: () => {
      typeToInterceptor.delete(interceptType);
    },
  }
};
 
const getInterceptEnhancer = () => next => (...args) => {
  const store = next(...args); // eslint-disable-line callback-return
  const getState = store.getState;
  const reduxDispatch = store.dispatch;
 
  let enhancedDispatch = null;
 
  const handleDispatch = (action, interceptorDispatchArg, ...restArgs) => {
    const {
      noIntercept,
      noInterceptTypes,
      onDispatchHandledCallback,
      isFromThunk,
    } = interceptorDispatchArg;
    const dispatchTimestamp = Date.now();
    const skipTypes = typeof noInterceptTypes === "string" ? new Set([noInterceptTypes]) : new Set(noInterceptTypes);
    let blockedBy = null;
    typeToInterceptor.forEach(({interceptor, interceptInThunks}, interceptType) => {
      if (blockedBy === null) {
        if (!isFromThunk || interceptInThunks) {
          if (!noIntercept && !skipTypes.has(interceptType)) {
            let interceptResult = null;
            interceptResult = interceptor({
              action,
              dispatch: enhancedDispatch,
              getState,
              dispatchTimestamp,
            }, ...restArgs);
            if (typeof interceptResult !== "boolean") {
              // We cannot allow for async interceptors, because redux users
              // expect the dispatch function to work synchronously.
              throw new Error("interceptor of type'" + interceptType + "' returned no boolean");
            }
            if (interceptResult === false) {
              blockedBy = interceptType;
            }
          }
        }
      }
    });
    if (blockedBy === null) {
      if (typeof action === "function") {
        // We must wrap the dispatch given to the thunk, to ensure that we not only pass over the
        // interceptor arg the thunk was called with, but also setting isFromThunk to true
        let newInterceptorDispatchArg = {...interceptorDispatchArg, isFromThunk: true};
        if (interceptorDispatchArg.doNotUseInThunk) {
          newInterceptorDispatchArg = {
            noIntercept: false,
            noInterceptTypes: null,
            onDispatchHandledCallback: null,
            isFromThunk: true,
            doNotUseInThunk: true,
          };
        }
        action((innerAction, ...dispatchArgs) => {
          enhancedDispatch(innerAction, newInterceptorDispatchArg, ...dispatchArgs);
        }, getState, ...restArgs);
      }
      else {
        reduxDispatch(action, ...restArgs);
      }
      if (typeof onDispatchHandledCallback === "function") {
        onDispatchHandledCallback({
          blocked: false,
          blockedBy,
          isFromThunk,
        })
      }
 
      // It's not possible to give another dispatch to promise middleware -> thus dispatch interceptor is not
      // compatible with redux-promise-middleware. We could make it work, by wrapping the promises in the payload,
      // but there could also be payloads with a promise without promise middleware being used.
    }
    else if (typeof onDispatchHandledCallback === "function") {
      onDispatchHandledCallback({
        blocked: true,
        blockedBy,
        isFromThunk,
      })
    }
  };
 
  enhancedDispatch = (action, ...dispatchArgs) => {
    let interceptorDispatchArg = {
      noIntercept: false,
      noInterceptTypes: null,
      onDispatchHandledCallback: null,
      isFromThunk: false,
      doNotUseInThunk: false,
    };
    const newDispatchArgs = [];
    for (let i = 0; i < dispatchArgs.length; ++i) {
      if (typeof dispatchArgs[i] === "object" && (
            typeof dispatchArgs[i].noIntercept === "boolean" ||
            typeof dispatchArgs[i].noInterceptTypes === "string" ||
            Array.isArray(dispatchArgs[i].noInterceptTypes) ||
            typeof dispatchArgs[i].onDispatchHandledCallback === "function" ||
            typeof dispatchArgs[i].isFromThunk === "boolean" ||
            typeof dispatchArgs[i].doNotUseInThunk === "boolean"
          )) {
        interceptorDispatchArg = {...interceptorDispatchArg, ...dispatchArgs[i]};
      }
      else {
        newDispatchArgs.push(dispatchArgs[i]);
      }
    }
    handleDispatch(action, interceptorDispatchArg, ...newDispatchArgs);
  };
 
  return {
    ...store,
    dispatch: enhancedDispatch,
  };
};
 
export {
  getInterceptEnhancer,
  addInterceptor,
};