microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
984ca036ea8e629228ba7ebe24a22b2ea2415fe1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/rnDebugSession.ts

236lines · 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 vscode from "vscode";
5import * as path from "path";
6import * as mkdirp from "mkdirp";
7import { logger } from "vscode-debugadapter";
8import { DebugProtocol } from "vscode-debugprotocol";
9import { ProjectVersionHelper } from "../common/projectVersionHelper";
10import { TelemetryHelper } from "../common/telemetryHelper";
11import { MultipleLifetimesAppWorker } from "./appWorker";
12import { RnCDPMessageHandler } from "../cdp-proxy/CDPMessageHandlers/rnCDPMessageHandler";
13import { DebugSessionBase, DebugSessionStatus, IAttachRequestArgs, ILaunchRequestArgs } from "./debugSessionBase";
14import * as nls from "vscode-nls";
15const localize = nls.loadMessageBundle();
16
17export class RNDebugSession extends DebugSessionBase {
18
19 private readonly terminateCommand: string;
20 private readonly pwaNodeSessionName: string;
21
22 private nodeSession: vscode.DebugSession | null;
23 private onDidStartDebugSessionHandler: vscode.Disposable;
24 private onDidTerminateDebugSessionHandler: vscode.Disposable;
25
26 constructor(session: vscode.DebugSession) {
27 super(session);
28
29 // constants definition
30 this.terminateCommand = "terminate"; // the "terminate" command is sent from the client to the debug adapter in order to give the debuggee a chance for terminating itself
31 this.pwaNodeSessionName = "pwa-node"; // the name of node debug session created by js-debug extension
32
33 // variables definition
34 this.onDidStartDebugSessionHandler = vscode.debug.onDidStartDebugSession(
35 this.handleStartDebugSession.bind(this)
36 );
37
38 this.onDidTerminateDebugSessionHandler = vscode.debug.onDidTerminateDebugSession(
39 this.handleTerminateDebugSession.bind(this)
40 );
41 }
42
43 protected async launchRequest(response: DebugProtocol.LaunchResponse, launchArgs: ILaunchRequestArgs, request?: DebugProtocol.Request): Promise<void> {
44 return new Promise<void>((resolve, reject) => this.initializeSettings(launchArgs)
45 .then(() => {
46 logger.log("Launching the application");
47 logger.verbose(`Launching the application: ${JSON.stringify(launchArgs, null , 2)}`);
48
49 this.appLauncher.launch(launchArgs)
50 .then(() => {
51 return this.appLauncher.getPackagerPort(launchArgs.cwd);
52 })
53 .then((packagerPort: number) => {
54 launchArgs.port = launchArgs.port || packagerPort;
55 this.attachRequest(response, launchArgs).then(() => {
56 resolve();
57 }).catch((e) => reject(e));
58 })
59 .catch((err) => {
60 logger.error("An error occurred while launching the application. " + err.message || err);
61 reject(err);
62 });
63 }))
64 .catch(err => this.showError(err.message, response));
65 }
66
67 protected async attachRequest(response: DebugProtocol.AttachResponse, attachArgs: IAttachRequestArgs, request?: DebugProtocol.Request): Promise<void> {
68 let extProps = {
69 platform: {
70 value: attachArgs.platform,
71 isPii: false,
72 },
73 };
74
75 this.previousAttachArgs = attachArgs;
76 return new Promise<void>((resolve, reject) => this.initializeSettings(attachArgs)
77 .then(() => {
78 logger.log("Attaching to the application");
79 logger.verbose(`Attaching to the application: ${JSON.stringify(attachArgs, null , 2)}`);
80 return ProjectVersionHelper.getReactNativeVersions(attachArgs.cwd, true)
81 .then(versions => {
82 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeVersion, "reactNativeVersion", extProps);
83 if (!ProjectVersionHelper.isVersionError(versions.reactNativeWindowsVersion)) {
84 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeWindowsVersion, "reactNativeWindowsVersion", extProps);
85 }
86 return TelemetryHelper.generate("attach", extProps, (generator) => {
87 attachArgs.port = attachArgs.port || this.appLauncher.getPackagerPort(attachArgs.cwd);
88 return this.appLauncher.getRnCdpProxy().stopServer()
89 .then(() => this.appLauncher.getRnCdpProxy().initializeServer(new RnCDPMessageHandler(), this.cdpProxyLogLevel))
90 .then(() => {
91 logger.log(localize("StartingDebuggerAppWorker", "Starting debugger app worker."));
92
93 const sourcesStoragePath = path.join(this.projectRootPath, ".vscode", ".react");
94 // Create folder if not exist to avoid problems if
95 // RN project root is not a ${workspaceFolder}
96 mkdirp.sync(sourcesStoragePath);
97
98 // If launch is invoked first time, appWorker is undefined, so create it here
99 this.appWorker = new MultipleLifetimesAppWorker(
100 attachArgs,
101 sourcesStoragePath,
102 this.projectRootPath,
103 undefined
104 );
105 this.appLauncher.setAppWorker(this.appWorker);
106
107 this.appWorker.on("connected", (port: number) => {
108 logger.log(localize("DebuggerWorkerLoadedRuntimeOnPort", "Debugger worker loaded runtime on port {0}", port));
109
110 this.appLauncher.getRnCdpProxy().setApplicationTargetPort(port);
111
112 if (this.debugSessionStatus === DebugSessionStatus.ConnectionPending) {
113 return;
114 }
115
116 if (this.debugSessionStatus === DebugSessionStatus.FirstConnection) {
117 this.debugSessionStatus = DebugSessionStatus.FirstConnectionPending;
118 this.establishDebugSession(resolve);
119 } else if (this.debugSessionStatus === DebugSessionStatus.ConnectionAllowed) {
120 if (this.nodeSession) {
121 this.debugSessionStatus = DebugSessionStatus.ConnectionPending;
122 this.nodeSession.customRequest(this.terminateCommand);
123 }
124 }
125 });
126 return this.appWorker.start();
127 });
128 })
129 .catch((err) => {
130 logger.error("An error occurred while attaching to the debugger. " + err.message || err);
131 reject(err);
132 });
133 });
134 }))
135 .catch(err => this.showError(err.message, response));
136 }
137
138 protected async disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments, request?: DebugProtocol.Request): Promise<void> {
139 // The client is about to disconnect so first we need to stop app worker
140 if (this.appWorker) {
141 this.appWorker.stop();
142 }
143
144 await this.appLauncher.getRnCdpProxy().stopServer();
145
146 this.onDidStartDebugSessionHandler.dispose();
147 this.onDidTerminateDebugSessionHandler.dispose();
148
149 // Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
150 if (this.previousAttachArgs.platform === "android") {
151 try {
152 this.appLauncher.stopMonitoringLogCat();
153 } catch (err) {
154 logger.warn(localize("CouldNotStopMonitoringLogcat", "Couldn't stop monitoring logcat: {0}", err.message || err));
155 }
156 }
157
158 super.disconnectRequest(response, args, request);
159 }
160
161 protected establishDebugSession(resolve?: (value?: void | PromiseLike<void> | undefined) => void): void {
162 const attachArguments = {
163 type: "pwa-node",
164 request: "attach",
165 name: "Attach",
166 continueOnAttach: true,
167 port: this.appLauncher.getCdpProxyPort(),
168 smartStep: false,
169 // The unique identifier of the debug session. It is used to distinguish React Native extension's
170 // debug sessions from other ones. So we can save and process only the extension's debug sessions
171 // in vscode.debug API methods "onDidStartDebugSession" and "onDidTerminateDebugSession".
172 rnDebugSessionId: this.session.id,
173 };
174
175 vscode.debug.startDebugging(
176 this.appLauncher.getWorkspaceFolder(),
177 attachArguments,
178 this.session
179 )
180 .then((childDebugSessionStarted: boolean) => {
181 if (childDebugSessionStarted) {
182 this.debugSessionStatus = DebugSessionStatus.ConnectionDone;
183 this.setConnectionAllowedIfPossible();
184 if (resolve) {
185 this.debugSessionStatus = DebugSessionStatus.ConnectionAllowed;
186 resolve();
187 }
188 } else {
189 this.debugSessionStatus = DebugSessionStatus.ConnectionFailed;
190 this.setConnectionAllowedIfPossible();
191 this.resetFirstConnectionStatus();
192 throw new Error("Cannot start child debug session");
193 }
194 },
195 err => {
196 this.debugSessionStatus = DebugSessionStatus.ConnectionFailed;
197 this.setConnectionAllowedIfPossible();
198 this.resetFirstConnectionStatus();
199 throw err;
200 });
201 }
202
203 private handleStartDebugSession(debugSession: vscode.DebugSession) {
204 if (
205 debugSession.configuration.rnDebugSessionId === this.session.id
206 && debugSession.type === this.pwaNodeSessionName
207 ) {
208 this.nodeSession = debugSession;
209 }
210 }
211
212 private handleTerminateDebugSession(debugSession: vscode.DebugSession) {
213 if (
214 debugSession.configuration.rnDebugSessionId === this.session.id
215 && this.debugSessionStatus === DebugSessionStatus.ConnectionPending
216 && debugSession.type === this.pwaNodeSessionName
217 ) {
218 this.establishDebugSession();
219 }
220 }
221
222 private setConnectionAllowedIfPossible(): void {
223 if (
224 this.debugSessionStatus === DebugSessionStatus.ConnectionDone
225 || this.debugSessionStatus === DebugSessionStatus.ConnectionFailed
226 ) {
227 this.debugSessionStatus = DebugSessionStatus.ConnectionAllowed;
228 }
229 }
230
231 private resetFirstConnectionStatus(): void {
232 if (this.debugSessionStatus === DebugSessionStatus.FirstConnectionPending) {
233 this.debugSessionStatus = DebugSessionStatus.FirstConnection;
234 }
235 }
236}
237