diff --git a/src/DecryptionFailureTracker.js b/src/DecryptionFailureTracker.js index 9eadb332a8..7bdfd6bfd0 100644 --- a/src/DecryptionFailureTracker.js +++ b/src/DecryptionFailureTracker.js @@ -21,6 +21,10 @@ class DecryptionFailure { } } +function eventIdHash(eventId) { + return crypto.subtle.digest('SHA-256', eventId); +} + export default class DecryptionFailureTracker { // Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list // is checked for failures that happened > `GRACE_PERIOD_MS` ago. Those that did @@ -31,6 +35,11 @@ export default class DecryptionFailureTracker { // one DecryptionFailure of this FIFO is removed and tracked. failuresToTrack = []; + // Event IDs of failures that were tracked previously + eventTrackedPreviously = { + // [eventIdHash(eventId)]: true + }; + // Spread the load on `Analytics` by sending at most 1 event per // `TRACK_INTERVAL_MS`. static TRACK_INTERVAL_MS = 1000; @@ -112,10 +121,24 @@ export default class DecryptionFailureTracker { // Only track one failure per event const dedupedFailuresMap = failuresGivenGrace.reduce( - (result, failure) => ({...result, [failure.failedEventId]: failure}), + (result, failure) => { + if (!this.eventTrackedPreviously[eventIdHash(failure.failedEventId)]) { + return {...result, [failure.failedEventId]: failure}; + } else { + return result; + } + }, {}, ); - const dedupedFailures = Object.keys(dedupedFailuresMap).map((k) => dedupedFailuresMap[k]); + + const trackedEventIds = Object.keys(dedupedFailuresMap); + + this.eventTrackedPreviously = trackedEventIds.reduce( + (result, eventId) => ({...result, [eventIdHash(eventId)]: true}), + this.eventTrackedPreviously, + ); + + const dedupedFailures = trackedEventIds.map((k) => dedupedFailuresMap[k]); this.failuresToTrack = [...this.failuresToTrack, ...dedupedFailures]; } diff --git a/test/DecryptionFailureTracker-test.js b/test/DecryptionFailureTracker-test.js index 9d3b035bf5..34e2df2b6e 100644 --- a/test/DecryptionFailureTracker-test.js +++ b/test/DecryptionFailureTracker-test.js @@ -125,4 +125,28 @@ describe.only('DecryptionFailureTracker', function() { done(); }); + + it('should not track a failure for an event that was tracked previously', (done) => { + const decryptedEvent = createFailedDecryptionEvent(); + + const failures = []; + const tracker = new DecryptionFailureTracker((failure) => failures.push(failure)); + + // Indicate decryption + tracker.eventDecrypted(decryptedEvent); + + // Pretend "now" is Infinity + tracker.checkFailures(Infinity); + + tracker.trackFailure(); + + // Indicate a second decryption, after having tracked the failure + tracker.eventDecrypted(decryptedEvent); + + tracker.trackFailure(); + + expect(failures.length).toBe(1, 'should only track a single failure per event'); + + done(); + }); });