11import * as path from 'path' ;
22import * as vscode from 'vscode' ;
33import WebSocket from 'ws' ;
4+ import TelemetryReporter from './telemetry' ;
45import QuickPickItem = vscode . QuickPickItem ;
56import * as utils from './utils' ;
67
7- let settings : vscode . WorkspaceConfiguration ;
8- let hostname : string ;
9- let port : number ;
8+ interface IPackageInfo {
9+ name : string ;
10+ version : string ;
11+ aiKey : string ;
12+ }
13+
1014const debuggerType : string = 'devtools-for-chrome' ;
15+ let telemetryReporter : TelemetryReporter ;
1116
1217export function activate ( context : vscode . ExtensionContext ) {
13- settings = vscode . workspace . getConfiguration ( 'vscode-devtools-for-chrome' ) ;
14- hostname = settings . get ( 'hostname' ) as string || 'localhost' ;
15- port = settings . get ( 'port' ) as number || 9222 ;
18+
19+ const packageInfo = getPackageInfo ( context ) ;
20+ if ( packageInfo && vscode . env . machineId !== 'someValue.machineId' ) {
21+ // Use the real telemetry reporter
22+ telemetryReporter = new TelemetryReporter ( packageInfo . name , packageInfo . version , packageInfo . aiKey ) ;
23+ } else {
24+ // Fallback to a fake telemetry reporter
25+ telemetryReporter = new DebugTelemetryReporter ( ) ;
26+ }
27+ context . subscriptions . push ( telemetryReporter ) ;
1628
1729 context . subscriptions . push ( vscode . commands . registerCommand ( 'devtools-for-chrome.launch' , async ( ) => {
1830 launch ( context ) ;
1931 } ) ) ;
2032
2133 context . subscriptions . push ( vscode . commands . registerCommand ( 'devtools-for-chrome.attach' , async ( ) => {
22- attach ( context ) ;
34+ attach ( context , /* viaConfig= */ false ) ;
2335 } ) ) ;
2436
2537 vscode . debug . registerDebugConfigurationProvider ( debuggerType , {
@@ -35,7 +47,7 @@ export function activate(context: vscode.ExtensionContext) {
3547 resolveDebugConfiguration ( folder : vscode . WorkspaceFolder | undefined , config : vscode . DebugConfiguration , token ?: vscode . CancellationToken ) : vscode . ProviderResult < vscode . DebugConfiguration > {
3648 if ( config && config . type == debuggerType ) {
3749 if ( config . request && config . request . localeCompare ( 'attach' , 'en' , { sensitivity : 'base' } ) == 0 ) {
38- attach ( context ) ;
50+ attach ( context , /* viaConfig= */ true ) ;
3951 } else {
4052 let launchUri : string = '' ;
4153 if ( folder . uri . scheme == 'file' ) {
@@ -56,13 +68,19 @@ export function activate(context: vscode.ExtensionContext) {
5668}
5769
5870async function launch ( context : vscode . ExtensionContext , launchUrl ?: string , chromePathFromLaunchConfig ?: string ) {
59- const portFree = await utils . isPortFree ( hostname , port ) ;
71+ const viaConfig = ! ! ( launchUrl || chromePathFromLaunchConfig ) ;
72+ const telemetryProps = { viaConfig : `${ viaConfig } ` } ;
73+ telemetryReporter . sendTelemetryEvent ( 'launch' , telemetryProps ) ;
6074
75+ const { hostname, port } = getSettings ( ) ;
76+ const portFree = await utils . isPortFree ( hostname , port ) ;
6177 if ( portFree ) {
78+ const settings = vscode . workspace . getConfiguration ( 'vscode-devtools-for-chrome' ) ;
6279 const pathToChrome = settings . get ( 'chromePath' ) as string || chromePathFromLaunchConfig || utils . getPathToChrome ( ) ;
6380
6481 if ( ! pathToChrome || ! utils . existsSync ( pathToChrome ) ) {
6582 vscode . window . showErrorMessage ( 'Chrome was not found. Chrome must be installed for this extension to function. If you have Chrome installed at a custom location you can specify it in the \'chromePath\' setting.' ) ;
83+ telemetryReporter . sendTelemetryEvent ( 'launch/error/chrome_not_found' , telemetryProps ) ;
6684 return ;
6785 }
6886
@@ -73,16 +91,22 @@ async function launch(context: vscode.ExtensionContext, launchUrl?: string, chro
7391
7492 if ( ! target || ! target . webSocketDebuggerUrl || target . webSocketDebuggerUrl == '' ) {
7593 vscode . window . showErrorMessage ( `Could not find the launched Chrome tab: (${ launchUrl } ).` ) ;
76- attach ( context ) ;
94+ telemetryReporter . sendTelemetryEvent ( 'launch/error/tab_not_found' , telemetryProps ) ;
95+ attach ( context , viaConfig ) ;
7796 } else {
78- DevToolsPanel . createOrShow ( context . extensionPath , target . webSocketDebuggerUrl ) ;
97+ DevToolsPanel . createOrShow ( context , target . webSocketDebuggerUrl ) ;
7998 }
8099}
81100
82- async function attach ( context : vscode . ExtensionContext ) {
83- const responseArray = await getListOfTargets ( ) ;
101+ async function attach ( context : vscode . ExtensionContext , viaConfig : boolean ) {
102+ const telemetryProps = { viaConfig : `${ viaConfig } ` } ;
103+ telemetryReporter . sendTelemetryEvent ( 'attach' , telemetryProps ) ;
84104
105+ const { hostname, port } = getSettings ( ) ;
106+ const responseArray = await getListOfTargets ( hostname , port ) ;
85107 if ( Array . isArray ( responseArray ) ) {
108+ telemetryReporter . sendTelemetryEvent ( 'attach/list' , telemetryProps , { targetCount : responseArray . length } ) ;
109+
86110 const items : QuickPickItem [ ] = [ ] ;
87111
88112 responseArray . forEach ( i => {
@@ -92,56 +116,86 @@ async function attach(context: vscode.ExtensionContext) {
92116
93117 vscode . window . showQuickPick ( items ) . then ( ( selection ) => {
94118 if ( selection ) {
95- DevToolsPanel . createOrShow ( context . extensionPath , selection . detail as string ) ;
119+ DevToolsPanel . createOrShow ( context , selection . detail as string ) ;
96120 }
97121 } ) ;
122+ } else {
123+ telemetryReporter . sendTelemetryEvent ( 'attach/error/no_json_array' , telemetryProps ) ;
124+ }
125+ }
126+
127+ function getSettings ( ) : { hostname : string , port : number } {
128+ const settings = vscode . workspace . getConfiguration ( 'vscode-devtools-for-chrome' ) ;
129+ const hostname = settings . get ( 'hostname' ) as string || 'localhost' ;
130+ const port = settings . get ( 'port' ) as number || 9222 ;
131+
132+ return { hostname, port } ;
133+ }
134+
135+ function getPackageInfo ( context : vscode . ExtensionContext ) : IPackageInfo {
136+ const extensionPackage = require ( context . asAbsolutePath ( './package.json' ) ) ;
137+ if ( extensionPackage ) {
138+ return {
139+ name : extensionPackage . name ,
140+ version : extensionPackage . version ,
141+ aiKey : extensionPackage . aiKey
142+ } ;
98143 }
144+ return undefined ;
99145}
100146
101- async function getListOfTargets ( ) : Promise < Array < any > > {
147+ async function getListOfTargets ( hostname : string , port : number ) : Promise < Array < any > > {
102148 const checkDiscoveryEndpoint = ( url : string ) => {
103149 return utils . getURL ( url , { headers : { Host : 'localhost' } } ) ;
104150 } ;
105151
106152 const jsonResponse = await checkDiscoveryEndpoint ( `http://${ hostname } :${ port } /json/list` )
107153 . catch ( ( ) => checkDiscoveryEndpoint ( `http://${ hostname } :${ port } /json` ) ) ;
108154
109- return JSON . parse ( jsonResponse ) ;
155+ let result : Array < string > ;
156+ try {
157+ result = JSON . parse ( jsonResponse ) ;
158+ } catch ( ex ) {
159+ result = undefined ;
160+ }
161+ return result ;
110162}
111163
112164class DevToolsPanel {
113165 private static currentPanel : DevToolsPanel ;
114166 private readonly _panel : vscode . WebviewPanel ;
167+ private readonly _context : vscode . ExtensionContext ;
115168 private readonly _extensionPath : string ;
116169 private readonly _targetUrl : string ;
117170 private _socket : WebSocket = undefined ;
118171 private _isConnected : boolean = false ;
119172 private _messages : any [ ] = [ ] ;
120173 private _disposables : vscode . Disposable [ ] = [ ] ;
121174
122- public static createOrShow ( extensionPath : string , targetUrl : string ) {
123- const column = vscode . window . activeTextEditor ? vscode . window . activeTextEditor . viewColumn : undefined ;
175+ public static createOrShow ( context : vscode . ExtensionContext , targetUrl : string ) {
176+ const column = vscode . ViewColumn . Beside ;
124177
125178 if ( DevToolsPanel . currentPanel ) {
126179 DevToolsPanel . currentPanel . _panel . reveal ( column ) ;
127180 } else {
128- const panel = vscode . window . createWebviewPanel ( 'devtools-for-chrome' , 'DevTools' , column || vscode . ViewColumn . Two , {
181+ const panel = vscode . window . createWebviewPanel ( 'devtools-for-chrome' , 'DevTools' , column , {
129182 enableScripts : true ,
130183 enableCommandUris : true ,
131184 retainContextWhenHidden : true
132185 } ) ;
133186
134- DevToolsPanel . currentPanel = new DevToolsPanel ( panel , extensionPath , targetUrl ) ;
187+ DevToolsPanel . currentPanel = new DevToolsPanel ( panel , context , targetUrl ) ;
135188 }
136189 }
137190
138- public static revive ( panel : vscode . WebviewPanel , extensionPath : string , targetUrl : string ) {
139- DevToolsPanel . currentPanel = new DevToolsPanel ( panel , extensionPath , targetUrl ) ;
191+ public static revive ( panel : vscode . WebviewPanel , context : vscode . ExtensionContext , targetUrl : string ) {
192+ DevToolsPanel . currentPanel = new DevToolsPanel ( panel , context , targetUrl ) ;
140193 }
141194
142- private constructor ( panel : vscode . WebviewPanel , extensionPath : string , targetUrl : string ) {
195+ private constructor ( panel : vscode . WebviewPanel , context : vscode . ExtensionContext , targetUrl : string ) {
143196 this . _panel = panel ;
144- this . _extensionPath = extensionPath ;
197+ this . _context = context ;
198+ this . _extensionPath = context . extensionPath ;
145199 this . _targetUrl = targetUrl ;
146200
147201 this . _update ( ) ;
@@ -181,18 +235,28 @@ class DevToolsPanel {
181235 private _disposeSocket ( ) {
182236 if ( this . _socket ) {
183237 // Reset the socket since the devtools have been reloaded
184- this . _socket . onerror = undefined ;
238+ telemetryReporter . sendTelemetryEvent ( 'websocket/dispose' ) ;
185239 this . _socket . onopen = undefined ;
186- this . _socket . onclose = undefined ;
187240 this . _socket . onmessage = undefined ;
241+ this . _socket . onerror = undefined ;
242+ this . _socket . onclose = undefined ;
188243 this . _socket . close ( ) ;
189244 this . _socket = undefined ;
190245 }
191246 }
192247
193248 private _onMessageFromWebview ( message : string ) {
194249 if ( message === 'ready' ) {
250+ if ( this . _socket ) {
251+ telemetryReporter . sendTelemetryEvent ( 'websocket/reconnect' ) ;
252+ }
195253 this . _disposeSocket ( ) ;
254+ } else if ( message . substr ( 0 , 10 ) === 'telemetry:' ) {
255+ return this . _sendTelemetryMessage ( message . substr ( 10 ) ) ;
256+ } else if ( message . substr ( 0 , 9 ) === 'getState:' ) {
257+ return this . _getDevtoolsState ( ) ;
258+ } else if ( message . substr ( 0 , 9 ) === 'setState:' ) {
259+ return this . _setDevtoolsState ( message . substr ( 9 ) ) ;
196260 }
197261
198262 if ( ! this . _socket ) {
@@ -212,22 +276,16 @@ class DevToolsPanel {
212276
213277 // Create the websocket
214278 this . _socket = new WebSocket ( url ) ;
215- this . _socket . onerror = this . _onError . bind ( this ) ;
216279 this . _socket . onopen = this . _onOpen . bind ( this ) ;
217280 this . _socket . onmessage = this . _onMessage . bind ( this ) ;
281+ this . _socket . onerror = this . _onError . bind ( this ) ;
218282 this . _socket . onclose = this . _onClose . bind ( this ) ;
219283 }
220284
221- private _onError ( ) {
222- if ( this . _isConnected ) {
223- // Tell the devtools that there was a connection error
224- this . _panel . webview . postMessage ( 'error' ) ;
225- }
226- }
227-
228285 private _onOpen ( ) {
229286 this . _isConnected = true ;
230287 // Tell the devtools that the real websocket was opened
288+ telemetryReporter . sendTelemetryEvent ( 'websocket/open' ) ;
231289 this . _panel . webview . postMessage ( 'open' ) ;
232290
233291 if ( this . _socket ) {
@@ -246,14 +304,48 @@ class DevToolsPanel {
246304 }
247305 }
248306
307+ private _onError ( ) {
308+ if ( this . _isConnected ) {
309+ // Tell the devtools that there was a connection error
310+ telemetryReporter . sendTelemetryEvent ( 'websocket/error' ) ;
311+ this . _panel . webview . postMessage ( 'error' ) ;
312+ }
313+ }
314+
249315 private _onClose ( ) {
250316 if ( this . _isConnected ) {
251317 // Tell the devtools that the real websocket was closed
318+ telemetryReporter . sendTelemetryEvent ( 'websocket/close' ) ;
252319 this . _panel . webview . postMessage ( 'close' ) ;
253320 }
254321 this . _isConnected = false ;
255322 }
256323
324+ private _sendTelemetryMessage ( message : string ) {
325+ const telemetry = JSON . parse ( message ) ;
326+ telemetryReporter . sendTelemetryEvent ( telemetry . name , telemetry . properties , telemetry . metrics ) ;
327+ }
328+
329+ private _getDevtoolsState ( ) {
330+ const allPrefsKey = 'devtools-preferences' ;
331+ const allPrefs : any = this . _context . workspaceState . get ( allPrefsKey ) ||
332+ {
333+ uiTheme : '"dark"' ,
334+ screencastEnabled : false
335+ } ;
336+ this . _panel . webview . postMessage ( `preferences:${ JSON . stringify ( allPrefs ) } ` ) ;
337+ }
338+
339+ private _setDevtoolsState ( state : string ) {
340+ // Parse the preference from the message and store it
341+ const pref = JSON . parse ( state ) as { name : string , value : string } ;
342+
343+ const allPrefsKey = 'devtools-preferences' ;
344+ const allPrefs : any = this . _context . workspaceState . get ( allPrefsKey ) || { } ;
345+ allPrefs [ pref . name ] = pref . value ;
346+ this . _context . workspaceState . update ( allPrefsKey , allPrefs ) ;
347+ }
348+
257349 private _update ( ) {
258350 this . _panel . webview . html = this . _getHtmlForWebview ( ) ;
259351 }
@@ -271,9 +363,10 @@ class DevToolsPanel {
271363 <head>
272364 <meta http-equiv="content-type" content="text/html; charset=utf-8">
273365 <style>
274- html, body {
366+ html, body, iframe {
275367 height: 100%;
276368 width: 100%;
369+ position: absolute;
277370 padding: 0;
278371 margin: 0;
279372 overflow: hidden;
@@ -285,4 +378,18 @@ class DevToolsPanel {
285378 </html>
286379 ` ;
287380 }
381+ }
382+
383+ class DebugTelemetryReporter extends TelemetryReporter {
384+ constructor ( ) {
385+ super ( 'extensionId' , 'extensionVersion' , 'key' ) ;
386+ }
387+
388+ public sendTelemetryEvent ( name : string , properties ?: any , measurements ?: any ) {
389+ console . log ( `${ name } : ${ JSON . stringify ( properties ) } , ${ JSON . stringify ( properties ) } ` ) ;
390+ }
391+
392+ public dispose ( ) : Promise < any > {
393+ return Promise . resolve ( ) ;
394+ }
288395}
0 commit comments