@revopush/react-native-code-push 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,7 +53,8 @@ We try our best to maintain backwards compatibility of our plugin with previous
53
53
  | 0.76, 0.77, 0.78, 0.79 | v1.0+ *(Support both New and Old Architectures)* |
54
54
  | v0.80 | v1.2 |
55
55
  | Expo sdk 52 | v1.3 |
56
- | v0.81 | v1.5 |
56
+ | v0.81, v0.82 | v1.5 |
57
+ | v0.83, expo 55 | v1.6 |
57
58
 
58
59
 
59
60
  We work hard to respond to new RN releases, but they do occasionally break us. We will update this chart with each RN release, so that users can check to see what our "official" support is.
@@ -18,16 +18,32 @@
18
18
 
19
19
  # Invoked via reflection, when setting js bundle.
20
20
  -keepclassmembers class com.facebook.react.ReactInstanceManager {
21
- private final ** mBundleLoader;
21
+ private ** mBundleLoader;
22
+ private ** mAttachedReactRoots;
23
+ private ** mAttachedRootViews;
22
24
  }
23
25
 
24
26
  -keepclassmembers class com.facebook.react.runtime.ReactHostImpl {
25
- private final ** mReactHostDelegate;
27
+ private ** mReactHostDelegate;
28
+ private ** reactHostDelegate;
26
29
  }
27
30
 
28
31
  -keep interface com.facebook.react.runtime.ReactHostDelegate { *; }
29
32
 
30
33
  -keep class * implements com.facebook.react.runtime.ReactHostDelegate { *; }
31
34
 
35
+ -keepclassmembers class * implements com.facebook.react.runtime.ReactHostDelegate {
36
+ private ** jsBundleLoader;
37
+ private ** _jsBundleLoader;
38
+ }
39
+
40
+ -keepclassmembers interface com.facebook.react.ReactRoot {
41
+ public ** getRootViewGroup();
42
+ }
43
+
44
+ -keepclassmembers class * implements com.facebook.react.ReactRoot {
45
+ public ** getRootViewGroup();
46
+ }
47
+
32
48
  # Can't find referenced class org.bouncycastle.**
33
49
  -dontwarn com.nimbusds.jose.**
@@ -41,7 +41,7 @@ public class CodePush implements ReactPackage {
41
41
 
42
42
  private boolean mDidUpdate = false;
43
43
 
44
- private String mAssetsBundleFileName;
44
+ private String mAssetsBundleFileName = CodePushConstants.DEFAULT_JS_BUNDLE_NAME;
45
45
 
46
46
  // Helper classes.
47
47
  private CodePushUpdateManager mUpdateManager;
@@ -5,15 +5,15 @@ import android.content.SharedPreferences;
5
5
  import android.os.AsyncTask;
6
6
  import android.os.Handler;
7
7
  import android.os.Looper;
8
- import android.view.View;
9
8
  import android.view.Choreographer;
9
+ import android.view.View;
10
+ import android.view.ViewGroup;
10
11
 
11
12
  import androidx.annotation.OptIn;
12
13
 
13
14
  import com.facebook.react.ReactApplication;
14
15
  import com.facebook.react.ReactHost;
15
16
  import com.facebook.react.ReactInstanceManager;
16
- import com.facebook.react.ReactRootView;
17
17
  import com.facebook.react.bridge.Arguments;
18
18
  import com.facebook.react.bridge.BaseJavaModule;
19
19
  import com.facebook.react.bridge.JSBundleLoader;
@@ -41,7 +41,6 @@ import java.lang.reflect.Method;
41
41
  import java.util.ArrayList;
42
42
  import java.util.Date;
43
43
  import java.util.HashMap;
44
- import java.util.List;
45
44
  import java.util.Map;
46
45
  import java.util.UUID;
47
46
 
@@ -56,6 +55,7 @@ public class CodePushNativeModule extends BaseJavaModule {
56
55
  private SettingsManager mSettingsManager;
57
56
  private CodePushTelemetryManager mTelemetryManager;
58
57
  private CodePushUpdateManager mUpdateManager;
58
+ private boolean mIsExpoApp = false;
59
59
 
60
60
  private boolean _allowed = true;
61
61
  private boolean _restartInProgress = false;
@@ -68,6 +68,7 @@ public class CodePushNativeModule extends BaseJavaModule {
68
68
  mSettingsManager = settingsManager;
69
69
  mTelemetryManager = codePushTelemetryManager;
70
70
  mUpdateManager = codePushUpdateManager;
71
+ mIsExpoApp = detectExpoEnvironment();
71
72
 
72
73
  // Initialize module state while we have a reference to the current context.
73
74
  mBinaryContentsHash = CodePushUpdateUtils.getHashForBinaryContents(reactContext, mCodePush.isDebugMode());
@@ -118,6 +119,31 @@ public class CodePushNativeModule extends BaseJavaModule {
118
119
  });
119
120
  }
120
121
 
122
+ private boolean detectExpoEnvironment() {
123
+ try {
124
+ Class.forName("expo.modules.ReactNativeHostWrapper");
125
+ return true;
126
+ } catch (ClassNotFoundException e) {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ private Field findField(Class<?> clazz, String... fieldNames) throws NoSuchFieldException {
132
+ Class<?> currentClass = clazz;
133
+ while (currentClass != null) {
134
+ for (String fieldName : fieldNames) {
135
+ try {
136
+ return currentClass.getDeclaredField(fieldName);
137
+ } catch (NoSuchFieldException ignored) {
138
+ }
139
+ }
140
+
141
+ currentClass = currentClass.getSuperclass();
142
+ }
143
+
144
+ throw new NoSuchFieldException(fieldNames.length > 0 ? fieldNames[0] : "");
145
+ }
146
+
121
147
  // Use reflection to find and set the appropriate fields on ReactInstanceManager. See #556 for a proposal for a less brittle way
122
148
  // to approach this.
123
149
  private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBundleFile) throws IllegalAccessException {
@@ -129,7 +155,7 @@ public class CodePushNativeModule extends BaseJavaModule {
129
155
  latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
130
156
  }
131
157
 
132
- Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
158
+ Field bundleLoaderField = findField(instanceManager.getClass(), "mBundleLoader");
133
159
  bundleLoaderField.setAccessible(true);
134
160
  bundleLoaderField.set(instanceManager, latestJSBundleLoader);
135
161
  } catch (Exception e) {
@@ -149,14 +175,10 @@ public class CodePushNativeModule extends BaseJavaModule {
149
175
  latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
150
176
  }
151
177
 
152
- Field bundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader");
178
+ Field bundleLoaderField = findField(reactHostDelegate.getClass(), "jsBundleLoader", "_jsBundleLoader");
153
179
  bundleLoaderField.setAccessible(true);
154
180
  bundleLoaderField.set(reactHostDelegate, latestJSBundleLoader);
155
- }
156
- catch (NoSuchFieldException noSuchFileFound) {
157
- // Ignore this error for Expo
158
- }
159
- catch (Exception e) {
181
+ } catch (Exception e) {
160
182
  CodePushUtils.log("Unable to set JSBundle of ReactHostDelegate - CodePush may not support this version of React Native");
161
183
  throw new IllegalAccessException("Could not setJSBundle");
162
184
  }
@@ -193,7 +215,7 @@ public class CodePushNativeModule extends BaseJavaModule {
193
215
  String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName());
194
216
 
195
217
  try {
196
- if (reactHost instanceof ReactHostImpl) {
218
+ if (!mIsExpoApp && reactHost instanceof ReactHostImpl) {
197
219
  ReactHostDelegate delegate = getReactHostDelegate((ReactHostImpl) reactHost);
198
220
  if (delegate != null) {
199
221
  // #2) Update the locally stored JS bundle file path
@@ -201,7 +223,9 @@ public class CodePushNativeModule extends BaseJavaModule {
201
223
  }
202
224
  }
203
225
  } catch (Exception e) {
204
- CodePushUtils.log("Exception setJSBundle: " + e.getMessage());
226
+ if (!mIsExpoApp) {
227
+ CodePushUtils.log("Exception setJSBundle: " + e.getMessage());
228
+ }
205
229
  }
206
230
 
207
231
  // #3) Get the context creation method
@@ -302,14 +326,38 @@ public class CodePushNativeModule extends BaseJavaModule {
302
326
  // resetReactRootViews allows to call recreateReactContextInBackground without any exceptions
303
327
  // This fix also relates to https://github.com/microsoft/react-native-code-push/issues/878
304
328
  private void resetReactRootViews(ReactInstanceManager instanceManager) throws NoSuchFieldException, IllegalAccessException {
305
- Field mAttachedRootViewsField = instanceManager.getClass().getDeclaredField("mAttachedRootViews");
306
- mAttachedRootViewsField.setAccessible(true);
307
- List<ReactRootView> mAttachedRootViews = (List<ReactRootView>)mAttachedRootViewsField.get(instanceManager);
308
- for (ReactRootView reactRootView : mAttachedRootViews) {
309
- reactRootView.removeAllViews();
310
- reactRootView.setId(View.NO_ID);
329
+ Field attachedRootsField = findField(instanceManager.getClass(), "mAttachedReactRoots", "mAttachedRootViews");
330
+ attachedRootsField.setAccessible(true);
331
+ Object attachedRoots = attachedRootsField.get(instanceManager);
332
+ if (attachedRoots instanceof Iterable) {
333
+ for (Object reactRoot : (Iterable<?>) attachedRoots) {
334
+ resetReactRootView(reactRoot);
335
+ }
336
+ }
337
+ }
338
+
339
+ private void resetReactRootView(Object reactRoot) {
340
+ try {
341
+ View rootView = null;
342
+ if (reactRoot instanceof View) {
343
+ rootView = (View) reactRoot;
344
+ } else {
345
+ Method getRootViewGroupMethod = reactRoot.getClass().getMethod("getRootViewGroup");
346
+ Object rootViewGroup = getRootViewGroupMethod.invoke(reactRoot);
347
+ if (rootViewGroup instanceof View) {
348
+ rootView = (View) rootViewGroup;
349
+ }
350
+ }
351
+
352
+ if (rootView instanceof ViewGroup) {
353
+ ((ViewGroup) rootView).removeAllViews();
354
+ }
355
+ if (rootView != null) {
356
+ rootView.setId(View.NO_ID);
357
+ }
358
+ } catch (Exception e) {
359
+ CodePushUtils.log("Failed to reset root view: " + e.getMessage());
311
360
  }
312
- mAttachedRootViewsField.set(instanceManager, mAttachedRootViews);
313
361
  }
314
362
 
315
363
  private void clearLifecycleEventListener() {
@@ -689,7 +737,12 @@ public class CodePushNativeModule extends BaseJavaModule {
689
737
  if (installMode == CodePushInstallMode.IMMEDIATE.getValue()
690
738
  || durationInBackground >= CodePushNativeModule.this.mMinimumBackgroundDuration) {
691
739
  CodePushUtils.log("Loading bundle on resume");
692
- restartAppInternal(false);
740
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
741
+ @Override
742
+ public void run() {
743
+ restartAppInternal(false);
744
+ }
745
+ });
693
746
  }
694
747
  }
695
748
  }
@@ -848,15 +901,7 @@ public class CodePushNativeModule extends BaseJavaModule {
848
901
 
849
902
  public ReactHostDelegate getReactHostDelegate(ReactHostImpl reactHostImpl) {
850
903
  try {
851
- Class<?> clazz = reactHostImpl.getClass();
852
- Field field;
853
- try {
854
- // RN < 0.81
855
- field = clazz.getDeclaredField("mReactHostDelegate");
856
- } catch (NoSuchFieldException e) {
857
- // RN >= 0.81
858
- field = clazz.getDeclaredField("reactHostDelegate");
859
- }
904
+ Field field = findField(reactHostImpl.getClass(), "mReactHostDelegate", "reactHostDelegate");
860
905
  field.setAccessible(true);
861
906
 
862
907
  // Get the value of the field for the provided instance
@@ -18,7 +18,7 @@ In order to integrate CodePush into your Android project, please perform the fol
18
18
 
19
19
  2. Update the `MainApplication` file to use CodePush via the following changes:
20
20
 
21
- For React Native 0.76 and above: update the `MainApplication.kt`
21
+ For React Native 0.76 - 0.82: update the `MainApplication.kt`
22
22
 
23
23
  **Important! : PackageList must be instantiated only one in application lifetime.**
24
24
 
@@ -45,6 +45,32 @@ In order to integrate CodePush into your Android project, please perform the fol
45
45
  }
46
46
  ```
47
47
 
48
+ For React Native 0.83 and above: update the `MainApplication.kt`
49
+
50
+ ```kotlin
51
+ ...
52
+ // 1. Import the plugin class.
53
+ import com.microsoft.codepush.react.CodePush
54
+
55
+ class MainApplication : Application(), ReactApplication {
56
+
57
+ override val reactHost: ReactHost by lazy {
58
+ getDefaultReactHost(
59
+ context = applicationContext,
60
+ packageList =
61
+ PackageList(this).packages.apply {
62
+ // Packages that cannot be autolinked yet can be added manually here, for example:
63
+ // add(MyReactNativePackage())
64
+ },
65
+ // 2. Override the jsBundleFilePath in order to let
66
+ // the CodePush runtime determine where to get the JS
67
+ // bundle location from on each app start
68
+ jsBundleFilePath = CodePush.getJSBundleFile(),
69
+ )
70
+ }
71
+ }
72
+ ```
73
+
48
74
 
49
75
  3. Add the Deployment key to `strings.xml`:
50
76
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  #import "CodePush.h"
19
19
 
20
- @interface CodePush () <RCTBridgeModule, RCTFrameUpdateObserver>
20
+ @interface CodePush () <RCTBridgeModule>
21
21
  @end
22
22
 
23
23
  @implementation CodePush {
@@ -30,8 +30,8 @@
30
30
 
31
31
  // Used to coordinate the dispatching of download progress events to JS.
32
32
  long long _latestExpectedContentLength;
33
- long long _latestReceivedConentLength;
34
- BOOL _didUpdateProgress;
33
+ long long _latestReceivedContentLength;
34
+ NSTimeInterval _lastProgressEmitTimestamp;
35
35
 
36
36
  BOOL _allowed;
37
37
  BOOL _restartInProgress;
@@ -255,18 +255,6 @@ static NSString *const LatestRollbackCountKey = @"count";
255
255
  #pragma mark - Private API methods
256
256
 
257
257
  @synthesize methodQueue = _methodQueue;
258
- @synthesize pauseCallback = _pauseCallback;
259
- @synthesize paused = _paused;
260
-
261
- - (void)setPaused:(BOOL)paused
262
- {
263
- if (_paused != paused) {
264
- _paused = paused;
265
- if (_pauseCallback) {
266
- _pauseCallback();
267
- }
268
- }
269
- }
270
258
 
271
259
  /*
272
260
  * This method is used to clear updates that are installed
@@ -332,7 +320,7 @@ static NSString *const LatestRollbackCountKey = @"count";
332
320
  @"totalBytes" : [NSNumber
333
321
  numberWithLongLong:_latestExpectedContentLength],
334
322
  @"receivedBytes" : [NSNumber
335
- numberWithLongLong:_latestReceivedConentLength]
323
+ numberWithLongLong:_latestReceivedContentLength]
336
324
  }];
337
325
  }
338
326
 
@@ -396,7 +384,6 @@ static NSString *const LatestRollbackCountKey = @"count";
396
384
  #ifdef DEBUG
397
385
  [self clearDebugUpdates];
398
386
  #endif
399
- self.paused = YES;
400
387
  NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
401
388
  NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];
402
389
  if (pendingUpdate) {
@@ -538,7 +525,7 @@ static NSString *const LatestRollbackCountKey = @"count";
538
525
  // file (since Chrome wouldn't support it). Otherwise, update
539
526
  // the current bundle URL to point at the latest update
540
527
  if ([CodePush isUsingTestConfiguration] || ![super.bridge.bundleURL.scheme hasPrefix:@"http"]) {
541
- [super.bridge setValue:[CodePush bundleURL] forKey:@"bundleURL"];
528
+ RCTReloadCommandSetBundleURL([CodePush bundleURL]);
542
529
  }
543
530
 
544
531
  RCTTriggerReloadCommandListeners(@"react-native-code-push: Restart");
@@ -719,10 +706,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
719
706
  }
720
707
 
721
708
  if (notifyProgress) {
722
- // Set up and unpause the frame observer so that it can emit
723
- // progress events every frame if the progress is updated.
724
- _didUpdateProgress = NO;
725
- self.paused = NO;
709
+ _lastProgressEmitTimestamp = 0;
726
710
  }
727
711
 
728
712
  NSString * publicKey = [[CodePushConfig current] publicKey];
@@ -734,17 +718,21 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
734
718
  operationQueue:_methodQueue
735
719
  // The download is progressing forward
736
720
  progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
737
- // Update the download progress so that the frame observer can notify the JS side
721
+ if (!notifyProgress) {
722
+ return;
723
+ }
724
+
738
725
  _latestExpectedContentLength = expectedContentLength;
739
- _latestReceivedConentLength = receivedContentLength;
740
- _didUpdateProgress = YES;
726
+ _latestReceivedContentLength = receivedContentLength;
741
727
 
742
- // If the download is completed, stop observing frame
743
- // updates and synchronously send the last event.
744
728
  if (expectedContentLength == receivedContentLength) {
745
- _didUpdateProgress = NO;
746
- self.paused = YES;
747
729
  [self dispatchDownloadProgressEvent];
730
+ } else {
731
+ NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
732
+ if (timestamp - _lastProgressEmitTimestamp > 0.3) {
733
+ _lastProgressEmitTimestamp = timestamp;
734
+ [self dispatchDownloadProgressEvent];
735
+ }
748
736
  }
749
737
  }
750
738
  // The download completed
@@ -763,9 +751,6 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
763
751
  [self saveFailedUpdate:mutableUpdatePackage];
764
752
  }
765
753
 
766
- // Stop observing frame updates if the download fails.
767
- _didUpdateProgress = NO;
768
- self.paused = YES;
769
754
  reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
770
755
  }];
771
756
  }
@@ -1107,16 +1092,4 @@ RCT_EXPORT_METHOD(saveStatusReportForRetry:(NSDictionary *)statusReport)
1107
1092
  [CodePushTelemetryManager saveStatusReportForRetry:statusReport];
1108
1093
  }
1109
1094
 
1110
- #pragma mark - RCTFrameUpdateObserver Methods
1111
-
1112
- - (void)didUpdateFrame:(RCTFrameUpdate *)update
1113
- {
1114
- if (!_didUpdateProgress) {
1115
- return;
1116
- }
1117
-
1118
- [self dispatchDownloadProgressEvent];
1119
- _didUpdateProgress = NO;
1120
- }
1121
-
1122
1095
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revopush/react-native-code-push",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "React Native plugin for the CodePush service",
5
5
  "main": "CodePush.js",
6
6
  "typings": "typings/react-native-code-push.d.ts",
@@ -65,6 +65,9 @@
65
65
  "tslint": "^6.1.3",
66
66
  "typescript": "^4.4.3"
67
67
  },
68
+ "peerDependencies": {
69
+ "react-native": ">=0.83.0"
70
+ },
68
71
  "rnpm": {
69
72
  "android": {
70
73
  "packageInstance": "new CodePush(getResources().getString(R.string.CodePushDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)"