microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fix-ts-error1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/forkedAppWorker.ts

211lines · 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 path from "path";
6eeec3c0Serge Svekolnikov8 years ago5import * as url from "url";
4f8669f9JiglioNero6 years ago6import * as cp from "child_process";
08722261Yuri Skorokhodov7 years ago7import * as fs from "fs";
623be8a6Ezio Li2 years ago8import { logger } from "@vscode/debugadapter";
e45838cbVladimir Kotikov9 years ago9import { ErrorHelper } from "../common/error/errorHelper";
1758f9a6Yuri Skorokhodov7 years ago10import { InternalErrorCode } from "../common/error/internalErrorCode";
08722261Yuri Skorokhodov7 years ago11import { getLoggingDirectory } from "../extension/log/LogHelper";
f872f4d5RedMickey6 years ago12import { generateRandomPortNumber } from "../common/extensionHelper";
09f6024fHeniker4 years ago13import { IDebuggeeWorker, RNAppMessage } from "./appWorker";
14import { ScriptImporter } from "./scriptImporter";
e45838cbVladimir Kotikov9 years ago15
1758f9a6Yuri Skorokhodov7 years ago16function printDebuggingError(error: Error, reason: any) {
34472878RedMickey5 years ago17const nestedError = ErrorHelper.getNestedError(
18error,
19InternalErrorCode.DebuggingWontWorkReloadJSAndReconnect,
20reason,
21);
0a68f8dbArtem Egorov8 years ago22
23logger.error(nestedError.message);
e45838cbVladimir Kotikov9 years ago24}
25
26/** This class will run the RN App logic inside a forked Node process. The framework to run the logic is provided by the file
27* debuggerWorker.js (designed to run on a WebWorker). We add a couple of tweaks (mostly to polyfill WebWorker API) to that
28* file and load it inside of a process.
29* On this side we listen to IPC messages and either respond to them or redirect them to packager via MultipleLifetimeAppWorker's
30* instance. We also intercept packager's signal to load the bundle's code and mutate the message with path to file we've downloaded
31* to let importScripts function take this file.
32*/
33export class ForkedAppWorker implements IDebuggeeWorker {
6eeec3c0Serge Svekolnikov8 years ago34protected scriptImporter: ScriptImporter;
4f8669f9JiglioNero6 years ago35protected debuggeeProcess: cp.ChildProcess | null = null;
ce5e88eeYuri Skorokhodov5 years ago36/** A promise that we use to make sure that worker has been loaded completely before start sending IPC messages */
0d77292aJiglioNero4 years ago37protected workerLoaded: Promise<void> | undefined;
38private bundleLoaded: Promise<void> | undefined;
08722261Yuri Skorokhodov7 years ago39private logWriteStream: fs.WriteStream;
40private logDirectory: string | null;
e45838cbVladimir Kotikov9 years ago41
42constructor(
6eeec3c0Serge Svekolnikov8 years ago43private packagerAddress: string,
e45838cbVladimir Kotikov9 years ago44private packagerPort: number,
45private sourcesStoragePath: string,
7daed3fcArtem Egorov8 years ago46private projectRootPath: string,
6eeec3c0Serge Svekolnikov8 years ago47private postReplyToApp: (message: any) => void,
48private packagerRemoteRoot?: string,
34472878RedMickey5 years ago49private packagerLocalRoot?: string,
e45838cbVladimir Kotikov9 years ago50) {
34472878RedMickey5 years ago51this.scriptImporter = new ScriptImporter(
52this.packagerAddress,
53this.packagerPort,
54this.sourcesStoragePath,
55this.packagerRemoteRoot,
56this.packagerLocalRoot,
57);
e45838cbVladimir Kotikov9 years ago58}
59
34472878RedMickey5 years ago60public stop(): void {
e45838cbVladimir Kotikov9 years ago61if (this.debuggeeProcess) {
0a68f8dbArtem Egorov8 years ago62logger.verbose(`About to kill debuggee with pid ${this.debuggeeProcess.pid}`);
e45838cbVladimir Kotikov9 years ago63this.debuggeeProcess.kill();
64this.debuggeeProcess = null;
65}
66}
67
0d77292aJiglioNero4 years ago68public async start(): Promise<number> {
09f6024fHeniker4 years ago69const scriptToRunPath = path.resolve(
34472878RedMickey5 years ago70this.sourcesStoragePath,
71ScriptImporter.DEBUGGER_WORKER_FILENAME,
72);
f872f4d5RedMickey6 years ago73const port = generateRandomPortNumber();
e45838cbVladimir Kotikov9 years ago74
045132a1Yuri Skorokhodov7 years ago75// Note that we set --inspect-brk flag to pause the process on the first line - this is
cc70057dVladimir Kotikov9 years ago76// required for debug adapter to set the breakpoints BEFORE the debuggee has started.
77// The adapter will continue execution once it's done with breakpoints.
13a99427Yuri Skorokhodov6 years ago78// --no-deprecation flag disables deprecation warnings like "[DEP0005] DeprecationWarning: Buffer() is deprecated..." and so on that leads to errors in native app
79// https://nodejs.org/dist/latest-v7.x/docs/api/cli.html
80const nodeArgs = [`--inspect-brk=${port}`, "--no-deprecation", scriptToRunPath];
cc70057dVladimir Kotikov9 years ago81// Start child Node process in debugging mode
f533409bRuslan Bikkinin7 years ago82// Using fork instead of spawn causes breakage of piping between app worker and VS Code debug console, e.g. console.log() in application
af0c5c30Yuri Skorokhodov5 years ago83// wouldn't work. Please see https://github.com/microsoft/vscode-react-native/issues/758
34472878RedMickey5 years ago84this.debuggeeProcess = cp
85.spawn("node", nodeArgs, {
86stdio: ["pipe", "pipe", "pipe", "ipc"],
87})
ce5e88eeYuri Skorokhodov5 years ago88.on("message", (message: any) => {
89// 'workerLoaded' is a special message that indicates that worker is done with loading.
90// We need to wait for it before doing any IPC because process.send doesn't seems to care
91// about whether the message has been received or not and the first messages are often get
92// discarded by spawned process
93if (message && message.workerLoaded) {
94this.workerLoaded = Promise.resolve();
95return;
96}
e45838cbVladimir Kotikov9 years ago97
ce5e88eeYuri Skorokhodov5 years ago98this.postReplyToApp(message);
99})
100.on("error", (error: Error) => {
34472878RedMickey5 years ago101printDebuggingError(
102ErrorHelper.getInternalError(
103InternalErrorCode.ReactNativeWorkerProcessThrownAnError,
104),
105error,
106);
ce5e88eeYuri Skorokhodov5 years ago107});
cc70057dVladimir Kotikov9 years ago108
08722261Yuri Skorokhodov7 years ago109// If special env variables are defined, then write process outputs to file
110this.logDirectory = getLoggingDirectory();
111
112if (this.logDirectory) {
34472878RedMickey5 years ago113this.logWriteStream = fs.createWriteStream(
114path.join(this.logDirectory, "nodeProcessLog.txt"),
115);
08722261Yuri Skorokhodov7 years ago116this.logWriteStream.on("error", err => {
34472878RedMickey5 years ago117logger.error(
09f6024fHeniker4 years ago118`Error creating log file at path: ${String(this.logDirectory)}. Error: ${String(
119err.toString(),
120)}\n`,
34472878RedMickey5 years ago121);
08722261Yuri Skorokhodov7 years ago122});
123this.debuggeeProcess.stdout.pipe(this.logWriteStream);
124this.debuggeeProcess.stderr.pipe(this.logWriteStream);
125this.debuggeeProcess.on("close", () => {
126this.logWriteStream.end();
127});
128}
129
cc70057dVladimir Kotikov9 years ago130// Resolve with port debugger server is listening on
131// This will be sent to subscribers of MLAppWorker in "connected" event
34472878RedMickey5 years ago132logger.verbose(
133`Spawned debuggee process with pid ${this.debuggeeProcess.pid} listening to ${port}`,
134);
cc70057dVladimir Kotikov9 years ago135
0d77292aJiglioNero4 years ago136return port;
e45838cbVladimir Kotikov9 years ago137}
138
0d77292aJiglioNero4 years ago139public async postMessage(rnMessage: RNAppMessage): Promise<RNAppMessage> {
140// Before sending messages, make sure that the worker is loaded
09f6024fHeniker4 years ago141await new Promise<void>(resolve => {
b8887dd1RedMickey4 years ago142if (this.workerLoaded) {
143resolve();
144} else {
145const checkWorkerLoaded = setInterval(() => {
146if (this.workerLoaded) {
147clearInterval(checkWorkerLoaded);
148resolve();
149}
150}, 1000);
151}
152});
0d77292aJiglioNero4 years ago153
154const promise = (async () => {
155await this.workerLoaded;
156
157if (rnMessage.method !== "executeApplicationScript") {
158// Before sending messages, make sure that the app script executed
159await this.bundleLoaded;
160return rnMessage;
161}
09f6024fHeniker4 years ago162// When packager asks worker to load bundle we download that bundle and
163// then set url field to point to that downloaded bundle, so the worker
164// will take our modified bundle
165if (rnMessage.url) {
166const packagerUrl = url.parse(rnMessage.url);
167packagerUrl.host = `${this.packagerAddress}:${this.packagerPort}`;
168rnMessage = {
169...rnMessage,
170url: url.format(packagerUrl),
171};
172logger.verbose(
173`Packager requested runtime to load script from ${String(rnMessage.url)}`,
174);
175const downloadedScript = await this.scriptImporter.downloadAppScript(
176<string>rnMessage.url,
177this.projectRootPath,
178);
179this.bundleLoaded = Promise.resolve();
180return Object.assign({}, rnMessage, {
181url: `${this.pathToFileUrl(downloadedScript.filepath)}`,
182});
183}
184throw ErrorHelper.getInternalError(
185InternalErrorCode.RNMessageWithMethodExecuteApplicationScriptDoesntHaveURLProperty,
186);
0d77292aJiglioNero4 years ago187})();
188promise.then(
189(message: RNAppMessage) => {
190if (this.debuggeeProcess) {
191this.debuggeeProcess.send({ data: message });
192}
193},
194reason =>
195printDebuggingError(
196ErrorHelper.getInternalError(
197InternalErrorCode.CouldntImportScriptAt,
198rnMessage.url,
34472878RedMickey5 years ago199),
0d77292aJiglioNero4 years ago200reason,
201),
202);
203return promise;
e45838cbVladimir Kotikov9 years ago204}
4cf8fdf4Yuri Skorokhodov6 years ago205
206// TODO: Replace by url.pathToFileURL method when Node 10 LTS become deprecated
34472878RedMickey5 years ago207public pathToFileUrl(url: string): string {
4cf8fdf4Yuri Skorokhodov6 years ago208const filePrefix = process.platform === "win32" ? "file:///" : "file://";
209return filePrefix + url;
210}
e45838cbVladimir Kotikov9 years ago211}