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';
@@ -11,14 +14,24 @@
action: BaseAction,
): QueuedDMOperations {
if (action.type === queueDMOpsActionType) {
- const { threadID, operation } = action.payload;
+ const { threadID, operation, timestamp } = action.payload;
return {
...store,
operations: {
...store.operations,
- [threadID]: [...(store.operations[threadID] ?? []), operation],
+ [threadID]: [
+ ...(store.operations[threadID] ?? []),
+ { operation, timestamp },
+ ],
},
};
+ } else if (action.type === pruneDMOpsQueueActionType) {
+ return {
+ ...store,
+ operations: _mapValues(operations =>
+ operations.filter(op => op.timestamp >= 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,37 @@
+// @flow
+import * as React from 'react';
+
+import { pruneDMOpsQueueActionType } from '../../types/dm-ops.js';
+import { useDispatch } from '../../utils/redux-utils.js';
+
+const PRUNING_FREQUENCY = 60 * 60 * 1000;
+const FIRST_PRUNING_DELAY = 10 * 60 * 1000;
+const MAX_AGE_PRUNED = 3 * 24 * 60 * 60 * 1000;
+
+function DMOpsQueuePruner(): React.Node {
+ const dispatch = useDispatch();
+
+ const prune = React.useCallback(() => {
+ const now = Date.now();
+ dispatch({
+ type: pruneDMOpsQueueActionType,
+ payload: {
+ pruneMaxTime: now - MAX_AGE_PRUNED,
+ },
+ });
+ }, [dispatch]);
+
+ React.useEffect(() => {
+ const timeoutID = setTimeout(prune, FIRST_PRUNING_DELAY);
+ const intervalID = setInterval(prune, PRUNING_FREQUENCY);
+
+ return () => {
+ clearTimeout(timeoutID);
+ clearInterval(intervalID);
+ };
+ }, [prune]);
+
+ 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
@@ -313,6 +313,12 @@
export type QueueDMOpsPayload = {
+operation: DMOperation,
+threadID: string,
+ +timestamp: number,
+};
+
+export const pruneDMOpsQueueActionType = 'PRUNE_DM_OPS_QUEUE';
+export type PruneDMOpsQueuePayload = {
+ +pruneMaxTime: number,
};
export const scheduleP2PMessagesActionType = 'SCHEDULE_P2P_MESSAGES';
@@ -323,6 +329,9 @@
export type QueuedDMOperations = {
+operations: {
- +[threadID: string]: $ReadOnlyArray,
+ +[threadID: string]: $ReadOnlyArray<{
+ +operation: DMOperation,
+ +timestamp: number,
+ }>,
},
};
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';
@@ -1581,7 +1582,8 @@
+deviceToken: string,
},
}
- | { +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);