diff --git a/lib/reducers/dm-operations-queue-reducer.js b/lib/reducers/dm-operations-queue-reducer.js --- a/lib/reducers/dm-operations-queue-reducer.js +++ b/lib/reducers/dm-operations-queue-reducer.js @@ -1,6 +1,9 @@ // @flow +import _mapValues from 'lodash/fp/mapValues.js'; + import { + pruneDMOpsQueueActionType, type QueuedDMOperations, queueDMOpsActionType, } from '../types/dm-ops.js'; @@ -19,6 +22,13 @@ [threadID]: [...(store.operations[threadID] ?? []), operation], }, }; + } else if (action.type === pruneDMOpsQueueActionType) { + return { + ...store, + operations: _mapValues(operations => + operations.filter(op => op.time <= action.payload.pruneMaxTime), + )(store.operations), + }; } return store; } diff --git a/lib/shared/dm-ops/dm-ops-queue-pruner.react.js b/lib/shared/dm-ops/dm-ops-queue-pruner.react.js new file mode 100644 --- /dev/null +++ b/lib/shared/dm-ops/dm-ops-queue-pruner.react.js @@ -0,0 +1,29 @@ +// @flow +import * as React from 'react'; + +import { pruneDMOpsQueueActionType } from '../../types/dm-ops.js'; +import { useDispatch } from '../../utils/redux-utils.js'; + +const PRUNING_FREQUENCY = 6 * 60 * 60 * 1000; +const MAX_AGE_PRUNED = 3 * 24 * 60 * 60 * 1000; + +function DmOpsQueuePruner(): React.Node { + const dispatch = useDispatch(); + React.useEffect(() => { + const intervalID = setInterval(() => { + const now = Date.now(); + dispatch({ + type: pruneDMOpsQueueActionType, + payload: { + pruneMaxTime: now - MAX_AGE_PRUNED, + }, + }); + }, PRUNING_FREQUENCY); + + return () => clearInterval(intervalID); + }, [dispatch]); + + return null; +} + +export { DmOpsQueuePruner }; diff --git a/lib/tunnelbroker/tunnelbroker-context.js b/lib/tunnelbroker/tunnelbroker-context.js --- a/lib/tunnelbroker/tunnelbroker-context.js +++ b/lib/tunnelbroker/tunnelbroker-context.js @@ -9,6 +9,7 @@ import { PeerToPeerMessageHandler } from './peer-to-peer-message-handler.js'; import type { SecondaryTunnelbrokerConnection } from './secondary-tunnelbroker-connection.js'; import { tunnnelbrokerURL } from '../facts/tunnelbroker.js'; +import { DmOpsQueuePruner } from '../shared/dm-ops/dm-ops-queue-pruner.react.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { tunnelbrokerHeartbeatTimeout } from '../shared/timeouts.js'; import { isWebPlatform } from '../types/device-types.js'; @@ -461,6 +462,7 @@ socketSend={socketSend} /> {children} + ); } diff --git a/lib/types/dm-ops.js b/lib/types/dm-ops.js --- a/lib/types/dm-ops.js +++ b/lib/types/dm-ops.js @@ -303,6 +303,11 @@ +threadID: string, }; +export const pruneDMOpsQueueActionType = 'PRUNE_DM_OPS_QUEUE'; +export type PruneDMOpsQueuePayload = { + +pruneMaxTime: number, +}; + export const scheduleP2PMessagesActionType = 'SCHEDULE_P2P_MESSAGES'; export type ScheduleP2PMessagesPayload = { +dmOpID: string, diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js --- a/lib/types/redux-types.js +++ b/lib/types/redux-types.js @@ -45,6 +45,7 @@ ProcessDMOpsPayload, QueuedDMOperations, QueueDMOpsPayload, + PruneDMOpsQueuePayload, } from './dm-ops.js'; import type { DraftStore } from './draft-types.js'; import type { EnabledApps, SupportedApps } from './enabled-apps.js'; @@ -1574,7 +1575,8 @@ +payload: ProcessDMOpsPayload, } | { +type: 'SCHEDULE_P2P_MESSAGES', +payload: ScheduleP2PMessagesPayload } - | { +type: 'QUEUE_DM_OPS', +payload: QueueDMOpsPayload }, + | { +type: 'QUEUE_DM_OPS', +payload: QueueDMOpsPayload } + | { +type: 'PRUNE_DM_OPS_QUEUE', +payload: PruneDMOpsQueuePayload }, }>; export type ActionPayload = ?(Object | Array<*> | $ReadOnlyArray<*> | string);