Skip to content
This repository was archived by the owner on Sep 17, 2021. It is now read-only.

Commit 32a43ba

Browse files
authored
Merge pull request #6 from CodeMooseUS/telemetry
Adding anonymous telemetry using vscode-extension-telemetry package
2 parents c6ce493 + bc5358c commit 32a43ba

File tree

8 files changed

+362
-53
lines changed

8 files changed

+362
-53
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ A VSCode extension to host the chrome devtools inside of a webview.
88
</a>
99
</p>
1010

11-
## Attach to a running chrome instance:
11+
## Attaching to a running chrome instance:
1212
![Demo1](demo.gif)
1313

14-
## Launch a chrome debugger project and use screencasting:
14+
## Launching a 'debugger for chrome' project and using screencast:
1515
![Demo2](demo2.gif)
1616

1717
# Using the extension
@@ -32,7 +32,9 @@ A VSCode extension to host the chrome devtools inside of a webview.
3232

3333
# Known Issues
3434
- Prototyping stage
35-
- Settings in the devtools are not persisted
35+
- Having the DevTools in a non-foreground tab can cause issues while debugging
36+
- This is due to VS Code suspending script execution of non-foreground webviews
37+
- The workaround is to put the DevTools in a split view tab so that they are always visible while open
3638
- Chrome browser extensions can sometimes cause the webview to terminate
3739

3840
# Developing the extension itself

demo2.gif

-17.9 KB
Loading

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
"name": "vscode-devtools-for-chrome",
33
"displayName": "DevTools for Chrome",
44
"description": "Open the chrome devtools as a dockable webview",
5-
"version": "0.0.1",
5+
"version": "0.0.2",
66
"preview": true,
77
"license": "SEE LICENSE IN LICENSE",
88
"publisher": "codemooseus",
99
"icon": "icon.png",
10+
"aiKey": "3ec4c1b4-542f-4adf-830a-a4b04370fa3f",
1011
"homepage": "https://github.com/CodeMooseUS/vscode-devtools/blob/master/README.md",
1112
"repository": {
1213
"type": "git",
@@ -116,13 +117,14 @@
116117
"postinstall": "node ./node_modules/vscode/bin/install"
117118
},
118119
"dependencies": {
120+
"applicationinsights": "^1.0.6",
119121
"chrome-devtools-frontend": "^1.0.602557",
120122
"vscode": "^1.1.18",
121123
"ws": "^6.1.0"
122124
},
123125
"devDependencies": {
124126
"@types/node": "^10.5.2",
125-
"@types/shelljs": "^0.8.0",
127+
"@types/shelljs": "^0.8.0",
126128
"@types/ws": "^6.0.1",
127129
"shelljs": "^0.8.2",
128130
"ts-node": "^7.0.1",

src/extension.ts

Lines changed: 142 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
import * as path from 'path';
22
import * as vscode from 'vscode';
33
import WebSocket from 'ws';
4+
import TelemetryReporter from './telemetry';
45
import QuickPickItem = vscode.QuickPickItem;
56
import * 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+
1014
const debuggerType: string = 'devtools-for-chrome';
15+
let telemetryReporter: TelemetryReporter;
1116

1217
export 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

5870
async 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

112164
class 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

Comments
 (0)