microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.15.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

320lines · modeblame

e45838cbVladimir Kotikov9 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as Q from "q";
5import * as path from "path";
6import * as fs from "fs";
7import stripJsonComments = require("strip-json-comments");
b8999098Dmitry Zinovyev9 years ago8import { Telemetry } from "../common/telemetry";
9import { TelemetryHelper } from "../common/telemetryHelper";
10import { RemoteExtension } from "../common/remoteExtension";
3a155214Artem Egorov8 years ago11import { RemoteTelemetryReporter, ReassignableTelemetryReporter } from "../common/telemetryReporters";
e67ace8aYuri Skorokhodov6 years ago12import { ChromeDebugSession, IChromeDebugSessionOpts, ChromeDebugAdapter, logger, Crdp, stoppedEvent, IOnPausedResult } from "vscode-chrome-debug-core";
e3b0fb3bChance An8 years ago13import { ContinuedEvent, TerminatedEvent, Logger, Response } from "vscode-debugadapter";
0a68f8dbArtem Egorov8 years ago14import { DebugProtocol } from "vscode-debugprotocol";
e45838cbVladimir Kotikov9 years ago15import { MultipleLifetimesAppWorker } from "./appWorker";
2d876061Ruslan Bikkinin8 years ago16import { ReactNativeProjectHelper } from "../common/reactNativeProjectHelper";
e3706a1cRedMickey6 years ago17import { ProjectVersionHelper } from "../common/projectVersionHelper";
d124bf0eYuri Skorokhodov7 years ago18import * as nls from "vscode-nls";
19import { ErrorHelper } from "../common/error/errorHelper";
20import { InternalErrorCode } from "../common/error/internalErrorCode";
08722261Yuri Skorokhodov7 years ago21import { getLoggingDirectory } from "../extension/log/LogHelper";
3955d20aYuri Skorokhodov6 years ago22import * as mkdirp from "mkdirp";
d124bf0eYuri Skorokhodov7 years ago23const localize = nls.loadMessageBundle();
0a68f8dbArtem Egorov8 years ago24
b8999098Dmitry Zinovyev9 years ago25export function makeSession(
0a68f8dbArtem Egorov8 years ago26debugSessionClass: typeof ChromeDebugSession,
27debugSessionOpts: IChromeDebugSessionOpts,
b8999098Dmitry Zinovyev9 years ago28telemetryReporter: ReassignableTelemetryReporter,
0a68f8dbArtem Egorov8 years ago29appName: string, version: string): typeof ChromeDebugSession {
e45838cbVladimir Kotikov9 years ago30
31return class extends debugSessionClass {
32
33private projectRootPath: string;
34private remoteExtension: RemoteExtension;
5c8365a6Artem Egorov8 years ago35private appWorker: MultipleLifetimesAppWorker | null = null;
e45838cbVladimir Kotikov9 years ago36
37constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
38super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
39}
40
41// Override ChromeDebugSession's sendEvent to control what we will send to client
0a68f8dbArtem Egorov8 years ago42public sendEvent(event: DebugProtocol.Event): void {
e45838cbVladimir Kotikov9 years ago43// Do not send "terminated" events signaling about session's restart to client as it would cause it
44// to restart adapter's process, while we want to stay alive and don't want to interrupt connection
6c75098eVladimir9 years ago45// to packager.
b8999098Dmitry Zinovyev9 years ago46
720e992dArtem Egorov8 years ago47if (event.event === "terminated" && event.body && event.body.restart) {
b8999098Dmitry Zinovyev9 years ago48
49// Worker has been reloaded and switched to "continue" state
50// So we have to send "continued" event to client instead of "terminated"
51// Otherwise client might mistakenly show "stopped" state
0a68f8dbArtem Egorov8 years ago52let continuedEvent: ContinuedEvent = {
b8999098Dmitry Zinovyev9 years ago53event: "continued",
54type: "event",
55seq: event["seq"], // tslint:disable-line
56body: { threadId: event.body.threadId },
57};
58
59super.sendEvent(continuedEvent);
e45838cbVladimir Kotikov9 years ago60return;
61}
62
63super.sendEvent(event);
64}
65
0a68f8dbArtem Egorov8 years ago66protected dispatchRequest(request: DebugProtocol.Request): void {
e45838cbVladimir Kotikov9 years ago67if (request.command === "disconnect")
68return this.disconnect(request);
69
70if (request.command === "attach")
71return this.attach(request);
72
73if (request.command === "launch")
74return this.launch(request);
75
76return super.dispatchRequest(request);
77}
78
0a68f8dbArtem Egorov8 years ago79private launch(request: DebugProtocol.Request): void {
2d876061Ruslan Bikkinin8 years ago80this.requestSetup(request.arguments)
81.then(() => {
db6fd42aRuslan Bikkinin7 years ago82logger.verbose(`Handle launch request: ${JSON.stringify(request.arguments, null , 2)}`);
2d876061Ruslan Bikkinin8 years ago83return this.remoteExtension.launch(request);
84})
cbb0e869Artem Egorov8 years ago85.then(() => {
18ea7d15Yuri Skorokhodov6 years ago86return this.remoteExtension.getPackagerPort(request.arguments.cwd || request.arguments.program);
0a68f8dbArtem Egorov8 years ago87})
88.then((packagerPort: number) => {
6eeec3c0Serge Svekolnikov8 years ago89this.attachRequest({
90...request,
91arguments: {
92...request.arguments,
93port: packagerPort,
94},
95});
0a68f8dbArtem Egorov8 years ago96})
97.catch(error => {
748105d9Artem Egorov8 years ago98this.bailOut(error.data || error.message);
cbb0e869Artem Egorov8 years ago99});
e45838cbVladimir Kotikov9 years ago100}
101
0a68f8dbArtem Egorov8 years ago102private attach(request: DebugProtocol.Request): void {
2d876061Ruslan Bikkinin8 years ago103this.requestSetup(request.arguments)
104.then(() => {
dbfbebd9Yuri Skorokhodov7 years ago105logger.verbose(`Handle attach request: ${JSON.stringify(request.arguments, null , 2)}`);
18ea7d15Yuri Skorokhodov6 years ago106return this.remoteExtension.getPackagerPort(request.arguments.cwd || request.arguments.program);
2d876061Ruslan Bikkinin8 years ago107})
0a68f8dbArtem Egorov8 years ago108.then((packagerPort: number) => {
6eeec3c0Serge Svekolnikov8 years ago109this.attachRequest({
110...request,
111arguments: {
112...request.arguments,
e92278b5Serge Svekolnikov8 years ago113port: request.arguments.port || packagerPort,
6eeec3c0Serge Svekolnikov8 years ago114},
115});
2d876061Ruslan Bikkinin8 years ago116})
117.catch(error => {
118this.bailOut(error.data || error.message);
cbb0e869Artem Egorov8 years ago119});
e45838cbVladimir Kotikov9 years ago120}
121
0a68f8dbArtem Egorov8 years ago122private disconnect(request: DebugProtocol.Request): void {
e45838cbVladimir Kotikov9 years ago123// The client is about to disconnect so first we need to stop app worker
f920e582Vladimir Kotikov9 years ago124if (this.appWorker) {
125this.appWorker.stop();
126}
e45838cbVladimir Kotikov9 years ago127
128// Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
0a68f8dbArtem Egorov8 years ago129if (request.arguments.platform === "android") {
e45838cbVladimir Kotikov9 years ago130this.remoteExtension.stopMonitoringLogcat()
d124bf0eYuri Skorokhodov7 years ago131.catch(reason => logger.warn(localize("CouldNotStopMonitoringLogcat", "Couldn't stop monitoring logcat: {0}", reason.message || reason)))
b8999098Dmitry Zinovyev9 years ago132.finally(() => super.dispatchRequest(request));
e45838cbVladimir Kotikov9 years ago133} else {
134super.dispatchRequest(request);
135}
136}
137
2d876061Ruslan Bikkinin8 years ago138private requestSetup(args: any): Q.Promise<void> {
08722261Yuri Skorokhodov7 years ago139// If special env variables are defined, then write process outputs to file
140let chromeDebugCoreLogs = getLoggingDirectory();
141if (chromeDebugCoreLogs) {
142chromeDebugCoreLogs = path.join(chromeDebugCoreLogs, "ChromeDebugCoreLogs.txt");
143}
0a68f8dbArtem Egorov8 years ago144let logLevel: string = args.trace;
145if (logLevel) {
146logLevel = logLevel.replace(logLevel[0], logLevel[0].toUpperCase());
08722261Yuri Skorokhodov7 years ago147logger.setup(Logger.LogLevel[logLevel], chromeDebugCoreLogs || false);
0a68f8dbArtem Egorov8 years ago148} else {
08722261Yuri Skorokhodov7 years ago149logger.setup(Logger.LogLevel.Log, chromeDebugCoreLogs || false);
0a68f8dbArtem Egorov8 years ago150}
151
18ea7d15Yuri Skorokhodov6 years ago152
135c493eYuri Skorokhodov7 years ago153if (!args.sourceMaps) {
154args.sourceMaps = true;
155}
2d876061Ruslan Bikkinin8 years ago156const projectRootPath = getProjectRoot(args);
157return ReactNativeProjectHelper.isReactNativeProject(projectRootPath)
158.then((result) => {
159if (!result) {
d124bf0eYuri Skorokhodov7 years ago160throw ErrorHelper.getInternalError(InternalErrorCode.NotInReactNativeFolderError);
2d876061Ruslan Bikkinin8 years ago161}
162this.projectRootPath = projectRootPath;
163this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
164
165// Start to send telemetry
166telemetryReporter.reassignTo(new RemoteTelemetryReporter(
167appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
23b5fa80Yuri Skorokhodov6 years ago168
169if (args.program) {
170// TODO: Remove this warning when program property will be completely removed
171logger.warn(localize("ProgramPropertyDeprecationWarning", "Launched debug configuration contains 'program' property which is deprecated and will be removed soon. Please replace it with: \"cwd\": \"${workspaceFolder}\""));
172const useProgramEvent = TelemetryHelper.createTelemetryEvent("useProgramProperty");
173Telemetry.send(useProgramEvent);
174}
175if (args.cwd) {
176// To match count of 'cwd' users with 'program' users. TODO: Remove when program property will be removed
177const useCwdEvent = TelemetryHelper.createTelemetryEvent("useCwdProperty");
178Telemetry.send(useCwdEvent);
179}
2d876061Ruslan Bikkinin8 years ago180return void 0;
181});
e45838cbVladimir Kotikov9 years ago182}
183
184/**
185* Runs logic needed to attach.
186* Attach should:
187* - Enable js debugging
188*/
0a68f8dbArtem Egorov8 years ago189// tslint:disable-next-line:member-ordering
031832ffArtem Egorov7 years ago190protected attachRequest(request: DebugProtocol.Request): Q.Promise<void> {
ba953e9fRedMickey6 years ago191let extProps = {
031832ffArtem Egorov7 years ago192platform: {
193value: request.arguments.platform,
194isPii: false,
195},
196};
197
e3706a1cRedMickey6 years ago198return ProjectVersionHelper.getReactNativeVersions(request.arguments.cwd, true)
7fa90b3bRedMickey6 years ago199.then(versions => {
200extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeVersion, "reactNativeVersion", extProps);
e3706a1cRedMickey6 years ago201if (!ProjectVersionHelper.isVersionError(versions.reactNativeWindowsVersion)) {
7fa90b3bRedMickey6 years ago202extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeWindowsVersion, "reactNativeWindowsVersion", extProps);
203}
ba953e9fRedMickey6 years ago204return TelemetryHelper.generate("attach", extProps, (generator) => {
205return Q({})
206.then(() => {
207logger.log(localize("StartingDebuggerAppWorker", "Starting debugger app worker."));
208// TODO: remove dependency on args.program - "program" property is technically
209// no more required in launch configuration and could be removed
210const workspaceRootPath = request.arguments.cwd ? path.resolve(request.arguments.cwd) : path.resolve(path.dirname(request.arguments.program), "..");
211const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
212// Create folder if not exist to avoid problems if
213// RN project root is not a ${workspaceFolder}
214mkdirp.sync(sourcesStoragePath);
215
216// If launch is invoked first time, appWorker is undefined, so create it here
217this.appWorker = new MultipleLifetimesAppWorker(
218request.arguments,
219sourcesStoragePath,
220this.projectRootPath,
221undefined);
222this.appWorker.on("connected", (port: number) => {
223logger.log(localize("DebuggerWorkerLoadedRuntimeOnPort", "Debugger worker loaded runtime on port {0}", port));
224// Don't mutate original request to avoid side effects
225let attachArguments = Object.assign({}, request.arguments, {
226address: "localhost",
227port,
228restart: true,
229request: "attach",
230remoteRoot: undefined,
231localRoot: undefined,
232});
233// Reinstantiate debug adapter, as the current implementation of ChromeDebugAdapter
234// doesn't allow us to reattach to another debug target easily. As of now it's easier
235// to throw previous instance out and create a new one.
236(this as any)._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
237
238// Explicity call _debugAdapter.attach() to prevent directly calling dispatchRequest()
239// yield a response as "attach" even for "launch" request. Because dispatchRequest() will
240// decide to do a sendResponse() aligning with the request parameter passed in.
241Q((this as any)._debugAdapter.attach(attachArguments, request.seq))
242.then((responseBody) => {
243const response: DebugProtocol.Response = new Response(request);
244response.body = responseBody;
245this.sendResponse(response);
246});
2d876061Ruslan Bikkinin8 years ago247});
e45838cbVladimir Kotikov9 years ago248
ba953e9fRedMickey6 years ago249return this.appWorker.start();
250})
251.catch(error => this.bailOut(error.message));
252});
253});
e45838cbVladimir Kotikov9 years ago254}
255
256/**
257* Logs error to user and finishes the debugging process.
258*/
259private bailOut(message: string): void {
d124bf0eYuri Skorokhodov7 years ago260logger.error(localize("CouldNotDebug", "Could not debug. {0}" , message));
0a68f8dbArtem Egorov8 years ago261this.sendEvent(new TerminatedEvent());
27710197Vladimir Kotikov8 years ago262}
e45838cbVladimir Kotikov9 years ago263};
264}
265
0a68f8dbArtem Egorov8 years ago266export function makeAdapter(debugAdapterClass: typeof ChromeDebugAdapter): typeof ChromeDebugAdapter {
e45838cbVladimir Kotikov9 years ago267return class extends debugAdapterClass {
e67ace8aYuri Skorokhodov6 years ago268private firstStop: boolean = true;
4f7b3bc0Anna Kocheshkova8 years ago269public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
4b86d595Vladimir Kotikov9 years ago270// We need to overwrite ChromeDebug's _attachMode to let Node2 adapter
e45838cbVladimir Kotikov9 years ago271// to set up breakpoints on initial pause event
0a68f8dbArtem Egorov8 years ago272(this as any)._attachMode = false;
e45838cbVladimir Kotikov9 years ago273return super.doAttach(port, targetUrl, address, timeout);
274}
639a73d7Artem Egorov7 years ago275
e67ace8aYuri Skorokhodov6 years ago276// Since the bundle runs inside the Node.js VM in debuggerWorker.js in runtime
277// Node debug adapter need time to parse new added code source maps
278// So we added 'debugger;' statement at the start of the bundle code
279// and wait for the adapter to receive a signal to stop on that statement
280// and then wait for code bundle to be processed and then send continue request to skip the code execution stop in VS Code UI
281public onPaused(notification: Crdp.Debugger.PausedEvent, expectingStopReason?: stoppedEvent.ReasonType): Promise<IOnPausedResult> {
282// When pause on 'debugger;' statement, notification contains reason with value "other" instead of "breakpoint"
283if (this.firstStop && notification.reason === "other") {
284return new Promise<IOnPausedResult>((resolve) => {
285setTimeout(() => {
286this.firstStop = false;
287this.continue();
288resolve({didPause: false});
289}, 50);
290});
291} else {
292return super.onPaused(notification, expectingStopReason);
293}
294}
295
639a73d7Artem Egorov7 years ago296public async terminate(args: DebugProtocol.TerminatedEvent) {
297return this.disconnect({
298terminateDebuggee: true,
299});
300}
e45838cbVladimir Kotikov9 years ago301};
302}
303
304/**
305* Parses settings.json file for workspace root property
306*/
549baae2RedMickey6 years ago307export function getProjectRoot(args: any): string {
31796719RedMickey6 years ago308const vsCodeRoot = args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
309const settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
e45838cbVladimir Kotikov9 years ago310try {
311let settingsContent = fs.readFileSync(settingsPath, "utf8");
312settingsContent = stripJsonComments(settingsContent);
313let parsedSettings = JSON.parse(settingsContent);
9a364375Serge Svekolnikov8 years ago314let projectRootPath = parsedSettings["react-native-tools.projectRoot"] || parsedSettings["react-native-tools"].projectRoot;
e45838cbVladimir Kotikov9 years ago315return path.resolve(vsCodeRoot, projectRootPath);
316} catch (e) {
52f2485aYuri Skorokhodov6 years ago317logger.verbose(`'projectRoot' setting is not found: ${settingsPath} file doesn't exist or its content is incorrect.`);
18ea7d15Yuri Skorokhodov6 years ago318return args.cwd ? path.resolve(args.cwd) : path.resolve(args.program, "../..");
e45838cbVladimir Kotikov9 years ago319}
320}