Permalink
Browse files

Implemented automatic IP detection for iOS

Summary:
Implemented automatic IP detection for iOS, based on #6345 and #6362.
As the previous pull requests did, this works by writing the IP address of the host to a file.
Closes #8091

Differential Revision: D3427657

Pulled By: javache

fbshipit-source-id: 3f534c9b32c4d6fb9615fc2e2c3c3aef421454c5
  • Loading branch information...
nathanajah authored and Facebook Github Bot 5 committed Jun 13, 2016
1 parent f9e26b3 commit 8c29a52c54392ce52148e7d3aa9f835537453aa4
@@ -19,9 +19,19 @@
static NSString *const kRCTEnableMinificationKey = @"RCT_enableMinification";
static NSString *const kDefaultPort = @"8081";
static NSString *ipGuess;
@implementation RCTBundleURLProvider
#if RCT_DEV
+ (void)initialize
{
NSString *ipPath = [[NSBundle mainBundle] pathForResource:@"ip" ofType:@"txt"];
NSString *ip = [NSString stringWithContentsOfFile:ipPath encoding:NSUTF8StringEncoding error:nil];
ipGuess = [ip stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
}
#endif
- (NSDictionary *)defaults
{
static NSDictionary *defaults;
@@ -75,8 +85,7 @@ - (BOOL)isPackagerRunning:(NSString *)host
- (NSString *)guessPackagerHost
{
NSString *host = @"localhost";
//TODO: Implement automatic IP address detection
NSString *host = ipGuess ?: @"localhost";
if ([self isPackagerRunning:host]) {
return host;
}
@@ -9,6 +9,7 @@
#import "AppDelegate.h"
#import "RCTBundleURLProvider.h"
#import "RCTRootView.h"
@implementation AppDelegate
@@ -17,31 +18,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
{
NSURL *jsCodeLocation;
/**
* Loading JavaScript code - uncomment the one you want.
*
* OPTION 1
* Load from development server. Start the server from the repository root:
*
* $ npm start
*
* To run on device, change `localhost` to the IP address of your computer
* (you can get this by typing `ifconfig` into the terminal and selecting the
* `inet` value under `en0:`) and make sure your computer and iOS device are
* on the same Wi-Fi network.
*/
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
/**
* OPTION 2
* Load from pre-bundled file on disk. The static bundle is automatically
* generated by the "Bundle React Native code and images" build step when
* running the project on an actual device or running the project on the
* simulator in the "Release" build configuration.
*/
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"<%= name %>"
@@ -68,6 +68,15 @@ type $NODE_BINARY >/dev/null 2>&1 || nodejs_not_found
set -x
DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
if [[ "$CONFIGURATION" = "Debug" && "$PLATFORM_NAME" != "iphonesimulator" ]]; then
PLISTBUDDY='/usr/libexec/PlistBuddy'
PLIST=$TARGET_BUILD_DIR/$INFOPLIST_PATH
IP=$(ipconfig getifaddr en0)
$PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" $PLIST
$PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP.xip.io:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" $PLIST
echo "$IP.xip.io" > "$DEST/ip.txt"
fi
$NODE_BINARY "$REACT_NATIVE_DIR/local-cli/cli.js" bundle \
--entry-file index.ios.js \
--platform ios \

20 comments on commit 8c29a52

@benvium

This comment has been minimized.

Show comment
Hide comment
@benvium

benvium Jun 28, 2016

Contributor

Great stuff, this is a huge improvement.

Contributor

benvium replied Jun 28, 2016

Great stuff, this is a huge improvement.

@zhileichen

This comment has been minimized.

Show comment
Hide comment
@zhileichen

zhileichen Jul 7, 2016

Great, save a ton of time for me

zhileichen replied Jul 7, 2016

Great, save a ton of time for me

@flexzuu

This comment has been minimized.

Show comment
Hide comment
@flexzuu

flexzuu Jul 10, 2016

Is there a way to know if this works because i can't get to work reloading from a dev server on my device. In the plist.info i only get an entrance for localhost. I am in the same network. Testing with a fresh init of a project. And is there a way to still set the host url staticly?

Edit: If i use the old way of defining the ip: jsCodeLocation = [NSURL URLWithString:@"http://192.168.178.24:8081/index.ios.bundle?platform=ios&dev=true"];
It does not work. I even added the ip to App Transport Security Settings Exception Domains with NSTemporaryExceptionAllowsInsecureHTTPLoads = true.

PS: I can access the bundle from that url from my phones safari browser.

flexzuu replied Jul 10, 2016

Is there a way to know if this works because i can't get to work reloading from a dev server on my device. In the plist.info i only get an entrance for localhost. I am in the same network. Testing with a fresh init of a project. And is there a way to still set the host url staticly?

Edit: If i use the old way of defining the ip: jsCodeLocation = [NSURL URLWithString:@"http://192.168.178.24:8081/index.ios.bundle?platform=ios&dev=true"];
It does not work. I even added the ip to App Transport Security Settings Exception Domains with NSTemporaryExceptionAllowsInsecureHTTPLoads = true.

PS: I can access the bundle from that url from my phones safari browser.

@nathanajah

This comment has been minimized.

Show comment
Hide comment
@nathanajah

nathanajah Jul 10, 2016

Contributor

You should be able to set the IP statically by using
[[RCTBundleURLProvider sharedSettings] setJsLocation:@"your_ip_here"].

Can you set a breakpoint and see what is the return value of packagerServerRoot in RCTBundleURLProvider.m? If the ip detection works correctly, it should return the ip address.

The reason why IP address exception doesn't work is that numerical IP address doesn't work with ATS exceptions. The workaround that I did to enable that is to use <ip_address>.xip.io instead, which should redirect to the IP address specified.

If you still can't get it to work, can you try enabling NSAllowArbitraryLoads?

Contributor

nathanajah replied Jul 10, 2016

You should be able to set the IP statically by using
[[RCTBundleURLProvider sharedSettings] setJsLocation:@"your_ip_here"].

Can you set a breakpoint and see what is the return value of packagerServerRoot in RCTBundleURLProvider.m? If the ip detection works correctly, it should return the ip address.

The reason why IP address exception doesn't work is that numerical IP address doesn't work with ATS exceptions. The workaround that I did to enable that is to use <ip_address>.xip.io instead, which should redirect to the IP address specified.

If you still can't get it to work, can you try enabling NSAllowArbitraryLoads?

@flexzuu

This comment has been minimized.

Show comment
Hide comment
@flexzuu

flexzuu Jul 10, 2016

Hey thanks fro the quick reply. I am not that farmiliar with ObjectiveC or the XCode Workflow so i maybe messed up your instructions.

For Static IP i tried to just replace jsCodeLocation = [NSURL URLWithString:@"http://192.168.178.24:8081/index.ios.bundle?platform=ios&dev=true"] with [[RCTBundleURLProvider sharedSettings] setJsLocation:@"192.168.178.24"] but it is not working.
I get the _fbBatchedBridge is undefined Error.

With the standard generated AppDelegate.m and adding NSAllowArbitraryLoads it works completely fine. I suppose (and checked via Breakpoint) the ip is picked up correctly but your workarround with the ip is not working for me.

flexzuu replied Jul 10, 2016

Hey thanks fro the quick reply. I am not that farmiliar with ObjectiveC or the XCode Workflow so i maybe messed up your instructions.

For Static IP i tried to just replace jsCodeLocation = [NSURL URLWithString:@"http://192.168.178.24:8081/index.ios.bundle?platform=ios&dev=true"] with [[RCTBundleURLProvider sharedSettings] setJsLocation:@"192.168.178.24"] but it is not working.
I get the _fbBatchedBridge is undefined Error.

With the standard generated AppDelegate.m and adding NSAllowArbitraryLoads it works completely fine. I suppose (and checked via Breakpoint) the ip is picked up correctly but your workarround with the ip is not working for me.

@flexzuu

This comment has been minimized.

Show comment
Hide comment
@flexzuu

flexzuu Jul 10, 2016

As addition i faced some issues today. I wanted to test it again so i started a new project and added only the NSAllowArbitraryLoads but it did not work.
I have really no idea what i did in the other project that it started to work.
The current behavior i have is that the app works on the device but only through a static bundle. Not through a served file. I would like to have some kind of toggle to switch between the two randomly appearing behaviors.

flexzuu replied Jul 10, 2016

As addition i faced some issues today. I wanted to test it again so i started a new project and added only the NSAllowArbitraryLoads but it did not work.
I have really no idea what i did in the other project that it started to work.
The current behavior i have is that the app works on the device but only through a static bundle. Not through a served file. I would like to have some kind of toggle to switch between the two randomly appearing behaviors.

@nathanajah

This comment has been minimized.

Show comment
Hide comment
@nathanajah

nathanajah Jul 11, 2016

Contributor

Ah, I think I get it.
setJsLocation uses the NSUserDefaults API, which means that unless it's cleared explicitly, the jsLocation will persist throughout different versions of the app inside the device.

So even though you removed the setJsLocation line, the location that has already been set will still be set.
To clear the actual jsLocation and use the IP detection, you can either call
[[RCTBundleURLProvider sharedSettings] setJsLocation:nil] or
[[RCTBundleURLProvider sharedSettings] resetToDefaults].

This might be something that we'll need to change to prevent confusion.

So what happened is that:

  1. Automatic IP detection without NSAllowArbitraryLoads doesn't work
  2. Automatic IP detection with NSAllowArbitraryLoads doesn't work
  3. setJsLocation without NSAllowArbitraryLoads doesn't work
  4. setJsLocation with NSAllowArbitraryLoads works

The reason for 3 and 4 is clear - it's because numerical IP address ATS exceptions doesn't work, so let's focus on 1 and 2. Is your device connected to the internet when you were testing, or was it just a local network?

Contributor

nathanajah replied Jul 11, 2016

Ah, I think I get it.
setJsLocation uses the NSUserDefaults API, which means that unless it's cleared explicitly, the jsLocation will persist throughout different versions of the app inside the device.

So even though you removed the setJsLocation line, the location that has already been set will still be set.
To clear the actual jsLocation and use the IP detection, you can either call
[[RCTBundleURLProvider sharedSettings] setJsLocation:nil] or
[[RCTBundleURLProvider sharedSettings] resetToDefaults].

This might be something that we'll need to change to prevent confusion.

So what happened is that:

  1. Automatic IP detection without NSAllowArbitraryLoads doesn't work
  2. Automatic IP detection with NSAllowArbitraryLoads doesn't work
  3. setJsLocation without NSAllowArbitraryLoads doesn't work
  4. setJsLocation with NSAllowArbitraryLoads works

The reason for 3 and 4 is clear - it's because numerical IP address ATS exceptions doesn't work, so let's focus on 1 and 2. Is your device connected to the internet when you were testing, or was it just a local network?

@flexzuu

This comment has been minimized.

Show comment
Hide comment
@flexzuu

flexzuu Jul 11, 2016

Hey thanks for the nice overview i undstand now quite good how it should work. My device is connected via mobile network to the internet and via WLAN to the local network, which is connected to the internet via my router.


Are we sure Automatic IP detection is working at all? Because maybe i interfered with the testing while i tried to set the ip via [[RCTBundleURLProvider sharedSettings] setJsLocation:@"192.168.178.24"]. I did not understand that the ip will be remaining after i remove the line so i thought it worked without it, but as we see in the new project it does not work without it.


My current workflow will be:

  1. Set NSAllowArbitraryLoads to true
    If i am developing i use [[RCTBundleURLProvider sharedSettings] setJsLocation:@"192.168.178.24"]to set the ip.
    If i am out in the wild testing and using the app I use [[RCTBundleURLProvider sharedSettings] resetToDefaults] to get back the offline package that does not require a server.

The optimal workflow would be to just set a flag and we use:

  1. a server and autofind the ip
  2. a server and explicitly set the ip
  3. a bundled version of the js stuff so we don't need a server running.

Maybe just use 3 as a fallback that we can choose on the device the last synced bundle and turn of refreshing from a server. Now if the server goes off and you try to refresh on the device there is no way to get back to a working version.

flexzuu replied Jul 11, 2016

Hey thanks for the nice overview i undstand now quite good how it should work. My device is connected via mobile network to the internet and via WLAN to the local network, which is connected to the internet via my router.


Are we sure Automatic IP detection is working at all? Because maybe i interfered with the testing while i tried to set the ip via [[RCTBundleURLProvider sharedSettings] setJsLocation:@"192.168.178.24"]. I did not understand that the ip will be remaining after i remove the line so i thought it worked without it, but as we see in the new project it does not work without it.


My current workflow will be:

  1. Set NSAllowArbitraryLoads to true
    If i am developing i use [[RCTBundleURLProvider sharedSettings] setJsLocation:@"192.168.178.24"]to set the ip.
    If i am out in the wild testing and using the app I use [[RCTBundleURLProvider sharedSettings] resetToDefaults] to get back the offline package that does not require a server.

The optimal workflow would be to just set a flag and we use:

  1. a server and autofind the ip
  2. a server and explicitly set the ip
  3. a bundled version of the js stuff so we don't need a server running.

Maybe just use 3 as a fallback that we can choose on the device the last synced bundle and turn of refreshing from a server. Now if the server goes off and you try to refresh on the device there is no way to get back to a working version.

@Bglgithub

This comment has been minimized.

Show comment
Hide comment
@Bglgithub

Bglgithub Jul 11, 2016

Bglgithub replied Jul 11, 2016

@ide

This comment has been minimized.

Show comment
Hide comment
@ide

ide Jul 12, 2016

Collaborator

Maybe just use 3 as a fallback that we can choose on the device the last synced bundle and turn of refreshing from a server.

We shouldn't do this during development -- it's important that you get an error if the bundle failed to load rather than loading a stale copy and confusing the developer.

Collaborator

ide replied Jul 12, 2016

Maybe just use 3 as a fallback that we can choose on the device the last synced bundle and turn of refreshing from a server.

We shouldn't do this during development -- it's important that you get an error if the bundle failed to load rather than loading a stale copy and confusing the developer.

@flexzuu

This comment has been minimized.

Show comment
Hide comment
@flexzuu

flexzuu Jul 13, 2016

We shouldn't do this during development -- it's important that you get an error if the bundle failed to load rather than loading a stale copy and confusing the developer.

I meant a really transparent way. Like just adding a Button to dev menu that says load on device bundle / load server bundle and you can switch transparently between the modes. And the Fallback i meant was just to add a hint if you are not connected to the server you can use a on device bundle if its intended that the server is not responding.

flexzuu replied Jul 13, 2016

We shouldn't do this during development -- it's important that you get an error if the bundle failed to load rather than loading a stale copy and confusing the developer.

I meant a really transparent way. Like just adding a Button to dev menu that says load on device bundle / load server bundle and you can switch transparently between the modes. And the Fallback i meant was just to add a hint if you are not connected to the server you can use a on device bundle if its intended that the server is not responding.

@nathanajah

This comment has been minimized.

Show comment
Hide comment
@nathanajah

nathanajah Jul 13, 2016

Contributor

Do you think it's possible that the device actually used the mobile network instead of the local connection?
Due to do workaround that we did using xip.io (accessing 192.168.178.24.xip.io instead of the IP address directly to circumvent the numeric IP address limitation in ATS), the device does need to connect to the internet to access the IP.

Contributor

nathanajah replied Jul 13, 2016

Do you think it's possible that the device actually used the mobile network instead of the local connection?
Due to do workaround that we did using xip.io (accessing 192.168.178.24.xip.io instead of the IP address directly to circumvent the numeric IP address limitation in ATS), the device does need to connect to the internet to access the IP.

@npomfret

This comment has been minimized.

Show comment
Hide comment
@npomfret

npomfret Jul 14, 2016

Contributor

I've run react-native upgrade on my project and now I can't load JS files dynamically, only the offline bundle works, and that's the default now. Also I can't debug remotely. Any tips on how I should debug what's going on?

Contributor

npomfret replied Jul 14, 2016

I've run react-native upgrade on my project and now I can't load JS files dynamically, only the offline bundle works, and that's the default now. Also I can't debug remotely. Any tips on how I should debug what's going on?

@nathanajah

This comment has been minimized.

Show comment
Hide comment
@nathanajah

nathanajah Jul 14, 2016

Contributor

To debug, can you try to set a breakpoint in xcode on jsCodeLocation = [[RCTBundleURLProvider ... and see if jsCodeLocation is set correctly?
How is your networking setup?

Meanwhile you can use a workaround to set static IP:
Add
[[RCTBundleURLProvider sharedSettings] setJsLocation:@"your_ip_here"] before
jsCodeLocation = [[RCTBundleURLProvider ...
and add NSAllowArbitraryLoads in NSAppTransportSecurity in info.plist.

Contributor

nathanajah replied Jul 14, 2016

To debug, can you try to set a breakpoint in xcode on jsCodeLocation = [[RCTBundleURLProvider ... and see if jsCodeLocation is set correctly?
How is your networking setup?

Meanwhile you can use a workaround to set static IP:
Add
[[RCTBundleURLProvider sharedSettings] setJsLocation:@"your_ip_here"] before
jsCodeLocation = [[RCTBundleURLProvider ...
and add NSAllowArbitraryLoads in NSAppTransportSecurity in info.plist.

@esthor

This comment has been minimized.

Show comment
Hide comment
@esthor

esthor Jul 18, 2016

I'm getting an error when building and running for release: "[error][tid:com.facebook.react.JavaScript] Expected listener to be a function." Any ideas? Thanks!

esthor replied Jul 18, 2016

I'm getting an error when building and running for release: "[error][tid:com.facebook.react.JavaScript] Expected listener to be a function." Any ideas? Thanks!

@EchoLawrence

This comment has been minimized.

Show comment
Hide comment
@EchoLawrence

EchoLawrence Aug 14, 2016

@nathanajah : thanks, your solution worked greatly.

EchoLawrence replied Aug 14, 2016

@nathanajah : thanks, your solution worked greatly.

@strefethen

This comment has been minimized.

Show comment
Hide comment
@strefethen

strefethen Aug 24, 2016

Just a note here that ipconfig getifaddr en0 looks for the IP Address of a specific device. In my case it was ethernet which I had unplugged and when the above mentioned problems started for me until I plugged my cable back in. Without the ethernet cable plugged in the IP.txt file was created with only ".xip.io" and was missing the actual IP address so things failed.

That aside, @nathanajah thanks for getting this work done. Much appreciated.

strefethen replied Aug 24, 2016

Just a note here that ipconfig getifaddr en0 looks for the IP Address of a specific device. In my case it was ethernet which I had unplugged and when the above mentioned problems started for me until I plugged my cable back in. Without the ethernet cable plugged in the IP.txt file was created with only ".xip.io" and was missing the actual IP address so things failed.

That aside, @nathanajah thanks for getting this work done. Much appreciated.

@npomfret

This comment has been minimized.

Show comment
Hide comment
@npomfret

npomfret Aug 24, 2016

Contributor

So I think the problem is then having more than one working network interface. The node process binds arbitrarily to the 1st one, which isn't necessarily the wifi. The physical device is only connected to wifi. And so when they aren't hitting the same network interface then obviously they can't talk t each other. So you still need to specify the ip address in AppDelegate.m. So...

  #ifdef DEBUG
    [[RCTBundleURLProvider sharedSettings] setJsLocation:@"ip_here"];
  #endif

Contributor

npomfret replied Aug 24, 2016

So I think the problem is then having more than one working network interface. The node process binds arbitrarily to the 1st one, which isn't necessarily the wifi. The physical device is only connected to wifi. And so when they aren't hitting the same network interface then obviously they can't talk t each other. So you still need to specify the ip address in AppDelegate.m. So...

  #ifdef DEBUG
    [[RCTBundleURLProvider sharedSettings] setJsLocation:@"ip_here"];
  #endif

@mosesoak

This comment has been minimized.

Show comment
Hide comment
@mosesoak

mosesoak Apr 6, 2017

Contributor

Wanted to say thanks and 💯 for getting this in the build finally! We had written our own fastlane command to add a run script phase with our custom ip-setter, it was a huge time drain. This is a great piece of missing functionality.

Out of curiosity where is Android on being able to run on device? I remember that was a big reason why they wouldn't consider merging this before...

Contributor

mosesoak replied Apr 6, 2017

Wanted to say thanks and 💯 for getting this in the build finally! We had written our own fastlane command to add a run script phase with our custom ip-setter, it was a huge time drain. This is a great piece of missing functionality.

Out of curiosity where is Android on being able to run on device? I remember that was a big reason why they wouldn't consider merging this before...

@NijaahnandhRV

This comment has been minimized.

Show comment
Hide comment
@NijaahnandhRV

NijaahnandhRV Jun 7, 2018

Hi, My app gets crashed when i run it. Can anyone please help me out with this issue

screen shot 2018-06-07 at 7 18 23 pm

NijaahnandhRV replied Jun 7, 2018

Hi, My app gets crashed when i run it. Can anyone please help me out with this issue

screen shot 2018-06-07 at 7 18 23 pm

Please sign in to comment.