microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
d3897897910e9b3fe5fea5c60a8d8fc5c5a99bbe

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/appWorker.ts

248lines · modeblame

9f036952Nisheet Jain10 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
4677921cdigeff10 years ago4import * as vm from "vm";
5import * as Q from "q";
6import * as path from "path";
ea8a5f88digeff10 years ago7import * as WebSocket from "ws";
4677921cdigeff10 years ago8import {ScriptImporter} from "./scriptImporter";
b0061ac6Meena Kunnathur Balakrishnan10 years ago9import {Packager} from "../common/packager";
c2bf3c4fdigeff10 years ago10import {ErrorHelper} from "../common/error/errorHelper";
11import {Log} from "../common/log/log";
12import {LogLevel} from "../common/log/logHelper";
b0061ac6Meena Kunnathur Balakrishnan10 years ago13import {Node} from "../common/node/node";
7cc67271digeff10 years ago14import {ExecutionsLimiter} from "../common/executionsLimiter";
4677921cdigeff10 years ago15
16import Module = require("module");
17
18// This file is a replacement of: https://github.com/facebook/react-native/blob/8d397b4cbc05ad801cfafb421cee39bcfe89711d/local-cli/server/util/debugger.html for Node.JS
19interface DebuggerWorkerSandbox {
20__filename: string;
21__dirname: string;
22self: DebuggerWorkerSandbox;
ea8a5f88digeff10 years ago23console: typeof console;
24require: (id: string) => any;
4677921cdigeff10 years ago25importScripts: (url: string) => void;
26postMessage: (object: any) => void;
ea8a5f88digeff10 years ago27onmessage: (object: RNAppMessage) => void;
28postMessageArgument: RNAppMessage; // We use this argument to pass messages to the worker
4677921cdigeff10 years ago29}
30
ea8a5f88digeff10 years ago31interface RNAppMessage {
32method: string;
33// These objects have also other properties but that we don't currently use
34}
5d4d4de0digeff10 years ago35
ce591c62digeff10 years ago36function printDebuggingError(message: string, reason: any) {
c2bf3c4fdigeff10 years ago37Log.logWarning(ErrorHelper.getNestedWarning(reason, `${message}. Debugging won't work: Try reloading the JS from inside the app, or Reconnect the VS Code debugger`));
5d4d4de0digeff10 years ago38}
39
4677921cdigeff10 years ago40export class SandboxedAppWorker {
ea8a5f88digeff10 years ago41/** This class will run the RN App logic inside a sandbox. The framework to run the logic is provided by the file
42* debuggerWorker.js (designed to run on a WebWorker). We load that file inside a sandbox, and then we use the
43* PROCESS_MESSAGE_INSIDE_SANDBOX script to execute the logic to respond to a message inside the sandbox.
44* The code inside the debuggerWorker.js will call the global function postMessage to send a reply back to the app,
45* so we define our custom function there, so we can handle the message. We also provide our own importScript function
46* to download any script used by debuggerWorker.js
47*/
4677921cdigeff10 years ago48private sourcesStoragePath: string;
5547a16fJimmy Thomson10 years ago49private debugAdapterPort: number;
4677921cdigeff10 years ago50private postReplyToApp: (message: any) => void;
51
52private sandbox: DebuggerWorkerSandbox;
53private sandboxContext: vm.Context;
ea8a5f88digeff10 years ago54private scriptToReceiveMessageInSandbox: vm.Script;
4677921cdigeff10 years ago55
5d4d4de0digeff10 years ago56private pendingScriptImport = Q(void 0);
4677921cdigeff10 years ago57
ea8a5f88digeff10 years ago58private static PROCESS_MESSAGE_INSIDE_SANDBOX = "onmessage({ data: postMessageArgument });";
59
5547a16fJimmy Thomson10 years ago60constructor(sourcesStoragePath: string, debugAdapterPort: number, postReplyToApp: (message: any) => void) {
4677921cdigeff10 years ago61this.sourcesStoragePath = sourcesStoragePath;
2570720bJimmy Thomson10 years ago62this.debugAdapterPort = debugAdapterPort;
4677921cdigeff10 years ago63this.postReplyToApp = postReplyToApp;
ea8a5f88digeff10 years ago64this.scriptToReceiveMessageInSandbox = new vm.Script(SandboxedAppWorker.PROCESS_MESSAGE_INSIDE_SANDBOX);
4677921cdigeff10 years ago65}
66
5d4d4de0digeff10 years ago67public start(): Q.Promise<void> {
2743f19cdlebu10 years ago68let scriptToRunPath = require.resolve(path.join(this.sourcesStoragePath, ScriptImporter.DEBUGGER_WORKER_FILE_BASENAME));
4677921cdigeff10 years ago69this.initializeSandboxAndContext(scriptToRunPath);
5d4d4de0digeff10 years ago70return this.readFileContents(scriptToRunPath).then(fileContents =>
71// On a debugger worker the onmessage variable already exist. We need to declare it before the
72// javascript file can assign it. We do it in the first line without a new line to not break
73// the debugging experience of debugging debuggerWorker.js itself (as part of the extension)
74this.runInSandbox(scriptToRunPath, "var onmessage = null; " + fileContents));
4677921cdigeff10 years ago75}
76
ea8a5f88digeff10 years ago77public postMessage(object: RNAppMessage): void {
78this.sandbox.postMessageArgument = object;
79this.scriptToReceiveMessageInSandbox.runInContext(this.sandboxContext);
4677921cdigeff10 years ago80}
81
82private initializeSandboxAndContext(scriptToRunPath: string): void {
83let scriptToRunModule = new Module(scriptToRunPath);
84
85this.sandbox = {
86__filename: scriptToRunPath,
87__dirname: path.dirname(scriptToRunPath),
88self: null,
89console: console,
90require: (filePath: string) => scriptToRunModule.require(filePath), // Give the sandbox access to require("<filePath>");
91importScripts: (url: string) => this.importScripts(url), // Import script like using <script/>
92postMessage: (object: any) => this.gotResponseFromDebuggerWorker(object), // Post message back to the UI thread
ea8a5f88digeff10 years ago93onmessage: null,
94postMessageArgument: null
4677921cdigeff10 years ago95};
96this.sandbox.self = this.sandbox;
97
98this.sandboxContext = vm.createContext(this.sandbox);
99}
100
b3a793eeNisheet Jain10 years ago101private runInSandbox(filename: string, fileContents?: string): Q.Promise<void> {
102let fileContentsPromise = fileContents
103? Q(fileContents)
104: this.readFileContents(filename);
105
106return fileContentsPromise.then(contents => {
107vm.runInContext(contents, this.sandboxContext, filename);
108});
109}
110
111private readFileContents(filename: string) {
112return new Node.FileSystem().readFile(filename).then(contents => contents.toString());
113}
114
4677921cdigeff10 years ago115private importScripts(url: string): void {
5d4d4de0digeff10 years ago116/* The debuggerWorker.js executes this code:
117importScripts(message.url);
118sendReply();
119
120In the original code importScripts is a sync call. In our code it's async, so we need to mess with sendReply() so we won't
121actually send the reply back to the application until after importScripts has finished executing. We use
122this.pendingScriptImport to make the gotResponseFromDebuggerWorker() method hold the reply back, until've finished importing
123and running the script */
4677921cdigeff10 years ago124let defer = Q.defer<{}>();
5d4d4de0digeff10 years ago125this.pendingScriptImport = defer.promise;
4677921cdigeff10 years ago126
127// The next line converts to any due to the incorrect typing on node.d.ts of vm.runInThisContext
2743f19cdlebu10 years ago128new ScriptImporter(this.sourcesStoragePath).downloadAppScript(url, this.debugAdapterPort)
4677921cdigeff10 years ago129.then(downloadedScript =>
130this.runInSandbox(downloadedScript.filepath, downloadedScript.contents))
5d4d4de0digeff10 years ago131.done(() => {
9d7db611digeff10 years ago132// Now we let the reply to the app proceed
133defer.resolve({});
134}, reason => {
ce591c62digeff10 years ago135printDebuggingError(`Couldn't import script at <${url}>`, reason);
9d7db611digeff10 years ago136});
4677921cdigeff10 years ago137}
138
139private gotResponseFromDebuggerWorker(object: any): void {
5d4d4de0digeff10 years ago140// We might need to hold the response until a script is imported. See comments on this.importScripts()
141this.pendingScriptImport.done(() =>
10873e11digeff10 years ago142this.postReplyToApp(object), reason => {
ce591c62digeff10 years ago143printDebuggingError("Unexpected internal error while processing a message from the RN App.", reason);
10873e11digeff10 years ago144});
4677921cdigeff10 years ago145}
146}
147
148export class MultipleLifetimesAppWorker {
ea8a5f88digeff10 years ago149/** This class will create a SandboxedAppWorker that will run the RN App logic, and then create a socket
150* and send the RN App messages to the SandboxedAppWorker. The only RN App message that this class handles
151* is the prepareJSRuntime, which we reply to the RN App that the sandbox was created succesfully.
152* When the socket closes, we'll create a new SandboxedAppWorker and a new socket pair and discard the old ones.
153*/
4677921cdigeff10 years ago154private sourcesStoragePath: string;
5547a16fJimmy Thomson10 years ago155private debugAdapterPort: number;
ea8a5f88digeff10 years ago156private socketToApp: WebSocket;
4677921cdigeff10 years ago157private singleLifetimeWorker: SandboxedAppWorker;
158
7cc67271digeff10 years ago159private executionLimiter = new ExecutionsLimiter();
160
5547a16fJimmy Thomson10 years ago161constructor(sourcesStoragePath: string, debugAdapterPort: number) {
4677921cdigeff10 years ago162this.sourcesStoragePath = sourcesStoragePath;
5547a16fJimmy Thomson10 years ago163this.debugAdapterPort = debugAdapterPort;
ea8a5f88digeff10 years ago164console.assert(!!this.sourcesStoragePath, "The sourcesStoragePath argument was null or empty");
4677921cdigeff10 years ago165}
166
5d4d4de0digeff10 years ago167public start(): Q.Promise<void> {
ff7dce65digeff10 years ago168this.socketToApp = this.createSocketToApp();
169return Q.resolve<void>(void 0); // Currently this method is sync
170}
171
172private startNewWorkerLifetime(): Q.Promise<void> {
5547a16fJimmy Thomson10 years ago173this.singleLifetimeWorker = new SandboxedAppWorker(this.sourcesStoragePath, this.debugAdapterPort, (message) => {
5d4d4de0digeff10 years ago174this.sendMessageToApp(message);
175});
ff7dce65digeff10 years ago176Log.logInternalMessage(LogLevel.Info, "A new app worker lifetime was created.");
177return this.singleLifetimeWorker.start();
4677921cdigeff10 years ago178}
179
180private createSocketToApp() {
181let socketToApp = new WebSocket(this.debuggerProxyUrl());
ea8a5f88digeff10 years ago182socketToApp.on("open", () =>
183this.onSocketOpened());
184socketToApp.on("close", () =>
185this.onSocketClose());
186socketToApp.on("message",
187(message: any) => this.onMessage(message));
188socketToApp.on("error",
ce591c62digeff10 years ago189(error: Error) => printDebuggingError("An error ocurred while using the socket to communicate with the React Native app", error));
4677921cdigeff10 years ago190return socketToApp;
191}
192
193private debuggerProxyUrl() {
f158bbd1Atticus White10 years ago194return `ws://${Packager.HOST}/debugger-proxy?role=debugger&name=vscode`;
4677921cdigeff10 years ago195}
196
ea8a5f88digeff10 years ago197private onSocketOpened() {
7cc67271digeff10 years ago198this.executionLimiter.execute("onSocketOpened.msg", /*limitInSeconds*/ 10, () =>
199Log.logMessage("Established a connection with the Proxy (Packager) to the React Native application"));
4677921cdigeff10 years ago200}
201
ea8a5f88digeff10 years ago202private onSocketClose() {
7cc67271digeff10 years ago203this.executionLimiter.execute("onSocketClose.msg", /*limitInSeconds*/ 10, () =>
204Log.logMessage("Disconnected from the Proxy (Packager) to the React Native application. Retrying reconnection soon..."));
5d4d4de0digeff10 years ago205setTimeout(() => this.start(), 100);
4677921cdigeff10 years ago206}
207
ea8a5f88digeff10 years ago208private onMessage(message: string) {
5d4d4de0digeff10 years ago209try {
ea8a5f88digeff10 years ago210Log.logInternalMessage(LogLevel.Trace, "From RN APP: " + message);
211let object = <RNAppMessage>JSON.parse(message);
5d4d4de0digeff10 years ago212if (object.method === "prepareJSRuntime") {
213// The MultipleLifetimesAppWorker will handle prepareJSRuntime aka create new lifetime
214this.gotPrepareJSRuntime(object);
ff7dce65digeff10 years ago215} else if (object.method === "$disconnected") {
216// We need to shutdown the current app worker, and create a new lifetime
217this.singleLifetimeWorker = null;
5d4d4de0digeff10 years ago218} else if (object.method) {
219// All the other messages are handled by the single lifetime worker
220this.singleLifetimeWorker.postMessage(object);
221} else {
ea8a5f88digeff10 years ago222// Message doesn't have a method. Ignore it. This is an info message instead of warn because it's normal and expected
223Log.logInternalMessage(LogLevel.Info, "The react-native app sent a message without specifying a method: " + message);
5d4d4de0digeff10 years ago224}
225} catch (exception) {
ce591c62digeff10 years ago226printDebuggingError(`Failed to process message from the React Native app. Message:\n${message}`, exception);
4677921cdigeff10 years ago227}
228}
229
230private gotPrepareJSRuntime(message: any): void {
231// Create the sandbox, and replay that we finished processing the message
ff7dce65digeff10 years ago232this.startNewWorkerLifetime().done(() => {
233this.sendMessageToApp({ replyID: parseInt(message.id, 10) });
234}, error => printDebuggingError(`Failed to prepare the JavaScript runtime environment. Message:\n${message}`, error));
4677921cdigeff10 years ago235}
236
ff7dce65digeff10 years ago237private sendMessageToApp(message: any): void {
354c28a1digeff10 years ago238let stringified: string = null;
239try {
240stringified = JSON.stringify(message);
241Log.logInternalMessage(LogLevel.Trace, "To RN APP: " + stringified);
242this.socketToApp.send(stringified);
243} catch (exception) {
244let messageToShow = stringified || ("" + message); // Try to show the stringified version, but show the toString if unavailable
ce591c62digeff10 years ago245printDebuggingError(`Failed to send message to the React Native app. Message:\n${messageToShow}`, exception);
354c28a1digeff10 years ago246}
4677921cdigeff10 years ago247}
248}