Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Main][Task]26079397: Add disableBeaconSplit sender config and fix potential duplicated events during unload/fallback sender #2208

Merged
merged 2 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class SenderTests extends AITestClass {
QUnit.assert.equal(10000, defaultSenderConfig.eventsLimitInMem, "Channel default eventsLimitInMem config is set");
QUnit.assert.equal(undefined, defaultSenderConfig.httpXHROverride, "Channel default httpXHROverride config is set");
QUnit.assert.equal(false, defaultSenderConfig.alwaysUseXhrOverride, "Channel default alwaysUseXhrOverride config is set");
QUnit.assert.equal(false, defaultSenderConfig.disableSendBeaconSplit, "Channel default disableSendBeaconSplit config is set");

//check dynamic config
core.config.extensionConfig = core.config.extensionConfig? core.config.extensionConfig : {};
Expand All @@ -134,7 +135,8 @@ export class SenderTests extends AITestClass {
disableXhr: true,
samplingPercentage: 90,
customHeaders: [{header: "header1",value:"value1"}],
alwaysUseXhrOverride: true
alwaysUseXhrOverride: true,
disableSendBeaconSplit: true
}
core.config.extensionConfig[id] = config;
this.clock.tick(1);
Expand All @@ -149,6 +151,7 @@ export class SenderTests extends AITestClass {
QUnit.assert.equal(90, curSenderConfig.samplingPercentage, "Channel samplingPercentage config is dynamically set");
QUnit.assert.deepEqual([{header: "header1",value:"value1"}], curSenderConfig.customHeaders, "Channel customHeaders config is dynamically set");
QUnit.assert.deepEqual(true, curSenderConfig.alwaysUseXhrOverride, "Channel alwaysUseXhrOverride config is dynamically set");
QUnit.assert.equal(true, curSenderConfig.disableSendBeaconSplit, "Channel disableSendBeaconSplit config is dynamically set");

core.config.extensionConfig[this._sender.identifier].emitLineDelimitedJson = undefined;
core.config.extensionConfig[this._sender.identifier].endpointUrl = undefined;
Expand Down Expand Up @@ -1584,6 +1587,270 @@ export class SenderTests extends AITestClass {
}
});

this.testCase({
name: "disableBeaconSplit is set to true, xhr should be used to send data diretly instead of splitting payloads.",
useFakeTimers: true,
test: () => {
let window = getWindow();
let fetchstub = this.sandbox.stub((window as any), "fetch");
let fakeXMLHttpRequest = (window as any).XMLHttpRequest;
let sessionStorage = window.sessionStorage;
QUnit.assert.ok(sessionStorage, "sessionStorage API is supported");
sessionStorage.clear();

let sendBeaconCalled = false;
this.hookSendBeacon((url: string) => {
sendBeaconCalled = true;
return false;
});

const sender = new Sender();
const cr = new AppInsightsCore();

sender.initialize({
instrumentationKey: "abc",
extensionConfig: {
[sender.identifier]: {
disableSendBeaconSplit: true,
onunloadDisableFetch: true,
disableXhr: true
// to make sure beacon is used
}
}

}, cr, []);
this.onDone(() => {
sender.teardown();
});

const telemetryItem: ITelemetryItem = {
name: "fake item",
iKey: "iKey",
baseType: "some type",
baseData: {
largePayload: new Array(64 + 1).join("test")
}
};

let buffer = sender._buffer;

QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported");
QUnit.assert.equal(false, sendBeaconCalled, "Beacon API was not called before");
QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before");
QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear");

try {
sender.processTelemetry(telemetryItem, null);
QUnit.assert.equal(1, buffer.getItems().length, "sender buffer should have one payload");
let bufferItems = JSON.parse(sessionStorage.getItem("AI_buffer") as any);
QUnit.assert.equal(bufferItems.length, 1, "sender buffer should have one payload");
let sentItems = JSON.parse(sessionStorage.getItem("AI_sentBuffer") as any);
QUnit.assert.equal(0, sentItems.length, "sent buffer should have zero payload");
sender.onunloadFlush();
} catch(e) {
QUnit.assert.ok(false);
}

this.clock.tick(5);

QUnit.assert.equal(true, sendBeaconCalled, "Beacon API should be called");
QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender should be called");
let xhrRequest = this._getXhrRequests()[0];
QUnit.assert.equal(false, fetchstub.called, "fetch sender is not called");
QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should not have one payload");
QUnit.assert.equal(0, buffer.count(), "sender buffer should not have any payload");
let bufferItems = JSON.parse(sessionStorage.getItem("AI_buffer") as any);
QUnit.assert.equal(bufferItems.length, 0, "sender buffer should be clear payload");
let sentItems = JSON.parse(sessionStorage.getItem("AI_sentBuffer") as any);
QUnit.assert.equal(1, sentItems.length, "sent buffer should have only one payload");

this.sendJsonResponse(xhrRequest, {}, 200);
bufferItems = JSON.parse(sessionStorage.getItem("AI_buffer") as any);
QUnit.assert.equal(bufferItems.length, 0, "sender buffer should be clear payload");
sentItems = JSON.parse(sessionStorage.getItem("AI_sentBuffer") as any);
QUnit.assert.equal(0, sentItems.length, "sent buffer should have no payload");

(window as any).XMLHttpRequest = fakeXMLHttpRequest;
sessionStorage.clear();

}
});

this.testCase({
name: "disableBeaconSplit is set to false, xhr should not be called to send small payload.",
test: () => {
let window = getWindow();
let fetchstub = this.sandbox.stub((window as any), "fetch");
let fakeXMLHttpRequest = (window as any).XMLHttpRequest;
let sessionStorage = window.sessionStorage;
QUnit.assert.ok(sessionStorage, "sessionStorage API is supported");
sessionStorage.clear();

let sendBeaconCalled = 0;
this.hookSendBeacon((url: string) => {
sendBeaconCalled += 1;
return true;
});

const sender = new Sender();
const cr = new AppInsightsCore();

sender.initialize({
instrumentationKey: "abc",
extensionConfig: {
[sender.identifier]: {
onunloadDisableFetch: true,
disableXhr: true
// to make sure beacon is used
}
}

}, cr, []);
this.onDone(() => {
sender.teardown();
});

const telemetryItem: ITelemetryItem = {
name: "item",
iKey: "iKey",
baseType: "type",
baseData: {}
};


let buffer = sender._buffer;

QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported");
QUnit.assert.equal(0, sendBeaconCalled, "Beacon API was not called before");
QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before");
QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear");

try {
sender.processTelemetry(telemetryItem, null);
QUnit.assert.equal(1, buffer.getItems().length, "sender buffer should have one payload");
let bufferItems = JSON.parse(sessionStorage.getItem("AI_buffer") as any);
QUnit.assert.equal(bufferItems.length, 1, "sender buffer should have one payload");
let sentItems = JSON.parse(sessionStorage.getItem("AI_sentBuffer") as any);
QUnit.assert.equal(0, sentItems.length, "sent buffer should have zero payload");
sender.onunloadFlush();
} catch(e) {
QUnit.assert.ok(false);
}

QUnit.assert.equal(1, sendBeaconCalled, "Beacon API should be called");
QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender should be called");
QUnit.assert.equal(false, fetchstub.called, "fetch sender is not called");
QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should not have one payload");
QUnit.assert.equal(0, buffer.count(), "sender buffer should not have any payload");
let bufferItems = JSON.parse(sessionStorage.getItem("AI_buffer") as any);
QUnit.assert.equal(bufferItems.length, 0, "sender buffer should be clear payload");
let sentItems = JSON.parse(sessionStorage.getItem("AI_sentBuffer") as any);
QUnit.assert.equal(0, sentItems.length, "sent buffer should not have one payload");

(window as any).XMLHttpRequest = fakeXMLHttpRequest;
sessionStorage.clear();

}
});

this.testCase({
name: "disableBeaconSplit is set to false, xhr should be called to send large payload.",
useFakeTimers: true,
test: () => {
let window = getWindow();
let fetchstub = this.sandbox.stub((window as any), "fetch");
let fakeXMLHttpRequest = (window as any).XMLHttpRequest;
let sessionStorage = window.sessionStorage;
QUnit.assert.ok(sessionStorage, "sessionStorage API is supported");
sessionStorage.clear();

let sendBeaconCalled = 0;
this.hookSendBeacon((url: string) => {
sendBeaconCalled += 1;
if (sendBeaconCalled == 2) {
return true;
}
return false;
});

const sender = new Sender();
const cr = new AppInsightsCore();

sender.initialize({
instrumentationKey: "abc",
extensionConfig: {
[sender.identifier]: {
onunloadDisableFetch: true,
disableXhr: true
// to make sure beacon is used
}
}

}, cr, []);
this.onDone(() => {
sender.teardown();
});

const telemetryItem: ITelemetryItem = {
name: "item",
iKey: "iKey",
baseType: "type",
baseData: {}
};

const telemetryItem1: ITelemetryItem = {
name: "item",
iKey: "iKey1",
baseType: "type",
baseData: {}
};


let buffer = sender._buffer;

QUnit.assert.ok(isBeaconApiSupported(), "Beacon API is supported");
QUnit.assert.equal(0, sendBeaconCalled, "Beacon API was not called before");
QUnit.assert.equal(0, this._getXhrRequests().length, "xhr sender was not called before");
QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should be clear");

try {
sender.processTelemetry(telemetryItem, null);
sender.processTelemetry(telemetryItem1, null);
QUnit.assert.equal(2, buffer.getItems().length, "sender buffer should have one payload");
let bufferItems = JSON.parse(sessionStorage.getItem("AI_buffer") as any);
QUnit.assert.equal(bufferItems.length, 2, "sender buffer should have one payload");
let sentItems = JSON.parse(sessionStorage.getItem("AI_sentBuffer") as any);
QUnit.assert.equal(0, sentItems.length, "sent buffer should have zero payload");
sender.onunloadFlush();
} catch(e) {
QUnit.assert.ok(false);
}
this.clock.tick(5);

QUnit.assert.equal(3, sendBeaconCalled, "Beacon API should be called 3 times");
QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender should be called");
let xhrRequest = this._getXhrRequests()[0];
QUnit.assert.equal(false, fetchstub.called, "fetch sender is not called");
QUnit.assert.equal(0, buffer.getItems().length, "sender buffer should not have one payload");
QUnit.assert.equal(0, buffer.count(), "sender buffer should not have any payload");
let bufferItems = JSON.parse(sessionStorage.getItem("AI_buffer") as any);
QUnit.assert.equal(bufferItems.length, 0, "sender buffer should be clear payload");
let sentItems = JSON.parse(sessionStorage.getItem("AI_sentBuffer") as any);
QUnit.assert.equal(1, sentItems.length, "sent buffer should have one payload");
QUnit.assert.ok(sentItems[0].indexOf("iKey1") >= 0, "sent buffer should have ikey1 payload");

this.sendJsonResponse(xhrRequest, {}, 200);
bufferItems = JSON.parse(sessionStorage.getItem("AI_buffer") as any);
QUnit.assert.equal(bufferItems.length, 0, "sender buffer should have no payload test1");
sentItems = JSON.parse(sessionStorage.getItem("AI_sentBuffer") as any);
QUnit.assert.equal(0, sentItems.length, "sent buffer should have zero payload test1");

(window as any).XMLHttpRequest = fakeXMLHttpRequest;
sessionStorage.clear();

}
});

this.testCase({
name: 'FetchAPI is used when isBeaconApiDisabled flag is true and disableXhr flag is true , use fetch sender.',
test: () => {
Expand Down
9 changes: 9 additions & 0 deletions channels/applicationinsights-channel-js/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ export interface ISenderConfig {
*/
alwaysUseXhrOverride?: boolean;

/**
* [Optional] Disable events splitting during sendbeacon.
* Default: false
* @since 3.0.5
Karlie-777 marked this conversation as resolved.
Show resolved Hide resolved
*/
disableSendBeaconSplit?: boolean;

/**
* [Optional] Either an array or single value identifying the requested TransportType type that should be used.
* This is used during initialization to identify the requested send transport, it will be ignored if a httpXHROverride is provided.
Expand All @@ -128,6 +135,8 @@ export interface ISenderConfig {
* is provided and alwaysUseXhrOverride is true.
*/
unloadTransports?: number | number[];


}

export interface IBackendResponse {
Expand Down
Loading
Loading