Skip to content

Commit

Permalink
Beta Part 4: Part of Mega Dynamic Load/Unload support (#1781)
Browse files Browse the repository at this point in the history
* Beta Part 4: Part of Mega Dynamic Load/Unload support
- Fix function typing issues
- Update Analytics Extension to start supporting teardown / unload (more tests required)
- Adds namespace option to instrumentation hooks (for debugging teardown issues)
- Update AITest Class to log and optionally assert events and hooks that have not been removed
- Add Update callback when plugins are added / removed (will be extended for config updates)
- Some minor minification improvements

* Update comments

* Add missing enum definition

* Update Sender tests
  • Loading branch information
MSNev authored Mar 14, 2022
1 parent 4c4b428 commit 4936a52
Show file tree
Hide file tree
Showing 33 changed files with 1,271 additions and 610 deletions.
2 changes: 1 addition & 1 deletion AISKU/Tests/Unit/src/AISKUSize.Tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AITestClass, Assert } from "@microsoft/ai-test-framework";
import * as pako from "pako";

export class AISKUSizeCheck extends AITestClass {
private readonly MAX_DEFLATE_SIZE = 43;
private readonly MAX_DEFLATE_SIZE = 44;
private readonly rawFilePath = "../dist/applicationinsights-web.min.js";
private readonly prodFilePath = "../browser/ai.2.min.js";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { AITestClass } from "@microsoft/ai-test-framework";
import { Sender } from "../../../src/Sender";
import { Offline } from '../../../src/Offline';
import { createOfflineListener, IOfflineListener } from '../../../src/Offline';
import { EnvelopeCreator } from '../../../src/EnvelopeCreator';
import { Exception, CtxTagKeys, Util } from "@microsoft/applicationinsights-common";
import { ITelemetryItem, AppInsightsCore, ITelemetryPlugin, DiagnosticLogger, NotificationManager, SendRequestReason, _InternalMessageId, LoggingSeverity, getGlobalInst, getGlobal } from "@microsoft/applicationinsights-core-js";

export class SenderTests extends AITestClass {
private _sender: Sender;
private _instrumentationKey = 'iKey';
private _offline: IOfflineListener;

public testInitialize() {
this._sender = new Sender();
this._sender.initialize({ instrumentationKey: this._instrumentationKey }, new AppInsightsCore(), []);
this._offline = createOfflineListener("SenderTests");
}

public testCleanup() {
if (this._offline) {
this._offline.unload();
}
if (this._sender) {
this._sender.teardown();
}
this._sender = null;
}

Expand Down Expand Up @@ -1021,9 +1029,8 @@ export class SenderTests extends AITestClass {
this.testCase({
name: 'Offline watcher is listening to events',
test: () => {
QUnit.assert.ok(Offline.isListening, 'Offline is listening');
QUnit.assert.equal(true, Offline.isOnline(), 'Offline reports online status');
QUnit.assert.equal(false, Offline.isOffline(), 'Offline reports offline status');
QUnit.assert.ok(this._offline.isListening(), 'Offline is listening');
QUnit.assert.equal(true, this._offline.isOnline(), 'Offline reports online status');
}
});

Expand All @@ -1036,22 +1043,22 @@ export class SenderTests extends AITestClass {
const onlineEvent = new Event('online');

// Verify precondition
QUnit.assert.ok(Offline.isListening);
QUnit.assert.ok(Offline.isOnline());
QUnit.assert.ok(this._offline.isListening());
QUnit.assert.ok(this._offline.isOnline());

// Act - Go offline
window.dispatchEvent(offlineEvent);
this.clock.tick(1);

// Verify offline
QUnit.assert.ok(Offline.isOffline());
QUnit.assert.ok(!this._offline.isOnline());

// Act - Go online
window.dispatchEvent(onlineEvent);
this.clock.tick(1);

// Verify online
QUnit.assert.ok(Offline.isOnline());
QUnit.assert.ok(this._offline.isOnline());
}
});

Expand Down
164 changes: 88 additions & 76 deletions channels/applicationinsights-channel-js/src/Offline.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,104 @@
import { getWindow, getDocument, getNavigator, isUndefined, isNullOrUndefined, attachEvent } from "@microsoft/applicationinsights-core-js";
import dynamicProto from "@microsoft/dynamicproto-js";
import { getWindow, getDocument, getNavigator, isUndefined, isNullOrUndefined, createUniqueNamespace, mergeEvtNamespace, eventOn, eventOff } from "@microsoft/applicationinsights-core-js";

export interface IOfflineListener {
isOnline: () => boolean;
isListening: () => boolean;
unload: () => void;
}

function _disableEvents(target: any, evtNamespace: string | string[]) {
eventOff(target, null, null, evtNamespace);
}

/**
* @description Monitors browser for offline events
* @export default - Offline: Static instance of OfflineListener
* @class OfflineListener
* Create a new OfflineListener instance to monitor browser online / offline events
* @param parentEvtNamespace - The parent event namespace to append to any specific events for this instance
*/
export class OfflineListener {
public static Offline = new OfflineListener;
public isListening: boolean;

constructor() {
let _window = getWindow();
let _document = getDocument();
let isListening = false;
let _onlineStatus: boolean = true;

dynamicProto(OfflineListener, this, (_self) => {
try {
if (_window) {
if (attachEvent(_window, "online", _setOnline)) {
attachEvent(_window, "offline", _setOffline);
isListening = true;
}
}

if (_document) {
// Also attach to the document.body or document
let target:any = _document.body || _document;

if (!isUndefined(target.ononline)) {
target.ononline = _setOnline;
target.onoffline = _setOffline;
isListening = true;

}
}
export function createOfflineListener(parentEvtNamespace?: string | string[]): IOfflineListener {
let _document = getDocument();
var _navigator = getNavigator(); // Gets the window.navigator or workerNavigator depending on the global
let _isListening: boolean = false;
let _onlineStatus: boolean = true;
let _evtNamespace = mergeEvtNamespace(createUniqueNamespace("OfflineListener"), parentEvtNamespace);

if (isListening) {
// We are listening to events so lets set the current status rather than assuming we are online #1538
var _navigator = getNavigator(); // Gets the window.navigator or workerNavigator depending on the global
if (_navigator && !isNullOrUndefined(_navigator.onLine)) { // navigator.onLine is undefined in react-native
_onlineStatus = _navigator.onLine;
}
try {
if (_enableEvents(getWindow())) {
_isListening = true;
}

if (_document) {
// Also attach to the document.body or document
let target:any = _document.body || _document;

if (target.ononline) {
if (_enableEvents(target)) {
_isListening = true;
}
} catch (e) {

// this makes react-native less angry
isListening = false;
}

_self.isListening = isListening;

_self.isOnline = (): boolean => {
let result = true;
var _navigator = getNavigator();
if (isListening) {
result = _onlineStatus
} else if (_navigator && !isNullOrUndefined(_navigator.onLine)) { // navigator.onLine is undefined in react-native
result = _navigator.onLine;
}
}

return result;
};

_self.isOffline = (): boolean => {
return !_self.isOnline();
};

function _setOnline() {
_onlineStatus = true;
if (_isListening) {
// We are listening to events so lets set the current status rather than assuming we are online #1538
if (_navigator && !isNullOrUndefined(_navigator.onLine)) { // navigator.onLine is undefined in react-native
_onlineStatus = _navigator.onLine;
}
}
} catch (e) {
// this makes react-native less angry
_isListening = false;
}

function _setOffline() {
_onlineStatus = false;
function _enableEvents(target: any): boolean {
let enabled = false;
if (target) {
enabled = eventOn(target, "online", _setOnline, _evtNamespace);
if (enabled) {
eventOn(target, "offline", _setOffline, _evtNamespace);
}
});
}

return enabled;
}

public isOnline(): boolean {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
return false;
function _setOnline() {
_onlineStatus = true;
}

public isOffline(): boolean {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
return false;
function _setOffline() {
_onlineStatus = false;
}
}

export const Offline = OfflineListener.Offline;
function _isOnline(): boolean {
let result = true;
if (_isListening) {
result = _onlineStatus
} else if (_navigator && !isNullOrUndefined(_navigator.onLine)) { // navigator.onLine is undefined in react-native
result = _navigator.onLine;
}

return result;
}

function _unload() {
let win = getWindow();
if (win && _isListening) {
_disableEvents(win, _evtNamespace);

if (_document) {
// Also attach to the document.body or document
let target:any = _document.body || _document;
if (!isUndefined(target.ononline)) {
_disableEvents(target, _evtNamespace);
}
}

_isListening = false;
}
}

return {
isOnline: _isOnline,
isListening: () => _isListening,
unload: _unload
};
}
Loading

0 comments on commit 4936a52

Please sign in to comment.