Skip to content

Commit

Permalink
Use device IP addresses to connect to RPC host
Browse files Browse the repository at this point in the history
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
  • Loading branch information
appden committed Mar 1, 2016
1 parent 578e6b9 commit 00b19d9
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 17 deletions.
1 change: 1 addition & 0 deletions lib/browser/.eslintrc
Expand Up @@ -13,6 +13,7 @@
"sourceType": "module"
},
"rules": {
"no-console": 0,
"strict": 0
}
}
25 changes: 23 additions & 2 deletions lib/browser/index.js
Expand Up @@ -18,13 +18,15 @@

'use strict';

import { NativeModules } from 'react-native';
import { keys, propTypes, objectTypes } from './constants';
import * as lists from './lists';
import * as objects from './objects';
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);
Expand Down Expand Up @@ -162,5 +164,24 @@ Object.defineProperties(Realm, {
},
});

// The session ID refers to the Realm constructor object in the RPC server.
Realm[keys.id] = rpc.createSession();
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(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.'
);
}
}
22 changes: 17 additions & 5 deletions lib/browser/rpc.js
Expand Up @@ -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.
Expand All @@ -45,8 +44,17 @@ export function registerTypeConverter(type, handler) {
typeConverters[type] = handler;
}

export function createSession() {
sessionId = sendRequest('create_session');
export function createSession(host) {
let oldHost = sessionHost;

try {
sessionHost = host;
sessionId = sendRequest('create_session');
} catch (e) {
sessionHost = oldHost;
throw e;
}

return sessionId;
}

Expand Down Expand Up @@ -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);
Expand Down
79 changes: 78 additions & 1 deletion react-native/RealmReact.mm
Expand Up @@ -24,14 +24,20 @@
#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>
#import <GCDWebServer/Requests/GCDWebServerDataRequest.h>
#import <GCDWebServer/Responses/GCDWebServerDataResponse.h>
#import <GCDWebServer/Responses/GCDWebServerErrorResponse.h>
#import "rpc.hpp"

#define WEB_SERVER_PORT 8082
#endif

@interface NSObject ()
Expand Down Expand Up @@ -118,6 +124,23 @@ - (dispatch_queue_t)methodQueue {
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) {
Expand All @@ -138,6 +161,60 @@ - (void)removeListenerForEvent:(NSString *)eventName handler:(RealmReactEventHan
}

#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];
Expand Down Expand Up @@ -178,7 +255,7 @@ - (void)startRPC {
return response;
}];

[_webServer startWithPort:8082 bonjourName:nil];
[_webServer startWithPort:WEB_SERVER_PORT bonjourName:nil];
return;
}

Expand Down
Expand Up @@ -101,8 +101,7 @@ public static RealmAnalytics getInstance(ApplicationInfo applicationInfo) {
}

public static boolean shouldExecute() {
return System.getenv("REALM_DISABLE_ANALYTICS") == null
&& (isRunningOnGenymotion() || isRunningOnStockEmulator());
return System.getenv("REALM_DISABLE_ANALYTICS") == null && isRunningOnEmulator();
}

private void send() {
Expand Down Expand Up @@ -152,12 +151,9 @@ private String generateJson() throws SocketException, NoSuchAlgorithmException {
.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");
}

/**
Expand Down
Expand Up @@ -9,8 +9,15 @@
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;

Expand Down Expand Up @@ -58,7 +65,18 @@ public Map<String, Object> getConstants() {
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
Expand All @@ -67,6 +85,35 @@ public void onCatalystInstanceDestroy() {
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);
Expand Down

0 comments on commit 00b19d9

Please sign in to comment.