microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
6eeec3c0610e032491c57fe0fc3426016e5177ed

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/nodeDebugWrapper.ts

226lines · modecode

1// 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
9import { Telemetry } from "../common/telemetry";
10import { TelemetryHelper } from "../common/telemetryHelper";
11import { RemoteExtension } from "../common/remoteExtension";
12import { ExtensionTelemetryReporter, ReassignableTelemetryReporter } from "../common/telemetryReporters";
13import { ChromeDebugSession, IChromeDebugSessionOpts, ChromeDebugAdapter, logger } from "vscode-chrome-debug-core";
14import { ContinuedEvent, TerminatedEvent, Logger } from "vscode-debugadapter";
15import { DebugProtocol } from "vscode-debugprotocol";
16
17import { MultipleLifetimesAppWorker } from "./appWorker";
18
19
20export function makeSession(
21 debugSessionClass: typeof ChromeDebugSession,
22 debugSessionOpts: IChromeDebugSessionOpts,
23 telemetryReporter: ReassignableTelemetryReporter,
24 appName: string, version: string): typeof ChromeDebugSession {
25
26 return class extends debugSessionClass {
27
28 private projectRootPath: string;
29 private remoteExtension: RemoteExtension;
30 private appWorker: MultipleLifetimesAppWorker | null = null;
31
32 constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
33 super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
34 }
35
36 // Override ChromeDebugSession's sendEvent to control what we will send to client
37 public sendEvent(event: DebugProtocol.Event): void {
38 // Do not send "terminated" events signaling about session's restart to client as it would cause it
39 // to restart adapter's process, while we want to stay alive and don't want to interrupt connection
40 // to packager.
41
42 if (event.event === "terminated" && event.body && event.body.restart) {
43
44 // Worker has been reloaded and switched to "continue" state
45 // So we have to send "continued" event to client instead of "terminated"
46 // Otherwise client might mistakenly show "stopped" state
47 let continuedEvent: ContinuedEvent = {
48 event: "continued",
49 type: "event",
50 seq: event["seq"], // tslint:disable-line
51 body: { threadId: event.body.threadId },
52 };
53
54 super.sendEvent(continuedEvent);
55 return;
56 }
57
58 super.sendEvent(event);
59 }
60
61 protected dispatchRequest(request: DebugProtocol.Request): void {
62 if (request.command === "disconnect")
63 return this.disconnect(request);
64
65 if (request.command === "attach")
66 return this.attach(request);
67
68 if (request.command === "launch")
69 return this.launch(request);
70
71 return super.dispatchRequest(request);
72 }
73
74 private launch(request: DebugProtocol.Request): void {
75 this.requestSetup(request.arguments);
76 this.remoteExtension.launch(request)
77 .then(() => {
78 return this.remoteExtension.getPackagerPort();
79 })
80 .then((packagerPort: number) => {
81 this.attachRequest({
82 ...request,
83 arguments: {
84 ...request.arguments,
85 port: packagerPort,
86 },
87 });
88 })
89 .catch(error => {
90 this.bailOut(error.data || error.message);
91 });
92 }
93
94 private attach(request: DebugProtocol.Request): void {
95 this.requestSetup(request.arguments);
96 this.remoteExtension.getPackagerPort()
97 .then((packagerPort: number) => {
98 this.attachRequest({
99 ...request,
100 arguments: {
101 ...request.arguments,
102 port: packagerPort,
103 },
104 });
105 });
106 }
107
108 private disconnect(request: DebugProtocol.Request): void {
109 // The client is about to disconnect so first we need to stop app worker
110 if (this.appWorker) {
111 this.appWorker.stop();
112 }
113
114 // Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
115 if (request.arguments.platform === "android") {
116 this.remoteExtension.stopMonitoringLogcat()
117 .catch(reason => logger.warn(`Couldn't stop monitoring logcat: ${reason.message || reason}`))
118 .finally(() => super.dispatchRequest(request));
119 } else {
120 super.dispatchRequest(request);
121 }
122 }
123
124 private requestSetup(args: any): void {
125 let logLevel: string = args.trace;
126 if (logLevel) {
127 logLevel = logLevel.replace(logLevel[0], logLevel[0].toUpperCase());
128 logger.setup(Logger.LogLevel[logLevel], false);
129 } else {
130 logger.setup(Logger.LogLevel.Log, false);
131 }
132
133 this.projectRootPath = getProjectRoot(args);
134 this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
135
136 // Start to send telemetry
137 telemetryReporter.reassignTo(new ExtensionTelemetryReporter(
138 appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
139 }
140
141 /**
142 * Runs logic needed to attach.
143 * Attach should:
144 * - Enable js debugging
145 */
146 // tslint:disable-next-line:member-ordering
147 protected attachRequest(
148 request: DebugProtocol.Request): Q.Promise<void> {
149 return TelemetryHelper.generate("attach", (generator) => {
150 return Q({})
151 .then(() => {
152 logger.log("Starting debugger app worker.");
153 // TODO: remove dependency on args.program - "program" property is technically
154 // no more required in launch configuration and could be removed
155 const workspaceRootPath = path.resolve(path.dirname(request.arguments.program), "..");
156 const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
157
158 // If launch is invoked first time, appWorker is undefined, so create it here
159 this.appWorker = new MultipleLifetimesAppWorker(
160 request.arguments,
161 sourcesStoragePath,
162 this.projectRootPath,
163 undefined);
164 this.appWorker.on("connected", (port: number) => {
165 logger.log("Debugger worker loaded runtime on port " + port);
166 // Don't mutate original request to avoid side effects
167 let attachArguments = Object.assign({}, request.arguments, {
168 address: "localhost",
169 port,
170 restart: true,
171 request: "attach",
172 remoteRoot: undefined,
173 localRoot: undefined,
174 });
175 let attachRequest = Object.assign({}, request, { command: "attach", arguments: attachArguments });
176
177 // Reinstantiate debug adapter, as the current implementation of ChromeDebugAdapter
178 // doesn't allow us to reattach to another debug target easily. As of now it's easier
179 // to throw previous instance out and create a new one.
180 (this as any)._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
181 super.dispatchRequest(attachRequest);
182 });
183
184 return this.appWorker.start();
185 })
186 .catch(error => this.bailOut(error.message));
187 });
188 }
189
190 /**
191 * Logs error to user and finishes the debugging process.
192 */
193 private bailOut(message: string): void {
194 logger.error(`Could not debug. ${message}`);
195 this.sendEvent(new TerminatedEvent());
196 }
197 };
198}
199
200export function makeAdapter(debugAdapterClass: typeof ChromeDebugAdapter): typeof ChromeDebugAdapter {
201 return class extends debugAdapterClass {
202 public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
203 // We need to overwrite ChromeDebug's _attachMode to let Node2 adapter
204 // to set up breakpoints on initial pause event
205 (this as any)._attachMode = false;
206 return super.doAttach(port, targetUrl, address, timeout);
207 }
208 };
209}
210
211/**
212 * Parses settings.json file for workspace root property
213 */
214function getProjectRoot(args: any): string {
215 try {
216 let vsCodeRoot = path.resolve(args.program, "../..");
217 let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
218 let settingsContent = fs.readFileSync(settingsPath, "utf8");
219 settingsContent = stripJsonComments(settingsContent);
220 let parsedSettings = JSON.parse(settingsContent);
221 let projectRootPath = parsedSettings["react-native-tools"].projectRoot;
222 return path.resolve(vsCodeRoot, projectRootPath);
223 } catch (e) {
224 return path.resolve(args.program, "../..");
225 }
226}
227