microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.7.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

265lines · 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");
8
b8999098Dmitry Zinovyev9 years ago9import { Telemetry } from "../common/telemetry";
10import { TelemetryHelper } from "../common/telemetryHelper";
11import { RemoteExtension } from "../common/remoteExtension";
3a155214Artem Egorov8 years ago12import { RemoteTelemetryReporter, ReassignableTelemetryReporter } from "../common/telemetryReporters";
2d876061Ruslan Bikkinin8 years ago13import { ChromeDebugSession, IChromeDebugSessionOpts, ChromeDebugAdapter, logger } from "vscode-chrome-debug-core";
e3b0fb3bChance An8 years ago14import { ContinuedEvent, TerminatedEvent, Logger, Response } from "vscode-debugadapter";
0a68f8dbArtem Egorov8 years ago15import { DebugProtocol } from "vscode-debugprotocol";
e45838cbVladimir Kotikov9 years ago16
17import { MultipleLifetimesAppWorker } from "./appWorker";
18
2d876061Ruslan Bikkinin8 years ago19import { ReactNativeProjectHelper } from "../common/reactNativeProjectHelper";
20
0a68f8dbArtem Egorov8 years ago21
b8999098Dmitry Zinovyev9 years ago22export function makeSession(
0a68f8dbArtem Egorov8 years ago23debugSessionClass: typeof ChromeDebugSession,
24debugSessionOpts: IChromeDebugSessionOpts,
b8999098Dmitry Zinovyev9 years ago25telemetryReporter: ReassignableTelemetryReporter,
0a68f8dbArtem Egorov8 years ago26appName: string, version: string): typeof ChromeDebugSession {
e45838cbVladimir Kotikov9 years ago27
28return class extends debugSessionClass {
29
30private projectRootPath: string;
31private remoteExtension: RemoteExtension;
5c8365a6Artem Egorov8 years ago32private appWorker: MultipleLifetimesAppWorker | null = null;
e45838cbVladimir Kotikov9 years ago33
34constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
35super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
36}
37
38// Override ChromeDebugSession's sendEvent to control what we will send to client
0a68f8dbArtem Egorov8 years ago39public sendEvent(event: DebugProtocol.Event): void {
e45838cbVladimir Kotikov9 years ago40// Do not send "terminated" events signaling about session's restart to client as it would cause it
41// to restart adapter's process, while we want to stay alive and don't want to interrupt connection
6c75098eVladimir9 years ago42// to packager.
b8999098Dmitry Zinovyev9 years ago43
720e992dArtem Egorov8 years ago44if (event.event === "terminated" && event.body && event.body.restart) {
b8999098Dmitry Zinovyev9 years ago45
46// Worker has been reloaded and switched to "continue" state
47// So we have to send "continued" event to client instead of "terminated"
48// Otherwise client might mistakenly show "stopped" state
0a68f8dbArtem Egorov8 years ago49let continuedEvent: ContinuedEvent = {
b8999098Dmitry Zinovyev9 years ago50event: "continued",
51type: "event",
52seq: event["seq"], // tslint:disable-line
53body: { threadId: event.body.threadId },
54};
55
56super.sendEvent(continuedEvent);
e45838cbVladimir Kotikov9 years ago57return;
58}
59
60super.sendEvent(event);
61}
62
0a68f8dbArtem Egorov8 years ago63protected dispatchRequest(request: DebugProtocol.Request): void {
e45838cbVladimir Kotikov9 years ago64if (request.command === "disconnect")
65return this.disconnect(request);
66
67if (request.command === "attach")
68return this.attach(request);
69
70if (request.command === "launch")
71return this.launch(request);
72
73return super.dispatchRequest(request);
74}
75
0a68f8dbArtem Egorov8 years ago76private launch(request: DebugProtocol.Request): void {
2d876061Ruslan Bikkinin8 years ago77this.requestSetup(request.arguments)
78.then(() => {
db6fd42aRuslan Bikkinin7 years ago79logger.verbose(`Handle launch request: ${JSON.stringify(request.arguments, null , 2)}`);
2d876061Ruslan Bikkinin8 years ago80return this.remoteExtension.launch(request);
81})
cbb0e869Artem Egorov8 years ago82.then(() => {
2e432a9eArtem Egorov8 years ago83return this.remoteExtension.getPackagerPort(request.arguments.program);
0a68f8dbArtem Egorov8 years ago84})
85.then((packagerPort: number) => {
6eeec3c0Serge Svekolnikov8 years ago86this.attachRequest({
87...request,
88arguments: {
89...request.arguments,
90port: packagerPort,
91},
92});
0a68f8dbArtem Egorov8 years ago93})
94.catch(error => {
748105d9Artem Egorov8 years ago95this.bailOut(error.data || error.message);
cbb0e869Artem Egorov8 years ago96});
e45838cbVladimir Kotikov9 years ago97}
98
0a68f8dbArtem Egorov8 years ago99private attach(request: DebugProtocol.Request): void {
2d876061Ruslan Bikkinin8 years ago100this.requestSetup(request.arguments)
101.then(() => {
db6fd42aRuslan Bikkinin7 years ago102logger.verbose(`Handle attach request: ${request.arguments}`);
2d876061Ruslan Bikkinin8 years ago103return this.remoteExtension.getPackagerPort(request.arguments.program);
104})
0a68f8dbArtem Egorov8 years ago105.then((packagerPort: number) => {
6eeec3c0Serge Svekolnikov8 years ago106this.attachRequest({
107...request,
108arguments: {
109...request.arguments,
e92278b5Serge Svekolnikov8 years ago110port: request.arguments.port || packagerPort,
6eeec3c0Serge Svekolnikov8 years ago111},
112});
2d876061Ruslan Bikkinin8 years ago113})
114.catch(error => {
115this.bailOut(error.data || error.message);
cbb0e869Artem Egorov8 years ago116});
e45838cbVladimir Kotikov9 years ago117}
118
0a68f8dbArtem Egorov8 years ago119private disconnect(request: DebugProtocol.Request): void {
e45838cbVladimir Kotikov9 years ago120// The client is about to disconnect so first we need to stop app worker
f920e582Vladimir Kotikov9 years ago121if (this.appWorker) {
122this.appWorker.stop();
123}
e45838cbVladimir Kotikov9 years ago124
125// Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
0a68f8dbArtem Egorov8 years ago126if (request.arguments.platform === "android") {
e45838cbVladimir Kotikov9 years ago127this.remoteExtension.stopMonitoringLogcat()
0a68f8dbArtem Egorov8 years ago128.catch(reason => logger.warn(`Couldn't stop monitoring logcat: ${reason.message || reason}`))
b8999098Dmitry Zinovyev9 years ago129.finally(() => super.dispatchRequest(request));
e45838cbVladimir Kotikov9 years ago130} else {
131super.dispatchRequest(request);
132}
133}
134
2d876061Ruslan Bikkinin8 years ago135private requestSetup(args: any): Q.Promise<void> {
0a68f8dbArtem Egorov8 years ago136let logLevel: string = args.trace;
137if (logLevel) {
138logLevel = logLevel.replace(logLevel[0], logLevel[0].toUpperCase());
139logger.setup(Logger.LogLevel[logLevel], false);
140} else {
141logger.setup(Logger.LogLevel.Log, false);
142}
143
2d876061Ruslan Bikkinin8 years ago144const projectRootPath = getProjectRoot(args);
145return ReactNativeProjectHelper.isReactNativeProject(projectRootPath)
146.then((result) => {
147if (!result) {
148throw new Error(`Seems to be that you are trying to debug from within directory that is not a React Native project root.
149If so, please, follow these instructions: https://github.com/Microsoft/vscode-react-native/blob/master/doc/customization.md#project-structure.`);
150}
151this.projectRootPath = projectRootPath;
152this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
153
154// Start to send telemetry
155telemetryReporter.reassignTo(new RemoteTelemetryReporter(
156appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
157return void 0;
158});
e45838cbVladimir Kotikov9 years ago159}
160
161/**
162* Runs logic needed to attach.
163* Attach should:
164* - Enable js debugging
165*/
0a68f8dbArtem Egorov8 years ago166// tslint:disable-next-line:member-ordering
031832ffArtem Egorov8 years ago167protected attachRequest(request: DebugProtocol.Request): Q.Promise<void> {
168const extProps = {
169platform: {
170value: request.arguments.platform,
171isPii: false,
172},
173};
174
175return TelemetryHelper.generate("attach", extProps, (generator) => {
e45838cbVladimir Kotikov9 years ago176return Q({})
177.then(() => {
0a68f8dbArtem Egorov8 years ago178logger.log("Starting debugger app worker.");
e45838cbVladimir Kotikov9 years ago179// TODO: remove dependency on args.program - "program" property is technically
180// no more required in launch configuration and could be removed
181const workspaceRootPath = path.resolve(path.dirname(request.arguments.program), "..");
182const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
183
184// If launch is invoked first time, appWorker is undefined, so create it here
6eeec3c0Serge Svekolnikov8 years ago185this.appWorker = new MultipleLifetimesAppWorker(
186request.arguments,
187sourcesStoragePath,
188this.projectRootPath,
189undefined);
e45838cbVladimir Kotikov9 years ago190this.appWorker.on("connected", (port: number) => {
0a68f8dbArtem Egorov8 years ago191logger.log("Debugger worker loaded runtime on port " + port);
e45838cbVladimir Kotikov9 years ago192// Don't mutate original request to avoid side effects
6eeec3c0Serge Svekolnikov8 years ago193let attachArguments = Object.assign({}, request.arguments, {
194address: "localhost",
195port,
196restart: true,
197request: "attach",
198remoteRoot: undefined,
199localRoot: undefined,
200});
6c75098eVladimir9 years ago201// Reinstantiate debug adapter, as the current implementation of ChromeDebugAdapter
202// doesn't allow us to reattach to another debug target easily. As of now it's easier
203// to throw previous instance out and create a new one.
0a68f8dbArtem Egorov8 years ago204(this as any)._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
e3b0fb3bChance An8 years ago205
206// Explicity call _debugAdapter.attach() to prevent directly calling dispatchRequest()
207// yield a response as "attach" even for "launch" request. Because dispatchRequest() will
208// decide to do a sendResponse() aligning with the request parameter passed in.
209Q((this as any)._debugAdapter.attach(attachArguments, request.seq))
2d876061Ruslan Bikkinin8 years ago210.then((responseBody) => {
211const response: DebugProtocol.Response = new Response(request);
212response.body = responseBody;
213this.sendResponse(response);
214});
e45838cbVladimir Kotikov9 years ago215});
216
217return this.appWorker.start();
218})
219.catch(error => this.bailOut(error.message));
220});
221}
222
223/**
224* Logs error to user and finishes the debugging process.
225*/
226private bailOut(message: string): void {
0a68f8dbArtem Egorov8 years ago227logger.error(`Could not debug. ${message}`);
228this.sendEvent(new TerminatedEvent());
27710197Vladimir Kotikov8 years ago229}
e45838cbVladimir Kotikov9 years ago230};
231}
232
0a68f8dbArtem Egorov8 years ago233export function makeAdapter(debugAdapterClass: typeof ChromeDebugAdapter): typeof ChromeDebugAdapter {
e45838cbVladimir Kotikov9 years ago234return class extends debugAdapterClass {
4f7b3bc0Anna Kocheshkova8 years ago235public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
4b86d595Vladimir Kotikov9 years ago236// We need to overwrite ChromeDebug's _attachMode to let Node2 adapter
e45838cbVladimir Kotikov9 years ago237// to set up breakpoints on initial pause event
0a68f8dbArtem Egorov8 years ago238(this as any)._attachMode = false;
e45838cbVladimir Kotikov9 years ago239return super.doAttach(port, targetUrl, address, timeout);
240}
639a73d7Artem Egorov7 years ago241
242public async terminate(args: DebugProtocol.TerminatedEvent) {
243return this.disconnect({
244terminateDebuggee: true,
245});
246}
e45838cbVladimir Kotikov9 years ago247};
248}
249
250/**
251* Parses settings.json file for workspace root property
252*/
253function getProjectRoot(args: any): string {
254try {
255let vsCodeRoot = path.resolve(args.program, "../..");
256let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
257let settingsContent = fs.readFileSync(settingsPath, "utf8");
258settingsContent = stripJsonComments(settingsContent);
259let parsedSettings = JSON.parse(settingsContent);
9a364375Serge Svekolnikov8 years ago260let projectRootPath = parsedSettings["react-native-tools.projectRoot"] || parsedSettings["react-native-tools"].projectRoot;
e45838cbVladimir Kotikov9 years ago261return path.resolve(vsCodeRoot, projectRootPath);
262} catch (e) {
263return path.resolve(args.program, "../..");
264}
265}