Use device IP addresses to connect to RPC host

It still only tries localhost for the simulator, but for devices, we now get a list of possible IP addresses to attempt to connect to before throw a more helpful exception.

Resolves #284 and fixes #276
This commit is contained in:
Scott Kyle 2016-02-29 03:24:51 -08:00
parent 578e6b9742
commit 00b19d95b8
6 changed files with 171 additions and 17 deletions

View File

@ -13,6 +13,7 @@
"sourceType": "module"
},
"rules": {
"no-console": 0,
"strict": 0
}
}

View File

@ -18,6 +18,7 @@
'use strict';
import { NativeModules } from 'react-native';
import { keys, propTypes, objectTypes } from './constants';
import * as lists from './lists';
import * as objects from './objects';
@ -25,6 +26,7 @@ import * as results from './results';
import * as rpc from './rpc';
import * as util from './util';
const {debugHosts, debugPort} = NativeModules.Realm;
const listenersKey = Symbol();
rpc.registerTypeConverter(objectTypes.LIST, lists.create);
@ -162,5 +164,24 @@ Object.defineProperties(Realm, {
},
});
for (let i = 0, len = debugHosts.length; i < len; i++) {
try {
// The session ID refers to the Realm constructor object in the RPC server.
Realm[keys.id] = rpc.createSession();
Realm[keys.id] = rpc.createSession(debugHosts[i] + ':' + debugPort);
break;
} catch (e) {
// Only throw exception after all hosts have been tried.
if (i < len - 1) {
continue;
}
// Log the original exception for debugging purposes.
console.error(e);
throw new Error(
'Realm failed to connect to the embedded debug server inside the app. ' +
'If attempting to use Chrome debugging from a device, ensure the device is ' +
'reachable on the same network as this machine.'
);
}
}

View File

@ -21,12 +21,11 @@
import * as base64 from './base64';
import { keys, objectTypes } from './constants';
const DEVICE_HOST = 'localhost:8082';
const {id: idKey, realm: realmKey} = keys;
const typeConverters = {};
let XMLHttpRequest = global.originalXMLHttpRequest || global.XMLHttpRequest;
let sessionHost;
let sessionId;
// Check if XMLHttpRequest has been overridden, and get the native one if that's the case.
@ -45,8 +44,17 @@ export function registerTypeConverter(type, handler) {
typeConverters[type] = handler;
}
export function createSession() {
export function createSession(host) {
let oldHost = sessionHost;
try {
sessionHost = host;
sessionId = sendRequest('create_session');
} catch (e) {
sessionHost = oldHost;
throw e;
}
return sessionId;
}
@ -158,11 +166,15 @@ function deserializeDict(realmId, info) {
}
function sendRequest(command, data) {
if (!sessionHost) {
throw new Error('Must first create RPC session with a valid host');
}
data = Object.assign({}, data, sessionId ? {sessionId} : null);
let body = JSON.stringify(data);
let request = new XMLHttpRequest();
let url = 'http://' + DEVICE_HOST + '/' + command;
let url = 'http://' + sessionHost + '/' + command;
request.open('POST', url, false);
request.send(body);

View File

@ -24,7 +24,11 @@
#import "shared_realm.hpp"
#import <objc/runtime.h>
#import <arpa/inet.h>
#import <dlfcn.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <net/if.h>
#if DEBUG
#import <GCDWebServer/Core/GCDWebServer.h>
@ -32,6 +36,8 @@
#import <GCDWebServer/Responses/GCDWebServerDataResponse.h>
#import <GCDWebServer/Responses/GCDWebServerErrorResponse.h>
#import "rpc.hpp"
#define WEB_SERVER_PORT 8082
#endif
@interface NSObject ()
@ -118,6 +124,23 @@ extern "C" JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executo
return dispatch_get_main_queue();
}
- (NSDictionary *)constantsToExport {
#if DEBUG
#if TARGET_IPHONE_SIMULATOR
NSArray *hosts = @[@"localhost"];
#else
NSArray *hosts = [self getIPAddresses];
#endif
return @{
@"debugHosts": hosts,
@"debugPort": @(WEB_SERVER_PORT),
};
#else
return @{};
#endif
}
- (void)addListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler {
NSMutableOrderedSet *handlers = _eventHandlers[eventName];
if (!handlers) {
@ -138,6 +161,60 @@ RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) {
}
#if DEBUG
- (NSArray *)getIPAddresses {
static const char * const wifiInterface = "en0";
struct ifaddrs *ifaddrs;
if (getifaddrs(&ifaddrs)) {
NSLog(@"Failed to get interface addresses: %s", strerror(errno));
return @[];
}
NSMutableArray *ipAddresses = [[NSMutableArray alloc] init];
char host[INET6_ADDRSTRLEN];
for (struct ifaddrs *ifaddr = ifaddrs; ifaddr; ifaddr = ifaddr->ifa_next) {
if ((ifaddr->ifa_flags & IFF_LOOPBACK) || !(ifaddr->ifa_flags & IFF_UP)) {
// Ignore loopbacks and interfaces that aren't up.
continue;
}
struct sockaddr *addr = ifaddr->ifa_addr;
if (addr->sa_family == AF_INET) {
// Ignore link-local ipv4 addresses.
in_addr_t sin_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
if (IN_LOOPBACK(sin_addr) || IN_LINKLOCAL(sin_addr) || IN_ZERONET(sin_addr)) {
continue;
}
}
else if (addr->sa_family == AF_INET6) {
// Ignore link-local ipv6 addresses.
struct in6_addr *sin6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr;
if (IN6_IS_ADDR_LOOPBACK(sin6_addr) || IN6_IS_ADDR_LINKLOCAL(sin6_addr) || IN6_IS_ADDR_UNSPECIFIED(sin6_addr)) {
continue;
}
}
else {
// Ignore addresses that are not ipv4 or ipv6.
continue;
}
if (strcmp(ifaddr->ifa_name, wifiInterface)) {
// Ignore non-wifi addresses.
continue;
}
if (int error = getnameinfo(addr, addr->sa_len, host, sizeof(host), NULL, 0, NI_NUMERICHOST)) {
NSLog(@"Couldn't resolve host name for address: %s", gai_strerror(error));
continue;
}
[ipAddresses addObject:@(host)];
}
freeifaddrs(ifaddrs);
return [ipAddresses copy];
}
- (void)startRPC {
[GCDWebServer setLogLevel:3];
_webServer = [[GCDWebServer alloc] init];
@ -178,7 +255,7 @@ RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) {
return response;
}];
[_webServer startWithPort:8082 bonjourName:nil];
[_webServer startWithPort:WEB_SERVER_PORT bonjourName:nil];
return;
}

View File

@ -101,8 +101,7 @@ public class RealmAnalytics {
}
public static boolean shouldExecute() {
return System.getenv("REALM_DISABLE_ANALYTICS") == null
&& (isRunningOnGenymotion() || isRunningOnStockEmulator());
return System.getenv("REALM_DISABLE_ANALYTICS") == null && isRunningOnEmulator();
}
private void send() {
@ -152,12 +151,9 @@ public class RealmAnalytics {
.replaceAll("%OS_VERSION%", System.getProperty("os.version"));
}
private static boolean isRunningOnGenymotion() {
return Build.FINGERPRINT.contains("vbox");
}
private static boolean isRunningOnStockEmulator() {
return Build.FINGERPRINT.contains("generic");
public static boolean isRunningOnEmulator() {
// Check if running in Genymotion or on the stock emulator.
return Build.FINGERPRINT.contains("vbox") || Build.FINGERPRINT.contains("generic");
}
/**

View File

@ -9,8 +9,15 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.soloader.SoLoader;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@ -58,7 +65,18 @@ public class RealmReactModule extends ReactContextBaseJavaModule {
if (!isContextInjected()) {
startWebServer();
}
return Collections.EMPTY_MAP;
List<String> hosts;
if (RealmAnalytics.isRunningOnEmulator()) {
hosts = Arrays.asList(new String[]{"localhost"});
} else {
hosts = getIPAddresses();
}
HashMap<String, Object> constants = new HashMap<String, Object>();
constants.put("debugHosts", hosts);
constants.put("debugPort", DEFAULT_PORT);
return constants;
}
@Override
@ -67,6 +85,35 @@ public class RealmReactModule extends ReactContextBaseJavaModule {
stopWebServer();
}
private List<String> getIPAddresses() {
ArrayList<String> ipAddresses = new ArrayList<String>();
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
continue;
}
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if (address.isLoopbackAddress() || address.isLinkLocalAddress() || address.isAnyLocalAddress()) {
continue;
}
ipAddresses.add(address.getHostAddress());
}
}
} catch (SocketException e) {
e.printStackTrace();
}
return ipAddresses;
}
private void startWebServer() {
setupChromeDebugModeRealmJsContext();
webServer = new AndroidWebServer(DEFAULT_PORT);