const url = require('url'); const http = require('http'); const invariant = require('assert'); const { createContext, Script } = require('vm'); const ws = require('./ws'); let currentContext = null; let scriptCached = null; // this is a dummy file path - without a file name the source map is not used in the vm const TEMP_BUNDLE_PATH = '/tmp/bridge/react-native.js'; /** * * @param replyId * @param result */ function sendResult(replyID, result) { ws.send( JSON.stringify({ replyID, result, }) ); } /** * TODO * @param message */ function sendError(error) { console.log('error'); throw error; } /** * * @param src * @param callback */ function getScript(src, callback) { if (scriptCached) return callback(null, scriptCached); return http .get(src, res => { let buff = ''; res.setEncoding('utf8'); res.on('data', chunk => { buff += chunk; }); res.on('end', () => { scriptCached = new Script(buff, { // lineOffset: -1, // columnOffset: -1, timeout: 120000, displayErrors: true, produceCachedData: true, filename: TEMP_BUNDLE_PATH, }); callback(null, scriptCached); }); }) .on('error', err => { callback(err); }); } function consoleShim() { return { ...console, log(...args) { if ( args[0] && typeof args[0] === 'string' && args[0].startsWith('Running application "') ) { return; } if ( args[0] && typeof args[0] === 'string' && args[0].startsWith('Deprecated') ) { return; } console.log(...args); }, warn(...args) { if ( args[0] && typeof args[0] === 'string' && args[0].startsWith('Running application "') ) { return; } if ( args[0] && typeof args[0] === 'string' && args[0].startsWith('Deprecated') ) { return; } console.log(...args); }, }; } process.on('ws-message', request => { // console.log(request.method); switch (request.method) { case 'prepareJSRuntime': if (currentContext) { try { for (const name in currentContext.__fbBatchedBridge) { currentContext.__fbBatchedBridge[name] = undefined; delete currentContext.__fbBatchedBridge[name]; } for (const name in currentContext.__fbGenNativeModule) { currentContext.__fbGenNativeModule[name] = undefined; delete currentContext.__fbGenNativeModule[name]; } for (const name in currentContext.__fbBatchedBridgeConfig) { currentContext.__fbBatchedBridgeConfig[name] = undefined; delete currentContext.__fbBatchedBridgeConfig[name]; } for (const name in currentContext) { currentContext[name] = undefined; delete currentContext[name]; } } catch (e) { console.error(e); } } currentContext = undefined; currentContext = createContext({ console: consoleShim(), __bridgeNode: { ready() { process.emit('rn-ready'); }, provideReactNativeModule(rnModule) { global.bridge.rn = undefined; global.bridge.rn = rnModule; }, provideModule(moduleExports) { global.bridge.module = undefined; global.bridge.module = moduleExports; }, provideReload(reloadFn) { global.bridge.reload = undefined; global.bridge.reload = reloadFn; }, provideRoot(rootComponent) { global.bridge.root = undefined; global.bridge.root = rootComponent; }, }, }); sendResult(request.id); return; case 'executeApplicationScript': // Modify the URL to make sure we get the inline source map. const parsedUrl = url.parse(request.url, /* parseQueryString */ true); invariant(parsedUrl.query); parsedUrl.query.inlineSourceMap = true; delete parsedUrl.search; // $FlowIssue url.format() does not accept what url.parse() returns. const scriptUrl = url.format(parsedUrl); getScript(scriptUrl, (err, script) => { if (err != null) { sendError(`Failed to get script from packager: ${err.message}`); return; } if (currentContext == null) { sendError('JS runtime not prepared'); return; } if (request.inject) { for (const name in request.inject) { currentContext[name] = JSON.parse(request.inject[name]); } } try { script.runInContext(currentContext, TEMP_BUNDLE_PATH); } catch (e) { sendError(e); } sendResult(request.id); }); return; default: let returnValue = [[], [], [], 0]; try { if ( currentContext != null && typeof currentContext.__fbBatchedBridge === 'object' ) { returnValue = currentContext.__fbBatchedBridge[request.method].apply( null, request.arguments ); } } catch (e) { if (request.method !== '$disconnected') { sendError( `Failed while making a call ${request.method}:::${e.message}` ); } } finally { sendResult(request.id, JSON.stringify(returnValue)); } } });