android support

This commit is contained in:
Geoffrey Goh
2015-11-23 12:02:01 -08:00
parent 380df40361
commit 25e3eb67f3
55 changed files with 55728 additions and 181 deletions

50
.gitignore vendored
View File

@@ -1,3 +1,7 @@
# OSX
#
.DS_Store
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
@@ -97,3 +101,49 @@ xcuserdata
*.xccheckout
*.moved-aside
*.xcuserstate
# Intellij project files
*.iml
*.ipr
*.iws
.idea/
#Gradle
.gradletasknamecache
.gradle/
build/
bin/
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
*/build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/

1
.watchmanconfig Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -1,17 +0,0 @@
/**
* Stub of UpdateManager for Android.
*
* @providesModule UpdateManager
* @flow
*/
'use strict';
var warning = require('warning');
var CodePush = {
test: function() {
warning("Not yet implemented for Android.");
}
};
module.exports = CodePush;

View File

@@ -1,11 +1,8 @@
'use strict';
var NativeCodePush = require("react-native").NativeModules.CodePush;
var requestFetchAdapter = require("./request-fetch-adapter.js");
var Sdk = require("code-push/script/acquisition-sdk").AcquisitionManager;
var packageMixins = require("./package-mixins")(NativeCodePush);
var { AlertIOS } = require("react-native");
var { NativeCodePush, PackageMixins, Alert } = require("./CodePushNativePlatformAdapter");
// This function is only used for tests. Replaces the default SDK, configuration and native bridge
function setUpTestDependencies(providedTestSdk, providedTestConfig, testNativeBridge){
@@ -102,7 +99,7 @@ function checkForUpdate() {
return resolve(null);
}
update = Object.assign(update, packageMixins.remote);
update = Object.assign(update, PackageMixins.remote);
NativeCodePush.isFailedUpdate(update.packageHash)
.then((isFailedHash) => {
@@ -252,7 +249,7 @@ function sync(options = {}, syncStatusChangeCallback, downloadProgressCallback)
}
syncStatusChangeCallback(CodePush.SyncStatus.AWAITING_USER_ACTION);
AlertIOS.alert(syncOptions.updateDialog.title, message, dialogButtons);
Alert.alert(syncOptions.updateDialog.title, message, dialogButtons);
} else {
doDownloadAndInstall();
}

View File

@@ -0,0 +1,66 @@
'use strict';
var NativeCodePush = require("react-native").NativeModules.CodePush;
var Platform = require("Platform");
var Alert;
if (Platform.OS === "android") {
/*
* Promisify native methods. Assumes that every native method takes
* two callback functions, resolve and reject.
*/
var methodsToPromisify = [
"installUpdate",
"downloadUpdate",
"getConfiguration",
"getCurrentPackage",
"isFailedUpdate",
"isFirstRun",
"notifyApplicationReady"
];
methodsToPromisify.forEach((methodName) => {
var aMethod = NativeCodePush[methodName];
NativeCodePush[methodName] = function() {
var args = [].slice.apply(arguments);
return new Promise((resolve, reject) => {
args.push(resolve);
args.push(reject);
aMethod.apply(this, args);
});
}
});
var CodePushDialog = require("react-native").NativeModules.CodePushDialog;
Alert = {
alert: function(title, message, buttons) {
if (buttons.length > 2) {
throw "Can only show 2 buttons for Android dialog.";
}
var button1Text = buttons[0] ? buttons[0].text : null;
var button2Text = buttons[1] ? buttons[1].text : null;
CodePushDialog.showDialog(
title, message, button1Text, button2Text,
(buttonPressedId) => {
buttons[buttonPressedId].onPress && buttons[buttonPressedId].onPress();
},
(error) => {
throw error;
});
}
};
} else if (Platform.OS === "ios") {
var { AlertIOS } = require("react-native");
Alert = AlertIOS;
}
var PackageMixins = require("./package-mixins")(NativeCodePush);
module.exports = {
NativeCodePush: NativeCodePush,
PackageMixins: PackageMixins,
Alert: Alert
}

View File

@@ -27,3 +27,49 @@ node_modules/
npm-debug.log
main.jsbundle
# Intellij project files
*.iml
*.ipr
*.iws
.idea/
#Gradle
.gradletasknamecache
.gradle/
build/
bin/
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
*/build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/

View File

@@ -0,0 +1 @@
{}

View File

@@ -1,6 +1,3 @@
/**
* @providesModule QueryUpdateTestApp
*/
'use strict';
var React = require('react-native');

View File

@@ -1,6 +1,3 @@
/**
* @providesModule QueryUpdateTestApp
*/
'use strict';
var React = require('react-native');

View File

@@ -0,0 +1,30 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.microsoft.codepushdemoapp"
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0.0"
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.0'
compile 'com.facebook.react:react-native:0.15.1'
compile project(':react-native-code-push')
}

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,22 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.codepushdemoapp">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
<activity
android:name="com.microsoft.codepushdemoapp.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
package com.microsoft.codepushdemoapp;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.KeyEvent;
import com.facebook.react.LifecycleState;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.microsoft.reactnativecodepush.CodePush;
public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler {
private ReactInstanceManager mReactInstanceManager;
private ReactRootView mReactRootView;
private CodePush codePush;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReactRootView = new ReactRootView(this);
codePush = new CodePush("d73bf5d8-4fbd-4e55-a837-accd328a21ba", this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJSBundleFile(codePush.getBundleUrl("index.android.bundle"))
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.addPackage(codePush.getReactPackage())
.setUseDeveloperSupport(true)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
mReactRootView.startReactApplication(mReactInstanceManager, "CodePushDemoApp", null);
setContentView(mReactRootView);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onPause();
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onResume(this, this);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">CodePushDemoApp</string>
</resources>

View File

@@ -0,0 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,20 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
}
}

View File

@@ -0,0 +1,20 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip

164
Examples/CodePushDemoApp/android/gradlew vendored Executable file
View File

@@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,4 @@
rootProject.name = 'CodePushDemoApp'
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../../../android/app')

View File

@@ -0,0 +1,140 @@
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
TouchableOpacity,
View,
} = React;
var Button = require("react-native-button");
var CodePush = require('react-native-code-push');
var CodePushDemoApp = React.createClass({
componentDidMount: function() {
},
sync: function() {
var self = this;
CodePush.sync(
{
updateDialog: true,
installMode: CodePush.InstallMode.ON_NEXT_RESUME
},
function(syncStatus) {
switch(syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
self.setState({
syncMessage: "Checking for update."
});
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
self.setState({
syncMessage: "Downloading package."
});
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
self.setState({
syncMessage: "Awaiting user action."
});
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
self.setState({
syncMessage: "Installing update."
});
break;
case CodePush.SyncStatus.UP_TO_DATE:
self.setState({
syncMessage: "App up to date.",
progress: false
});
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
self.setState({
syncMessage: "Update cancelled by user.",
progress: false
});
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
self.setState({
syncMessage: "Update installed and will be run when the app next resumes.",
progress: false
});
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
self.setState({
syncMessage: "An unknown error occurred.",
progress: false
});
break;
}
},
function(progress) {
self.setState({
progress: progress
});
}
).catch(function(error) {
CodePush.log(error);
});
},
getInitialState: function() {
return { };
},
render: function() {
var syncView;
var syncButton;
var progressView;
if (this.state.syncMessage) {
syncView = (
<Text style={styles.messages}>{this.state.syncMessage}</Text>
);
} else {
syncButton = (
<Button style={{color: 'green'}} onPress={this.sync}>
Start Sync!
</Button>
);
}
if (this.state.progress) {
progressView = (
<Text style={styles.messages}>{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received</Text>
);
}
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to CodePush!
</Text>
{syncButton}
{syncView}
{progressView}
</View>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
marginTop: 50
},
messages: {
textAlign: 'center',
},
});
AppRegistry.registerComponent('CodePushDemoApp', () => CodePushDemoApp);

View File

@@ -0,0 +1 @@
require("./crossplatformdemo.js");

View File

@@ -1,144 +1 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
'use strict';
var React = require('react-native');
var {
AppRegistry,
StyleSheet,
Text,
TouchableOpacity,
View,
} = React;
var Button = require("react-native-button");
var CodePush = require('react-native-code-push');
var CodePushDemoApp = React.createClass({
componentDidMount: function() {
},
sync: function() {
var self = this;
CodePush.sync(
{
updateDialog: true,
installMode: CodePush.InstallMode.ON_NEXT_RESUME
},
function(syncStatus) {
switch(syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
self.setState({
syncMessage: "Checking for update."
});
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
self.setState({
syncMessage: "Downloading package."
});
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
self.setState({
syncMessage: "Awaiting user action."
});
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
self.setState({
syncMessage: "Installing update."
});
break;
case CodePush.SyncStatus.UP_TO_DATE:
self.setState({
syncMessage: "App up to date.",
progress: false
});
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
self.setState({
syncMessage: "Update cancelled by user.",
progress: false
});
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
self.setState({
syncMessage: "Update installed and will be run when the app next resumes.",
progress: false
});
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
self.setState({
syncMessage: "An unknown error occurred.",
progress: false
});
break;
}
},
function(progress) {
self.setState({
progress: progress
});
}
).catch(function(error) {
CodePush.log(error);
});
},
getInitialState: function() {
return { };
},
render: function() {
var syncView;
var syncButton;
var progressView;
if (this.state.syncMessage) {
syncView = (
<Text style={styles.messages}>{this.state.syncMessage}</Text>
);
} else {
syncButton = (
<Button style={{color: 'green'}} onPress={this.sync}>
Start Sync!
</Button>
);
}
if (this.state.progress) {
progressView = (
<Text style={styles.messages}>{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received</Text>
);
}
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to CodePush!
</Text>
{syncButton}
{syncView}
{progressView}
</View>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
marginTop: 50
},
messages: {
textAlign: 'center',
},
});
AppRegistry.registerComponent('CodePushDemoApp', () => CodePushDemoApp);
require("./crossplatformdemo.js");

View File

@@ -6,7 +6,7 @@
"start": "node_modules/react-native/packager/packager.sh --root ../../"
},
"dependencies": {
"react-native": "0.11.4",
"react-native": "0.14.2",
"react-native-button": "^1.2.0",
"react-native-code-push": "file:../../"
}

View File

@@ -15,7 +15,7 @@ that isn't currently covered well by sync, please [let us know](mailto:codepushf
## Supported React Native platforms
- iOS
- Coming soon: Android (Try it out on [this branch](https://github.com/Microsoft/react-native-code-push/tree/first-check-in-for-android))
- Android
## How does it work?
@@ -33,7 +33,7 @@ Acquire the React Native CodePush plugin by running the following command within
npm install --save react-native-code-push
```
## Plugin Installation
## Plugin Installation - iOS
Once you've acquired the CodePush plugin, you need to integrate it into the Xcode project of your React Native app. To do this, take the following steps:
@@ -52,7 +52,26 @@ Add a new value, `$(SRCROOT)/../node_modules/react-native-code-push` and select
![Add CodePush library reference](https://cloud.githubusercontent.com/assets/516559/10322038/b8157962-6c30-11e5-9264-494d65fd2626.png)
## Plugin Configuration
## Plugin Installation - Android
To integrate CodePush into your Android project, do the following steps:
1. In your `android/settings.gradle` file, make the following additions:
```
include ':app'**, ':react-native-code-push'**
**project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android')**
```
2. In your `android/app/build.gradle` file, add CodePush as one of the dependencies:
```
...
dependencies {
...
compile project(':react-native-code-push')
}
```
## Plugin Configuration - iOS
Once your Xcode project has been setup to build/link the CodePush plugin, you need to configure your app to consult CodePush for the location of your JS bundle, since it will "take control" of managing the current and all future versions. To do this, perform the following steps:
@@ -80,6 +99,53 @@ To let the CodePush runtime know which deployment it should query for updates ag
1. Open your app's `Info.plist` and add a new `CodePushDeploymentKey` entry, whose value is the key of the deployment you want to configure this app against (e.g. the Staging deployment for FooBar app)
2. In your app's `Info.plist` make sure your `CFBundleShortVersionString` value is a valid [semver](http://semver.org/) version (e.g. 1.0.0 not 1.0)
## Plugin Configuration - Android
After installing the plugin and sync-ing your Android Studio project with Gradle, you need to configure your app to consult CodePush for the location of your JS bundle, since it will "take control" of managing the current and all future versions. To do this, perform the following steps:
1. Initialize the module in MainActivity.java:
```
...
// Import the plugin class
import com.microsoft.reactnativecodepush.CodePush;
// Optional: extend FragmentActivity if you intend to show a dialog prompting users about updates.
public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler {
...
// Declare an object level private instance of CodePush
private CodePush codePush;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Initialize CodePush with your deployment key and an instance of your MainActivity
codePush = new CodePush("d73bf5d8-4fbd-4e55-a837-accd328a21ba", this);
...
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
...
// Let CodePush determine which location to load the most updated bundle from
.setJSBundleFile(codePush.getBundleUrl("index.android.bundle"))
// Expose the CodePush module to JavaScript
.addPackage(codePush.getReactPackage())
}
}
```
2. Let the CodePush runtime know which deployment it should query for updates against. Be sure to set the `versionName` in your `android/app/build.gradle`:
```
android {
...
defaultConfig {
...
versionName "1.0.0"
...
}
...
}
```
## Plugin consumption
With the CodePush plugin downloaded and linked, and your app asking CodePush where to get the right JS bundle from, the only thing left is to add the neccessary code to your app to control the following:

28
android/app/build.gradle Normal file
View File

@@ -0,0 +1,28 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.0'
compile 'com.facebook.react:react-native:0.15.1'
}

17
android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.reactnativecodepush">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:label="@string/app_name">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

View File

@@ -0,0 +1,407 @@
package com.microsoft.reactnativecodepush;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.soloader.SoLoader;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
public class CodePush {
private String deploymentKey;
private boolean resumablePendingUpdateAvailable = false;
private boolean didUpdate = false;
private Timer timer;
private boolean usingTestFolder = false;
private String assetsBundleFileName;
private final String FAILED_UPDATES_KEY = "CODE_PUSH_FAILED_UPDATES";
private final String PENDING_UPDATE_KEY = "CODE_PUSH_PENDING_UPDATE";
private final String PENDING_UPDATE_HASH_KEY = "hash";
private final String PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY = "rollbackTimeout";
private final String ASSETS_BUNDLE_PREFIX = "assets://";
private final String CODE_PUSH_PREFERENCES = "CodePush";
private final String DOWNLOAD_PROGRESS_EVENT_NAME = "CodePushDownloadProgress";
private CodePushPackage codePushPackage;
private CodePushReactPackage codePushReactPackage;
private CodePushNativeModule codePushNativeModule;
private CodePushConfig codePushConfig;
private Activity mainActivity;
private Context applicationContext;
public CodePush(String deploymentKey, Activity mainActivity) {
SoLoader.init(mainActivity, false);
this.deploymentKey = deploymentKey;
this.codePushPackage = new CodePushPackage(mainActivity.getFilesDir().getAbsolutePath());
this.mainActivity = mainActivity;
this.applicationContext = mainActivity.getApplicationContext();
this.codePushConfig = new CodePushConfig(deploymentKey, this.applicationContext);
checkForPendingUpdate(/*needsRestart*/ false);
}
public ReactPackage getReactPackage() {
if (codePushReactPackage == null) {
codePushReactPackage = new CodePushReactPackage();
}
return codePushReactPackage;
}
public String getDocumentsDirectory() {
return codePushPackage.getDocumentsDirectory();
}
public String getBundleUrl(String assetsBundleFileName) {
this.assetsBundleFileName = assetsBundleFileName;
String binaryJsBundleUrl = ASSETS_BUNDLE_PREFIX + assetsBundleFileName;
try {
String packageFile = codePushPackage.getCurrentPackageBundlePath();
if (packageFile == null) {
// There has not been any downloaded updates.
return binaryJsBundleUrl;
}
return packageFile;
} catch (IOException e) {
throw new CodePushUnknownException("Error in getting current package bundle path", e);
}
}
private void cancelRollbackTimer() {
if(timer != null) {
timer.cancel();
timer = null;
}
}
private void checkForPendingUpdate(boolean needsRestart) {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
String pendingUpdateString = settings.getString(PENDING_UPDATE_KEY, null);
if (pendingUpdateString != null) {
try {
JSONObject pendingUpdateJSON = new JSONObject(pendingUpdateString);
String pendingHash = pendingUpdateJSON.getString(PENDING_UPDATE_HASH_KEY);
String currentHash = codePushPackage.getCurrentPackageHash();
if (pendingHash.equals(currentHash)) {
int rollbackTimeout = pendingUpdateJSON.getInt(PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY);
initializeUpdateWithRollbackTimeout(rollbackTimeout, needsRestart);
settings.edit().remove(PENDING_UPDATE_KEY).commit();
}
} catch (JSONException e) {
// Should not happen.
throw new CodePushUnknownException("Unable to parse pending update metadata " +
pendingUpdateString + " stored in SharedPreferences", e);
} catch (IOException e) {
// There is no current package hash.
throw new CodePushUnknownException("Should not register a pending update without a saving a current package", e);
}
}
}
private void checkForPendingUpdateDuringResume() {
if (resumablePendingUpdateAvailable) {
checkForPendingUpdate(/*needsRestart*/ true);
}
}
private WritableMap constantsToExport() {
// Export the values of the CodePushInstallMode enum
// so that the script-side can easily stay in sync
WritableMap map = new WritableNativeMap();
map.putInt("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue());
map.putInt("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue());
map.putInt("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue());
return map;
}
private void initializeUpdateWithRollbackTimeout(int rollbackTimeout, boolean needsRestart) {
didUpdate = true;
if (needsRestart) {
codePushNativeModule.loadBundle();
}
if (0 != rollbackTimeout) {
startRollbackTimer(rollbackTimeout);
}
}
private boolean isFailedHash(String packageHash) {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null);
if (failedUpdatesString == null) {
return false;
}
try {
JSONObject failedUpdates = new JSONObject(failedUpdatesString);
return failedUpdates.has(packageHash);
} catch (JSONException e) {
// Should not happen.
throw new CodePushUnknownException("Unable to parse failed updates information " +
failedUpdatesString + " stored in SharedPreferences", e);
}
}
private void rollbackPackage() {
try {
String packageHash = codePushPackage.getCurrentPackageHash();
saveFailedUpdate(packageHash);
} catch (IOException e) {
throw new CodePushUnknownException("Attempted a rollback without having a current downloaded package", e);
}
try {
codePushPackage.rollbackPackage();
} catch (IOException e) {
throw new CodePushUnknownException("Error in rolling back package", e);
}
codePushNativeModule.loadBundle();
}
private void saveFailedUpdate(String packageHash) {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
String failedUpdatesString = settings.getString(FAILED_UPDATES_KEY, null);
JSONObject failedUpdates;
if (failedUpdatesString == null) {
failedUpdates = new JSONObject();
} else {
try {
failedUpdates = new JSONObject(failedUpdatesString);
} catch (JSONException e) {
// Should not happen.
throw new CodePushMalformedDataException("Unable to parse failed updates information " +
failedUpdatesString + " stored in SharedPreferences", e);
}
}
try {
failedUpdates.put(packageHash, true);
settings.edit().putString(FAILED_UPDATES_KEY, failedUpdates.toString()).commit();
} catch (JSONException e) {
// Should not happen unless the packageHash is null.
throw new CodePushUnknownException("Unable to save package hash " +
packageHash + " as a failed update", e);
}
}
private void savePendingUpdate(String packageHash, int rollbackTimeout) {
SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0);
JSONObject pendingUpdate = new JSONObject();
try {
pendingUpdate.put(PENDING_UPDATE_HASH_KEY, packageHash);
pendingUpdate.put(PENDING_UPDATE_ROLLBACK_TIMEOUT_KEY, rollbackTimeout);
settings.edit().putString(PENDING_UPDATE_KEY, pendingUpdate.toString()).commit();
} catch (JSONException e) {
// Should not happen.
throw new CodePushUnknownException("Unable to save pending update.", e);
}
}
private void startRollbackTimer(int rollbackTimeout) {
timer = new Timer();
Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.MILLISECOND, rollbackTimeout);
Date timeout = c.getTime();
timer.schedule(new TimerTask() {
@Override
public void run() {
rollbackPackage();
}
}, timeout);
}
private class CodePushNativeModule extends ReactContextBaseJavaModule {
private void loadBundle() {
String assetsBundleFileUrl = CodePush.this.getBundleUrl(CodePush.this.assetsBundleFileName);
Intent intent = mainActivity.getIntent();
mainActivity.finish();
mainActivity.startActivity(intent);
}
@ReactMethod
public void installUpdate(ReadableMap updatePackage, int rollbackTimeout, int installMode,
Callback resolve, Callback reject) {
try {
codePushPackage.installPackage(updatePackage);
if (installMode != CodePushInstallMode.IMMEDIATE.getValue()) {
resumablePendingUpdateAvailable = installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue();
String pendingHash = CodePushUtils.tryGetString(updatePackage, codePushPackage.PACKAGE_HASH_KEY);
if (pendingHash == null) {
throw new CodePushUnknownException("Update package to be installed has no hash.");
} else {
savePendingUpdate(pendingHash, rollbackTimeout);
}
}
resolve.invoke("");
} catch (IOException e) {
e.printStackTrace();
reject.invoke(e.getMessage());
}
}
@ReactMethod
public void restartApp(int rollbackTimeout) {
initializeUpdateWithRollbackTimeout(rollbackTimeout, /*needsRestart*/ true);
}
@ReactMethod
public void downloadUpdate(final ReadableMap updatePackage, final Callback resolve, final Callback reject) {
try {
codePushPackage.downloadPackage(applicationContext, updatePackage, new DownloadProgressCallback() {
@Override
public void call(DownloadProgress downloadProgress) {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap());
}
});
WritableMap newPackage = codePushPackage.getPackage(CodePushUtils.tryGetString(updatePackage, codePushPackage.PACKAGE_HASH_KEY));
resolve.invoke(newPackage);
} catch (IOException e) {
e.printStackTrace();
reject.invoke(e.getMessage());
}
}
@ReactMethod
public void getConfiguration(Callback resolve, Callback reject) {
resolve.invoke(codePushConfig.getConfiguration());
}
@ReactMethod
public void getCurrentPackage(Callback resolve, Callback reject) {
try {
resolve.invoke(codePushPackage.getCurrentPackage());
} catch (IOException e) {
e.printStackTrace();
reject.invoke(e.getMessage());
}
}
@ReactMethod
public void isFailedUpdate(String packageHash, Callback resolve, Callback reject) {
resolve.invoke(isFailedHash(packageHash));
}
@ReactMethod
public void isFirstRun(String packageHash, Callback resolve, Callback reject) {
try {
boolean isFirstRun = didUpdate
&& packageHash != null
&& packageHash.length() > 0
&& packageHash.equals(codePushPackage.getCurrentPackageHash());
resolve.invoke(isFirstRun);
} catch (IOException e) {
e.printStackTrace();
reject.invoke(e.getMessage());
}
}
@ReactMethod
public void notifyApplicationReady(Callback resolve, Callback reject) {
cancelRollbackTimer();
resolve.invoke("");
}
@ReactMethod
public void setUsingTestFolder(boolean shouldUseTestFolder) {
usingTestFolder = shouldUseTestFolder;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue());
constants.put("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue());
constants.put("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue());
return constants;
}
public CodePushNativeModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "CodePush";
}
}
private class CodePushReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
List<NativeModule> nativeModules = new ArrayList<>();
CodePush.this.codePushNativeModule = new CodePushNativeModule(reactApplicationContext);
CodePushDialog dialogModule = new CodePushDialog(reactApplicationContext, mainActivity);
nativeModules.add(CodePush.this.codePushNativeModule);
nativeModules.add(dialogModule);
reactApplicationContext.addLifecycleEventListener(new LifecycleEventListener() {
@Override
public void onHostResume() {
CodePush.this.checkForPendingUpdateDuringResume();
}
@Override
public void onHostPause() {
}
@Override
public void onHostDestroy() {
}
});
return nativeModules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return new ArrayList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
return new ArrayList();
}
}
}

View File

@@ -0,0 +1,69 @@
package com.microsoft.reactnativecodepush;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableNativeMap;
public class CodePushConfig {
private String appVersion;
private int buildVersion;
private String deploymentKey;
private String serverUrl = "https://codepush.azurewebsites.net/";
public CodePushConfig(String deploymentKey, Context applicationContext) {
this.deploymentKey = deploymentKey;
PackageInfo pInfo = null;
try {
pInfo = applicationContext.getPackageManager().getPackageInfo(applicationContext.getPackageName(), 0);
appVersion = pInfo.versionName;
buildVersion = pInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
throw new CodePushUnknownException("Unable to get package info for " + applicationContext.getPackageName(), e);
}
}
public void setDeploymentKey(String deploymentKey) {
this.deploymentKey = deploymentKey;
}
public String getDeploymentKey() {
return deploymentKey;
}
public void setServerUrl(String serverUrl) {
this.serverUrl = serverUrl;
}
public String getServerUrl() {
return serverUrl;
}
public void setAppVersion(String appVersion) {
this.appVersion = appVersion;
}
public String getAppVersion() {
return appVersion;
}
public void setBuildVersion(int buildVersion) {
this.buildVersion = buildVersion;
}
public int getBuildVersion() {
return buildVersion;
}
public ReadableMap getConfiguration() {
WritableNativeMap configMap = new WritableNativeMap();
configMap.putString("appVersion", appVersion);
configMap.putInt("buildVersion", buildVersion);
configMap.putString("deploymentKey", deploymentKey);
configMap.putString("serverUrl", serverUrl);
return configMap;
}
}

View File

@@ -0,0 +1,77 @@
package com.microsoft.reactnativecodepush;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.support.v4.app.FragmentActivity;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class CodePushDialog extends ReactContextBaseJavaModule{
Activity mainActivity;
public CodePushDialog(ReactApplicationContext reactContext, Activity mainActivity) {
super(reactContext);
this.mainActivity = mainActivity;
}
@ReactMethod
public void showDialog(String title, String message, String button1Text, String button2Text,
final Callback successCallback, Callback errorCallback) {
FragmentActivity fragmentActivity = null;
try {
fragmentActivity = (FragmentActivity) mainActivity;
} catch (ClassCastException e) {
errorCallback.invoke("Unable to show dialog, main activity is not a FragmentActivity");
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(fragmentActivity);
DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
successCallback.invoke(0);
break;
case DialogInterface.BUTTON_NEGATIVE:
successCallback.invoke(1);
break;
default:
throw new CodePushUnknownException("Unknown button ID pressed.");
}
}
};
if (title != null) {
builder.setTitle(title);
}
if (message != null) {
builder.setMessage(message);
}
if (button1Text != null) {
builder.setPositiveButton(button1Text, clickListener);
}
if (button2Text != null) {
builder.setNegativeButton(button2Text, clickListener);
}
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
public String getName() {
return "CodePushDialog";
}
}

View File

@@ -0,0 +1,15 @@
package com.microsoft.reactnativecodepush;
public enum CodePushInstallMode {
IMMEDIATE(0),
ON_NEXT_RESTART(1),
ON_NEXT_RESUME(2);
private final int value;
private CodePushInstallMode(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}

View File

@@ -0,0 +1,12 @@
package com.microsoft.reactnativecodepush;
import java.net.MalformedURLException;
public class CodePushMalformedDataException extends RuntimeException {
public CodePushMalformedDataException(String path, Throwable cause) {
super("Unable to parse contents of " + path + ", the file may be corrupted.", cause);
}
public CodePushMalformedDataException(String url, MalformedURLException cause) {
super("The package has an invalid downloadUrl: " + url, cause);
}
}

View File

@@ -0,0 +1,184 @@
package com.microsoft.reactnativecodepush;
import android.content.Context;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class CodePushPackage {
public final String CODE_PUSH_FOLDER_PREFIX = "CodePush";
public final String STATUS_FILE = "codepush.json";
public final String UPDATE_BUNDLE_FILE_NAME = "app.jsbundle";
public final String CURRENT_PACKAGE_KEY = "currentPackage";
public final String PREVIOUS_PACKAGE_KEY = "previousPackage";
public final String PACKAGE_FILE_NAME = "app.json";
public final String PACKAGE_HASH_KEY = "packageHash";
public final String DOWNLOAD_URL_KEY = "downloadUrl";
public final int DOWNLOAD_BUFFER_SIZE = 8192;
private String documentsDirectory;
public CodePushPackage(String documentsDirectory) {
this.documentsDirectory = documentsDirectory;
}
public String getDocumentsDirectory() {
return documentsDirectory;
}
public String getCodePushPath() {
return CodePushUtils.appendPathComponent(getDocumentsDirectory(), CODE_PUSH_FOLDER_PREFIX);
}
public String getStatusFilePath() {
return CodePushUtils.appendPathComponent(getCodePushPath(), STATUS_FILE);
}
public WritableMap getCurrentPackageInfo() throws IOException {
String statusFilePath = getStatusFilePath();
if (!CodePushUtils.fileAtPathExists(statusFilePath)) {
return new WritableNativeMap();
}
return CodePushUtils.getWritableMapFromFile(statusFilePath);
}
public void updateCurrentPackageInfo(ReadableMap packageInfo) throws IOException {
CodePushUtils.writeReadableMapToFile(packageInfo, getStatusFilePath());
}
public String getCurrentPackageFolderPath() throws IOException {
WritableMap info = getCurrentPackageInfo();
String packageHash = CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY);
if (packageHash == null) {
return null;
}
return getPackageFolderPath(packageHash);
}
public String getCurrentPackageBundlePath() throws IOException {
String packageFolder = getCurrentPackageFolderPath();
if (packageFolder == null) {
return null;
}
return CodePushUtils.appendPathComponent(packageFolder, UPDATE_BUNDLE_FILE_NAME);
}
public String getPackageFolderPath(String packageHash) {
return CodePushUtils.appendPathComponent(getCodePushPath(), packageHash);
}
public String getCurrentPackageHash() throws IOException {
WritableMap info = getCurrentPackageInfo();
return CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY);
}
public String getPreviousPackageHash() throws IOException {
WritableMap info = getCurrentPackageInfo();
return CodePushUtils.tryGetString(info, PREVIOUS_PACKAGE_KEY);
}
public WritableMap getCurrentPackage() throws IOException {
String folderPath = getCurrentPackageFolderPath();
if (folderPath == null) {
return new WritableNativeMap();
}
String packagePath = CodePushUtils.appendPathComponent(folderPath, PACKAGE_FILE_NAME);
try {
return CodePushUtils.getWritableMapFromFile(packagePath);
} catch (IOException e) {
// There is no current package.
return null;
}
}
public WritableMap getPackage(String packageHash) throws IOException {
String folderPath = getPackageFolderPath(packageHash);
String packageFilePath = CodePushUtils.appendPathComponent(folderPath, PACKAGE_FILE_NAME);
try {
return CodePushUtils.getWritableMapFromFile(packageFilePath);
} catch (IOException e) {
return null;
}
}
public void downloadPackage(Context applicationContext, ReadableMap updatePackage,
DownloadProgressCallback progressCallback) throws IOException {
String packageFolderPath = getPackageFolderPath(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY));
String downloadUrlString = CodePushUtils.tryGetString(updatePackage, DOWNLOAD_URL_KEY);
URL downloadUrl = null;
HttpURLConnection connection = null;
BufferedInputStream bin = null;
FileOutputStream fos = null;
BufferedOutputStream bout = null;
try {
downloadUrl = new URL(downloadUrlString);
connection = (HttpURLConnection) (downloadUrl.openConnection());
long totalBytes = connection.getContentLength();
long receivedBytes = 0;
bin = new BufferedInputStream(connection.getInputStream());
File downloadFolder = new File(packageFolderPath);
downloadFolder.mkdirs();
File downloadFile = new File(downloadFolder, UPDATE_BUNDLE_FILE_NAME);
fos = new FileOutputStream(downloadFile);
bout = new BufferedOutputStream(fos, DOWNLOAD_BUFFER_SIZE);
byte[] data = new byte[DOWNLOAD_BUFFER_SIZE];
int numBytesRead = 0;
while ((numBytesRead = bin.read(data, 0, DOWNLOAD_BUFFER_SIZE)) >= 0) {
receivedBytes += numBytesRead;
bout.write(data, 0, numBytesRead);
progressCallback.call(new DownloadProgress(totalBytes, receivedBytes));
}
assert totalBytes == receivedBytes;
String bundlePath = CodePushUtils.appendPathComponent(packageFolderPath, PACKAGE_FILE_NAME);
CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath);
} catch (MalformedURLException e) {
throw new CodePushMalformedDataException(downloadUrlString, e);
} finally {
try {
if (bout != null) bout.close();
if (fos != null) fos.close();
if (bin != null) bin.close();
if (connection != null) connection.disconnect();
} catch (IOException e) {
throw new CodePushUnknownException("Error closing IO resources.", e);
}
}
}
public void installPackage(ReadableMap updatePackage) throws IOException {
String packageHash = CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY);
WritableMap info = getCurrentPackageInfo();
info.putString(PREVIOUS_PACKAGE_KEY, CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY));
info.putString(CURRENT_PACKAGE_KEY, packageHash);
updateCurrentPackageInfo(info);
}
public void rollbackPackage() throws IOException {
WritableMap info = getCurrentPackageInfo();
info.putString(CURRENT_PACKAGE_KEY, CodePushUtils.tryGetString(info, PREVIOUS_PACKAGE_KEY));
info.putNull(PREVIOUS_PACKAGE_KEY);
updateCurrentPackageInfo(info);
}
}

View File

@@ -0,0 +1,12 @@
package com.microsoft.reactnativecodepush;
public class CodePushUnknownException extends RuntimeException {
public CodePushUnknownException(String message, Throwable cause) {
super(message, cause);
}
public CodePushUnknownException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,237 @@
package com.microsoft.reactnativecodepush;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Iterator;
public class CodePushUtils {
public static String appendPathComponent(String basePath, String appendPathComponent) {
return new File(basePath, appendPathComponent).getAbsolutePath();
}
public static boolean fileAtPathExists(String filePath) {
return new File(filePath).exists();
}
public static String readFileToString(String filePath) throws IOException {
FileInputStream fin = null;
BufferedReader reader = null;
try {
File fl = new File(filePath);
fin = new FileInputStream(fl);
reader = new BufferedReader(new InputStreamReader(fin));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
} finally {
if (reader != null) reader.close();
if (fin != null) fin.close();
}
}
public static void writeStringToFile(String content, String filePath) throws IOException {
PrintWriter out = null;
try {
out = new PrintWriter(filePath);
out.print(content);
} finally {
if (out != null) out.close();
}
}
public static boolean createFolderAtPath(String filePath) {
File file = new File(filePath);
return file.mkdir();
}
public static WritableMap getWritableMapFromFile(String filePath) throws IOException {
String content = CodePushUtils.readFileToString(filePath);
JSONObject json = null;
try {
json = new JSONObject(content);
return convertJsonObjectToWriteable(json);
} catch (JSONException jsonException) {
throw new CodePushMalformedDataException(filePath, jsonException);
}
}
public static void writeReadableMapToFile(ReadableMap map, String filePath) throws IOException {
JSONObject json = CodePushUtils.convertReadableToJsonObject(map);
String jsonString = json.toString();
CodePushUtils.writeStringToFile(jsonString, filePath);
}
public static WritableMap convertJsonObjectToWriteable(JSONObject jsonObj) {
WritableMap map = Arguments.createMap();
Iterator<String> it = jsonObj.keys();
while(it.hasNext()){
String key = it.next();
Object obj = null;
try {
obj = jsonObj.get(key);
} catch (JSONException jsonException) {
// Should not happen.
throw new CodePushUnknownException("Key " + key + " should exist in " + jsonObj.toString() + ".", jsonException);
}
if (obj instanceof JSONObject)
map.putMap(key, convertJsonObjectToWriteable((JSONObject) obj));
else if (obj instanceof JSONArray)
map.putArray(key, convertJsonArrayToWriteable((JSONArray) obj));
else if (obj instanceof String)
map.putString(key, (String) obj);
else if (obj instanceof Double)
map.putDouble(key, (Double) obj);
else if (obj instanceof Integer)
map.putInt(key, (Integer) obj);
else if (obj instanceof Boolean)
map.putBoolean(key, (Boolean) obj);
else if (obj == null)
map.putNull(key);
else
throw new CodePushUnknownException("Unrecognized object: " + obj);
}
return map;
}
public static WritableArray convertJsonArrayToWriteable(JSONArray jsonArr) {
WritableArray arr = Arguments.createArray();
for (int i=0; i<jsonArr.length(); i++) {
Object obj = null;
try {
obj = jsonArr.get(i);
} catch (JSONException jsonException) {
// Should not happen.
throw new CodePushUnknownException(i + " should be within bounds of array " + jsonArr.toString(), jsonException);
}
if (obj instanceof JSONObject)
arr.pushMap(convertJsonObjectToWriteable((JSONObject) obj));
else if (obj instanceof JSONArray)
arr.pushArray(convertJsonArrayToWriteable((JSONArray) obj));
else if (obj instanceof String)
arr.pushString((String) obj);
else if (obj instanceof Double)
arr.pushDouble((Double) obj);
else if (obj instanceof Integer)
arr.pushInt((Integer) obj);
else if (obj instanceof Boolean)
arr.pushBoolean((Boolean) obj);
else if (obj == null)
arr.pushNull();
else
throw new CodePushUnknownException("Unrecognized object: " + obj);
}
return arr;
}
public static JSONObject convertReadableToJsonObject(ReadableMap map) {
JSONObject jsonObj = new JSONObject();
ReadableMapKeySetIterator it = map.keySetIterator();
while (it.hasNextKey()) {
String key = it.nextKey();
ReadableType type = map.getType(key);
try {
switch (type) {
case Map:
jsonObj.put(key, convertReadableToJsonObject(map.getMap(key)));
break;
case Array:
jsonObj.put(key, convertReadableToJsonArray(map.getArray(key)));
break;
case String:
jsonObj.put(key, map.getString(key));
break;
case Number:
jsonObj.put(key, map.getDouble(key));
break;
case Boolean:
jsonObj.put(key, map.getBoolean(key));
break;
case Null:
jsonObj.put(key, null);
break;
default:
throw new CodePushUnknownException("Unrecognized type: " + type + " of key: " + key);
}
} catch (JSONException jsonException) {
throw new CodePushUnknownException("Error setting key: " + key + " in JSONObject", jsonException);
}
}
return jsonObj;
}
public static JSONArray convertReadableToJsonArray(ReadableArray arr) {
JSONArray jsonArr = new JSONArray();
for (int i=0; i<arr.size(); i++) {
ReadableType type = arr.getType(i);
switch (type) {
case Map:
jsonArr.put(convertReadableToJsonObject(arr.getMap(i)));
break;
case Array:
jsonArr.put(convertReadableToJsonArray(arr.getArray(i)));
break;
case String:
jsonArr.put(arr.getString(i));
break;
case Number:
Double number = arr.getDouble(i);
if ((number == Math.floor(number)) && !Double.isInfinite(number)) {
// This is a whole number.
jsonArr.put(number.longValue());
} else {
try {
jsonArr.put(number.doubleValue());
} catch (JSONException jsonException) {
throw new CodePushUnknownException("Unable to put value " + arr.getDouble(i) + " in JSONArray");
}
}
break;
case Boolean:
jsonArr.put(arr.getBoolean(i));
break;
case Null:
jsonArr.put(null);
break;
}
}
return jsonArr;
}
public static String tryGetString(ReadableMap map, String key) {
try {
return map.getString(key);
} catch (NoSuchKeyException e) {
return null;
}
}
}

View File

@@ -0,0 +1,26 @@
package com.microsoft.reactnativecodepush;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
public class DownloadProgress {
public long totalBytes;
public long receivedBytes;
public DownloadProgress (long totalBytes, long receivedBytes){
this.totalBytes = totalBytes;
this.receivedBytes = receivedBytes;
}
public WritableMap createWritableMap() {
WritableMap map = new WritableNativeMap();
if (totalBytes < Integer.MAX_VALUE) {
map.putInt("totalBytes", (int) totalBytes);
map.putInt("receivedBytes", (int) receivedBytes);
} else {
map.putDouble("totalBytes", totalBytes);
map.putDouble("receivedBytes", receivedBytes);
}
return map;
}
}

View File

@@ -0,0 +1,5 @@
package com.microsoft.reactnativecodepush;
public interface DownloadProgressCallback {
void call(DownloadProgress downloadProgress);
}

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">ReactNativeCodePush</string>
</resources>

20
android/build.gradle Normal file
View File

@@ -0,0 +1,20 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
}
}

20
android/gradle.properties Normal file
View File

@@ -0,0 +1,20 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip

164
android/gradlew vendored Executable file
View File

@@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
android/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

3
android/settings.gradle Normal file
View File

@@ -0,0 +1,3 @@
rootProject.name = 'ReactNativeCodePush'
include ':app'

View File

@@ -1,4 +1,13 @@
var { NativeAppEventEmitter } = require("react-native");
var Platform = require("Platform");
var EventEmitter;
if (Platform.OS === "android") {
var { DeviceEventEmitter } = require("react-native");
EventEmitter = DeviceEventEmitter;
} else if (Platform.OS === "ios") {
var { NativeAppEventEmitter } = require("react-native");
EventEmitter = NativeAppEventEmitter;
}
module.exports = (NativeCodePush) => {
var remote = {
@@ -13,7 +22,7 @@ module.exports = (NativeCodePush) => {
var downloadProgressSubscription;
if (downloadProgressCallback) {
// Use event subscription to obtain download progress.
downloadProgressSubscription = NativeAppEventEmitter.addListener(
downloadProgressSubscription = EventEmitter.addListener(
"CodePushDownloadProgress",
downloadProgressCallback
);

View File

@@ -2,7 +2,7 @@
"name": "react-native-code-push",
"version": "1.2.1-beta",
"description": "React Native plugin for the CodePush service",
"main": "CodePush.ios.js",
"main": "CodePush.js",
"homepage": "https://microsoft.github.io/code-push",
"keywords": [
"react-native",
@@ -19,6 +19,6 @@
"code-push": "^1.1.1-beta"
},
"devDependencies": {
"react-native": "0.11.4"
"react-native": "0.14.2"
}
}