b7685a151d
## Issue 85 Add additional GA events ## Approach Instead of placing analytic calls all over the GUI code (no separation of concerns), try to do it through a redux middleware instead. ## Changes - Updated GA event and parameter naming after understanding how reporting works. - Removed unused analytics.
233 lines
7 KiB
JavaScript
233 lines
7 KiB
JavaScript
import * as ACTIONS from 'constants/action_types';
|
|
import { persistStore, persistReducer } from 'redux-persist';
|
|
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
|
|
import createCompressor from 'redux-persist-transform-compress';
|
|
import { createFilter, createBlacklistFilter } from 'redux-persist-transform-filter';
|
|
import localForage from 'localforage';
|
|
import { createStore, applyMiddleware, compose } from 'redux';
|
|
import thunk from 'redux-thunk';
|
|
import { createMemoryHistory, createBrowserHistory } from 'history';
|
|
import { routerMiddleware } from 'connected-react-router';
|
|
import createRootReducer from './reducers';
|
|
import Lbry from 'lbry';
|
|
import { createAnalyticsMiddleware } from 'redux/middleware/analytics';
|
|
import { buildSharedStateMiddleware } from 'redux/middleware/shared-state';
|
|
import { doSyncLoop } from 'redux/actions/sync';
|
|
import { getAuthToken } from 'util/saved-passwords';
|
|
import { generateInitialUrl } from 'util/url';
|
|
import { X_LBRY_AUTH_TOKEN } from 'constants/token';
|
|
|
|
function isFunction(object) {
|
|
return typeof object === 'function';
|
|
}
|
|
|
|
function isNotFunction(object) {
|
|
return !isFunction(object);
|
|
}
|
|
|
|
function createBulkThunkMiddleware() {
|
|
return ({ dispatch, getState }) => (next) => (action) => {
|
|
if (action.type === 'BATCH_ACTIONS') {
|
|
action.actions.filter(isFunction).map((actionFn) => actionFn(dispatch, getState));
|
|
}
|
|
return next(action);
|
|
};
|
|
}
|
|
|
|
function enableBatching(reducer) {
|
|
return function batchingReducer(state, action) {
|
|
switch (action.type) {
|
|
case 'BATCH_ACTIONS':
|
|
return action.actions.filter(isNotFunction).reduce(batchingReducer, state);
|
|
default:
|
|
return reducer(state, action);
|
|
}
|
|
};
|
|
}
|
|
|
|
const contentFilter = createFilter('content', ['positions', 'history']);
|
|
const fileInfoFilter = createFilter('fileInfo', [
|
|
'fileListPublishedSort',
|
|
'fileListDownloadedSort',
|
|
'fileListSubscriptionSort',
|
|
]);
|
|
const appFilter = createFilter('app', [
|
|
'hasClickedComment',
|
|
'searchOptionsExpanded',
|
|
'volume',
|
|
'muted',
|
|
'allowAnalytics',
|
|
'welcomeVersion',
|
|
'interestedInYoutubeSync',
|
|
'splashAnimationEnabled',
|
|
'activeChannel',
|
|
]);
|
|
const claimsFilter = createFilter('claims', ['pendingById']);
|
|
// We only need to persist the receiveAddress for the wallet
|
|
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
|
const searchFilter = createFilter('search', ['options']);
|
|
const tagsFilter = createFilter('tags', ['followedTags']);
|
|
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
|
const blockedFilter = createFilter('blocked', ['blockedChannels']);
|
|
const coinSwapsFilter = createFilter('coinSwap', ['coinSwaps']);
|
|
const settingsFilter = createBlacklistFilter('settings', ['loadedLanguages', 'language']);
|
|
const collectionsFilter = createFilter('collections', ['builtin', 'saved', 'unpublished', 'edited', 'pending']);
|
|
const whiteListedReducers = [
|
|
'claims',
|
|
'fileInfo',
|
|
'publish',
|
|
'wallet',
|
|
'tags',
|
|
'content',
|
|
'app',
|
|
'search',
|
|
'blocked',
|
|
'coinSwap',
|
|
'settings',
|
|
'subscriptions',
|
|
'collections',
|
|
];
|
|
|
|
const transforms = [
|
|
claimsFilter,
|
|
fileInfoFilter,
|
|
walletFilter,
|
|
blockedFilter,
|
|
coinSwapsFilter,
|
|
tagsFilter,
|
|
appFilter,
|
|
searchFilter,
|
|
tagsFilter,
|
|
contentFilter,
|
|
subscriptionsFilter,
|
|
settingsFilter,
|
|
collectionsFilter,
|
|
createCompressor(),
|
|
];
|
|
|
|
const persistOptions = {
|
|
key: 'v0',
|
|
storage: localForage,
|
|
stateReconciler: autoMergeLevel2,
|
|
whitelist: whiteListedReducers,
|
|
// Order is important. Needs to be compressed last or other transforms can't
|
|
// read the data
|
|
transforms,
|
|
};
|
|
|
|
let history;
|
|
// @if TARGET='app'
|
|
history = createMemoryHistory({
|
|
initialEntries: [generateInitialUrl(window.location.hash)],
|
|
initialIndex: 0,
|
|
});
|
|
// @endif
|
|
// @if TARGET='web'
|
|
history = createBrowserHistory();
|
|
// @endif
|
|
|
|
const triggerSharedStateActions = [
|
|
ACTIONS.CHANNEL_SUBSCRIBE,
|
|
ACTIONS.CHANNEL_UNSUBSCRIBE,
|
|
ACTIONS.TOGGLE_BLOCK_CHANNEL,
|
|
ACTIONS.ADD_COIN_SWAP,
|
|
ACTIONS.REMOVE_COIN_SWAP,
|
|
ACTIONS.TOGGLE_TAG_FOLLOW,
|
|
ACTIONS.CREATE_CHANNEL_COMPLETED,
|
|
ACTIONS.SYNC_CLIENT_SETTINGS,
|
|
// Disabled until we can overwrite preferences
|
|
ACTIONS.SHARED_PREFERENCE_SET,
|
|
ACTIONS.COLLECTION_EDIT,
|
|
ACTIONS.COLLECTION_DELETE,
|
|
ACTIONS.COLLECTION_NEW,
|
|
ACTIONS.COLLECTION_PENDING,
|
|
// MAYBE COLLECTOIN SAVE
|
|
// ACTIONS.SET_WELCOME_VERSION,
|
|
// ACTIONS.SET_ALLOW_ANALYTICS,
|
|
];
|
|
|
|
/**
|
|
* source: the reducer name
|
|
* property: the property in the reducer-specific state
|
|
* transform: optional method to modify the value to be stored
|
|
*
|
|
* See https://github.com/lbryio/lbry-redux/blob/master/src/redux/middleware/shared-state.js for the source
|
|
* This is based off v0.1
|
|
* If lbry-redux changes to another version, this code will need to be changed when upgrading
|
|
*/
|
|
const sharedStateFilters = {
|
|
tags: { source: 'tags', property: 'followedTags' },
|
|
subscriptions: {
|
|
source: 'subscriptions',
|
|
property: 'subscriptions',
|
|
transform: (value) => {
|
|
return value.map(({ uri }) => uri);
|
|
},
|
|
},
|
|
following: {
|
|
source: 'subscriptions',
|
|
property: 'following',
|
|
},
|
|
blocked: { source: 'blocked', property: 'blockedChannels' },
|
|
coin_swap_codes: {
|
|
source: 'coinSwap',
|
|
property: 'coinSwaps',
|
|
transform: (coinSwaps) => {
|
|
return coinSwaps.map((coinSwapInfo) => coinSwapInfo.chargeCode);
|
|
},
|
|
},
|
|
settings: { source: 'settings', property: 'sharedPreferences' },
|
|
app_welcome_version: { source: 'app', property: 'welcomeVersion' },
|
|
sharing_3P: { source: 'app', property: 'allowAnalytics' },
|
|
builtinCollections: { source: 'collections', property: 'builtin' },
|
|
editedCollections: { source: 'collections', property: 'edited' },
|
|
// savedCollections: { source: 'collections', property: 'saved' },
|
|
unpublishedCollections: { source: 'collections', property: 'unpublished' },
|
|
};
|
|
|
|
const sharedStateCb = ({ dispatch, getState }) => {
|
|
dispatch(doSyncLoop());
|
|
};
|
|
|
|
const populateAuthTokenHeader = () => {
|
|
return (next) => (action) => {
|
|
if (
|
|
(action.type === ACTIONS.USER_FETCH_SUCCESS || action.type === ACTIONS.AUTHENTICATION_SUCCESS) &&
|
|
action.data.user.has_verified_email === true
|
|
) {
|
|
const authToken = getAuthToken();
|
|
Lbry.setApiHeader(X_LBRY_AUTH_TOKEN, authToken);
|
|
}
|
|
|
|
return next(action);
|
|
};
|
|
};
|
|
|
|
const sharedStateMiddleware = buildSharedStateMiddleware(triggerSharedStateActions, sharedStateFilters, sharedStateCb);
|
|
const rootReducer = createRootReducer(history);
|
|
const persistedReducer = persistReducer(persistOptions, rootReducer);
|
|
const bulkThunk = createBulkThunkMiddleware();
|
|
const analyticsMiddleware = createAnalyticsMiddleware();
|
|
|
|
const middleware = [
|
|
sharedStateMiddleware,
|
|
// @if TARGET='web'
|
|
populateAuthTokenHeader,
|
|
// @endif
|
|
routerMiddleware(history),
|
|
thunk,
|
|
bulkThunk,
|
|
analyticsMiddleware,
|
|
];
|
|
|
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
|
const store = createStore(
|
|
enableBatching(persistedReducer),
|
|
{}, // initial state
|
|
composeEnhancers(applyMiddleware(...middleware))
|
|
);
|
|
|
|
const persistor = persistStore(store);
|
|
window.persistor = persistor;
|
|
|
|
export { store, persistor, history, whiteListedReducers };
|