@exodus/react-native-bundle-loader 0.2.0-exodus.2 → 0.2.0-exodus.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.
|
@@ -7,6 +7,7 @@ import android.util.Log;
|
|
|
7
7
|
|
|
8
8
|
import androidx.annotation.NonNull;
|
|
9
9
|
|
|
10
|
+
import com.facebook.react.ReactApplication;
|
|
10
11
|
import com.facebook.react.bridge.Promise;
|
|
11
12
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
12
13
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
@@ -24,12 +25,13 @@ import java.security.NoSuchAlgorithmException;
|
|
|
24
25
|
public class BundleLoaderModule extends ReactContextBaseJavaModule {
|
|
25
26
|
|
|
26
27
|
private static final String TAG = "BundleLoader";
|
|
27
|
-
//
|
|
28
|
-
// Do not rename without updating the host app integration accordingly.
|
|
28
|
+
// Host app references these as string literals (library is debugImplementation only).
|
|
29
29
|
static final String BUNDLE_FILENAME = "verified-bundle.jsbundle";
|
|
30
30
|
static final String PREFS_NAME = "BundleLoader";
|
|
31
31
|
static final String PREFS_PENDING_KEY = "pending_remote_bundle";
|
|
32
32
|
static final String PREFS_ACTIVE_KEY = "active_remote_bundle";
|
|
33
|
+
// Metro/APK source URL captured at load time for correct asset resolution after restart.
|
|
34
|
+
static final String PREFS_METRO_SOURCE_URL_KEY = "metro_source_url";
|
|
33
35
|
|
|
34
36
|
private static final int CONNECT_TIMEOUT_MS = 30_000;
|
|
35
37
|
private static final int READ_TIMEOUT_MS = 30_000;
|
|
@@ -128,21 +130,35 @@ public class BundleLoaderModule extends ReactContextBaseJavaModule {
|
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
private void setPendingFlag() {
|
|
131
|
-
|
|
132
|
-
// before killProcess() terminates the process.
|
|
133
|
-
getReactApplicationContext()
|
|
133
|
+
SharedPreferences.Editor editor = getReactApplicationContext()
|
|
134
134
|
.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
135
135
|
.edit()
|
|
136
|
-
.putBoolean(PREFS_PENDING_KEY, true)
|
|
137
|
-
|
|
136
|
+
.putBoolean(PREFS_PENDING_KEY, true);
|
|
137
|
+
|
|
138
|
+
// Store scriptURL for asset resolution after restart: Metro URL if available,
|
|
139
|
+
// asset:/// otherwise (genesis/release — assets served from APK).
|
|
140
|
+
String sourceUrl = getMetroSourceUrl();
|
|
141
|
+
editor.putString(PREFS_METRO_SOURCE_URL_KEY,
|
|
142
|
+
sourceUrl != null ? sourceUrl : "asset:///index.android.bundle");
|
|
143
|
+
|
|
144
|
+
// commit() not apply() — write must reach disk before killProcess().
|
|
145
|
+
editor.commit();
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
private String getMetroSourceUrl() {
|
|
149
|
+
try {
|
|
150
|
+
ReactApplication app = (ReactApplication) getReactApplicationContext().getApplicationContext();
|
|
151
|
+
String url = app.getReactNativeHost()
|
|
152
|
+
.getReactInstanceManager()
|
|
153
|
+
.getDevSupportManager()
|
|
154
|
+
.getSourceUrl();
|
|
155
|
+
return (url != null && !url.isEmpty()) ? url : null;
|
|
156
|
+
} catch (Exception e) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Kills and relaunches the process. Avoids running two Hermes runtimes simultaneously. */
|
|
146
162
|
private void restartApp() {
|
|
147
163
|
Context context = getReactApplicationContext();
|
|
148
164
|
Intent intent = context.getPackageManager()
|
|
@@ -154,20 +170,12 @@ public class BundleLoaderModule extends ReactContextBaseJavaModule {
|
|
|
154
170
|
android.os.Process.killProcess(android.os.Process.myPid());
|
|
155
171
|
}
|
|
156
172
|
|
|
157
|
-
/**
|
|
158
|
-
* Returns true iff the URL is non-null and uses the https scheme.
|
|
159
|
-
* Extracted as a static helper so it can be unit-tested without the threading
|
|
160
|
-
* machinery in {@link #load(String)}.
|
|
161
|
-
*/
|
|
173
|
+
/** Package-private for testing. */
|
|
162
174
|
static boolean isHttps(String url) {
|
|
163
175
|
return url != null && url.startsWith("https://");
|
|
164
176
|
}
|
|
165
177
|
|
|
166
|
-
/**
|
|
167
|
-
* Parses a 64-character lowercase hex string into a 32-byte SHA-256 digest.
|
|
168
|
-
* Throws {@link IllegalArgumentException} on invalid input so callers can
|
|
169
|
-
* reject the promise before touching the network.
|
|
170
|
-
*/
|
|
178
|
+
/** Parses a 64-char hex string into a 32-byte digest. Package-private for testing. */
|
|
171
179
|
static byte[] parseHexSha256(String hex) {
|
|
172
180
|
if (hex == null || hex.length() != 64) {
|
|
173
181
|
throw new IllegalArgumentException("Expected SHA-256 must be a 64-character hex string");
|
|
@@ -184,11 +192,7 @@ public class BundleLoaderModule extends ReactContextBaseJavaModule {
|
|
|
184
192
|
return out;
|
|
185
193
|
}
|
|
186
194
|
|
|
187
|
-
/**
|
|
188
|
-
* Constant-time byte array comparison: XORs all pairs into an accumulator
|
|
189
|
-
* and returns true iff the accumulator is zero. Both arrays must be the
|
|
190
|
-
* same length; returns false immediately if they differ.
|
|
191
|
-
*/
|
|
195
|
+
/** Constant-time equality check to prevent timing attacks. Package-private for testing. */
|
|
192
196
|
static boolean timingSafeEquals(byte[] a, byte[] b) {
|
|
193
197
|
if (a.length != b.length) return false;
|
|
194
198
|
int diff = 0;
|
|
@@ -199,14 +203,8 @@ public class BundleLoaderModule extends ReactContextBaseJavaModule {
|
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
/**
|
|
202
|
-
* Downloads
|
|
203
|
-
*
|
|
204
|
-
* <p>
|
|
205
|
-
* Mirrors the security properties of {@link #downloadToCache}: redirects are
|
|
206
|
-
* disabled, non-200 responses throw, and the body is capped at {@code maxBytes}.
|
|
207
|
-
* <p>
|
|
208
|
-
* Package-private and static so the JVM unit tests can drive it against a
|
|
209
|
-
* MockWebServer without spinning up a ReactApplicationContext.
|
|
206
|
+
* Downloads into {@code targetFile} and returns its SHA-256 digest.
|
|
207
|
+
* No redirects; non-200 throws; body capped at {@code maxBytes}. Package-private for testing.
|
|
210
208
|
*/
|
|
211
209
|
static byte[] downloadAndHashToCache(
|
|
212
210
|
String urlString,
|
|
@@ -253,13 +251,8 @@ public class BundleLoaderModule extends ReactContextBaseJavaModule {
|
|
|
253
251
|
}
|
|
254
252
|
|
|
255
253
|
/**
|
|
256
|
-
* Downloads
|
|
257
|
-
*
|
|
258
|
-
* status code in the message. The download is hard-capped at {@code maxBytes};
|
|
259
|
-
* once the cap is exceeded the read loop aborts with IOException.
|
|
260
|
-
* <p>
|
|
261
|
-
* Package-private and static so the JVM unit tests can drive it against a
|
|
262
|
-
* MockWebServer without spinning up a ReactApplicationContext.
|
|
254
|
+
* Downloads into {@code targetFile}. No redirects; non-200 throws; body capped at
|
|
255
|
+
* {@code maxBytes}. Package-private for testing.
|
|
263
256
|
*/
|
|
264
257
|
static File downloadToCache(
|
|
265
258
|
String urlString,
|
package/ios/BundleLoader.h
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
2
|
#import <React/RCTRootView.h>
|
|
3
3
|
|
|
4
|
+
// NSUserDefaults key storing the pending remote bundle URL for loadSourceForBridge:.
|
|
5
|
+
extern NSString * const RNBundleLoaderPendingURLKey;
|
|
6
|
+
|
|
4
7
|
@interface BundleLoader : NSObject <RCTBridgeModule>
|
|
5
8
|
|
|
6
9
|
@end
|
package/ios/BundleLoader.m
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#import "BundleLoader.h"
|
|
2
2
|
#import <CommonCrypto/CommonDigest.h>
|
|
3
3
|
|
|
4
|
+
NSString * const RNBundleLoaderPendingURLKey = @"RNBundleLoaderPendingURL";
|
|
5
|
+
|
|
4
6
|
@implementation BundleLoader
|
|
5
7
|
|
|
6
8
|
@synthesize bridge = _bridge;
|
|
@@ -9,16 +11,16 @@ RCT_EXPORT_MODULE()
|
|
|
9
11
|
|
|
10
12
|
- (void)setBundleURLAndReload:(NSURL *)url
|
|
11
13
|
{
|
|
12
|
-
[
|
|
14
|
+
[[NSUserDefaults standardUserDefaults] setURL:url forKey:RNBundleLoaderPendingURLKey];
|
|
15
|
+
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
13
16
|
[_bridge reload];
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
RCT_EXPORT_METHOD(runningMode:(RCTPromiseResolveBlock)resolve
|
|
17
20
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
18
21
|
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
resolve(isRemote ? @"REMOTE" : @"LOCAL");
|
|
22
|
+
NSURL *pending = [[NSUserDefaults standardUserDefaults] URLForKey:RNBundleLoaderPendingURLKey];
|
|
23
|
+
resolve(pending ? @"REMOTE" : @"LOCAL");
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
RCT_EXPORT_METHOD(load:(NSURL *)url)
|