mirror of
https://github.com/zhigang1992/react-native-code-push.git
synced 2026-06-10 07:10:36 +08:00
android support
This commit is contained in:
50
.gitignore
vendored
50
.gitignore
vendored
@@ -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
1
.watchmanconfig
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
66
CodePushNativePlatformAdapter.js
Normal file
66
CodePushNativePlatformAdapter.js
Normal 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
|
||||
}
|
||||
46
Examples/CodePushDemoApp/.gitignore
vendored
46
Examples/CodePushDemoApp/.gitignore
vendored
@@ -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/
|
||||
1
Examples/CodePushDemoApp/.watchmanconfig
Normal file
1
Examples/CodePushDemoApp/.watchmanconfig
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* @providesModule QueryUpdateTestApp
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* @providesModule QueryUpdateTestApp
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
|
||||
30
Examples/CodePushDemoApp/android/app/build.gradle
Normal file
30
Examples/CodePushDemoApp/android/app/build.gradle
Normal 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')
|
||||
}
|
||||
17
Examples/CodePushDemoApp/android/app/proguard-rules.pro
vendored
Normal file
17
Examples/CodePushDemoApp/android/app/proguard-rules.pro
vendored
Normal 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 *;
|
||||
#}
|
||||
@@ -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
@@ -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 |
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">CodePushDemoApp</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
20
Examples/CodePushDemoApp/android/build.gradle
Normal file
20
Examples/CodePushDemoApp/android/build.gradle
Normal 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
Examples/CodePushDemoApp/android/gradle.properties
Normal file
20
Examples/CodePushDemoApp/android/gradle.properties
Normal 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
|
||||
BIN
Examples/CodePushDemoApp/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
Examples/CodePushDemoApp/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
Examples/CodePushDemoApp/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
Examples/CodePushDemoApp/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
164
Examples/CodePushDemoApp/android/gradlew
vendored
Executable 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
Examples/CodePushDemoApp/android/gradlew.bat
vendored
Normal file
90
Examples/CodePushDemoApp/android/gradlew.bat
vendored
Normal 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
|
||||
4
Examples/CodePushDemoApp/android/settings.gradle
Normal file
4
Examples/CodePushDemoApp/android/settings.gradle
Normal 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')
|
||||
140
Examples/CodePushDemoApp/crossplatformdemo.js
Normal file
140
Examples/CodePushDemoApp/crossplatformdemo.js
Normal 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);
|
||||
1
Examples/CodePushDemoApp/index.android.js
Normal file
1
Examples/CodePushDemoApp/index.android.js
Normal file
@@ -0,0 +1 @@
|
||||
require("./crossplatformdemo.js");
|
||||
@@ -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");
|
||||
@@ -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:../../"
|
||||
}
|
||||
|
||||
72
README.md
72
README.md
@@ -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
|
||||
|
||||

|
||||
|
||||
## 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
28
android/app/build.gradle
Normal 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
17
android/app/proguard-rules.pro
vendored
Normal 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 *;
|
||||
#}
|
||||
12
android/app/src/main/AndroidManifest.xml
Normal file
12
android/app/src/main/AndroidManifest.xml
Normal 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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.microsoft.reactnativecodepush;
|
||||
|
||||
public interface DownloadProgressCallback {
|
||||
void call(DownloadProgress downloadProgress);
|
||||
}
|
||||
3
android/app/src/main/res/values/strings.xml
Normal file
3
android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">ReactNativeCodePush</string>
|
||||
</resources>
|
||||
20
android/build.gradle
Normal file
20
android/build.gradle
Normal 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
20
android/gradle.properties
Normal 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
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
164
android/gradlew
vendored
Executable 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
90
android/gradlew.bat
vendored
Normal 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
3
android/settings.gradle
Normal file
@@ -0,0 +1,3 @@
|
||||
rootProject.name = 'ReactNativeCodePush'
|
||||
|
||||
include ':app'
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user