microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.1.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/common/telemetry.ts

388lines · modeblame

d976d077Meena Kunnathur Balakrishnan10 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
6cd1e194Meena Kunnathur Balakrishnan10 years ago3
d976d077Meena Kunnathur Balakrishnan10 years ago4/// <reference path="../typings/applicationinsights/applicationinsights.d.ts" />
5/// <reference path="../typings/winreg/winreg.d.ts" />
6cd1e194Meena Kunnathur Balakrishnan10 years ago6
d976d077Meena Kunnathur Balakrishnan10 years ago7import * as appInsights from "applicationinsights";
8import * as crypto from "crypto";
9import * as fs from "fs";
10import * as getmac from "getmac";
11import * as os from "os";
12import * as path from "path";
13import * as Q from "q";
14import * as winreg from "winreg";
6cd1e194Meena Kunnathur Balakrishnan10 years ago15
16// for poking around at internal applicationinsights options
d976d077Meena Kunnathur Balakrishnan10 years ago17/* tslint:disable:no-var-requires */
18let sender = require ("applicationinsights/Library/Sender");
19let telemetryLogger = require ("applicationinsights/Library/Logging");
20/* tslint:enable:no-var-requires */
6cd1e194Meena Kunnathur Balakrishnan10 years ago21
22/**
23* Telemetry module specialized for vscode integration.
24*/
25export module Telemetry {
d976d077Meena Kunnathur Balakrishnan10 years ago26export let appName: string;
27export let isOptedIn: boolean = false;
6cd1e194Meena Kunnathur Balakrishnan10 years ago28
29export interface ITelemetryProperties {
30[propertyName: string]: any;
31};
32
33/**
34* TelemetryEvent represents a basic telemetry data point
35*/
36export class TelemetryEvent {
37public name: string;
38public properties: ITelemetryProperties;
d976d077Meena Kunnathur Balakrishnan10 years ago39private static PII_HASH_KEY: string = "959069c9-9e93-4fa1-bf16-3f8120d7db0c";
6cd1e194Meena Kunnathur Balakrishnan10 years ago40private eventId: string;
41
42constructor(name: string, properties?: ITelemetryProperties) {
43this.name = name;
44this.properties = properties || {};
45
46this.eventId = TelemetryUtils.generateGuid();
47}
48
49public setPiiProperty(name: string, value: string): void {
d976d077Meena Kunnathur Balakrishnan10 years ago50let hmac: any = crypto.createHmac("sha256", new Buffer(TelemetryEvent.PII_HASH_KEY, "utf8"));
51let hashedValue: any = hmac.update(value).digest("hex");
6cd1e194Meena Kunnathur Balakrishnan10 years ago52
53this.properties[name] = hashedValue;
54
55if (Telemetry.isInternal()) {
d976d077Meena Kunnathur Balakrishnan10 years ago56this.properties[name + ".nothashed"] = value;
6cd1e194Meena Kunnathur Balakrishnan10 years ago57}
58}
59};
60
61/**
62* TelemetryActivity automatically includes timing data, used for scenarios where we want to track performance.
63* Calls to start() and end() are optional, if not called explicitly then the constructor will be the start and send will be the end.
64* This event will include a property called reserved.activity.duration which represents time in milliseconds.
65*/
66export class TelemetryActivity extends TelemetryEvent {
67private startTime: number[];
68private endTime: number[];
69
70constructor(name: string, properties?: ITelemetryProperties) {
71super(name, properties);
72this.start();
73}
74
75public start(): void {
76this.startTime = process.hrtime();
77}
78
79public end(): void {
80if (!this.endTime) {
81this.endTime = process.hrtime(this.startTime);
82
83// convert [seconds, nanoseconds] to milliseconds and include as property
d976d077Meena Kunnathur Balakrishnan10 years ago84this.properties["reserved.activity.duration"] = this.endTime[0] * 1000 + this.endTime[1] / 1000000;
6cd1e194Meena Kunnathur Balakrishnan10 years ago85}
86}
87};
88
89export function init(appNameValue: string, appVersion?: string, isOptedInValue?: boolean): Q.Promise<any> {
90try {
91Telemetry.appName = appNameValue;
92return TelemetryUtils.init(appVersion, isOptedInValue);
93} catch (err) {
94console.error(err);
95}
96}
97
98export function send(event: TelemetryEvent, ignoreOptIn: boolean = false): void {
99if (Telemetry.isOptedIn || ignoreOptIn) {
100TelemetryUtils.addCommonProperties(event);
101
102try {
103if (event instanceof TelemetryActivity) {
104(<TelemetryActivity> event).end();
105}
106
107if (appInsights.client) { // no-op if telemetry is not initialized
108appInsights.client.trackEvent(event.name, event.properties);
109}
2d3a052eMeena Kunnathur Balakrishnan10 years ago110
6cd1e194Meena Kunnathur Balakrishnan10 years ago111} catch (err) {
112console.error(err);
113}
114}
115}
116
117export function sendPendingData(): Q.Promise<string> {
d976d077Meena Kunnathur Balakrishnan10 years ago118let defer: Q.Deferred<string> = Q.defer<string>();
6cd1e194Meena Kunnathur Balakrishnan10 years ago119appInsights.client.sendPendingData((result: string) => defer.resolve(result));
120return defer.promise;
121}
122
123export function isInternal(): boolean {
124return TelemetryUtils.userType === TelemetryUtils.USERTYPE_INTERNAL;
125}
126
127export function getSessionId(): string {
128return TelemetryUtils.sessionId;
129}
130
131export function setSessionId(sessionId: string): void {
132TelemetryUtils.sessionId = sessionId;
133}
134
135interface ITelemetrySettings {
136[settingKey: string]: any;
137userId?: string;
138machineId?: string;
139optIn?: boolean;
140userType?: string;
141}
142
143class TelemetryUtils {
d976d077Meena Kunnathur Balakrishnan10 years ago144public static USERTYPE_INTERNAL: string = "Internal";
145public static USERTYPE_EXTERNAL: string = "External";
6cd1e194Meena Kunnathur Balakrishnan10 years ago146public static userType: string;
147public static sessionId: string;
148public static optInCollectedForCurrentSession: boolean;
149
150private static userId: string;
151private static machineId: string;
152private static telemetrySettings: ITelemetrySettings = null;
d976d077Meena Kunnathur Balakrishnan10 years ago153private static TELEMETRY_SETTINGS_FILENAME: string = "VSCodeTelemetrySettings.json";
154private static APPINSIGHTS_INSTRUMENTATIONKEY: string = "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217"; // Matches vscode telemetry key
155private static REGISTRY_SQMCLIENT_NODE: string = "\\SOFTWARE\\Microsoft\\SQMClient";
156private static REGISTRY_USERID_VALUE: string = "UserId";
157private static INTERNAL_DOMAIN_SUFFIX: string = "microsoft.com";
158private static INTERNAL_USER_ENV_VAR: string = "TACOINTERNAL";
6cd1e194Meena Kunnathur Balakrishnan10 years ago159
160private static get settingsHome(): string {
161switch (os.platform()) {
d976d077Meena Kunnathur Balakrishnan10 years ago162case "win32":
163return path.join(process.env.APPDATA, "vscode-react-native");
164case "darwin":
165case "linux":
166return path.join(process.env.HOME, ".vscode-react-native");
6cd1e194Meena Kunnathur Balakrishnan10 years ago167default:
d976d077Meena Kunnathur Balakrishnan10 years ago168throw new Error("UnexpectedPlatform");
169}
6cd1e194Meena Kunnathur Balakrishnan10 years ago170}
171
172private static get telemetrySettingsFile(): string {
173return path.join(TelemetryUtils.settingsHome, TelemetryUtils.TELEMETRY_SETTINGS_FILENAME);
174}
175
176public static init(appVersion: string, isOptedInValue: boolean): Q.Promise<any> {
177TelemetryUtils.loadSettings();
178
179let client = appInsights.setup(TelemetryUtils.APPINSIGHTS_INSTRUMENTATIONKEY)
180.setOfflineMode(true)
181.setAutoCollectConsole(false)
182.setAutoCollectRequests(false)
183.setAutoCollectPerformance(false)
184.setAutoCollectExceptions(false)
185.start().client;
186appInsights.client.config.maxBatchIntervalMs = 100;
187sender.WAIT_BETWEEN_RESEND = 0;
188telemetryLogger.disableWarnings = true;
189
190if (client && client.context && client.context.keys && client.context.tags) {
191// Remove potential PII
192let machineNameKey = client.context.keys.deviceMachineName;
d976d077Meena Kunnathur Balakrishnan10 years ago193client.context.tags[machineNameKey] = "";
6cd1e194Meena Kunnathur Balakrishnan10 years ago194}
195
196if (appVersion) {
d976d077Meena Kunnathur Balakrishnan10 years ago197let context: Context = appInsights.client.context;
6cd1e194Meena Kunnathur Balakrishnan10 years ago198context.tags[context.keys.applicationVersion] = appVersion;
199}
200
201// Change endpoint to match Aimov key
202client.config.endpointUrl = "https://vortex.data.microsoft.com/collect/v1";
203
204return Q.all([TelemetryUtils.getUserId(), TelemetryUtils.getMachineId()])
205.spread<any>(function (userId: string, machineId: string): void {
206TelemetryUtils.userId = userId;
207TelemetryUtils.machineId = machineId;
208TelemetryUtils.sessionId = TelemetryUtils.generateGuid();
209TelemetryUtils.userType = TelemetryUtils.getUserType();
210
211Telemetry.isOptedIn = TelemetryUtils.getTelemetryOptInSetting();
212TelemetryUtils.saveSettings();
213});
214}
215
216public static addCommonProperties(event: any): void {
217if (Telemetry.isOptedIn) {
d976d077Meena Kunnathur Balakrishnan10 years ago218// for the opt out event, don"t include tracking properties
219event.properties["RN.userId"] = TelemetryUtils.userId;
220event.properties["RN.machineId"] = TelemetryUtils.machineId;
6cd1e194Meena Kunnathur Balakrishnan10 years ago221}
222
d976d077Meena Kunnathur Balakrishnan10 years ago223event.properties["RN.sessionId"] = TelemetryUtils.sessionId;
224event.properties["RN.userType"] = TelemetryUtils.userType;
225event.properties["RN.hostOS"] = os.platform();
226event.properties["RN.hostOSRelease"] = os.release();
6cd1e194Meena Kunnathur Balakrishnan10 years ago227}
228
229public static generateGuid(): string {
d976d077Meena Kunnathur Balakrishnan10 years ago230let hexValues: string[] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
6cd1e194Meena Kunnathur Balakrishnan10 years ago231// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
d976d077Meena Kunnathur Balakrishnan10 years ago232let oct: string = "";
233let tmp: number;
6cd1e194Meena Kunnathur Balakrishnan10 years ago234/* tslint:disable:no-bitwise */
d976d077Meena Kunnathur Balakrishnan10 years ago235for (let a: number = 0; a < 4; a++) {
6cd1e194Meena Kunnathur Balakrishnan10 years ago236tmp = (4294967296 * Math.random()) | 0;
237oct += hexValues[tmp & 0xF] + hexValues[tmp >> 4 & 0xF] + hexValues[tmp >> 8 & 0xF] + hexValues[tmp >> 12 & 0xF] + hexValues[tmp >> 16 & 0xF] + hexValues[tmp >> 20 & 0xF] + hexValues[tmp >> 24 & 0xF] + hexValues[tmp >> 28 & 0xF];
238}
239
d976d077Meena Kunnathur Balakrishnan10 years ago240// "Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively"
241let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
242return oct.substr(0, 8) + "-" + oct.substr(9, 4) + "-4" + oct.substr(13, 3) + "-" + clockSequenceHi + oct.substr(16, 3) + "-" + oct.substr(19, 12);
6cd1e194Meena Kunnathur Balakrishnan10 years ago243/* tslint:enable:no-bitwise */
244}
245
246public static getTelemetryOptInSetting(): boolean {
247if (TelemetryUtils.telemetrySettings.optIn === undefined) {
248// Opt-in by default
249TelemetryUtils.telemetrySettings.optIn = true;
250}
251
252return TelemetryUtils.telemetrySettings.optIn;
253}
254
255public static setTelemetryOptInSetting(optIn: boolean): void {
256TelemetryUtils.telemetrySettings.optIn = optIn;
257
258if (!optIn) {
d976d077Meena Kunnathur Balakrishnan10 years ago259Telemetry.send(new TelemetryEvent(Telemetry.appName + "/telemetryOptOut"), true);
6cd1e194Meena Kunnathur Balakrishnan10 years ago260}
261
262TelemetryUtils.optInCollectedForCurrentSession = true;
263TelemetryUtils.saveSettings();
264}
265
266private static getUserType(): string {
d976d077Meena Kunnathur Balakrishnan10 years ago267let userType: string = TelemetryUtils.telemetrySettings.userType;
6cd1e194Meena Kunnathur Balakrishnan10 years ago268
269if (userType === undefined) {
270if (process.env[TelemetryUtils.INTERNAL_USER_ENV_VAR]) {
271userType = TelemetryUtils.USERTYPE_INTERNAL;
d976d077Meena Kunnathur Balakrishnan10 years ago272} else if (os.platform() === "win32") {
273let domain: string = process.env.USERDNSDOMAIN;
6cd1e194Meena Kunnathur Balakrishnan10 years ago274domain = domain ? domain.toLowerCase().substring(domain.length - TelemetryUtils.INTERNAL_DOMAIN_SUFFIX.length) : null;
275userType = domain === TelemetryUtils.INTERNAL_DOMAIN_SUFFIX ? TelemetryUtils.USERTYPE_INTERNAL : TelemetryUtils.USERTYPE_EXTERNAL;
276} else {
277userType = TelemetryUtils.USERTYPE_EXTERNAL;
278}
279
280TelemetryUtils.telemetrySettings.userType = userType;
281}
282
283return userType;
284}
285
286private static getRegistryValue(key: string, value: string, hive: string): Q.Promise<string> {
d976d077Meena Kunnathur Balakrishnan10 years ago287let deferred: Q.Deferred<string> = Q.defer<string>();
288let regKey = new winreg({
6cd1e194Meena Kunnathur Balakrishnan10 years ago289hive: hive,
290key: key
291});
292regKey.get(value, function (err: any, itemValue: winreg.RegistryItem) {
293if (err) {
294// Fail gracefully by returning null if there was an error.
295deferred.resolve(null);
d976d077Meena Kunnathur Balakrishnan10 years ago296} else {
6cd1e194Meena Kunnathur Balakrishnan10 years ago297deferred.resolve(itemValue.value);
298}
299});
300
301return deferred.promise;
302}
303
304/*
305* Load settings data from settingsHome/TelemetrySettings.json
306*/
307private static loadSettings(): ITelemetrySettings {
308try {
309TelemetryUtils.telemetrySettings = JSON.parse(<any>fs.readFileSync(TelemetryUtils.telemetrySettingsFile));
310} catch (e) {
311// if file does not exist or fails to parse then assume no settings are saved and start over
312TelemetryUtils.telemetrySettings = {};
313}
314
315return TelemetryUtils.telemetrySettings;
316}
317
318/*
319* Save settings data in settingsHome/TelemetrySettings.json
320*/
321private static saveSettings(): void {
322if (!fs.existsSync(TelemetryUtils.settingsHome)) {
323fs.mkdirSync(TelemetryUtils.settingsHome);
324}
325
326fs.writeFileSync(TelemetryUtils.telemetrySettingsFile, JSON.stringify(TelemetryUtils.telemetrySettings));
327}
328
329private static getUniqueId(regValue: string, regHive: string, fallback: () => string): Q.Promise<any> {
d976d077Meena Kunnathur Balakrishnan10 years ago330let uniqueId: string;
331if (os.platform() === "win32") {
6cd1e194Meena Kunnathur Balakrishnan10 years ago332return TelemetryUtils.getRegistryValue(TelemetryUtils.REGISTRY_SQMCLIENT_NODE, regValue, regHive)
333.then(function(id: string): Q.Promise<string> {
334if (id) {
d976d077Meena Kunnathur Balakrishnan10 years ago335uniqueId = id.replace(/[{}]/g, "");
6cd1e194Meena Kunnathur Balakrishnan10 years ago336return Q.resolve(uniqueId);
337} else {
338return Q.resolve(fallback());
339}
340});
341} else {
342return Q.resolve(fallback());
343}
344}
345
346private static generateMachineId(): Q.Promise<string> {
347return TelemetryUtils.getMacAddress().then((macAddress: string) => {
d976d077Meena Kunnathur Balakrishnan10 years ago348return crypto.createHash("sha256").update(macAddress, "utf8").digest("hex");
6cd1e194Meena Kunnathur Balakrishnan10 years ago349});
350}
351
352private static getMachineId(): Q.Promise<string> {
d976d077Meena Kunnathur Balakrishnan10 years ago353let machineId: string = TelemetryUtils.telemetrySettings.machineId;
6cd1e194Meena Kunnathur Balakrishnan10 years ago354if (!machineId) {
355return TelemetryUtils.generateMachineId()
356.then(function(id: string): Q.Promise<string> {
357TelemetryUtils.telemetrySettings.machineId = id;
358return Q.resolve(id);
359});
360} else {
361TelemetryUtils.telemetrySettings.machineId = machineId;
362return Q.resolve(machineId);
363}
364}
365
366private static getMacAddress(): Q.Promise<string> {
367// Return a mac address, or failing that, a unique ID
368// using getmac to attempt to match telemetry identifiers of vs code
369return Q.nfcall(getmac.getMac).catch(() => {
370return TelemetryUtils.generateGuid();
371});
372}
373
374private static getUserId(): Q.Promise<string> {
d976d077Meena Kunnathur Balakrishnan10 years ago375let userId: string = TelemetryUtils.telemetrySettings.userId;
6cd1e194Meena Kunnathur Balakrishnan10 years ago376if (!userId) {
377return TelemetryUtils.getUniqueId(TelemetryUtils.REGISTRY_USERID_VALUE, winreg.HKCU, TelemetryUtils.generateGuid)
378.then(function(id: string): Q.Promise<string> {
379TelemetryUtils.telemetrySettings.userId = id;
380return Q.resolve(id);
381});
382} else {
383TelemetryUtils.telemetrySettings.userId = userId;
384return Q.resolve(userId);
385}
386}
387};
388};