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 4ebd8e378..cb7431121 100644 --- a/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts +++ b/channels/applicationinsights-channel-js/Tests/Unit/src/Sender.tests.ts @@ -2739,6 +2739,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); + this.clock.tick(30000); + } catch(e) { + QUnit.assert.ok(false); + } + const CrossOriginResourcePolicyHeader: string = "X-Set-Cross-Origin-Resource-Policy"; + QUnit.assert.equal(1, this._getXhrRequests().length, "xhr sender is called"); + let headers = this._getXhrRequests()[0].requestHeaders; + QUnit.assert.ok(headers.hasOwnProperty(CrossOriginResourcePolicyHeader)); + QUnit.assert.equal(headers[CrossOriginResourcePolicyHeader], 'cross-origin'); + 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); + this.clock.tick(30000); + } catch(e) { + QUnit.assert.ok(false); + } + headers = this._getXhrRequests()[1].requestHeaders; + QUnit.assert.ok(headers.hasOwnProperty(CrossOriginResourcePolicyHeader)); + QUnit.assert.equal(headers[CrossOriginResourcePolicyHeader], '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); + this.clock.tick(30000); + } catch(e) { + QUnit.assert.ok(false); + } + headers = this._getXhrRequests()[2].requestHeaders; + QUnit.assert.notOk(this._getXhrRequests()[2].requestHeaders.hasOwnProperty(CrossOriginResourcePolicyHeader)); + } + }); + 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 1df9c6625..3a0d975ef 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.7 + */ + corsPolicy?: string; } export interface IBackendResponse { diff --git a/channels/applicationinsights-channel-js/src/Sender.ts b/channels/applicationinsights-channel-js/src/Sender.ts index 992520ecc..9e76c676d 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-Set-Cross-Origin-Resource-Policy"; + function _chkSampling(value: number) { return !isNaN(value) && value > 0 && value <= 100; } @@ -268,7 +271,6 @@ 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 curExtUrl = senderConfig.endpointUrl; // if it is not inital change (_endpointUrl has value) // if current sender endpoint url is not changed directly @@ -283,6 +285,15 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls { } } + 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.instrumentationKey)) { // if it is promise, means the endpoint url is from core.endpointurl senderConfig.instrumentationKey = config.instrumentationKey as any;