- Add ability to tag Dynamic Config properties as referenced to support updating extensionConfig in-place with a new object instance - Add IUnloadHookContainer to allow passing extension level unload hook container to sub-components
This commit is contained in:
Родитель
09dec1c433
Коммит
af918812b6
|
@ -1753,9 +1753,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001448",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz",
|
||||
"integrity": "sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA==",
|
||||
"version": "1.0.30001449",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001449.tgz",
|
||||
"integrity": "sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -2225,9 +2225,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-security": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.6.0.tgz",
|
||||
"integrity": "sha512-SGvyejbhW/dziRbzOroKX5bj8z/qtBOw7Q95C9CBbJQqBtFB2o4OxSM3MCO2u9noPp7B6DDaFGtXTx8ImPiO/A==",
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.7.0.tgz",
|
||||
"integrity": "sha512-+ahcCh7M5w7fdFaNccaChBGq8nd3Wa+XvGJS+hY74kvrMhG4EuLbljRIjilOqh1iDMW/EckB1oOWmiVIYlVACQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-regex": "^2.1.1"
|
||||
|
@ -3157,9 +3157,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
|
||||
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
|
@ -5145,9 +5145,9 @@
|
|||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
|
||||
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
|
||||
},
|
||||
"node_modules/tsutils": {
|
||||
"version": "3.21.0",
|
||||
|
@ -6856,9 +6856,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001448",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz",
|
||||
"integrity": "sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA=="
|
||||
"version": "1.0.30001449",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001449.tgz",
|
||||
"integrity": "sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw=="
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
|
@ -7226,9 +7226,9 @@
|
|||
}
|
||||
},
|
||||
"eslint-plugin-security": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.6.0.tgz",
|
||||
"integrity": "sha512-SGvyejbhW/dziRbzOroKX5bj8z/qtBOw7Q95C9CBbJQqBtFB2o4OxSM3MCO2u9noPp7B6DDaFGtXTx8ImPiO/A==",
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.7.0.tgz",
|
||||
"integrity": "sha512-+ahcCh7M5w7fdFaNccaChBGq8nd3Wa+XvGJS+hY74kvrMhG4EuLbljRIjilOqh1iDMW/EckB1oOWmiVIYlVACQ==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"safe-regex": "^2.1.1"
|
||||
|
@ -7926,9 +7926,9 @@
|
|||
"integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA=="
|
||||
},
|
||||
"http-cache-semantics": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
|
||||
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "2.0.0",
|
||||
|
@ -9427,9 +9427,9 @@
|
|||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
|
||||
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
|
||||
},
|
||||
"tsutils": {
|
||||
"version": "3.21.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Assert, AITestClass } from "@microsoft/ai-test-framework";
|
||||
import { SinonStub } from 'sinon';
|
||||
import { AppInsightsCore, DiagnosticLogger, createCookieMgr, newId, dateNow } from "@microsoft/applicationinsights-core-js";
|
||||
import { AppInsightsCore, DiagnosticLogger, createCookieMgr, newId, dateNow, createDynamicConfig } from "@microsoft/applicationinsights-core-js";
|
||||
import PropertiesPlugin from "../../../src/PropertiesPlugin";
|
||||
import { _SessionManager } from "../../../src/Context/Session";
|
||||
|
||||
|
@ -64,13 +64,12 @@ export class SessionManagerTests extends AITestClass {
|
|||
test: () => {
|
||||
|
||||
var sessionPrefix = newId();
|
||||
var config = {
|
||||
var config = createDynamicConfig({
|
||||
namePrefix: sessionPrefix,
|
||||
sessionExpirationMs: undefined,
|
||||
sessionRenewalMs: undefined,
|
||||
cookieDomain: undefined
|
||||
|
||||
};
|
||||
}).cfg;
|
||||
// Setup
|
||||
let cookie = "";
|
||||
const cookieStub: SinonStub = this.sandbox.stub(this.core.getCookieMgr(), 'set').callsFake((cookieName, value, maxAge, domain, path) => {
|
||||
|
@ -92,14 +91,15 @@ export class SessionManagerTests extends AITestClass {
|
|||
test: () => {
|
||||
|
||||
var sessionPrefix = newId();
|
||||
var config = {
|
||||
var config = createDynamicConfig({
|
||||
namePrefix: sessionPrefix,
|
||||
sessionCookiePostfix: "testSessionCookieNamePostfix",
|
||||
sessionExpirationMs: undefined,
|
||||
sessionRenewalMs: undefined,
|
||||
cookieDomain: undefined
|
||||
|
||||
};
|
||||
}).cfg;
|
||||
|
||||
// Setup
|
||||
let cookie = "";
|
||||
const cookieStub: SinonStub = this.sandbox.stub(this.core.getCookieMgr(), 'set').callsFake((cookieName, value, maxAge, domain, path) => {
|
||||
|
@ -121,12 +121,12 @@ export class SessionManagerTests extends AITestClass {
|
|||
useFakeTimers: true,
|
||||
test: () => {
|
||||
var sessionPrefix = newId();
|
||||
var config = {
|
||||
var config = createDynamicConfig({
|
||||
namePrefix: sessionPrefix,
|
||||
sessionExpirationMs: 30 * 60 * 1000,
|
||||
sessionRenewalMs: 24 * 60 * 60 * 1000,
|
||||
cookieDomain: undefined
|
||||
};
|
||||
}).cfg;
|
||||
|
||||
// Simulate 100ms as when zero the cookie values are deemed to be invalid
|
||||
this.clock.tick(100);
|
||||
|
@ -152,12 +152,12 @@ export class SessionManagerTests extends AITestClass {
|
|||
useFakeTimers: true,
|
||||
test: () => {
|
||||
var sessionPrefix = newId();
|
||||
var config = {
|
||||
var config = createDynamicConfig({
|
||||
namePrefix: sessionPrefix,
|
||||
sessionExpirationMs: 5000,
|
||||
sessionRenewalMs: 24 * 60 * 60 * 1000,
|
||||
cookieDomain: undefined
|
||||
};
|
||||
}).cfg;
|
||||
|
||||
// Simulate 100ms as when zero the cookie values are deemed to be invalid
|
||||
this.clock.tick(100);
|
||||
|
@ -211,12 +211,12 @@ export class SessionManagerTests extends AITestClass {
|
|||
useFakeTimers: true,
|
||||
test: () => {
|
||||
var sessionPrefix = newId();
|
||||
var config = {
|
||||
var config = createDynamicConfig({
|
||||
namePrefix: sessionPrefix,
|
||||
sessionExpirationMs: 86400000,
|
||||
sessionRenewalMs: 5000,
|
||||
cookieDomain: undefined
|
||||
};
|
||||
}).cfg;
|
||||
|
||||
// Simulate 100ms as when zero the cookie values are deemed to be invalid
|
||||
this.clock.tick(100);
|
||||
|
@ -269,12 +269,12 @@ export class SessionManagerTests extends AITestClass {
|
|||
useFakeTimers: true,
|
||||
test: () => {
|
||||
var sessionPrefix = newId();
|
||||
var config = {
|
||||
var config = createDynamicConfig({
|
||||
namePrefix: sessionPrefix,
|
||||
sessionExpirationMs: 30 * 60 * 1000,
|
||||
sessionRenewalMs: 24 * 60 * 60 * 1000,
|
||||
cookieDomain: undefined
|
||||
};
|
||||
}).cfg;
|
||||
|
||||
// Simulate 100ms as when zero the cookie values are deemed to be invalid
|
||||
this.clock.tick(100);
|
||||
|
@ -315,12 +315,12 @@ export class SessionManagerTests extends AITestClass {
|
|||
useFakeTimers: true,
|
||||
test: () => {
|
||||
var sessionPrefix = newId();
|
||||
var config = {
|
||||
var config = createDynamicConfig({
|
||||
namePrefix: sessionPrefix,
|
||||
sessionExpirationMs: 5000,
|
||||
sessionRenewalMs: 24 * 60 * 60 * 1000,
|
||||
cookieDomain: undefined
|
||||
};
|
||||
}).cfg;
|
||||
|
||||
// Simulate 100ms as when zero the cookie values are deemed to be invalid
|
||||
this.clock.tick(100);
|
||||
|
|
|
@ -130,7 +130,6 @@ export class PropertiesTests extends AITestClass {
|
|||
let extConfig = core.config.extensionConfig[id];
|
||||
|
||||
let exceptedDefaultConfig = {
|
||||
instrumentationKey: "instrumentation_key",
|
||||
accountId: null,
|
||||
sessionRenewalMs: 30 * 60 * 1000,
|
||||
samplingPercentage: 100,
|
||||
|
@ -166,7 +165,6 @@ export class PropertiesTests extends AITestClass {
|
|||
const channel = new TestChannelPlugin();
|
||||
const properties = new PropertiesPlugin();
|
||||
let exceptedDefaultConfig = {
|
||||
instrumentationKey: "key1",
|
||||
accountId: "id1",
|
||||
sessionRenewalMs: 30 * 60 * 101,
|
||||
samplingPercentage: 90,
|
||||
|
@ -280,7 +278,6 @@ export class PropertiesTests extends AITestClass {
|
|||
|
||||
// change config
|
||||
let newConfig = {
|
||||
instrumentationKey: "key2",
|
||||
accountId: "id2",
|
||||
sessionRenewalMs: 30 * 60 * 102,
|
||||
samplingPercentage: 90,
|
||||
|
@ -375,8 +372,229 @@ export class PropertiesTests extends AITestClass {
|
|||
core.getCookieMgr().del("ai_session" + "postfix2");
|
||||
}
|
||||
});
|
||||
|
||||
this.testCase({
|
||||
name: "Properties Configuration: config can be set from root dynamically and updated as a new single object",
|
||||
useFakeTimers: true,
|
||||
test: () => {
|
||||
const core = new AppInsightsCore();
|
||||
core.setCookieMgr(createCookieMgr({
|
||||
cookieCfg: {
|
||||
setCookie: (name: string, value: string) => this._setCookie(name, value),
|
||||
getCookie: (name: string) => this._getCookie(name),
|
||||
delCookie: (name: string) => this._delCookie(name)
|
||||
}
|
||||
}, core.logger))
|
||||
const channel = new TestChannelPlugin();
|
||||
const properties = new PropertiesPlugin();
|
||||
let exceptedDefaultConfig = {
|
||||
accountId: "id1",
|
||||
sessionRenewalMs: 30 * 60 * 101,
|
||||
samplingPercentage: 90,
|
||||
sessionExpirationMs: 24 * 60 * 60 * 101,
|
||||
cookieDomain: "domain1",
|
||||
sdkExtension: "ext1",
|
||||
isBrowserLinkTrackingEnabled: false,
|
||||
appId: "id1",
|
||||
getSessionId: "session1",
|
||||
namePrefix: "prefix1",
|
||||
sessionCookiePostfix: "postfix1",
|
||||
userCookiePostfix: "usercookie1",
|
||||
idLength: 26,
|
||||
getNewId: (idLength?: number) => {
|
||||
return "" + (idLength || 0);
|
||||
}
|
||||
} as IPropertiesConfig;
|
||||
let id = properties.identifier;
|
||||
const config = {
|
||||
instrumentationKey: "instrumentation_key",
|
||||
extensionConfig: {
|
||||
[id]: exceptedDefaultConfig
|
||||
}
|
||||
};
|
||||
this.onDone(() => {
|
||||
core.unload(false);
|
||||
});
|
||||
let appBuild = "build";
|
||||
let deviceId = "newDeviceId";
|
||||
let locId = "locId";
|
||||
let parentId = "parentId";
|
||||
let seId = "sessionId";
|
||||
let cookie = "";
|
||||
let cookieValue = ""
|
||||
const cookieStub: SinonStub = this.sandbox.stub(core.getCookieMgr(), "set").callsFake((cookieName, value, maxAge, domain, path) => {
|
||||
cookie = cookieName;
|
||||
cookieValue = value;
|
||||
});
|
||||
|
||||
// Initialize
|
||||
core.initialize(config, [channel, properties]);
|
||||
let extConfig = properties["_extConfig"];
|
||||
Assert.deepEqual(extConfig, exceptedDefaultConfig, "intial config is set");
|
||||
|
||||
// check inital context
|
||||
let propCtx = properties.context;
|
||||
let appId = propCtx.appId();
|
||||
Assert.equal(appId, null, "appId should be null");
|
||||
|
||||
let application = propCtx.application;
|
||||
Assert.equal(application.build, null, "application build should be null by default");
|
||||
|
||||
let device = propCtx.device;
|
||||
Assert.equal(device.id, "browser", "device id should not be null");
|
||||
|
||||
let location = propCtx.location;
|
||||
Assert.ok(location, "location should not be null");
|
||||
|
||||
let trace = propCtx.telemetryTrace;
|
||||
let traceId = trace.traceID;
|
||||
Assert.ok(trace, "trace should not be null");
|
||||
Assert.ok(trace.name, "trace name should not be null");
|
||||
Assert.ok(traceId, "trace id should not be null");
|
||||
Assert.ok(!trace.parentID, "trace parent should be null");
|
||||
|
||||
let user = propCtx.user;
|
||||
Assert.deepEqual(user.config, exceptedDefaultConfig, "user config should be updated");
|
||||
|
||||
let internalSdkVer = propCtx.internal.sdkVersion;
|
||||
Assert.ok(internalSdkVer.indexOf("ext1") > -1, "sdk ext prefix should be used");
|
||||
|
||||
let session = propCtx.session;
|
||||
Assert.ok(session, "session should not be null");
|
||||
|
||||
let sessionMgr = propCtx.sessionManager;
|
||||
Assert.ok(sessionMgr.automaticSession, "session mgr should not be null");
|
||||
|
||||
let os = propCtx.os;
|
||||
Assert.equal(os, null, "os should be null");
|
||||
|
||||
let web = propCtx.web;
|
||||
Assert.equal(web, null, "web should be null");
|
||||
|
||||
let sessionId = propCtx.getSessionId();
|
||||
Assert.equal(sessionId, null, "session Id should be null");
|
||||
|
||||
// change properities here to make sure we won't overwrite them after config change
|
||||
device.id = deviceId;
|
||||
location.ip = locId;
|
||||
trace.parentID = parentId;
|
||||
application.build = appBuild;
|
||||
session.id = seId;
|
||||
sessionMgr.automaticSession.id = seId;
|
||||
Assert.equal(propCtx.getSessionId(), seId, "session Id should be updated test1");
|
||||
let sessionPrefix = "ai_session" + "postfix1";
|
||||
sessionMgr.backup();
|
||||
let sessionStorage = utlGetLocalStorage(core.logger, sessionPrefix);
|
||||
Assert.ok(sessionStorage.indexOf(seId) > -1, "sessionStorage should be set based on session id test1");
|
||||
|
||||
sessionMgr.update();
|
||||
Assert.ok(cookieStub.called, "cookie set test1");
|
||||
Assert.equal(sessionPrefix, cookie, "cookie name is set test1");
|
||||
Assert.ok(cookieValue.indexOf(seId) > -1, "cookie value is set test1");
|
||||
this.clock.tick(20);
|
||||
sessionMgr.update();
|
||||
Assert.equal(sessionMgr.automaticSession.id, seId, "session id should be same test1");
|
||||
this.clock.tick(24 * 60 * 60 * 101 - 20);
|
||||
sessionMgr.update();
|
||||
Assert.equal(sessionMgr.automaticSession.id, "26", "session id should be renewed test1");
|
||||
Assert.ok(cookieValue.indexOf("26|") > -1,"cookie value is reset test1");
|
||||
|
||||
// change config
|
||||
let newConfig = {
|
||||
accountId: "id2",
|
||||
sessionRenewalMs: 30 * 60 * 102,
|
||||
samplingPercentage: 90,
|
||||
sessionExpirationMs: 24 * 60 * 60 * 102,
|
||||
cookieDomain: "domain2",
|
||||
sdkExtension: "ext2",
|
||||
isBrowserLinkTrackingEnabled: true,
|
||||
appId: "id2",
|
||||
getSessionId: "session2",
|
||||
namePrefix: "prefix2",
|
||||
sessionCookiePostfix: "postfix2",
|
||||
userCookiePostfix: "usercookie2",
|
||||
idLength: 26,
|
||||
getNewId: (idLength?: number) => {
|
||||
return "" + (idLength || 0) + 1;
|
||||
}
|
||||
} as IPropertiesConfig;
|
||||
|
||||
core.config.extensionConfig = core.config.extensionConfig? core.config.extensionConfig : {};
|
||||
core.config.extensionConfig[id] = newConfig;
|
||||
this.clock.tick(1);
|
||||
|
||||
// properties that should be updated
|
||||
extConfig = properties["_extConfig"];
|
||||
Assert.deepEqual(extConfig, newConfig, "extConfig should be updated");
|
||||
propCtx = properties.context;
|
||||
|
||||
user = propCtx.user;
|
||||
Assert.deepEqual(user.config, newConfig, "user config should be updated");
|
||||
|
||||
internalSdkVer = propCtx.internal.sdkVersion;
|
||||
Assert.ok(internalSdkVer.indexOf("ext2") > -1, "sdk ext prefix should be used and updated");
|
||||
|
||||
sessionMgr = propCtx.sessionManager;
|
||||
Assert.ok(sessionMgr.automaticSession, "session mgr should not be null");
|
||||
sessionPrefix = "ai_session" + "postfix2";
|
||||
sessionMgr.backup();
|
||||
sessionStorage = utlGetLocalStorage(core.logger, sessionPrefix);
|
||||
Assert.ok(sessionStorage.indexOf("26") > -1, "sessionStorage should be updated based on session id test2");
|
||||
Assert.ok(cookieValue.indexOf("26|") > -1, "cookie should not be updated test2");
|
||||
Assert.equal(sessionMgr.automaticSession.id, "26", "session id should not be renewed test2");
|
||||
|
||||
this.clock.tick(60000);
|
||||
sessionMgr.update();
|
||||
Assert.ok(cookieStub.called, "cookie set test3");
|
||||
Assert.equal(sessionPrefix, cookie, "cookie name is set test3");
|
||||
Assert.equal(sessionMgr.automaticSession.id, "26", "session id should not be renewed test3");
|
||||
Assert.ok(cookieValue.indexOf("26|") > -1, "cookie value should use previous session id test3");
|
||||
Assert.equal(sessionMgr.automaticSession.id, "26", "session id should not be renewed test4");
|
||||
this.clock.tick(24 * 60 * 60 * 102);
|
||||
sessionMgr.update();
|
||||
Assert.ok(cookieStub.called, "cookie set test5");
|
||||
Assert.equal(sessionPrefix, cookie, "cookie name is set test5");
|
||||
Assert.ok(cookieValue.indexOf("261|") > -1, "cookie value is renewed test6");
|
||||
Assert.equal(sessionMgr.automaticSession.id, "261", "session id should be renewed test6");
|
||||
|
||||
//properties that should not be updated
|
||||
appId = propCtx.appId();
|
||||
Assert.equal(appId, null, "appId should be null");
|
||||
|
||||
application = propCtx.application;
|
||||
Assert.equal(application.build, appBuild, "application build should not be updated");
|
||||
|
||||
device = propCtx.device;
|
||||
Assert.equal(device.id, deviceId, "device id should not be updated");
|
||||
|
||||
location = propCtx.location;
|
||||
Assert.equal(location.ip, locId, "location should not be updated");
|
||||
|
||||
trace = propCtx.telemetryTrace;
|
||||
Assert.ok(trace, "trace should not be null");
|
||||
Assert.ok(trace.name, "trace name should not be null");
|
||||
Assert.equal(trace.traceID, traceId, "trace id should be same with previous one");
|
||||
Assert.equal(trace.parentID, parentId, "trace parent should not be updated");
|
||||
|
||||
session = propCtx.session;
|
||||
Assert.equal(session.id, seId, "session should not be updated");
|
||||
|
||||
os = propCtx.os;
|
||||
Assert.equal(os, null,"os should not be updated");
|
||||
|
||||
sessionId = propCtx.getSessionId();
|
||||
Assert.equal(sessionId, seId, "session Id should not be updated");
|
||||
|
||||
if (utlCanUseLocalStorage()) {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
core.getCookieMgr().del("ai_session" + "postfix1");
|
||||
core.getCookieMgr().del("ai_session" + "postfix2");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private addDeviceTests() {
|
||||
this.testCase({
|
||||
name: 'Device: device context adds Browser field to ITelemetryItem',
|
||||
|
@ -962,7 +1180,6 @@ export class PropertiesTests extends AITestClass {
|
|||
|
||||
private getTelemetryConfig(): IPropertiesConfig {
|
||||
return {
|
||||
instrumentationKey: "",
|
||||
accountId: "",
|
||||
sessionRenewalMs: 1000,
|
||||
samplingPercentage: 0,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { IInternal } from "@microsoft/applicationinsights-common";
|
||||
import { onConfigChange } from "@microsoft/applicationinsights-core-js";
|
||||
import { IUnloadHookContainer, onConfigChange } from "@microsoft/applicationinsights-core-js";
|
||||
import { IPropertiesConfig } from "../Interfaces/IPropertiesConfig";
|
||||
|
||||
const Version = "2.8.5";
|
||||
|
@ -37,11 +37,13 @@ export class Internal implements IInternal {
|
|||
/**
|
||||
* Constructs a new instance of the internal telemetry data class.
|
||||
*/
|
||||
constructor(config: IPropertiesConfig) {
|
||||
constructor(config: IPropertiesConfig, unloadHookContainer?: IUnloadHookContainer) {
|
||||
|
||||
onConfigChange((config), () => {
|
||||
let unloadHook = onConfigChange((config), () => {
|
||||
let prefix = config.sdkExtension;
|
||||
this.sdkVersion = (prefix ? prefix + "_" : "") + "javascript:" + Version;
|
||||
});
|
||||
|
||||
unloadHookContainer && unloadHookContainer.add(unloadHook);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
import dynamicProto from "@microsoft/dynamicproto-js";
|
||||
import { ISession, utlCanUseLocalStorage, utlGetLocalStorage, utlSetLocalStorage } from "@microsoft/applicationinsights-common";
|
||||
import {
|
||||
IAppInsightsCore, ICookieMgr, IDiagnosticLogger, _eInternalMessageId, _throwInternal, dateNow, dumpObj, eLoggingSeverity,
|
||||
getExceptionName, isFunction, newId, safeGetCookieMgr, safeGetLogger
|
||||
IAppInsightsCore, ICookieMgr, IDiagnosticLogger, IUnloadHookContainer, _eInternalMessageId, _throwInternal, dateNow, dumpObj,
|
||||
eLoggingSeverity, getExceptionName, isFunction, newId, onConfigChange, safeGetCookieMgr, safeGetLogger
|
||||
} from "@microsoft/applicationinsights-core-js";
|
||||
|
||||
const SESSION_COOKIE_NAME = "ai_session";
|
||||
|
@ -51,11 +51,14 @@ export class _SessionManager {
|
|||
|
||||
public automaticSession: Session;
|
||||
|
||||
constructor(config: ISessionConfig, core?: IAppInsightsCore) {
|
||||
constructor(config: ISessionConfig, core?: IAppInsightsCore, unloadHookContainer?: IUnloadHookContainer) {
|
||||
let self = this;
|
||||
let _storageNamePrefix: string;
|
||||
let _cookieUpdatedTimestamp: number;
|
||||
let _logger: IDiagnosticLogger = safeGetLogger(core);
|
||||
let _cookieManager: ICookieMgr = safeGetCookieMgr(core);
|
||||
let _sessionExpirationMs: number;
|
||||
let _sessionRenewalMs: number;
|
||||
|
||||
dynamicProto(_SessionManager, self, (_self) => {
|
||||
|
||||
|
@ -63,6 +66,18 @@ export class _SessionManager {
|
|||
config = ({} as any);
|
||||
}
|
||||
|
||||
let unloadHook = onConfigChange(config, (details) => {
|
||||
_sessionExpirationMs = config.sessionExpirationMs || ACQUISITION_SPAN;
|
||||
_sessionRenewalMs = config.sessionRenewalMs || RENEWAL_SPAN;
|
||||
|
||||
// sessionCookiePostfix takes the preference if it is configured, otherwise takes namePrefix if configured.
|
||||
const sessionCookiePostfix = config.sessionCookiePostfix || config.namePrefix || "";
|
||||
|
||||
_storageNamePrefix = SESSION_COOKIE_NAME + sessionCookiePostfix;
|
||||
});
|
||||
|
||||
unloadHookContainer && unloadHookContainer.add(unloadHook);
|
||||
|
||||
_self.automaticSession = new Session();
|
||||
|
||||
_self.update = () => {
|
||||
|
@ -76,12 +91,12 @@ export class _SessionManager {
|
|||
isExpired = !_initializeAutomaticSession(session, nowMs);
|
||||
}
|
||||
|
||||
if (!isExpired && _sessionExpirationMs() > 0) {
|
||||
if (!isExpired && _sessionExpirationMs > 0) {
|
||||
const timeSinceAcqMs = nowMs - session.acquisitionDate;
|
||||
const timeSinceRenewalMs = nowMs - session.renewalDate;
|
||||
isExpired = timeSinceAcqMs < 0 || timeSinceRenewalMs < 0; // expired if the acquisition or last renewal are in the future
|
||||
isExpired = isExpired || timeSinceAcqMs > _sessionExpirationMs(); // expired if the time since acquisition is more than session Expiration
|
||||
isExpired = isExpired || timeSinceRenewalMs > _sessionRenewalMs(); // expired if the time since last renewal is more than renewal period
|
||||
isExpired = isExpired || timeSinceAcqMs > _sessionExpirationMs; // expired if the time since acquisition is more than session Expiration
|
||||
isExpired = isExpired || timeSinceRenewalMs > _sessionRenewalMs; // expired if the time since last renewal is more than renewal period
|
||||
}
|
||||
|
||||
// renew if acquisitionSpan or renewalSpan has elapsed
|
||||
|
@ -106,21 +121,6 @@ export class _SessionManager {
|
|||
_setStorage(session.id, session.acquisitionDate, session.renewalDate);
|
||||
};
|
||||
|
||||
//TODO: move _storageNamePrefix, _sessionExpirationMs _sessionRenewalMs back to local variables after dynamic config fix
|
||||
function _storageNamePrefix(): string {
|
||||
// sessionCookiePostfix takes the preference if it is configured, otherwise takes namePrefix if configured.
|
||||
let sessionCookiePostfix = config.sessionCookiePostfix || config.namePrefix || "";
|
||||
return SESSION_COOKIE_NAME + sessionCookiePostfix;
|
||||
}
|
||||
|
||||
function _sessionExpirationMs(): number {
|
||||
return config.sessionExpirationMs || ACQUISITION_SPAN;
|
||||
}
|
||||
|
||||
function _sessionRenewalMs(): number {
|
||||
return config.sessionRenewalMs || RENEWAL_SPAN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use config.namePrefix + ai_session cookie data or local storage data (when the cookie is unavailable) to
|
||||
* initialize the automatic session.
|
||||
|
@ -128,7 +128,7 @@ export class _SessionManager {
|
|||
*/
|
||||
function _initializeAutomaticSession(session: ISession, now: number): boolean {
|
||||
let isValid = false;
|
||||
const cookieValue = _cookieManager.get(_storageNamePrefix());
|
||||
const cookieValue = _cookieManager.get(_storageNamePrefix);
|
||||
if (cookieValue && isFunction(cookieValue.split)) {
|
||||
isValid = _initializeAutomaticSessionWithData(session, cookieValue);
|
||||
} else {
|
||||
|
@ -136,7 +136,7 @@ export class _SessionManager {
|
|||
// This can happen if the session expired or the user actively deleted the cookie
|
||||
// We only want to recover data if the cookie is missing from expiry. We should respect the user's wishes if the cookie was deleted actively.
|
||||
// The User class handles this for us and deletes our local storage object if the persistent user cookie was removed.
|
||||
const storageValue = utlGetLocalStorage(_logger, _storageNamePrefix());
|
||||
const storageValue = utlGetLocalStorage(_logger, _storageNamePrefix);
|
||||
if (storageValue) {
|
||||
isValid = _initializeAutomaticSessionWithData(session, storageValue);
|
||||
}
|
||||
|
@ -210,11 +210,11 @@ export class _SessionManager {
|
|||
let acq = session.acquisitionDate;
|
||||
session.renewalDate = nowMs;
|
||||
|
||||
let renewalPeriodMs = _sessionRenewalMs();
|
||||
let renewalPeriodMs = _sessionRenewalMs;
|
||||
|
||||
// Set cookie to expire after the session expiry time passes or the session renewal deadline, whichever is sooner
|
||||
// Expiring the cookie will cause the session to expire even if the user isn't on the page
|
||||
const acqTimeLeftMs = (acq +_sessionExpirationMs()) - nowMs;
|
||||
const acqTimeLeftMs = (acq +_sessionExpirationMs) - nowMs;
|
||||
const cookie = [session.id, acq, nowMs];
|
||||
let maxAgeSec = 0;
|
||||
|
||||
|
@ -229,7 +229,7 @@ export class _SessionManager {
|
|||
// if sessionExpirationMs is set to 0, it means the expiry is set to 0 for this session cookie
|
||||
// A cookie with 0 expiry in the session cookie will never expire for that browser session. If the browser is closed the cookie expires.
|
||||
// Depending on the browser, another instance does not inherit this cookie, however, another tab will
|
||||
_cookieManager.set(_storageNamePrefix(), cookie.join("|"), _sessionExpirationMs() > 0 ? maxAgeSec : null, cookieDomain);
|
||||
_cookieManager.set(_storageNamePrefix, cookie.join("|"), _sessionExpirationMs > 0 ? maxAgeSec : null, cookieDomain);
|
||||
_cookieUpdatedTimestamp = nowMs;
|
||||
}
|
||||
|
||||
|
@ -237,7 +237,7 @@ export class _SessionManager {
|
|||
// Keep data in local storage to retain the last session id, allowing us to cleanly end the session when it expires
|
||||
// Browsers that don't support local storage won't be able to end sessions cleanly from the client
|
||||
// The server will notice this and end the sessions itself, with loss of accurate session duration
|
||||
utlSetLocalStorage(_logger, _storageNamePrefix(), [guid, acq, renewal].join("|"));
|
||||
utlSetLocalStorage(_logger, _storageNamePrefix, [guid, acq, renewal].join("|"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
import dynamicProto from "@microsoft/dynamicproto-js";
|
||||
import { IUserContext, utlRemoveStorage } from "@microsoft/applicationinsights-common";
|
||||
import {
|
||||
IAppInsightsCore, ICookieMgr, _eInternalMessageId, _throwInternal, eLoggingSeverity, newId, onConfigChange, safeGetCookieMgr,
|
||||
safeGetLogger, toISOString
|
||||
IAppInsightsCore, ICookieMgr, IUnloadHookContainer, _eInternalMessageId, _throwInternal, eLoggingSeverity, newId, onConfigChange,
|
||||
safeGetCookieMgr, safeGetLogger, toISOString
|
||||
} from "@microsoft/applicationinsights-core-js";
|
||||
import { objDefineProp } from "@nevware21/ts-utils";
|
||||
import { IPropertiesConfig } from "../Interfaces/IPropertiesConfig";
|
||||
|
@ -69,7 +69,7 @@ export class User implements IUserContext {
|
|||
*/
|
||||
public isUserCookieSet = false;
|
||||
|
||||
constructor(config: IPropertiesConfig, core: IAppInsightsCore) {
|
||||
constructor(config: IPropertiesConfig, core: IAppInsightsCore, unloadHookContainer?: IUnloadHookContainer) {
|
||||
let _logger = safeGetLogger(core);
|
||||
let _cookieManager: ICookieMgr = safeGetCookieMgr(core);
|
||||
let _storageNamePrefix: string;
|
||||
|
@ -82,7 +82,7 @@ export class User implements IUserContext {
|
|||
get: () => config
|
||||
});
|
||||
|
||||
onConfigChange(config, () => {
|
||||
let unloadHook = onConfigChange(config, () => {
|
||||
|
||||
const userCookiePostfix = config.userCookiePostfix || "";
|
||||
_storageNamePrefix = User.userCookieName + userCookiePostfix;
|
||||
|
@ -131,6 +131,8 @@ export class User implements IUserContext {
|
|||
}
|
||||
});
|
||||
|
||||
unloadHookContainer && unloadHookContainer.add(unloadHook);
|
||||
|
||||
function _generateNewId() {
|
||||
let theConfig = (config || {}) as IPropertiesConfig;
|
||||
let getNewId = theConfig.getNewId || newId;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
export interface IPropertiesConfig {
|
||||
readonly instrumentationKey: string;
|
||||
readonly accountId: string;
|
||||
readonly sessionRenewalMs: number;
|
||||
readonly samplingPercentage: number;
|
||||
|
|
|
@ -21,7 +21,6 @@ let undefString: string;
|
|||
const nullValue: any = null;
|
||||
|
||||
const _defaultConfig: IConfigDefaults<IPropertiesConfig> = objDeepFreeze({
|
||||
instrumentationKey: undefString,
|
||||
accountId: nullValue,
|
||||
sessionRenewalMs: 30 * 60 * 1000,
|
||||
samplingPercentage: 100,
|
||||
|
@ -68,14 +67,6 @@ export default class PropertiesPlugin extends BaseTelemetryPlugin implements IPr
|
|||
_self.initialize = (config: IConfiguration & IConfig, core: IAppInsightsCore, extensions: IPlugin[], pluginChain?:ITelemetryPluginChain) => {
|
||||
_base.initialize(config, core, extensions, pluginChain);
|
||||
_populateDefaults(config);
|
||||
_previousTraceCtx = core.getTraceCtx(false);
|
||||
_context = new TelemetryContext(core, _extensionConfig, _previousTraceCtx);
|
||||
_distributedTraceCtx = createDistributedTraceContextFromTrace(_self.context.telemetryTrace, _previousTraceCtx);
|
||||
core.setTraceCtx(_distributedTraceCtx);
|
||||
_self.context.appId = () => {
|
||||
let breezeChannel = core.getPlugin<IPlugin>(BreezeChannelIdentifier);
|
||||
return breezeChannel ? breezeChannel.plugin["_appId"] : null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -147,6 +138,16 @@ export default class PropertiesPlugin extends BaseTelemetryPlugin implements IPr
|
|||
// Test hook to allow accessing the internal values -- explicitly not defined as an available property on the class
|
||||
_self["_extConfig"] = _extensionConfig;
|
||||
}));
|
||||
|
||||
// This is outside of the onConfigChange as we don't want to update (replace) these values whenever a referenced config item changes
|
||||
_previousTraceCtx = core.getTraceCtx(false);
|
||||
_context = new TelemetryContext(core, _extensionConfig, _previousTraceCtx, _self._unloadHooks);
|
||||
_distributedTraceCtx = createDistributedTraceContextFromTrace(_self.context.telemetryTrace, _previousTraceCtx);
|
||||
core.setTraceCtx(_distributedTraceCtx);
|
||||
_self.context.appId = () => {
|
||||
let breezeChannel = core.getPlugin<IPlugin>(BreezeChannelIdentifier);
|
||||
return breezeChannel ? breezeChannel.plugin["_appId"] : null;
|
||||
};
|
||||
}
|
||||
|
||||
function _processTelemetryInternal(evt: ITelemetryItem, itemCtx: IProcessTelemetryContext) {
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
PageView
|
||||
} from "@microsoft/applicationinsights-common";
|
||||
import {
|
||||
IAppInsightsCore, IDistributedTraceContext, IProcessTelemetryContext, ITelemetryItem, _InternalLogMessage, getSetValue, hasWindow,
|
||||
isNullOrUndefined, isString, objKeys, setValue
|
||||
IAppInsightsCore, IDistributedTraceContext, IProcessTelemetryContext, ITelemetryItem, IUnloadHookContainer, _InternalLogMessage,
|
||||
getSetValue, hasWindow, isNullOrUndefined, isString, objKeys, setValue
|
||||
} from "@microsoft/applicationinsights-core-js";
|
||||
import { Application } from "./Context/Application";
|
||||
import { Device } from "./Context/Device";
|
||||
|
@ -50,19 +50,19 @@ export class TelemetryContext implements IPropTelemetryContext {
|
|||
public appId: () => string;
|
||||
public getSessionId: () => string;
|
||||
|
||||
constructor(core: IAppInsightsCore, defaultConfig: IPropertiesConfig, previousTraceCtx?: IDistributedTraceContext) {
|
||||
constructor(core: IAppInsightsCore, defaultConfig: IPropertiesConfig, previousTraceCtx?: IDistributedTraceContext, unloadHookContainer?: IUnloadHookContainer) {
|
||||
let logger = core.logger
|
||||
|
||||
dynamicProto(TelemetryContext, this, (_self) => {
|
||||
_self.appId = _nullResult;
|
||||
_self.getSessionId = _nullResult;
|
||||
_self.application = new Application();
|
||||
_self.internal = new Internal(defaultConfig);
|
||||
_self.internal = new Internal(defaultConfig, unloadHookContainer);
|
||||
if (hasWindow()) {
|
||||
_self.sessionManager = new _SessionManager(defaultConfig, core);
|
||||
_self.sessionManager = new _SessionManager(defaultConfig, core, unloadHookContainer);
|
||||
_self.device = new Device();
|
||||
_self.location = new Location();
|
||||
_self.user = new User(defaultConfig, core);
|
||||
_self.user = new User(defaultConfig, core, unloadHookContainer);
|
||||
|
||||
let traceId: string;
|
||||
let parentId: string;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { IConfigDefaults } from "../../../src/Config/IConfigDefaults";
|
|||
import { IConfiguration } from "../../../src/JavaScriptSDK.Interfaces/IConfiguration";
|
||||
import { getDynamicConfigHandler } from "../../../src/Config/DynamicSupport";
|
||||
import { createDynamicConfig, onConfigChange } from "../../../src/Config/DynamicConfig";
|
||||
import { arrForEach, dumpObj, isArray, isFunction, objForEachKey, objKeys, isPlainObject } from "@nevware21/ts-utils";
|
||||
import { arrForEach, dumpObj, isArray, isFunction, objForEachKey, objKeys, isPlainObject, objHasOwn } from "@nevware21/ts-utils";
|
||||
import { IAppInsightsCore } from "../../../src/JavaScriptSDK.Interfaces/IAppInsightsCore";
|
||||
import { INotificationManager } from "../../../src/JavaScriptSDK.Interfaces/INotificationManager";
|
||||
import { IPerfManager } from "../../../src/JavaScriptSDK.Interfaces/IPerfManager";
|
||||
|
@ -23,6 +23,14 @@ const verifyArray = <T>() => <U extends NoRepeats<U> & readonly T[]>(
|
|||
u: (U | [never]) & ([T] extends [U[number]] ? unknown : never)
|
||||
) => u;
|
||||
|
||||
function _expectException(cb: () => void, message: string) {
|
||||
try {
|
||||
cb();
|
||||
QUnit.ok(false, "Expected an exception: " + (message || ""));
|
||||
} catch (e) {
|
||||
QUnit.ok(true, message);
|
||||
}
|
||||
}
|
||||
export class DynamicConfigTests extends AITestClass {
|
||||
|
||||
public testInitialize() {
|
||||
|
@ -553,6 +561,196 @@ export class DynamicConfigTests extends AITestClass {
|
|||
}
|
||||
});
|
||||
|
||||
this.testCase({
|
||||
name: "Validate updating objects / arrays replaces the contents in-place",
|
||||
useFakeTimers: true,
|
||||
test: () => {
|
||||
let theConfig: any = {
|
||||
instrumentationKey: "testiKey",
|
||||
endpointUrl: "https://localhost:9001",
|
||||
enableDebugExceptions: false,
|
||||
loggingLevelConsole: 1,
|
||||
extensionConfig: {
|
||||
"test": {} as any
|
||||
},
|
||||
userCfg: {
|
||||
userTest: {} as any
|
||||
}
|
||||
};
|
||||
|
||||
const channelPlugin = new TestChannelPlugin();
|
||||
let core = new AppInsightsCore();
|
||||
|
||||
core.initialize(theConfig, [channelPlugin]);
|
||||
let cfg = core.config as any;
|
||||
|
||||
// Grab a copy of the testExtCfg
|
||||
let testExtCfg = cfg.extensionConfig?.test;
|
||||
let userTestCfg = cfg.userCfg.userTest;
|
||||
|
||||
let helloCfg: any = {
|
||||
"hello": "World"
|
||||
};
|
||||
|
||||
let myCfg: any = {
|
||||
"my": "newCfg"
|
||||
};
|
||||
|
||||
Assert.ok(!objHasOwn(cfg.extensionConfig!.test, "hello"), "The hello property should not exist");
|
||||
Assert.ok(!objHasOwn(testExtCfg, "hello"), "The hello property should not exist");
|
||||
|
||||
// Assign the new config
|
||||
cfg.extensionConfig!.test = helloCfg;
|
||||
|
||||
// Assign the new config to a new key
|
||||
cfg.extensionConfig!.newTest = helloCfg;
|
||||
|
||||
// The test should now have "copied" the helloCfg
|
||||
Assert.ok(objHasOwn(cfg.extensionConfig!.test, "hello"), "The hello property should exist");
|
||||
Assert.equal("World", cfg.extensionConfig!.test.hello, "And should be assigned");
|
||||
Assert.ok(cfg.extensionConfig!.test !== helloCfg, "The new config should have copied but not directly referenced the helloCfg");
|
||||
|
||||
// Check that the previously referenced config also has the value
|
||||
Assert.ok(objHasOwn(testExtCfg, "hello"), "The hello property should exist");
|
||||
Assert.equal("World", testExtCfg.hello, "And should be assigned");
|
||||
Assert.ok(cfg.extensionConfig!.test === testExtCfg, "The previous reference and the current value should still be the same instance");
|
||||
|
||||
// The newTest element should refer to the helloCfg as it was NOT tagged as referenced
|
||||
Assert.ok(objHasOwn(cfg.extensionConfig!.newTest, "hello"), "The hello property should exist");
|
||||
Assert.equal("World", cfg.extensionConfig!.newTest.hello, "And should be assigned");
|
||||
Assert.ok(cfg.extensionConfig!.newTest === helloCfg, "The new config should have directly referenced the helloCfg as it was not previously present or referenced");
|
||||
|
||||
// Final validation that test !== newTest
|
||||
Assert.ok(cfg.extensionConfig!.newTest !== cfg.extensionConfig!.test, "NewTest and old test should not reference the same instance");
|
||||
|
||||
// Assign the new config
|
||||
cfg.extensionConfig!.test = myCfg;
|
||||
|
||||
// The test should now have "copied" the helloCfg
|
||||
Assert.ok(objHasOwn(cfg.extensionConfig!.test, "hello"), "The hello property should still exist");
|
||||
Assert.equal(undefined, cfg.extensionConfig!.test.hello, "But hello should be undefined");
|
||||
|
||||
Assert.ok(objHasOwn(cfg.extensionConfig!.test, "my"), "But the my property should");
|
||||
Assert.equal("newCfg", cfg.extensionConfig!.test.my, "And should be assigned");
|
||||
Assert.ok(cfg.extensionConfig!.test !== myCfg, "The new config should have copied but not directly referenced the helloCfg");
|
||||
|
||||
// Check that the previously referenced config also has the value
|
||||
Assert.ok(objHasOwn(testExtCfg, "hello"), "The hello property should exist");
|
||||
Assert.equal(undefined, cfg.extensionConfig!.test.hello, "But hello should be undefined");
|
||||
|
||||
Assert.ok(objHasOwn(testExtCfg, "my"), "The my property should exist");
|
||||
Assert.equal("newCfg", testExtCfg.my, "And should be assigned");
|
||||
Assert.ok(cfg.extensionConfig!.test === testExtCfg, "The previous reference and the current value should still be the same instance");
|
||||
|
||||
// New Test should be unchanged
|
||||
// The newTest element should refer to the helloCfg as it was NOT tagged as referenced
|
||||
Assert.ok(objHasOwn(cfg.extensionConfig!.newTest, "hello"), "The hello property should exist");
|
||||
Assert.equal("World", cfg.extensionConfig!.newTest.hello, "And should be assigned");
|
||||
Assert.ok(cfg.extensionConfig!.newTest === helloCfg, "The new config should have directly referenced the helloCfg as it was not previously present or referenced");
|
||||
|
||||
// Final validation that test !== newTest
|
||||
Assert.ok(cfg.extensionConfig!.newTest !== cfg.extensionConfig!.test, "NewTest and old test should not reference the same instance");
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Validate that updating non-referenced objects replaces the object
|
||||
Assert.ok(!objHasOwn(cfg.userCfg.userTest, "hello"), "The hello property should not exist");
|
||||
Assert.ok(!objHasOwn(userTestCfg, "hello"), "The hello property should not exist");
|
||||
|
||||
// Assign the new config
|
||||
cfg.userCfg!.userTest = helloCfg;
|
||||
|
||||
// Assign the new config to a new key
|
||||
cfg.userCfg!.newTest = helloCfg;
|
||||
|
||||
// The test should now have "copied" the helloCfg
|
||||
Assert.ok(objHasOwn(cfg.userCfg!.userTest, "hello"), "The hello property should exist");
|
||||
Assert.equal("World", cfg.userCfg!.userTest.hello, "And should be assigned");
|
||||
Assert.ok(cfg.userCfg!.userTest === helloCfg, "The new config should directly referenced the helloCfg");
|
||||
// The original reference obtained before updating the userCfg should not have changed
|
||||
Assert.ok(!objHasOwn(userTestCfg, "hello"), "The hello property should not exist");
|
||||
|
||||
// The newTest element should refer to the helloCfg
|
||||
Assert.ok(objHasOwn(cfg.userCfg!.newTest, "hello"), "The hello property should exist");
|
||||
Assert.equal("World", cfg.userCfg!.newTest.hello, "And should be assigned");
|
||||
Assert.ok(cfg.userCfg!.newTest === helloCfg, "The new config should have directly referenced the helloCfg");
|
||||
|
||||
// Final validation that test !== newTest
|
||||
Assert.ok(cfg.userCfg!.newTest === cfg.userCfg!.userTest, "NewTest and old test should reference the same instance");
|
||||
|
||||
// Assign the new config
|
||||
cfg.userCfg!.userTest = myCfg;
|
||||
|
||||
// The test should now have "copied" the helloCfg
|
||||
Assert.ok(!objHasOwn(cfg.userCfg!.userTest, "hello"), "The hello property should not exist");
|
||||
Assert.ok(objHasOwn(cfg.userCfg!.userTest, "my"), "The my property should exist");
|
||||
Assert.equal("newCfg", cfg.userCfg!.userTest.my, "And should be assigned");
|
||||
Assert.ok(cfg.userCfg!.userTest === myCfg, "The new config should directly referenced the myCfg");
|
||||
|
||||
// The original reference obtained before updating the userCfg should not have changed
|
||||
Assert.ok(!objHasOwn(userTestCfg, "hello"), "The hello property should not exist");
|
||||
Assert.ok(!objHasOwn(userTestCfg, "my"), "The my property should not exist");
|
||||
|
||||
// The newTest element should still reference the helloCfg
|
||||
Assert.ok(objHasOwn(cfg.userCfg!.newTest, "hello"), "The hello property should exist");
|
||||
Assert.equal("World", cfg.userCfg!.newTest.hello, "And should be assigned");
|
||||
Assert.ok(cfg.userCfg!.newTest === helloCfg, "The new config should have directly referenced the helloCfg");
|
||||
|
||||
// Final validation that test !== newTest
|
||||
Assert.ok(cfg.userCfg!.newTest !== cfg.userCfg!.test, "NewTest and old test should not reference the same instance");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.testCase({
|
||||
name: "Validate read-only",
|
||||
useFakeTimers: true,
|
||||
test: () => {
|
||||
let theConfig: any = {
|
||||
instrumentationKey: "testiKey",
|
||||
endpointUrl: "https://localhost:9001",
|
||||
enableDebugExceptions: false,
|
||||
loggingLevelConsole: 1,
|
||||
extensionConfig: {
|
||||
"test": {} as any
|
||||
},
|
||||
anArray: [ 1, 2, 3 ]
|
||||
};
|
||||
|
||||
let handler = createDynamicConfig(theConfig, undefined, undefined, true);
|
||||
|
||||
handler.rdOnly(theConfig, "instrumentationKey");
|
||||
_expectException(() => {
|
||||
theConfig.instrumentationKey = "newTestKey";
|
||||
}, "Should not be able to re-assign instrumentationKey");
|
||||
|
||||
// Re-Assigning with the same value doesn't cause an exception
|
||||
theConfig.instrumentationKey = "testiKey";
|
||||
|
||||
handler.rdOnly(theConfig, "extensionConfig");
|
||||
_expectException(() => {
|
||||
theConfig.extensionConfig = {};
|
||||
}, "Should not be able to re-assign extensionConfig");
|
||||
|
||||
// Assigning a property to a read-only property is allowed
|
||||
theConfig.extensionConfig.hello = "World";
|
||||
QUnit.assert.equal("World", theConfig.extensionConfig.hello, "Hello should be assigned")
|
||||
QUnit.assert.deepEqual({}, theConfig.extensionConfig.test, "test should be assigned")
|
||||
|
||||
handler.rdOnly(theConfig, "anArray");
|
||||
_expectException(() => {
|
||||
theConfig.anArray = [];
|
||||
}, "Should not be able to re-assign anArray");
|
||||
|
||||
// Assigning a property to a read-only property is allowed
|
||||
theConfig.anArray.hello = "World";
|
||||
QUnit.assert.equal("World", theConfig.anArray.hello, "Hello should be assigned")
|
||||
theConfig.anArray[0] = 0;
|
||||
QUnit.assert.equal(0, theConfig.anArray[0], "0");
|
||||
QUnit.assert.equal(2, theConfig.anArray[1], "2");
|
||||
QUnit.assert.equal(3, theConfig.anArray[2], "3");
|
||||
}
|
||||
});
|
||||
|
||||
function createPerfMgr(core: IAppInsightsCore, manager: INotificationManager): IPerfManager {
|
||||
return createPerfMgr(core, manager);
|
||||
}
|
||||
|
|
|
@ -73,6 +73,16 @@ function _createDynamicHandler<T extends IConfiguration>(logger: IDiagnosticLogg
|
|||
theState.use(null, configHandler);
|
||||
}
|
||||
|
||||
function _ref<C>(target: C, name: string) {
|
||||
// Make sure it's dynamic and mark as referenced with it's current value
|
||||
return _setDynamicProperty(theState, target, name, target[name], true);
|
||||
}
|
||||
|
||||
function _rdOnly<C>(target: C, name: string) {
|
||||
// Make sure it's dynamic and mark as readonly with it's current value
|
||||
return _setDynamicProperty(theState, target, name, target[name], false, true);
|
||||
}
|
||||
|
||||
function _applyDefaults<C>(theConfig: C, defaultValues: IConfigDefaults<C, T>): C {
|
||||
if (defaultValues) {
|
||||
// Resolve/apply the defaults
|
||||
|
@ -93,6 +103,8 @@ function _createDynamicHandler<T extends IConfiguration>(logger: IDiagnosticLogg
|
|||
set: _setValue,
|
||||
setDf: _applyDefaults,
|
||||
watch: _watch,
|
||||
ref: _ref,
|
||||
rdOnly: _rdOnly,
|
||||
_block: _block
|
||||
};
|
||||
|
||||
|
|
|
@ -33,7 +33,12 @@ function _patchArray<T>(state: _IDynamicConfigHandlerState<T>, target: any) {
|
|||
}
|
||||
}
|
||||
|
||||
function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<T>, theConfig: C, name: string, value: V) {
|
||||
function _getOwnPropGetter<T>(target: T, name: PropertyKey) {
|
||||
let propDesc = objGetOwnPropertyDescriptor(target, name);
|
||||
return propDesc && propDesc.get;
|
||||
}
|
||||
|
||||
function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<T>, theConfig: C, name: string, value: V): V {
|
||||
// Does not appear to be dynamic so lets make it so
|
||||
let detail: _IDynamicDetail<T> = {
|
||||
n: name,
|
||||
|
@ -88,23 +93,36 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
|
|||
|
||||
function _setProperty(newValue: V) {
|
||||
if (value !== newValue) {
|
||||
if (!!_setProperty[state.ro] && state.upd) {
|
||||
if (!!_getProperty[state.ro] && !state.upd) {
|
||||
// field is marked as readonly so return false
|
||||
throwInvalidAccess("[" + name + "] is sealed from " + dumpObj(theConfig));
|
||||
throwInvalidAccess("[" + name + "] is read-only:" + dumpObj(theConfig));
|
||||
}
|
||||
|
||||
// As we are replacing the value, if already dynamic then we need to notify any listeners that changes
|
||||
// are happening
|
||||
if (value && value[CFG_HANDLER_LINK]) {
|
||||
// For objects / arrays, we can indirectly inform any listeners by just changing the value to undefined
|
||||
// This will trigger any listeners by simply calling their version of the setter.
|
||||
if (isPlainObject(value) || isArray(value)) {
|
||||
let isReferenced = _getProperty[state.rf];
|
||||
if(isPlainObject(value) || isArray(value)) {
|
||||
if (isReferenced) {
|
||||
// Reassign the properties from the current value to the same properties from the newValue
|
||||
// This will set properties not in the newValue to undefined
|
||||
objForEachKey(value, (key) => {
|
||||
value[key] = newValue[key];
|
||||
});
|
||||
|
||||
// Now assign / re-assign value with all of the keys from newValue
|
||||
objForEachKey(newValue, (key, theValue) => {
|
||||
_setDynamicProperty(state, value, key, theValue);
|
||||
});
|
||||
|
||||
// Now drop newValue so when we assign value later it keeps the existing reference
|
||||
newValue = value;
|
||||
} else if (value && value[CFG_HANDLER_LINK]) {
|
||||
// As we are replacing the value, if it's already dynamic then we need to notify the listeners
|
||||
// for every property it has already
|
||||
objForEachKey(value, (key) => {
|
||||
// Check if the value is dynamic
|
||||
let propDesc = objGetOwnPropertyDescriptor(value, key);
|
||||
if (propDesc && propDesc.get) {
|
||||
let getter = _getOwnPropGetter(value, key);
|
||||
if (getter) {
|
||||
// And if it is tell it's listeners that the value has changed
|
||||
let valueState: _IDynamicGetter = propDesc.get[state.prop];
|
||||
let valueState: _IDynamicGetter = getter[state.prop];
|
||||
valueState && valueState.chng();
|
||||
}
|
||||
});
|
||||
|
@ -112,10 +130,12 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
|
|||
}
|
||||
|
||||
checkDynamic = false;
|
||||
if (isPlainObject(newValue) || isArray(newValue)) {
|
||||
if (!isReferenced && (isPlainObject(newValue) || isArray(newValue))) {
|
||||
// As the newValue is an object/array lets preemptively make it dynamic
|
||||
_makeDynamicObject(state, newValue);
|
||||
}
|
||||
|
||||
// Now assign the internal "value" to the newValue
|
||||
value = newValue;
|
||||
|
||||
// Cause any listeners to be scheduled for notification
|
||||
|
@ -124,23 +144,35 @@ function _makeDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<
|
|||
}
|
||||
|
||||
objDefineAccessors(theConfig, detail.n, _getProperty, _setProperty, true);
|
||||
|
||||
// Return the dynamic reference
|
||||
return _getProperty();
|
||||
}
|
||||
|
||||
export function _setDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<T>, target: C, name: string, value: V) {
|
||||
export function _setDynamicProperty<T, C, V = any>(state: _IDynamicConfigHandlerState<T>, target: C, name: string, value: V, inPlace?: boolean, rdOnly?: boolean): V {
|
||||
if (target) {
|
||||
let isDynamic = false;
|
||||
// To be a dynamic property it needs to have a get function
|
||||
let propDesc = objGetOwnPropertyDescriptor(target, name);
|
||||
if (propDesc && propDesc.get) {
|
||||
isDynamic = !!propDesc.get[state.prop];
|
||||
}
|
||||
let getter = _getOwnPropGetter(target, name);
|
||||
let isDynamic = getter && !!getter[state.prop];
|
||||
|
||||
if (!isDynamic) {
|
||||
_makeDynamicProperty(state, target, name, value);
|
||||
value = _makeDynamicProperty(state, target, name, value);
|
||||
if (inPlace || rdOnly) {
|
||||
getter = _getOwnPropGetter(target, name);
|
||||
}
|
||||
} else {
|
||||
// Looks like it's already dynamic and a different value so just assign the new value
|
||||
// Looks like it's already dynamic just assign the new value
|
||||
target[name] = value;
|
||||
}
|
||||
|
||||
// Assign the optional flags if true
|
||||
if (inPlace) {
|
||||
getter[state.rf] = inPlace;
|
||||
}
|
||||
|
||||
if (rdOnly) {
|
||||
getter[state.ro] = rdOnly;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
|
|
|
@ -11,26 +11,10 @@ import { _IDynamicConfigHandlerState } from "./_IDynamicConfigHandlerState";
|
|||
const symPrefix = "[[ai_";
|
||||
const symPostfix = "]]";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
interface _IWaitingHandlers<T> {
|
||||
/**
|
||||
* The handler to be re-executed
|
||||
*/
|
||||
h: IWatcherHandler<T>;
|
||||
|
||||
/**
|
||||
* The detail objects that caused this handler to be re-executed, each property has a detail instance
|
||||
* but may be the same handler
|
||||
*/
|
||||
//d: _IDynamicDetail<T>[];
|
||||
}
|
||||
|
||||
export function _createState<T>(cfgHandler: _IInternalDynamicConfigHandler<T>): _IDynamicConfigHandlerState<T> {
|
||||
let dynamicPropertySymbol = newSymbol(symPrefix + "get" + cfgHandler.uid + symPostfix);
|
||||
let dynamicPropertyReadOnly = newSymbol(symPrefix + "ro" + cfgHandler.uid + symPostfix);
|
||||
let dynamicPropertyReferenced = newSymbol(symPrefix + "rf" + cfgHandler.uid + symPostfix);
|
||||
let dynamicPropertyDetail = newSymbol(symPrefix + "dtl" + cfgHandler.uid + symPostfix);
|
||||
let _waitingHandlers: IWatcherHandler<T>[] = null;
|
||||
let _watcherTimer: ITimerHandler = null;
|
||||
|
@ -52,7 +36,9 @@ export function _createState<T>(cfgHandler: _IInternalDynamicConfigHandler<T>):
|
|||
callback({
|
||||
cfg: cfgHandler.cfg,
|
||||
set: cfgHandler.set.bind(cfgHandler),
|
||||
setDf: cfgHandler.setDf.bind(cfgHandler)
|
||||
setDf: cfgHandler.setDf.bind(cfgHandler),
|
||||
ref: cfgHandler.ref.bind(cfgHandler),
|
||||
rdOnly: cfgHandler.rdOnly.bind(cfgHandler)
|
||||
});
|
||||
} catch(e) {
|
||||
let logger = cfgHandler.logger;
|
||||
|
@ -156,6 +142,7 @@ export function _createState<T>(cfgHandler: _IInternalDynamicConfigHandler<T>):
|
|||
theState = {
|
||||
prop: dynamicPropertySymbol,
|
||||
ro: dynamicPropertyReadOnly,
|
||||
rf: dynamicPropertyReferenced,
|
||||
hdlr: cfgHandler,
|
||||
add: _addWatcher,
|
||||
notify: _notifyWatchers,
|
||||
|
|
|
@ -49,6 +49,22 @@ export interface IDynamicConfigHandler<T extends IConfiguration> {
|
|||
* @param defaultValues - The default values to apply to the config
|
||||
*/
|
||||
setDf: <C>(theConfig: C, defaultValues: IConfigDefaults<C, T>) => C;
|
||||
|
||||
/**
|
||||
* Set this named property of the target as referenced, which will cause any object or array instances
|
||||
* to be updated in-place rather than being entirely replaced. All other values will continue to be replaced.
|
||||
* @returns The referenced properties current value
|
||||
*/
|
||||
ref: <C, V = any>(target: C, name: string) => V;
|
||||
|
||||
/**
|
||||
* Set this named property of the target as read-only, which will block this single named property from
|
||||
* ever being changed for the target instance.
|
||||
* This does NOT freeze or seal the instance, it just stops the direct re-assignment of the named property,
|
||||
* if the value is a non-primitive (ie. an object or array) it's properties will still be mutable.
|
||||
* @returns The referenced properties current value
|
||||
*/
|
||||
rdOnly: <C, V = any>(target: C, name: string) => V;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,6 +26,22 @@ export interface IWatchDetails<T extends IConfiguration> {
|
|||
* @param defaultValues - The default values to apply to the config
|
||||
*/
|
||||
setDf: <C>(theConfig: C, defaultValues: IConfigDefaults<C>) => C;
|
||||
|
||||
/**
|
||||
* Set this named property of the target as referenced, which will cause any object or array instance
|
||||
* to be updated in-place rather than being entirely replaced. All other values will continue to be replaced.
|
||||
* @returns The referenced properties current value
|
||||
*/
|
||||
ref: <C, V = any>(target: C, name: string) => V;
|
||||
|
||||
/**
|
||||
* Set this named property of the target as read-only, which will block this single named property from
|
||||
* ever being changed for the target instance.
|
||||
* This does NOT freeze or seal the instance, it just stops the direct re-assignment of the named property,
|
||||
* if the value is a non-primitive (ie. an object or array) it's properties will still be mutable.
|
||||
* @returns The referenced properties current value
|
||||
*/
|
||||
rdOnly: <C, V = any>(target: C, name: string) => V;
|
||||
}
|
||||
|
||||
export type WatcherFunction<T extends IConfiguration> = (details: IWatchDetails<T>) => void;
|
||||
|
|
|
@ -20,8 +20,9 @@ export interface _IDynamicGetter {
|
|||
* Interface for the global dynamic config handler
|
||||
*/
|
||||
export interface _IDynamicConfigHandlerState<T> {
|
||||
prop: symbol;
|
||||
ro: symbol;
|
||||
prop: symbol; // Identify that this is a dynamic property
|
||||
ro: symbol; // Identify that this property is read-only
|
||||
rf: symbol; // Identify that this property is referenced and should be updated in-place
|
||||
|
||||
/**
|
||||
* Link to the handler
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
import dynamicProto from "@microsoft/dynamicproto-js";
|
||||
import {
|
||||
ITimerHandler, arrAppend, arrForEach, arrIndexOf, deepExtend, dumpObj, hasDocument, isFunction, isNullOrUndefined, isPlainObject,
|
||||
objDeepFreeze, objDefineProp, objForEachKey, objFreeze, objHasOwn, scheduleInterval, scheduleTimeout, throwError
|
||||
ITimerHandler, arrAppend, arrForEach, arrIndexOf, deepExtend, hasDocument, isFunction, isNullOrUndefined, isPlainObject, objDeepFreeze,
|
||||
objDefineProp, objForEachKey, objFreeze, objHasOwn, scheduleInterval, scheduleTimeout, throwError
|
||||
} from "@nevware21/ts-utils";
|
||||
import { createDynamicConfig, onConfigChange } from "../Config/DynamicConfig";
|
||||
import { IConfigDefaults } from "../Config/IConfigDefaults";
|
||||
|
@ -50,6 +50,7 @@ import {
|
|||
import { _getPluginState, createDistributedTraceContext, initializePlugins, sortPlugins } from "./TelemetryHelpers";
|
||||
import { TelemetryInitializerPlugin } from "./TelemetryInitializerPlugin";
|
||||
import { IUnloadHandlerContainer, UnloadHandler, createUnloadHandlerContainer } from "./UnloadHandlerContainer";
|
||||
import { IUnloadHookContainer, createUnloadHookContainer } from "./UnloadHookContainer";
|
||||
|
||||
const strValidationError = "Plugins must provide initialize method";
|
||||
const strNotificationManager = "_notificationManager";
|
||||
|
@ -246,7 +247,7 @@ export class AppInsightsCore implements IAppInsightsCore {
|
|||
let _internalLogsEventName: string | null;
|
||||
let _evtNamespace: string;
|
||||
let _unloadHandlers: IUnloadHandlerContainer;
|
||||
let _hooks: Array<ILegacyUnloadHook | IUnloadHook>;
|
||||
let _hookContainer: IUnloadHookContainer;
|
||||
let _debugListener: INotificationListener | null;
|
||||
let _traceCtx: IDistributedTraceContext | null;
|
||||
let _instrumentationKey: string | null;
|
||||
|
@ -293,6 +294,14 @@ export class AppInsightsCore implements IAppInsightsCore {
|
|||
_addUnloadHook(_configHandler.watch((details) => {
|
||||
_instrumentationKey = details.cfg.instrumentationKey;
|
||||
|
||||
// Mark the extensionConfig and all first level keys as referenced
|
||||
// This is so that calls to getExtCfg() will always return the same object
|
||||
// Even when a user may "re-assign" the plugin properties (or it's unloaded/reloaded)
|
||||
let extCfg = details.ref(details.cfg, STR_EXTENSION_CONFIG);
|
||||
objForEachKey(extCfg, (key) => {
|
||||
details.ref(extCfg, key);
|
||||
});
|
||||
|
||||
if (isNullOrUndefined(_instrumentationKey)) {
|
||||
throwError("Please provide instrumentation key");
|
||||
}
|
||||
|
@ -527,18 +536,7 @@ export class AppInsightsCore implements IAppInsightsCore {
|
|||
|
||||
let processUnloadCtx = createProcessTelemetryUnloadContext(_getPluginChain(), _self);
|
||||
processUnloadCtx.onComplete(() => {
|
||||
let oldHooks = _hooks;
|
||||
_hooks = [];
|
||||
|
||||
// Remove all registered unload hooks
|
||||
arrForEach(oldHooks, (fn) => {
|
||||
// allow either rm or remove callback function
|
||||
try{
|
||||
((fn as IUnloadHook).rm || (fn as ILegacyUnloadHook).remove).call(fn);
|
||||
} catch (e) {
|
||||
_throwInternal(_self.logger, eLoggingSeverity.WARNING, _eInternalMessageId.PluginException, "Unloading:" + dumpObj(e));
|
||||
}
|
||||
});
|
||||
_hookContainer.run(_self.logger);
|
||||
|
||||
_initDefaults();
|
||||
unloadComplete && unloadComplete(unloadState);
|
||||
|
@ -739,7 +737,7 @@ export class AppInsightsCore implements IAppInsightsCore {
|
|||
|
||||
// Use a default logger so initialization errors are not dropped on the floor with full logging
|
||||
_configHandler = createDynamicConfig({}, defaultConfig, _self.logger);
|
||||
|
||||
|
||||
// Set the logging level to critical so that any critical initialization failures are displayed on the console
|
||||
_configHandler.cfg.loggingLevelConsole = eLoggingSeverity.CRITICAL;
|
||||
|
||||
|
@ -800,7 +798,7 @@ export class AppInsightsCore implements IAppInsightsCore {
|
|||
_unloadHandlers = createUnloadHandlerContainer();
|
||||
_traceCtx = null;
|
||||
_instrumentationKey = null;
|
||||
_hooks = [];
|
||||
_hookContainer = createUnloadHookContainer();
|
||||
_cfgListeners = [];
|
||||
_pluginVersionString = null;
|
||||
_pluginVersionStringArr = null;
|
||||
|
@ -1103,9 +1101,7 @@ export class AppInsightsCore implements IAppInsightsCore {
|
|||
}
|
||||
|
||||
function _addUnloadHook(hooks: IUnloadHook | IUnloadHook[] | Iterator<IUnloadHook> | ILegacyUnloadHook | ILegacyUnloadHook[] | Iterator<ILegacyUnloadHook>) {
|
||||
if (hooks) {
|
||||
arrAppend(_hooks, hooks);
|
||||
}
|
||||
_hookContainer.add(hooks);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
"use strict";
|
||||
|
||||
import dynamicProto from "@microsoft/dynamicproto-js";
|
||||
import { arrAppend, arrForEach, dumpObj, isFunction } from "@nevware21/ts-utils";
|
||||
import { isFunction, objDefine } from "@nevware21/ts-utils";
|
||||
import { createDynamicConfig } from "../Config/DynamicConfig";
|
||||
import { IConfigDefaults } from "../Config/IConfigDefaults";
|
||||
import { _eInternalMessageId, eLoggingSeverity } from "../JavaScriptSDK.Enums/LoggingEnums";
|
||||
import { TelemetryUnloadReason } from "../JavaScriptSDK.Enums/TelemetryUnloadReason";
|
||||
import { TelemetryUpdateReason } from "../JavaScriptSDK.Enums/TelemetryUpdateReason";
|
||||
import { IAppInsightsCore } from "../JavaScriptSDK.Interfaces/IAppInsightsCore";
|
||||
|
@ -21,18 +20,18 @@ import { ITelemetryPluginChain } from "../JavaScriptSDK.Interfaces/ITelemetryPlu
|
|||
import { ITelemetryUnloadState } from "../JavaScriptSDK.Interfaces/ITelemetryUnloadState";
|
||||
import { ITelemetryUpdateState } from "../JavaScriptSDK.Interfaces/ITelemetryUpdateState";
|
||||
import { ILegacyUnloadHook, IUnloadHook } from "../JavaScriptSDK.Interfaces/IUnloadHook";
|
||||
import { _throwInternal } from "./DiagnosticLogger";
|
||||
import { isNotNullOrUndefined, proxyFunctionAs } from "./HelperFuncs";
|
||||
import { STR_EXTENSION_CONFIG } from "./InternalConstants";
|
||||
import {
|
||||
createProcessTelemetryContext, createProcessTelemetryUnloadContext, createProcessTelemetryUpdateContext
|
||||
} from "./ProcessTelemetryContext";
|
||||
import { IUnloadHandlerContainer, UnloadHandler, createUnloadHandlerContainer } from "./UnloadHandlerContainer";
|
||||
import { IUnloadHookContainer, createUnloadHookContainer } from "./UnloadHookContainer";
|
||||
|
||||
let strGetPlugin = "getPlugin";
|
||||
|
||||
const defaultValues: IConfigDefaults<IConfiguration> = {
|
||||
[STR_EXTENSION_CONFIG]: { isVal: isNotNullOrUndefined, v: []}
|
||||
[STR_EXTENSION_CONFIG]: { isVal: isNotNullOrUndefined, v: {} }
|
||||
};
|
||||
|
||||
|
||||
|
@ -110,6 +109,13 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin {
|
|||
*/
|
||||
protected _doUpdate?: (updateCtx?: IProcessTelemetryUpdateContext, updateState?: ITelemetryUpdateState, asyncCallback?: () => void) => void | boolean;
|
||||
|
||||
/**
|
||||
* Exposes the underlying unload hook container instance for this extension to allow it to be passed down to any sub components of the class.
|
||||
* This should NEVER be exposed or called publically as it's scope is for internal use by BaseTelemetryPlugin and any derived class (which is why
|
||||
* it's scoped as protected)
|
||||
*/
|
||||
protected readonly _unloadHooks: IUnloadHookContainer;
|
||||
|
||||
constructor() {
|
||||
let _self = this; // Setting _self here as it's used outside of the dynamicProto as well
|
||||
|
||||
|
@ -118,7 +124,7 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin {
|
|||
let _rootCtx: IProcessTelemetryContext; // Used as the root context, holding the current config and initialized core
|
||||
let _nextPlugin: ITelemetryPlugin | ITelemetryPluginChain; // Used for backward compatibility where plugins don't call the main pipeline
|
||||
let _unloadHandlerContainer: IUnloadHandlerContainer;
|
||||
let _hooks: Array<ILegacyUnloadHook| IUnloadHook>;
|
||||
let _hookContainer: IUnloadHookContainer;
|
||||
|
||||
_initDefaults();
|
||||
|
||||
|
@ -151,19 +157,7 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin {
|
|||
unloadDone = true;
|
||||
|
||||
_unloadHandlerContainer.run(theUnloadCtx, unloadState);
|
||||
|
||||
let oldHooks = _hooks;
|
||||
_hooks = [];
|
||||
|
||||
// Remove all registered unload hooks
|
||||
arrForEach(oldHooks, (fn) => {
|
||||
// allow either rm or remove callback function
|
||||
try{
|
||||
((fn as IUnloadHook).rm || (fn as ILegacyUnloadHook).remove).call(fn);
|
||||
} catch (e) {
|
||||
_throwInternal(theUnloadCtx.diagLog(), eLoggingSeverity.WARNING, _eInternalMessageId.PluginException, "Unloading:" + dumpObj(e));
|
||||
}
|
||||
});
|
||||
_hookContainer.run(theUnloadCtx.diagLog());
|
||||
|
||||
if (result === true) {
|
||||
theUnloadCtx.processNext(theUnloadState);
|
||||
|
@ -215,13 +209,9 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin {
|
|||
return result;
|
||||
};
|
||||
|
||||
_self._addHook = (hooks: IUnloadHook | IUnloadHook[] | ILegacyUnloadHook | ILegacyUnloadHook[]) => {
|
||||
if (hooks) {
|
||||
arrAppend(_hooks, hooks);
|
||||
}
|
||||
};
|
||||
|
||||
proxyFunctionAs(_self, "_addUnloadCb", () => _unloadHandlerContainer, "add");
|
||||
proxyFunctionAs(_self, "_addHook", () => _hookContainer, "add");
|
||||
objDefine(_self, "_unloadHooks" as keyof BaseTelemetryPlugin, { g: () => _hookContainer });
|
||||
});
|
||||
|
||||
// These are added after the dynamicProto so that are not moved to the prototype
|
||||
|
@ -301,7 +291,7 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin {
|
|||
_self.core = null;
|
||||
_rootCtx = null;
|
||||
_nextPlugin = null;
|
||||
_hooks = [];
|
||||
_hookContainer = createUnloadHookContainer();
|
||||
_unloadHandlerContainer = createUnloadHandlerContainer();
|
||||
}
|
||||
}
|
||||
|
@ -347,7 +337,7 @@ export abstract class BaseTelemetryPlugin implements ITelemetryPlugin {
|
|||
* Add this hook so that it is automatically removed during unloading
|
||||
* @param hooks - The single hook or an array of IInstrumentHook objects
|
||||
*/
|
||||
protected _addHook(hooks: IUnloadHook | IUnloadHook[] | ILegacyUnloadHook | ILegacyUnloadHook[]): void {
|
||||
protected _addHook(hooks: IUnloadHook | IUnloadHook[] | Iterator<IUnloadHook> | ILegacyUnloadHook | ILegacyUnloadHook[] | Iterator<ILegacyUnloadHook>): void {
|
||||
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,19 +162,22 @@ function _createInternalContext<T extends IBaseProcessingContext>(telemetryChain
|
|||
extCfg = {};
|
||||
}
|
||||
|
||||
// Always set the value so that it's created as a dynamic config element (if not already)
|
||||
dynamicHandler.set(cfg, STR_EXTENSION_CONFIG, extCfg); // Note: it is valid for the "value" to be undefined
|
||||
extCfg = cfg.extensionConfig;
|
||||
|
||||
// Always set the value so that the property always exists
|
||||
cfg[STR_EXTENSION_CONFIG] = extCfg; // Note: it is valid for the "value" to be undefined
|
||||
|
||||
// Calling `ref()` has a side effect of causing the referenced property to become dynamic (if not already)
|
||||
extCfg = dynamicHandler.ref(cfg, STR_EXTENSION_CONFIG);
|
||||
if (extCfg) {
|
||||
idCfg = extCfg[identifier];
|
||||
if (!idCfg && createIfMissing) {
|
||||
idCfg = {} as T;
|
||||
}
|
||||
|
||||
// Always set the value so that it's created as a dynamic config element (if not already)
|
||||
dynamicHandler.set(extCfg, identifier, idCfg);
|
||||
idCfg = extCfg[identifier];
|
||||
// Always set the value so that the property always exists
|
||||
extCfg[identifier] = idCfg; // Note: it is valid for the "value" to be undefined
|
||||
|
||||
// Calling `ref()` has a side effect of causing the referenced property to become dynamic (if not already)
|
||||
idCfg = dynamicHandler.ref(extCfg, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export interface IUnloadHandlerContainer {
|
|||
run: (itemCtx: IProcessTelemetryUnloadContext, unloadState: ITelemetryUnloadState) => void
|
||||
}
|
||||
|
||||
export function createUnloadHandlerContainer() {
|
||||
export function createUnloadHandlerContainer(): IUnloadHandlerContainer {
|
||||
let handlers: UnloadHandler[] = [];
|
||||
|
||||
function _addHandler(handler: UnloadHandler) {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { arrAppend, arrForEach, dumpObj } from "@nevware21/ts-utils";
|
||||
import { eLoggingSeverity, _eInternalMessageId } from "../JavaScriptSDK.Enums/LoggingEnums";
|
||||
import { IDiagnosticLogger } from "../JavaScriptSDK.Interfaces/IDiagnosticLogger";
|
||||
import { ILegacyUnloadHook, IUnloadHook } from "../JavaScriptSDK.Interfaces/IUnloadHook";
|
||||
import { _throwInternal } from "./DiagnosticLogger";
|
||||
|
||||
/**
|
||||
* Interface which identifiesAdd this hook so that it is automatically removed during unloading
|
||||
* @param hooks - The single hook or an array of IInstrumentHook objects
|
||||
*/
|
||||
export interface IUnloadHookContainer {
|
||||
add: (hooks: IUnloadHook | IUnloadHook[] | Iterator<IUnloadHook> | ILegacyUnloadHook | ILegacyUnloadHook[] | Iterator<ILegacyUnloadHook>) => void;
|
||||
run: (logger?: IDiagnosticLogger) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a IUnloadHookContainer which can be used to remember unload hook functions to be executed during the component unloading
|
||||
* process.
|
||||
* @returns A new IUnloadHookContainer instance
|
||||
*/
|
||||
export function createUnloadHookContainer(): IUnloadHookContainer {
|
||||
let _hooks: Array<ILegacyUnloadHook | IUnloadHook> = [];
|
||||
|
||||
function _doUnload(logger: IDiagnosticLogger) {
|
||||
let oldHooks = _hooks;
|
||||
_hooks = [];
|
||||
|
||||
// Remove all registered unload hooks
|
||||
arrForEach(oldHooks, (fn) => {
|
||||
// allow either rm or remove callback function
|
||||
try{
|
||||
((fn as IUnloadHook).rm || (fn as ILegacyUnloadHook).remove).call(fn);
|
||||
} catch (e) {
|
||||
_throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.PluginException, "Unloading:" + dumpObj(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _addHook(hooks: IUnloadHook | IUnloadHook[] | Iterator<IUnloadHook> | ILegacyUnloadHook | ILegacyUnloadHook[] | Iterator<ILegacyUnloadHook>) {
|
||||
if (hooks) {
|
||||
arrAppend(_hooks, hooks);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
run: _doUnload,
|
||||
add: _addHook
|
||||
};
|
||||
}
|
|
@ -80,6 +80,7 @@ export { getDebugListener, getDebugExt } from "./JavaScriptSDK/DbgExtensionUtils
|
|||
export { TelemetryInitializerFunction, ITelemetryInitializerHandler, ITelemetryInitializerContainer } from "./JavaScriptSDK.Interfaces/ITelemetryInitializers";
|
||||
export { createUniqueNamespace } from "./JavaScriptSDK/DataCacheHelper";
|
||||
export { UnloadHandler, IUnloadHandlerContainer, createUnloadHandlerContainer } from "./JavaScriptSDK/UnloadHandlerContainer";
|
||||
export { IUnloadHookContainer, createUnloadHookContainer } from "./JavaScriptSDK/UnloadHookContainer";
|
||||
export { ITelemetryUpdateState } from "./JavaScriptSDK.Interfaces/ITelemetryUpdateState";
|
||||
export { ITelemetryUnloadState } from "./JavaScriptSDK.Interfaces/ITelemetryUnloadState";
|
||||
export { IDistributedTraceContext } from "./JavaScriptSDK.Interfaces/IDistributedTraceContext";
|
||||
|
|
Загрузка…
Ссылка в новой задаче