10000 [FSSDK-9605] add sendBeacon event dispatcher support (#866) · optimizely/javascript-sdk@894398b · GitHub
[go: up one dir, main page]

Skip to content

Commit 894398b

Browse files
authored
[FSSDK-9605] add sendBeacon event dispatcher support (#866)
1 parent f5b4b9a commit 894398b

File tree

11 files changed

+1799
-3371
lines changed

11 files changed

+1799
-3371
lines changed

lib/index.browser.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { LocalStoragePendingEventsDispatcher } from './modules/event_processor';
1919
import configValidator from './utils/config_validator';
2020
import defaultErrorHandler from './plugins/error_handler';
2121
import defaultEventDispatcher from './plugins/event_dispatcher/index.browser';
22+
import sendBeaconEventDispatcher from './plugins/event_dispatcher/send_beacon_dispatcher';
2223
import * as enums from './utils/enums';
2324
import * as loggerPlugin from './plugins/logger';
2425
import eventProcessorConfigValidator from './utils/event_processor_config_validator';
@@ -90,6 +91,12 @@ const createInstance = function(config: Config): Client | null {
9091
eventDispatcher = config.eventDispatcher;
9192
}
9293

94+
let closingDispatcher = config.closingEventDispatcher;
95+
96+
if (!config.eventDispatcher && !closingDispatcher && window.navigator && 'sendBeacon' in window.navigator) {
97+
closingDispatcher = sendBeaconEventDispatcher;
98+
}
99+
93100
let eventBatchSize = config.eventBatchSize;
94101
let eventFlushInterval = config.eventFlushInterval;
95102

@@ -111,6 +118,7 @@ const createInstance = function(config: Config): Client | null {
111118

112119
const eventProcessorConfig = {
113120
dispatcher: eventDispatcher,
121+
closingDispatcher,
114122
flushInterval: eventFlushInterval,
115123
batchSize: eventBatchSize,
116124
maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE,
@@ -172,6 +180,7 @@ export {
172180
loggerPlugin as logging,
173181
defaultErrorHandler as errorHandler,
174182
defaultEventDispatcher as eventDispatcher,
183+
sendBeaconEventDispatcher,
175184
enums,
176185
setLogHandler as setLogger,
177186
setLogLevel,
@@ -189,6 +198,7 @@ export default {
189198
logging: loggerPlugin,
190199
errorHandler: defaultErrorHandler,
191200
eventDispatcher: defaultEventDispatcher,
201+
sendBeaconEventDispatcher,
192202
enums,
193203
setLogger: setLogHandler,
194204
setLogLevel,

lib/modules/event_processor/eventProcessor.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2022, Optimizely
2+
* Copyright 2022-2023 Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -57,13 +57,20 @@ export function validateAndGetBatchSize(batchSize: number): number {
5757
return batchSize
5858
}
5959

60-
export function getQueue(batchSize: number, flushInterval: number, sink: EventQueueSink<ProcessableEvent>, batchComparator: (eventA: ProcessableEvent, eventB: ProcessableEvent) => boolean ): EventQueue<ProcessableEvent> {
60+
export function getQueue(
61+
batchSize: number,
62+
flushInterval: number,
63+
batchComparator: (eventA: ProcessableEvent, eventB: ProcessableEvent) => boolean,
64+
sink: EventQueueSink<ProcessableEvent>,
65+
closingSink?: EventQueueSink<ProcessableEvent>
66+
): EventQueue<ProcessableEvent> {
6167
let queue: EventQueue<ProcessableEvent>
6268
if (batchSize > 1) {
6369
queue = new DefaultEventQueue<ProcessableEvent>({
6470
flushInterval,
6571
maxQueueSize: batchSize,
6672
sink,
73+
closingSink,
6774
batchComparator,
6875
})
6976
} else {
Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2022, Optimizely
2+
* Copyright 2022-2023, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,149 +14,149 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { getLogger } from '../logging'
17+
import { getLogger } from '../logging';
1818
// TODO change this to use Managed from js-sdk-models when available
19-
import { Managed } from './managed'
19+
import { Managed } from './managed';
2020

21-
const logger = getLogger('EventProcessor')
21+
const logger = getLogger('EventProcessor');
2222

23-
export type EventQueueSink<K> = (buffer: K[]) => Promise<any>
23+
export type EventQueueSink<K> = (buffer: K[]) => Promise<any>;
2424

2525
export interface EventQueue<K> extends Managed {
26-
enqueue(event: K): void
26+
enqueue(event: K): void;
2727
}
2828

2929
export interface EventQueueFactory<K> {
30-
createEventQueue(config: {
31-
sink: EventQueueSink<K>
32-
flushInterval: number
33-
maxQueueSize: number
34-
}): EventQueue<K>
30+
createEventQueue(config: { sink: EventQueueSink<K>, flushInterval: number, maxQueueSize: number }): EventQueue<K>;
3531
}
3632

3733
class Timer {
38-
private timeout: number
39-
private callback: () => void
40-
private timeoutId?: number
34+
private timeout: number;
35+
private callback: () => void;
36+
private timeoutId?: number;
4137

4238
constructor({ timeout, callback }: { timeout: number; callback: () => void }) {
43-
this.timeout = Math.max(timeout, 0)
44-
this.callback = callback
39+
this.timeout = Math.max(timeout, 0);
40+
this.callback = callback;
4541
}
4642

4743
start(): void {
48-
this.timeoutId = setTimeout(this.callback, this.timeout) as any
44+
this.timeoutId = setTimeout(this.callback, this.timeout) as any;
4945
}
5046

5147
refresh(): void {
52-
this.stop()
53-
this.start()
48+
this.stop();
49+
this.start();
5450
}
5551

5652
stop(): void {
5753
if (this.timeoutId) {
58-
clearTimeout(this.timeoutId as any)
54+
clearTimeout(this.timeoutId as any);
5955
}
6056
}
6157
}
6258

6359
export class SingleEventQueue<K> implements EventQueue<K> {
64-
private sink: EventQueueSink<K>
60+
private sink: EventQueueSink<K>;
6561

6662
constructor({ sink }: { sink: EventQueueSink<K> }) {
67-
this.sink = sink
63+
this.sink = sink;
6864
}
6965

7066
start(): Promise<any> {
7167
// no-op
72-
return Promise.resolve()
68+
return Promise.resolve();
7369
}
7470

7571
stop(): Promise<any> {
7672
// no-op
77-
return Promise.resolve()
73+
return Promise.resolve();
7874
}
7975

8076
enqueue(event: K): void {
81-
this.sink([event])
77+
this.sink([event]);
8278
}
8379
}
8480

8581
export class DefaultEventQueue<K> implements EventQueue<K> {
8682
// expose for testing
87-
public timer: Timer
88-
private buffer: K[]
89-
private maxQueueSize: number
90-
private sink: EventQueueSink<K>
83+
public timer: Timer;
84+
private buffer: K[];
85+
private maxQueueSize: number;
86+
private sink: EventQueueSink<K>;
87+
private closingSink?: EventQueueSink<K>;
9188
// batchComparator is called to determine whether two events can be included
9289
// together in the same batch
93-
private batchComparator: (eventA: K, eventB: K) => boolean
94-
private started: boolean
90+
private batchComparator: (eventA: K, eventB: K) => boolean;
91+
private started: boolean;
9592

9693
constructor({
9794
flushInterval,
9895
maxQueueSize,
9996
sink,
97+
closingSink,
10098
batchComparator,
10199
}: {
102-
flushInterval: number
103-
maxQueueSize: number
104-
sink: EventQueueSink<K>
105-
batchComparator: (eventA: K, eventB: K) => boolean
100+
flushInterval: number;
101+
maxQueueSize: number;
102+
sink: EventQueueSink<K>;
103+
closingSink?: EventQueueSink<K>;
104+
batchComparator: (eventA: K, eventB: K) => boolean;
106105
}) {
107-
this.buffer = []
108-
this.maxQueueSize = Math.max(maxQueueSize, 1)
109-
this.sink = sink
110-
this.batchComparator = batchComparator
106+
this.buffer = [];
107+
this.maxQueueSize = Math.max(maxQueueSize, 1);
108+
this.sink = sink;
109+
this.closingSink = closingSink;
110+
this.batchComparator = batchComparator;
111111
this.timer = new Timer({
112112
callback: this.flush.bind(this),
113113
timeout: flushInterval,
114-
})
115-
this.started = false
114+
});
115+
this.started = false;
116116
}
117117

118118
start(): Promise<any> {
119-
this.started = true
119+
this.started = true;
120120
// dont start the timer until the first event is enqueued
121121

122122
return Promise.resolve();
123123
}
124124

125125
stop(): Promise<any> {
126-
this.started = false
127-
const result = this.sink(this.buffer)
128-
this.buffer = []
129-
this.timer.stop()
130-
return result
126+
this.started = false;
127+
const result = this.closingSink ? this.closingSink(this.buffer) : this.sink(this.buffer);
128+
this.buffer = [];
129+
this.timer.stop();
130+
return result;
131131
}
132132

133133
enqueue(event: K): void {
134134
if (!this.started) {
135-
logger.warn('Queue is stopped, not accepting event')
136-
return
135+
logger.warn('Queue is stopped, not accepting event');
136+
return;
137137
}
138138

139139
// If new event cannot be included into the current batch, flush so it can
140140
// be in its own new batch.
141-
const bufferedEvent: K | undefined = this.buffer[0]
141+
const bufferedEvent: K | undefined = this.buffer[0];
142142
if (bufferedEvent && !this.batchComparator(bufferedEvent, event)) {
143-
this.flush()
143+
this.flush();
144144
}
145145

146146
// start the timer when the first event is put in
147147
if (this.buffer.length === 0) {
148-
this.timer.refresh()
148+
this.timer.refresh();
149149
}
150-
this.buffer.push(event)
150+
this.buffer.push(event);
151151

152152
if (this.buffer.length >= this.maxQueueSize) {
153-
this.flush()
153+
this.flush();
154154
}
155155
}
156156

157-
flush() : void {
158-
this.sink(this.buffer)
159-
this.buffer = []
160-
this.timer.stop()
157+
flush(): void {
158+
this.sink(this.buffer);
159+
this.buffer = [];
160+
this.timer.stop();
161161
}
162162
}

lib/modules/event_processor/v1/v1EventProcessor.react_native.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2022, Optimizely
2+
* Copyright 2022-2023, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -104,7 +104,7 @@ export class LogTierV1EventProcessor implements EventProcessor {
104104

105105
flushInterval = validateAndGetFlushInterval(flushInterval)
106106
batchSize = validateAndGetBatchSize(batchSize)
107-
this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual)
107+
this.queue = getQueue(batchSize, flushInterval, areEventContextsEqual, this.drainQueue.bind(this))
108108
this.pendingEventsStore = new ReactNativeEventsStore(maxQueueSize, PENDING_EVENTS_STORE_KEY)
109109
this.eventBufferStore = new ReactNativeEventsStore(maxQueueSize, EVENT_BUFFER_STORE_KEY)
110110
}

lib/modules/event_processor/v1/v1EventProcessor.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2022, Optimizely
2+
* Copyright 2022-2023, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,31 +36,41 @@ const logger = getLogger('LogTierV1EventProcessor')
3636

3737
export class LogTierV1EventProcessor implements EventProcessor {
3838
private dispatcher: EventDispatcher
39+
private closingDispatcher?: EventDispatcher
3940
private queue: EventQueue<ProcessableEvent>
4041
private notificationCenter?: NotificationSender
4142
private requestTracker: RequestTracker
4243

4344
constructor({
4445
dispatcher,
46+
closingDispatcher,
4547
flushInterval = DEFAULT_FLUSH_INTERVAL,
4648
batchSize = DEFAULT_BATCH_SIZE,
4749
notificationCenter,
4850
}: {
4951
dispatcher: EventDispatcher
52+
closingDispatcher?: EventDispatcher
5053
flushInterval?: number
5154
batchSize?: number
5255
notificationCenter?: NotificationSender
5356
}) {
5457
this.dispatcher = dispatcher
58+
this.closingDispatcher = closingDispatcher
5559
this.notificationCenter = notificationCenter
5660
this.requestTracker = new RequestTracker()
5761

5862
flushInterval = validateAndGetFlushInterval(flushInterval)
5963
batchSize = validateAndGetBatchSize(batchSize)
60-
this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual)
64+
this.queue = getQueue(
65+
batchSize,
66+
flushInterval,
67+
areEventContextsEqual,
68+
this.drainQueue.bind(this, false),
69+
this.drainQueue.bind(this, true),
70+
);
6171
}
6272

63-
drainQueue(buffer: ProcessableEvent[]): Promise<void> {
73+
private drainQueue(useClosingDispatcher: boolean, buffer: ProcessableEvent[]): Promise<void> {
6474
const reqPromise = new Promise<void>(resolve => {
6575
logger.debug('draining queue with %s events', buffer.length)
6676

@@ -70,7 +80,10 @@ export class LogTierV1EventProcessor implements EventProcessor {
7080
}
7181

7282
const formattedEvent = formatEvents(buffer)
73-
this.dispatcher.dispatchEvent(formattedEvent, () => {
83+
const dispatcher = useClosingDispatcher && this.closingDispatcher
84+
? this.closingDispatcher : this.dispatcher;
85+
86+
dispatcher.dispatchEvent(formattedEvent, () => {
7487
resolve()
7588
})
7689
sendEventNotification(this.notificationCenter, formattedEvent)

0 commit comments

Comments
 (0)
0