@revopush/react-native-code-push 2.5.0 → 2.6.0-rc.3

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 ** reactHostDelegate;
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.**
@@ -44,7 +44,7 @@ public class CodePush implements ReactPackage {
44
44
 
45
45
  private boolean mDidUpdate = false;
46
46
 
47
- private String mAssetsBundleFileName;
47
+ private String mAssetsBundleFileName = CodePushConstants.DEFAULT_JS_BUNDLE_NAME;
48
48
 
49
49
  // Helper classes.
50
50
  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;
@@ -43,7 +43,6 @@ import java.lang.reflect.Method;
43
43
  import java.util.ArrayList;
44
44
  import java.util.Date;
45
45
  import java.util.HashMap;
46
- import java.util.List;
47
46
  import java.util.Map;
48
47
  import java.util.UUID;
49
48
 
@@ -58,6 +57,7 @@ public class CodePushNativeModule extends BaseJavaModule {
58
57
  private SettingsManager mSettingsManager;
59
58
  private CodePushTelemetryManager mTelemetryManager;
60
59
  private CodePushUpdateManager mUpdateManager;
60
+ private boolean mIsExpoApp = false;
61
61
 
62
62
  private boolean _allowed = true;
63
63
  private boolean _restartInProgress = false;
@@ -70,6 +70,8 @@ public class CodePushNativeModule extends BaseJavaModule {
70
70
  mSettingsManager = settingsManager;
71
71
  mTelemetryManager = codePushTelemetryManager;
72
72
  mUpdateManager = codePushUpdateManager;
73
+ mIsExpoApp = detectExpoEnvironment();
74
+
73
75
  // Initialize module state while we have a reference to the current context.
74
76
 
75
77
  mNativeBundleHash = bundleMetadataManager.getNativeBundleHash();
@@ -119,6 +121,31 @@ public class CodePushNativeModule extends BaseJavaModule {
119
121
  });
120
122
  }
121
123
 
124
+ private boolean detectExpoEnvironment() {
125
+ try {
126
+ Class.forName("expo.modules.ReactNativeHostWrapper");
127
+ return true;
128
+ } catch (ClassNotFoundException e) {
129
+ return false;
130
+ }
131
+ }
132
+
133
+ private Field findField(Class<?> clazz, String... fieldNames) throws NoSuchFieldException {
134
+ Class<?> currentClass = clazz;
135
+ while (currentClass != null) {
136
+ for (String fieldName : fieldNames) {
137
+ try {
138
+ return currentClass.getDeclaredField(fieldName);
139
+ } catch (NoSuchFieldException ignored) {
140
+ }
141
+ }
142
+
143
+ currentClass = currentClass.getSuperclass();
144
+ }
145
+
146
+ throw new NoSuchFieldException(fieldNames.length > 0 ? fieldNames[0] : "");
147
+ }
148
+
122
149
  // Use reflection to find and set the appropriate fields on ReactInstanceManager. See #556 for a proposal for a less brittle way
123
150
  // to approach this.
124
151
  private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBundleFile) throws IllegalAccessException {
@@ -130,7 +157,7 @@ public class CodePushNativeModule extends BaseJavaModule {
130
157
  latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
131
158
  }
132
159
 
133
- Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
160
+ Field bundleLoaderField = findField(instanceManager.getClass(), "mBundleLoader");
134
161
  bundleLoaderField.setAccessible(true);
135
162
  bundleLoaderField.set(instanceManager, latestJSBundleLoader);
136
163
  } catch (Exception e) {
@@ -150,12 +177,10 @@ public class CodePushNativeModule extends BaseJavaModule {
150
177
  latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
151
178
  }
152
179
 
153
- Field bundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader");
180
+ Field bundleLoaderField = findField(reactHostDelegate.getClass(), "jsBundleLoader", "_jsBundleLoader");
154
181
  bundleLoaderField.setAccessible(true);
155
182
  bundleLoaderField.set(reactHostDelegate, latestJSBundleLoader);
156
- } catch (NoSuchFieldException noSuchFileFound) {
157
- // Ignore this error for Expo
158
- } catch (Exception e) {
183
+ } catch (Exception e) {
159
184
  CodePushUtils.log("Unable to set JSBundle of ReactHostDelegate - CodePush may not support this version of React Native");
160
185
  throw new IllegalAccessException("Could not setJSBundle");
161
186
  }
@@ -192,7 +217,7 @@ public class CodePushNativeModule extends BaseJavaModule {
192
217
  String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName());
193
218
 
194
219
  try {
195
- if (reactHost instanceof ReactHostImpl) {
220
+ if (!mIsExpoApp && reactHost instanceof ReactHostImpl) {
196
221
  ReactHostDelegate delegate = getReactHostDelegate((ReactHostImpl) reactHost);
197
222
  if (delegate != null) {
198
223
  // #2) Update the locally stored JS bundle file path
@@ -200,7 +225,9 @@ public class CodePushNativeModule extends BaseJavaModule {
200
225
  }
201
226
  }
202
227
  } catch (Exception e) {
203
- CodePushUtils.log("Exception setJSBundle: " + e.getMessage());
228
+ if (!mIsExpoApp) {
229
+ CodePushUtils.log("Exception setJSBundle: " + e.getMessage());
230
+ }
204
231
  }
205
232
 
206
233
  // #3) Get the context creation method
@@ -301,14 +328,38 @@ public class CodePushNativeModule extends BaseJavaModule {
301
328
  // resetReactRootViews allows to call recreateReactContextInBackground without any exceptions
302
329
  // This fix also relates to https://github.com/microsoft/react-native-code-push/issues/878
303
330
  private void resetReactRootViews(ReactInstanceManager instanceManager) throws NoSuchFieldException, IllegalAccessException {
304
- Field mAttachedRootViewsField = instanceManager.getClass().getDeclaredField("mAttachedRootViews");
305
- mAttachedRootViewsField.setAccessible(true);
306
- List<ReactRootView> mAttachedRootViews = (List<ReactRootView>) mAttachedRootViewsField.get(instanceManager);
307
- for (ReactRootView reactRootView : mAttachedRootViews) {
308
- reactRootView.removeAllViews();
309
- reactRootView.setId(View.NO_ID);
331
+ Field attachedRootsField = findField(instanceManager.getClass(), "mAttachedReactRoots", "mAttachedRootViews");
332
+ attachedRootsField.setAccessible(true);
333
+ Object attachedRoots = attachedRootsField.get(instanceManager);
334
+ if (attachedRoots instanceof Iterable) {
335
+ for (Object reactRoot : (Iterable<?>) attachedRoots) {
336
+ resetReactRootView(reactRoot);
337
+ }
338
+ }
339
+ }
340
+
341
+ private void resetReactRootView(Object reactRoot) {
342
+ try {
343
+ View rootView = null;
344
+ if (reactRoot instanceof View) {
345
+ rootView = (View) reactRoot;
346
+ } else {
347
+ Method getRootViewGroupMethod = reactRoot.getClass().getMethod("getRootViewGroup");
348
+ Object rootViewGroup = getRootViewGroupMethod.invoke(reactRoot);
349
+ if (rootViewGroup instanceof View) {
350
+ rootView = (View) rootViewGroup;
351
+ }
352
+ }
353
+
354
+ if (rootView instanceof ViewGroup) {
355
+ ((ViewGroup) rootView).removeAllViews();
356
+ }
357
+ if (rootView != null) {
358
+ rootView.setId(View.NO_ID);
359
+ }
360
+ } catch (Exception e) {
361
+ CodePushUtils.log("Failed to reset root view: " + e.getMessage());
310
362
  }
311
- mAttachedRootViewsField.set(instanceManager, mAttachedRootViews);
312
363
  }
313
364
 
314
365
  private void clearLifecycleEventListener() {
@@ -690,7 +741,12 @@ public class CodePushNativeModule extends BaseJavaModule {
690
741
  if (installMode == CodePushInstallMode.IMMEDIATE.getValue()
691
742
  || durationInBackground >= CodePushNativeModule.this.mMinimumBackgroundDuration) {
692
743
  CodePushUtils.log("Loading bundle on resume");
693
- restartAppInternal(false);
744
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
745
+ @Override
746
+ public void run() {
747
+ restartAppInternal(false);
748
+ }
749
+ });
694
750
  }
695
751
  }
696
752
  }
@@ -849,14 +905,13 @@ public class CodePushNativeModule extends BaseJavaModule {
849
905
 
850
906
  public ReactHostDelegate getReactHostDelegate(ReactHostImpl reactHostImpl) {
851
907
  try {
852
- Class<?> clazz = reactHostImpl.getClass();
853
908
  Field field;
854
909
  try {
855
910
  // RN < 0.81
856
911
  field = clazz.getDeclaredField("mReactHostDelegate");
857
912
  } catch (NoSuchFieldException e) {
858
913
  // RN >= 0.81
859
- field = clazz.getDeclaredField("reactHostDelegate");
914
+ field = findField(reactHostImpl.getClass(), "mReactHostDelegate", "reactHostDelegate");
860
915
  }
861
916
  field.setAccessible(true);
862
917
 
@@ -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
 
@@ -19,7 +19,7 @@
19
19
 
20
20
  #import "CodePush.h"
21
21
 
22
- @interface CodePush () <RCTBridgeModule, RCTFrameUpdateObserver>
22
+ @interface CodePush () <RCTBridgeModule>
23
23
  @end
24
24
 
25
25
  @implementation CodePush {
@@ -32,8 +32,8 @@
32
32
 
33
33
  // Used to coordinate the dispatching of download progress events to JS.
34
34
  long long _latestExpectedContentLength;
35
- long long _latestReceivedConentLength;
36
- BOOL _didUpdateProgress;
35
+ long long _latestReceivedContentLength;
36
+ NSTimeInterval _lastProgressEmitTimestamp;
37
37
 
38
38
  BOOL _allowed;
39
39
  BOOL _restartInProgress;
@@ -263,18 +263,6 @@ static NSString *const LatestRollbackCountKey = @"count";
263
263
  #pragma mark - Private API methods
264
264
 
265
265
  @synthesize methodQueue = _methodQueue;
266
- @synthesize pauseCallback = _pauseCallback;
267
- @synthesize paused = _paused;
268
-
269
- - (void)setPaused:(BOOL)paused
270
- {
271
- if (_paused != paused) {
272
- _paused = paused;
273
- if (_pauseCallback) {
274
- _pauseCallback();
275
- }
276
- }
277
- }
278
266
 
279
267
  /*
280
268
  * This method is used to clear updates that are installed
@@ -340,7 +328,7 @@ static NSString *const LatestRollbackCountKey = @"count";
340
328
  @"totalBytes" : [NSNumber
341
329
  numberWithLongLong:_latestExpectedContentLength],
342
330
  @"receivedBytes" : [NSNumber
343
- numberWithLongLong:_latestReceivedConentLength]
331
+ numberWithLongLong:_latestReceivedContentLength]
344
332
  }];
345
333
  }
346
334
 
@@ -404,7 +392,6 @@ static NSString *const LatestRollbackCountKey = @"count";
404
392
  #ifdef DEBUG
405
393
  [self clearDebugUpdates];
406
394
  #endif
407
- self.paused = YES;
408
395
  NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
409
396
  NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];
410
397
  if (pendingUpdate) {
@@ -546,7 +533,7 @@ static NSString *const LatestRollbackCountKey = @"count";
546
533
  // file (since Chrome wouldn't support it). Otherwise, update
547
534
  // the current bundle URL to point at the latest update
548
535
  if ([CodePush isUsingTestConfiguration] || ![super.bridge.bundleURL.scheme hasPrefix:@"http"]) {
549
- [super.bridge setValue:[CodePush bundleURL] forKey:@"bundleURL"];
536
+ RCTReloadCommandSetBundleURL([CodePush bundleURL]);
550
537
  }
551
538
 
552
539
  RCTTriggerReloadCommandListeners(@"react-native-code-push: Restart");
@@ -727,10 +714,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
727
714
  }
728
715
 
729
716
  if (notifyProgress) {
730
- // Set up and unpause the frame observer so that it can emit
731
- // progress events every frame if the progress is updated.
732
- _didUpdateProgress = NO;
733
- self.paused = NO;
717
+ _lastProgressEmitTimestamp = 0;
734
718
  }
735
719
 
736
720
  NSString * publicKey = [[CodePushConfig current] publicKey];
@@ -742,17 +726,21 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
742
726
  operationQueue:_methodQueue
743
727
  // The download is progressing forward
744
728
  progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
745
- // Update the download progress so that the frame observer can notify the JS side
729
+ if (!notifyProgress) {
730
+ return;
731
+ }
732
+
746
733
  _latestExpectedContentLength = expectedContentLength;
747
- _latestReceivedConentLength = receivedContentLength;
748
- _didUpdateProgress = YES;
734
+ _latestReceivedContentLength = receivedContentLength;
749
735
 
750
- // If the download is completed, stop observing frame
751
- // updates and synchronously send the last event.
752
736
  if (expectedContentLength == receivedContentLength) {
753
- _didUpdateProgress = NO;
754
- self.paused = YES;
755
737
  [self dispatchDownloadProgressEvent];
738
+ } else {
739
+ NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
740
+ if (timestamp - _lastProgressEmitTimestamp > 0.3) {
741
+ _lastProgressEmitTimestamp = timestamp;
742
+ [self dispatchDownloadProgressEvent];
743
+ }
756
744
  }
757
745
  }
758
746
  // The download completed
@@ -773,9 +761,6 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
773
761
  [self saveFailedUpdate:mutableUpdatePackage];
774
762
  }
775
763
 
776
- // Stop observing frame updates if the download fails.
777
- _didUpdateProgress = NO;
778
- self.paused = YES;
779
764
  reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
780
765
  }];
781
766
  }
@@ -1128,16 +1113,4 @@ RCT_EXPORT_METHOD(saveStatusReportForRetry:(NSDictionary *)statusReport)
1128
1113
  [CodePushTelemetryManager saveStatusReportForRetry:statusReport];
1129
1114
  }
1130
1115
 
1131
- #pragma mark - RCTFrameUpdateObserver Methods
1132
-
1133
- - (void)didUpdateFrame:(RCTFrameUpdate *)update
1134
- {
1135
- if (!_didUpdateProgress) {
1136
- return;
1137
- }
1138
-
1139
- [self dispatchDownloadProgressEvent];
1140
- _didUpdateProgress = NO;
1141
- }
1142
-
1143
1116
  @end
@@ -197,16 +197,24 @@ NSString * const IgnoreCodePushMetadata = @".codepushrelease";
197
197
  return AssetsFolderName;
198
198
  }
199
199
 
200
+ + (NSString *)cacheKeyForBinaryAtURL:(NSURL *)binaryBundleUrl
201
+ {
202
+ NSString *bundleModifiedDate = [self modifiedDateStringOfFileAtURL:binaryBundleUrl];
203
+ return [NSString stringWithFormat:@"%@:%@",
204
+ [[NSBundle mainBundle] bundlePath],
205
+ bundleModifiedDate ?: @"unknown"];
206
+ }
207
+
200
208
  + (NSString *)getHashForBinaryContents:(NSURL *)binaryBundleUrl
201
209
  error:(NSError **)error
202
210
  {
203
211
  // Get the cached hash from user preferences if it exists.
204
- NSString *binaryModifiedDate = [self modifiedDateStringOfFileAtURL:binaryBundleUrl];
212
+ NSString *binaryCacheKey = [self cacheKeyForBinaryAtURL:binaryBundleUrl];
205
213
  NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
206
214
  NSMutableDictionary *binaryHashDictionary = [preferences objectForKey:BinaryHashKey];
207
215
  NSString *binaryHash = nil;
208
216
  if (binaryHashDictionary != nil) {
209
- binaryHash = [binaryHashDictionary objectForKey:binaryModifiedDate];
217
+ binaryHash = [binaryHashDictionary objectForKey:binaryCacheKey];
210
218
  if (binaryHash == nil) {
211
219
  [preferences removeObjectForKey:BinaryHashKey];
212
220
  [preferences synchronize];
@@ -237,9 +245,7 @@ NSString * const IgnoreCodePushMetadata = @".codepushrelease";
237
245
 
238
246
  binaryHash = [self computeFinalHashFromManifest:manifest error:error];
239
247
 
240
- // Cache the hash in user preferences. This assumes that the modified date for the
241
- // JS bundle changes every time a new bundle is generated by the packager.
242
- [binaryHashDictionary setObject:binaryHash forKey:binaryModifiedDate];
248
+ [binaryHashDictionary setObject:binaryHash forKey:binaryCacheKey];
243
249
  [preferences setObject:binaryHashDictionary forKey:BinaryHashKey];
244
250
  [preferences synchronize];
245
251
  return binaryHash;
@@ -255,11 +261,10 @@ NSString * const IgnoreCodePushMetadata = @".codepushrelease";
255
261
  NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
256
262
  NSMutableDictionary *assetsHashDictionary = [preferences objectForKey:BinaryAssetsHashKey];
257
263
 
258
- NSString *assetsModifiedDate = [self modifiedDateStringOfFileAtURL:
259
- [NSURL fileURLWithPath:assetsPath isDirectory:YES]];
264
+ NSString *assetsCacheKey = [self cacheKeyForBinaryAtURL:[CodePush binaryBundleURL]];
260
265
 
261
266
  if (assetsHashDictionary != nil) {
262
- NSString *cached = [assetsHashDictionary objectForKey:assetsModifiedDate];
267
+ NSString *cached = [assetsHashDictionary objectForKey:assetsCacheKey];
263
268
  if (cached != nil) {
264
269
  return cached;
265
270
  } else {
@@ -285,7 +290,7 @@ NSString * const IgnoreCodePushMetadata = @".codepushrelease";
285
290
  return nil;
286
291
  }
287
292
 
288
- [assetsHashDictionary setObject:assetsHash forKey:assetsModifiedDate];
293
+ [assetsHashDictionary setObject:assetsHash forKey:assetsCacheKey];
289
294
  [preferences setObject:assetsHashDictionary forKey:BinaryAssetsHashKey];
290
295
  [preferences synchronize];
291
296
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revopush/react-native-code-push",
3
- "version": "2.5.0",
3
+ "version": "2.6.0-rc.3",
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)"