Skip to content

Commit

Permalink
feat: Add useBeacon config for visibilitychange dispatch behavior. (#194
Browse files Browse the repository at this point in the history
)
  • Loading branch information
qhanam committed Aug 9, 2022
1 parent eca6e1e commit 00ef55f
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 25 deletions.
76 changes: 51 additions & 25 deletions src/dispatch/Dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@ type SendFunction = (
) => Promise<{ response: HttpResponse }>;

interface DataPlaneClientInterface {
sendFetch: (
putRumEventsRequest: PutRumEventsRequest
) => Promise<{ response: HttpResponse }>;
sendBeacon: (
putRumEventsRequest: PutRumEventsRequest
) => Promise<{ response: HttpResponse }>;
sendFetch: SendFunction;
sendBeacon: SendFunction;
}

const NO_CRED_MSG = 'CWR: Cannot dispatch; no AWS credentials.';
Expand Down Expand Up @@ -102,14 +98,23 @@ export class Dispatch {
* Send meta data and events to the AWS RUM data plane service via fetch.
*/
public dispatchFetch = async (): Promise<{ response: HttpResponse }> => {
return this.dispatch(this.rum.sendFetch).catch(this.handleReject);
if (this.doRequest()) {
return this.rum
.sendFetch(this.createRequest())
.catch(this.handleReject);
}
};

/**
* Send meta data and events to the AWS RUM data plane service via beacon.
*/
public dispatchBeacon = async (): Promise<{ response: HttpResponse }> => {
return this.dispatch(this.rum.sendBeacon);
if (this.doRequest()) {
const request: PutRumEventsRequest = this.createRequest();
return this.rum
.sendBeacon(request)
.catch(() => this.rum.sendFetch(request));
}
};

/**
Expand Down Expand Up @@ -142,9 +147,31 @@ export class Dispatch {
public startDispatchTimer() {
document.addEventListener(
'visibilitychange',
this.dispatchBeaconFailSilent
// The page is moving to the hidden state, which means it may be
// unloaded. The sendBeacon API would typically be used in this
// case. However, ad-blockers prevent sendBeacon from functioning.
// We therefore have two bad options:
//
// (1) Use sendBeacon. Data will be lost when ad blockers are
// used and the page loses visibility
// (2) Use fetch. Data will be lost when the page is unloaded
// before fetch completes
//
// A third option is to send both, however this would increase
// bandwitch and require deduping server side.
this.config.useBeacon
? this.dispatchBeaconFailSilent
: this.dispatchFetchFailSilent
);
// Using 'pagehide' is redundant most of the time (visibilitychange is
// always fired before pagehide) but older browsers may support
// 'pagehide' but not 'visibilitychange'.
document.addEventListener(
'pagehide',
this.config.useBeacon
? this.dispatchBeaconFailSilent
: this.dispatchFetchFailSilent
);
document.addEventListener('pagehide', this.dispatchBeaconFailSilent);
if (this.config.dispatchInterval <= 0 || this.dispatchTimerId) {
return;
}
Expand All @@ -160,34 +187,33 @@ export class Dispatch {
public stopDispatchTimer() {
document.removeEventListener(
'visibilitychange',
this.dispatchBeaconFailSilent
this.config.useBeacon
? this.dispatchBeaconFailSilent
: this.dispatchFetchFailSilent
);
document.removeEventListener(
'pagehide',
this.config.useBeacon
? this.dispatchBeaconFailSilent
: this.dispatchFetchFailSilent
);
document.removeEventListener('pagehide', this.dispatchBeaconFailSilent);
if (this.dispatchTimerId) {
window.clearInterval(this.dispatchTimerId);
this.dispatchTimerId = undefined;
}
}

private async dispatch(
send: SendFunction
): Promise<{ response: HttpResponse }> {
if (!this.enabled) {
return;
}

if (!this.eventCache.hasEvents()) {
return;
}
private doRequest(): boolean {
return this.enabled && this.eventCache.hasEvents();
}

const putRumEventsRequest: PutRumEventsRequest = {
private createRequest(): PutRumEventsRequest | undefined {
return {
BatchId: v4(),
AppMonitorDetails: this.eventCache.getAppMonitorDetails(),
UserDetails: this.eventCache.getUserDetails(),
RumEvents: this.eventCache.getEventBatch()
};

return send(putRumEventsRequest);
}

private handleReject = (e: any): { response: HttpResponse } => {
Expand Down
44 changes: 44 additions & 0 deletions src/dispatch/__tests__/Dispatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,50 @@ describe('Dispatch tests', () => {
expect(sendBeacon).toHaveBeenCalled();
});

test('when useBeacon is false then visibilitychange uses fetch dispatch', async () => {
// Init
const dispatch = new Dispatch(
Utils.AWS_RUM_REGION,
Utils.AWS_RUM_ENDPOINT,
Utils.createDefaultEventCacheWithEvents(),
{
...DEFAULT_CONFIG,
...{ dispatchInterval: 1, useBeacon: false }
}
);
dispatch.setAwsCredentials(Utils.createAwsCredentials());
dispatch.startDispatchTimer();

// Run
document.dispatchEvent(new Event('visibilitychange'));

// Assert
expect(sendBeacon).not.toHaveBeenCalled();
expect(sendFetch).toHaveBeenCalled();
});

test('when useBeacon is false then pagehide uses fetch dispatch', async () => {
// Init
const dispatch = new Dispatch(
Utils.AWS_RUM_REGION,
Utils.AWS_RUM_ENDPOINT,
Utils.createDefaultEventCacheWithEvents(),
{
...DEFAULT_CONFIG,
...{ dispatchInterval: 1, useBeacon: false }
}
);
dispatch.setAwsCredentials(Utils.createAwsCredentials());
dispatch.startDispatchTimer();

// Run
document.dispatchEvent(new Event('pagehide'));

// Assert
expect(sendBeacon).not.toHaveBeenCalled();
expect(sendFetch).toHaveBeenCalled();
});

test('when plugin is disabled then beacon dispatch does not run', async () => {
// Init
const dispatch = new Dispatch(
Expand Down
3 changes: 3 additions & 0 deletions src/orchestration/Orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export type PartialConfig = {
* plugins which map to the selected categories.
*/
telemetries?: Telemetry[];
useBeacon?: boolean;
userIdRetentionDays?: number;
};

Expand Down Expand Up @@ -131,6 +132,7 @@ export const defaultConfig = (cookieAttributes: CookieAttributes): Config => {
sessionLengthSeconds: 60 * 30,
sessionSampleRate: 1,
telemetries: [],
useBeacon: true,
userIdRetentionDays: 30
};
};
Expand Down Expand Up @@ -178,6 +180,7 @@ export type Config = {
sessionLengthSeconds: number;
sessionSampleRate: number;
telemetries: Telemetry[];
useBeacon: boolean;
userIdRetentionDays: number;
};

Expand Down
1 change: 1 addition & 0 deletions src/orchestration/__tests__/Orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ describe('Orchestration tests', () => {
sessionSampleRate: 1,
userIdRetentionDays: 30,
enableRumClient: true,
useBeacon: true,
fetchFunction: fetch
});
});
Expand Down

0 comments on commit 00ef55f

Please sign in to comment.