Skip to content

Commit a889f4c

Browse files
authored
Merge pull request #111 from microsoft/lramos15/1dsSupport
Implement 1DS SDK Support
2 parents 02e1cf1 + bfae6f7 commit a889f4c

8 files changed

+283
-14
lines changed

package-lock.json

+103-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
"compile": "tsc -noEmit -p 'src/browser/tsconfig.json' && tsc -noEmit -p 'src/node/tsconfig.json' && npm run build"
1919
},
2020
"devDependencies": {
21+
"@microsoft/1ds-core-js": "^3.2.3",
22+
"@microsoft/1ds-post-js": "^3.2.3",
2123
"@microsoft/applicationinsights-web": "^2.6.4",
2224
"@types/mocha": "^9.1.1",
2325
"@types/node": "^16.11.32",

src/browser/telemetryReporter.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import type { ApplicationInsights } from "@microsoft/applicationinsights-web";
66
import * as vscode from "vscode";
7+
import { oneDataSystemClientFactory } from "../common/1dsClientFactory";
78
import { BaseTelemetryAppender, BaseTelemetryClient } from "../common/baseTelemetryAppender";
89
import { AppenderData, BaseTelemetryReporter, ReplacementOption } from "../common/baseTelemetryReporter";
910
import { TelemetryUtil } from "../common/util";
@@ -14,7 +15,7 @@ const webAppInsightsClientFactory = async (key: string, replacementOptions?: Rep
1415
const web = await import("@microsoft/applicationinsights-web");
1516
let endpointUrl: undefined | string;
1617
if (key && key.indexOf("AIF-") === 0) {
17-
endpointUrl = "https://vortex.data.microsoft.com/collect/v1";
18+
endpointUrl = "https://vscode.vortex.data.microsoft.com/collect/v1";
1819
}
1920
appInsightsClient = new web.ApplicationInsights({
2021
config: {
@@ -71,8 +72,15 @@ const webAppInsightsClientFactory = async (key: string, replacementOptions?: Rep
7172

7273
export default class TelemetryReporter extends BaseTelemetryReporter {
7374
constructor(extensionId: string, extensionVersion: string, key: string, firstParty?: boolean, replacementOptions?: ReplacementOption[]) {
74-
const appender = new BaseTelemetryAppender(key, key => webAppInsightsClientFactory(key, replacementOptions));
75-
if (key && key.indexOf("AIF-") === 0) {
75+
let clientFactory = (key: string) => webAppInsightsClientFactory(key, replacementOptions);
76+
// If key is usable by 1DS use the 1DS SDk
77+
if (TelemetryUtil.shouldUseOneDataSystemSDK(key)) {
78+
clientFactory = (key: string) => oneDataSystemClientFactory(key, vscode);
79+
}
80+
81+
const appender = new BaseTelemetryAppender(key, clientFactory);
82+
// If it's a specialized AIF app insights key or a 1DS key then it is first party
83+
if (key && (key.indexOf("AIF-") === 0 || TelemetryUtil.shouldUseOneDataSystemSDK(key))) {
7684
firstParty = true;
7785
}
7886
super(extensionId, extensionVersion, appender, {

src/common/1dsClientFactory.ts

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import type { AppInsightsCore, IExtendedConfiguration } from "@microsoft/1ds-core-js";
7+
import type { IChannelConfiguration, IXHROverride, PostChannel } from "@microsoft/1ds-post-js";
8+
import type * as vscode from "vscode";
9+
import type { BaseTelemetryClient } from "./baseTelemetryAppender";
10+
import { AppenderData } from "./baseTelemetryReporter";
11+
12+
/**
13+
* Configures 1DS properly and returns the core client object
14+
* @param key The ingestion key
15+
* @param xhrOverride An optional override to use for requests instead of the XHTMLRequest object. Useful for node environments
16+
* @returns The AI core object
17+
*/
18+
const getAICore = async (key: string, vscodeAPI: typeof vscode, xhrOverride?: IXHROverride): Promise<AppInsightsCore> => {
19+
const oneDs = await import("@microsoft/1ds-core-js");
20+
const postPlugin = await import("@microsoft/1ds-post-js");
21+
const appInsightsCore = new oneDs.AppInsightsCore();
22+
const collectorChannelPlugin: PostChannel = new postPlugin.PostChannel();
23+
// Configure the app insights core to send to collector++ and disable logging of debug info
24+
const coreConfig: IExtendedConfiguration = {
25+
instrumentationKey: key,
26+
endpointUrl: "https://mobile.events.data.microsoft.com/OneCollector/1.0",
27+
loggingLevelTelemetry: 0,
28+
loggingLevelConsole: 0,
29+
disableCookiesUsage: true,
30+
disableDbgExt: true,
31+
disableInstrumentationKeyValidation: true,
32+
channels: [[
33+
collectorChannelPlugin
34+
]]
35+
};
36+
37+
if (xhrOverride) {
38+
coreConfig.extensionConfig = {};
39+
// Configure the channel to use a XHR Request override since it's not available in node
40+
const channelConfig: IChannelConfiguration = {
41+
alwaysUseXhrOverride: true,
42+
httpXHROverride: xhrOverride
43+
};
44+
coreConfig.extensionConfig[collectorChannelPlugin.identifier] = channelConfig;
45+
}
46+
47+
const config = vscodeAPI.workspace.getConfiguration("telemetry");
48+
const internalTesting = config.get<boolean>("internalTesting");
49+
50+
appInsightsCore.initialize(coreConfig, []);
51+
52+
appInsightsCore.addTelemetryInitializer((envelope) => {
53+
// Only add this flag when `telemetry.internalTesting` is enabled
54+
if (!internalTesting) {
55+
return;
56+
}
57+
envelope["ext"] = envelope["ext"] ?? {};
58+
envelope["ext"]["utc"] = envelope["ext"]["utc"] ?? {};
59+
// Sets it to be internal only based on Windows UTC flagging
60+
envelope["ext"]["utc"]["flags"] = 0x0000811ECD;
61+
});
62+
63+
return appInsightsCore;
64+
};
65+
66+
/**
67+
* Configures and creates a telemetry client using the 1DS sdk
68+
* @param key The ingestion key
69+
* @param xhrOverride An optional override to use for requests instead of the XHTMLRequest object. Useful for node environments
70+
*/
71+
export const oneDataSystemClientFactory = async (key: string, vscodeAPI: typeof vscode, xhrOverride?: IXHROverride): Promise<BaseTelemetryClient> => {
72+
const appInsightsCore = await getAICore(key, vscodeAPI, xhrOverride);
73+
// Shape the app insights core from 1DS into a standard format
74+
const telemetryClient: BaseTelemetryClient = {
75+
logEvent: (eventName: string, data?: AppenderData) => {
76+
try {
77+
appInsightsCore?.track({
78+
name: eventName,
79+
baseData: { name: eventName, properties: data?.properties, measurements: data?.measurements }
80+
});
81+
} catch (e: any) {
82+
throw new Error("Failed to log event to app insights!\n" + e.message);
83+
}
84+
},
85+
logException: (_exception: Error, _data?: AppenderData) => {
86+
throw new Error("1DS SDK does not support logging exceptions, please use logEvent for exception tracking");
87+
},
88+
flush: async () => {
89+
try {
90+
appInsightsCore?.unload();
91+
} catch (e: any) {
92+
throw new Error("Failed to flush app insights!\n" + e.message);
93+
}
94+
}
95+
};
96+
return telemetryClient;
97+
};

src/common/baseTelemetryReporter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export class BaseTelemetryReporter {
149149
// __GDPR__COMMON__ "common.isnewappinstall" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
150150
// __GDPR__COMMON__ "common.product" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
151151
private getCommonProperties(): TelemetryEventProperties {
152-
const commonProperties = Object.create(null);
152+
const commonProperties: Record<string, any> = {};
153153
commonProperties["common.os"] = this.osShim.platform;
154154
commonProperties["common.nodeArch"] = this.osShim.architecture;
155155
commonProperties["common.platformversion"] = (this.osShim.release || "").replace(/^(\d+)(\.\d+)?(\.\d+)?(.*)/, "$1$2$3");
@@ -242,7 +242,7 @@ export class BaseTelemetryReporter {
242242
if (typeof properties !== "object") {
243243
return;
244244
}
245-
const cleanedObject = Object.create(null);
245+
const cleanedObject: Record<string, any> = {};
246246
// Loop through key and values of the properties object
247247
for (const key of Object.keys(properties)) {
248248
const value = properties[key];

src/common/util.ts

+17
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,23 @@ export class TelemetryUtil {
5656
}
5757
}
5858

59+
/**
60+
* Given a key checks if it is a valid 1DS key
61+
* @param key The key to check if it's a valid 1DS key
62+
*/
63+
public static shouldUseOneDataSystemSDK(key: string): boolean {
64+
// Simple to check to ensure the key is the right length and the dashes are in the right spot
65+
return (
66+
key.length === 74 &&
67+
key[32] === "-" &&
68+
key[41] === "-" &&
69+
key[46] === "-" &&
70+
key[51] === "-" &&
71+
key[56] === "-" &&
72+
key[69] === "-"
73+
);
74+
}
75+
5976
// Get singleton instance of TelemetryUtil
6077
public static getInstance(vscodeAPI: typeof vscode): TelemetryUtil {
6178
if (!TelemetryUtil._instance) {

0 commit comments

Comments
 (0)