Skip to content

Instantly share code, notes, and snippets.

@joenoon
Created April 27, 2017 21:14
Show Gist options
  • Save joenoon/ae7a71fb2f01bf9574edf041e764fd22 to your computer and use it in GitHub Desktop.
Save joenoon/ae7a71fb2f01bf9574edf041e764fd22 to your computer and use it in GitHub Desktop.
# Creates classes in Obj-C and Java that contain values from
# config.yaml (which can be overridden by ENV).
# Values are obfuscated. (Untested as to how secure this is,
# but the point is to do better than completely plain
# public api keys visible in builds/plists)
# The key value pairs can be accessed from React Native via:
# NativeModules.AppEnv.XYZ
# Need to change "MyApp" and "myapp" and make path adjustments
# as needed in your project.
# - Joe Noon ([email protected])
require 'yaml'
require 'erb'
config = ENV["CONFIGURATION"]
yaml_filename = File.expand_path("config.yaml", File.dirname(__FILE__))
yaml = YAML::load(ERB.new(IO.read(yaml_filename)).result)
env = {}
yaml.each_pair do |k,opts|
env[k] = (ENV[k] || opts[config]).to_s
if env[k] == "!"
raise "MISSING ENV['#{k}']"
end
end
env_filename = File.expand_path("ios/MyApp/AppEnv.h", File.dirname(__FILE__))
output = %Q{
@import UIKit;
#import "RCTBridgeModule.h"
@interface AppEnv : NSObject <RCTBridgeModule>
@property NSTimeInterval timeToParse;
@property NSMutableDictionary *env;
+ (instancetype)sharedInstance;
- (NSDictionary *)constantsToExport;
#{env.keys.map { |k| "- (NSString*)#{k};" }.join("\n")}
@end
}
File.open(env_filename, "w") do |f|
f.puts output
end
env_filename = File.expand_path("ios/MyApp/AppEnv.m", File.dirname(__FILE__))
env_builders = []
func_builders = []
# each env var is put into a hash with value obfuscated.
# a comment is prepended for the developer with the unobfuscated value, but does not make it into the binary.
env.each_pair do |k,v|
v_ords = v.to_s.scan(/./).map { |x| x.ord }
if v_ords.any?
env_builders << %Q{ [_env setObject:[self getVal:(const char[])#{v_ords.inspect.sub("[", "{").sub("]", "}")} length:#{v_ords.size}] forKey:@#{k.inspect}]; }
else
env_builders << %Q{ [_env setObject:@"" forKey:@#{k.inspect}]; }
end
func_builders << "// #{k} #=> #{v.inspect}"
func_builders << "- (NSString*)#{k} { return [_env valueForKey:@#{k.inspect}]; }"
end
output = %Q{
#import "RCTBridge.h"
#import "AppEnv.h"
@implementation AppEnv
RCT_EXPORT_MODULE()
@synthesize timeToParse = _timeToParse;
@synthesize env = _env;
@synthesize bridge = _bridge;
RCT_EXPORT_METHOD(reload)
{
[_bridge reload];
}
+ (instancetype)sharedInstance {
static AppEnv *_sharedInstance = nil;
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
_sharedInstance = [AppEnv new];
});
return _sharedInstance;
}
- (instancetype)init
{
if ((self = [super init])) {
_env = [NSMutableDictionary dictionary];
NSDate* startTime = [NSDate date];
#{env_builders.join("\n")}
//[_env setObject:[self getVal:(const char[]){ 115, 111, 109, 101, 116, 104, 105, 110, 103, 32, 110, 101, 119, 32, 104, 101, 114, 101, 33 } length:19] forKey:@"something"];
NSDate* endTime = [NSDate date];
_timeToParse = [endTime timeIntervalSinceDate:startTime];
NSBundle* mainBundle = [NSBundle mainBundle];
[_env setObject:[mainBundle bundleIdentifier] forKey:@"bundleIdentifier"];
[_env setObject:[mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:@"longVersion"];
[_env setObject:[mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"] forKey:@"shortVersion"];
[_env setObject:UIApplicationOpenSettingsURLString forKey:@"UIApplicationOpenSettingsURLString"];
}
return self;
}
- (NSString*)getVal:(const char *)bytes length:(int)length {
return [[NSString alloc] initWithBytes:bytes length:length encoding:NSUTF8StringEncoding];
}
#{func_builders.join("\n")}
//- (NSString*)something { return [_env valueForKey:@"something"]; }
- (NSDictionary *)constantsToExport { return _env; }
@end
}
File.open(env_filename, "w") do |f|
f.puts output
end
env_filename = File.expand_path("android/app/src/main/java/com/MyApp/AppEnv.java", File.dirname(__FILE__))
env_builders = []
# each env var is put into a hash with value obfuscated.
# a comment is prepended for the developer with the unobfuscated value, but does not make it into the binary.
env.each_pair do |k,v|
v_ords = v.to_s.scan(/./).map { |x| x.ord }
env_builders << %Q{ // #{k}: #{v.inspect}}
env_builders << %Q{ this.env.put(#{k.inspect}, this.getVal(new byte[]#{v_ords.inspect.sub("[", "{").sub("]", "}")}));}
end
output = %Q{
package com.myapp;
import android.widget.Toast;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import java.util.ArrayList;
import java.util.HashMap;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import com.facebook.react.shell.MainReactPackage;
import java.util.List;
import java.io.UnsupportedEncodingException;
public class AppEnv extends ReactContextBaseJavaModule {
public Map<String, Object> env = new HashMap<>();;
public AppEnv(ReactApplicationContext reactContext) {
super(reactContext);
this.env.put("packageName", "com.myapp");
this.env.put("longVersion", "1");
this.env.put("shortVersion", "1");
#{env_builders.join("\n")}
}
@Override
public String getName() {
return "AppEnv";
}
@Override
public Map<String, Object> getConstants() {
return env;
}
public String getVal(byte[] bytes) {
String string = "";
try
{
string = new String(bytes, "UTF8");
}
catch( UnsupportedEncodingException e)
{
}
return string;
}
}
class AppEnvPackage extends MainReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new AppEnv(reactContext));
return modules;
}
}
}
File.open(env_filename, "w") do |f|
f.puts output
end
puts "***********************"
puts "* AppEnv constants *"
puts "***********************"
env.keys.sort.each do |k|
puts "#{k}: #{env[k]}"
end
# Example config.yaml use-cases
# Only keys here will make it into AppEnv.
# Env vars for the same keys will override values here.
SENTRY_DSN:
Debug:
Release: https://[email protected]/1
APP_GIT_COMMIT:
Debug:
Release: # blank, so ENV["APP_GIT_COMMIT"] will be checked when building AppEnv
GOOGLE_TRACKING_ID:
Debug: UA-1
Release: UA-2
google_place_key:
Debug: ABC
Release: XYZ
graphqlURL:
Debug: http://localhost:3001/graphql
Release: https://api.productionurl/graphql
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment