Skip to content

Commit de429df

Browse files
committed
Add caching of events to event manager
1 parent c863b57 commit de429df

File tree

2 files changed

+83
-9
lines changed

2 files changed

+83
-9
lines changed

packages-exp/auth-exp/src/core/auth/auth_event_manager.test.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,14 @@ import * as sinon from 'sinon';
2020
import * as sinonChai from 'sinon-chai';
2121

2222
import {
23-
AuthEvent,
24-
AuthEventConsumer,
25-
AuthEventError,
26-
AuthEventType
23+
AuthEvent, AuthEventConsumer, AuthEventError, AuthEventType
2724
} from '../../model/popup_redirect';
2825
import { AuthErrorCode } from '../errors';
2926
import { AuthEventManager } from './auth_event_manager';
3027

3128
use(sinonChai);
3229

33-
describe('src/core/auth/auth_event_manager', () => {
30+
describe.only('src/core/auth/auth_event_manager', () => {
3431
let manager: AuthEventManager;
3532

3633
function makeConsumer(
@@ -191,4 +188,49 @@ describe('src/core/auth/auth_event_manager', () => {
191188
expect(consumer.onAuthEvent).to.have.been.calledWith(event);
192189
});
193190
});
191+
192+
context('caching', () => {
193+
let clock: sinon.SinonFakeTimers;
194+
195+
beforeEach(() => {
196+
clock = sinon.useFakeTimers();
197+
});
198+
199+
it('only runs the event once for the consumer', () => {
200+
const consumer = makeConsumer(AuthEventType.LINK_VIA_POPUP);
201+
202+
const evt = makeEvent(AuthEventType.LINK_VIA_POPUP);
203+
manager.registerConsumer(consumer);
204+
manager.onEvent(evt);
205+
manager.onEvent(evt);
206+
207+
expect(consumer.onAuthEvent).to.have.been.calledOnce;
208+
});
209+
210+
it('clears the cache after ten minutes', () => {
211+
const consumer = makeConsumer(AuthEventType.LINK_VIA_POPUP);
212+
213+
const evt = makeEvent(AuthEventType.LINK_VIA_POPUP);
214+
manager.registerConsumer(consumer);
215+
manager.onEvent(evt);
216+
clock.tick(11 * 60 * 1000);
217+
manager.onEvent(evt);
218+
219+
expect(consumer.onAuthEvent).to.have.been.calledTwice;
220+
});
221+
222+
it('also caches stored redirects', () => {
223+
const consumer = makeConsumer([
224+
AuthEventType.SIGN_IN_VIA_REDIRECT,
225+
AuthEventType.LINK_VIA_REDIRECT,
226+
AuthEventType.REAUTH_VIA_REDIRECT,
227+
AuthEventType.UNKNOWN
228+
]);
229+
const event = makeEvent(AuthEventType.REAUTH_VIA_REDIRECT);
230+
manager.onEvent(event);
231+
manager.registerConsumer(consumer);
232+
manager.onEvent(event);
233+
expect(consumer.onAuthEvent).to.have.been.calledOnce;
234+
});
235+
});
194236
});

packages-exp/auth-exp/src/core/auth/auth_event_manager.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@
1616
*/
1717

1818
import {
19-
AuthEvent,
20-
AuthEventConsumer,
21-
AuthEventType,
22-
EventManager
19+
AuthEvent, AuthEventConsumer, AuthEventType, EventManager
2320
} from '../../model/popup_redirect';
2421
import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors';
2522

23+
// The amount of time to store the UIDs of seen events; this is
24+
// set to 10 min by default
25+
const EVENT_DUPLICATION_CACHE_DURATION_MS = 10 * 60 * 1000;
26+
2627
export class AuthEventManager implements EventManager {
28+
private readonly cachedEventUids: Set<string> = new Set();
2729
private readonly consumers: Set<AuthEventConsumer> = new Set();
2830
private queuedRedirectEvent: AuthEvent | null = null;
2931
private hasHandledPotentialRedirect = false;
32+
private lastProcessedEventTime = Date.now();
3033

3134
constructor(private readonly appName: string) {}
3235

@@ -38,6 +41,7 @@ export class AuthEventManager implements EventManager {
3841
this.isEventForConsumer(this.queuedRedirectEvent, authEventConsumer)
3942
) {
4043
this.sendToConsumer(this.queuedRedirectEvent, authEventConsumer);
44+
this.saveEventToCache(this.queuedRedirectEvent);
4145
this.queuedRedirectEvent = null;
4246
}
4347
}
@@ -47,11 +51,17 @@ export class AuthEventManager implements EventManager {
4751
}
4852

4953
onEvent(event: AuthEvent): boolean {
54+
// Check if the event has already been handled
55+
if (this.hasEventBeenHandled(event)) {
56+
return false;
57+
}
58+
5059
let handled = false;
5160
this.consumers.forEach(consumer => {
5261
if (this.isEventForConsumer(event, consumer)) {
5362
handled = true;
5463
this.sendToConsumer(event, consumer);
64+
this.saveEventToCache(event);
5565
}
5666
});
5767

@@ -96,6 +106,28 @@ export class AuthEventManager implements EventManager {
96106
(!!event.eventId && event.eventId === consumer.eventId);
97107
return consumer.filter.includes(event.type) && eventIdMatches;
98108
}
109+
110+
private hasEventBeenHandled(event: AuthEvent): boolean {
111+
if (Date.now() - this.lastProcessedEventTime >= EVENT_DUPLICATION_CACHE_DURATION_MS) {
112+
this.cachedEventUids.clear();
113+
}
114+
115+
return this.cachedEventUids.has(eventUid(event));
116+
}
117+
118+
private saveEventToCache(event: AuthEvent): void {
119+
this.cachedEventUids.add(eventUid(event));
120+
this.lastProcessedEventTime = Date.now();
121+
}
122+
}
123+
124+
function eventUid(e: AuthEvent): string {
125+
return [
126+
e.type,
127+
e.eventId,
128+
e.sessionId,
129+
e.tenantId
130+
].filter(v => v).join('-');
99131
}
100132

101133
function isNullRedirectEvent({ type, error }: AuthEvent): boolean {

0 commit comments

Comments
 (0)