diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m index d43c67321..d40713ee3 100644 --- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m +++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m @@ -21,7 +21,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); - (instancetype)init { - return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081"]]; + return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]]; } - (instancetype)initWithURL:(NSURL *)url @@ -35,6 +35,10 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply); _injectedObjects = [NSMutableDictionary dictionary]; [_socket setDelegateOperationQueue:_jsQueue]; + + NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:url]; + [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; + if (![self connectToProxy]) { RCTLogError(@"Connection to %@ timed out. Are you running node proxy?", url); [self invalidate]; diff --git a/packager/debugger.html b/packager/debugger.html new file mode 100644 index 000000000..f7cabc5f8 --- /dev/null +++ b/packager/debugger.html @@ -0,0 +1,112 @@ + + +
+ + + ++ React Native JS code runs inside this Chrome tab +
+Press ⌘⌥J to open Developer Tools. Enable Pause On Caught Exceptions for a better debugging experience.
+Status: Loading
+ + diff --git a/packager/launchChromeDevTools.applescript b/packager/launchChromeDevTools.applescript new file mode 100755 index 000000000..4384b3ae0 --- /dev/null +++ b/packager/launchChromeDevTools.applescript @@ -0,0 +1,41 @@ +#!/usr/bin/env osascript + + +on run argv + set theURL to item 1 of argv + + tell application "Google Chrome" + activate + + if (count every window) = 0 then + make new window + end if + + -- Find a tab currently running the debugger + set found to false + set theTabIndex to -1 + repeat with theWindow in every window + set theTabIndex to 0 + repeat with theTab in every tab of theWindow + set theTabIndex to theTabIndex + 1 + if theTab's URL is theURL then + set found to true + exit repeat + end if + end repeat + + if found then + exit repeat + end if + end repeat + + if found then + set index of theWindow to 1 + set theWindow's active tab index to theTabIndex + else + tell window 1 + make new tab with properties {URL:theURL} + end tell + end if + end tell +end run diff --git a/packager/packager.js b/packager/packager.js index bed452014..9920667e1 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -16,12 +16,14 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { process.exit(); } +var exec = require('child_process').exec; var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); var connect = require('connect'); var http = require('http'); var launchEditor = require('./launchEditor.js'); var parseCommandLine = require('./parseCommandLine.js'); +var webSocketProxy = require('./webSocketProxy.js'); var options = parseCommandLine([{ command: 'port', @@ -68,10 +70,12 @@ process.on('uncaughtException', function(e) { 'any existing instances that are already running.\n\n'); }); -runServer(options, function() { +var server = runServer(options, function() { console.log('\nReact packager ready.\n'); }); +webSocketProxy.attachToServer(server, '/debugger-proxy'); + function loadRawBody(req, res, next) { req.rawBody = ''; req.setEncoding('utf8'); @@ -95,6 +99,30 @@ function openStackFrameInEditor(req, res, next) { } } +function getDevToolsLauncher(options) { + return function(req, res, next) { + if (req.url === '/debugger-ui') { + var debuggerPath = path.join(__dirname, 'debugger.html'); + res.writeHead(200, {'Content-Type': 'text/html'}); + fs.createReadStream(debuggerPath).pipe(res); + } else if (req.url === '/launch-chrome-devtools') { + var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui'; + var script = 'launchChromeDevTools.applescript'; + console.log('Launching Dev Tools...'); + exec(path.join(__dirname, script) + ' ' + debuggerURL, function(err, stdout, stderr) { + if (err) { + console.log('Failed to run ' + script, err); + } + console.log(stdout); + console.warn(stderr); + }); + res.end('OK'); + } else { + next(); + } + }; +} + function getAppMiddleware(options) { return ReactPackager.middleware({ projectRoots: options.projectRoots, @@ -112,6 +140,7 @@ function runServer( var app = connect() .use(loadRawBody) .use(openStackFrameInEditor) + .use(getDevToolsLauncher(options)) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { diff --git a/packager/webSocketProxy.js b/packager/webSocketProxy.js new file mode 100644 index 000000000..dada059a8 --- /dev/null +++ b/packager/webSocketProxy.js @@ -0,0 +1,41 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ + +'use strict'; + +var WebSocketServer = require('ws').Server; + +function attachToServer(server, path) { + var wss = new WebSocketServer({ + server: server, + path: path + }); + var clients = []; + + wss.on('connection', function(ws) { + clients.push(ws); + + var allClientsExcept = function(ws) { + return clients.filter(function(cn) { return cn !== ws; }); + }; + + ws.onerror = function() { + clients = allClientsExcept(ws); + }; + + ws.onclose = function() { + clients = allClientsExcept(ws); + }; + + ws.on('message', function(message) { + allClientsExcept(ws).forEach(function(cn) { + cn.send(message); + }); + }); + }); +} + +module.exports = { + attachToServer: attachToServer +};