/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import getmac = require('getmac'); import crypto = require('crypto'); import * as nls from 'vs/nls'; import * as errors from 'vs/base/common/errors'; import * as uuid from 'vs/base/common/uuid'; import {MainTelemetryService} from 'vs/platform/telemetry/browser/mainTelemetryService'; import {ITelemetryService, ITelemetryInfo, ITelemetryServiceConfig} from 'vs/platform/telemetry/common/telemetry'; import {IStorageService} from 'vs/platform/storage/common/storage'; import {IConfigurationRegistry, Extensions} from 'vs/platform/configuration/common/configurationRegistry'; import {IConfigurationService, IConfigurationServiceEvent, ConfigurationServiceEventTypes} from 'vs/platform/configuration/common/configuration'; import {Registry} from 'vs/platform/platform'; const TELEMETRY_SECTION_ID = 'telemetry'; class StorageKeys { public static MachineId: string = 'telemetry.machineId'; public static InstanceId: string = 'telemetry.instanceId'; } interface ITelemetryEvent { eventName: string; data?: any; } export class ElectronTelemetryService extends MainTelemetryService implements ITelemetryService { private static MAX_BUFFER_SIZE = 100; private _setupIds: Promise; private _buffer: ITelemetryEvent[]; private _optInStatusLoaded: boolean; constructor(@IConfigurationService private configurationService: IConfigurationService, @IStorageService private storageService: IStorageService, config?: ITelemetryServiceConfig) { super(config); this._buffer = []; this._optInStatusLoaded = false; this.loadOptinSettings(); this._setupIds = this.setupIds(); } /** * override the base getTelemetryInfo to make sure this information is not retrieved before it's ready */ public getTelemetryInfo(): Promise { return this._setupIds; } /** * override the base publicLog to prevent reporting any events before the optIn status is read from configuration */ public publicLog(eventName:string, data?: any): void { if (this._optInStatusLoaded) { super.publicLog(eventName, data); } else { // in case loading configuration is delayed, make sure the buffer does not grow beyond MAX_BUFFER_SIZE if (this._buffer.length > ElectronTelemetryService.MAX_BUFFER_SIZE) { this._buffer = []; } this._buffer.push({eventName: eventName, data: data}); } } private flushBuffer(): void { let event: ITelemetryEvent = null; while(event = this._buffer.pop()) { super.publicLog(event.eventName, event.data); } } private loadOptinSettings(): void { this.configurationService.loadConfiguration(TELEMETRY_SECTION_ID).done(config => { this.config.userOptIn = config ? config.enableTelemetry : this.config.userOptIn; this._optInStatusLoaded = true; this.publicLog('optInStatus', {optIn: this.config.userOptIn}); this.flushBuffer(); }); this.toUnbind.push(this.configurationService.addListener(ConfigurationServiceEventTypes.UPDATED, (e: IConfigurationServiceEvent) => { this.config.userOptIn = e.config && e.config[TELEMETRY_SECTION_ID] ? e.config[TELEMETRY_SECTION_ID].enableTelemetry : this.config.userOptIn; })); } private setupIds(): Promise { return Promise.all([this.setupInstanceId(), this.setupMachineId()]).then(() => { return { machineId: this.machineId, instanceId: this.instanceId, sessionId: this.sessionId }; }); } private setupInstanceId(): Promise { let instanceId = this.storageService.get(StorageKeys.InstanceId); if (!instanceId) { instanceId = uuid.generateUuid(); this.storageService.store(StorageKeys.InstanceId, instanceId); } this.instanceId = instanceId; return Promise.resolve(this.instanceId); } private setupMachineId(): Promise { let machineId = this.storageService.get(StorageKeys.MachineId); if (machineId) { this.machineId = machineId; return Promise.resolve(this.machineId); } else { return new Promise((resolve, reject) => { try { // add a unique machine id as a hash of the macAddress getmac.getMac((error, macAddress) => { if (!error) { // crypt machine id machineId = crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'); } else { // generate a UUID machineId = uuid.generateUuid(); } this.machineId = machineId; this.storageService.store(StorageKeys.MachineId, machineId); resolve(this.machineId); }); } catch (err) { errors.onUnexpectedError(err); // generate a UUID machineId = uuid.generateUuid(); this.machineId = machineId; this.storageService.store(StorageKeys.MachineId, machineId); resolve(this.machineId); } }); } } } const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ 'id': TELEMETRY_SECTION_ID, 'order': 20, 'type': 'object', 'title': nls.localize('telemetryConfigurationTitle', "Telemetry configuration"), 'properties': { 'telemetry.enableTelemetry': { 'type': 'boolean', 'description': nls.localize('telemetry.enableTelemetry', "Enable usage data and errors to be sent to Microsoft."), 'default': true } } });