diff --git a/ts/state/createStore.ts b/ts/state/createStore.ts index 3780f1584913..63608e9dac1b 100644 --- a/ts/state/createStore.ts +++ b/ts/state/createStore.ts @@ -3,16 +3,19 @@ /* eslint-disable no-console */ -import type { Store } from 'redux'; +import type { Middleware, Store } from 'redux'; import { applyMiddleware, createStore as reduxCreateStore } from 'redux'; import promise from 'redux-promise-middleware'; import thunk from 'redux-thunk'; import { createLogger } from 'redux-logger'; +import * as log from '../logging/log'; import type { StateType } from './reducer'; import { reducer } from './reducer'; import { dispatchItemsMiddleware } from '../shims/dispatchItemsMiddleware'; +import { isOlderThan } from '../util/timestamp'; +import { SECOND } from '../util/durations'; declare global { // We want to extend `window`'s properties, so we need an interface. @@ -47,10 +50,49 @@ const logger = createLogger({ }, }); +const ACTION_COUNT_THRESHOLD = 25; +type ActionStats = { + timestamp: number; + names: Array; +}; +const actionStats: ActionStats = { + timestamp: Date.now(), + names: [], +}; +export const actionRateLogger: Middleware = () => next => action => { + const name = action.type; + const lastTimestamp = actionStats.timestamp; + let count = actionStats.names.length; + + if (isOlderThan(lastTimestamp, SECOND)) { + if (count > 0) { + actionStats.names = []; + } + actionStats.timestamp = Date.now(); + + return next(action); + } + + actionStats.names.push(name); + count += 1; + + if (count >= ACTION_COUNT_THRESHOLD) { + log.warn( + `ActionRateLogger: got ${count} events since ${lastTimestamp}: ${actionStats.names.join(',')}` + ); + + actionStats.names = []; + actionStats.timestamp = Date.now(); + } + + return next(action); +}; + const middlewareList = [ promise, thunk, dispatchItemsMiddleware, + actionRateLogger, ...(env === 'production' ? [] : [logger]), ]; diff --git a/ts/state/getInitialState.ts b/ts/state/getInitialState.ts index 0d3189392dca..b50ab14917eb 100644 --- a/ts/state/getInitialState.ts +++ b/ts/state/getInitialState.ts @@ -12,11 +12,10 @@ import { getEmptyState as composerEmptyState } from './ducks/composer'; import { getEmptyState as conversationsEmptyState } from './ducks/conversations'; import { getEmptyState as crashReportsEmptyState } from './ducks/crashReports'; import { getEmptyState as emojiEmptyState } from './ducks/emojis'; -import { getEmptyState as itemsEmptyState } from './ducks/items'; -import { getEmptyState as stickersEmptyState } from './ducks/stickers'; import { getEmptyState as expirationEmptyState } from './ducks/expiration'; import { getEmptyState as globalModalsEmptyState } from './ducks/globalModals'; import { getEmptyState as inboxEmptyState } from './ducks/inbox'; +import { getEmptyState as itemsEmptyState } from './ducks/items'; import { getEmptyState as lightboxEmptyState } from './ducks/lightbox'; import { getEmptyState as linkPreviewsEmptyState } from './ducks/linkPreviews'; import { getEmptyState as mediaGalleryEmptyState } from './ducks/mediaGallery'; @@ -25,6 +24,7 @@ import { getEmptyState as networkEmptyState } from './ducks/network'; import { getEmptyState as preferredReactionsEmptyState } from './ducks/preferredReactions'; import { getEmptyState as safetyNumberEmptyState } from './ducks/safetyNumber'; import { getEmptyState as searchEmptyState } from './ducks/search'; +import { getEmptyState as stickersEmptyState } from './ducks/stickers'; import { getEmptyState as storiesEmptyState } from './ducks/stories'; import { getEmptyState as storyDistributionListsEmptyState } from './ducks/storyDistributionLists'; import { getEmptyState as toastEmptyState } from './ducks/toast';