diff --git a/src/DecryptionFailureTracker.js b/src/DecryptionFailureTracker.js index 7bdfd6bfd0..2f3d286d55 100644 --- a/src/DecryptionFailureTracker.js +++ b/src/DecryptionFailureTracker.js @@ -36,7 +36,7 @@ export default class DecryptionFailureTracker { failuresToTrack = []; // Event IDs of failures that were tracked previously - eventTrackedPreviously = { + trackedEventHashMap = { // [eventIdHash(eventId)]: true }; @@ -59,6 +59,14 @@ export default class DecryptionFailureTracker { this.trackDecryptionFailure = fn; } + loadTrackedEventHashMap() { + this.trackedEventHashMap = JSON.parse(localStorage.getItem('mx-decryption-failure-event-id-hashes')); + } + + saveTrackedEventHashMap() { + localStorage.setItem('mx-decryption-failure-event-id-hashes', JSON.stringify(this.trackedEventHashMap)); + } + eventDecrypted(e) { if (e.isDecryptionFailure()) { this.addDecryptionFailureForEvent(e); @@ -122,7 +130,7 @@ export default class DecryptionFailureTracker { // Only track one failure per event const dedupedFailuresMap = failuresGivenGrace.reduce( (result, failure) => { - if (!this.eventTrackedPreviously[eventIdHash(failure.failedEventId)]) { + if (!this.trackedEventHashMap[eventIdHash(failure.failedEventId)]) { return {...result, [failure.failedEventId]: failure}; } else { return result; @@ -133,11 +141,13 @@ export default class DecryptionFailureTracker { const trackedEventIds = Object.keys(dedupedFailuresMap); - this.eventTrackedPreviously = trackedEventIds.reduce( + this.trackedEventHashMap = trackedEventIds.reduce( (result, eventId) => ({...result, [eventIdHash(eventId)]: true}), - this.eventTrackedPreviously, + this.trackedEventHashMap, ); + this.saveTrackedEventHashMap(); + const dedupedFailures = trackedEventIds.map((k) => dedupedFailuresMap[k]); this.failuresToTrack = [...this.failuresToTrack, ...dedupedFailures]; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 2a2fee1a21..19c0c17d1d 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1313,6 +1313,7 @@ export default React.createClass({ // TODO: Pass reason for failure as third argument to trackEvent Analytics.trackEvent('E2E', 'Decryption failure'); }); + dft.loadEventTrackedPreviously(); const stopDft = dft.start(); diff --git a/test/DecryptionFailureTracker-test.js b/test/DecryptionFailureTracker-test.js index 66bc6daf4b..0ea710e5c7 100644 --- a/test/DecryptionFailureTracker-test.js +++ b/test/DecryptionFailureTracker-test.js @@ -150,4 +150,34 @@ describe.only('DecryptionFailureTracker', function() { done(); }); + + it('should not track a failure for an event that was tracked in a previous session', (done) => { + // This test uses localStorage, clear it beforehand + localStorage.clear(); + + const decryptedEvent = createFailedDecryptionEvent(); + + const failures = []; + const tracker = new DecryptionFailureTracker((failure) => failures.push(failure)); + + // Indicate decryption + tracker.eventDecrypted(decryptedEvent); + + // Pretend "now" is Infinity + // NB: This saves to localStorage specific to DFT + tracker.checkFailures(Infinity); + + tracker.trackFailure(); + + // Simulate the browser refreshing by destroying tracker and creating a new tracker + const secondTracker = new DecryptionFailureTracker((failure) => failures.push(failure)); + secondTracker.loadTrackedEventHashMap(); + secondTracker.eventDecrypted(decryptedEvent); + secondTracker.checkFailures(Infinity); + secondTracker.trackFailure(); + + expect(failures.length).toBe(1, 'should track a single failure per event per session, got ' + failures.length); + + done(); + }); });