microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
748105d9c1d909e917309cbb58ed2d80fd77c015

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/forkedAppWorker.ts

140lines · 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 child_process from "child_process";
7import {ScriptImporter, DownloadedScript} from "./scriptImporter";
8
9import { logger } from "vscode-chrome-debug-core";
10import { ErrorHelper } from "../common/error/errorHelper";
11import { IDebuggeeWorker, RNAppMessage } from "./appWorker";
12import { RemoteExtension } from "../common/remoteExtension";
13
14function printDebuggingError(message: string, reason: any) {
15 const nestedError = ErrorHelper.getNestedWarning(reason, `${message}. Debugging won't work: Try reloading the JS from inside the app, or Reconnect the VS Code debugger`);
16
17 logger.error(nestedError.message);
18}
19
20/** This class will run the RN App logic inside a forked Node process. The framework to run the logic is provided by the file
21 * debuggerWorker.js (designed to run on a WebWorker). We add a couple of tweaks (mostly to polyfill WebWorker API) to that
22 * file and load it inside of a process.
23 * On this side we listen to IPC messages and either respond to them or redirect them to packager via MultipleLifetimeAppWorker's
24 * instance. We also intercept packager's signal to load the bundle's code and mutate the message with path to file we've downloaded
25 * to let importScripts function take this file.
26 */
27export class ForkedAppWorker implements IDebuggeeWorker {
28
29 private scriptImporter: ScriptImporter;
30 private debuggeeProcess: child_process.ChildProcess | null = null;
31 /** A deferred that we use to make sure that worker has been loaded completely defore start sending IPC messages */
32 private workerLoaded = Q.defer<void>();
33 private bundleLoaded: Q.Deferred<void>;
34 private remoteExtension: RemoteExtension;
35
36 constructor(
37 private packagerPort: number,
38 private sourcesStoragePath: string,
39 private projectRootPath: string,
40 private postReplyToApp: (message: any) => void
41 ) {
42 this.scriptImporter = new ScriptImporter(this.packagerPort, this.sourcesStoragePath);
43
44 this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
45
46 this.remoteExtension.api.Debugger.onShowDevMenu(() => {
47 this.postMessage({
48 method: "vscode_showDevMenu",
49 });
50 });
51
52 this.remoteExtension.api.Debugger.onReloadApp(() => {
53 this.postMessage({
54 method: "vscode_reloadApp",
55 });
56 });
57 }
58
59 public stop() {
60 if (this.debuggeeProcess) {
61 logger.verbose(`About to kill debuggee with pid ${this.debuggeeProcess.pid}`);
62 this.debuggeeProcess.kill();
63 this.debuggeeProcess = null;
64 }
65 }
66
67 public start(): Q.Promise<number> {
68 let scriptToRunPath = path.resolve(this.sourcesStoragePath, ScriptImporter.DEBUGGER_WORKER_FILENAME);
69 const port = Math.round(Math.random() * 40000 + 3000);
70
71 // Note that we set --debug-brk flag to pause the process on the first line - this is
72 // required for debug adapter to set the breakpoints BEFORE the debuggee has started.
73 // The adapter will continue execution once it's done with breakpoints.
74 const nodeArgs = [`--inspect=${port}`, "--debug-brk", scriptToRunPath];
75 // Start child Node process in debugging mode
76 this.debuggeeProcess = child_process.spawn("node", nodeArgs, {
77 stdio: ["pipe", "pipe", "pipe", "ipc"],
78 })
79 .on("message", (message: any) => {
80 // 'workerLoaded' is a special message that indicates that worker is done with loading.
81 // We need to wait for it before doing any IPC because process.send doesn't seems to care
82 // about whether the messahe has been received or not and the first messages are often get
83 // discarded by spawned process
84 if (message && message.workerLoaded) {
85 this.workerLoaded.resolve(void 0);
86 return;
87 }
88
89 this.postReplyToApp(message);
90 })
91 .on("error", (error: Error) => {
92 printDebuggingError("React Native worker process thrown an error", error);
93 });
94
95 // Resolve with port debugger server is listening on
96 // This will be sent to subscribers of MLAppWorker in "connected" event
97 logger.verbose(`Spawned debuggee process with pid ${this.debuggeeProcess.pid} listening to ${port}`);
98
99 return Q.resolve(port);
100 }
101
102 public postMessage(rnMessage: RNAppMessage): void {
103 // Before sending messages, make sure that the worker is loaded
104 this.workerLoaded.promise
105 .then(() => {
106 if (rnMessage.method !== "executeApplicationScript") {
107 // Before sending messages, make sure that the app script executed
108 if (this.bundleLoaded) {
109 return this.bundleLoaded.promise.then(() => {
110 return rnMessage;
111 });
112 } else {
113 return rnMessage;
114 }
115 } else {
116 this.bundleLoaded = Q.defer<void>();
117 // When packager asks worker to load bundle we download that bundle and
118 // then set url field to point to that downloaded bundle, so the worker
119 // will take our modified bundle
120 if (rnMessage.url) {
121 logger.verbose("Packager requested runtime to load script from " + rnMessage.url);
122 return this.scriptImporter.downloadAppScript(rnMessage.url)
123 .then((downloadedScript: DownloadedScript) => {
124 this.bundleLoaded.resolve(void 0);
125 return Object.assign({}, rnMessage, { url: downloadedScript.filepath });
126 });
127 } else {
128 throw Error("RNMessage with method 'executeApplicationScript' doesn't have 'url' property");
129 }
130 }
131 })
132 .done(
133 (message: RNAppMessage) => {
134 if (this.debuggeeProcess) {
135 this.debuggeeProcess.send({ data: message });
136 }
137 },
138 (reason) => printDebuggingError(`Couldn't import script at <${rnMessage.url}>`, reason));
139 }
140}
141