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);