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]16238553: Provide an override option for the Sender #2113

Merged
merged 20 commits into from
Sep 27, 2023
1 change: 1 addition & 0 deletions .aiAutoMinify.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"eLoggingSeverity",
"_eInternalMessageId",
"SendRequestReason",
"TransportType",
"TelemetryUnloadReason",
"TelemetryUpdateReason"
]
Expand Down
8 changes: 4 additions & 4 deletions AISKU/Tests/Unit/src/AISKUSize.Tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { Snippet } from "../../../src/Snippet";
import { utlRemoveSessionStorage } from "@microsoft/applicationinsights-common";

export class AISKUSizeCheck extends AITestClass {
private readonly MAX_RAW_SIZE = 133;
private readonly MAX_BUNDLE_SIZE = 133;
private readonly MAX_RAW_DEFLATE_SIZE = 53;
private readonly MAX_BUNDLE_DEFLATE_SIZE = 53;
private readonly MAX_RAW_SIZE = 134;
private readonly MAX_BUNDLE_SIZE = 134;
private readonly MAX_RAW_DEFLATE_SIZE = 54;
private readonly MAX_BUNDLE_DEFLATE_SIZE = 54;
private readonly rawFilePath = "../dist/es5/applicationinsights-web.min.js";
// Automatically updated by version scripts
private readonly currentVer = "3.0.3";
Expand Down
4 changes: 2 additions & 2 deletions AISKULight/Tests/Unit/src/AISKULightSize.Tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { AITestClass, Assert } from "@microsoft/ai-test-framework";
import * as pako from "pako";

export class AISKULightSizeCheck extends AITestClass {
private readonly MAX_RAW_SIZE = 80;
private readonly MAX_BUNDLE_SIZE = 80;
private readonly MAX_RAW_SIZE = 81;
private readonly MAX_BUNDLE_SIZE = 81;
private readonly MAX_RAW_DEFLATE_SIZE = 33;
private readonly MAX_BUNDLE_DEFLATE_SIZE = 33;
private readonly rawFilePath = "../dist/es5/applicationinsights-web-basic.min.js";
Expand Down
31 changes: 1 addition & 30 deletions channels/1ds-post-js/src/DataModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,9 @@
* File containing the interfaces for Post channel module.
*/
import {
IDiagnosticLogger, IExtendedTelemetryItem, IProcessTelemetryContext, ITelemetryPlugin, IUnloadHook, IValueSanitizer
IDiagnosticLogger, IExtendedTelemetryItem, IProcessTelemetryContext, ITelemetryPlugin, IUnloadHook, IValueSanitizer, IPayloadData, IXHROverride
} from "@microsoft/1ds-core-js";

/** IPayloadData describes interface of payload sent via POST channel */
export interface IPayloadData {
urlString: string;
data: Uint8Array | string;
headers?: { [name: string]: string };
timeout?: number;
disableXhrSync?: boolean;
disableFetchKeepAlive?: boolean;
}

/**
* Defines the function signature for the Payload Preprocessor.
Expand Down Expand Up @@ -245,26 +236,6 @@ export interface IChannelConfiguration {
addNoResponse?: boolean;
}

/**
* SendPOSTFunction type defines how an HTTP POST request is sent to an ingestion server
* @param payload - The payload object that should be sent, contains the url, bytes/string and headers for the request
* @param oncomplete - The function to call once the request has completed whether a success, failure or timeout
* @param sync - A boolean flag indicating whether the request should be sent as a synchronous request.
*/
export type SendPOSTFunction = (payload: IPayloadData, oncomplete: (status: number, headers: { [headerName: string]: string; }, response?: string) => void, sync?: boolean) => void;

/**
* The IXHROverride interface overrides the way HTTP requests are sent.
*/
export interface IXHROverride {
/**
* This method sends data to the specified URI using a POST request. If sync is true,
* then the request is sent synchronously. The <i>oncomplete</i> function should always be called after the request is
* completed (either successfully or timed out or failed due to errors).
*/
sendPOST: SendPOSTFunction;
}

/**
* An interface which extends the telemetry event to track send attempts.
*/
Expand Down
8 changes: 4 additions & 4 deletions channels/1ds-post-js/src/HttpManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*/
import dynamicProto from "@microsoft/dynamicproto-js";
import {
EventSendType, FullVersionString, IAppInsightsCore, ICookieMgr, IDiagnosticLogger, IExtendedConfiguration, IPerfEvent, IUnloadHook,
EventSendType, FullVersionString, IAppInsightsCore, ICookieMgr, IDiagnosticLogger, IExtendedConfiguration, IPayloadData, IPerfEvent, IUnloadHook,
IXHROverride, OnCompleteCallback, SendPOSTFunction,
SendRequestReason, TransportType, _eExtendedInternalMessageId, _eInternalMessageId, _throwInternal, _warnToConsole, arrForEach, dateNow,
doPerf, dumpObj, eLoggingSeverity, extend, getLocation, getNavigator, getTime, hasOwnProperty, isArray, isBeaconsSupported,
isFetchSupported, isNullOrUndefined, isNumber, isReactNative, isString, isUndefined, isValueAssigned, isXhrSupported, objForEachKey,
Expand All @@ -15,8 +16,8 @@ import { arrAppend } from "@nevware21/ts-utils";
import { BatchNotificationAction, BatchNotificationActions } from "./BatchNotificationActions";
import { ClockSkewManager } from "./ClockSkewManager";
import {
EventBatchNotificationReason, IChannelConfiguration, ICollectorResult, IPayloadData, IPostChannel, IPostTransmissionTelemetryItem,
IXHROverride, PayloadListenerFunction, PayloadPreprocessorFunction, SendPOSTFunction
EventBatchNotificationReason, IChannelConfiguration, ICollectorResult, IPostChannel, IPostTransmissionTelemetryItem,
PayloadListenerFunction, PayloadPreprocessorFunction
} from "./DataModels";
import { EventBatch } from "./EventBatch";
import {
Expand Down Expand Up @@ -77,7 +78,6 @@ _addCollectorHeaderQsMapping(STR_TIME_DELTA_TO_APPLY, STR_TIME_DELTA_TO_APPLY);
_addCollectorHeaderQsMapping(STR_UPLOAD_TIME, STR_UPLOAD_TIME);
_addCollectorHeaderQsMapping(STR_AUTH_XTOKEN, STR_AUTH_XTOKEN);

type OnCompleteCallback = (status: number, headers: { [headerName: string]: string }, response?: string) => void;

function _getResponseText(xhr: XMLHttpRequest | IXDomainRequest) {
try {
Expand Down
8 changes: 5 additions & 3 deletions channels/1ds-post-js/src/Index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
*/

import {
BE_PROFILE, IChannelConfiguration, IPayloadData, IPostChannel, IXHROverride, NRT_PROFILE, PayloadListenerFunction,
PayloadPreprocessorFunction, RT_PROFILE, SendPOSTFunction
BE_PROFILE, IChannelConfiguration, IPostChannel, NRT_PROFILE, PayloadListenerFunction,
PayloadPreprocessorFunction, RT_PROFILE
} from "./DataModels";
import { PostChannel } from "./PostChannel";

import { IPayloadData, IXHROverride, OnCompleteCallback, SendPOSTFunction } from "@microsoft/1ds-core-js";

export {
PostChannel, IChannelConfiguration,
BE_PROFILE, NRT_PROFILE, RT_PROFILE, IXHROverride, IPostChannel,
SendPOSTFunction, IPayloadData, PayloadPreprocessorFunction, PayloadListenerFunction
SendPOSTFunction, IPayloadData, PayloadPreprocessorFunction, PayloadListenerFunction, OnCompleteCallback
};
4 changes: 2 additions & 2 deletions channels/1ds-post-js/test/Unit/src/HttpManagerTest.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AITestClass } from "@microsoft/ai-test-framework";
import { HttpManager } from "../../../src/HttpManager";
import { AppInsightsCore, EventSendType, IExtendedConfiguration, SendRequestReason, TransportType, isBeaconsSupported } from "@microsoft/1ds-core-js";
import { PostChannel, IXHROverride } from "../../../src/Index";
import { IPostTransmissionTelemetryItem, IPayloadData, EventBatchNotificationReason, IChannelConfiguration } from "../../../src/DataModels";
import { PostChannel, IXHROverride, IPayloadData } from "../../../src/Index";
import { IPostTransmissionTelemetryItem, EventBatchNotificationReason, IChannelConfiguration } from "../../../src/DataModels";
import { EventBatch } from "../../../src/EventBatch";

interface EventDetail {
Expand Down
139 changes: 137 additions & 2 deletions channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Sender } from "../../../src/Sender";
import { createOfflineListener, IOfflineListener } from '../../../src/Offline';
import { EnvelopeCreator } from '../../../src/EnvelopeCreator';
import { Exception, CtxTagKeys, isBeaconApiSupported, DEFAULT_BREEZE_ENDPOINT, DEFAULT_BREEZE_PATH, utlCanUseSessionStorage, utlGetSessionStorage, utlSetSessionStorage } from "@microsoft/applicationinsights-common";
import { ITelemetryItem, AppInsightsCore, ITelemetryPlugin, DiagnosticLogger, NotificationManager, SendRequestReason, _eInternalMessageId, getGlobalInst, safeGetLogger, getJSON, isString, isArray, arrForEach, isBeaconsSupported } from "@microsoft/applicationinsights-core-js";
import { ITelemetryItem, AppInsightsCore, ITelemetryPlugin, DiagnosticLogger, NotificationManager, SendRequestReason, _eInternalMessageId, getGlobalInst, safeGetLogger, getJSON, isString, isArray, arrForEach, isBeaconsSupported, IXHROverride, IPayloadData, isFetchSupported} from "@microsoft/applicationinsights-core-js";
import { ArraySendBuffer, SessionStorageSendBuffer } from "../../../src/SendBuffer";
import { ISenderConfig } from "../../../src/Interfaces";

Expand Down Expand Up @@ -118,6 +118,8 @@ export class SenderTests extends AITestClass {
QUnit.assert.equal(undefined, defaultSenderConfig.customHeaders, "Channel default customHeaders config is set");
QUnit.assert.equal(undefined, defaultSenderConfig.convertUndefined, "Channel default convertUndefined config is set");
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");

//check dynamic config
core.config.extensionConfig = core.config.extensionConfig? core.config.extensionConfig : {};
Expand All @@ -131,7 +133,8 @@ export class SenderTests extends AITestClass {
isRetryDisabled: true,
disableXhr: true,
samplingPercentage: 90,
customHeaders: [{header: "header1",value:"value1"}]
customHeaders: [{header: "header1",value:"value1"}],
alwaysUseXhrOverride: true
}
core.config.extensionConfig[id] = config;
this.clock.tick(1);
Expand All @@ -145,6 +148,7 @@ export class SenderTests extends AITestClass {
QUnit.assert.equal(true, curSenderConfig.isRetryDisabled, "Channel isRetryDisabled config is dynamically set");
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");

core.config.extensionConfig[this._sender.identifier].emitLineDelimitedJson = undefined;
core.config.extensionConfig[this._sender.identifier].endpointUrl = undefined;
Expand All @@ -154,6 +158,137 @@ export class SenderTests extends AITestClass {
}
});

this.testCase({
name: "Channel Config: Sender override can be handled correctly",
useFakeTimers: true,
test: () => {
let core = new AppInsightsCore();
let sentPayloadData: any[] = [];
var xhrOverride: IXHROverride = {
sendPOST: (payload: IPayloadData, oncomplete: (status: number, headers: {[headerName: string]: string;}, response?: string) => void, sync?: boolean) => {
sentPayloadData.push({payload: payload, sync: sync});
}
};

let coreConfig = {
instrumentationKey: "abc",
extensionConfig: {
[this._sender.identifier]: {
httpXHROverride: xhrOverride
}
}
}
let testBatch: string[] = ["test", "test1"];
const telemetryItem: ITelemetryItem = {
name: "fake item",
iKey: "test",
baseType: "some type",
baseData: {}
};
core.initialize(coreConfig, [this._sender]);

// with always override to false
QUnit.assert.deepEqual(xhrOverride, this._sender._senderConfig.httpXHROverride, "Channel httpXHROverride config is set");
QUnit.assert.deepEqual(false, this._sender._senderConfig.alwaysUseXhrOverride, "Channel alwaysUseXhrOverride config is set");
this._sender._sender(testBatch, true);
QUnit.assert.equal(0, sentPayloadData.length, "httpXHROverride is not called once with always override to false");
this._sender._sender(testBatch, false);
QUnit.assert.equal(0, sentPayloadData.length, "httpXHROverride is not called once with always override to false test1");

try {
this._sender.processTelemetry(telemetryItem);
} catch(e) {
QUnit.assert.ok(false, "Exception - " + e);
}
this._sender.onunloadFlush();
QUnit.assert.deepEqual(0, sentPayloadData.length, "httpXHROverride should not be called again test2");


// with always override to true
core.config.extensionConfig = core.config.extensionConfig || {};
core.config.extensionConfig[this._sender.identifier].alwaysUseXhrOverride = true;
this.clock.tick(1);
QUnit.assert.deepEqual(true, this._sender._senderConfig.alwaysUseXhrOverride, "Channel alwaysUseXhrOverride config is set to true dynamically");
this._sender._sender(testBatch, true);
QUnit.assert.deepEqual(1, sentPayloadData.length, "httpXHROverride should be called with always override to true");
let payload = sentPayloadData[0].payload;
let sync = sentPayloadData[0].sync;
QUnit.assert.equal(false, sync, "Channel httpXHROverride sync is called with false during send test1 (sender interface should be opposite with the sender)");
QUnit.assert.deepEqual(testBatch, payload.oriPayload, "Channel httpXHROverride sync is called with expected original payload");
QUnit.assert.deepEqual(this._sender._buffer.batchPayloads(testBatch),payload.data, "Channel httpXHROverride sync is called with expected batch payload");

try {
this._sender.processTelemetry(telemetryItem);
} catch(e) {
QUnit.assert.ok(false, "Exception - " + e);
}
this._sender.onunloadFlush();
QUnit.assert.deepEqual(2, sentPayloadData.length, "httpXHROverride should be called");
let data = sentPayloadData[1].payload.oriPayload;
payload = JSON.parse(data[0]);
QUnit.assert.deepEqual("test", payload.iKey, "httpXHROverride should send expected payload test1");
sync = sentPayloadData[1].sync;
QUnit.assert.equal(true, sync, "Channel httpXHROverride sync is called with true during send test2 (sender interface should be opposite with the sender)");

}
});

this.testCase({
name: "Channel Config: Invalid paylod Sender should not be sent",
useFakeTimers: true,
test: () => {
let core = new AppInsightsCore();
let sentPayloadData: any[] = [];
var xhrOverride: IXHROverride = {
sendPOST: (payload: IPayloadData, oncomplete: (status: number, headers: {[headerName: string]: string;}, response?: string) => void, sync?: boolean) => {
sentPayloadData.push({payload: payload, sync: sync});
}
};

let coreConfig = {
instrumentationKey: "abc",
extensionConfig: {
[this._sender.identifier]: {
httpXHROverride: xhrOverride,
alwaysUseXhrOverride: true
}
}
}
let testBatch: string[] = ["test", "test1"];

core.initialize(coreConfig, [this._sender]);

QUnit.assert.deepEqual(xhrOverride, this._sender._senderConfig.httpXHROverride, "Channel httpXHROverride config is set");
QUnit.assert.deepEqual(true, this._sender._senderConfig.alwaysUseXhrOverride, "Channel alwaysUseXhrOverride config is set");
// case 1: payload is null
this._sender._sender(null as any, true);
QUnit.assert.equal(0, sentPayloadData.length, "httpXHROverride is not called test1");
this._sender._sender(null as any, false);
QUnit.assert.equal(0, sentPayloadData.length, "httpXHROverride is not called once sync test1");

// case 2: payload is none array
this._sender._sender({} as any, true);
QUnit.assert.equal(0, sentPayloadData.length, "httpXHROverride is not called test2");
this._sender._sender({} as any, false);
QUnit.assert.equal(0, sentPayloadData.length, "httpXHROverride is not called once sync test2");

// case 3: payload is an empty array
this._sender._sender([] as any, true);
QUnit.assert.equal(0, sentPayloadData.length, "httpXHROverride is not called test3");
this._sender._sender([] as any, false);
QUnit.assert.equal(0, sentPayloadData.length, "httpXHROverride is not called once sync test3");


this._sender._sender(testBatch, true);
QUnit.assert.equal(1, sentPayloadData.length, "httpXHROverride is called test4");
this._sender._sender(testBatch, false);
QUnit.assert.equal(2, sentPayloadData.length, "httpXHROverride is called once sync test4");


}
});


this.testCase({
name: "Channel Config: sessionStorage change from true to false can be handled correctly",
useFakeTimers: true,
Expand Down
19 changes: 19 additions & 0 deletions channels/applicationinsights-channel-js/src/Interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IStorageBuffer } from "@microsoft/applicationinsights-common";
import { IXHROverride } from "@microsoft/applicationinsights-core-js";

export interface ISenderConfig {
/**
Expand Down Expand Up @@ -94,6 +95,24 @@ export interface ISenderConfig {
* @since 3.0.1
*/
enableSendPromise?: boolean;

/**
* [Optional] The HTTP override that should be used to send requests, as an IXHROverride object.
* By default during the unload of a page or if the event specifies that it wants to use sendBeacon() or sync fetch (with keep-alive),
* this override will NOT be called.
* If onunloadDisableFetch and onunloadDisableBeacon are true, You can now change this behavior by enabling the 'alwaysUseXhrOverride' configuration value.
* The payload data (first argument) now also includes any configured 'timeout' (defaults to undefined) and whether you should avoid
* creating any synchronous XHR requests 'disableXhr' (defaults to false/undefined)
*/
httpXHROverride?: IXHROverride;
Karlie-777 marked this conversation as resolved.
Show resolved Hide resolved

/**
* [Optional] By default during unload (or when you specify to use sendBeacon() or sync fetch (with keep-alive) for an event) the SDK
* ignores any provided httpXhrOverride and attempts to use sendBeacon() or fetch(with keep-alive) when they are available.
* When this configuration option is true any provided httpXhrOverride will always be used, so any provided httpXhrOverride will
* also need to "handle" the synchronous unload scenario.
*/
alwaysUseXhrOverride?: boolean;
}

export interface IBackendResponse {
Expand Down
Loading
Loading