diff --git a/.gitignore b/.gitignore index c51d7b6db..4c0623576 100644 --- a/.gitignore +++ b/.gitignore @@ -150,9 +150,6 @@ proguard/ # Android Studio captures folder captures/ -# Remove after this framework is published on NPM -code-push-plugin-testing-framework/node_modules - # Windows windows/.vs/ windows/obj/ @@ -192,4 +189,4 @@ Examples/testapp_rn # Android debug build files (conflict ignoring #Visual Studio files) !android/app/src/debug/ -.temp/ \ No newline at end of file +.temp/ diff --git a/.npmignore b/.npmignore index d385ceef2..754f0a0a5 100644 --- a/.npmignore +++ b/.npmignore @@ -34,10 +34,8 @@ Recipes/ bin/ test/ -# Remove after this framework is published on NPM -code-push-plugin-testing-framework/ - # Android build artifacts and Android Studio bits +android/build android/app/build android/local.properties android/.gradle diff --git a/AlertAdapter.js b/AlertAdapter.js index 124375891..260491e24 100644 --- a/AlertAdapter.js +++ b/AlertAdapter.js @@ -2,7 +2,22 @@ import React, { Platform } from "react-native"; let { Alert } = React; if (Platform.OS === "android") { - const { NativeModules: { CodePushDialog } } = React; + function resolveNativeModule(name) { + const ReactNative = require("react-native"); + try { + const turboModule = + ReactNative.TurboModuleRegistry && ReactNative.TurboModuleRegistry.get + ? ReactNative.TurboModuleRegistry.get(name) + : null; + if (turboModule) return turboModule; + } catch (_e) { + // Ignore and fall back to legacy NativeModules. + } + + return ReactNative.NativeModules ? ReactNative.NativeModules[name] : null; + } + + const CodePushDialog = resolveNativeModule("CodePushDialog"); Alert = { alert(title, message, buttons) { @@ -13,6 +28,10 @@ if (Platform.OS === "android") { const button1Text = buttons[0] ? buttons[0].text : null, button2Text = buttons[1] ? buttons[1].text : null; + if (!CodePushDialog) { + throw "CodePushDialog native module is not installed."; + } + CodePushDialog.showDialog( title, message, button1Text, button2Text, (buttonId) => { buttons[buttonId].onPress && buttons[buttonId].onPress(); }, @@ -21,4 +40,4 @@ if (Platform.OS === "android") { }; } -module.exports = { Alert }; \ No newline at end of file +module.exports = { Alert }; diff --git a/CodePush.js b/CodePush.js index 2d1d9deda..c811bb07a 100644 --- a/CodePush.js +++ b/CodePush.js @@ -5,7 +5,22 @@ import { AppState, Platform } from "react-native"; import log from "./logging"; import hoistStatics from 'hoist-non-react-statics'; -let NativeCodePush = require("react-native").NativeModules.CodePush; +function resolveNativeModule(name) { + const ReactNative = require("react-native"); + try { + const turboModule = + ReactNative.TurboModuleRegistry && ReactNative.TurboModuleRegistry.get + ? ReactNative.TurboModuleRegistry.get(name) + : null; + if (turboModule) return turboModule; + } catch (_e) { + // Ignore and fall back to legacy NativeModules. + } + + return ReactNative.NativeModules ? ReactNative.NativeModules[name] : null; +} + +let NativeCodePush = resolveNativeModule("CodePush"); const PackageMixins = require("./package-mixins")(NativeCodePush); async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchCallback = null) { diff --git a/CodePush.podspec b/CodePush.podspec index 6d354a1ea..3d896818a 100644 --- a/CodePush.podspec +++ b/CodePush.podspec @@ -10,8 +10,8 @@ Pod::Spec.new do |s| s.license = package['license'] s.homepage = package['homepage'] s.source = { :git => 'https://github.com/srcpush/react-native-code-push.git', :tag => "v#{s.version}"} - s.ios.deployment_target = '15.5' - s.tvos.deployment_target = '15.5' + s.ios.deployment_target = '11.0' + s.tvos.deployment_target = '11.0' s.preserve_paths = '*.js' s.library = 'z' s.source_files = 'ios/CodePush/*.{h,m}' diff --git a/android/app/build.gradle b/android/app/build.gradle index 133dd3e36..38f92c399 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,48 +1,98 @@ -apply plugin: "com.android.library" - -def isNewArchitectureEnabled() { - // To opt-in for the New Architecture, you can either: - // - Set `newArchEnabled` to true inside the `gradle.properties` file - // - Invoke gradle with `-newArchEnabled=true` - // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` - return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" -} +import groovy.json.JsonSlurper -def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled() +apply plugin: "com.android.library" -if (IS_NEW_ARCHITECTURE_ENABLED) { - apply plugin: "com.facebook.react" +def safeExtGet(prop, fallback) { + return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } def DEFAULT_COMPILE_SDK_VERSION = 26 -def DEFAULT_BUILD_TOOLS_VERSION = "26.0.3" def DEFAULT_TARGET_SDK_VERSION = 26 def DEFAULT_MIN_SDK_VERSION = 16 +def findReactNativePackageJson() { + File currentDir = projectDir + + while (currentDir != null) { + File packageJson = new File(currentDir, "node_modules/react-native/package.json") + if (packageJson.exists()) { + return packageJson + } + + currentDir = currentDir.parentFile + } + + return null +} + +def getReactNativeVersion() { + File packageJson = findReactNativePackageJson() + if (packageJson == null) { + return null + } + + def version = new JsonSlurper().parseText(packageJson.text).version + return version.tokenize("-")[0] +} + +def getReactNativeMinorVersion() { + def reactNativeVersion = getReactNativeVersion() + if (reactNativeVersion == null) { + return null + } + + def versionParts = reactNativeVersion.tokenize(".") + if (versionParts.size() < 2) { + return null + } + + return versionParts[1].toInteger() +} + +def reactNativeVersion = getReactNativeVersion() +def reactNativeMinorVersion = getReactNativeMinorVersion() +def reactNativeDependency = reactNativeMinorVersion != null && reactNativeMinorVersion < 71 + ? "com.facebook.react:react-native:${reactNativeVersion ?: '+'}" + : "com.facebook.react:react-android:${reactNativeVersion ?: '+'}" + android { - namespace "com.microsoft.codepush.react" + if (project.android.hasProperty("namespace")) { + namespace "com.microsoft.codepush.react" + } - compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION - buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION + compileSdkVersion safeExtGet("compileSdkVersion", DEFAULT_COMPILE_SDK_VERSION) defaultConfig { - minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : DEFAULT_MIN_SDK_VERSION - targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION + minSdkVersion safeExtGet("minSdkVersion", DEFAULT_MIN_SDK_VERSION) + targetSdkVersion safeExtGet("targetSdkVersion", DEFAULT_TARGET_SDK_VERSION) versionCode 1 versionName "1.0" - buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", IS_NEW_ARCHITECTURE_ENABLED.toString() + consumerProguardFiles "../proguard-rules.pro" + } + + sourceSets { + main { + java.srcDirs = ["../src/main/java"] + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { abortOnError false } +} - defaultConfig { - consumerProguardFiles 'proguard-rules.pro' - } +repositories { + google() + mavenCentral() + mavenLocal() } dependencies { - implementation "com.facebook.react:react-native:+" - implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3' + implementation(reactNativeDependency) + implementation "com.nimbusds:nimbus-jose-jwt:9.37.3" } diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index df32a1faf..fbb2ef838 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,9 +1,3 @@ - - - - - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b07e42801..96ba5b66f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,2 @@ - - - - - + diff --git a/android/app/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java b/android/app/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java deleted file mode 100644 index a5d54fdfc..000000000 --- a/android/app/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.microsoft.codepush.react; - -import com.facebook.react.ReactHost; -import com.facebook.react.runtime.ReactHostDelegate; - -/** - * Provides access to a {@link ReactHostDelegate} - */ -public interface ReactHostHolder { - ReactHost getReactHost(); -} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 31a524838..6bcd77cb9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,24 +1,106 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. +import groovy.json.JsonSlurper buildscript { + ext { + kotlinVersion = "1.9.24" + } repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.0' + classpath("com.android.tools.build:gradle:8.2.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + } +} + +apply plugin: "com.android.library" + +def safeExtGet(prop, fallback) { + return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} + +def DEFAULT_COMPILE_SDK_VERSION = 26 +def DEFAULT_TARGET_SDK_VERSION = 26 +def DEFAULT_MIN_SDK_VERSION = 16 + +def findReactNativePackageJson() { + File currentDir = projectDir + + while (currentDir != null) { + File packageJson = new File(currentDir, "node_modules/react-native/package.json") + if (packageJson.exists()) { + return packageJson + } - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + currentDir = currentDir.parentFile } + + return null +} + +def getReactNativeVersion() { + File packageJson = findReactNativePackageJson() + if (packageJson == null) { + return null + } + + def version = new JsonSlurper().parseText(packageJson.text).version + return version.tokenize("-")[0] } -allprojects { - android { +def getReactNativeMinorVersion() { + def reactNativeVersion = getReactNativeVersion() + if (reactNativeVersion == null) { + return null + } + + def versionParts = reactNativeVersion.tokenize(".") + if (versionParts.size() < 2) { + return null + } + + return versionParts[1].toInteger() +} + +def reactNativeVersion = getReactNativeVersion() +def reactNativeMinorVersion = getReactNativeMinorVersion() +def reactNativeDependency = reactNativeMinorVersion != null && reactNativeMinorVersion < 71 + ? "com.facebook.react:react-native:${reactNativeVersion ?: '+'}" + : "com.facebook.react:react-android:${reactNativeVersion ?: '+'}" + +android { + if (project.android.hasProperty("namespace")) { namespace "com.microsoft.codepush.react" } - repositories { - mavenLocal() - mavenCentral() + + compileSdkVersion safeExtGet("compileSdkVersion", DEFAULT_COMPILE_SDK_VERSION) + + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", DEFAULT_MIN_SDK_VERSION) + targetSdkVersion safeExtGet("targetSdkVersion", DEFAULT_TARGET_SDK_VERSION) + versionCode 1 + versionName "1.0" + consumerProguardFiles "proguard-rules.pro" } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + abortOnError false + } +} + +repositories { + google() + mavenCentral() + mavenLocal() +} + +dependencies { + implementation(reactNativeDependency) + implementation "com.nimbusds:nimbus-jose-jwt:9.37.3" } diff --git a/android/gradle.properties b/android/gradle.properties index 1fd964e90..f54c3e7fd 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -10,11 +10,11 @@ # 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 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 +android.useAndroidX=true +android.enableJetifier=true # 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 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index b9fbfaba0..45181329e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ 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 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/android/app/proguard-rules.pro b/android/proguard-rules.pro similarity index 100% rename from android/app/proguard-rules.pro rename to android/proguard-rules.pro diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 9d495b34f..000000000 --- a/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' \ No newline at end of file diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..b07e42801 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/src/main/java/com/microsoft/codepush/react/CodePush.java similarity index 96% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePush.java rename to android/src/main/java/com/microsoft/codepush/react/CodePush.java index df6d6430f..0c614b3e4 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -5,7 +5,6 @@ import android.content.pm.PackageManager; import android.content.res.Resources; -import com.facebook.react.ReactHost; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JavaScriptModule; @@ -60,6 +59,7 @@ public static CodePush getInstance(String deploymentKey, Context context, boolea private static ReactInstanceHolder mReactInstanceHolder; private static ReactHostHolder mReactHostHolder; + private static Object mReactHost; public CodePush(String deploymentKey, Context context) { this(deploymentKey, context, false); @@ -69,7 +69,7 @@ public static String getServiceUrl() { return mServerUrl; } - private CodePush(String deploymentKey, Context context, boolean isDebugMode) { + public CodePush(String deploymentKey, Context context, boolean isDebugMode) { mContext = context.getApplicationContext(); mUpdateManager = new CodePushUpdateManager(context.getFilesDir().getAbsolutePath()); @@ -406,6 +406,10 @@ public static void setReactHost(ReactHostHolder reactHostHolder) { mReactHostHolder = reactHostHolder; } + public static void setReactHost(Object reactHost) { + mReactHost = reactHost; + } + static ReactInstanceManager getReactInstanceManager() { if (mReactInstanceHolder == null) { return null; @@ -413,7 +417,11 @@ static ReactInstanceManager getReactInstanceManager() { return mReactInstanceHolder.getReactInstanceManager(); } - static ReactHost getReactHost() { + static Object getReactHost() { + if (mReactHost != null) { + return mReactHost; + } + if (mReactHostHolder == null) { return null; } @@ -423,13 +431,10 @@ static ReactHost getReactHost() { @Override public List createNativeModules(ReactApplicationContext reactApplicationContext) { - CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager); - CodePushDialog dialogModule = new CodePushDialog(reactApplicationContext); - - List nativeModules = new ArrayList<>(); - nativeModules.add(codePushModule); - nativeModules.add(dialogModule); - return nativeModules; + List modules = new ArrayList<>(); + modules.add(new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager)); + modules.add(new CodePushDialog(reactApplicationContext)); + return modules; } // Deprecated in RN v0.47. diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushBuilder.java b/android/src/main/java/com/microsoft/codepush/react/CodePushBuilder.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushBuilder.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushBuilder.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java b/android/src/main/java/com/microsoft/codepush/react/CodePushConstants.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushConstants.java diff --git a/android/src/main/java/com/microsoft/codepush/react/CodePushDialog.java b/android/src/main/java/com/microsoft/codepush/react/CodePushDialog.java new file mode 100644 index 000000000..973306e0f --- /dev/null +++ b/android/src/main/java/com/microsoft/codepush/react/CodePushDialog.java @@ -0,0 +1,50 @@ +package com.microsoft.codepush.react; + +import com.facebook.react.bridge.BaseJavaModule; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactMethod; + +public class CodePushDialog extends BaseJavaModule { + public static final String NAME = "CodePushDialog"; + + private final CodePushDialogImpl impl; + + public CodePushDialog(ReactApplicationContext reactContext) { + super(reactContext); + impl = new CodePushDialogImpl(reactContext); + } + + @ReactMethod + public void showDialog( + final String title, + final String message, + final String button1Text, + final String button2Text, + final Callback successCallback, + Callback errorCallback + ) { + try { + impl.showDialog(title, message, button1Text, button2Text, successCallback, errorCallback); + } catch (Throwable e) { + if (errorCallback != null) { + errorCallback.invoke(e.getMessage()); + } + } + } + + @ReactMethod + public void addListener(String eventName) { + // no-op + } + + @ReactMethod + public void removeListeners(double count) { + // no-op + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java b/android/src/main/java/com/microsoft/codepush/react/CodePushDialogImpl.java similarity index 59% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushDialogImpl.java index 66236eafa..e6c01ed61 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java +++ b/android/src/main/java/com/microsoft/codepush/react/CodePushDialogImpl.java @@ -4,43 +4,43 @@ import android.app.AlertDialog; import android.content.DialogInterface; -import com.facebook.react.bridge.BaseJavaModule; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactMethod; -public class CodePushDialog extends BaseJavaModule { +final class CodePushDialogImpl { + private final ReactApplicationContext reactContext; - public CodePushDialog(ReactApplicationContext reactContext) { - super(reactContext); + CodePushDialogImpl(ReactApplicationContext reactContext) { + this.reactContext = reactContext; } - @ReactMethod - public void showDialog(final String title, final String message, final String button1Text, - final String button2Text, final Callback successCallback, Callback errorCallback) { - Activity currentActivity = getReactApplicationContext().getCurrentActivity(); + void showDialog( + final String title, + final String message, + final String button1Text, + final String button2Text, + final Callback successCallback, + final Callback errorCallback + ) { + Activity currentActivity = reactContext.getCurrentActivity(); if (currentActivity == null || currentActivity.isFinishing()) { - // If getCurrentActivity is null, it could be because the app is backgrounded, - // so we show the dialog when the app resumes) - getReactApplicationContext().addLifecycleEventListener(new LifecycleEventListener() { + reactContext.addLifecycleEventListener(new LifecycleEventListener() { @Override public void onHostResume() { - Activity currentActivity = getReactApplicationContext().getCurrentActivity(); - if (currentActivity != null) { - getReactApplicationContext().removeLifecycleEventListener(this); - showDialogInternal(title, message, button1Text, button2Text, successCallback, currentActivity); + Activity resumedActivity = reactContext.getCurrentActivity(); + if (resumedActivity != null && !resumedActivity.isFinishing()) { + reactContext.removeLifecycleEventListener(this); + showDialogInternal(title, message, button1Text, button2Text, successCallback, resumedActivity); } } @Override public void onHostPause() { - } @Override public void onHostDestroy() { - } }); } else { @@ -48,10 +48,15 @@ public void onHostDestroy() { } } - private void showDialogInternal(String title, String message, String button1Text, - String button2Text, final Callback successCallback, Activity currentActivity) { + private void showDialogInternal( + String title, + String message, + String button1Text, + String button2Text, + final Callback successCallback, + Activity currentActivity + ) { AlertDialog.Builder builder = new AlertDialog.Builder(currentActivity); - builder.setCancelable(false); DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { @@ -61,10 +66,10 @@ public void onClick(DialogInterface dialog, int which) { dialog.cancel(); switch (which) { case DialogInterface.BUTTON_POSITIVE: - successCallback.invoke(0); + if (successCallback != null) successCallback.invoke(0); break; case DialogInterface.BUTTON_NEGATIVE: - successCallback.invoke(1); + if (successCallback != null) successCallback.invoke(1); break; default: throw new CodePushUnknownException("Unknown button ID pressed."); @@ -94,9 +99,4 @@ public void onClick(DialogInterface dialog, int which) { AlertDialog dialog = builder.create(); dialog.show(); } - - @Override - public String getName() { - return "CodePushDialog"; - } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushInstallMode.java b/android/src/main/java/com/microsoft/codepush/react/CodePushInstallMode.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushInstallMode.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushInstallMode.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidPublicKeyException.java b/android/src/main/java/com/microsoft/codepush/react/CodePushInvalidPublicKeyException.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidPublicKeyException.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushInvalidPublicKeyException.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java b/android/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java b/android/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java diff --git a/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java new file mode 100644 index 000000000..3725cc98c --- /dev/null +++ b/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java @@ -0,0 +1,136 @@ +package com.microsoft.codepush.react; + +import com.facebook.react.bridge.BaseJavaModule; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; + +import java.util.Map; + +public class CodePushNativeModule extends BaseJavaModule { + public static final String NAME = "CodePush"; + + private final CodePushNativeModuleImpl impl; + + public CodePushNativeModule( + ReactApplicationContext reactContext, + CodePush codePush, + CodePushUpdateManager codePushUpdateManager, + CodePushTelemetryManager codePushTelemetryManager, + SettingsManager settingsManager + ) { + super(reactContext); + impl = new CodePushNativeModuleImpl(reactContext, codePush, codePushUpdateManager, codePushTelemetryManager, settingsManager); + } + + @Override + public Map getConstants() { + return impl.getConstants(); + } + + @Override + public String getName() { + return NAME; + } + + @ReactMethod + public void allow(Promise promise) { + impl.allow(promise); + } + + @ReactMethod + public void clearPendingRestart(Promise promise) { + impl.clearPendingRestart(promise); + } + + @ReactMethod + public void disallow(Promise promise) { + impl.disallow(promise); + } + + @ReactMethod + public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) { + impl.restartApp(onlyIfUpdateIsPending, promise); + } + + @ReactMethod + public void downloadUpdate(ReadableMap updatePackage, boolean notifyProgress, Promise promise) { + impl.downloadUpdate(updatePackage, notifyProgress, promise); + } + + @ReactMethod + public void getConfiguration(Promise promise) { + impl.getConfiguration(promise); + } + + @ReactMethod + public void getUpdateMetadata(double updateState, Promise promise) { + impl.getUpdateMetadata(updateState, promise); + } + + @ReactMethod + public void getNewStatusReport(Promise promise) { + impl.getNewStatusReport(promise); + } + + @ReactMethod + public void installUpdate(ReadableMap updatePackage, double installMode, double minimumBackgroundDuration, Promise promise) { + impl.installUpdate(updatePackage, installMode, minimumBackgroundDuration, promise); + } + + @ReactMethod + public void isFailedUpdate(String packageHash, Promise promise) { + impl.isFailedUpdate(packageHash, promise); + } + + @ReactMethod + public void getLatestRollbackInfo(Promise promise) { + impl.getLatestRollbackInfo(promise); + } + + @ReactMethod + public void setLatestRollbackInfo(String packageHash, Promise promise) { + impl.setLatestRollbackInfo(packageHash, promise); + } + + @ReactMethod + public void isFirstRun(String packageHash, Promise promise) { + impl.isFirstRun(packageHash, promise); + } + + @ReactMethod + public void notifyApplicationReady(Promise promise) { + impl.notifyApplicationReady(promise); + } + + @ReactMethod + public void recordStatusReported(ReadableMap statusReport) { + impl.recordStatusReported(statusReport); + } + + @ReactMethod + public void saveStatusReportForRetry(ReadableMap statusReport) { + impl.saveStatusReportForRetry(statusReport); + } + + @ReactMethod + public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) { + impl.downloadAndReplaceCurrentBundle(remoteBundleUrl); + } + + @ReactMethod + public void clearUpdates() { + impl.clearUpdates(); + } + + @ReactMethod + public void addListener(String eventName) { + impl.addListener(eventName); + } + + @ReactMethod + public void removeListeners(double count) { + impl.removeListeners(count); + } +} diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java similarity index 60% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java index aabae6ec2..69f2de330 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +++ b/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java @@ -5,31 +5,23 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; +import android.view.Choreographer; import android.view.View; -import androidx.annotation.OptIn; - import com.facebook.react.ReactApplication; -import com.facebook.react.ReactHost; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactRootView; import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.BaseJavaModule; import com.facebook.react.bridge.JSBundleLoader; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; -import com.facebook.react.common.annotations.UnstableReactNativeAPI; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.modules.debug.interfaces.DeveloperSettings; -import com.facebook.react.runtime.ReactHostDelegate; -import com.facebook.react.runtime.ReactHostImpl; -import android.view.Choreographer; import org.json.JSONArray; import org.json.JSONException; @@ -45,31 +37,35 @@ import java.util.Map; import java.util.UUID; -@OptIn(markerClass = UnstableReactNativeAPI.class) -public class CodePushNativeModule extends BaseJavaModule { +final class CodePushNativeModuleImpl { private String mBinaryContentsHash = null; private String mClientUniqueId = null; private LifecycleEventListener mLifecycleEventListener = null; private int mMinimumBackgroundDuration = 0; - private CodePush mCodePush; - private SettingsManager mSettingsManager; - private CodePushTelemetryManager mTelemetryManager; - private CodePushUpdateManager mUpdateManager; - - private boolean _allowed = true; - private boolean _restartInProgress = false; - private ArrayList _restartQueue = new ArrayList<>(); - - public CodePushNativeModule(ReactApplicationContext reactContext, CodePush codePush, CodePushUpdateManager codePushUpdateManager, CodePushTelemetryManager codePushTelemetryManager, SettingsManager settingsManager) { - super(reactContext); - + private final ReactApplicationContext reactContext; + private final CodePush mCodePush; + private final SettingsManager mSettingsManager; + private final CodePushTelemetryManager mTelemetryManager; + private final CodePushUpdateManager mUpdateManager; + + private boolean _allowed = true; + private boolean _restartInProgress = false; + private final ArrayList _restartQueue = new ArrayList<>(); + + CodePushNativeModuleImpl( + ReactApplicationContext reactContext, + CodePush codePush, + CodePushUpdateManager codePushUpdateManager, + CodePushTelemetryManager codePushTelemetryManager, + SettingsManager settingsManager + ) { + this.reactContext = reactContext; mCodePush = codePush; mSettingsManager = settingsManager; mTelemetryManager = codePushTelemetryManager; mUpdateManager = codePushUpdateManager; - // Initialize module state while we have a reference to the current context. mBinaryContentsHash = CodePushUpdateUtils.getHashForBinaryContents(reactContext, mCodePush.isDebugMode()); SharedPreferences preferences = codePush.getContext().getSharedPreferences(CodePushConstants.CODE_PUSH_PREFERENCES, 0); @@ -80,8 +76,7 @@ public CodePushNativeModule(ReactApplicationContext reactContext, CodePush codeP } } - @Override - public Map getConstants() { + Map getConstants() { final Map constants = new HashMap<>(); constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue()); @@ -96,16 +91,9 @@ public Map getConstants() { return constants; } - @Override - public String getName() { - return "CodePush"; - } - private void loadBundleLegacy() { - final Activity currentActivity = getReactApplicationContext().getCurrentActivity(); + final Activity currentActivity = reactContext.getCurrentActivity(); if (currentActivity == null) { - // The currentActivity can be null if it is backgrounded / destroyed, so we simply - // no-op to prevent any null pointer exceptions. return; } mCodePush.invalidateCurrentInstance(); @@ -118,13 +106,11 @@ public void run() { }); } - // Use reflection to find and set the appropriate fields on ReactInstanceManager. See #556 for a proposal for a less brittle way - // to approach this. private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBundleFile) throws IllegalAccessException { try { JSBundleLoader latestJSBundleLoader; if (latestJSBundleFile.toLowerCase().startsWith("assets://")) { - latestJSBundleLoader = JSBundleLoader.createAssetLoader(getReactApplicationContext(), latestJSBundleFile, false); + latestJSBundleLoader = JSBundleLoader.createAssetLoader(reactContext, latestJSBundleFile, false); } else { latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile); } @@ -138,131 +124,50 @@ private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBu } } - // Use reflection to find and set the appropriate fields on ReactHostDelegate. See #556 for a proposal for a less brittle way - // to approach this. - private void setJSBundle(ReactHostDelegate reactHostDelegate, String latestJSBundleFile) throws IllegalAccessException { - try { - JSBundleLoader latestJSBundleLoader; - if (latestJSBundleFile.toLowerCase().startsWith("assets://")) { - latestJSBundleLoader = JSBundleLoader.createAssetLoader(getReactApplicationContext(), latestJSBundleFile, false); - } else { - latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile); - } - - Field bundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader"); - bundleLoaderField.setAccessible(true); - bundleLoaderField.set(reactHostDelegate, latestJSBundleLoader); - } catch (Exception e) { - CodePushUtils.log("Unable to set JSBundle of ReactHostDelegate - CodePush may not support this version of React Native"); - throw new IllegalAccessException("Could not setJSBundle"); - } - } - private void loadBundle() { clearLifecycleEventListener(); - // ReactNative core components are changed on new architecture. - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - try { - DevSupportManager devSupportManager = null; - ReactHost reactHost = resolveReactHost(); - if (reactHost != null) { - devSupportManager = reactHost.getDevSupportManager(); - } - boolean isLiveReloadEnabled = isLiveReloadEnabled(devSupportManager); - - mCodePush.clearDebugCacheIfNeeded(isLiveReloadEnabled); - } catch(Exception e) { - // If we got error in out reflection we should clear debug cache anyway. - mCodePush.clearDebugCacheIfNeeded(false); - } - - try { - // #1) Get the ReactHost instance, which is what includes the - // logic to reload the current React context. - final ReactHost reactHost = resolveReactHost(); - if (reactHost == null) { - return; - } - - String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName()); + try { + DevSupportManager devSupportManager = resolveDevSupportManager(); + boolean isLiveReloadEnabled = isLiveReloadEnabled(devSupportManager); + mCodePush.clearDebugCacheIfNeeded(isLiveReloadEnabled); + } catch (Exception e) { + mCodePush.clearDebugCacheIfNeeded(false); + } - // #2) Update the locally stored JS bundle file path - setJSBundle(getReactHostDelegate((ReactHostImpl) reactHost), latestJSBundleFile); + try { + String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName()); + Object reactHost = resolveReactHost(); - // #3) Get the context creation method - try { - reactHost.reload("CodePush triggers reload"); + if (reactHost != null) { + setReactHostBundleLoader(reactHost, latestJSBundleFile); + if (reloadReactHost(reactHost)) { mCodePush.initializeUpdateAfterRestart(); - } catch (Exception e) { - // The recreation method threw an unknown exception - // so just simply fallback to restarting the Activity (if it exists) - loadBundleLegacy(); - } - - } catch (Exception e) { - // Our reflection logic failed somewhere - // so fall back to restarting the Activity (if it exists) - CodePushUtils.log("Failed to load the bundle, falling back to restarting the Activity (if it exists). " + e.getMessage()); - loadBundleLegacy(); - } - - } else { - - try { - DevSupportManager devSupportManager = null; - ReactInstanceManager reactInstanceManager = resolveInstanceManager(); - if (reactInstanceManager != null) { - devSupportManager = reactInstanceManager.getDevSupportManager(); - } - boolean isLiveReloadEnabled = isLiveReloadEnabled(devSupportManager); - - mCodePush.clearDebugCacheIfNeeded(isLiveReloadEnabled); - } catch(Exception e) { - // If we got error in out reflection we should clear debug cache anyway. - mCodePush.clearDebugCacheIfNeeded(false); - } - - try { - // #1) Get the ReactInstanceManager instance, which is what includes the - // logic to reload the current React context. - final ReactInstanceManager instanceManager = resolveInstanceManager(); - if (instanceManager == null) { return; } + } - String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName()); - - // #2) Update the locally stored JS bundle file path - setJSBundle(instanceManager, latestJSBundleFile); - - // #3) Get the context creation method and fire it on the UI thread (which RN enforces) - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - try { - // We don't need to resetReactRootViews anymore - // due the issue https://github.com/facebook/react-native/issues/14533 - // has been fixed in RN 0.46.0 - //resetReactRootViews(instanceManager); - - instanceManager.recreateReactContextInBackground(); - mCodePush.initializeUpdateAfterRestart(); - } catch (Exception e) { - // The recreation method threw an unknown exception - // so just simply fallback to restarting the Activity (if it exists) - loadBundleLegacy(); - } - } - }); - - } catch (Exception e) { - // Our reflection logic failed somewhere - // so fall back to restarting the Activity (if it exists) - CodePushUtils.log("Failed to load the bundle, falling back to restarting the Activity (if it exists). " + e.getMessage()); + final ReactInstanceManager instanceManager = resolveInstanceManager(); + if (instanceManager == null) { loadBundleLegacy(); + return; } + setJSBundle(instanceManager, latestJSBundleFile); + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + try { + instanceManager.recreateReactContextInBackground(); + mCodePush.initializeUpdateAfterRestart(); + } catch (Exception e) { + loadBundleLegacy(); + } + } + }); + } catch (Exception e) { + CodePushUtils.log("Failed to load the bundle, falling back to restarting the Activity (if it exists). " + e.getMessage()); + loadBundleLegacy(); } } @@ -284,13 +189,11 @@ private boolean isLiveReloadEnabled(DevSupportManager devSupportManager) { return false; } - // This workaround has been implemented in order to fix https://github.com/facebook/react-native/issues/14533 - // resetReactRootViews allows to call recreateReactContextInBackground without any exceptions - // This fix also relates to https://github.com/microsoft/react-native-code-push/issues/878 + @SuppressWarnings("unused") private void resetReactRootViews(ReactInstanceManager instanceManager) throws NoSuchFieldException, IllegalAccessException { Field mAttachedRootViewsField = instanceManager.getClass().getDeclaredField("mAttachedRootViews"); mAttachedRootViewsField.setAccessible(true); - List mAttachedRootViews = (List)mAttachedRootViewsField.get(instanceManager); + List mAttachedRootViews = (List) mAttachedRootViewsField.get(instanceManager); for (ReactRootView reactRootView : mAttachedRootViews) { reactRootView.removeAllViews(); reactRootView.setId(View.NO_ID); @@ -299,44 +202,58 @@ private void resetReactRootViews(ReactInstanceManager instanceManager) throws No } private void clearLifecycleEventListener() { - // Remove LifecycleEventListener to prevent infinite restart loop if (mLifecycleEventListener != null) { - getReactApplicationContext().removeLifecycleEventListener(mLifecycleEventListener); + reactContext.removeLifecycleEventListener(mLifecycleEventListener); mLifecycleEventListener = null; } } - // Use reflection to find the ReactInstanceManager. See #556 for a proposal for a less brittle way to approach this. - private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldException, IllegalAccessException { + private ReactInstanceManager resolveInstanceManager() { ReactInstanceManager instanceManager = CodePush.getReactInstanceManager(); if (instanceManager != null) { return instanceManager; } - final Activity currentActivity = getReactApplicationContext().getCurrentActivity(); + final Activity currentActivity = reactContext.getCurrentActivity(); if (currentActivity == null) { return null; } ReactApplication reactApplication = (ReactApplication) currentActivity.getApplication(); - instanceManager = reactApplication.getReactNativeHost().getReactInstanceManager(); + return reactApplication.getReactNativeHost().getReactInstanceManager(); + } - return instanceManager; + private DevSupportManager resolveDevSupportManager() { + Object reactHost = resolveReactHost(); + if (reactHost != null) { + DevSupportManager devSupportManager = getDevSupportManagerFromReactHost(reactHost); + if (devSupportManager != null) { + return devSupportManager; + } + } + + ReactInstanceManager instanceManager = resolveInstanceManager(); + return instanceManager != null ? instanceManager.getDevSupportManager() : null; } - private ReactHost resolveReactHost() throws NoSuchFieldException, IllegalAccessException { - ReactHost reactHost = CodePush.getReactHost(); + private Object resolveReactHost() { + Object reactHost = CodePush.getReactHost(); if (reactHost != null) { return reactHost; } - final Activity currentActivity = getReactApplicationContext().getCurrentActivity(); + final Activity currentActivity = reactContext.getCurrentActivity(); if (currentActivity == null) { return null; } ReactApplication reactApplication = (ReactApplication) currentActivity.getApplication(); - return reactApplication.getReactHost(); + try { + Method getReactHostMethod = reactApplication.getClass().getMethod("getReactHost"); + return getReactHostMethod.invoke(reactApplication); + } catch (Exception e) { + return null; + } } private void restartAppInternal(boolean onlyIfUpdateIsPending) { @@ -365,8 +282,7 @@ private void restartAppInternal(boolean onlyIfUpdateIsPending) { } } - @ReactMethod - public void allow(Promise promise) { + void allow(Promise promise) { CodePushUtils.log("Re-allowing restarts"); this._allowed = true; @@ -378,37 +294,30 @@ public void allow(Promise promise) { } promise.resolve(null); - return; } - @ReactMethod - public void clearPendingRestart(Promise promise) { + void clearPendingRestart(Promise promise) { this._restartQueue.clear(); promise.resolve(null); - return; } - @ReactMethod - public void disallow(Promise promise) { + void disallow(Promise promise) { CodePushUtils.log("Disallowing restarts"); this._allowed = false; promise.resolve(null); - return; } - @ReactMethod - public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) { + void restartApp(boolean onlyIfUpdateIsPending, Promise promise) { try { restartAppInternal(onlyIfUpdateIsPending); promise.resolve(null); - } catch(CodePushUnknownException e) { + } catch (CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); } } - @ReactMethod - public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) { + void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -426,7 +335,6 @@ public void call(DownloadProgress downloadProgress) { } latestDownloadProgress = downloadProgress; - // If the download is completed, synchronously send the last event. if (latestDownloadProgress.isCompleted()) { dispatchDownloadProgressEvent(); return; @@ -437,7 +345,7 @@ public void call(DownloadProgress downloadProgress) { } hasScheduledNextFrame = true; - getReactApplicationContext().runOnUiQueueThread(new Runnable() { + reactContext.runOnUiQueueThread(new Runnable() { @Override public void run() { ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() { @@ -455,7 +363,7 @@ public void doFrame(long frameTimeNanos) { } public void dispatchDownloadProgressEvent() { - getReactApplicationContext() + reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); } @@ -479,29 +387,26 @@ public void dispatchDownloadProgressEvent() { asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - @ReactMethod - public void getConfiguration(Promise promise) { + void getConfiguration(Promise promise) { try { - WritableMap configMap = Arguments.createMap(); + WritableMap configMap = Arguments.createMap(); configMap.putString("appVersion", mCodePush.getAppVersion()); configMap.putString("clientUniqueId", mClientUniqueId); configMap.putString("deploymentKey", mCodePush.getDeploymentKey()); configMap.putString("serverUrl", mCodePush.getServerUrl()); - // The binary hash may be null in debug builds if (mBinaryContentsHash != null) { configMap.putString(CodePushConstants.PACKAGE_HASH_KEY, mBinaryContentsHash); } promise.resolve(configMap); - } catch(CodePushUnknownException e) { + } catch (CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); } } - @ReactMethod - public void getUpdateMetadata(final int updateState, final Promise promise) { + void getUpdateMetadata(final double updateState, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -520,43 +425,29 @@ protected Void doInBackground(Void... params) { currentUpdateIsPending = mSettingsManager.isPendingUpdate(currentHash); } - if (updateState == CodePushUpdateState.PENDING.getValue() && !currentUpdateIsPending) { - // The caller wanted a pending update - // but there isn't currently one. + int updateStateValue = (int) updateState; + if (updateStateValue == CodePushUpdateState.PENDING.getValue() && !currentUpdateIsPending) { promise.resolve(null); - } else if (updateState == CodePushUpdateState.RUNNING.getValue() && currentUpdateIsPending) { - // The caller wants the running update, but the current - // one is pending, so we need to grab the previous. + } else if (updateStateValue == CodePushUpdateState.RUNNING.getValue() && currentUpdateIsPending) { JSONObject previousPackage = mUpdateManager.getPreviousPackage(); - if (previousPackage == null) { promise.resolve(null); return null; } - promise.resolve(CodePushUtils.convertJsonObjectToWritable(previousPackage)); } else { - // The current package satisfies the request: - // 1) Caller wanted a pending, and there is a pending update - // 2) Caller wanted the running update, and there isn't a pending - // 3) Caller wants the latest update, regardless if it's pending or not if (mCodePush.isRunningBinaryVersion()) { - // This only matters in Debug builds. Since we do not clear "outdated" updates, - // we need to indicate to the JS side that somehow we have a current update on - // disk that is not actually running. CodePushUtils.setJSONValueForKey(currentPackage, "_isDebugOnly", true); } - // Enable differentiating pending vs. non-pending updates CodePushUtils.setJSONValueForKey(currentPackage, "isPending", currentUpdateIsPending); promise.resolve(CodePushUtils.convertJsonObjectToWritable(currentPackage)); } } catch (CodePushMalformedDataException e) { - // We need to recover the app in case 'codepush.json' is corrupted CodePushUtils.log(e.getMessage()); clearUpdates(); promise.resolve(null); - } catch(CodePushUnknownException e) { + } catch (CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); } @@ -568,8 +459,7 @@ protected Void doInBackground(Void... params) { asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - @ReactMethod - public void getNewStatusReport(final Promise promise) { + void getNewStatusReport(final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -613,8 +503,8 @@ protected Void doInBackground(Void... params) { } } - promise.resolve(""); - } catch(CodePushUnknownException e) { + promise.resolve(null); + } catch (CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); } @@ -625,8 +515,7 @@ protected Void doInBackground(Void... params) { asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - @ReactMethod - public void installUpdate(final ReadableMap updatePackage, final int installMode, final int minimumBackgroundDuration, final Promise promise) { + void installUpdate(final ReadableMap updatePackage, final double installMode, final double minimumBackgroundDuration, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -637,23 +526,17 @@ protected Void doInBackground(Void... params) { if (pendingHash == null) { throw new CodePushUnknownException("Update package to be installed has no hash."); } else { - mSettingsManager.savePendingUpdate(pendingHash, /* isLoading */false); + mSettingsManager.savePendingUpdate(pendingHash, /* isLoading */ false); } - if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue() || - // We also add the resume listener if the installMode is IMMEDIATE, because - // if the current activity is backgrounded, we want to reload the bundle when - // it comes back into the foreground. - installMode == CodePushInstallMode.IMMEDIATE.getValue() || - installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) { + int installModeValue = (int) installMode; + if (installModeValue == CodePushInstallMode.ON_NEXT_RESUME.getValue() + || installModeValue == CodePushInstallMode.IMMEDIATE.getValue() + || installModeValue == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) { - // Store the minimum duration on the native module as an instance - // variable instead of relying on a closure below, so that any - // subsequent resume-based installs could override it. - CodePushNativeModule.this.mMinimumBackgroundDuration = minimumBackgroundDuration; + CodePushNativeModuleImpl.this.mMinimumBackgroundDuration = (int) minimumBackgroundDuration; if (mLifecycleEventListener == null) { - // Ensure we do not add the listener twice. mLifecycleEventListener = new LifecycleEventListener() { private Date lastPausedDate = null; private Handler appSuspendHandler = new Handler(Looper.getMainLooper()); @@ -668,12 +551,10 @@ public void run() { @Override public void onHostResume() { appSuspendHandler.removeCallbacks(loadBundleRunnable); - // As of RN 36, the resume handler fires immediately if the app is in - // the foreground, so explicitly wait for it to be backgrounded first if (lastPausedDate != null) { long durationInBackground = (new Date().getTime() - lastPausedDate.getTime()) / 1000; - if (installMode == CodePushInstallMode.IMMEDIATE.getValue() - || durationInBackground >= CodePushNativeModule.this.mMinimumBackgroundDuration) { + if (installModeValue == CodePushInstallMode.IMMEDIATE.getValue() + || durationInBackground >= CodePushNativeModuleImpl.this.mMinimumBackgroundDuration) { CodePushUtils.log("Loading bundle on resume"); restartAppInternal(false); } @@ -682,12 +563,10 @@ public void onHostResume() { @Override public void onHostPause() { - // Save the current time so that when the app is later - // resumed, we can detect how long it was in the background. lastPausedDate = new Date(); - if (installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue() && mSettingsManager.isPendingUpdate(null)) { - appSuspendHandler.postDelayed(loadBundleRunnable, minimumBackgroundDuration * 1000); + if (installModeValue == CodePushInstallMode.ON_NEXT_SUSPEND.getValue() && mSettingsManager.isPendingUpdate(null)) { + appSuspendHandler.postDelayed(loadBundleRunnable, CodePushNativeModuleImpl.this.mMinimumBackgroundDuration * 1000L); } } @@ -696,12 +575,12 @@ public void onHostDestroy() { } }; - getReactApplicationContext().addLifecycleEventListener(mLifecycleEventListener); + reactContext.addLifecycleEventListener(mLifecycleEventListener); } } - promise.resolve(""); - } catch(CodePushUnknownException e) { + promise.resolve(null); + } catch (CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); } @@ -713,8 +592,7 @@ public void onHostDestroy() { asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - @ReactMethod - public void isFailedUpdate(String packageHash, Promise promise) { + void isFailedUpdate(String packageHash, Promise promise) { try { promise.resolve(mSettingsManager.isFailedHash(packageHash)); } catch (CodePushUnknownException e) { @@ -723,8 +601,7 @@ public void isFailedUpdate(String packageHash, Promise promise) { } } - @ReactMethod - public void getLatestRollbackInfo(Promise promise) { + void getLatestRollbackInfo(Promise promise) { try { JSONObject latestRollbackInfo = mSettingsManager.getLatestRollbackInfo(); if (latestRollbackInfo != null) { @@ -738,8 +615,7 @@ public void getLatestRollbackInfo(Promise promise) { } } - @ReactMethod - public void setLatestRollbackInfo(String packageHash, Promise promise) { + void setLatestRollbackInfo(String packageHash, Promise promise) { try { mSettingsManager.setLatestRollbackInfo(packageHash); promise.resolve(null); @@ -749,53 +625,46 @@ public void setLatestRollbackInfo(String packageHash, Promise promise) { } } - @ReactMethod - public void isFirstRun(String packageHash, Promise promise) { + void isFirstRun(String packageHash, Promise promise) { try { boolean isFirstRun = mCodePush.didUpdate() && packageHash != null && packageHash.length() > 0 && packageHash.equals(mUpdateManager.getCurrentPackageHash()); promise.resolve(isFirstRun); - } catch(CodePushUnknownException e) { + } catch (CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); } } - @ReactMethod - public void notifyApplicationReady(Promise promise) { + void notifyApplicationReady(Promise promise) { try { mSettingsManager.removePendingUpdate(); - promise.resolve(""); - } catch(CodePushUnknownException e) { + promise.resolve(null); + } catch (CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); } } - @ReactMethod - public void recordStatusReported(ReadableMap statusReport) { + void recordStatusReported(ReadableMap statusReport) { try { mTelemetryManager.recordStatusReported(statusReport); - } catch(CodePushUnknownException e) { + } catch (CodePushUnknownException e) { CodePushUtils.log(e); } } - @ReactMethod - public void saveStatusReportForRetry(ReadableMap statusReport) { + void saveStatusReportForRetry(ReadableMap statusReport) { try { mTelemetryManager.saveStatusReportForRetry(statusReport); - } catch(CodePushUnknownException e) { + } catch (CodePushUnknownException e) { CodePushUtils.log(e); } } - @ReactMethod - // Replaces the current bundle with the one downloaded from removeBundleUrl. - // It is only to be used during tests. No-ops if the test configuration flag is not set. - public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) { + void downloadAndReplaceCurrentBundle(String remoteBundleUrl) { try { if (mCodePush.isUsingTestConfiguration()) { try { @@ -804,45 +673,113 @@ public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) { throw new CodePushUnknownException("Unable to replace current bundle", e); } } - } catch(CodePushUnknownException | CodePushMalformedDataException e) { + } catch (CodePushUnknownException | CodePushMalformedDataException e) { CodePushUtils.log(e); } } - /** - * This method clears CodePush's downloaded updates. - * It is needed to switch to a different deployment if the current deployment is more recent. - * Note: we don’t recommend to use this method in scenarios other than that (CodePush will call - * this method automatically when needed in other cases) as it could lead to unpredictable - * behavior. - */ - @ReactMethod - public void clearUpdates() { + void clearUpdates() { CodePushUtils.log("Clearing updates."); mCodePush.clearUpdates(); } - @ReactMethod - public void addListener(String eventName) { - // Set up any upstream listeners or background tasks as necessary + void addListener(String eventName) { + // no-op + } + + void removeListeners(double count) { + // no-op + } + + private JSBundleLoader createBundleLoader(String latestJSBundleFile) { + if (latestJSBundleFile.toLowerCase().startsWith("assets://")) { + return JSBundleLoader.createAssetLoader(reactContext, latestJSBundleFile, false); + } + + return JSBundleLoader.createFileLoader(latestJSBundleFile); } - @ReactMethod - public void removeListeners(Integer count) { - // Remove upstream listeners, stop unnecessary background tasks + private DevSupportManager getDevSupportManagerFromReactHost(Object reactHost) { + try { + Method method = reactHost.getClass().getMethod("getDevSupportManager"); + Object devSupportManager = method.invoke(reactHost); + return devSupportManager instanceof DevSupportManager ? (DevSupportManager) devSupportManager : null; + } catch (Exception e) { + return null; + } } - public ReactHostDelegate getReactHostDelegate(ReactHostImpl reactHostImpl) { + private Object getReactHostDelegate(Object reactHost) { try { - Class clazz = reactHostImpl.getClass(); - Field field = clazz.getDeclaredField("mReactHostDelegate"); - field.setAccessible(true); + Method method = reactHost.getClass().getMethod("getReactHostDelegate"); + return method.invoke(reactHost); + } catch (Exception ignored) { + } - // Get the value of the field for the provided instance - return (ReactHostDelegate) field.get(reactHostImpl); - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); + try { + Field field = findField(reactHost.getClass(), "mReactHostDelegate", "reactHostDelegate"); + if (field == null) { + return null; + } + + field.setAccessible(true); + return field.get(reactHost); + } catch (Exception e) { return null; } } + + private void setReactHostBundleLoader(Object reactHost, String latestJSBundleFile) throws IllegalAccessException { + Object reactHostDelegate = getReactHostDelegate(reactHost); + if (reactHostDelegate == null) { + throw new IllegalAccessException("Could not resolve ReactHostDelegate"); + } + + try { + Field bundleLoaderField = findField(reactHostDelegate.getClass(), "jsBundleLoader", "mJsBundleLoader"); + if (bundleLoaderField == null) { + throw new NoSuchFieldException("jsBundleLoader"); + } + + bundleLoaderField.setAccessible(true); + bundleLoaderField.set(reactHostDelegate, createBundleLoader(latestJSBundleFile)); + } catch (Exception e) { + CodePushUtils.log("Unable to set JSBundle of ReactHostDelegate - CodePush may not support this version of React Native"); + throw new IllegalAccessException("Could not setJSBundle"); + } + } + + private boolean reloadReactHost(Object reactHost) { + try { + Method reloadWithReasonMethod = reactHost.getClass().getMethod("reload", String.class); + reloadWithReasonMethod.invoke(reactHost, "CodePush triggers reload"); + return true; + } catch (Exception ignored) { + } + + try { + Method reloadMethod = reactHost.getClass().getMethod("reload"); + reloadMethod.invoke(reactHost); + return true; + } catch (Exception e) { + return false; + } + } + + private Field findField(Class type, String... fieldNames) { + Class currentType = type; + while (currentType != null) { + for (String fieldName : fieldNames) { + try { + return currentType.getDeclaredField(fieldName); + } catch (NoSuchFieldException ignored) { + } + } + + currentType = currentType.getSuperclass(); + } + + return null; + } } + diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNotInitializedException.java b/android/src/main/java/com/microsoft/codepush/react/CodePushNotInitializedException.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushNotInitializedException.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushNotInitializedException.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java b/android/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java b/android/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java b/android/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java b/android/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java b/android/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java b/android/src/main/java/com/microsoft/codepush/react/CodePushUtils.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushUtils.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java b/android/src/main/java/com/microsoft/codepush/react/DownloadProgress.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java rename to android/src/main/java/com/microsoft/codepush/react/DownloadProgress.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgressCallback.java b/android/src/main/java/com/microsoft/codepush/react/DownloadProgressCallback.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/DownloadProgressCallback.java rename to android/src/main/java/com/microsoft/codepush/react/DownloadProgressCallback.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java b/android/src/main/java/com/microsoft/codepush/react/FileUtils.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java rename to android/src/main/java/com/microsoft/codepush/react/FileUtils.java diff --git a/android/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java b/android/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java new file mode 100644 index 000000000..864443732 --- /dev/null +++ b/android/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java @@ -0,0 +1,5 @@ +package com.microsoft.codepush.react; + +public interface ReactHostHolder { + Object getReactHost(); +} diff --git a/android/app/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java b/android/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java rename to android/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/SettingsManager.java b/android/src/main/java/com/microsoft/codepush/react/SettingsManager.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/SettingsManager.java rename to android/src/main/java/com/microsoft/codepush/react/SettingsManager.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java b/android/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java rename to android/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java diff --git a/package-lock.json b/package-lock.json index 28b7ac609..2fd7fc28a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "xcode": "3.0.1" }, "devDependencies": { + "@srcpush/plugin-testing-framework": "0.1.0", "@types/assert": "^1.5.2", "@types/mkdirp": "^1.0.1", "@types/mocha": "^9.0.0", @@ -25,7 +26,6 @@ "@types/q": "^1.5.4", "archiver": "latest", "body-parser": "latest", - "code-push-plugin-testing-framework": "file:./code-push-plugin-testing-framework", "express": "^5.1.0", "mkdirp": "latest", "mocha": "^11.7.2", @@ -39,7 +39,7 @@ }, "code-push-plugin-testing-framework": { "version": "0.0.1", - "dev": true, + "extraneous": true, "license": "MIT", "dependencies": { "@types/uuid": "^8.3.1", @@ -306,6 +306,20 @@ "node": ">=14" } }, + "node_modules/@srcpush/plugin-testing-framework": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@srcpush/plugin-testing-framework/-/plugin-testing-framework-0.1.0.tgz", + "integrity": "sha512-yb4qkTxfc9+WxhJYkcRV8ss3E5eEKUSxLnvqkQfMQU61T/ROQN+g+oLz17Tc0jzTjqj7xDkhrihIAAGeAXGT1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver": "^7.0.1", + "express": "^5.1.0" + }, + "engines": { + "node": ">=18.18" + } + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -344,12 +358,6 @@ "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==", "dev": true }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", - "dev": true - }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -769,12 +777,6 @@ "dev": true, "optional": true }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "dev": true - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1056,15 +1058,6 @@ "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", "license": "MIT" }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1165,10 +1158,6 @@ "yazl": "^2.5.1" } }, - "node_modules/code-push-plugin-testing-framework": { - "resolved": "code-push-plugin-testing-framework", - "link": true - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1334,15 +1323,6 @@ "node": ">= 8" } }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", @@ -2258,12 +2238,6 @@ "node": ">= 0.10" } }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -2511,17 +2485,6 @@ "node": ">= 0.4" } }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "dev": true, - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -2642,7 +2605,6 @@ "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", @@ -2674,22 +2636,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/mocha-junit-reporter": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.2.1.tgz", - "integrity": "sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==", - "dev": true, - "dependencies": { - "debug": "^4.3.4", - "md5": "^2.3.0", - "mkdirp": "^3.0.0", - "strip-ansi": "^6.0.1", - "xml": "^1.0.1" - }, - "peerDependencies": { - "mocha": ">=2.2.5" - } - }, "node_modules/mocha/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -2924,15 +2870,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/pac-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", @@ -3318,220 +3255,6 @@ "node": ">=10.0.0" } }, - "node_modules/replace": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/replace/-/replace-1.2.2.tgz", - "integrity": "sha512-C4EDifm22XZM2b2JOYe6Mhn+lBsLBAvLbK8drfUQLTfD1KYl/n3VaW/CDju0Ny4w3xTtegBpg8YNSpFJPUDSjA==", - "dev": true, - "dependencies": { - "chalk": "2.4.2", - "minimatch": "3.0.5", - "yargs": "^15.3.1" - }, - "bin": { - "replace": "bin/replace.js", - "search": "bin/search.js" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/replace/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/replace/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/replace/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/replace/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/replace/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/replace/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/replace/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/replace/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/replace/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/replace/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/replace/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/replace/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/replace/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3541,12 +3264,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -3790,12 +3507,6 @@ "node": ">= 18" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -4489,7 +4200,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4498,13 +4208,6 @@ "node": ">=4.2.0" } }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT", - "optional": true - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -4536,15 +4239,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -4578,12 +4272,6 @@ "node": ">= 8" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, "node_modules/workerpool": { "version": "9.3.4", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", @@ -4648,12 +4336,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/xml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", - "dev": true - }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", diff --git a/package.json b/package.json index 587530705..809f6eda6 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "@types/mocha": "^9.0.0", "@types/node": "^14.0.27", "@types/q": "^1.5.4", + "@srcpush/plugin-testing-framework": "0.1.0", "archiver": "latest", "body-parser": "latest", - "code-push-plugin-testing-framework": "file:./code-push-plugin-testing-framework", "express": "^5.1.0", "mkdirp": "latest", "mocha": "^11.7.2", @@ -82,4 +82,4 @@ "postunlink": "node node_modules/@srcpush/react-native-code-push/scripts/postunlink/run" } } -} \ No newline at end of file +} diff --git a/react-native.config.js b/react-native.config.js index 386fbf066..a251afec3 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -2,9 +2,10 @@ module.exports = { dependency: { platforms: { android: { + sourceDir: './android', + packageImportPath: 'import com.microsoft.codepush.react.CodePush;', packageInstance: - "CodePush.getInstance(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)", - sourceDir: './android/app', + 'CodePush.getInstance(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)', } } } diff --git a/test/plugin-testing-framework-compat.d.ts b/test/plugin-testing-framework-compat.d.ts new file mode 100644 index 000000000..55dcc7781 --- /dev/null +++ b/test/plugin-testing-framework-compat.d.ts @@ -0,0 +1,191 @@ +declare module "@srcpush/plugin-testing-framework" { + import Q = require("q"); + + namespace Platform { + interface IPlatform { + getName(): string; + getCommandLineFlagName(): string; + getServerUrl(): string; + getEmulatorManager(): IEmulatorManager; + getDefaultDeploymentKey(): string; + } + + interface IEmulatorManager { + getTargetEmulator(): Q.Promise; + bootEmulator(restartEmulators: boolean): Q.Promise; + launchInstalledApplication(appId: string): Q.Promise; + endRunningApplication(appId: string): Q.Promise; + restartApplication(appId: string): Q.Promise; + resumeApplication(appId: string, delayBeforeResumingMs?: number): Q.Promise; + prepareEmulatorForTest(appId: string): Q.Promise; + uninstallApplication(appId: string): Q.Promise; + } + + class Android implements IPlatform { + constructor(emulatorManager: IEmulatorManager); + getName(): string; + getCommandLineFlagName(): string; + getServerUrl(): string; + getEmulatorManager(): IEmulatorManager; + getDefaultDeploymentKey(): string; + } + + class IOS implements IPlatform { + constructor(emulatorManager: IEmulatorManager); + getName(): string; + getCommandLineFlagName(): string; + getServerUrl(): string; + getEmulatorManager(): IEmulatorManager; + getDefaultDeploymentKey(): string; + } + + class AndroidEmulatorManager implements IEmulatorManager { + getTargetEmulator(): Q.Promise; + bootEmulator(restartEmulators: boolean): Q.Promise; + launchInstalledApplication(appId: string): Q.Promise; + endRunningApplication(appId: string): Q.Promise; + restartApplication(appId: string): Q.Promise; + resumeApplication(appId: string, delayBeforeResumingMs?: number): Q.Promise; + prepareEmulatorForTest(appId: string): Q.Promise; + uninstallApplication(appId: string): Q.Promise; + } + + class IOSEmulatorManager implements IEmulatorManager { + getTargetEmulator(): Q.Promise; + bootEmulator(restartEmulators: boolean): Q.Promise; + launchInstalledApplication(appId: string): Q.Promise; + endRunningApplication(appId: string): Q.Promise; + restartApplication(appId: string): Q.Promise; + resumeApplication(appId: string, delayBeforeResumingMs?: number): Q.Promise; + prepareEmulatorForTest(appId: string): Q.Promise; + uninstallApplication(appId: string): Q.Promise; + } + } + + namespace PluginTestingFramework { + function initializeTests( + projectManager: ProjectManager, + supportedTargetPlatforms: Platform.IPlatform[], + describeTests: (projectManager: ProjectManager, targetPlatform: Platform.IPlatform) => void + ): void; + } + + class ProjectManager { + static DEFAULT_APP_VERSION: string; + getPluginName(): string; + setupProject(projectDirectory: string, templatePath: string, appName: string, appNamespace: string, version?: string): Q.Promise; + setupScenario(projectDirectory: string, appId: string, templatePath: string, jsPath: string, targetPlatform: Platform.IPlatform, version?: string): Q.Promise; + createUpdateArchive(projectDirectory: string, targetPlatform: Platform.IPlatform, isDiff?: boolean): Q.Promise; + preparePlatform(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise; + cleanupAfterPlatform(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise; + runApplication(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise; + } + + function setupTestRunScenario(projectManager: ProjectManager, targetPlatform: Platform.IPlatform, scenarioJsPath: string, version?: string): Q.Promise; + function setupUpdateScenario(projectManager: ProjectManager, targetPlatform: Platform.IPlatform, scenarioJsPath: string, version: string): Q.Promise; + + namespace ServerUtil { + let server: any; + let updateResponse: any; + let testMessageResponse: any; + let testMessageCallback: (requestBody: any) => void; + let updateCheckCallback: (requestBody: any) => void; + let updatePackagePath: string; + + function setupServer(targetPlatform: Platform.IPlatform): void; + function cleanupServer(): void; + function createDefaultResponse(): any; + function createUpdateResponse(mandatory?: boolean, targetPlatform?: Platform.IPlatform, randomHash?: boolean): any; + function expectTestMessages(expectedMessages: any[]): Q.Promise; + + class TestMessage { + static CHECK_UP_TO_DATE: string; + static CHECK_UPDATE_AVAILABLE: string; + static CHECK_ERROR: string; + static DOWNLOAD_SUCCEEDED: string; + static DOWNLOAD_ERROR: string; + static UPDATE_INSTALLED: string; + static INSTALL_ERROR: string; + static DEVICE_READY_AFTER_UPDATE: string; + static UPDATE_FAILED_PREVIOUSLY: string; + static NOTIFY_APP_READY_SUCCESS: string; + static NOTIFY_APP_READY_FAILURE: string; + static SKIPPED_NOTIFY_APPLICATION_READY: string; + static SYNC_STATUS: string; + static RESTART_SUCCEEDED: string; + static RESTART_FAILED: string; + static PENDING_PACKAGE: string; + static CURRENT_PACKAGE: string; + static SYNC_UP_TO_DATE: number; + static SYNC_UPDATE_INSTALLED: number; + static SYNC_UPDATE_IGNORED: number; + static SYNC_ERROR: number; + static SYNC_IN_PROGRESS: number; + static SYNC_CHECKING_FOR_UPDATE: number; + static SYNC_AWAITING_USER_ACTION: number; + static SYNC_DOWNLOADING_PACKAGE: number; + static SYNC_INSTALLING_UPDATE: number; + } + + class TestMessageResponse { + static SKIP_NOTIFY_APPLICATION_READY: string; + } + + class AppMessage { + message: string; + args: any[]; + constructor(message: string, args: any[]); + static fromString(message: string): AppMessage; + } + } + + class TestBuilder { + static describe: { + (description: string, spec: () => void, scenarioPath?: string): void; + only(description: string, spec: () => void, scenarioPath?: string): void; + skip(description: string, spec: () => void, scenarioPath?: string): void; + }; + static it: { + (expectation: string, isCoreTest: boolean, assertion: (done: Mocha.Done) => void): void; + only(expectation: string, isCoreTest: boolean, assertion: (done: Mocha.Done) => void): void; + skip(expectation: string, isCoreTest: boolean, assertion: (done: Mocha.Done) => void): void; + }; + } + + namespace TestConfig { + const TestAppName: string; + const TestNamespace: string; + const AcquisitionSDKPluginName: string; + const templatePath: string; + const thisPluginInstallString: string; + const testRunDirectory: string; + const updatesDirectory: string; + const onlyRunCoreTests: boolean; + const shouldSetup: boolean; + const restartEmulators: boolean; + const isOldArchitecture: boolean; + } + + class TestUtil { + static ANDROID_KEY_PLACEHOLDER: string; + static IOS_KEY_PLACEHOLDER: string; + static SERVER_URL_PLACEHOLDER: string; + static INDEX_JS_PLACEHOLDER: string; + static CODE_PUSH_APP_VERSION_PLACEHOLDER: string; + static CODE_PUSH_TEST_APP_NAME_PLACEHOLDER: string; + static CODE_PUSH_APP_ID_PLACEHOLDER: string; + static PLUGIN_VERSION_PLACEHOLDER: string; + + static readMochaCommandLineOption(optionName: string, defaultValue?: string): string; + static readMochaCommandLineFlag(optionName: string): boolean; + static getProcessOutput(command: string, options?: any): Q.Promise; + static getPluginName(): string; + static getPluginVersion(): string; + static replaceString(filePath: string, regex: string, replacement: string): void; + static copyFile(source: string, destination: string, overwrite: boolean): Q.Promise; + static archiveFolder(sourceFolder: string, targetFolder: string, archivePath: string, isDiff: boolean): Q.Promise; + static resolveBooleanVariables(variable: string | undefined): boolean; + } + + export { Platform, PluginTestingFramework, ProjectManager, setupTestRunScenario, setupUpdateScenario, ServerUtil, TestBuilder, TestConfig, TestUtil }; +} diff --git a/test/test.ts b/test/test.ts index a9c7861b9..e7bdd75e3 100644 --- a/test/test.ts +++ b/test/test.ts @@ -6,12 +6,10 @@ import mkdirp = require("mkdirp"); import path = require("path"); import slash = require("slash"); -import { Platform, PluginTestingFramework, ProjectManager, setupTestRunScenario, setupUpdateScenario, ServerUtil, TestBuilder, TestConfig, TestUtil } from "code-push-plugin-testing-framework"; +import { Platform, PluginTestingFramework, ProjectManager, setupTestRunScenario, setupUpdateScenario, ServerUtil, TestBuilder, TestConfig, TestUtil } from "@srcpush/plugin-testing-framework"; import Q = require("q"); -import {isOldArchitecture} from "code-push-plugin-testing-framework/script/testConfig"; - ////////////////////////////////////////////////////////////////////////////////////////// // Create the platforms to run the tests on. diff --git a/tsconfig.json b/tsconfig.json index be6029c2c..aea1c6257 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "noImplicitAny": true, "noEmitOnError": true, "moduleResolution": "node", + "skipLibCheck": true, "sourceMap": true, "rootDir": "test", "outDir": "bin", @@ -16,4 +17,4 @@ "exclude": [ "Examples" ] -} \ No newline at end of file +}