diff --git a/README.md b/README.md index b60331c..bfbcf1b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Your application will be launched and the VSCode debugger will attach. If you wa ### NativeScript commands -Type `NativeScript` in the Command Palette and you will see all available commands. +Type `NativeScript` in the Command Palette and you will see all available commands. ![NativeScript Commands](https://raw.githubusercontent.com/NativeScript/nativescript-vscode-extension/master/images/screenshots/nativescript-commands.png) @@ -50,7 +50,7 @@ If your version of NativeScript is incompatible with the extension you will see npm install npm run build # compiles TypeScript source files to JavaScript npm run package # produces nativescript-*.*.*.vsix in the root folder - ``` + ``` 3. To test the extension run the following commands in the root repository folder: @@ -60,6 +60,19 @@ If your version of NativeScript is incompatible with the extension you will see npm run launch-as-server # launches the debug adapter in server mode # execute this in a separate terminal npm run test-mac # run tests on ios device - ``` + ``` 4. To install the extension drag and drop the `nativescript-*.*.*.vsix` package in the VS Code. + +### How to disable the analytics +The anonymous usage data collected by Progress from the NativeScript extension for Visual Studio Code is used strictly to improve the product and its services, and enhance the overall user experience. + +If you have previously enabled the analytics option, you can disable it by following the steps outlined below: + +1. Open the Visual Studio Code Settings + - on Windows, select **File > Preferences > Settings** + - on macOS, select **Code > Preferences > Settings** +2. Add the following option (or update the existing one) to disable the analytics: +``` +"nativescript.analytics.enabled": false +``` \ No newline at end of file diff --git a/package.json b/package.json index 155ec0b..d562879 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "source-map": "0.6.1", "tree-kill": "^1.2.0", "universal-analytics": "0.4.13", + "uuid": "^3.2.1", "vscode-chrome-debug-core": "~3.9.0", "vscode-debugadapter": "1.26.0", "vscode-debugprotocol": "1.26.0", @@ -67,7 +68,7 @@ "properties": { "nativescript.analytics.enabled": { "type": "boolean", - "default": true, + "default": false, "description": "Enables the extension tracking." }, "nativescript.tnsPath": { diff --git a/src/analytics/analyticsBaseInfo.ts b/src/analytics/analyticsBaseInfo.ts index 5098a67..0de72b5 100644 --- a/src/analytics/analyticsBaseInfo.ts +++ b/src/analytics/analyticsBaseInfo.ts @@ -9,5 +9,5 @@ export interface AnalyticsBaseInfo { operatingSystem: OperatingSystem, cliVersion: string, extensionVersion: string, - userId: string + clientId: string } \ No newline at end of file diff --git a/src/analytics/analyticsService.ts b/src/analytics/analyticsService.ts index 511bc89..1eba74f 100644 --- a/src/analytics/analyticsService.ts +++ b/src/analytics/analyticsService.ts @@ -1,76 +1,114 @@ import * as os from 'os'; import { Version } from '../common/version'; import { GUAService } from './guaService'; -import { TelerikAnalyticsService } from './telerikAnalyticsService'; import { AnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; import { Services } from '../services/extensionHostServices'; import * as utils from '../common/utilities'; +import * as vscode from 'vscode'; +import * as uuid from "uuid"; export class AnalyticsService { + private static HAS_ANALYTICS_PROMPT_SHOWN_KEY = "nativescript.hasAnalyticsPromptShown"; + private static CLIENT_ID_KEY = "nativescript.analyticsClientId"; + private static ANALYTICS_PROMPT_MESSAGE = `Help us improve the NativeScript extension by allowing Progress to collect anonymous usage data. + For more information about the gathered information and how it is used, read our [privacy statement](https://www.telerik.com/about/privacy-policy). + You can [disable the analytics and data collection](https://github.com/NativeScript/nativescript-vscode-extension/blob/master/README.md#how-to-disable-the-analytics) at any given time. + Do you want to enable analytics?`; + private static ANALYTICS_PROMPT_ACCEPT_ACTION = "Yes"; + private static ANALYTICS_PROMPT_DENY_ACTION = "No"; + + private _globalState: vscode.Memento; private _baseInfo: AnalyticsBaseInfo; private _gua: GUAService; - private _ta: TelerikAnalyticsService; private _analyticsEnabled: boolean; - public static generateMachineId(): string { - let machineId = ''; - try { - let netInterfaces = os.networkInterfaces(); - Object.keys(netInterfaces).forEach(interfName => { - netInterfaces[interfName].forEach(interf => { - if (!interf.internal) { - machineId += `${interf.mac}-`; - } - }); - }); - } catch(e) {} - return machineId; - } + constructor(globalState: vscode.Memento) { + this._globalState = globalState; - constructor() { - this._analyticsEnabled = Services.workspaceConfigService().isAnalyticsEnabled; - let operatingSystem = OperatingSystem.Other; - switch(process.platform) { - case 'win32': { operatingSystem = OperatingSystem.Windows; break; } - case 'darwin': { operatingSystem = OperatingSystem.OSX; break; } - case 'linux': - case 'freebsd': { operatingSystem = OperatingSystem.Linux; break; } - }; + vscode.workspace.onDidChangeConfiguration(() => this.updateAnalyticsEnabled()); this._baseInfo = { cliVersion: Services.cli().version.toString(), extensionVersion: utils.getInstalledExtensionVersion().toString(), - operatingSystem: operatingSystem, - userId: AnalyticsService.generateMachineId() + operatingSystem: AnalyticsService.getOperatingSystem(), + clientId: this.getOrGenerateClientId() }; - - if(this._analyticsEnabled) { - this._gua = new GUAService('UA-111455-29', this._baseInfo); - this._ta = new TelerikAnalyticsService('b8b2e51f188f43e9b0dfb899f7b71cc6', this._baseInfo); - } } public launchDebugger(request: string, platform: string): Promise { if(this._analyticsEnabled) { try { - return Promise.all([ - this._gua.launchDebugger(request, platform), - this._ta.launchDebugger(request, platform) - ]); + return this._gua.launchDebugger(request, platform); } catch(e) {} } + return Promise.resolve(); } public runRunCommand(platform: string): Promise { if(this._analyticsEnabled) { try { - return Promise.all([ - this._gua.runRunCommand(platform), - this._ta.runRunCommand(platform) - ]); + return this._gua.runRunCommand(platform); } catch(e) { } } + return Promise.resolve(); } + + private static getOperatingSystem() : OperatingSystem { + switch(process.platform) { + case 'win32': + return OperatingSystem.Windows; + case 'darwin': + return OperatingSystem.OSX; + case 'linux': + case 'freebsd': + return OperatingSystem.Linux; + default: + return OperatingSystem.Other; + }; + } + + public initialize() : void { + const hasAnalyticsPromptShown = this._globalState.get(AnalyticsService.HAS_ANALYTICS_PROMPT_SHOWN_KEY); + if(!hasAnalyticsPromptShown) { + vscode.window.showInformationMessage(AnalyticsService.ANALYTICS_PROMPT_MESSAGE, + AnalyticsService.ANALYTICS_PROMPT_ACCEPT_ACTION, + AnalyticsService.ANALYTICS_PROMPT_DENY_ACTION + ) + .then(result => this.onAnalyticsMessageConfirmation(result)); + + return; + } + + this.updateAnalyticsEnabled(); + } + + private getOrGenerateClientId(): string { + let clientId = this._globalState.get(AnalyticsService.CLIENT_ID_KEY); + + if(!clientId) { + clientId = uuid.v4(); + this._globalState.update(AnalyticsService.CLIENT_ID_KEY, clientId); + } + + return clientId; + } + + private onAnalyticsMessageConfirmation(result: string) : void { + const shouldEnableAnalytics = result === AnalyticsService.ANALYTICS_PROMPT_ACCEPT_ACTION ? true : false; + + this._globalState.update(AnalyticsService.HAS_ANALYTICS_PROMPT_SHOWN_KEY, true); + + Services.workspaceConfigService().isAnalyticsEnabled = shouldEnableAnalytics; + this.updateAnalyticsEnabled(); + } + + private updateAnalyticsEnabled() { + this._analyticsEnabled = Services.workspaceConfigService().isAnalyticsEnabled; + + if(this._analyticsEnabled && !this._gua) { + this._gua = new GUAService('UA-111455-29', this._baseInfo); + } + } } \ No newline at end of file diff --git a/src/analytics/eqatecMonitor.min.js b/src/analytics/eqatecMonitor.min.js deleted file mode 100755 index f87f322..0000000 --- a/src/analytics/eqatecMonitor.min.js +++ /dev/null @@ -1,30 +0,0 @@ -(function(k){function z(b){var a=!1,f="",e=0,d=1,g={},h=[],i=[],l=function(a){if(c.isString(a)){var f=c.trim(c.asString(a));if(f){if(c.stringTooLong(b,f,1E3))return null;var a="eq_"+a,e=g[a];e||(e={featureName:f,sessionHitCount:0,timingStart:0},g[a]=e);return e}return null}};return{addFeatureUsage:function(a){if(a=l(a))a.sessionHitCount+=1,d++},startFeatureTiming:function(a){if((a=l(a))&&!(0b++;a+=51*b&52?(b^15?8^Math.random()*(b^20?16:4):4).toString(16):"-");return a},setCookie:function(b,a,f,e,d){var g=document;g&&(b=b+"="+a+";path="+f+";",e&&0f?(c.error(b,"Input is too long: "+a),!0):!1}};(function(){function b(a){return 10>a?"0"+a:a}function a(a){d.lastIndex=0;return d.test(a)?'"'+a.replace(d,function(a){var b=i[a];return"string"===typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+ -'"':'"'+a+'"'}function f(b,c){var e,d,i,k,n=g,m,j=c[b];j&&"object"===typeof j&&"function"===typeof j.toJSON&&(j=j.toJSON(b));"function"===typeof l&&(j=l.call(c,b,j));switch(typeof j){case "string":return a(j);case "number":return isFinite(j)?""+j:"null";case "boolean":case "null":return""+j;case "object":if(!j)return"null";g+=h;m=[];if("[object Array]"===Object.prototype.toString.apply(j)){k=j.length;for(e=0;e\s*\(/gm,"{anonymous}()@").split("\n")},firefox:function(a){return a.stack.replace(/(?:\n@:0)?\s+$/m,"").replace(/^\(/gm,"{anonymous}(").split("\n")}, -opera10:function(a){var a=a.stacktrace.split("\n"),b=/.*line (\d+), column (\d+) in ((/g,"{anonymous}");a[d++]=i+"@"+h}a.splice(d,a.length-d);return a},opera:function(a){var a=a.message.split("\n"),b=/Line\s+(\d+).*script\s+(http\S+)(?:.*in\s+function\s+(\S+))?/i,c,d,g;c=4; -d=0;for(g=a.length;cc.length;){g=b.test(a.toString());h=[];try{h=Array.prototype.slice.call(a.arguments),a=a.caller}catch(i){a=null}g=g?RegExp.$1||"{anonymous}":"{anonymous}";c[d++]=g+"("+this.stringifyArguments(h)+")";if(!a)break}return c}, -stringifyArguments:function(a){for(var b=0;bc.length?"["+this.stringifyArguments(c)+"]":"["+this.stringifyArguments(Array.prototype.slice.call(c,0,1))+"..."+this.stringifyArguments(Array.prototype.slice.call(c,-1))+"]":c.constructor===Object?a[b]="#object":c.constructor===Function?a[b]="#function":c.constructor===String&&(a[b]='"'+c+'"'))}return a.join(",")}};c.stackTrace=b})(); -var t=k._eqatec||{};k._eqatec=t;t.createMonitor=function(b){var a=0,f="",e=!1,d={logError:function(){},logMessage:function(){}},g=0,h=!1,i=!1,l=0,t="",r="",w="",s={},x=!1,u=!0,n=!1,m=void 0,j=!1,y="",o={};try{b=b||{},f=c.trim(c.asString(b.productId))||"",d=b.loggingInterface||d,w=b.version,s=b.location||{},x=c.cookiesEnabled()&&b.useCookies,u=b.useHttps,m=b.userAgent,n=b.testMode,j=c.trackingDisabled(),y=c.getServerUri(d,f,b.serverUri,u),o=z(d),(32>f.length||36r.length||36 any; constructor(trackingId: string, baseInfo: AnalyticsBaseInfo) { - this._visitor = ua(trackingId, baseInfo.userId, { requestOptions: {}, strictCidFormat: false }); + this._visitor = ua(trackingId, baseInfo.clientId, { requestOptions: {}, strictCidFormat: false }); this._getBasePayload = () => { return { - uid: baseInfo.userId, + cid: baseInfo.clientId, dh: 'ns-vs-extension.org', cd5: baseInfo.cliVersion, cd6: OperatingSystem[baseInfo.operatingSystem], diff --git a/src/analytics/telerikAnalyticsService.ts b/src/analytics/telerikAnalyticsService.ts deleted file mode 100644 index 20b728a..0000000 --- a/src/analytics/telerikAnalyticsService.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { AnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; -import * as os from 'os'; - -// Hack needed for the Telerik Analytics JavaScript monitor to work in node environment -(global as any).XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; -(global as any).XMLHttpRequest.prototype.withCredentials = false; - -function capitalizeFirstLetter(str) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - -export class TelerikAnalyticsService { - - private _eqatecMonitor: any; - - private static getUserAgentString(): string { - let userAgentString: string; - let osType = os.type(); - if(osType === "Windows_NT") { - userAgentString = "(Windows NT " + os.release() + ")"; - } else if(osType === "Darwin") { - userAgentString = "(Mac OS X " + os.release() + ")"; - } else { - userAgentString = "(" + osType +")"; - } - - return userAgentString; - } - - constructor(projectKey: string, baseInfo: AnalyticsBaseInfo) { - require("./eqatecMonitor.min"); - let eqatec = (global as any)._eqatec; - let settings = eqatec.createSettings(projectKey); - settings.useHttps = false; - settings.userAgent = TelerikAnalyticsService.getUserAgentString(); - settings.version = baseInfo.extensionVersion; - settings.useCookies = false; - - /* - settings.loggingInterface = { - logMessage: console.log, - logError: console.log - }; - */ - - //settings.testMode = true; - this._eqatecMonitor = eqatec.createMonitor(settings); - this._eqatecMonitor.setInstallationID(baseInfo.userId); - this._eqatecMonitor.setUserID(baseInfo.userId); - this._eqatecMonitor.start(); - process.on('exit', () => { - this._eqatecMonitor.stop(); - }); - this._eqatecMonitor.trackFeature(`CLIVersion.${baseInfo.cliVersion}`); - this._eqatecMonitor.trackFeature(`ExtensionVersion.${baseInfo.extensionVersion}`); - } - - public launchDebugger(request: string, platform: string): Promise { - this._eqatecMonitor.trackFeature(`${capitalizeFirstLetter(request)}.${capitalizeFirstLetter(platform)}`); - return Promise.resolve(); - } - - public runRunCommand(platform: string): Promise { - this._eqatecMonitor.trackFeature(`Run.${capitalizeFirstLetter(platform)}`); - return Promise.resolve(); - } -} \ No newline at end of file diff --git a/src/common/workspaceConfigService.ts b/src/common/workspaceConfigService.ts index d2a6d10..862ef29 100644 --- a/src/common/workspaceConfigService.ts +++ b/src/common/workspaceConfigService.ts @@ -5,6 +5,10 @@ export class WorkspaceConfigService { return vscode.workspace.getConfiguration('nativescript').get('analytics.enabled') as boolean; } + public set isAnalyticsEnabled(isAnalyticsEnabled: boolean) { + vscode.workspace.getConfiguration('nativescript').update('analytics.enabled', isAnalyticsEnabled, vscode.ConfigurationTarget.Global); + } + public get tnsPath(): string { return vscode.workspace.getConfiguration('nativescript').get('tnsPath') as string; } diff --git a/src/main.ts b/src/main.ts index 72de81f..e1c19a3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,7 @@ export function activate(context: vscode.ExtensionContext) { Services.globalState = context.globalState; Services.cliPath = Services.workspaceConfigService().tnsPath || Services.cliPath; Services.extensionServer().start(); + Services.analyticsService().initialize(); // Check for newer extension version Services.extensionVersionService().isLatestInstalled.then(result => { diff --git a/src/services/extensionHostServices.ts b/src/services/extensionHostServices.ts index 4f7ba9a..7885918 100644 --- a/src/services/extensionHostServices.ts +++ b/src/services/extensionHostServices.ts @@ -36,7 +36,7 @@ export class ExtensionHostServices extends BaseServices { } public analyticsService(): AnalyticsService { - this._analyticsService = this._analyticsService || new AnalyticsService(); + this._analyticsService = this._analyticsService || new AnalyticsService(this.globalState); return this._analyticsService; } } diff --git a/src/tsconfig.json b/src/tsconfig.json index 624fcdd..7598b3c 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -22,7 +22,6 @@ "./debug-adapter/webKitDebug.ts", "./main.ts", - "./tests/adapter.test.ts", - "./analytics/eqatecMonitor.min.js" + "./tests/adapter.test.ts" ] } \ No newline at end of file