diff --git a/.eslintrc.json b/.eslintrc.json index fa9e0d1ba89..a02921b9f6b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -210,7 +210,8 @@ // - electron-browser "when": "hasBrowser", "allow": [ - "vs/css!./**/*" + "vs/css!./**/*", + "@microsoft/applicationinsights-web" ] }, { @@ -220,7 +221,6 @@ // - electron-main "when": "hasNode", "allow": [ - "@microsoft/applicationinsights-web", "@parcel/watcher", "@vscode/sqlite3", "@vscode/vscode-languagedetection", @@ -445,8 +445,7 @@ }, // TODO@layers "tas-client-umd", // node module allowed even in /common/ "vscode-textmate", // node module allowed even in /common/ - "@vscode/vscode-languagedetection", // node module allowed even in /common/ - "@microsoft/applicationinsights-web" // node module allowed even in /common/ + "@vscode/vscode-languagedetection" // node module allowed even in /common/ ] }, { diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 628e877dcd4..3d942854f5d 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -30,6 +30,7 @@ "node_modules/*", "vs/platform/files/browser/htmlFileSystemProvider.ts", "vs/platform/files/browser/webFileSystemAccess.ts", + "vs/platform/telemetry/browser/*", "vs/platform/assignment/*" ] } diff --git a/src/vs/platform/telemetry/browser/appInsightsAppender.ts b/src/vs/platform/telemetry/browser/appInsightsAppender.ts new file mode 100644 index 00000000000..2b3f099cf83 --- /dev/null +++ b/src/vs/platform/telemetry/browser/appInsightsAppender.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { ApplicationInsights } from '@microsoft/applicationinsights-web'; +import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; + +export class WebAppInsightsAppender implements ITelemetryAppender { + private _aiClient: ApplicationInsights | undefined; + private _aiClientLoaded = false; + private _telemetryCache: { eventName: string; data: any }[] = []; + + constructor(private _eventPrefix: string, aiKey: string) { + const endpointUrl = 'https://mobile.events.data.microsoft.com/collect/v1'; + import('@microsoft/applicationinsights-web').then(aiLibrary => { + this._aiClient = new aiLibrary.ApplicationInsights({ + config: { + instrumentationKey: aiKey, + endpointUrl, + disableAjaxTracking: true, + disableExceptionTracking: true, + disableFetchTracking: true, + disableCorrelationHeaders: true, + disableCookiesUsage: true, + autoTrackPageVisitTime: false, + emitLineDelimitedJson: true, + }, + }); + this._aiClient.loadAppInsights(); + // Client is loaded we can now flush the cached events + this._aiClientLoaded = true; + this._telemetryCache.forEach(cacheEntry => this.log(cacheEntry.eventName, cacheEntry.data)); + this._telemetryCache = []; + + // If we cannot access the endpoint this most likely means it's being blocked + // and we should not attempt to send any telemetry. + fetch(endpointUrl, { method: 'POST' }).catch(() => (this._aiClient = undefined)); + }).catch(err => { + console.error(err); + }); + } + + /** + * Logs a telemetry event with eventName and data + * @param eventName The event name + * @param data The data associated with the events + */ + public log(eventName: string, data: any): void { + if (!this._aiClient && this._aiClientLoaded) { + return; + } else if (!this._aiClient && !this._aiClientLoaded) { + this._telemetryCache.push({ eventName, data }); + return; + } + + data = validateTelemetryData(data); + + // Web does not expect properties and measurements so we must + // spread them out. This is different from desktop which expects them + data = { ...data.properties, ...data.measurements }; + + // undefined assertion is ok since above two if statements cover both cases + this._aiClient!.trackEvent({ name: this._eventPrefix + '/' + eventName }, data); + } + + /** + * Flushes all the telemetry data still in the buffer + */ + public flush(): Promise { + if (this._aiClient) { + this._aiClient.flush(); + this._aiClient = undefined; + } + return Promise.resolve(undefined); + } +} diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index d720696d8cb..bd161f54f33 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { ApplicationInsights } from '@microsoft/applicationinsights-web'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservableValue } from 'vs/base/common/observableValue'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -11,98 +10,16 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { WebAppInsightsAppender } from 'vs/platform/telemetry/browser/appInsightsAppender'; import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { ITelemetryServiceConfig, TelemetryService as BaseTelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { ITelemetryAppender, NullTelemetryService, supportsTelemetry, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; +import { ITelemetryAppender, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/browser/workbenchCommonProperties'; -class WebAppInsightsAppender implements ITelemetryAppender { - private _aiClient: ApplicationInsights | undefined; - private _aiClientLoaded = false; - private _telemetryCache: { eventName: string; data: any }[] = []; - - constructor(private _eventPrefix: string, aiKey: string) { - const endpointUrl = 'https://mobile.events.data.microsoft.com/collect/v1'; - import('@microsoft/applicationinsights-web').then(aiLibrary => { - this._aiClient = new aiLibrary.ApplicationInsights({ - config: { - instrumentationKey: aiKey, - endpointUrl, - disableAjaxTracking: true, - disableExceptionTracking: true, - disableFetchTracking: true, - disableCorrelationHeaders: true, - disableCookiesUsage: true, - autoTrackPageVisitTime: false, - emitLineDelimitedJson: true, - }, - }); - this._aiClient.loadAppInsights(); - // Client is loaded we can now flush the cached events - this._aiClientLoaded = true; - this._telemetryCache.forEach(cacheEntry => this.log(cacheEntry.eventName, cacheEntry.data)); - this._telemetryCache = []; - - // If we cannot access the endpoint this most likely means it's being blocked - // and we should not attempt to send any telemetry. - fetch(endpointUrl, { method: 'POST' }).catch(() => (this._aiClient = undefined)); - }).catch(err => { - console.error(err); - }); - } - - /** - * Logs a telemetry event with eventName and data - * @param eventName The event name - * @param data The data associated with the events - */ - public log(eventName: string, data: any): void { - if (!this._aiClient && this._aiClientLoaded) { - return; - } else if (!this._aiClient && !this._aiClientLoaded) { - this._telemetryCache.push({ eventName, data }); - return; - } - - data = validateTelemetryData(data); - - // Web does not expect properties and measurements so we must - // spread them out. This is different from desktop which expects them - data = { ...data.properties, ...data.measurements }; - - // undefined assertion is ok since above two if statements cover both cases - this._aiClient!.trackEvent({ name: this._eventPrefix + '/' + eventName }, data); - } - - /** - * Flushes all the telemetry data still in the buffer - */ - public flush(): Promise { - if (this._aiClient) { - this._aiClient.flush(); - this._aiClient = undefined; - } - return Promise.resolve(undefined); - } -} - -class WebTelemetryAppender implements ITelemetryAppender { - - constructor(private _appender: ITelemetryAppender) { } - - log(eventName: string, data: any): void { - this._appender.log(eventName, data); - } - - flush(): Promise { - return this._appender.flush(); - } -} - export class TelemetryService extends Disposable implements ITelemetryService { declare readonly _serviceBrand: undefined; @@ -124,7 +41,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { // If remote server is present send telemetry through that, else use the client side appender const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new WebAppInsightsAppender('monacoworkbench', productService.aiConfig?.asimovKey); const config: ITelemetryServiceConfig = { - appenders: [new WebTelemetryAppender(telemetryProvider), new TelemetryLogAppender(loggerService, environmentService)], + appenders: [telemetryProvider, new TelemetryLogAppender(loggerService, environmentService)], commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.remoteAuthority, productService.embedderIdentifier, productService.removeTelemetryMachineId, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), sendErrorTelemetry: this.sendErrorTelemetry, };