microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4677921ce087214eb6edd7fb26bd1938637970be

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/debugger/appWorker.ts

161lines · modecode

1import * as vm from "vm";
2import * as fs from "fs";
3import * as Q from "q";
4import * as path from "path";
5import * as websocket from "websocket";
6import {ScriptImporter} from "./scriptImporter";
7import {Packager} from "./packager";
8import {Log} from "../utils/commands/log";
9
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;
19 self: DebuggerWorkerSandbox;
20 console: any;
21 require: (filePath: string) => any;
22 importScripts: (url: string) => void;
23 postMessage: (object: any) => void;
24 onmessage: (object: any) => void;
25}
26
27export class SandboxedAppWorker {
28 private sourcesStoragePath: string;
29 private postReplyToApp: (message: any) => void;
30
31 private sandbox: DebuggerWorkerSandbox;
32 private sandboxContext: vm.Context;
33
34 private isAnswerReady = Q({});
35
36 constructor(sourcesStoragePath: string, postReplyToApp: (message: any) => void) {
37 this.sourcesStoragePath = sourcesStoragePath;
38 this.postReplyToApp = postReplyToApp;
39 }
40
41 private runInSandbox(filename: string, fileContents?: string) {
42 if (!fileContents) {
43 fileContents = fs.readFileSync(filename).toString();
44 }
45
46 vm.runInContext(fileContents, this.sandboxContext, filename);
47 }
48
49 public start(): SandboxedAppWorker {
50 let scriptToRunPath = require.resolve(path.join(this.sourcesStoragePath, "debuggerWorker"));
51 this.initializeSandboxAndContext(scriptToRunPath);
52 let fileContents = fs.readFileSync(scriptToRunPath).toString();
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)
56 this.runInSandbox(scriptToRunPath, "var onmessage = null; " + fileContents);
57 return this;
58 }
59
60 public postMessage(object: any): void {
61 this.sandbox.onmessage({ data: object });
62 }
63
64 private initializeSandboxAndContext(scriptToRunPath: string): void {
65 let scriptToRunModule = new Module(scriptToRunPath);
66
67 this.sandbox = {
68 __filename: scriptToRunPath,
69 __dirname: path.dirname(scriptToRunPath),
70 self: null,
71 console: console,
72 require: (filePath: string) => scriptToRunModule.require(filePath), // Give the sandbox access to require("<filePath>");
73 importScripts: (url: string) => this.importScripts(url), // Import script like using <script/>
74 postMessage: (object: any) => this.gotResponseFromDebuggerWorker(object), // Post message back to the UI thread
75 onmessage: null
76 };
77 this.sandbox.self = this.sandbox;
78
79 this.sandboxContext = vm.createContext(this.sandbox);
80 }
81
82 private importScripts(url: string): void {
83 // The debugger worker gives a replay synchronically. We use the promise to make that replay
84 // get blocked until the script gets actually imported
85 let defer = Q.defer<{}>();
86 this.isAnswerReady = defer.promise;
87
88 // The next line converts to any due to the incorrect typing on node.d.ts of vm.runInThisContext
89 new ScriptImporter(this.sourcesStoragePath).download(url)
90 .then(downloadedScript =>
91 this.runInSandbox(downloadedScript.filepath, downloadedScript.contents))
92 .done(() =>
93 defer.resolve({})); // Now we let the reply to the app proceed
94 }
95
96 private gotResponseFromDebuggerWorker(object: any): void {
97 // We might need to hold the response until a script is imported
98 this.isAnswerReady.done(() =>
99 this.postReplyToApp(object));
100 }
101}
102
103export class MultipleLifetimesAppWorker {
104 private sourcesStoragePath: string;
105 private socketToApp: any;
106 private singleLifetimeWorker: SandboxedAppWorker;
107
108 constructor(sourcesStoragePath: string) {
109 this.sourcesStoragePath = sourcesStoragePath;
110 }
111
112 public start() {
113 this.singleLifetimeWorker = new SandboxedAppWorker(this.sourcesStoragePath, (message) =>
114 this.sendMessageToApp(message)).start();
115 this.socketToApp = this.createSocketToApp();
116 }
117
118 private createSocketToApp() {
119 let socketToApp = new WebSocket(this.debuggerProxyUrl());
120 socketToApp.onopen = () => this.socketToAppWasOpened();
121 socketToApp.onclose = () => this.socketWasClosed();
122 socketToApp.onmessage = (message: any) => this.messageReceivedFromApp(message);
123 return socketToApp;
124 }
125
126 private debuggerProxyUrl() {
127 return `ws://${Packager.HOST}/debugger-proxy`;
128 }
129
130 private socketToAppWasOpened() {
131 Log.logMessage("Established a connection with the Proxy (Packager) to the React Native application");
132 }
133
134 private socketWasClosed() {
135 Log.logMessage("Disconnected from the Proxy (Packager) to the React Native application. Retrying reconnection soon...");
136 setTimeout(this.start, 100);
137 }
138
139 private messageReceivedFromApp(message: any) {
140 let object = JSON.parse(message.data);
141 if (object.method === "prepareJSRuntime") {
142 // The MultipleLifetimesAppWorker will handle prepareJSRuntime aka create new lifetime
143 this.gotPrepareJSRuntime(object);
144 } else if (object.method) {
145 // All the other messages are handled by the single lifetime worker
146 this.singleLifetimeWorker.postMessage(object);
147 } else {
148 // Message doesn't have a method. Ignore it.
149 Log.logInternalMessage("The react-native app sent a message without specifying a method: " + message);
150 }
151 }
152
153 private gotPrepareJSRuntime(message: any): void {
154 // Create the sandbox, and replay that we finished processing the message
155 this.sendMessageToApp({ replyID: parseInt(message.id, 10) });
156 }
157
158 private sendMessageToApp(message: any) {
159 this.socketToApp.send(JSON.stringify(message));
160 }
161}
162