@bravemobile/react-native-code-push 12.3.1 → 12.4.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.
Files changed (80) hide show
  1. package/CodePush.podspec +0 -2
  2. package/README.md +4 -2
  3. package/android/app/build.gradle +0 -1
  4. package/android/app/proguard-rules.pro +5 -0
  5. package/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +0 -35
  6. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +0 -1
  7. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +33 -9
  8. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +1 -28
  9. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +1 -104
  10. package/cli/commands/bundleCommand/bundleCodePush.ts +29 -0
  11. package/cli/commands/bundleCommand/index.ts +3 -0
  12. package/cli/commands/releaseCommand/index.ts +3 -0
  13. package/cli/commands/releaseCommand/release.ts +2 -1
  14. package/cli/dist/commands/bundleCommand/bundleCodePush.js +14 -1
  15. package/cli/dist/commands/bundleCommand/index.js +2 -1
  16. package/cli/dist/commands/releaseCommand/index.js +2 -1
  17. package/cli/dist/commands/releaseCommand/release.js +2 -2
  18. package/cli/dist/utils/hash-utils.js +1 -4
  19. package/cli/utils/hash-utils.ts +1 -4
  20. package/expo/plugin/withCodePushAndroid.js +15 -16
  21. package/ios/CodePush/CodePush.h +0 -16
  22. package/ios/CodePush/CodePush.m +0 -12
  23. package/ios/CodePush/CodePushConfig.m +0 -10
  24. package/ios/CodePush/CodePushPackage.m +0 -39
  25. package/ios/CodePush/CodePushUpdateUtils.m +1 -91
  26. package/ios/CodePush.xcodeproj/project.pbxproj +0 -324
  27. package/package.json +11 -28
  28. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushInvalidPublicKeyException.java +0 -12
  29. package/ios/CodePush/Base64/Base64/MF_Base64Additions.h +0 -34
  30. package/ios/CodePush/Base64/Base64/MF_Base64Additions.m +0 -252
  31. package/ios/CodePush/Base64/README.md +0 -47
  32. package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithm.h +0 -69
  33. package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmFactory.h +0 -16
  34. package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmFactory.m +0 -51
  35. package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmNone.h +0 -15
  36. package/ios/CodePush/JWT/Core/Algorithms/Base/JWTAlgorithmNone.m +0 -55
  37. package/ios/CodePush/JWT/Core/Algorithms/ESFamily/JWTAlgorithmESBase.h +0 -24
  38. package/ios/CodePush/JWT/Core/Algorithms/ESFamily/JWTAlgorithmESBase.m +0 -41
  39. package/ios/CodePush/JWT/Core/Algorithms/HSFamily/JWTAlgorithmHSBase.h +0 -28
  40. package/ios/CodePush/JWT/Core/Algorithms/HSFamily/JWTAlgorithmHSBase.m +0 -205
  41. package/ios/CodePush/JWT/Core/Algorithms/Holders/JWTAlgorithmDataHolder.h +0 -103
  42. package/ios/CodePush/JWT/Core/Algorithms/Holders/JWTAlgorithmDataHolder.m +0 -322
  43. package/ios/CodePush/JWT/Core/Algorithms/Holders/JWTAlgorithmDataHolderChain.h +0 -37
  44. package/ios/CodePush/JWT/Core/Algorithms/Holders/JWTAlgorithmDataHolderChain.m +0 -145
  45. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/JWTAlgorithmRSBase.h +0 -35
  46. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/JWTAlgorithmRSBase.m +0 -551
  47. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/JWTRSAlgorithm.h +0 -23
  48. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoKey.h +0 -43
  49. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoKey.m +0 -230
  50. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoKeyExtractor.h +0 -31
  51. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoKeyExtractor.m +0 -113
  52. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoSecurity.h +0 -38
  53. package/ios/CodePush/JWT/Core/Algorithms/RSFamily/RSKeys/JWTCryptoSecurity.m +0 -500
  54. package/ios/CodePush/JWT/Core/ClaimSet/JWTClaim.h +0 -18
  55. package/ios/CodePush/JWT/Core/ClaimSet/JWTClaim.m +0 -214
  56. package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSet.h +0 -23
  57. package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSet.m +0 -29
  58. package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSetSerializer.h +0 -19
  59. package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSetSerializer.m +0 -68
  60. package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSetVerifier.h +0 -18
  61. package/ios/CodePush/JWT/Core/ClaimSet/JWTClaimsSetVerifier.m +0 -72
  62. package/ios/CodePush/JWT/Core/Coding/JWTCoding+ResultTypes.h +0 -67
  63. package/ios/CodePush/JWT/Core/Coding/JWTCoding+ResultTypes.m +0 -111
  64. package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionOne.h +0 -119
  65. package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionOne.m +0 -307
  66. package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionThree.h +0 -94
  67. package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionThree.m +0 -619
  68. package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionTwo.h +0 -164
  69. package/ios/CodePush/JWT/Core/Coding/JWTCoding+VersionTwo.m +0 -514
  70. package/ios/CodePush/JWT/Core/Coding/JWTCoding.h +0 -24
  71. package/ios/CodePush/JWT/Core/Coding/JWTCoding.m +0 -11
  72. package/ios/CodePush/JWT/Core/FrameworkSupplement/JWT.h +0 -52
  73. package/ios/CodePush/JWT/Core/FrameworkSupplement/Map.modulemap +0 -5
  74. package/ios/CodePush/JWT/Core/Supplement/JWTBase64Coder.h +0 -28
  75. package/ios/CodePush/JWT/Core/Supplement/JWTBase64Coder.m +0 -70
  76. package/ios/CodePush/JWT/Core/Supplement/JWTDeprecations.h +0 -22
  77. package/ios/CodePush/JWT/Core/Supplement/JWTErrorDescription.h +0 -34
  78. package/ios/CodePush/JWT/Core/Supplement/JWTErrorDescription.m +0 -73
  79. package/ios/CodePush/JWT/LICENSE +0 -19
  80. package/ios/CodePush/JWT/README.md +0 -489
package/CodePush.podspec CHANGED
@@ -22,6 +22,4 @@ Pod::Spec.new do |s|
22
22
  # linked properly at a parent workspace level.
23
23
  s.dependency 'React-Core'
24
24
  s.dependency 'SSZipArchive', '~> 2.5.5'
25
- s.dependency 'JWT', '~> 3.0.0-beta.12'
26
- s.dependency 'Base64', '~> 1.1'
27
25
  end
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ### 🚀 New Architecture support
10
10
 
11
- Supports React Native 0.74 ~ 0.83.
11
+ Supports React Native 0.77 ~ 0.84.
12
12
 
13
13
  > [!NOTE]
14
14
  > If you are using React Native 0.76 or lower, please use version `12.0.2` of this library.
@@ -17,7 +17,7 @@ Supports React Native 0.74 ~ 0.83.
17
17
 
18
18
  ### ✅ Requirements
19
19
 
20
- - **React Native**: 0.74 or higher
20
+ - **React Native**: 0.77 or higher
21
21
  - **iOS**: 15.5 or higher
22
22
  - **Android**: API level 16 or higher
23
23
 
@@ -397,6 +397,8 @@ module.exports = Config;
397
397
 
398
398
  > [!TIP]
399
399
  > You can use `--help` command to see the available commands and options.
400
+ >
401
+ > For detailed documentation, see the [CLI README](cli/README.md) ([한국어](cli/README.ko.md)).
400
402
 
401
403
  (interactive mode not supported yet)
402
404
 
@@ -29,5 +29,4 @@ android {
29
29
 
30
30
  dependencies {
31
31
  implementation "com.facebook.react:react-native:+"
32
- implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'
33
32
  }
@@ -25,6 +25,11 @@
25
25
  private ** jsBundleLoader;
26
26
  }
27
27
  # bridgeless
28
+ -keepclassmembers class * extends com.facebook.react.runtime.ReactHostDelegate {
29
+ private ** jsBundleLoader;
30
+ private ** _jsBundleLoader;
31
+ }
32
+ # bridgeless
28
33
  -keepclassmembers class com.facebook.react.runtime.ReactHostImpl {
29
34
  private final ** mReactHostDelegate; # RN < 0.81
30
35
  private final ** reactHostDelegate; # RN 0.81+
@@ -3,10 +3,8 @@ package com.microsoft.codepush.react;
3
3
  import android.content.Context;
4
4
  import android.content.pm.PackageInfo;
5
5
  import android.content.pm.PackageManager;
6
- import android.content.res.Resources;
7
6
 
8
7
  import com.facebook.react.ReactPackage;
9
- import com.facebook.react.bridge.JavaScriptModule;
10
8
  import com.facebook.react.bridge.NativeModule;
11
9
  import com.facebook.react.bridge.ReactApplicationContext;
12
10
  import com.facebook.react.uimanager.ViewManager;
@@ -40,8 +38,6 @@ public class CodePush implements ReactPackage {
40
38
  private Context mContext;
41
39
  private final boolean mIsDebugMode;
42
40
 
43
- private static String mPublicKey;
44
-
45
41
  private static CodePush mCurrentInstance;
46
42
 
47
43
  public static String getServiceUrl() {
@@ -75,33 +71,12 @@ public class CodePush implements ReactPackage {
75
71
 
76
72
  mCurrentInstance = this;
77
73
 
78
- String publicKeyFromStrings = getCustomPropertyFromStringsIfExist("PublicKey");
79
- if (publicKeyFromStrings != null) mPublicKey = publicKeyFromStrings;
80
-
81
74
  String serverUrlFromStrings = getCustomPropertyFromStringsIfExist("ServerUrl");
82
75
  if (serverUrlFromStrings != null) mServerUrl = serverUrlFromStrings;
83
76
 
84
77
  initializeUpdateAfterRestart();
85
78
  }
86
79
 
87
- private String getPublicKeyByResourceDescriptor(int publicKeyResourceDescriptor){
88
- String publicKey;
89
- try {
90
- publicKey = mContext.getString(publicKeyResourceDescriptor);
91
- } catch (Resources.NotFoundException e) {
92
- throw new CodePushInvalidPublicKeyException(
93
- "Unable to get public key, related resource descriptor " +
94
- publicKeyResourceDescriptor +
95
- " can not be found", e
96
- );
97
- }
98
-
99
- if (publicKey.isEmpty()) {
100
- throw new CodePushInvalidPublicKeyException("Specified public key is empty");
101
- }
102
- return publicKey;
103
- }
104
-
105
80
  private String getCustomPropertyFromStringsIfExist(String propertyName) {
106
81
  String property;
107
82
 
@@ -133,10 +108,6 @@ public class CodePush implements ReactPackage {
133
108
  return mAssetsBundleFileName;
134
109
  }
135
110
 
136
- public String getPublicKey() {
137
- return mPublicKey;
138
- }
139
-
140
111
  public String getPackageFolder() {
141
112
  JSONObject codePushLocalPackage = mUpdateManager.getCurrentPackage();
142
113
  if (codePushLocalPackage == null) {
@@ -338,12 +309,6 @@ public class CodePush implements ReactPackage {
338
309
  nativeModules.add(dialogModule);
339
310
  return nativeModules;
340
311
  }
341
-
342
- // Deprecated in RN v0.47.
343
- public List<Class<? extends JavaScriptModule>> createJSModules() {
344
- return new ArrayList<>();
345
- }
346
-
347
312
  @Override
348
313
  public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
349
314
  return new ArrayList<>();
@@ -24,7 +24,6 @@ public class CodePushConstants {
24
24
  public static final String RELATIVE_BUNDLE_PATH_KEY = "bundlePath";
25
25
  public static final String STATUS_FILE = "codepush.json";
26
26
  public static final String UNZIPPED_FOLDER_NAME = "unzipped";
27
- public static final String BUNDLE_JWT_FILE = ".codepushrelease";
28
27
  public static final String LATEST_ROLLBACK_INFO_KEY = "LATEST_ROLLBACK_INFO";
29
28
  public static final String LATEST_ROLLBACK_PACKAGE_HASH_KEY = "packageHash";
30
29
  public static final String LATEST_ROLLBACK_TIME_KEY = "time";
@@ -151,18 +151,31 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
151
151
 
152
152
  @OptIn(markerClass = UnstableReactNativeAPI.class)
153
153
  private void setJSBundleLoaderBridgeless(ReactHost reactHost, JSBundleLoader latestJSBundleLoader) throws NoSuchFieldException, IllegalAccessException {
154
- Field reactHostDelegateField;
155
- try {
156
- // RN < 0.81
157
- reactHostDelegateField = reactHost.getClass().getDeclaredField("mReactHostDelegate");
158
- } catch (NoSuchFieldException e) {
154
+ // RN < 0.81
155
+ Field reactHostDelegateField = resolveDeclaredField(reactHost.getClass(), "mReactHostDelegate");
156
+ if (reactHostDelegateField == null) {
159
157
  // RN >= 0.81
160
- reactHostDelegateField = reactHost.getClass().getDeclaredField("reactHostDelegate");
158
+ reactHostDelegateField = resolveDeclaredField(reactHost.getClass(), "reactHostDelegate");
159
+ }
160
+ if (reactHostDelegateField == null) {
161
+ throw new NoSuchFieldException("Unable to resolve ReactHostDelegate field.");
161
162
  }
163
+
162
164
  reactHostDelegateField.setAccessible(true);
163
165
  ReactHostDelegate reactHostDelegate = (ReactHostDelegate) reactHostDelegateField.get(reactHost);
164
166
  assert reactHostDelegate != null;
165
- Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader");
167
+
168
+ // Expo ReactHost delegate keeps this mutable backing field specifically
169
+ // so integrations can override the bundle loader at runtime.
170
+ Field jsBundleLoaderField = resolveDeclaredField(reactHostDelegate.getClass(), "_jsBundleLoader");
171
+ if (jsBundleLoaderField == null) {
172
+ // Fallback for non-Expo delegates.
173
+ jsBundleLoaderField = resolveDeclaredField(reactHostDelegate.getClass(), "jsBundleLoader");
174
+ }
175
+ if (jsBundleLoaderField == null) {
176
+ throw new NoSuchFieldException("Unable to resolve JSBundleLoader field.");
177
+ }
178
+
166
179
  jsBundleLoaderField.setAccessible(true);
167
180
  jsBundleLoaderField.set(reactHostDelegate, latestJSBundleLoader);
168
181
  }
@@ -234,13 +247,24 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
234
247
  private ReactHost resolveReactHost() {
235
248
  ReactDelegate reactDelegate = resolveReactDelegate();
236
249
  if (reactDelegate == null) {
237
- CodePushUtils.log("Unable to resolve ReactDelegate");
238
250
  return null;
239
251
  }
240
252
 
241
253
  return reactDelegate.getReactHost();
242
254
  }
243
255
 
256
+ private Field resolveDeclaredField(Class<?> targetClass, String fieldName) {
257
+ Class<?> cursor = targetClass;
258
+ while (cursor != null) {
259
+ try {
260
+ return cursor.getDeclaredField(fieldName);
261
+ } catch (NoSuchFieldException ignored) {
262
+ cursor = cursor.getSuperclass();
263
+ }
264
+ }
265
+ return null;
266
+ }
267
+
244
268
  private void restartAppInternal(boolean onlyIfUpdateIsPending) {
245
269
  if (this._restartInProgress) {
246
270
  CodePushUtils.log("Restart request queued until the current restart is completed");
@@ -360,7 +384,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
360
384
  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
361
385
  .emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap());
362
386
  }
363
- }, mCodePush.getPublicKey());
387
+ });
364
388
 
365
389
  JSONObject newPackage = mUpdateManager.getPackage(CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY));
366
390
  promise.resolve(CodePushUtils.convertJsonObjectToWritable(newPackage));
@@ -144,8 +144,7 @@ public class CodePushUpdateManager {
144
144
  }
145
145
 
146
146
  public void downloadPackage(JSONObject updatePackage, String expectedBundleFileName,
147
- DownloadProgressCallback progressCallback,
148
- String stringPublicKey) throws IOException {
147
+ DownloadProgressCallback progressCallback) throws IOException {
149
148
  String newUpdateHash = updatePackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
150
149
  String newUpdateFolderPath = getPackageFolderPath(newUpdateHash);
151
150
  String newUpdateMetadataPath = CodePushUtils.appendPathComponent(newUpdateFolderPath, CodePushConstants.PACKAGE_FILE_NAME);
@@ -266,32 +265,6 @@ public class CodePushUpdateManager {
266
265
  }
267
266
 
268
267
  CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
269
-
270
- boolean isSignatureVerificationEnabled = (stringPublicKey != null);
271
-
272
- String signaturePath = CodePushUpdateUtils.getSignatureFilePath(newUpdateFolderPath);
273
- boolean isSignatureAppearedInBundle = FileUtils.fileAtPathExists(signaturePath);
274
-
275
- if (isSignatureVerificationEnabled) {
276
- if (isSignatureAppearedInBundle) {
277
- CodePushUpdateUtils.verifyUpdateSignature(newUpdateFolderPath, newUpdateHash, stringPublicKey);
278
- } else {
279
- throw new CodePushInvalidUpdateException(
280
- "Error! Public key was provided but there is no JWT signature within app bundle to verify. " +
281
- "Possible reasons, why that might happen: \n" +
282
- "1. You've been released CodePush bundle update using version of CodePush CLI that is not support code signing.\n" +
283
- "2. You've been released CodePush bundle update without providing --privateKeyPath option."
284
- );
285
- }
286
- } else {
287
- if (isSignatureAppearedInBundle) {
288
- CodePushUtils.log(
289
- "Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. " +
290
- "Please ensure that public key is properly configured within your application."
291
- );
292
- }
293
- }
294
-
295
268
  CodePushUtils.setJSONValueForKey(updatePackage, CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
296
269
  }
297
270
  } else {
@@ -1,13 +1,6 @@
1
1
  package com.microsoft.codepush.react;
2
2
 
3
3
  import android.content.Context;
4
- import android.util.Base64;
5
-
6
- import com.nimbusds.jose.JWSVerifier;
7
- import com.nimbusds.jose.crypto.RSASSAVerifier;
8
- import com.nimbusds.jwt.SignedJWT;
9
-
10
- import java.security.interfaces.*;
11
4
 
12
5
  import org.json.JSONArray;
13
6
  import org.json.JSONException;
@@ -20,14 +13,10 @@ import java.io.FileNotFoundException;
20
13
  import java.io.IOException;
21
14
  import java.io.InputStream;
22
15
  import java.security.DigestInputStream;
23
- import java.security.KeyFactory;
24
16
  import java.security.MessageDigest;
25
17
  import java.security.NoSuchAlgorithmException;
26
- import java.security.PublicKey;
27
- import java.security.spec.X509EncodedKeySpec;
28
18
  import java.util.ArrayList;
29
19
  import java.util.Collections;
30
- import java.util.Map;
31
20
 
32
21
  public class CodePushUpdateUtils {
33
22
 
@@ -38,13 +27,10 @@ public class CodePushUpdateUtils {
38
27
  public static boolean isHashIgnored(String relativeFilePath) {
39
28
  final String __MACOSX = "__MACOSX/";
40
29
  final String DS_STORE = ".DS_Store";
41
- final String CODEPUSH_METADATA = ".codepushrelease";
42
30
 
43
31
  return relativeFilePath.startsWith(__MACOSX)
44
32
  || relativeFilePath.equals(DS_STORE)
45
- || relativeFilePath.endsWith("/" + DS_STORE)
46
- || relativeFilePath.equals(CODEPUSH_METADATA)
47
- || relativeFilePath.endsWith("/" + CODEPUSH_METADATA);
33
+ || relativeFilePath.endsWith("/" + DS_STORE);
48
34
  }
49
35
 
50
36
  private static void addContentsOfFolderToManifest(String folderPath, String pathPrefix, ArrayList<String> manifest) {
@@ -149,11 +135,6 @@ public class CodePushUpdateUtils {
149
135
  try {
150
136
  return CodePushUtils.getStringFromInputStream(context.getAssets().open(CodePushConstants.CODE_PUSH_OLD_HASH_FILE_NAME));
151
137
  } catch (IOException ex) {
152
- if (!isDebugMode) {
153
- // Only print this message in "Release" mode. In "Debug", we may not have the
154
- // hash if the build skips bundling the files.
155
- CodePushUtils.log("Unable to get the hash of the binary's bundled resources - \"codepush.gradle\" may have not been added to the build definition.");
156
- }
157
138
  }
158
139
  return null;
159
140
  }
@@ -188,88 +169,4 @@ public class CodePushUpdateUtils {
188
169
  CodePushUtils.log("The update contents succeeded the data integrity check.");
189
170
  }
190
171
 
191
- public static Map<String, Object> verifyAndDecodeJWT(String jwt, PublicKey publicKey) {
192
- try {
193
- SignedJWT signedJWT = SignedJWT.parse(jwt);
194
- JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey);
195
- if (signedJWT.verify(verifier)) {
196
- Map<String, Object> claims = signedJWT.getJWTClaimsSet().getClaims();
197
- CodePushUtils.log("JWT verification succeeded, payload content: " + claims.toString());
198
- return claims;
199
- }
200
- return null;
201
- } catch (Exception ex) {
202
- CodePushUtils.log(ex.getMessage());
203
- CodePushUtils.log(ex.getStackTrace().toString());
204
- return null;
205
- }
206
- }
207
-
208
- public static PublicKey parsePublicKey(String stringPublicKey) {
209
- try {
210
- //remove unnecessary "begin/end public key" entries from string
211
- stringPublicKey = stringPublicKey
212
- .replace("-----BEGIN PUBLIC KEY-----", "")
213
- .replace("-----END PUBLIC KEY-----", "")
214
- .replace(NEW_LINE, "");
215
- byte[] byteKey = Base64.decode(stringPublicKey.getBytes(), Base64.DEFAULT);
216
- X509EncodedKeySpec X509Key = new X509EncodedKeySpec(byteKey);
217
- KeyFactory kf = KeyFactory.getInstance("RSA");
218
-
219
- return kf.generatePublic(X509Key);
220
- } catch (Exception e) {
221
- CodePushUtils.log(e.getMessage());
222
- CodePushUtils.log(e.getStackTrace().toString());
223
- return null;
224
- }
225
- }
226
-
227
- public static String getSignatureFilePath(String updateFolderPath) {
228
- return CodePushUtils.appendPathComponent(
229
- CodePushUtils.appendPathComponent(updateFolderPath, CodePushConstants.CODE_PUSH_FOLDER_PREFIX),
230
- CodePushConstants.BUNDLE_JWT_FILE
231
- );
232
- }
233
-
234
- public static String getSignature(String folderPath) {
235
- final String signatureFilePath = getSignatureFilePath(folderPath);
236
-
237
- try {
238
- return FileUtils.readFileToString(signatureFilePath);
239
- } catch (IOException e) {
240
- CodePushUtils.log(e.getMessage());
241
- CodePushUtils.log(e.getStackTrace().toString());
242
- return null;
243
- }
244
- }
245
-
246
- public static void verifyUpdateSignature(String folderPath, String packageHash, String stringPublicKey) throws CodePushInvalidUpdateException {
247
- CodePushUtils.log("Verifying signature for folder path: " + folderPath);
248
-
249
- final PublicKey publicKey = parsePublicKey(stringPublicKey);
250
- if (publicKey == null) {
251
- throw new CodePushInvalidUpdateException("The update could not be verified because no public key was found.");
252
- }
253
-
254
- final String signature = getSignature(folderPath);
255
- if (signature == null) {
256
- throw new CodePushInvalidUpdateException("The update could not be verified because no signature was found.");
257
- }
258
-
259
- final Map<String, Object> claims = verifyAndDecodeJWT(signature, publicKey);
260
- if (claims == null) {
261
- throw new CodePushInvalidUpdateException("The update could not be verified because it was not signed by a trusted party.");
262
- }
263
-
264
- final String contentHash = (String) claims.get("contentHash");
265
- if (contentHash == null) {
266
- throw new CodePushInvalidUpdateException("The update could not be verified because the signature did not specify a content hash.");
267
- }
268
-
269
- if (!contentHash.equals(packageHash)) {
270
- throw new CodePushInvalidUpdateException("The update contents failed the code signing check.");
271
- }
272
-
273
- CodePushUtils.log("The update contents succeeded the code signing check.");
274
- }
275
172
  }
@@ -1,4 +1,5 @@
1
1
  import fs from "fs";
2
+ import path from "path";
2
3
  import { prepareToBundleJS } from "../../functions/prepareToBundleJS.js";
3
4
  import { runReactNativeBundleCommand } from "../../functions/runReactNativeBundleCommand.js";
4
5
  import { runExpoBundleCommand } from "../../functions/runExpoBundleCommand.js";
@@ -17,6 +18,7 @@ export async function bundleCodePush(
17
18
  entryFile: string = ENTRY_FILE,
18
19
  jsBundleName: string, // JS bundle file name (not CodePush bundle file)
19
20
  bundleDirectory: string, // CodePush bundle output directory
21
+ outputMetroDir?: string,
20
22
  ): Promise<string> {
21
23
  if (fs.existsSync(outputRootPath)) {
22
24
  fs.rmSync(outputRootPath, { recursive: true });
@@ -49,6 +51,8 @@ export async function bundleCodePush(
49
51
 
50
52
  console.log('log: JS bundling complete');
51
53
 
54
+ copyMetroOutputsIfNeeded(outputRootPath, outputMetroDir, OUTPUT_CONTENT_PATH, _jsBundleName, SOURCEMAP_OUTPUT);
55
+
52
56
  await runHermesEmitBinaryCommand(
53
57
  _jsBundleName,
54
58
  OUTPUT_CONTENT_PATH,
@@ -61,3 +65,28 @@ export async function bundleCodePush(
61
65
 
62
66
  return codePushBundleFileName;
63
67
  }
68
+
69
+ function copyMetroOutputsIfNeeded(
70
+ outputRootPath: string,
71
+ outputMetroDir: string | undefined,
72
+ outputContentPath: string,
73
+ jsBundleName: string,
74
+ sourceMapOutputPath: string,
75
+ ) {
76
+ if (!outputMetroDir) {
77
+ return;
78
+ }
79
+
80
+ const resolvedOutputMetroDir = path.join(outputRootPath, outputMetroDir);
81
+
82
+ fs.mkdirSync(resolvedOutputMetroDir, { recursive: true });
83
+ fs.copyFileSync(
84
+ path.join(outputContentPath, jsBundleName),
85
+ path.join(resolvedOutputMetroDir, jsBundleName),
86
+ );
87
+ fs.copyFileSync(
88
+ sourceMapOutputPath,
89
+ path.join(resolvedOutputMetroDir, path.basename(sourceMapOutputPath)),
90
+ );
91
+ console.log(`log: Metro outputs copied to: ${resolvedOutputMetroDir}`);
92
+ }
@@ -9,6 +9,7 @@ type Options = {
9
9
  entryFile: string;
10
10
  bundleName: string;
11
11
  outputBundleDir: string;
12
+ outputMetroDir?: string;
12
13
  }
13
14
 
14
15
  program.command('bundle')
@@ -18,6 +19,7 @@ program.command('bundle')
18
19
  .option('-o, --output-path <string>', 'path to output root directory', ROOT_OUTPUT_DIR)
19
20
  .option('-e, --entry-file <string>', 'path to JS/TS entry file', ENTRY_FILE)
20
21
  .option('-b, --bundle-name <string>', 'bundle file name (default-ios: "main.jsbundle" / default-android: "index.android.bundle")')
22
+ .option('--output-metro-dir <string>', 'name of directory to copy the Metro JS bundle and sourcemap before Hermes compilation')
21
23
  .option('--output-bundle-dir <string>', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR)
22
24
  .action((options: Options) => {
23
25
  bundleCodePush(
@@ -27,5 +29,6 @@ program.command('bundle')
27
29
  options.entryFile,
28
30
  options.bundleName,
29
31
  `${options.outputPath}/${options.outputBundleDir}`,
32
+ options.outputMetroDir,
30
33
  )
31
34
  });
@@ -19,6 +19,7 @@ type Options = {
19
19
  skipBundle: boolean;
20
20
  skipCleanup: boolean;
21
21
  outputBundleDir: string;
22
+ outputMetroDir?: string;
22
23
  hashCalc?: boolean;
23
24
  }
24
25
 
@@ -39,6 +40,7 @@ program.command('release')
39
40
  .option('--skip-bundle <bool>', 'skip bundle process', parseBoolean, false)
40
41
  .option('--hash-calc <bool>', 'calculates the bundle file hash used for packageHash in the release history (Requires setting --skip-bundle to true)', parseBoolean)
41
42
  .option('--skip-cleanup <bool>', 'skip cleanup process', parseBoolean, false)
43
+ .option('--output-metro-dir <string>', 'name of directory to copy the Metro JS bundle and sourcemap before Hermes compilation')
42
44
  .option('--output-bundle-dir <string>', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR)
43
45
  .action(async (options: Options) => {
44
46
  const config = findAndReadConfigFile(process.cwd(), options.config);
@@ -71,6 +73,7 @@ program.command('release')
71
73
  options.skipBundle,
72
74
  options.skipCleanup,
73
75
  `${options.outputPath}/${options.outputBundleDir}`,
76
+ options.outputMetroDir,
74
77
  options.hashCalc,
75
78
  )
76
79
 
@@ -24,11 +24,12 @@ export async function release(
24
24
  skipBundle: boolean,
25
25
  skipCleanup: boolean,
26
26
  bundleDirectory: string,
27
+ outputMetroDir?: string,
27
28
  hashCalc?: boolean,
28
29
  ): Promise<void> {
29
30
  const bundleFileName = skipBundle
30
31
  ? readBundleFileNameFrom(bundleDirectory)
31
- : await bundleCodePush(framework, platform, outputPath, entryFile, jsBundleName, bundleDirectory);
32
+ : await bundleCodePush(framework, platform, outputPath, entryFile, jsBundleName, bundleDirectory, outputMetroDir);
32
33
  const bundleFilePath = `${bundleDirectory}/${bundleFileName}`;
33
34
 
34
35
  const packageHash = await (() => {
@@ -1,4 +1,5 @@
1
1
  import fs from "fs";
2
+ import path from "path";
2
3
  import { prepareToBundleJS } from "../../functions/prepareToBundleJS.js";
3
4
  import { runReactNativeBundleCommand } from "../../functions/runReactNativeBundleCommand.js";
4
5
  import { runExpoBundleCommand } from "../../functions/runExpoBundleCommand.js";
@@ -10,7 +11,8 @@ import { ROOT_OUTPUT_DIR, ENTRY_FILE } from "../../constant.js";
10
11
  * @return {Promise<string>} CodePush bundle file name (equals to packageHash)
11
12
  */
12
13
  export async function bundleCodePush(framework, platform = 'ios', outputRootPath = ROOT_OUTPUT_DIR, entryFile = ENTRY_FILE, jsBundleName, // JS bundle file name (not CodePush bundle file)
13
- bundleDirectory) {
14
+ bundleDirectory, // CodePush bundle output directory
15
+ outputMetroDir) {
14
16
  if (fs.existsSync(outputRootPath)) {
15
17
  fs.rmSync(outputRootPath, { recursive: true });
16
18
  }
@@ -26,9 +28,20 @@ bundleDirectory) {
26
28
  runReactNativeBundleCommand(_jsBundleName, OUTPUT_CONTENT_PATH, platform, SOURCEMAP_OUTPUT, entryFile);
27
29
  }
28
30
  console.log('log: JS bundling complete');
31
+ copyMetroOutputsIfNeeded(outputRootPath, outputMetroDir, OUTPUT_CONTENT_PATH, _jsBundleName, SOURCEMAP_OUTPUT);
29
32
  await runHermesEmitBinaryCommand(_jsBundleName, OUTPUT_CONTENT_PATH, SOURCEMAP_OUTPUT);
30
33
  console.log('log: Hermes compilation complete');
31
34
  const { bundleFileName: codePushBundleFileName } = await makeCodePushBundle(OUTPUT_CONTENT_PATH, bundleDirectory);
32
35
  console.log(`log: CodePush bundle created (file path: ./${bundleDirectory}/${codePushBundleFileName})`);
33
36
  return codePushBundleFileName;
34
37
  }
38
+ function copyMetroOutputsIfNeeded(outputRootPath, outputMetroDir, outputContentPath, jsBundleName, sourceMapOutputPath) {
39
+ if (!outputMetroDir) {
40
+ return;
41
+ }
42
+ const resolvedOutputMetroDir = path.join(outputRootPath, outputMetroDir);
43
+ fs.mkdirSync(resolvedOutputMetroDir, { recursive: true });
44
+ fs.copyFileSync(path.join(outputContentPath, jsBundleName), path.join(resolvedOutputMetroDir, jsBundleName));
45
+ fs.copyFileSync(sourceMapOutputPath, path.join(resolvedOutputMetroDir, path.basename(sourceMapOutputPath)));
46
+ console.log(`log: Metro outputs copied to: ${resolvedOutputMetroDir}`);
47
+ }
@@ -8,7 +8,8 @@ program.command('bundle')
8
8
  .option('-o, --output-path <string>', 'path to output root directory', ROOT_OUTPUT_DIR)
9
9
  .option('-e, --entry-file <string>', 'path to JS/TS entry file', ENTRY_FILE)
10
10
  .option('-b, --bundle-name <string>', 'bundle file name (default-ios: "main.jsbundle" / default-android: "index.android.bundle")')
11
+ .option('--output-metro-dir <string>', 'name of directory to copy the Metro JS bundle and sourcemap before Hermes compilation')
11
12
  .option('--output-bundle-dir <string>', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR)
12
13
  .action((options) => {
13
- bundleCodePush(options.framework, options.platform, options.outputPath, options.entryFile, options.bundleName, `${options.outputPath}/${options.outputBundleDir}`);
14
+ bundleCodePush(options.framework, options.platform, options.outputPath, options.entryFile, options.bundleName, `${options.outputPath}/${options.outputBundleDir}`, options.outputMetroDir);
14
15
  });
@@ -19,6 +19,7 @@ program.command('release')
19
19
  .option('--skip-bundle <bool>', 'skip bundle process', parseBoolean, false)
20
20
  .option('--hash-calc <bool>', 'calculates the bundle file hash used for packageHash in the release history (Requires setting --skip-bundle to true)', parseBoolean)
21
21
  .option('--skip-cleanup <bool>', 'skip cleanup process', parseBoolean, false)
22
+ .option('--output-metro-dir <string>', 'name of directory to copy the Metro JS bundle and sourcemap before Hermes compilation')
22
23
  .option('--output-bundle-dir <string>', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR)
23
24
  .action(async (options) => {
24
25
  const config = findAndReadConfigFile(process.cwd(), options.config);
@@ -30,7 +31,7 @@ program.command('release')
30
31
  console.error('--hash-calc option can be used only when --skip-bundle is set to true.');
31
32
  process.exit(1);
32
33
  }
33
- await release(config.bundleUploader, config.getReleaseHistory, config.setReleaseHistory, options.binaryVersion, options.appVersion, options.framework, options.platform, options.identifier, options.outputPath, options.entryFile, options.bundleName, options.mandatory, options.enable, options.rollout, options.skipBundle, options.skipCleanup, `${options.outputPath}/${options.outputBundleDir}`, options.hashCalc);
34
+ await release(config.bundleUploader, config.getReleaseHistory, config.setReleaseHistory, options.binaryVersion, options.appVersion, options.framework, options.platform, options.identifier, options.outputPath, options.entryFile, options.bundleName, options.mandatory, options.enable, options.rollout, options.skipBundle, options.skipCleanup, `${options.outputPath}/${options.outputBundleDir}`, options.outputMetroDir, options.hashCalc);
34
35
  console.log('🚀 Release completed.');
35
36
  });
36
37
  function parseBoolean(value) {
@@ -4,10 +4,10 @@ import { bundleCodePush } from "../bundleCommand/bundleCodePush.js";
4
4
  import { addToReleaseHistory } from "./addToReleaseHistory.js";
5
5
  import { generatePackageHashFromDirectory } from "../../utils/hash-utils.js";
6
6
  import { unzip } from "../../utils/unzip.js";
7
- export async function release(bundleUploader, getReleaseHistory, setReleaseHistory, binaryVersion, appVersion, framework, platform, identifier, outputPath, entryFile, jsBundleName, mandatory, enable, rollout, skipBundle, skipCleanup, bundleDirectory, hashCalc) {
7
+ export async function release(bundleUploader, getReleaseHistory, setReleaseHistory, binaryVersion, appVersion, framework, platform, identifier, outputPath, entryFile, jsBundleName, mandatory, enable, rollout, skipBundle, skipCleanup, bundleDirectory, outputMetroDir, hashCalc) {
8
8
  const bundleFileName = skipBundle
9
9
  ? readBundleFileNameFrom(bundleDirectory)
10
- : await bundleCodePush(framework, platform, outputPath, entryFile, jsBundleName, bundleDirectory);
10
+ : await bundleCodePush(framework, platform, outputPath, entryFile, jsBundleName, bundleDirectory, outputMetroDir);
11
11
  const bundleFilePath = `${bundleDirectory}/${bundleFileName}`;
12
12
  const packageHash = await (() => {
13
13
  if (skipBundle && hashCalc) {
@@ -48,12 +48,9 @@ class PackageManifest {
48
48
  static isIgnored(relativeFilePath) {
49
49
  const __MACOSX = '__MACOSX/';
50
50
  const DS_STORE = '.DS_Store';
51
- const CODEPUSH_METADATA = '.codepushrelease';
52
51
  return (relativeFilePath.startsWith(__MACOSX) ||
53
52
  relativeFilePath === DS_STORE ||
54
- relativeFilePath.endsWith('/' + DS_STORE) ||
55
- relativeFilePath === CODEPUSH_METADATA ||
56
- relativeFilePath.endsWith('/' + CODEPUSH_METADATA));
53
+ relativeFilePath.endsWith('/' + DS_STORE));
57
54
  }
58
55
  }
59
56
  export async function generatePackageHashFromDirectory(directoryPath, basePath) {
@@ -61,13 +61,10 @@ class PackageManifest {
61
61
  static isIgnored(relativeFilePath: string): boolean {
62
62
  const __MACOSX = '__MACOSX/';
63
63
  const DS_STORE = '.DS_Store';
64
- const CODEPUSH_METADATA = '.codepushrelease';
65
64
  return (
66
65
  relativeFilePath.startsWith(__MACOSX) ||
67
66
  relativeFilePath === DS_STORE ||
68
- relativeFilePath.endsWith('/' + DS_STORE) ||
69
- relativeFilePath === CODEPUSH_METADATA ||
70
- relativeFilePath.endsWith('/' + CODEPUSH_METADATA)
67
+ relativeFilePath.endsWith('/' + DS_STORE)
71
68
  );
72
69
  }
73
70
  }