microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
f920e582648215f80fb6b42513500bb1ab406158

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/forkedAppWorker.ts

131lines · 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} from "./scriptImporter";
8import {FileSystem} from "../common/node/fileSystem";
9
10import { Log } from "../common/log/log";
11import { ErrorHelper } from "../common/error/errorHelper";
12import { IDebuggeeWorker, RNAppMessage } from "./appWorker";
13
14// tslint:disable-next-line:align
15const WORKER_BOOTSTRAP = `
16// Hacks to avoid accessing unitialized variables
17var onmessage=null, self=global;
18// Avoid Node's GLOBAL deprecation warning
19global.GLOBAL=global;
20process.on("message", function(message){
21 if (onmessage) onmessage(message);
22});
23postMessage = function(message){
24 process.send(message);
25};
26importScripts = function(scriptUrl){
27 var scriptCode = require("fs").readFileSync(scriptUrl, "utf8");
28 require("vm").runInThisContext(scriptCode, { filename: scriptUrl });
29};`;
30
31const WORKER_DONE = `// Notify debugger that we're done with loading
32// and started listening for IPC messages
33postMessage({workerLoaded:true});`;
34
35function printDebuggingError(message: string, reason: any) {
36 Log.logWarning(ErrorHelper.getNestedWarning(reason, `${message}. Debugging won't work: Try reloading the JS from inside the app, or Reconnect the VS Code debugger`));
37}
38
39// TODO: Add more logging
40// TODO: Replace all console logging with Log calls
41
42/** This class will run the RN App logic inside a forked Node process. The framework to run the logic is provided by the file
43 * debuggerWorker.js (designed to run on a WebWorker). We add a couple of tweaks (mostly to polyfill WebWorker API) to that
44 * file and load it inside of a process.
45 * On this side we listen to IPC messages and either respond to them or redirect them to packager via MultipleLifetimeAppWorker's
46 * instance. We also intercept packager's signal to load the bundle's code and mutate the message with path to file we've downloaded
47 * to let importScripts function take this file.
48 */
49export class ForkedAppWorker implements IDebuggeeWorker {
50
51 private nodeFileSystem: FileSystem = new FileSystem();
52 private scriptImporter: ScriptImporter;
53 private debuggeeProcess: child_process.ChildProcess = null;
54 /** A deferred that we use to make sure that worker has been loaded completely defore start sending IPC messages */
55 private workerLoaded = Q.defer<void>();
56
57 constructor(
58 private packagerPort: number,
59 private sourcesStoragePath: string,
60 private postReplyToApp: (message: any) => void
61 ) {
62 this.scriptImporter = new ScriptImporter(packagerPort, sourcesStoragePath);
63 }
64
65 public stop() {
66 if (this.debuggeeProcess) {
67 console.log(`KILLING ${this.debuggeeProcess.pid} !!!!!!!!!!!!!!!!!!!!!!!!!!`);
68 this.debuggeeProcess.kill();
69 this.debuggeeProcess = null;
70 }
71 }
72
73 public start(): Q.Promise<number> {
74 let scriptToRunPath = path.resolve(this.sourcesStoragePath, ScriptImporter.DEBUGGER_WORKER_FILENAME);
75
76 return this.scriptImporter.downloadDebuggerWorker(this.sourcesStoragePath)
77 .then(() => this.nodeFileSystem.readFile(scriptToRunPath, "utf8"))
78 .then((workerContent: string) => {
79 // Add our customizations to debugger worker to get it working smoothly
80 // in Node env and polyfill WebWorkers API over Node's IPC.
81 const modifiedDebuggeeContent = [WORKER_BOOTSTRAP, workerContent, WORKER_DONE].join("\n");
82 return this.nodeFileSystem.writeFile(scriptToRunPath, modifiedDebuggeeContent);
83 })
84 .then(() => {
85 // Create a random port to use for debugging
86 const port = Math.round(Math.random() * 40000 + 3000);
87 // Start forked Node process in debugging mode
88 this.debuggeeProcess = child_process.fork(scriptToRunPath, [], {
89 // Note that we set --debug-brk flag to pause the process on the first line - this is
90 // required for debug adapter to set the breakpoints BEFORE the debuggee has started.
91 // The adapter will continue execution once it's done with breakpoints.
92 execArgv: [`--inspect=${port}`, "--debug-brk"],
93 })
94 // TODO: shouldn't this be of RNAppMessage?
95 .on("message", (message: any) => {
96 // 'workerLoaded' is a special message that indicates that worker is done with loading.
97 // We need to wait for it before doing any IPC because process.send doesn't seems to care
98 // about whether the messahe has been received or not and the first messages are often get
99 // discarded by spawned process
100 if (message && message.workerLoaded) {
101 this.workerLoaded.resolve(void 0);
102 return;
103 }
104
105 this.postReplyToApp(message);
106 });
107
108 console.log(`SPAWNED ${this.debuggeeProcess.pid} !!!!!!!!!!!!!!!!!!!!!!!!!!`);
109
110 // Resolve with port debugger server is listening on
111 // This will be sent to subscribers of MLAppWorker in "connected" event
112 return port;
113 });
114 }
115
116 public postMessage(rnMessage: RNAppMessage): void {
117 // Before sending messages, make sure that the worker is loaded
118 this.workerLoaded.promise
119 .then(() => {
120 if (rnMessage.method !== "executeApplicationScript") return Q.resolve(rnMessage);
121
122 // When packager asks worker to load bundle we download that bundle first
123 // and then set url field to point to that downloaded bundle, so the worker
124 // will take our modified bundle
125 return this.scriptImporter.downloadAppScript(rnMessage.url)
126 .then(downloadedScript => Object.assign({}, rnMessage, { url: downloadedScript.filepath }));
127 })
128 .done((message: RNAppMessage) => this.debuggeeProcess.send({ data: message }),
129 reason => printDebuggingError(`Couldn't import script at <${rnMessage.url}>`, reason));
130 }
131}
132