"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getSessionBundle = exports.sessionResponseSnippet = void 0;

var _reduxBundler = require("redux-bundler");

var _toGqlQuery = require("../utils/to-gql-query");

var _generateBundle = require("../utils/generate-bundle");

var _object = require("../utils/object");

function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }

function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

const IS_CYPRESS = !!(typeof window !== 'undefined' && window.Cypress);
const STARTED = 'SESSION_FETCH_STARTED';
const FAILED = 'SESSION_FETCH_FAILED';
const SESSION_RECEIVED = 'SESSION_RECEIVED';
const sessionResponseSnippet = (0, _toGqlQuery.toGqlQuery)(['multiFactorLast', 'multiFactorAuthExp', 'sessionRenewedAt', 'sessionExpiresAt', 'token', 'orgs{id roles status type memberId}']);
/**
 * @typedef GetSessionBundleOptions
 * @property {number} [renewSessionWhenOlderThan=180000] Time in milliseconds
 *   after which to renew the token. Defaults to three minutes. Default is `180000`
 * @property {number} [retryFailedFetchAfter=30000] Time in milliseconds after
 *   which to try again if we failed to fetchDefault is 30 seconds `30000`.
 *   Default is `30000`
 * @property {string[]} selectorsThatDetermineActivity If any of these return a
 *   truthy value, they will keep the session renewing.
 * @property {string} activeOrgIdSelectorName
 * @property {boolean} [persist=false] Default is `false`
 */

/**
 * @param {GetSessionBundleOptions} [opts]
 * @returns {ReduxBundlerBundle}
 */

exports.sessionResponseSnippet = sessionResponseSnippet;

const getSessionBundle = opts => {
  /** @type {GetSessionBundleOptions} */
  const options = _objectSpread({
    renewSessionWhenOlderThan: 180000,
    retryFailedFetchAfter: 30000
  }, opts);

  const getRoleSelector = role => (0, _reduxBundler.createSelector)('selectActiveOrgRoles', roles => roles.includes(role));

  return (0, _generateBundle.generateBundle)({
    name: 'session',
    initialState: {
      token: '',
      loading: false,
      data: null,
      lastExtendError: null
    },
    actionTypes: {
      [STARTED]: {
        handler: state => _objectSpread(_objectSpread({}, state), {}, {
          loading: true
        })
      },
      [FAILED]: {
        handler: (state, payload) => {
          const {
            time
          } = payload;
          return _objectSpread(_objectSpread({}, state), {}, {
            loading: false,
            lastExtendError: time
          });
        }
      },
      [SESSION_RECEIVED]: {
        handler: (state, payload) => {
          const {
            token,
            data
          } = payload;
          return _objectSpread(_objectSpread({}, state), {}, {
            loading: false,
            data,
            token,
            lastExtendError: null
          });
        },
        persist: !!options.persist
      }
    },
    selectSessionRaw: state => state.session,
    selectSessionData: state => state.session.data,
    selectSessionDataIsLoading: state => state.session.loading,
    selectSessionLastExtendError: state => state.session.lastExtendError,
    selectSessionTimes: (0, _reduxBundler.createSelector)('selectSessionData', sessionData => {
      const keys = ['multiFactorAuthExp', 'multiFactorLast', 'sessionExpiresAt', 'sessionRenewedAt'];
      const data = sessionData || {};
      const result = {};

      for (const key of keys) {
        const asNumber = Number(data[key]);
        result[key] = isNaN(asNumber) ? null : asNumber;
      }

      return result;
    }),
    selectSessionHasValidMFA: (0, _reduxBundler.createSelector)('selectSessionTimes', 'selectAppTime', ({
      multiFactorAuthExp,
      multiFactorLast
    }, appTime) => !!multiFactorLast && multiFactorAuthExp > appTime),
    selectHasValidSession: (0, _reduxBundler.createSelector)('selectSessionTimes', 'selectAppTime', ({
      sessionExpiresAt,
      sessionRenewedAt
    }, appTime) => !!sessionRenewedAt && sessionExpiresAt > appTime),
    selectIsSignedIn: (0, _reduxBundler.createSelector)('selectHasAuthToken', 'selectHasValidSession', (authToken, hasValidSession) => !!(authToken && hasValidSession)),
    selectSessionOrgs: (0, _reduxBundler.createSelector)('selectSessionData', data => data && data.orgs || null),
    selectSessionRegularOrgIds: (0, _reduxBundler.createSelector)('selectSessionOrgs', orgs => (orgs || []).filter(org => !org.type).map(org => org.id)),
    selectSessionOrgIds: (0, _reduxBundler.createSelector)('selectSessionOrgs', orgs => orgs && orgs.map(item => item.id) || []),
    selectActiveOrgMembership: (0, _reduxBundler.createSelector)('selectSessionOrgs', opts.activeOrgIdSelectorName, (orgs, activeOrgId) => orgs && orgs.find(org => org.id === activeOrgId) || null),
    selectActiveOrgMemberId: (0, _reduxBundler.createSelector)('selectActiveOrgMembership', membership => membership && membership.memberId),
    selectActiveOrgType: (0, _reduxBundler.createSelector)('selectActiveOrgMembership', membership => membership ? membership.type || 'regular' : null),
    selectActiveOrgIsXchart: (0, _reduxBundler.createSelector)('selectActiveOrgType', type => type === 'xchart'),
    selectActiveOrgIsParent: (0, _reduxBundler.createSelector)('selectActiveOrgType', type => type === 'parent'),
    selectActiveOrgIsRegular: (0, _reduxBundler.createSelector)('selectActiveOrgType', type => type === 'regular'),
    selectActiveOrgRoles: (0, _reduxBundler.createSelector)('selectActiveOrgMembership', membership => membership && membership.roles || []),
    selectIsOrgUber: getRoleSelector('uber'),
    selectIsOrgAdmin: getRoleSelector('admin'),
    selectIsOrgProvider: getRoleSelector('provider'),
    selectParentOrgMemberships: (0, _reduxBundler.createSelector)('selectSessionOrgs', orgs => (0, _object.nullIfEmpty)(orgs && orgs.filter(org => org.type === 'parent'))),
    selectXchartOrgRoles: (0, _reduxBundler.createSelector)('selectSessionOrgs', orgs => {
      const membership = orgs && orgs.find(org => org.id === 'xchart' && org.type === 'xchart');
      return membership && membership.roles || null;
    }),
    selectIsXchartOrgMember: (0, _reduxBundler.createSelector)('selectXchartOrgRoles', roles => !!roles),
    selectIsXchartUberUser: (0, _reduxBundler.createSelector)('selectXchartOrgRoles', roles => !!(roles && roles.includes('uber'))),
    selectAuthStatus: (0, _reduxBundler.createSelector)('selectHasAuthToken', 'selectSessionData', 'selectSessionHasValidMFA', 'selectHasValidSession', (hasToken, sessionData, hasMFA, hasValidSession) => {
      // this means we're still figuring it out
      if (hasToken && !sessionData) {
        return null;
      }

      if (!hasToken) {
        return 'none';
      }

      if (hasMFA && hasValidSession) {
        return 'full';
      }

      if (hasValidSession) {
        return 'partial';
      }

      return 'expired';
    }),
    selectIsDeterminingAuthStatus: (0, _reduxBundler.createSelector)('selectAuthStatus', status => !status),
    selectIsFullyAuthenticated: (0, _reduxBundler.createSelector)('selectAuthStatus', status => status === 'full'),
    selectNotFullyAuthenticated: (0, _reduxBundler.createSelector)('selectIsDeterminingAuthStatus', 'selectIsFullyAuthenticated', (isDetermining, fullyAuthenticated) => !isDetermining && !fullyAuthenticated),
    doFetchSession: (update = false) => ({
      gqlFetch,
      dispatch,
      store
    }) => {
      dispatch({
        type: STARTED
      });
      let fetchPromise;

      if (update) {
        fetchPromise = gqlFetch('mutation extendSession($testOnly_overrideTime:String){' + "extendSession(testOnly_overrideTime:$testOnly_overrideTime)".concat(sessionResponseSnippet, "}"), {
          testOnly_overrideTime: IS_CYPRESS ? Date.now().toString() : null
        }).then(res => res.data.extendSession);
      } else {
        fetchPromise = gqlFetch("{session".concat(sessionResponseSnippet, "}")).then(res => res.data.session);
      }

      return fetchPromise.then(res => {
        store.doReceiveSession(res);
      }).catch(() => {
        dispatch({
          type: FAILED,
          payload: {
            time: Date.now()
          }
        });
      });
    },
    doReceiveSession: updatedSession => ({
      store,
      dispatch
    }) => {
      const {
        token
      } = updatedSession,
            rest = _objectWithoutProperties(updatedSession, ["token"]);

      if (token !== store.selectAuthToken()) {
        store.doReceiveToken(token);
      } // this keeps the in alphabetical order then we don't have
      // to sort it on reads.


      if (rest.orgs && rest.orgs.length) {
        rest.orgs.sort((a, b) => a.id.localeCompare(b.id));
      }

      dispatch({
        type: SESSION_RECEIVED,
        payload: {
          token,
          data: rest
        }
      });
    },
    selectSessionShouldRetryFailed: (0, _reduxBundler.createSelector)('selectSessionLastExtendError', 'selectAppTime', (lastExtendError, appTime) => lastExtendError && appTime - lastExtendError > options.retryFailedFetchAfter),
    reactShouldExtendSession: (0, _reduxBundler.createSelector)('selectIsSignedIn', 'selectSessionData', 'selectSessionRaw', 'selectSessionShouldRetryFailed', 'selectAppTime', ...options.selectorsThatDetermineActivity, (signedIn, data, raw, shouldRetryFailed, appTime, ...otherValues) => {
      const {
        sessionRenewedAt
      } = data || {};
      const {
        loading
      } = raw;
      const active = otherValues.some(Boolean);

      if (!signedIn || !active || !sessionRenewedAt || loading) {
        return false;
      }

      const sessionAge = appTime - Number(sessionRenewedAt);

      if (shouldRetryFailed || sessionAge > options.renewSessionWhenOlderThan) {
        return {
          actionCreator: 'doFetchSession',
          args: [true]
        };
      }
    }),
    reactShouldFetchSession: (0, _reduxBundler.createSelector)('selectShowUserLoading', 'selectSessionRaw', 'selectAuthToken', (userLoading, raw, authToken) => {
      if (userLoading || raw.loading || !authToken) {
        return;
      }

      if (raw.token !== authToken) {
        return {
          actionCreator: 'doFetchSession'
        };
      }
    })
  });
};

exports.getSessionBundle = getSessionBundle;