microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
f8d3243916133136da9c1d8eedef7428db609d7d

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/appWorker.ts

192lines · modeblame

4677921cdigeff10 years ago1import * as vm from "vm";
2import * as Q from "q";
3import * as path from "path";
4import * as websocket from "websocket";
5import {ScriptImporter} from "./scriptImporter";
6import {Packager} from "./packager";
7import {Log} from "../utils/commands/log";
5d4d4de0digeff10 years ago8import {Node} from "../utils/node/node";
4677921cdigeff10 years ago9
10import Module = require("module");
11
12let WebSocket = (<any>websocket).w3cwebsocket;
13
14// This file is a replacement of: https://github.com/facebook/react-native/blob/8d397b4cbc05ad801cfafb421cee39bcfe89711d/local-cli/server/util/debugger.html for Node.JS
15
16interface DebuggerWorkerSandbox {
17__filename: string;
18__dirname: string;
19self: DebuggerWorkerSandbox;
20console: any;
21require: (filePath: string) => any;
22importScripts: (url: string) => void;
23postMessage: (object: any) => void;
24onmessage: (object: any) => void;
25}
26
5d4d4de0digeff10 years ago27
28function printDebuggingFatalError(message: string, reason: any) {
fb8f49fbMeena Kunnathur Balakrishnan10 years ago29Log.logError(`${message}. Debugging won't work: Try reloading the JS from inside the app, or Reconnect the VS Code debugger`, reason);
5d4d4de0digeff10 years ago30}
31
4677921cdigeff10 years ago32export class SandboxedAppWorker {
33private sourcesStoragePath: string;
34private postReplyToApp: (message: any) => void;
35
36private sandbox: DebuggerWorkerSandbox;
37private sandboxContext: vm.Context;
38
5d4d4de0digeff10 years ago39private pendingScriptImport = Q(void 0);
4677921cdigeff10 years ago40
41constructor(sourcesStoragePath: string, postReplyToApp: (message: any) => void) {
42this.sourcesStoragePath = sourcesStoragePath;
43this.postReplyToApp = postReplyToApp;
44}
45
5d4d4de0digeff10 years ago46private runInSandbox(filename: string, fileContents?: string): Q.Promise<void> {
47let fileContentsPromise = fileContents
48? Q(fileContents)
49: this.readFileContents(filename);
50
46cad7ecdigeff10 years ago51return fileContentsPromise.then(contents => {
9d7db611digeff10 years ago52vm.runInContext(contents, this.sandboxContext, filename);
53});
5d4d4de0digeff10 years ago54}
4677921cdigeff10 years ago55
5d4d4de0digeff10 years ago56private readFileContents(filename: string) {
57return new Node.FileSystem().readFile(filename).then(contents => contents.toString());
4677921cdigeff10 years ago58}
59
5d4d4de0digeff10 years ago60public start(): Q.Promise<void> {
61let scriptToRunPath = require.resolve(path.join(this.sourcesStoragePath, Packager.DEBUGGER_WORKER_FILE_BASENAME));
4677921cdigeff10 years ago62this.initializeSandboxAndContext(scriptToRunPath);
5d4d4de0digeff10 years ago63return this.readFileContents(scriptToRunPath).then(fileContents =>
64// On a debugger worker the onmessage variable already exist. We need to declare it before the
65// javascript file can assign it. We do it in the first line without a new line to not break
66// the debugging experience of debugging debuggerWorker.js itself (as part of the extension)
67this.runInSandbox(scriptToRunPath, "var onmessage = null; " + fileContents));
4677921cdigeff10 years ago68}
69
70public postMessage(object: any): void {
9d7db611digeff10 years ago71// TODO: Run this call inside of the sandbox
4677921cdigeff10 years ago72this.sandbox.onmessage({ data: object });
73}
74
75private initializeSandboxAndContext(scriptToRunPath: string): void {
76let scriptToRunModule = new Module(scriptToRunPath);
77
78this.sandbox = {
79__filename: scriptToRunPath,
80__dirname: path.dirname(scriptToRunPath),
81self: null,
82console: console,
83require: (filePath: string) => scriptToRunModule.require(filePath), // Give the sandbox access to require("<filePath>");
84importScripts: (url: string) => this.importScripts(url), // Import script like using <script/>
85postMessage: (object: any) => this.gotResponseFromDebuggerWorker(object), // Post message back to the UI thread
86onmessage: null
87};
88this.sandbox.self = this.sandbox;
89
90this.sandboxContext = vm.createContext(this.sandbox);
91}
92
93private importScripts(url: string): void {
5d4d4de0digeff10 years ago94/* The debuggerWorker.js executes this code:
95importScripts(message.url);
96sendReply();
97
98In 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
99actually send the reply back to the application until after importScripts has finished executing. We use
100this.pendingScriptImport to make the gotResponseFromDebuggerWorker() method hold the reply back, until've finished importing
101and running the script */
4677921cdigeff10 years ago102let defer = Q.defer<{}>();
5d4d4de0digeff10 years ago103this.pendingScriptImport = defer.promise;
4677921cdigeff10 years ago104
105// The next line converts to any due to the incorrect typing on node.d.ts of vm.runInThisContext
106new ScriptImporter(this.sourcesStoragePath).download(url)
107.then(downloadedScript =>
108this.runInSandbox(downloadedScript.filepath, downloadedScript.contents))
5d4d4de0digeff10 years ago109.done(() => {
9d7db611digeff10 years ago110// Now we let the reply to the app proceed
111defer.resolve({});
112}, reason => {
113printDebuggingFatalError(`Couldn't import script at <${url}>`, reason);
114});
4677921cdigeff10 years ago115}
116
117private gotResponseFromDebuggerWorker(object: any): void {
5d4d4de0digeff10 years ago118// We might need to hold the response until a script is imported. See comments on this.importScripts()
119this.pendingScriptImport.done(() =>
4677921cdigeff10 years ago120this.postReplyToApp(object));
121}
122}
123
124export class MultipleLifetimesAppWorker {
125private sourcesStoragePath: string;
126private socketToApp: any;
127private singleLifetimeWorker: SandboxedAppWorker;
128
129constructor(sourcesStoragePath: string) {
130this.sourcesStoragePath = sourcesStoragePath;
131}
132
5d4d4de0digeff10 years ago133public start(): Q.Promise<void> {
134this.singleLifetimeWorker = new SandboxedAppWorker(this.sourcesStoragePath, (message) => {
135this.sendMessageToApp(message);
136});
137return this.singleLifetimeWorker.start().then(() => {
138this.socketToApp = this.createSocketToApp();
139});
4677921cdigeff10 years ago140}
141
142private createSocketToApp() {
143let socketToApp = new WebSocket(this.debuggerProxyUrl());
144socketToApp.onopen = () => this.socketToAppWasOpened();
145socketToApp.onclose = () => this.socketWasClosed();
146socketToApp.onmessage = (message: any) => this.messageReceivedFromApp(message);
5d4d4de0digeff10 years ago147// TODO: Add on error handler
4677921cdigeff10 years ago148return socketToApp;
149}
150
151private debuggerProxyUrl() {
152return `ws://${Packager.HOST}/debugger-proxy`;
153}
154
155private socketToAppWasOpened() {
156Log.logMessage("Established a connection with the Proxy (Packager) to the React Native application");
157}
158
159private socketWasClosed() {
5d4d4de0digeff10 years ago160// TODO: Add some logic to not print this message that often, we'll spam the user
4677921cdigeff10 years ago161Log.logMessage("Disconnected from the Proxy (Packager) to the React Native application. Retrying reconnection soon...");
5d4d4de0digeff10 years ago162setTimeout(() => this.start(), 100);
4677921cdigeff10 years ago163}
164
5d4d4de0digeff10 years ago165// TODO: Add proper typings for message
4677921cdigeff10 years ago166private messageReceivedFromApp(message: any) {
5d4d4de0digeff10 years ago167try {
168let object = JSON.parse(message.data);
169if (object.method === "prepareJSRuntime") {
170// The MultipleLifetimesAppWorker will handle prepareJSRuntime aka create new lifetime
171this.gotPrepareJSRuntime(object);
172} else if (object.method) {
173// All the other messages are handled by the single lifetime worker
174this.singleLifetimeWorker.postMessage(object);
175} else {
176// Message doesn't have a method. Ignore it.
177Log.logInternalMessage("The react-native app sent a message without specifying a method: " + message);
178}
179} catch (exception) {
180printDebuggingFatalError(`Failed to process message from the React Native app. Message:\n${message}`, exception);
4677921cdigeff10 years ago181}
182}
183
184private gotPrepareJSRuntime(message: any): void {
185// Create the sandbox, and replay that we finished processing the message
186this.sendMessageToApp({ replyID: parseInt(message.id, 10) });
187}
188
189private sendMessageToApp(message: any) {
190this.socketToApp.send(JSON.stringify(message));
191}
192}