Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
12 changes: 6 additions & 6 deletions AISKU/src/AISku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,11 @@ export class AppInsightsSku implements IApplicationInsights {

_self.snippet = snippet;

_self.flush = (async: boolean = true, callBack?: () => void) => {
_self.flush = (isAsync: boolean = true, callBack?: () => void) => {
let result: void | IPromise<void>;

doPerf(_core, () => "AISKU.flush", () => {
if (async && !callBack) {
if (isAsync && !callBack) {
result = createPromise((resolve) => {
callBack = resolve;
});
Expand All @@ -294,23 +294,23 @@ export class AppInsightsSku implements IApplicationInsights {
arrForEach(_core.getChannels(), channel => {
if (channel) {
waiting++;
channel.flush(async, flushDone);
channel.flush(isAsync, flushDone);
}
});

// decrement the initial "waiting"
flushDone();
}, null, async);
}, null, isAsync);

return result;
};

_self.onunloadFlush = (async: boolean = true) => {
_self.onunloadFlush = (isAsync: boolean = true) => {
arrForEach(_core.getChannels(), (channel: IChannelControls & Sender) => {
if (channel.onunloadFlush) {
channel.onunloadFlush();
} else {
channel.flush(async);
channel.flush(isAsync);
}
});
};
Expand Down
4 changes: 2 additions & 2 deletions AISKULight/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ export class ApplicationInsights {

/**
* Immediately send all batched telemetry
* @param async - Should the flush be performed asynchronously
* @param isAsync - Should the flush be performed asynchronously
*/
public flush(async: boolean = true) {
public flush(isAsync: boolean = true) {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
}

Expand Down
28 changes: 28 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

> Note: ES3/IE8 compatibility will be removed in the future v3.x.x releases (scheduled for mid-late 2022), so if you need to retain ES3 compatibility you will need to remain on the 2.x.x versions of the SDK or your runtime will need install polyfill's to your ES3 environment before loading / initializing the SDK.

## Unreleased Changes

### Potential breaking changes

This release contains a potential breaking change to the `flush` method signature in the `IChannelControls` interface. The parameter name has been changed from `async` to `isAsync` to avoid potential conflicts with the `async` keyword.

**Interface change:**
```typescript
// Before:
flush(async: boolean = true, callBack?: (flushComplete?: boolean) => void): void | IPromise<boolean>;

// After:
flush(isAsync: boolean = true, callBack?: (flushComplete?: boolean) => void, sendReason?: SendRequestReason): boolean | void | IPromise<boolean>;
```

**This is only a breaking change if you rely on named parameters.** If you have custom channels or plugins that implement the `IChannelControls` interface directly and rely on passing named parameters, you will need to update the parameter name from `async` to `isAsync` in your implementation.

### Changelog

- #2628 Fix flush method root cause - handle async callbacks in _doSend with proper error handling
- **Potential breaking change**: Renamed `flush` method parameter from `async` to `isAsync` in `IChannelControls` interface to avoid potential keyword conflicts (only affects code that relies on named parameters)
- Fixed return type of `flush` method to properly include `boolean` when callbacks complete synchronously
- Fixed root cause where `_doSend()` couldn't handle asynchronous callbacks from `preparePayload()` when compression is enabled
- `await applicationInsights.flush()` now works correctly with compression enabled
- Added proper error handling and promise rejection propagation through async callback chains
- Improved handling of both synchronous and asynchronous callback execution patterns
- No polling overhead - uses direct callback invocation for better performance

## 3.3.9 (June 25th, 2025)

This release contains an important fix for a change introduced in v3.3.7 that caused the `autoCaptureHandler` to incorrectly evaluate elements within `trackElementsType`, resulting in some click events not being auto-captured. See more details [here](https://github.com/microsoft/ApplicationInsights-JS/issues/2589).
Expand Down
10 changes: 5 additions & 5 deletions channels/1ds-post-js/src/PostChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,14 +520,14 @@ export class PostChannel extends BaseTelemetryPlugin implements IChannelControls
};


_self.flush = (async: boolean = true, callback?: (flushComplete?: boolean) => void, sendReason?: SendRequestReason): boolean | void | IPromise<boolean> => {
_self.flush = (isAsync: boolean = true, callback?: (flushComplete?: boolean) => void, sendReason?: SendRequestReason): boolean | void | IPromise<boolean> => {
let result: IPromise<boolean>;

if (!_paused) {

sendReason = sendReason || SendRequestReason.ManualFlush;

if (async) {
if (isAsync) {

if (!callback) {
result = createPromise<boolean>((resolve) => {
Expand Down Expand Up @@ -1211,17 +1211,17 @@ export class PostChannel extends BaseTelemetryPlugin implements IChannelControls
* you DO NOT pass a callback function then a [IPromise](https://nevware21.github.io/ts-async/typedoc/interfaces/IPromise.html)
* will be returned which will resolve once the flush is complete. The actual implementation of the `IPromise`
* will be a native Promise (if supported) or the default as supplied by [ts-async library](https://github.com/nevware21/ts-async)
* @param async - send data asynchronously when true
* @param isAsync - send data asynchronously when true
* @param callBack - if specified, notify caller when send is complete, the channel should return true to indicate to the caller that it will be called.
* If the caller doesn't return true the caller should assume that it may never be called.
* @param sendReason - specify the reason that you are calling "flush" defaults to ManualFlush (1) if not specified
* @returns - If a callback is provided `true` to indicate that callback will be called after the flush is complete otherwise the caller
* should assume that any provided callback will never be called, Nothing or if occurring asynchronously a
* [IPromise](https://nevware21.github.io/ts-async/typedoc/interfaces/IPromise.html) which will be resolved once the unload is complete,
* the [IPromise](https://nevware21.github.io/ts-async/typedoc/interfaces/IPromise.html) will only be returned when no callback is provided
* and async is true.
* and isAsync is true.
*/
public flush(async: boolean = true, callBack?: (flushComplete?: boolean) => void, sendReason?: SendRequestReason): boolean | void | IPromise<boolean> {
public flush(isAsync: boolean = true, callBack?: (flushComplete?: boolean) => void, sendReason?: SendRequestReason): boolean | void | IPromise<boolean> {
// @DynamicProtoStub - DO NOT add any code as this will be removed during packaging
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ITelemetryItem, AppInsightsCore, ITelemetryPlugin, DiagnosticLogger, No
import { ArraySendBuffer, SessionStorageSendBuffer } from "../../../src/SendBuffer";
import { IInternalStorageItem, ISenderConfig } from "../../../src/Interfaces";
import { createAsyncResolvedPromise } from "@nevware21/ts-async";
import { isPromiseLike, isUndefined } from "@nevware21/ts-utils";
import { SinonSpy } from 'sinon';


Expand Down Expand Up @@ -4173,6 +4174,181 @@ export class SenderTests extends AITestClass {
QUnit.assert.equal(1024, appInsightsEnvelope.tags["ai.operation.name"].length, "The ai.operation.name should have been truncated to the maximum");
}
});

this.testCase({
name: "flush method handles synchronous callback execution",
useFakeTimers: true,
test: () => {
let core = new AppInsightsCore();
this._sender.initialize({
instrumentationKey: 'abc',
endpointUrl: 'https://example.com',
isBeaconApiDisabled: true
}, core, []);
this.onDone(() => {
this._sender.teardown();
});

const telemetryItem: ITelemetryItem = {
name: 'test item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};

// Add some telemetry to flush
this._sender.processTelemetry(telemetryItem);

// Test sync flush with callback
let callbackCalled = false;
let callbackResult: boolean;
const result = this._sender.flush(false, (success) => {
callbackCalled = true;
callbackResult = success;
});

QUnit.assert.equal(typeof result, 'boolean', "flush should return boolean when callback provided");
QUnit.assert.equal(result, true, "flush should return true when callback will be called");
QUnit.assert.equal(callbackCalled, true, "callback should be called synchronously");
QUnit.assert.equal(callbackResult, true, "callback should receive success=true");
}
});

this.testCase({
name: "flush method handles asynchronous callback execution without callback",
useFakeTimers: true,
test: () => {
let core = new AppInsightsCore();
this._sender.initialize({
instrumentationKey: 'abc',
endpointUrl: 'https://example.com',
isBeaconApiDisabled: true
}, core, []);
this.onDone(() => {
this._sender.teardown();
});

const telemetryItem: ITelemetryItem = {
name: 'test item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};

// Add some telemetry to flush
this._sender.processTelemetry(telemetryItem);

// Test async flush without callback - should return promise-like
const result = this._sender.flush(true);

// Check if result is promise-like (has then method)
QUnit.assert.ok(isPromiseLike(result), "flush should return promise-like object when async=true and no callback");
}
});

this.testCase({
name: "flush method handles asynchronous callback execution with callback",
useFakeTimers: true,
test: () => {
let core = new AppInsightsCore();
this._sender.initialize({
instrumentationKey: 'abc',
endpointUrl: 'https://example.com',
isBeaconApiDisabled: true
}, core, []);
this.onDone(() => {
this._sender.teardown();
});

const telemetryItem: ITelemetryItem = {
name: 'test item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};

// Add some telemetry to flush
this._sender.processTelemetry(telemetryItem);

// Test async flush with callback
let callbackCalled = false;
let callbackResult: boolean;
const result = this._sender.flush(true, (success) => {
callbackCalled = true;
callbackResult = success;
});

QUnit.assert.equal(typeof result, 'boolean', "flush should return boolean when callback provided");
QUnit.assert.equal(result, true, "flush should return true when callback will be called");
QUnit.assert.equal(callbackCalled, true, "callback should be called synchronously even when async=true");
QUnit.assert.equal(callbackResult, true, "callback should receive success=true");
}
});

this.testCase({
name: "flush method returns correct boolean result for sync operation",
useFakeTimers: true,
test: () => {
let core = new AppInsightsCore();
this._sender.initialize({
instrumentationKey: 'abc',
endpointUrl: 'https://example.com',
isBeaconApiDisabled: true
}, core, []);
this.onDone(() => {
this._sender.teardown();
});

const telemetryItem: ITelemetryItem = {
name: 'test item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};

// Add some telemetry to flush
this._sender.processTelemetry(telemetryItem);

// Test sync flush without callback - should return undefined/void
const result = this._sender.flush(false);

QUnit.assert.ok(isUndefined(result), "flush should return undefined when sync=true and no callback");
}
});

this.testCase({
name: "flush method handles paused state correctly",
useFakeTimers: true,
test: () => {
let core = new AppInsightsCore();
this._sender.initialize({
instrumentationKey: 'abc',
endpointUrl: 'https://example.com',
isBeaconApiDisabled: true
}, core, []);
this.onDone(() => {
this._sender.teardown();
});

const telemetryItem: ITelemetryItem = {
name: 'test item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};

// Add some telemetry to flush
this._sender.processTelemetry(telemetryItem);

// Pause the sender
this._sender.pause();

// Test flush when paused - should return undefined
const result = this._sender.flush(true);

QUnit.assert.ok(isUndefined(result), "flush should return undefined when sender is paused");
}
});
}
}

Expand Down
Loading