[ReactNative] Bring Chrome debugger to OSS. Part 2
This commit is contained in:
parent
da2b122e21
commit
8dea55618d
|
@ -21,7 +21,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||||
|
|
||||||
- (instancetype)init
|
- (instancetype)init
|
||||||
{
|
{
|
||||||
return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081"]];
|
return [self initWithURL:[NSURL URLWithString:@"http://localhost:8081/debugger-proxy"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithURL:(NSURL *)url
|
- (instancetype)initWithURL:(NSURL *)url
|
||||||
|
@ -35,6 +35,10 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||||
_injectedObjects = [NSMutableDictionary dictionary];
|
_injectedObjects = [NSMutableDictionary dictionary];
|
||||||
[_socket setDelegateOperationQueue:_jsQueue];
|
[_socket setDelegateOperationQueue:_jsQueue];
|
||||||
|
|
||||||
|
|
||||||
|
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:url];
|
||||||
|
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil];
|
||||||
|
|
||||||
if (![self connectToProxy]) {
|
if (![self connectToProxy]) {
|
||||||
RCTLogError(@"Connection to %@ timed out. Are you running node proxy?", url);
|
RCTLogError(@"Connection to %@ timed out. Are you running node proxy?", url);
|
||||||
[self invalidate];
|
[self invalidate];
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<!-- Fake favicon, to avoid extra request to server -->
|
||||||
|
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||||
|
<title>React Native Debugger</title>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
var sessionID = window.localStorage.getItem('sessionID');
|
||||||
|
window.localStorage.removeItem('sessionID');
|
||||||
|
|
||||||
|
window.onbeforeunload = function() {
|
||||||
|
if (sessionID) {
|
||||||
|
return 'If you reload this page, it is going to break the debugging session. ' +
|
||||||
|
'You should ⌘+R in the iOS simulator to reload.';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function setStatus(status) {
|
||||||
|
document.getElementById('status').innerHTML = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
var messageHandlers = {
|
||||||
|
// This method is a bit hacky. Catalyst asks for a new clean JS runtime.
|
||||||
|
// The easiest way to do this is to reload this page. That also means that
|
||||||
|
// web socket connection will be lost. To send reply back we need to remember
|
||||||
|
// message id
|
||||||
|
'prepareJSRuntime': function(message) {
|
||||||
|
window.onbeforeunload = undefined;
|
||||||
|
window.localStorage.setItem('sessionID', message.id);
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
'executeApplicationScript:sourceURL:onComplete:': function(message, sendReply) {
|
||||||
|
for (var key in message.inject) {
|
||||||
|
window[key] = JSON.parse(message.inject[key]);
|
||||||
|
}
|
||||||
|
loadScript(message.url, sendReply.bind(null, null));
|
||||||
|
},
|
||||||
|
'executeJSCall:method:arguments:callback:': function(message, sendReply) {
|
||||||
|
var returnValue = [[], [], [], [], []];
|
||||||
|
try {
|
||||||
|
if (window && window.require) {
|
||||||
|
returnValue = window.require(message.moduleName)[message.moduleMethod].apply(null, message.arguments);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
sendReply(JSON.stringify(returnValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy');
|
||||||
|
|
||||||
|
ws.onopen = function() {
|
||||||
|
if (sessionID) {
|
||||||
|
setStatus('Debugger session #' + sessionID + ' active');
|
||||||
|
ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)}));
|
||||||
|
} else {
|
||||||
|
setStatus('Waiting for simulator');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = function(message) {
|
||||||
|
var object = JSON.parse(message.data);
|
||||||
|
var sendReply = function(result) {
|
||||||
|
ws.send(JSON.stringify({replyID: object.id, result: result}));
|
||||||
|
}
|
||||||
|
var handler = messageHandlers[object.method];
|
||||||
|
if (handler) {
|
||||||
|
handler(object, sendReply);
|
||||||
|
} else {
|
||||||
|
console.warn('Unknown method: ' + object.method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onclose = function() {
|
||||||
|
setStatus('Disconnected from proxy. Is node server running?');
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadScript(src, callback) {
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.src = src;
|
||||||
|
script.onload = callback;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
.shortcut {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #eee;
|
||||||
|
background-color: #333;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
React Native JS code runs inside this Chrome tab
|
||||||
|
</p>
|
||||||
|
<p>Press <span class="shortcut">⌘⌥J</span> to open Developer Tools. Enable <a href="http://stackoverflow.com/a/17324511/232122" target="_blank">Pause On Caught Exceptions</a> for a better debugging experience.</p>
|
||||||
|
<p>Status: <span id="status">Loading</span></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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
|
|
@ -16,12 +16,14 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) {
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var exec = require('child_process').exec;
|
||||||
var ReactPackager = require('./react-packager');
|
var ReactPackager = require('./react-packager');
|
||||||
var blacklist = require('./blacklist.js');
|
var blacklist = require('./blacklist.js');
|
||||||
var connect = require('connect');
|
var connect = require('connect');
|
||||||
var http = require('http');
|
var http = require('http');
|
||||||
var launchEditor = require('./launchEditor.js');
|
var launchEditor = require('./launchEditor.js');
|
||||||
var parseCommandLine = require('./parseCommandLine.js');
|
var parseCommandLine = require('./parseCommandLine.js');
|
||||||
|
var webSocketProxy = require('./webSocketProxy.js');
|
||||||
|
|
||||||
var options = parseCommandLine([{
|
var options = parseCommandLine([{
|
||||||
command: 'port',
|
command: 'port',
|
||||||
|
@ -68,10 +70,12 @@ process.on('uncaughtException', function(e) {
|
||||||
'any existing instances that are already running.\n\n');
|
'any existing instances that are already running.\n\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
runServer(options, function() {
|
var server = runServer(options, function() {
|
||||||
console.log('\nReact packager ready.\n');
|
console.log('\nReact packager ready.\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
webSocketProxy.attachToServer(server, '/debugger-proxy');
|
||||||
|
|
||||||
function loadRawBody(req, res, next) {
|
function loadRawBody(req, res, next) {
|
||||||
req.rawBody = '';
|
req.rawBody = '';
|
||||||
req.setEncoding('utf8');
|
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) {
|
function getAppMiddleware(options) {
|
||||||
return ReactPackager.middleware({
|
return ReactPackager.middleware({
|
||||||
projectRoots: options.projectRoots,
|
projectRoots: options.projectRoots,
|
||||||
|
@ -112,6 +140,7 @@ function runServer(
|
||||||
var app = connect()
|
var app = connect()
|
||||||
.use(loadRawBody)
|
.use(loadRawBody)
|
||||||
.use(openStackFrameInEditor)
|
.use(openStackFrameInEditor)
|
||||||
|
.use(getDevToolsLauncher(options))
|
||||||
.use(getAppMiddleware(options));
|
.use(getAppMiddleware(options));
|
||||||
|
|
||||||
options.projectRoots.forEach(function(root) {
|
options.projectRoots.forEach(function(root) {
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
Loading…
Reference in New Issue