diff --git a/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts b/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts index e9ea85896..69bc49de2 100644 --- a/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts +++ b/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts @@ -2708,6 +2708,77 @@ export class SenderTests extends AITestClass { } }); + this.testCase({ + name: 'Users could set the cross-origin header via request', + useFakeTimers: true, + test: () => { + let core = new AppInsightsCore(); + let id = this._sender.identifier; + let coreConfig = { + instrumentationKey: 'abc', + isBeaconApiDisabled: true, + extensionConfig: { + [this._sender.identifier]: { + corsPolicy: "cross-origin", + } + } + } + core.initialize(coreConfig, [this._sender]); + + let sendBeaconCalled = false; + this.hookSendBeacon((url: string) => { + sendBeaconCalled = true; + return true; + }); + + const telemetryItem: ITelemetryItem = { + name: 'fake item', + iKey: 'iKey', + baseType: 'some type', + baseData: {} + }; + + try { + this._sender.processTelemetry(telemetryItem, null); + this._sender.flush(); + } catch(e) { + QUnit.assert.ok(false); + } + + QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender is called"); + let headers = this._getXhrRequests()[0].requestHeaders; + QUnit.assert.equal(headers['X-Cross-Origin-Resource-Policy'], 'cross-origin'); + QUnit.assert.ok(headers.hasOwnProperty('X-Cross-Origin-Resource-Policy')); + QUnit.assert.notOk(this._getXhrRequests()[0].requestHeaders.hasOwnProperty('testHeader')); + + // dynamic change + core.config.extensionConfig[this._sender.identifier].corsPolicy = "same-origin"; + this.clock.tick(1); + try { + this._sender.processTelemetry(telemetryItem, null); + this._sender.flush(); + } catch(e) { + QUnit.assert.ok(false); + } + headers = this._getXhrRequests()[1].requestHeaders; + QUnit.assert.ok(headers.hasOwnProperty('X-Cross-Origin-Resource-Policy')); + QUnit.assert.equal(headers['X-Cross-Origin-Resource-Policy'], 'same-origin'); + QUnit.assert.notOk(this._getXhrRequests()[1].requestHeaders.hasOwnProperty('testHeader')); + + // dynamic change to null + core.config.extensionConfig[this._sender.identifier].corsPolicy = null; + this.clock.tick(1); + try { + this._sender.processTelemetry(telemetryItem, null); + this._sender.flush(); + } catch(e) { + QUnit.assert.ok(false); + } + headers = this._getXhrRequests()[2].requestHeaders; + QUnit.assert.notOk(this._getXhrRequests()[2].requestHeaders.hasOwnProperty('X-Cross-Origin-Resource-Policy')); + } + }); + this.testCase({ name: 'Users are allowed to add customHeaders when endpointUrl is not Breeze.', test: () => { diff --git a/channels/applicationinsights-channel-js/src/Interfaces.ts b/channels/applicationinsights-channel-js/src/Interfaces.ts index 7d6a43142..834c0082f 100644 --- a/channels/applicationinsights-channel-js/src/Interfaces.ts +++ b/channels/applicationinsights-channel-js/src/Interfaces.ts @@ -166,6 +166,20 @@ export interface ISenderConfig { * @since 3.2.0 */ maxRetryCnt?: number; + + /** + * [Optional] Specifies the Cross-Origin Resource Policy (CORP) for the endpoint. + * This value is included in the response header as `Cross-Origin-Resource-Policy`, + * which helps control how resources can be shared across different origins. + * + * Possible values: + * - `same-site`: Allows access only from the same site. + * - `same-origin`: Allows access only from the same origin (protocol, host, and port). + * - `cross-origin`: Allows access from any origin. + * + * @since 3.3.3 + */ + corsPolicy?: string; } export interface IBackendResponse { diff --git a/channels/applicationinsights-channel-js/src/Sender.ts b/channels/applicationinsights-channel-js/src/Sender.ts index 55c42665b..946febd5b 100644 --- a/channels/applicationinsights-channel-js/src/Sender.ts +++ b/channels/applicationinsights-channel-js/src/Sender.ts @@ -78,9 +78,12 @@ const defaultAppInsightsChannelConfig: IConfigDefaults = objDeepF alwaysUseXhrOverride: cfgDfBoolean(), transports: UNDEFINED_VALUE, retryCodes: UNDEFINED_VALUE, + corsPolicy: UNDEFINED_VALUE, maxRetryCnt: {isVal: isNumber, v:10} }); +const CrossOriginResourcePolicyHeader: string = "X-Cross-Origin-Resource-Policy"; + function _chkSampling(value: number) { return !isNaN(value) && value > 0 && value <= 100; } @@ -268,6 +271,14 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { let ctx = createProcessTelemetryContext(null, config, core); // getExtCfg only finds undefined values from core let senderConfig = ctx.getExtCfg(identifier, defaultAppInsightsChannelConfig); + let corsPolicy = senderConfig.corsPolicy; + if (corsPolicy){ + if (corsPolicy === "same-origin" || corsPolicy === "same-site" || corsPolicy === "cross-origin") { + this.addHeader(CrossOriginResourcePolicyHeader, corsPolicy); + } + } else { + delete _headers[CrossOriginResourcePolicyHeader]; + } if(isPromiseLike(senderConfig.endpointUrl)) { // if it is promise, means the endpoint url is from core.endpointurl senderConfig.endpointUrl = config.endpointUrl as any;