From df04f5e08a451f7281f7c9d5e76dc1710e110d07 Mon Sep 17 00:00:00 2001 From: Hebert Date: Sat, 28 Feb 2026 13:38:03 -0300 Subject: [PATCH 01/13] feat: Implement new architecture for CodePush module and dialog - Added CodePushDialogImpl to handle dialog display logic. - Introduced CodePushNativeModuleImpl for native module functionality. - Created new CodePushDialog and CodePushNativeModule classes for the new architecture. - Migrated existing dialog and native module implementations to the new structure. - Updated package.json to include codegen configuration for new modules. - Added TypeScript specifications for CodePush and CodePushDialog. - Ensured backward compatibility with old architecture by retaining old classes. --- AlertAdapter.js | 23 +- CodePush.js | 17 +- android/app/build.gradle | 33 +- .../microsoft/codepush/react/CodePush.java | 62 +++- ...ushDialog.java => CodePushDialogImpl.java} | 56 ++-- ...ule.java => CodePushNativeModuleImpl.java} | 292 ++++++------------ .../codepush/react/CodePushDialog.java | 49 +++ .../codepush/react/CodePushNativeModule.java | 143 +++++++++ .../codepush/react/CodePushDialog.java | 49 +++ .../codepush/react/CodePushNativeModule.java | 141 +++++++++ package.json | 10 +- src/specs/NativeCodePush.ts | 56 ++++ src/specs/NativeCodePushDialog.ts | 19 ++ 13 files changed, 701 insertions(+), 249 deletions(-) rename android/app/src/main/java/com/microsoft/codepush/react/{CodePushDialog.java => CodePushDialogImpl.java} (59%) rename android/app/src/main/java/com/microsoft/codepush/react/{CodePushNativeModule.java => CodePushNativeModuleImpl.java} (70%) create mode 100644 android/app/src/newarch/java/com/microsoft/codepush/react/CodePushDialog.java create mode 100644 android/app/src/newarch/java/com/microsoft/codepush/react/CodePushNativeModule.java create mode 100644 android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushDialog.java create mode 100644 android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushNativeModule.java create mode 100644 src/specs/NativeCodePush.ts create mode 100644 src/specs/NativeCodePushDialog.ts 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/android/app/build.gradle b/android/app/build.gradle index 133dd3e36..8c505f44d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,9 @@ apply plugin: "com.android.library" +def safeExtGet(prop, fallback) { + return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} + def isNewArchitectureEnabled() { // To opt-in for the New Architecture, you can either: // - Set `newArchEnabled` to true inside the `gradle.properties` file @@ -22,27 +26,38 @@ def DEFAULT_MIN_SDK_VERSION = 16 android { 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) + buildToolsVersion safeExtGet("buildToolsVersion", DEFAULT_BUILD_TOOLS_VERSION) + + buildFeatures { + buildConfig true + } 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' } - lintOptions { - abortOnError false + sourceSets { + main { + if (IS_NEW_ARCHITECTURE_ENABLED) { + java.srcDirs += ["src/newarch/java"] + } else { + java.srcDirs += ["src/oldarch/java"] + } + } } - defaultConfig { - consumerProguardFiles 'proguard-rules.pro' + lintOptions { + abortOnError false } } dependencies { - implementation "com.facebook.react:react-native:+" + implementation("com.facebook.react:react-android") implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3' } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index df6d6430f..8f60678fb 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -7,10 +7,12 @@ import com.facebook.react.ReactHost; import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactPackage; +import com.facebook.react.BaseReactPackage; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; import com.facebook.react.uimanager.ViewManager; import org.json.JSONException; @@ -18,9 +20,11 @@ import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; -public class CodePush implements ReactPackage { +public class CodePush extends BaseReactPackage { private static final Object LOCK = new Object(); private static volatile CodePush mCurrentInstance; public static CodePush getInstance(String deploymentKey, Context context, boolean isDebugMode) { @@ -422,14 +426,14 @@ 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; + public NativeModule getModule(String name, ReactApplicationContext reactApplicationContext) { + if (CodePushNativeModule.NAME.equals(name)) { + return new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager); + } + if (CodePushDialog.NAME.equals(name)) { + return new CodePushDialog(reactApplicationContext); + } + return null; } // Deprecated in RN v0.47. @@ -441,4 +445,42 @@ public List> createJSModules() { public List createViewManagers(ReactApplicationContext reactApplicationContext) { return new ArrayList<>(); } + + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return new ReactModuleInfoProvider() { + @Override + public Map getReactModuleInfos() { + boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + + Map map = new HashMap<>(); + map.put( + CodePushNativeModule.NAME, + new ReactModuleInfo( + CodePushNativeModule.NAME, + CodePushNativeModule.NAME, + false, + false, + true, + false, + isTurboModule + ) + ); + map.put( + CodePushDialog.NAME, + new ReactModuleInfo( + CodePushDialog.NAME, + CodePushDialog.NAME, + false, + false, + false, + false, + isTurboModule + ) + ); + + return map; + } + }; + } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java b/android/app/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/app/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/app/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/CodePushNativeModule.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java similarity index 70% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java rename to android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java index aabae6ec2..dce599b32 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java @@ -5,6 +5,7 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; +import android.view.Choreographer; import android.view.View; import androidx.annotation.OptIn; @@ -14,12 +15,10 @@ 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; @@ -29,7 +28,6 @@ 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; @@ -46,30 +44,35 @@ 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 +83,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 +98,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 +113,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,13 +131,11 @@ 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); + latestJSBundleLoader = JSBundleLoader.createAssetLoader(reactContext, latestJSBundleFile, false); } else { latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile); } @@ -161,54 +152,42 @@ private void setJSBundle(ReactHostDelegate reactHostDelegate, String latestJSBun 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(); - } + 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. + } catch (Exception e) { 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()); + ReactHostDelegate delegate = getReactHostDelegate((ReactHostImpl) reactHost); + if (delegate != null) { + setJSBundle(delegate, latestJSBundleFile); + } - // #2) Update the locally stored JS bundle file path - setJSBundle(getReactHostDelegate((ReactHostImpl) reactHost), latestJSBundleFile); - - // #3) Get the context creation method try { reactHost.reload("CodePush triggers reload"); 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(); @@ -216,53 +195,35 @@ private void loadBundle() { 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. + } catch (Exception e) { 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()); loadBundleLegacy(); } - } } @@ -284,13 +245,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,29 +258,25 @@ 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 { 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 instanceManager; + return reactApplication.getReactNativeHost().getReactInstanceManager(); } private ReactHost resolveReactHost() throws NoSuchFieldException, IllegalAccessException { @@ -330,7 +285,7 @@ private ReactHost resolveReactHost() throws NoSuchFieldException, IllegalAccessE return reactHost; } - final Activity currentActivity = getReactApplicationContext().getCurrentActivity(); + final Activity currentActivity = reactContext.getCurrentActivity(); if (currentActivity == null) { return null; } @@ -365,8 +320,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 +332,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 +373,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 +383,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 +401,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 +425,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 +463,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 +497,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 +541,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 +553,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 +564,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 +589,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 +601,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 +613,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 +630,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 +639,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 +653,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 +663,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,41 +711,29 @@ 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 } - @ReactMethod - public void removeListeners(Integer count) { - // Remove upstream listeners, stop unnecessary background tasks + void removeListeners(double count) { + // no-op } - public ReactHostDelegate getReactHostDelegate(ReactHostImpl reactHostImpl) { + ReactHostDelegate getReactHostDelegate(ReactHostImpl reactHostImpl) { try { Class clazz = reactHostImpl.getClass(); Field field = clazz.getDeclaredField("mReactHostDelegate"); field.setAccessible(true); - - // Get the value of the field for the provided instance return (ReactHostDelegate) field.get(reactHostImpl); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); @@ -846,3 +741,4 @@ public ReactHostDelegate getReactHostDelegate(ReactHostImpl reactHostImpl) { } } } + diff --git a/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushDialog.java b/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushDialog.java new file mode 100644 index 000000000..0c7af229d --- /dev/null +++ b/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushDialog.java @@ -0,0 +1,49 @@ +package com.microsoft.codepush.react; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.annotations.ReactModule; + +@ReactModule(name = CodePushDialog.NAME) +public class CodePushDialog extends NativeCodePushDialogSpec { + public static final String NAME = "CodePushDialog"; + + private final CodePushDialogImpl impl; + + public CodePushDialog(ReactApplicationContext reactContext) { + super(reactContext); + impl = new CodePushDialogImpl(reactContext); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public void showDialog( + String title, + String message, + String button1Text, + String button2Text, + Callback successCallback, + Callback errorCallback + ) { + try { + impl.showDialog(title, message, button1Text, button2Text, successCallback, errorCallback); + } catch (Throwable e) { + if (errorCallback != null) errorCallback.invoke(e.getMessage()); + } + } + + @Override + public void addListener(String eventName) { + // no-op + } + + @Override + public void removeListeners(double count) { + // no-op + } +} + diff --git a/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushNativeModule.java new file mode 100644 index 000000000..09ad81ce3 --- /dev/null +++ b/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushNativeModule.java @@ -0,0 +1,143 @@ +package com.microsoft.codepush.react; + +import androidx.annotation.NonNull; +import androidx.annotation.OptIn; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.common.annotations.UnstableReactNativeAPI; +import com.facebook.react.module.annotations.ReactModule; + +import java.util.Map; + +@OptIn(markerClass = UnstableReactNativeAPI.class) +@ReactModule(name = CodePushNativeModule.NAME) +public class CodePushNativeModule extends NativeCodePushSpec { + 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); + } + + @NonNull + @Override + public String getName() { + return NAME; + } + + @Override + protected Map getTypedExportedConstants() { + return impl.getConstants(); + } + + @Override + public void allow(Promise promise) { + impl.allow(promise); + } + + @Override + public void clearPendingRestart(Promise promise) { + impl.clearPendingRestart(promise); + } + + @Override + public void disallow(Promise promise) { + impl.disallow(promise); + } + + @Override + public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) { + impl.restartApp(onlyIfUpdateIsPending, promise); + } + + @Override + public void downloadUpdate(ReadableMap updatePackage, boolean notifyProgress, Promise promise) { + impl.downloadUpdate(updatePackage, notifyProgress, promise); + } + + @Override + public void getConfiguration(Promise promise) { + impl.getConfiguration(promise); + } + + @Override + public void getUpdateMetadata(double updateState, Promise promise) { + impl.getUpdateMetadata(updateState, promise); + } + + @Override + public void getNewStatusReport(Promise promise) { + impl.getNewStatusReport(promise); + } + + @Override + public void installUpdate(ReadableMap updatePackage, double installMode, double minimumBackgroundDuration, Promise promise) { + impl.installUpdate(updatePackage, installMode, minimumBackgroundDuration, promise); + } + + @Override + public void isFailedUpdate(String packageHash, Promise promise) { + impl.isFailedUpdate(packageHash, promise); + } + + @Override + public void getLatestRollbackInfo(Promise promise) { + impl.getLatestRollbackInfo(promise); + } + + @Override + public void setLatestRollbackInfo(String packageHash, Promise promise) { + impl.setLatestRollbackInfo(packageHash, promise); + } + + @Override + public void isFirstRun(String packageHash, Promise promise) { + impl.isFirstRun(packageHash, promise); + } + + @Override + public void notifyApplicationReady(Promise promise) { + impl.notifyApplicationReady(promise); + } + + @Override + public void recordStatusReported(ReadableMap statusReport) { + impl.recordStatusReported(statusReport); + } + + @Override + public void saveStatusReportForRetry(ReadableMap statusReport) { + impl.saveStatusReportForRetry(statusReport); + } + + @Override + public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) { + impl.downloadAndReplaceCurrentBundle(remoteBundleUrl); + } + + @Override + public void clearUpdates() { + impl.clearUpdates(); + } + + @Override + public void addListener(String eventName) { + impl.addListener(eventName); + } + + @Override + public void removeListeners(double count) { + impl.removeListeners(count); + } +} + diff --git a/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushDialog.java b/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushDialog.java new file mode 100644 index 000000000..223844a11 --- /dev/null +++ b/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushDialog.java @@ -0,0 +1,49 @@ +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/oldarch/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushNativeModule.java new file mode 100644 index 000000000..138d29ef4 --- /dev/null +++ b/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushNativeModule.java @@ -0,0 +1,141 @@ +package com.microsoft.codepush.react; + +import androidx.annotation.OptIn; + +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 com.facebook.react.common.annotations.UnstableReactNativeAPI; + +import java.util.Map; + +@OptIn(markerClass = UnstableReactNativeAPI.class) +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/package.json b/package.json index 587530705..d82625b95 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,14 @@ "semver": "^7.3.5", "xcode": "3.0.1" }, + "codegenConfig": { + "name": "SrcPushCodePushSpec", + "type": "modules", + "jsSrcsDir": "./src/specs", + "android": { + "javaPackageName": "com.microsoft.codepush.react" + } + }, "devDependencies": { "@types/assert": "^1.5.2", "@types/mkdirp": "^1.0.1", @@ -82,4 +90,4 @@ "postunlink": "node node_modules/@srcpush/react-native-code-push/scripts/postunlink/run" } } -} \ No newline at end of file +} diff --git a/src/specs/NativeCodePush.ts b/src/specs/NativeCodePush.ts new file mode 100644 index 000000000..28dda1c16 --- /dev/null +++ b/src/specs/NativeCodePush.ts @@ -0,0 +1,56 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; +import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'; + +export type CodePushConstants = { + codePushInstallModeImmediate: number; + codePushInstallModeOnNextRestart: number; + codePushInstallModeOnNextResume: number; + codePushInstallModeOnNextSuspend: number; + + codePushUpdateStateRunning: number; + codePushUpdateStatePending: number; + codePushUpdateStateLatest: number; +}; + +export interface Spec extends TurboModule { + getConstants(): CodePushConstants; + + allow(): Promise; + clearPendingRestart(): Promise; + disallow(): Promise; + restartApp(onlyIfUpdateIsPending: boolean): Promise; + + downloadUpdate( + updatePackage: UnsafeObject, + notifyProgress: boolean, + ): Promise; + + getConfiguration(): Promise; + getUpdateMetadata(updateState: number): Promise; + getNewStatusReport(): Promise; + installUpdate( + updatePackage: UnsafeObject, + installMode: number, + minimumBackgroundDuration: number, + ): Promise; + + isFailedUpdate(packageHash: string): Promise; + getLatestRollbackInfo(): Promise; + setLatestRollbackInfo(packageHash: string): Promise; + isFirstRun(packageHash: string): Promise; + + notifyApplicationReady(): Promise; + + recordStatusReported(statusReport: UnsafeObject): void; + saveStatusReportForRetry(statusReport: UnsafeObject): void; + + downloadAndReplaceCurrentBundle(remoteBundleUrl: string): void; + clearUpdates(): void; + + addListener(eventName: string): void; + removeListeners(count: number): void; +} + +export default TurboModuleRegistry.getEnforcing('CodePush'); + diff --git a/src/specs/NativeCodePushDialog.ts b/src/specs/NativeCodePushDialog.ts new file mode 100644 index 000000000..9516ac47d --- /dev/null +++ b/src/specs/NativeCodePushDialog.ts @@ -0,0 +1,19 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + showDialog( + title: string | null, + message: string | null, + button1Text: string | null, + button2Text: string | null, + successCallback: (buttonId: number) => void, + errorCallback: (error: string) => void, + ): void; + + addListener(eventName: string): void; + removeListeners(count: number): void; +} + +export default TurboModuleRegistry.getEnforcing('CodePushDialog'); + From ee9e8266e9dd35c0d18513d26aabe50fedd8800a Mon Sep 17 00:00:00 2001 From: Hebert Date: Sat, 28 Feb 2026 13:50:53 -0300 Subject: [PATCH 02/13] feat: add generated source directory for new architecture in build.gradle --- android/app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/build.gradle b/android/app/build.gradle index 8c505f44d..7b92eaadd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -46,6 +46,7 @@ android { main { if (IS_NEW_ARCHITECTURE_ENABLED) { java.srcDirs += ["src/newarch/java"] + java.srcDirs += ["$buildDir/generated/source/codegen/java"] } else { java.srcDirs += ["src/oldarch/java"] } From 413e7971d42da44b819323c62ca4949cbb4163c0 Mon Sep 17 00:00:00 2001 From: Hebert Date: Sat, 28 Feb 2026 15:51:12 -0300 Subject: [PATCH 03/13] feat: update CodePush package instance initialization for debug flag handling --- react-native.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react-native.config.js b/react-native.config.js index 386fbf066..ce1cd35ac 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -2,8 +2,10 @@ module.exports = { dependency: { platforms: { android: { + packageImportPath: + "import com.microsoft.codepush.react.CodePush;", packageInstance: - "CodePush.getInstance(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)", + "CodePush.getInstance(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), (0 != (getApplicationContext().getApplicationInfo().flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE)))", sourceDir: './android/app', } } From ce9f08cb39d4bd7eda7cc9a40270f46064f9d169 Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Sat, 28 Feb 2026 19:47:40 -0300 Subject: [PATCH 04/13] fix: simplify build.gradle by removing unnecessary sections and retaining essential configurations --- android/build.gradle | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 31a524838..dd2e7d459 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,24 +1,18 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. +apply plugin: 'com.android.library' -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:1.3.0' +android { + namespace "com.microsoft.codepush.react" + compileSdkVersion 33 - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + defaultConfig { + minSdkVersion 21 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" } } -allprojects { - android { - namespace "com.microsoft.codepush.react" - } - repositories { - mavenLocal() - mavenCentral() - } -} +repositories { + mavenLocal() + mavenCentral() +} \ No newline at end of file From 0b4436b5afa65e43625f1d9799302c11977d259b Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Sat, 28 Feb 2026 22:25:38 -0300 Subject: [PATCH 05/13] feat: enhance new architecture support by allowing environment variable for newArchEnabled --- android/app/build.gradle | 2 +- react-native.config.js | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 7b92eaadd..6b4ba22f0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -9,7 +9,7 @@ def isNewArchitectureEnabled() { // - 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" + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" || System.env.ORG_GRADLE_PROJECT_newArchEnabled == "true" } def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled() diff --git a/react-native.config.js b/react-native.config.js index ce1cd35ac..72b7a9dc6 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -2,10 +2,6 @@ module.exports = { dependency: { platforms: { android: { - packageImportPath: - "import com.microsoft.codepush.react.CodePush;", - packageInstance: - "CodePush.getInstance(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), (0 != (getApplicationContext().getApplicationInfo().flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE)))", sourceDir: './android/app', } } From 7dfe1e5f0ffca03d4eefdeae6a5abab6143be531 Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Sat, 28 Feb 2026 23:05:47 -0300 Subject: [PATCH 06/13] feat: update source directories for new architecture code generation in build.gradle --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 6b4ba22f0..656bd5413 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -46,7 +46,7 @@ android { main { if (IS_NEW_ARCHITECTURE_ENABLED) { java.srcDirs += ["src/newarch/java"] - java.srcDirs += ["$buildDir/generated/source/codegen/java"] + java.srcDirs += ["$buildDir/generated/source/codegen/java", "$buildDir/generated/source/codegen/android/app/build/generated/source/codegen/java"] } else { java.srcDirs += ["src/oldarch/java"] } From 628b09bfea02b904889b82b9be2538693e53cfff Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Wed, 25 Mar 2026 14:55:54 -0300 Subject: [PATCH 07/13] feat: refactor CodePush module for new architecture support and update Gradle configurations --- CodePush.podspec | 4 +- .../org.eclipse.buildship.core.prefs | 2 + android/app/build.gradle | 95 ++++--- .../microsoft/codepush/react/CodePush.java | 71 ++---- .../codepush/react/CodePushDialog.java | 50 ++++ .../codepush/react/CodePushNativeModule.java | 136 ++++++++++ .../react/CodePushNativeModuleImpl.java | 237 ++++++++++-------- .../codepush/react/ReactHostHolder.java | 10 +- android/build.gradle | 37 +-- android/gradle.properties | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- package-lock.json | 9 - package.json | 8 - src/specs/NativeCodePush.ts | 56 ----- src/specs/NativeCodePushDialog.ts | 19 -- 15 files changed, 439 insertions(+), 303 deletions(-) create mode 100644 android/app/.settings/org.eclipse.buildship.core.prefs create mode 100644 android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java create mode 100644 android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java delete mode 100644 src/specs/NativeCodePush.ts delete mode 100644 src/specs/NativeCodePushDialog.ts 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/.settings/org.eclipse.buildship.core.prefs b/android/app/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..b1886adb4 --- /dev/null +++ b/android/app/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=.. +eclipse.preferences.version=1 diff --git a/android/app/build.gradle b/android/app/build.gradle index 656bd5413..4df330c0d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,36 +1,72 @@ +import groovy.json.JsonSlurper + apply plugin: "com.android.library" def safeExtGet(prop, fallback) { return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } -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" || System.env.ORG_GRADLE_PROJECT_newArchEnabled == "true" +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 getReactNativeMinorVersion() { + File packageJson = findReactNativePackageJson() + if (packageJson == null) { + return null + } + + def version = new JsonSlurper().parseText(packageJson.text).version + def stableVersion = version.tokenize("-")[0] + def versionParts = stableVersion.tokenize(".") + if (versionParts.size() < 2) { + return null + } + + return versionParts[1].toInteger() } -def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled() +def getReactNativeVersion() { + File packageJson = findReactNativePackageJson() + if (packageJson == null) { + return null + } -if (IS_NEW_ARCHITECTURE_ENABLED) { - apply plugin: "com.facebook.react" + def version = new JsonSlurper().parseText(packageJson.text).version + return version.tokenize("-")[0] } -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 reactNativeMinorVersion = getReactNativeMinorVersion() +def reactNativeVersion = getReactNativeVersion() +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 safeExtGet("compileSdkVersion", DEFAULT_COMPILE_SDK_VERSION) - buildToolsVersion safeExtGet("buildToolsVersion", DEFAULT_BUILD_TOOLS_VERSION) - buildFeatures { - buildConfig true + if (project.android.hasProperty("buildToolsVersion")) { + buildToolsVersion safeExtGet("buildToolsVersion", DEFAULT_BUILD_TOOLS_VERSION) } defaultConfig { @@ -38,19 +74,12 @@ android { 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' + consumerProguardFiles "proguard-rules.pro" } - sourceSets { - main { - if (IS_NEW_ARCHITECTURE_ENABLED) { - java.srcDirs += ["src/newarch/java"] - java.srcDirs += ["$buildDir/generated/source/codegen/java", "$buildDir/generated/source/codegen/android/app/build/generated/source/codegen/java"] - } else { - java.srcDirs += ["src/oldarch/java"] - } - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { @@ -58,7 +87,13 @@ android { } } +repositories { + google() + mavenCentral() + mavenLocal() +} + dependencies { - implementation("com.facebook.react:react-android") - 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/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 8f60678fb..202df16d5 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -5,14 +5,11 @@ import android.content.pm.PackageManager; import android.content.res.Resources; -import com.facebook.react.ReactHost; import com.facebook.react.ReactInstanceManager; -import com.facebook.react.BaseReactPackage; +import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.module.model.ReactModuleInfo; -import com.facebook.react.module.model.ReactModuleInfoProvider; import com.facebook.react.uimanager.ViewManager; import org.json.JSONException; @@ -20,11 +17,9 @@ import java.io.File; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -public class CodePush extends BaseReactPackage { +public class CodePush implements ReactPackage { private static final Object LOCK = new Object(); private static volatile CodePush mCurrentInstance; public static CodePush getInstance(String deploymentKey, Context context, boolean isDebugMode) { @@ -64,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); @@ -410,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; @@ -417,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; } @@ -426,14 +430,11 @@ static ReactHost getReactHost() { } @Override - public NativeModule getModule(String name, ReactApplicationContext reactApplicationContext) { - if (CodePushNativeModule.NAME.equals(name)) { - return new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager); - } - if (CodePushDialog.NAME.equals(name)) { - return new CodePushDialog(reactApplicationContext); - } - return null; + public List createNativeModules(ReactApplicationContext reactApplicationContext) { + 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. @@ -445,42 +446,4 @@ public List> createJSModules() { public List createViewManagers(ReactApplicationContext reactApplicationContext) { return new ArrayList<>(); } - - @Override - public ReactModuleInfoProvider getReactModuleInfoProvider() { - return new ReactModuleInfoProvider() { - @Override - public Map getReactModuleInfos() { - boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - - Map map = new HashMap<>(); - map.put( - CodePushNativeModule.NAME, - new ReactModuleInfo( - CodePushNativeModule.NAME, - CodePushNativeModule.NAME, - false, - false, - true, - false, - isTurboModule - ) - ); - map.put( - CodePushDialog.NAME, - new ReactModuleInfo( - CodePushDialog.NAME, - CodePushDialog.NAME, - false, - false, - false, - false, - isTurboModule - ) - ); - - return map; - } - }; - } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java new file mode 100644 index 000000000..973306e0f --- /dev/null +++ b/android/app/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/CodePushNativeModule.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java new file mode 100644 index 000000000..3725cc98c --- /dev/null +++ b/android/app/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/CodePushNativeModuleImpl.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java index dce599b32..69f2de330 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java @@ -8,10 +8,7 @@ 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; @@ -21,13 +18,10 @@ import com.facebook.react.bridge.ReactApplicationContext; 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 org.json.JSONArray; import org.json.JSONException; @@ -43,7 +37,6 @@ import java.util.Map; import java.util.UUID; -@OptIn(markerClass = UnstableReactNativeAPI.class) final class CodePushNativeModuleImpl { private String mBinaryContentsHash = null; private String mClientUniqueId = null; @@ -131,99 +124,50 @@ private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBu } } - private void setJSBundle(ReactHostDelegate reactHostDelegate, String latestJSBundleFile) throws IllegalAccessException { - try { - JSBundleLoader latestJSBundleLoader; - if (latestJSBundleFile.toLowerCase().startsWith("assets://")) { - latestJSBundleLoader = JSBundleLoader.createAssetLoader(reactContext, latestJSBundleFile, false); - } else { - latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile); - } + private void loadBundle() { + clearLifecycleEventListener(); - Field bundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader"); - bundleLoaderField.setAccessible(true); - bundleLoaderField.set(reactHostDelegate, latestJSBundleLoader); + try { + DevSupportManager devSupportManager = resolveDevSupportManager(); + boolean isLiveReloadEnabled = isLiveReloadEnabled(devSupportManager); + mCodePush.clearDebugCacheIfNeeded(isLiveReloadEnabled); } 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"); + mCodePush.clearDebugCacheIfNeeded(false); } - } - - private void loadBundle() { - clearLifecycleEventListener(); - 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) { - mCodePush.clearDebugCacheIfNeeded(false); - } + try { + String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName()); + Object reactHost = resolveReactHost(); - try { - final ReactHost reactHost = resolveReactHost(); - if (reactHost == null) { + if (reactHost != null) { + setReactHostBundleLoader(reactHost, latestJSBundleFile); + if (reloadReactHost(reactHost)) { + mCodePush.initializeUpdateAfterRestart(); return; } + } - String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName()); - ReactHostDelegate delegate = getReactHostDelegate((ReactHostImpl) reactHost); - if (delegate != null) { - setJSBundle(delegate, latestJSBundleFile); - } - - try { - reactHost.reload("CodePush triggers reload"); - 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()); + final ReactInstanceManager instanceManager = resolveInstanceManager(); + if (instanceManager == null) { 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) { - mCodePush.clearDebugCacheIfNeeded(false); + return; } - try { - final ReactInstanceManager instanceManager = resolveInstanceManager(); - if (instanceManager == null) { - return; - } - - String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName()); - setJSBundle(instanceManager, latestJSBundleFile); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - try { - instanceManager.recreateReactContextInBackground(); - mCodePush.initializeUpdateAfterRestart(); - } catch (Exception e) { - loadBundleLegacy(); - } + 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(); - } + } + }); + } catch (Exception e) { + CodePushUtils.log("Failed to load the bundle, falling back to restarting the Activity (if it exists). " + e.getMessage()); + loadBundleLegacy(); } } @@ -264,7 +208,7 @@ private void clearLifecycleEventListener() { } } - private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldException, IllegalAccessException { + private ReactInstanceManager resolveInstanceManager() { ReactInstanceManager instanceManager = CodePush.getReactInstanceManager(); if (instanceManager != null) { return instanceManager; @@ -279,8 +223,21 @@ private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldExceptio return reactApplication.getReactNativeHost().getReactInstanceManager(); } - private ReactHost resolveReactHost() throws NoSuchFieldException, IllegalAccessException { - ReactHost reactHost = CodePush.getReactHost(); + 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 Object resolveReactHost() { + Object reactHost = CodePush.getReactHost(); if (reactHost != null) { return reactHost; } @@ -291,7 +248,12 @@ private ReactHost resolveReactHost() throws NoSuchFieldException, IllegalAccessE } 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) { @@ -729,16 +691,95 @@ void removeListeners(double count) { // no-op } - ReactHostDelegate getReactHostDelegate(ReactHostImpl reactHostImpl) { + private JSBundleLoader createBundleLoader(String latestJSBundleFile) { + if (latestJSBundleFile.toLowerCase().startsWith("assets://")) { + return JSBundleLoader.createAssetLoader(reactContext, latestJSBundleFile, false); + } + + return JSBundleLoader.createFileLoader(latestJSBundleFile); + } + + 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; + } + } + + private Object getReactHostDelegate(Object reactHost) { try { - Class clazz = reactHostImpl.getClass(); - Field field = clazz.getDeclaredField("mReactHostDelegate"); + Method method = reactHost.getClass().getMethod("getReactHostDelegate"); + return method.invoke(reactHost); + } catch (Exception ignored) { + } + + try { + Field field = findField(reactHost.getClass(), "mReactHostDelegate", "reactHostDelegate"); + if (field == null) { + return null; + } + field.setAccessible(true); - return (ReactHostDelegate) field.get(reactHostImpl); - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); + 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/ReactHostHolder.java b/android/app/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java index a5d54fdfc..864443732 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java @@ -1,11 +1,5 @@ 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 + Object getReactHost(); +} diff --git a/android/build.gradle b/android/build.gradle index dd2e7d459..ba89b3b78 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,18 +1,25 @@ -apply plugin: 'com.android.library' - -android { - namespace "com.microsoft.codepush.react" - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 33 - versionCode 1 - versionName "1.0" +buildscript { + ext { + buildToolsVersion = "35.0.0" + minSdkVersion = 24 + compileSdkVersion = 35 + targetSdkVersion = 35 + kotlinVersion = "1.9.24" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.2.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } } -repositories { - mavenLocal() - mavenCentral() -} \ No newline at end of file +allprojects { + repositories { + google() + mavenCentral() + mavenLocal() + } +} 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/package-lock.json b/package-lock.json index 28b7ac609..3b9796835 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2642,7 +2642,6 @@ "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", @@ -4489,7 +4488,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 +4496,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", diff --git a/package.json b/package.json index d82625b95..7030ff9e9 100644 --- a/package.json +++ b/package.json @@ -49,14 +49,6 @@ "semver": "^7.3.5", "xcode": "3.0.1" }, - "codegenConfig": { - "name": "SrcPushCodePushSpec", - "type": "modules", - "jsSrcsDir": "./src/specs", - "android": { - "javaPackageName": "com.microsoft.codepush.react" - } - }, "devDependencies": { "@types/assert": "^1.5.2", "@types/mkdirp": "^1.0.1", diff --git a/src/specs/NativeCodePush.ts b/src/specs/NativeCodePush.ts deleted file mode 100644 index 28dda1c16..000000000 --- a/src/specs/NativeCodePush.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { TurboModule } from 'react-native'; -import { TurboModuleRegistry } from 'react-native'; -import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'; - -export type CodePushConstants = { - codePushInstallModeImmediate: number; - codePushInstallModeOnNextRestart: number; - codePushInstallModeOnNextResume: number; - codePushInstallModeOnNextSuspend: number; - - codePushUpdateStateRunning: number; - codePushUpdateStatePending: number; - codePushUpdateStateLatest: number; -}; - -export interface Spec extends TurboModule { - getConstants(): CodePushConstants; - - allow(): Promise; - clearPendingRestart(): Promise; - disallow(): Promise; - restartApp(onlyIfUpdateIsPending: boolean): Promise; - - downloadUpdate( - updatePackage: UnsafeObject, - notifyProgress: boolean, - ): Promise; - - getConfiguration(): Promise; - getUpdateMetadata(updateState: number): Promise; - getNewStatusReport(): Promise; - installUpdate( - updatePackage: UnsafeObject, - installMode: number, - minimumBackgroundDuration: number, - ): Promise; - - isFailedUpdate(packageHash: string): Promise; - getLatestRollbackInfo(): Promise; - setLatestRollbackInfo(packageHash: string): Promise; - isFirstRun(packageHash: string): Promise; - - notifyApplicationReady(): Promise; - - recordStatusReported(statusReport: UnsafeObject): void; - saveStatusReportForRetry(statusReport: UnsafeObject): void; - - downloadAndReplaceCurrentBundle(remoteBundleUrl: string): void; - clearUpdates(): void; - - addListener(eventName: string): void; - removeListeners(count: number): void; -} - -export default TurboModuleRegistry.getEnforcing('CodePush'); - diff --git a/src/specs/NativeCodePushDialog.ts b/src/specs/NativeCodePushDialog.ts deleted file mode 100644 index 9516ac47d..000000000 --- a/src/specs/NativeCodePushDialog.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { TurboModule } from 'react-native'; -import { TurboModuleRegistry } from 'react-native'; - -export interface Spec extends TurboModule { - showDialog( - title: string | null, - message: string | null, - button1Text: string | null, - button2Text: string | null, - successCallback: (buttonId: number) => void, - errorCallback: (error: string) => void, - ): void; - - addListener(eventName: string): void; - removeListeners(count: number): void; -} - -export default TurboModuleRegistry.getEnforcing('CodePushDialog'); - From 2c66d000dcb60b9a18f1a8d905d8ee84adef7068 Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Wed, 25 Mar 2026 19:04:28 -0300 Subject: [PATCH 08/13] feat: add package import and instance initialization for CodePush in react-native.config.js --- react-native.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/react-native.config.js b/react-native.config.js index 72b7a9dc6..291bca21f 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -3,6 +3,8 @@ module.exports = { platforms: { android: { sourceDir: './android/app', + packageImportPath: 'import com.microsoft.codepush.react.CodePush;', + packageInstance: 'new CodePush(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)', } } } From d79a40a16df236caca0679f0cf2b922a72ec4cf8 Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Wed, 25 Mar 2026 19:17:26 -0300 Subject: [PATCH 09/13] feat: change CodePush constructor to public for accessibility in new architecture --- .../src/main/java/com/microsoft/codepush/react/CodePush.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 202df16d5..0c614b3e4 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -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()); From 8c8e7324932514bdd9845d6edaf77914a58fb8f5 Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Wed, 25 Mar 2026 19:53:04 -0300 Subject: [PATCH 10/13] feat: Add CodePush update management utilities and enhance file handling - Introduced CodePushUpdateState enum to manage update states. - Implemented CodePushUpdateUtils for hashing, file management, and update verification. - Added FileUtils for directory and file operations, including copying and unzipping. - Created DownloadProgress and DownloadProgressCallback for tracking download progress. - Developed SettingsManager for managing update preferences and rollback information. - Added TLSSocketFactory to support secure socket connections. - Updated react-native.config.js for improved integration with CodePush. --- .npmignore | 2 + .../org.eclipse.buildship.core.prefs | 2 - android/app/build.gradle | 99 ------------ android/app/src/debug/AndroidManifest.xml | 9 -- .../codepush/react/CodePushDialog.java | 49 ------ .../codepush/react/CodePushNativeModule.java | 143 ------------------ .../codepush/react/CodePushDialog.java | 49 ------ .../codepush/react/CodePushNativeModule.java | 141 ----------------- android/build.gradle | 99 ++++++++++-- android/{app => }/proguard-rules.pro | 0 android/settings.gradle | 2 +- .../{app => }/src/main/AndroidManifest.xml | 0 .../microsoft/codepush/react/CodePush.java | 0 .../codepush/react/CodePushBuilder.java | 0 .../codepush/react/CodePushConstants.java | 0 .../codepush/react/CodePushDialog.java | 0 .../codepush/react/CodePushDialogImpl.java | 0 .../codepush/react/CodePushInstallMode.java | 0 .../CodePushInvalidPublicKeyException.java | 0 .../react/CodePushInvalidUpdateException.java | 0 .../react/CodePushMalformedDataException.java | 0 .../codepush/react/CodePushNativeModule.java | 0 .../react/CodePushNativeModuleImpl.java | 0 .../CodePushNotInitializedException.java | 0 .../react/CodePushTelemetryManager.java | 0 .../react/CodePushUnknownException.java | 0 .../codepush/react/CodePushUpdateManager.java | 0 .../codepush/react/CodePushUpdateState.java | 0 .../codepush/react/CodePushUpdateUtils.java | 0 .../codepush/react/CodePushUtils.java | 0 .../codepush/react/DownloadProgress.java | 0 .../react/DownloadProgressCallback.java | 0 .../microsoft/codepush/react/FileUtils.java | 0 .../codepush/react/ReactHostHolder.java | 0 .../codepush/react/ReactInstanceHolder.java | 0 .../codepush/react/SettingsManager.java | 0 .../codepush/react/TLSSocketFactory.java | 0 react-native.config.js | 5 +- 38 files changed, 96 insertions(+), 504 deletions(-) delete mode 100644 android/app/.settings/org.eclipse.buildship.core.prefs delete mode 100644 android/app/build.gradle delete mode 100644 android/app/src/debug/AndroidManifest.xml delete mode 100644 android/app/src/newarch/java/com/microsoft/codepush/react/CodePushDialog.java delete mode 100644 android/app/src/newarch/java/com/microsoft/codepush/react/CodePushNativeModule.java delete mode 100644 android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushDialog.java delete mode 100644 android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushNativeModule.java rename android/{app => }/proguard-rules.pro (100%) rename android/{app => }/src/main/AndroidManifest.xml (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePush.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushBuilder.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushConstants.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushDialog.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushDialogImpl.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushInstallMode.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushInvalidPublicKeyException.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushInvalidUpdateException.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushNotInitializedException.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushTelemetryManager.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/CodePushUtils.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/DownloadProgress.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/DownloadProgressCallback.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/FileUtils.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/ReactInstanceHolder.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/SettingsManager.java (100%) rename android/{app => }/src/main/java/com/microsoft/codepush/react/TLSSocketFactory.java (100%) diff --git a/.npmignore b/.npmignore index d385ceef2..a89898f9e 100644 --- a/.npmignore +++ b/.npmignore @@ -38,6 +38,8 @@ test/ code-push-plugin-testing-framework/ # Android build artifacts and Android Studio bits +android/build +android/app/ android/app/build android/local.properties android/.gradle diff --git a/android/app/.settings/org.eclipse.buildship.core.prefs b/android/app/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index b1886adb4..000000000 --- a/android/app/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir=.. -eclipse.preferences.version=1 diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index 4df330c0d..000000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,99 +0,0 @@ -import groovy.json.JsonSlurper - -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_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 getReactNativeMinorVersion() { - File packageJson = findReactNativePackageJson() - if (packageJson == null) { - return null - } - - def version = new JsonSlurper().parseText(packageJson.text).version - def stableVersion = version.tokenize("-")[0] - def versionParts = stableVersion.tokenize(".") - if (versionParts.size() < 2) { - return null - } - - return versionParts[1].toInteger() -} - -def getReactNativeVersion() { - File packageJson = findReactNativePackageJson() - if (packageJson == null) { - return null - } - - def version = new JsonSlurper().parseText(packageJson.text).version - return version.tokenize("-")[0] -} - -def reactNativeMinorVersion = getReactNativeMinorVersion() -def reactNativeVersion = getReactNativeVersion() -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" - } - - compileSdkVersion safeExtGet("compileSdkVersion", DEFAULT_COMPILE_SDK_VERSION) - - if (project.android.hasProperty("buildToolsVersion")) { - buildToolsVersion safeExtGet("buildToolsVersion", DEFAULT_BUILD_TOOLS_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/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index df32a1faf..000000000 --- a/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushDialog.java b/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushDialog.java deleted file mode 100644 index 0c7af229d..000000000 --- a/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushDialog.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.microsoft.codepush.react; - -import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.module.annotations.ReactModule; - -@ReactModule(name = CodePushDialog.NAME) -public class CodePushDialog extends NativeCodePushDialogSpec { - public static final String NAME = "CodePushDialog"; - - private final CodePushDialogImpl impl; - - public CodePushDialog(ReactApplicationContext reactContext) { - super(reactContext); - impl = new CodePushDialogImpl(reactContext); - } - - @Override - public String getName() { - return NAME; - } - - @Override - public void showDialog( - String title, - String message, - String button1Text, - String button2Text, - Callback successCallback, - Callback errorCallback - ) { - try { - impl.showDialog(title, message, button1Text, button2Text, successCallback, errorCallback); - } catch (Throwable e) { - if (errorCallback != null) errorCallback.invoke(e.getMessage()); - } - } - - @Override - public void addListener(String eventName) { - // no-op - } - - @Override - public void removeListeners(double count) { - // no-op - } -} - diff --git a/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushNativeModule.java deleted file mode 100644 index 09ad81ce3..000000000 --- a/android/app/src/newarch/java/com/microsoft/codepush/react/CodePushNativeModule.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.microsoft.codepush.react; - -import androidx.annotation.NonNull; -import androidx.annotation.OptIn; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.annotations.UnstableReactNativeAPI; -import com.facebook.react.module.annotations.ReactModule; - -import java.util.Map; - -@OptIn(markerClass = UnstableReactNativeAPI.class) -@ReactModule(name = CodePushNativeModule.NAME) -public class CodePushNativeModule extends NativeCodePushSpec { - 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); - } - - @NonNull - @Override - public String getName() { - return NAME; - } - - @Override - protected Map getTypedExportedConstants() { - return impl.getConstants(); - } - - @Override - public void allow(Promise promise) { - impl.allow(promise); - } - - @Override - public void clearPendingRestart(Promise promise) { - impl.clearPendingRestart(promise); - } - - @Override - public void disallow(Promise promise) { - impl.disallow(promise); - } - - @Override - public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) { - impl.restartApp(onlyIfUpdateIsPending, promise); - } - - @Override - public void downloadUpdate(ReadableMap updatePackage, boolean notifyProgress, Promise promise) { - impl.downloadUpdate(updatePackage, notifyProgress, promise); - } - - @Override - public void getConfiguration(Promise promise) { - impl.getConfiguration(promise); - } - - @Override - public void getUpdateMetadata(double updateState, Promise promise) { - impl.getUpdateMetadata(updateState, promise); - } - - @Override - public void getNewStatusReport(Promise promise) { - impl.getNewStatusReport(promise); - } - - @Override - public void installUpdate(ReadableMap updatePackage, double installMode, double minimumBackgroundDuration, Promise promise) { - impl.installUpdate(updatePackage, installMode, minimumBackgroundDuration, promise); - } - - @Override - public void isFailedUpdate(String packageHash, Promise promise) { - impl.isFailedUpdate(packageHash, promise); - } - - @Override - public void getLatestRollbackInfo(Promise promise) { - impl.getLatestRollbackInfo(promise); - } - - @Override - public void setLatestRollbackInfo(String packageHash, Promise promise) { - impl.setLatestRollbackInfo(packageHash, promise); - } - - @Override - public void isFirstRun(String packageHash, Promise promise) { - impl.isFirstRun(packageHash, promise); - } - - @Override - public void notifyApplicationReady(Promise promise) { - impl.notifyApplicationReady(promise); - } - - @Override - public void recordStatusReported(ReadableMap statusReport) { - impl.recordStatusReported(statusReport); - } - - @Override - public void saveStatusReportForRetry(ReadableMap statusReport) { - impl.saveStatusReportForRetry(statusReport); - } - - @Override - public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) { - impl.downloadAndReplaceCurrentBundle(remoteBundleUrl); - } - - @Override - public void clearUpdates() { - impl.clearUpdates(); - } - - @Override - public void addListener(String eventName) { - impl.addListener(eventName); - } - - @Override - public void removeListeners(double count) { - impl.removeListeners(count); - } -} - diff --git a/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushDialog.java b/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushDialog.java deleted file mode 100644 index 223844a11..000000000 --- a/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushDialog.java +++ /dev/null @@ -1,49 +0,0 @@ -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/oldarch/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushNativeModule.java deleted file mode 100644 index 138d29ef4..000000000 --- a/android/app/src/oldarch/java/com/microsoft/codepush/react/CodePushNativeModule.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.microsoft.codepush.react; - -import androidx.annotation.OptIn; - -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 com.facebook.react.common.annotations.UnstableReactNativeAPI; - -import java.util.Map; - -@OptIn(markerClass = UnstableReactNativeAPI.class) -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/build.gradle b/android/build.gradle index ba89b3b78..6bcd77cb9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,9 +1,7 @@ +import groovy.json.JsonSlurper + buildscript { ext { - buildToolsVersion = "35.0.0" - minSdkVersion = 24 - compileSdkVersion = 35 - targetSdkVersion = 35 kotlinVersion = "1.9.24" } repositories { @@ -16,10 +14,93 @@ buildscript { } } -allprojects { - repositories { - google() - mavenCentral() - mavenLocal() +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 + } + + 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 { + if (project.android.hasProperty("namespace")) { + namespace "com.microsoft.codepush.react" + } + + 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/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 index 9d495b34f..4cd20b485 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1 @@ -include ':app' \ No newline at end of file +rootProject.name = "react-native-code-push" diff --git a/android/app/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml similarity index 100% rename from android/app/src/main/AndroidManifest.xml rename to android/src/main/AndroidManifest.xml 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 100% 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 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/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java b/android/src/main/java/com/microsoft/codepush/react/CodePushDialog.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushDialog.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialogImpl.java b/android/src/main/java/com/microsoft/codepush/react/CodePushDialogImpl.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushDialogImpl.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushDialogImpl.java 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/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java b/android/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java rename to android/src/main/java/com/microsoft/codepush/react/CodePushNativeModuleImpl.java 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/app/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java b/android/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java similarity index 100% rename from android/app/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java rename to android/src/main/java/com/microsoft/codepush/react/ReactHostHolder.java 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/react-native.config.js b/react-native.config.js index 291bca21f..a251afec3 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -2,9 +2,10 @@ module.exports = { dependency: { platforms: { android: { - sourceDir: './android/app', + sourceDir: './android', packageImportPath: 'import com.microsoft.codepush.react.CodePush;', - packageInstance: 'new CodePush(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)', + packageInstance: + 'CodePush.getInstance(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)', } } } From 3a1e95a707b1dca1001040efda23f8376ecdca6d Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Wed, 25 Mar 2026 20:03:21 -0300 Subject: [PATCH 11/13] feat: Add Android build configuration and manifest files for new architecture support --- .npmignore | 1 - android/app/build.gradle | 98 +++++++++++++++++++++++ android/app/src/debug/AndroidManifest.xml | 3 + android/app/src/main/AndroidManifest.xml | 2 + android/settings.gradle | 1 - 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 android/app/build.gradle create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml delete mode 100644 android/settings.gradle diff --git a/.npmignore b/.npmignore index a89898f9e..d56a890a0 100644 --- a/.npmignore +++ b/.npmignore @@ -39,7 +39,6 @@ code-push-plugin-testing-framework/ # Android build artifacts and Android Studio bits android/build -android/app/ android/app/build android/local.properties android/.gradle diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 000000000..38f92c399 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,98 @@ +import groovy.json.JsonSlurper + +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 + } + + 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 { + if (project.android.hasProperty("namespace")) { + namespace "com.microsoft.codepush.react" + } + + 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" + } + + sourceSets { + main { + java.srcDirs = ["../src/main/java"] + } + } + + 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/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..fbb2ef838 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..96ba5b66f --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 4cd20b485..000000000 --- a/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "react-native-code-push" From 7cabfe0a9523551d1d6bdbb83dbea791824101c0 Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Wed, 25 Mar 2026 22:42:27 -0300 Subject: [PATCH 12/13] feat: Update testing framework integration and configuration for new architecture support --- .gitignore | 5 +- .npmignore | 3 - package-lock.json | 341 +++------------------------------------------- package.json | 2 +- test/test.ts | 29 ++-- tsconfig.json | 3 +- 6 files changed, 40 insertions(+), 343 deletions(-) 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 d56a890a0..754f0a0a5 100644 --- a/.npmignore +++ b/.npmignore @@ -34,9 +34,6 @@ 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 diff --git a/package-lock.json b/package-lock.json index 3b9796835..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", @@ -2673,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", @@ -2923,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", @@ -3317,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", @@ -3540,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", @@ -3789,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", @@ -4527,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", @@ -4569,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", @@ -4639,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 7030ff9e9..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", diff --git a/test/test.ts b/test/test.ts index a9c7861b9..290d2b282 100644 --- a/test/test.ts +++ b/test/test.ts @@ -6,11 +6,22 @@ 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 PluginTestingFrameworkPackage = require("@srcpush/plugin-testing-framework"); import Q = require("q"); -import {isOldArchitecture} from "code-push-plugin-testing-framework/script/testConfig"; +const Framework: any = PluginTestingFrameworkPackage; +const Platform = Framework.Platform; +const PluginTestingFramework = Framework.PluginTestingFramework; +const ProjectManager = Framework.ProjectManager; +const setupTestRunScenario = Framework.setupTestRunScenario; +const setupUpdateScenario = Framework.setupUpdateScenario; +const ServerUtil = Framework.ServerUtil; +const TestBuilder = Framework.TestBuilder; +const TestConfig = Framework.TestConfig; +const TestUtil = Framework.TestUtil; + +type FrameworkPlatform = any; ////////////////////////////////////////////////////////////////////////////////////////// // Create the platforms to run the tests on. @@ -254,7 +265,7 @@ class RNIOS extends Platform.IOS implements RNPlatform { } } -const supportedTargetPlatforms: Platform.IPlatform[] = [new RNAndroid(), new RNIOS()]; +const supportedTargetPlatforms: FrameworkPlatform[] = [new RNAndroid(), new RNIOS()]; ////////////////////////////////////////////////////////////////////////////////////////// // Create the ProjectManager to use for the tests. @@ -343,7 +354,7 @@ class RNProjectManager extends ProjectManager { /** * Sets up the scenario for a test in an already existing project. */ - public setupScenario(projectDirectory: string, appId: string, templatePath: string, jsPath: string, targetPlatform: Platform.IPlatform, version?: string): Q.Promise { + public setupScenario(projectDirectory: string, appId: string, templatePath: string, jsPath: string, targetPlatform: FrameworkPlatform, version?: string): Q.Promise { // We don't need to anything if it is the current scenario. if (RNProjectManager.currentScenario[projectDirectory] === jsPath) return Q(null); RNProjectManager.currentScenario[projectDirectory] = jsPath; @@ -368,7 +379,7 @@ class RNProjectManager extends ProjectManager { /** * Creates a CodePush update package zip for a project. */ - public createUpdateArchive(projectDirectory: string, targetPlatform: Platform.IPlatform, isDiff?: boolean): Q.Promise { + public createUpdateArchive(projectDirectory: string, targetPlatform: FrameworkPlatform, isDiff?: boolean): Q.Promise { const bundleFolder: string = path.join(projectDirectory, TestConfig.TestAppName, "CodePush/"); const bundleName: string = (targetPlatform).getBundleName(); const bundlePath: string = path.join(bundleFolder, bundleName); @@ -398,7 +409,7 @@ class RNProjectManager extends ProjectManager { /** * Prepares a specific platform for tests. */ - public preparePlatform(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise { + public preparePlatform(projectDirectory: string, targetPlatform: FrameworkPlatform): Q.Promise { const deferred = Q.defer(); const platformsJSONPath = path.join(projectDirectory, RNProjectManager.platformsJSON); @@ -428,7 +439,7 @@ class RNProjectManager extends ProjectManager { /** * Cleans up a specific platform after tests. */ - public cleanupAfterPlatform(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise { + public cleanupAfterPlatform(projectDirectory: string, targetPlatform: FrameworkPlatform): Q.Promise { // Can't uninstall from command line, so noop. return Q(null); } @@ -436,7 +447,7 @@ class RNProjectManager extends ProjectManager { /** * Runs the test app on the given target / platform. */ - public runApplication(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise { + public runApplication(projectDirectory: string, targetPlatform: FrameworkPlatform): Q.Promise { console.log("Running project in " + projectDirectory + " on " + targetPlatform.getName()); return Q(null) @@ -498,7 +509,7 @@ const UpdateNotifyApplicationReadyConditional = "updateNARConditional.js"; // Initialize the tests. PluginTestingFramework.initializeTests(new RNProjectManager(), supportedTargetPlatforms, - (projectManager: ProjectManager, targetPlatform: Platform.IPlatform) => { + (projectManager: any, targetPlatform: FrameworkPlatform) => { TestBuilder.describe("#window.codePush.checkForUpdate", () => { TestBuilder.it("window.codePush.checkForUpdate.noUpdate", false, 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 +} From 8c54e37b38336b48a21fb551bc34d6803b277324 Mon Sep 17 00:00:00 2001 From: hebertcisco Date: Wed, 25 Mar 2026 22:44:46 -0300 Subject: [PATCH 13/13] feat: Refactor test imports and platform handling for improved architecture compatibility --- test/plugin-testing-framework-compat.d.ts | 191 ++++++++++++++++++++++ test/test.ts | 29 +--- 2 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 test/plugin-testing-framework-compat.d.ts 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 290d2b282..e7bdd75e3 100644 --- a/test/test.ts +++ b/test/test.ts @@ -6,23 +6,10 @@ import mkdirp = require("mkdirp"); import path = require("path"); import slash = require("slash"); -import PluginTestingFrameworkPackage = require("@srcpush/plugin-testing-framework"); +import { Platform, PluginTestingFramework, ProjectManager, setupTestRunScenario, setupUpdateScenario, ServerUtil, TestBuilder, TestConfig, TestUtil } from "@srcpush/plugin-testing-framework"; import Q = require("q"); -const Framework: any = PluginTestingFrameworkPackage; -const Platform = Framework.Platform; -const PluginTestingFramework = Framework.PluginTestingFramework; -const ProjectManager = Framework.ProjectManager; -const setupTestRunScenario = Framework.setupTestRunScenario; -const setupUpdateScenario = Framework.setupUpdateScenario; -const ServerUtil = Framework.ServerUtil; -const TestBuilder = Framework.TestBuilder; -const TestConfig = Framework.TestConfig; -const TestUtil = Framework.TestUtil; - -type FrameworkPlatform = any; - ////////////////////////////////////////////////////////////////////////////////////////// // Create the platforms to run the tests on. @@ -265,7 +252,7 @@ class RNIOS extends Platform.IOS implements RNPlatform { } } -const supportedTargetPlatforms: FrameworkPlatform[] = [new RNAndroid(), new RNIOS()]; +const supportedTargetPlatforms: Platform.IPlatform[] = [new RNAndroid(), new RNIOS()]; ////////////////////////////////////////////////////////////////////////////////////////// // Create the ProjectManager to use for the tests. @@ -354,7 +341,7 @@ class RNProjectManager extends ProjectManager { /** * Sets up the scenario for a test in an already existing project. */ - public setupScenario(projectDirectory: string, appId: string, templatePath: string, jsPath: string, targetPlatform: FrameworkPlatform, version?: string): Q.Promise { + public setupScenario(projectDirectory: string, appId: string, templatePath: string, jsPath: string, targetPlatform: Platform.IPlatform, version?: string): Q.Promise { // We don't need to anything if it is the current scenario. if (RNProjectManager.currentScenario[projectDirectory] === jsPath) return Q(null); RNProjectManager.currentScenario[projectDirectory] = jsPath; @@ -379,7 +366,7 @@ class RNProjectManager extends ProjectManager { /** * Creates a CodePush update package zip for a project. */ - public createUpdateArchive(projectDirectory: string, targetPlatform: FrameworkPlatform, isDiff?: boolean): Q.Promise { + public createUpdateArchive(projectDirectory: string, targetPlatform: Platform.IPlatform, isDiff?: boolean): Q.Promise { const bundleFolder: string = path.join(projectDirectory, TestConfig.TestAppName, "CodePush/"); const bundleName: string = (targetPlatform).getBundleName(); const bundlePath: string = path.join(bundleFolder, bundleName); @@ -409,7 +396,7 @@ class RNProjectManager extends ProjectManager { /** * Prepares a specific platform for tests. */ - public preparePlatform(projectDirectory: string, targetPlatform: FrameworkPlatform): Q.Promise { + public preparePlatform(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise { const deferred = Q.defer(); const platformsJSONPath = path.join(projectDirectory, RNProjectManager.platformsJSON); @@ -439,7 +426,7 @@ class RNProjectManager extends ProjectManager { /** * Cleans up a specific platform after tests. */ - public cleanupAfterPlatform(projectDirectory: string, targetPlatform: FrameworkPlatform): Q.Promise { + public cleanupAfterPlatform(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise { // Can't uninstall from command line, so noop. return Q(null); } @@ -447,7 +434,7 @@ class RNProjectManager extends ProjectManager { /** * Runs the test app on the given target / platform. */ - public runApplication(projectDirectory: string, targetPlatform: FrameworkPlatform): Q.Promise { + public runApplication(projectDirectory: string, targetPlatform: Platform.IPlatform): Q.Promise { console.log("Running project in " + projectDirectory + " on " + targetPlatform.getName()); return Q(null) @@ -509,7 +496,7 @@ const UpdateNotifyApplicationReadyConditional = "updateNARConditional.js"; // Initialize the tests. PluginTestingFramework.initializeTests(new RNProjectManager(), supportedTargetPlatforms, - (projectManager: any, targetPlatform: FrameworkPlatform) => { + (projectManager: ProjectManager, targetPlatform: Platform.IPlatform) => { TestBuilder.describe("#window.codePush.checkForUpdate", () => { TestBuilder.it("window.codePush.checkForUpdate.noUpdate", false,