microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2ec44b6d62529aa08a88dc1dbe190833e1838036

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/appWorker.ts

195lines · 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";
7import * as websocket from "websocket";
8import {ScriptImporter} from "./scriptImporter";
9import {Packager} from "./packager";
10import {Log} from "../utils/commands/log";
5d4d4de0digeff10 years ago11import {Node} from "../utils/node/node";
4677921cdigeff10 years ago12
13import Module = require("module");
14
15let WebSocket = (<any>websocket).w3cwebsocket;
16
17// This file is a replacement of: https://github.com/facebook/react-native/blob/8d397b4cbc05ad801cfafb421cee39bcfe89711d/local-cli/server/util/debugger.html for Node.JS
18
19interface DebuggerWorkerSandbox {
20__filename: string;
21__dirname: string;
22self: DebuggerWorkerSandbox;
23console: any;
24require: (filePath: string) => any;
25importScripts: (url: string) => void;
26postMessage: (object: any) => void;
27onmessage: (object: any) => void;
28}
29
5d4d4de0digeff10 years ago30
31function printDebuggingFatalError(message: string, reason: any) {
fb8f49fbMeena Kunnathur Balakrishnan10 years ago32Log.logError(`${message}. Debugging won't work: Try reloading the JS from inside the app, or Reconnect the VS Code debugger`, reason);
5d4d4de0digeff10 years ago33}
34
4677921cdigeff10 years ago35export class SandboxedAppWorker {
36private sourcesStoragePath: string;
37private postReplyToApp: (message: any) => void;
38
39private sandbox: DebuggerWorkerSandbox;
40private sandboxContext: vm.Context;
41
5d4d4de0digeff10 years ago42private pendingScriptImport = Q(void 0);
4677921cdigeff10 years ago43
44constructor(sourcesStoragePath: string, postReplyToApp: (message: any) => void) {
45this.sourcesStoragePath = sourcesStoragePath;
46this.postReplyToApp = postReplyToApp;
47}
48
5d4d4de0digeff10 years ago49public start(): Q.Promise<void> {
50let scriptToRunPath = require.resolve(path.join(this.sourcesStoragePath, Packager.DEBUGGER_WORKER_FILE_BASENAME));
4677921cdigeff10 years ago51this.initializeSandboxAndContext(scriptToRunPath);
5d4d4de0digeff10 years ago52return this.readFileContents(scriptToRunPath).then(fileContents =>
53// On a debugger worker the onmessage variable already exist. We need to declare it before the
54// javascript file can assign it. We do it in the first line without a new line to not break
55// the debugging experience of debugging debuggerWorker.js itself (as part of the extension)
56this.runInSandbox(scriptToRunPath, "var onmessage = null; " + fileContents));
4677921cdigeff10 years ago57}
58
59public postMessage(object: any): void {
9d7db611digeff10 years ago60// TODO: Run this call inside of the sandbox
4677921cdigeff10 years ago61this.sandbox.onmessage({ data: object });
62}
63
64private initializeSandboxAndContext(scriptToRunPath: string): void {
65let scriptToRunModule = new Module(scriptToRunPath);
66
67this.sandbox = {
68__filename: scriptToRunPath,
69__dirname: path.dirname(scriptToRunPath),
70self: null,
71console: console,
72require: (filePath: string) => scriptToRunModule.require(filePath), // Give the sandbox access to require("<filePath>");
73importScripts: (url: string) => this.importScripts(url), // Import script like using <script/>
74postMessage: (object: any) => this.gotResponseFromDebuggerWorker(object), // Post message back to the UI thread
75onmessage: null
76};
77this.sandbox.self = this.sandbox;
78
79this.sandboxContext = vm.createContext(this.sandbox);
80}
81
b3a793eeNisheet Jain10 years ago82private runInSandbox(filename: string, fileContents?: string): Q.Promise<void> {
83let fileContentsPromise = fileContents
84? Q(fileContents)
85: this.readFileContents(filename);
86
87return fileContentsPromise.then(contents => {
88vm.runInContext(contents, this.sandboxContext, filename);
89});
90}
91
92private readFileContents(filename: string) {
93return new Node.FileSystem().readFile(filename).then(contents => contents.toString());
94}
95
4677921cdigeff10 years ago96private importScripts(url: string): void {
5d4d4de0digeff10 years ago97/* The debuggerWorker.js executes this code:
98importScripts(message.url);
99sendReply();
100
101In 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
102actually send the reply back to the application until after importScripts has finished executing. We use
103this.pendingScriptImport to make the gotResponseFromDebuggerWorker() method hold the reply back, until've finished importing
104and running the script */
4677921cdigeff10 years ago105let defer = Q.defer<{}>();
5d4d4de0digeff10 years ago106this.pendingScriptImport = defer.promise;
4677921cdigeff10 years ago107
108// The next line converts to any due to the incorrect typing on node.d.ts of vm.runInThisContext
109new ScriptImporter(this.sourcesStoragePath).download(url)
110.then(downloadedScript =>
111this.runInSandbox(downloadedScript.filepath, downloadedScript.contents))
5d4d4de0digeff10 years ago112.done(() => {
9d7db611digeff10 years ago113// Now we let the reply to the app proceed
114defer.resolve({});
115}, reason => {
116printDebuggingFatalError(`Couldn't import script at <${url}>`, reason);
117});
4677921cdigeff10 years ago118}
119
120private gotResponseFromDebuggerWorker(object: any): void {
5d4d4de0digeff10 years ago121// We might need to hold the response until a script is imported. See comments on this.importScripts()
122this.pendingScriptImport.done(() =>
4677921cdigeff10 years ago123this.postReplyToApp(object));
124}
125}
126
127export class MultipleLifetimesAppWorker {
128private sourcesStoragePath: string;
129private socketToApp: any;
130private singleLifetimeWorker: SandboxedAppWorker;
131
132constructor(sourcesStoragePath: string) {
133this.sourcesStoragePath = sourcesStoragePath;
134}
135
5d4d4de0digeff10 years ago136public start(): Q.Promise<void> {
137this.singleLifetimeWorker = new SandboxedAppWorker(this.sourcesStoragePath, (message) => {
138this.sendMessageToApp(message);
139});
140return this.singleLifetimeWorker.start().then(() => {
141this.socketToApp = this.createSocketToApp();
142});
4677921cdigeff10 years ago143}
144
145private createSocketToApp() {
146let socketToApp = new WebSocket(this.debuggerProxyUrl());
147socketToApp.onopen = () => this.socketToAppWasOpened();
148socketToApp.onclose = () => this.socketWasClosed();
149socketToApp.onmessage = (message: any) => this.messageReceivedFromApp(message);
5d4d4de0digeff10 years ago150// TODO: Add on error handler
4677921cdigeff10 years ago151return socketToApp;
152}
153
154private debuggerProxyUrl() {
155return `ws://${Packager.HOST}/debugger-proxy`;
156}
157
158private socketToAppWasOpened() {
159Log.logMessage("Established a connection with the Proxy (Packager) to the React Native application");
160}
161
162private socketWasClosed() {
5d4d4de0digeff10 years ago163// TODO: Add some logic to not print this message that often, we'll spam the user
4677921cdigeff10 years ago164Log.logMessage("Disconnected from the Proxy (Packager) to the React Native application. Retrying reconnection soon...");
5d4d4de0digeff10 years ago165setTimeout(() => this.start(), 100);
4677921cdigeff10 years ago166}
167
5d4d4de0digeff10 years ago168// TODO: Add proper typings for message
4677921cdigeff10 years ago169private messageReceivedFromApp(message: any) {
5d4d4de0digeff10 years ago170try {
171let object = JSON.parse(message.data);
172if (object.method === "prepareJSRuntime") {
173// The MultipleLifetimesAppWorker will handle prepareJSRuntime aka create new lifetime
174this.gotPrepareJSRuntime(object);
175} else if (object.method) {
176// All the other messages are handled by the single lifetime worker
177this.singleLifetimeWorker.postMessage(object);
178} else {
179// Message doesn't have a method. Ignore it.
180Log.logInternalMessage("The react-native app sent a message without specifying a method: " + message);
181}
182} catch (exception) {
183printDebuggingFatalError(`Failed to process message from the React Native app. Message:\n${message}`, exception);
4677921cdigeff10 years ago184}
185}
186
187private gotPrepareJSRuntime(message: any): void {
188// Create the sandbox, and replay that we finished processing the message
189this.sendMessageToApp({ replyID: parseInt(message.id, 10) });
190}
191
192private sendMessageToApp(message: any) {
193this.socketToApp.send(JSON.stringify(message));
194}
195}