Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.0 #2

Merged
merged 8 commits into from
Jan 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ test:
- npm test
- mkdir ios.bundle
- react-native bundle --minify --dev false --entry-file index.ios.js --platform ios --assets-dest ios.bundle --bundle-output ios.bundle/main.jsbundle
- npm run sign ios.bundle
- zip -r main.zip ios.bundle
- zip -r ios.bundle.zip ios.bundle
- npm run sign ios.bundle.zip
- zip main.zip ios.bundle.zip signature.txt

deployment:
production:
Expand Down
Binary file added images/test1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 24 additions & 28 deletions ios/Chat/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,38 @@

#import "RCTRootView.h"
#import "RemoteBundle.h"
#import "RCTAssert.h"

@implementation AppDelegate

-(void)loadBundle:(NSDictionary *)launchOptions {
dispatch_async(dispatch_get_main_queue(), ^{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:[RemoteBundle bundle]
moduleName:@"Chat"
initialProperties:nil
launchOptions:launchOptions];

UIViewController *rootViewController = [[UIViewController alloc] init];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
});
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
/**
* 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.
*/
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:[RemoteBundle bundle]
moduleName:@"Chat"
initialProperties:nil
launchOptions:launchOptions];

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [[UIViewController alloc] init];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
[RemoteBundle checkUpdate];
RCTSetFatalHandler(^(NSError *error) {
// remove loaded version!
if ([RemoteBundle removeCurrentVersion]){
[self loadBundle:launchOptions];
}
});
[self loadBundle:launchOptions];
return YES;
}

-(void)applicationDidEnterBackground:(UIApplication *)application {
__block UIBackgroundTaskIdentifier taskId = [application beginBackgroundTaskWithExpirationHandler:^{
taskId = UIBackgroundTaskInvalid;
}];
[RemoteBundle bundle];
[RemoteBundle checkUpdate];
}

@end
4 changes: 3 additions & 1 deletion ios/RemoteBundle/RemoteBundle.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

#import <Foundation/Foundation.h>

@interface RemoteBundle : NSObject

@interface RemoteBundle : NSObject
+(BOOL)removeCurrentVersion;
+(NSURL *)bundle;
+(void)checkUpdate;
@end
149 changes: 111 additions & 38 deletions ios/RemoteBundle/RemoteBundle.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,66 +11,139 @@
#import <Security/Security.h>
#import "Shared.h"
#import "AH_SSZipArchive.h"
#import "RCTBridge.h"
#import "RCTExceptionsManager.h"
#import "RCTRootView.h"
#import "RCTAssert.h"

NSString * const ETag = @"ETag";

@implementation RemoteBundle




+(NSURL *)bundle {
#if TARGET_IPHONE_SIMULATOR && TESTING
return [NSURL URLWithString:@"http://10.0.1.7:8081/index.ios.bundle?platform=ios&dev=true"];
#else
NSURL *result = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+(void)checkUpdate {
// 1. check if there new file on S3
NSError* error;
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSString *version = info[@"CFBundleShortVersionString"];
NSString *filename = [NSString stringWithFormat:@"ios_%@.zip", version];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSLog(@"Checking for update");
NSString *filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectory, filename];

NSString *newBundlePath = [documentsDirectory stringByAppendingPathComponent:@"new"];
// create folder for bundle
if (![[NSFileManager defaultManager] fileExistsAtPath:newBundlePath]){
if( ! [[NSFileManager defaultManager] createDirectoryAtPath:newBundlePath withIntermediateDirectories:NO attributes:nil error:&error]){
NSLog(@"[%@] ERROR: attempting to write create new directory", [self class]);
return;
}
}
NSString *cdn = [NSString stringWithFormat:
@"https://rn-chat.s3.amazonaws.com/%@", filename];

NSURL *url = [NSURL URLWithString:cdn];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSLog(@"Documents directory: %@", documentsDirectory);
NSString *filePath = [NSString stringWithFormat:@"%@/%@", documentsDirectory, filename];
NSString *signaturePath = [NSString stringWithFormat:@"%@/ios.bundle/signature.txt", documentsDirectory];
NSString *bundlePath = [NSString stringWithFormat:@"%@/ios.bundle/main.jsbundle", documentsDirectory];
NSString* publicKeyPath = [[NSBundle mainBundle] pathForResource:@"public" ofType:@"pem"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:1000];
NSString *eTag = [[NSUserDefaults standardUserDefaults] objectForKey:ETag];

if (eTag && [Verifier verifyContent:bundlePath publicKeyPath:publicKeyPath signaturePath:signaturePath]){
if (eTag){
[request addValue:eTag forHTTPHeaderField:@"If-None-Match"];
result = [[NSURL alloc] initFileURLWithPath:bundlePath];
}
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration ephemeralSessionConfiguration];

defaultConfigObject.requestCachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;

NSURLSession *session = [NSURLSession sessionWithConfiguration:defaultConfigObject];

[[session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSInteger code = [((NSHTTPURLResponse *)response) statusCode];
if (!error && code == 200){
[data writeToFile:filePath atomically:YES];
[AH_SSZipArchive unzipFileAtPath:filePath toDestination:documentsDirectory];
NSDictionary *headers = [((NSHTTPURLResponse *)response) allHeaderFields];
// save Etag
if (headers[ETag]){
[[NSUserDefaults standardUserDefaults] setValue:headers[ETag] forKey:ETag];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}

}] resume];
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSInteger code = [((NSHTTPURLResponse *)response) statusCode];
if (!error && code == 200){
// 2. load new bundle, unzip and verify content
[data writeToFile:filePath atomically:YES];
[AH_SSZipArchive unzipFileAtPath:filePath toDestination:newBundlePath];
NSDictionary *headers = [((NSHTTPURLResponse *)response) allHeaderFields];
// save Etag
if (headers[ETag]){
[[NSUserDefaults standardUserDefaults] setValue:headers[ETag] forKey:ETag];
[[NSUserDefaults standardUserDefaults] synchronize];
}

// 3. verify signature
NSString* publicKeyPath = [[NSBundle mainBundle] pathForResource:@"public" ofType:@"pem"];
NSString *signaturePath = [NSString stringWithFormat:@"%@/signature.txt", newBundlePath];
NSString *bundlePath = [NSString stringWithFormat:@"%@/ios.bundle.zip", newBundlePath];

if (![Verifier verifyContent:bundlePath publicKeyPath:publicKeyPath signaturePath:signaturePath]){
NSLog(@"Verification of signature FAILED!");
return;
}

// 4. unzip inner bundle
[AH_SSZipArchive unzipFileAtPath:bundlePath toDestination:newBundlePath];
NSLog(@"Unzip to %@", newBundlePath);
} else {
NSLog(@"No new update");
}

}] resume];

}

+(BOOL)removeCurrentVersion {
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *currentBundlePath = [documentsDirectory stringByAppendingPathComponent:@"current"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *bundlePath = [NSString stringWithFormat:@"%@/ios.bundle/main.jsbundle", currentBundlePath];
if ([fileManager fileExistsAtPath:bundlePath]){
[fileManager removeItemAtPath:currentBundlePath error:&error];
if (error){
return NO;
} else {
return YES;
}

} else {
return NO;
}
}

+(NSURL *)bundle {
#if TARGET_IPHONE_SIMULATOR && TESTING
return [NSURL URLWithString:@"http://10.0.1.7:8081/index.ios.bundle?platform=ios&dev=true"];
#else
NSError *error;
// 1. check if new update loaded
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *newBundlePath = [documentsDirectory stringByAppendingPathComponent:@"new"];
NSString *currentBundlePath = [documentsDirectory stringByAppendingPathComponent:@"current"];
NSString *bundlePath = [NSString stringWithFormat:@"%@/ios.bundle/main.jsbundle", newBundlePath];
NSString *currentBundle = [NSString stringWithFormat:@"%@/ios.bundle/main.jsbundle", currentBundlePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:bundlePath]){
[fileManager removeItemAtPath:currentBundlePath error:&error];
if (error){
NSLog(@"Error: %@",[ error localizedDescription]);
}
// move it to current
NSLog(@"Found update, moving to current");
[fileManager moveItemAtPath:newBundlePath toPath:currentBundlePath error:&error];
if (error){
NSLog(@"Error: %@",[ error localizedDescription]);
}
}

if ([fileManager fileExistsAtPath:currentBundle]){
NSLog(@"Use bundle from: %@", currentBundle);
NSURL *url= [NSURL fileURLWithPath:currentBundle];
return url;
} else {
NSLog(@"Use built-in bundle");
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
}


return result;
#endif
}

Expand Down
4 changes: 2 additions & 2 deletions keys/sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ var crypto = require('crypto');
var fs = require('fs');
var args = process.argv.slice(2);
var pem = fs.readFileSync('keys/private.pem');
var bundle = fs.readFileSync(args[0]+'/main.jsbundle').toString('utf8');
var bundle = fs.readFileSync(args[0]);
var key = pem.toString('ascii');

var sign = crypto.createSign('RSA-SHA256');
sign.update(bundle);
var sig = sign.sign(key, 'base64');
fs.writeFileSync(args[0]+'/signature.txt',sig);
fs.writeFileSync('signature.txt',sig);

//var verifier = crypto.createVerify('sha256');
//verifier.update(bundle);
Expand Down