edge-core-js 2.41.0 → 2.41.2

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.
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <title>edge-core-js</title>
6
- <script charset="utf-8" defer src="/edge-core.js"></script>
6
+ <script charset="utf-8" defer src="edge-core.js"></script>
7
7
  </head>
8
8
  <body></body>
9
9
  </html>
@@ -0,0 +1,31 @@
1
+ package app.edge.reactnative.core;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import com.facebook.react.bridge.ReactApplicationContext;
5
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
6
+ import java.util.HashMap;
7
+ import java.util.Map;
8
+
9
+ /**
10
+ * Native module that exports constants for edge-core-js. Accessible via
11
+ * NativeModules.EdgeCoreModule.getConstants() in JavaScript.
12
+ */
13
+ public class EdgeCoreModule extends ReactContextBaseJavaModule {
14
+ public EdgeCoreModule(ReactApplicationContext context) {
15
+ super(context);
16
+ }
17
+
18
+ @NonNull
19
+ @Override
20
+ public String getName() {
21
+ return "EdgeCoreModule";
22
+ }
23
+
24
+ @Override
25
+ public Map<String, Object> getConstants() {
26
+ final Map<String, Object> constants = new HashMap<>();
27
+ constants.put("bundleBaseUri", LocalContentWebViewClient.BUNDLE_BASE_URI);
28
+ constants.put("rootBaseUri", "file:///android_asset/");
29
+ return constants;
30
+ }
31
+ }
@@ -12,7 +12,7 @@ public class EdgeCorePackage implements ReactPackage {
12
12
  @NonNull
13
13
  @Override
14
14
  public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
15
- return Collections.emptyList();
15
+ return Collections.<NativeModule>singletonList(new EdgeCoreModule(reactContext));
16
16
  }
17
17
 
18
18
  @NonNull
@@ -1,27 +1,44 @@
1
1
  package app.edge.reactnative.core;
2
2
 
3
- import android.graphics.Bitmap;
4
- import android.os.Handler;
5
- import android.os.Looper;
3
+ import android.content.res.AssetManager;
6
4
  import android.util.Log;
7
5
  import android.webkit.JavascriptInterface;
6
+ import android.webkit.WebResourceResponse;
8
7
  import android.webkit.WebView;
9
- import android.webkit.WebViewClient;
10
8
  import com.facebook.react.bridge.Arguments;
11
9
  import com.facebook.react.bridge.WritableMap;
12
10
  import com.facebook.react.uimanager.ThemedReactContext;
13
11
  import com.facebook.react.uimanager.events.RCTEventEmitter;
12
+ import java.io.ByteArrayInputStream;
13
+ import java.io.ByteArrayOutputStream;
14
+ import java.io.IOException;
15
+ import java.io.InputStream;
16
+ import java.util.HashMap;
17
+ import java.util.Map;
14
18
  import org.json.JSONArray;
15
19
 
20
+ /**
21
+ * A WebView that loads edge-core-js content using WebViewAssetLoader.
22
+ *
23
+ * <p>Uses WebViewAssetLoader to serve local assets via HTTPS URLs, which: - Provides a proper
24
+ * non-null origin for same-origin policy compliance - Eliminates the need for a local HTTP server -
25
+ * Is more secure (content served directly from assets without network stack)
26
+ *
27
+ * <p>Includes Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers required for
28
+ * SharedArrayBuffer support (needed by mixFetch web workers).
29
+ *
30
+ * <p>Note: WebViewAssetLoader only handles requests within this specific WebView instance. It does
31
+ * not register a system-wide URL handler - other apps cannot access these URLs.
32
+ */
16
33
  class EdgeCoreWebView extends WebView {
17
34
  private static final String TAG = "EdgeCoreWebView";
35
+
36
+ /** Default URL for the WebView. Uses the WebViewAssetLoader URL format. */
37
+ private static final String DEFAULT_SOURCE =
38
+ LocalContentWebViewClient.BUNDLE_BASE_URI + "/edge-core-js/index.html";
39
+
18
40
  private final ThemedReactContext mContext;
19
41
  private final EdgeNative mNative;
20
- private BundleHTTPServer mHttpServer;
21
- private int mServerPort = 0;
22
- private boolean mServerReady = false;
23
- private boolean mIsDestroyed = false;
24
- private final Handler mMainHandler = new Handler(Looper.getMainLooper());
25
42
 
26
43
  // react api--------------------------------------------------------------
27
44
 
@@ -49,61 +66,106 @@ class EdgeCoreWebView extends WebView {
49
66
  mContext = context;
50
67
  mNative = new EdgeNative(mContext.getFilesDir());
51
68
 
52
- getSettings().setAllowFileAccess(true);
69
+ getSettings().setAllowFileAccess(false);
53
70
  getSettings().setJavaScriptEnabled(true);
54
- setWebViewClient(new Client());
71
+ setWebViewClient(new LocalContentWebViewClient(this));
55
72
  addJavascriptInterface(new JsMethods(), "edgeCore");
56
73
 
57
- // Start the HTTP server on an ephemeral port bound to loopback only
58
- mHttpServer = new BundleHTTPServer(context);
59
- mHttpServer.start(new BundleHTTPServer.OnServerStartedListener() {
60
- @Override
61
- public void onServerStarted(int port) {
62
- mMainHandler.post(() -> {
63
- // Check if WebView was destroyed before this callback executed
64
- if (mIsDestroyed) return;
65
-
66
- mServerPort = port;
67
- mServerReady = true;
68
- // Now that the server is ready with its assigned port, load the page
69
- visitPage();
70
- });
71
- }
72
-
73
- @Override
74
- public void onServerError(Exception error) {
75
- Log.e(TAG, "Failed to start HTTP server: " + error.getMessage());
76
- // Server failed to start - the WebView won't be able to load local content
77
- }
78
- });
74
+ // WebViewAssetLoader is ready immediately - no async startup needed
75
+ visitPage();
79
76
  }
80
77
 
81
78
  @Override
82
79
  protected void onDetachedFromWindow() {
83
80
  super.onDetachedFromWindow();
84
-
85
- // Mark as destroyed first to prevent callbacks from calling methods on destroyed WebView
86
- mIsDestroyed = true;
87
-
88
- // Stop the HTTP server when view is detached
89
- if (mHttpServer != null) {
90
- mHttpServer.stop();
91
- mHttpServer = null;
92
- }
93
-
94
81
  destroy();
95
82
  }
96
83
 
97
- // callbacks -------------------------------------------------------------
84
+ // file serving ----------------------------------------------------------
85
+
86
+ /**
87
+ * Serve a file from assets with COOP/COEP headers. Package-private for use by
88
+ * LocalContentWebViewClient.
89
+ */
90
+ WebResourceResponse serveFileWithHeaders(String resourcePath) {
91
+ AssetManager assetManager = mContext.getAssets();
92
+ byte[] data = null;
93
+
94
+ try (InputStream inputStream = assetManager.open(resourcePath)) {
95
+ data = readAllBytes(inputStream);
96
+ Log.d(TAG, "Serving file: " + resourcePath);
97
+ } catch (IOException e) {
98
+ // File not in assets
99
+ }
98
100
 
99
- class Client extends WebViewClient {
100
- @Override
101
- public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
102
- // Reload on navigation errors (matches iOS didFailProvisionalNavigation behavior)
103
- visitPage();
101
+ if (data == null) {
102
+ Log.d(TAG, "File not found: " + resourcePath);
103
+ return createNotFoundResponse();
104
104
  }
105
+
106
+ String mimeType = getMimeType(resourcePath);
107
+ return createResponseWithHeaders(mimeType, data);
108
+ }
109
+
110
+ /** Create a WebResourceResponse with COOP/COEP headers for SharedArrayBuffer support. */
111
+ private WebResourceResponse createResponseWithHeaders(String mimeType, byte[] data) {
112
+ Map<String, String> headers = new HashMap<>();
113
+ // CORS headers to allow cross-origin requests (needed for debug mode with localhost)
114
+ headers.put("Access-Control-Allow-Origin", "*");
115
+ headers.put("Cross-Origin-Resource-Policy", "cross-origin");
116
+ // Cross-origin isolation headers required for SharedArrayBuffer (needed by mixFetch web
117
+ // workers)
118
+ headers.put("Cross-Origin-Opener-Policy", "same-origin");
119
+ headers.put("Cross-Origin-Embedder-Policy", "require-corp");
120
+
121
+ return new WebResourceResponse(
122
+ mimeType, "UTF-8", 200, "OK", headers, new ByteArrayInputStream(data));
123
+ }
124
+
125
+ /** Create a 404 Not Found response. Package-private for use by LocalContentWebViewClient. */
126
+ WebResourceResponse createNotFoundResponse() {
127
+ Map<String, String> headers = new HashMap<>();
128
+ // CORS headers
129
+ headers.put("Access-Control-Allow-Origin", "*");
130
+ headers.put("Cross-Origin-Resource-Policy", "cross-origin");
131
+ // Include COOP/COEP even on error responses
132
+ headers.put("Cross-Origin-Opener-Policy", "same-origin");
133
+ headers.put("Cross-Origin-Embedder-Policy", "require-corp");
134
+
135
+ return new WebResourceResponse(
136
+ "text/plain",
137
+ "UTF-8",
138
+ 404,
139
+ "Not Found",
140
+ headers,
141
+ new ByteArrayInputStream("Not Found".getBytes()));
105
142
  }
106
143
 
144
+ private byte[] readAllBytes(InputStream inputStream) throws IOException {
145
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
146
+ byte[] chunk = new byte[4096];
147
+ int bytesRead;
148
+ while ((bytesRead = inputStream.read(chunk)) != -1) {
149
+ buffer.write(chunk, 0, bytesRead);
150
+ }
151
+ return buffer.toByteArray();
152
+ }
153
+
154
+ // We only serve HTML, JS, and WASM files
155
+ private String getMimeType(String path) {
156
+ String lowerPath = path.toLowerCase();
157
+ if (lowerPath.endsWith(".html") || lowerPath.endsWith(".htm")) {
158
+ return "text/html";
159
+ } else if (lowerPath.endsWith(".js")) {
160
+ return "application/javascript";
161
+ } else if (lowerPath.endsWith(".wasm")) {
162
+ return "application/wasm";
163
+ }
164
+ return "application/octet-stream";
165
+ }
166
+
167
+ // JavaScript interface --------------------------------------------------
168
+
107
169
  class JsMethods {
108
170
  @JavascriptInterface
109
171
  public void call(int id, final String name, final String args) {
@@ -131,28 +193,18 @@ class EdgeCoreWebView extends WebView {
131
193
 
132
194
  // utilities -------------------------------------------------------------
133
195
 
134
- private String defaultSource() {
135
- if (!mServerReady) {
136
- return null;
137
- }
138
- return "http://127.0.0.1:" + mServerPort + "/index.html";
139
- }
140
-
141
- private void visitPage() {
196
+ /** Reload the page. Package-private for use by LocalContentWebViewClient. */
197
+ void visitPage() {
142
198
  // If source is set, use it directly (e.g., webpack dev server for debugging)
143
- // Otherwise, use the local bundle HTTP server with ephemeral port
199
+ // Otherwise, use the WebViewAssetLoader URL
144
200
  String baseUrl;
145
201
  if (mSource != null && !mSource.isEmpty()) {
146
202
  baseUrl = mSource;
147
203
  } else {
148
- baseUrl = defaultSource();
149
- if (baseUrl == null) {
150
- Log.w(TAG, "visitPage called before server is ready");
151
- return;
152
- }
204
+ baseUrl = DEFAULT_SOURCE;
153
205
  }
154
-
155
- // Load the page from the HTTP server to get COOP/COEP headers
206
+
207
+ // Load the page - WebViewAssetLoader intercepts and serves with COOP/COEP headers
156
208
  // which are required for SharedArrayBuffer support (needed by mixFetch web workers)
157
209
  loadUrl(baseUrl);
158
210
  }
@@ -0,0 +1,79 @@
1
+ package app.edge.reactnative.core;
2
+
3
+ import android.net.Uri;
4
+ import android.webkit.WebResourceRequest;
5
+ import android.webkit.WebResourceResponse;
6
+ import android.webkit.WebView;
7
+ import android.webkit.WebViewClient;
8
+ import androidx.annotation.RequiresApi;
9
+
10
+ /**
11
+ * WebViewClient that intercepts requests and serves local assets with COOP/COEP headers.
12
+ *
13
+ * <p>Works with EdgeCoreWebView to serve files from the assets folder with
14
+ * Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers required for
15
+ * SharedArrayBuffer support (needed by mixFetch web workers).
16
+ *
17
+ * <p>Note: WebViewAssetLoader only handles requests within this specific WebView instance. It does
18
+ * not register a system-wide URL handler - other apps cannot access these URLs.
19
+ */
20
+ class LocalContentWebViewClient extends WebViewClient {
21
+ /** The domain used for serving local assets. This provides the origin for same-origin policy. */
22
+ static final String ASSET_LOADER_DOMAIN = "edge.bundle";
23
+
24
+ /** Base URI for serving bundled assets (e.g., "https://edge.bundle"). */
25
+ static final String BUNDLE_BASE_URI = "https://" + ASSET_LOADER_DOMAIN;
26
+
27
+ private final EdgeCoreWebView mWebView;
28
+
29
+ LocalContentWebViewClient(EdgeCoreWebView webView) {
30
+ mWebView = webView;
31
+ }
32
+
33
+ @Override
34
+ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
35
+ // Reload on navigation errors (matches iOS didFailProvisionalNavigation behavior)
36
+ mWebView.visitPage();
37
+ }
38
+
39
+ @Override
40
+ @RequiresApi(21)
41
+ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
42
+ Uri uri = request.getUrl();
43
+
44
+ // Only intercept requests to our asset loader domain
45
+ if (!ASSET_LOADER_DOMAIN.equals(uri.getHost())) {
46
+ return null; // Let WebView handle external requests normally
47
+ }
48
+
49
+ // Get the path without leading slash
50
+ String path = uri.getPath();
51
+ if (path == null || path.isEmpty() || path.equals("/")) {
52
+ return mWebView.createNotFoundResponse();
53
+ }
54
+ String resourcePath = path.startsWith("/") ? path.substring(1) : path;
55
+
56
+ // Serve the file with COOP/COEP headers
57
+ return mWebView.serveFileWithHeaders(resourcePath);
58
+ }
59
+
60
+ // For API < 21, use the deprecated method
61
+ @Override
62
+ @SuppressWarnings("deprecation")
63
+ public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
64
+ Uri uri = Uri.parse(url);
65
+
66
+ // Only intercept requests to our asset loader domain
67
+ if (!ASSET_LOADER_DOMAIN.equals(uri.getHost())) {
68
+ return null;
69
+ }
70
+
71
+ String path = uri.getPath();
72
+ if (path == null || path.isEmpty() || path.equals("/")) {
73
+ return mWebView.createNotFoundResponse();
74
+ }
75
+ String resourcePath = path.startsWith("/") ? path.substring(1) : path;
76
+
77
+ return mWebView.serveFileWithHeaders(resourcePath);
78
+ }
79
+ }
@@ -24,7 +24,9 @@ Pod::Spec.new do |s|
24
24
  "android/src/main/cpp/scrypt/sysendian.h",
25
25
  "ios/Disklet.swift",
26
26
  "ios/edge-core-js-Bridging-Header.h",
27
- "ios/BundleHTTPServer.swift",
27
+ "ios/EdgeAssetsSchemeHandler.swift",
28
+ "ios/EdgeCoreModule.m",
29
+ "ios/EdgeCoreModule.swift",
28
30
  "ios/EdgeCoreWebView.swift",
29
31
  "ios/EdgeCoreWebViewManager.m",
30
32
  "ios/EdgeCoreWebViewManager.swift",
@@ -0,0 +1,175 @@
1
+ import Foundation
2
+ import WebKit
3
+
4
+ /// Custom URL scheme for serving local assets.
5
+ let EDGE_SCHEME = "edgebundle"
6
+ let EDGE_HOST = "edge.bundle"
7
+ let BUNDLE_BASE_URI = "\(EDGE_SCHEME)://\(EDGE_HOST)"
8
+
9
+ /// Handles requests to the custom "edgebundle://" URL scheme.
10
+ /// Serves local bundle assets with COOP/COEP headers for SharedArrayBuffer support.
11
+ ///
12
+ /// Note: WKURLSchemeHandler only handles requests within this specific WKWebView instance.
13
+ /// It does not register a system-wide URL scheme - other apps cannot access this handler.
14
+ class EdgeAssetsSchemeHandler: NSObject, WKURLSchemeHandler {
15
+
16
+ func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
17
+ guard let url = urlSchemeTask.request.url else {
18
+ sendErrorResponse(urlSchemeTask, code: 400, message: "Bad Request")
19
+ return
20
+ }
21
+
22
+ // Get the path without leading slash
23
+ var path = url.path
24
+ if path.hasPrefix("/") {
25
+ path = String(path.dropFirst())
26
+ }
27
+
28
+ // Require explicit file name - no auto-matching for root path
29
+ if path.isEmpty {
30
+ sendErrorResponse(urlSchemeTask, code: 404, message: "Not Found")
31
+ return
32
+ }
33
+
34
+ // Load file from bundle
35
+ guard let data = loadFile(path: path) else {
36
+ print("EdgeAssetsSchemeHandler: File not found: \(path)")
37
+ sendErrorResponse(urlSchemeTask, code: 404, message: "Not Found")
38
+ return
39
+ }
40
+
41
+ let mime = mimeType(for: path)
42
+ let headers = [
43
+ "Content-Type": mime,
44
+ "Content-Length": "\(data.count)",
45
+ // CORS headers to allow cross-origin requests (needed for debug mode with localhost)
46
+ "Access-Control-Allow-Origin": "*",
47
+ "Cross-Origin-Resource-Policy": "cross-origin",
48
+ // Cross-origin isolation headers required for SharedArrayBuffer (needed by mixFetch web workers)
49
+ "Cross-Origin-Opener-Policy": "same-origin",
50
+ "Cross-Origin-Embedder-Policy": "require-corp",
51
+ ]
52
+
53
+ guard
54
+ let response = HTTPURLResponse(
55
+ url: url,
56
+ statusCode: 200,
57
+ httpVersion: "HTTP/1.1",
58
+ headerFields: headers
59
+ )
60
+ else {
61
+ sendErrorResponse(urlSchemeTask, code: 500, message: "Internal Server Error")
62
+ return
63
+ }
64
+
65
+ urlSchemeTask.didReceive(response)
66
+ urlSchemeTask.didReceive(data)
67
+ urlSchemeTask.didFinish()
68
+ }
69
+
70
+ func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
71
+ // Nothing to clean up - file loading is synchronous
72
+ }
73
+
74
+ // MARK: - File Loading
75
+
76
+ /// Load a file from the appropriate bundle location.
77
+ private func loadFile(path: String) -> Data? {
78
+ let bundlePath = Bundle.main.bundlePath
79
+
80
+ if path.contains(".bundle/") || path.hasPrefix("edge-core/") {
81
+ // Plugin bundle file or edge-core plugin - look in app bundle root
82
+ let fullPath = (bundlePath as NSString).appendingPathComponent(path)
83
+ if FileManager.default.fileExists(atPath: fullPath) {
84
+ do {
85
+ return try Data(contentsOf: URL(fileURLWithPath: fullPath))
86
+ } catch {
87
+ print("EdgeAssetsSchemeHandler: Error reading file at \(fullPath): \(error)")
88
+ }
89
+ }
90
+ } else {
91
+ // Core file - look in edge-core-js.bundle
92
+ let nsPath = path as NSString
93
+ let fileExtension = nsPath.pathExtension
94
+ let filename = nsPath.deletingPathExtension
95
+
96
+ if let bundleUrl = Bundle.main.url(forResource: "edge-core-js", withExtension: "bundle"),
97
+ let bundle = Bundle(url: bundleUrl)
98
+ {
99
+ var url: URL?
100
+ if !fileExtension.isEmpty {
101
+ url = bundle.url(forResource: filename, withExtension: fileExtension)
102
+ }
103
+
104
+ if let url = url {
105
+ do {
106
+ return try Data(contentsOf: url)
107
+ } catch {
108
+ print("EdgeAssetsSchemeHandler: Error reading core file: \(error)")
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ return nil
115
+ }
116
+
117
+ // MARK: - Response Helpers
118
+
119
+ private func sendErrorResponse(_ urlSchemeTask: WKURLSchemeTask, code: Int, message: String) {
120
+ // When URL is nil we cannot build an HTTP response; complete the task with didFailWithError
121
+ // so we never leave a task uncompleted (WKURLSchemeHandler contract).
122
+ guard let url = urlSchemeTask.request.url else {
123
+ let error = NSError(
124
+ domain: "EdgeAssetsSchemeHandler",
125
+ code: code,
126
+ userInfo: [NSLocalizedDescriptionKey: message]
127
+ )
128
+ urlSchemeTask.didFailWithError(error)
129
+ return
130
+ }
131
+
132
+ let bodyData = message.data(using: .utf8) ?? Data()
133
+ let headers = [
134
+ "Content-Type": "text/plain",
135
+ "Content-Length": "\(bodyData.count)",
136
+ // CORS headers
137
+ "Access-Control-Allow-Origin": "*",
138
+ "Cross-Origin-Resource-Policy": "cross-origin",
139
+ // Include COOP/COEP even on error responses
140
+ "Cross-Origin-Opener-Policy": "same-origin",
141
+ "Cross-Origin-Embedder-Policy": "require-corp",
142
+ ]
143
+
144
+ if let response = HTTPURLResponse(
145
+ url: url,
146
+ statusCode: code,
147
+ httpVersion: "HTTP/1.1",
148
+ headerFields: headers
149
+ ) {
150
+ urlSchemeTask.didReceive(response)
151
+ urlSchemeTask.didReceive(bodyData)
152
+ urlSchemeTask.didFinish()
153
+ } else {
154
+ // HTTPURLResponse creation failed; complete the task with didFailWithError
155
+ let error = NSError(
156
+ domain: "EdgeAssetsSchemeHandler",
157
+ code: code,
158
+ userInfo: [NSLocalizedDescriptionKey: message]
159
+ )
160
+ urlSchemeTask.didFailWithError(error)
161
+ }
162
+ }
163
+
164
+ private func mimeType(for path: String) -> String {
165
+ let ext = (path as NSString).pathExtension.lowercased()
166
+
167
+ // We only serve HTML, JS, and WASM files
168
+ switch ext {
169
+ case "html", "htm": return "text/html"
170
+ case "js": return "application/javascript"
171
+ case "wasm": return "application/wasm"
172
+ default: return "application/octet-stream"
173
+ }
174
+ }
175
+ }
@@ -0,0 +1,4 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(EdgeCoreModule, NSObject)
4
+ @end
@@ -0,0 +1,15 @@
1
+ import Foundation
2
+
3
+ /// Native module that exports constants for edge-core-js.
4
+ /// Accessible via NativeModules.EdgeCoreModule.getConstants() in JavaScript.
5
+ @objc(EdgeCoreModule)
6
+ class EdgeCoreModule: NSObject {
7
+ @objc static func requiresMainQueueSetup() -> Bool { return false }
8
+
9
+ @objc func constantsToExport() -> [AnyHashable: Any]! {
10
+ return [
11
+ "bundleBaseUri": BUNDLE_BASE_URI,
12
+ "rootBaseUri": "file://\(Bundle.main.bundlePath)/",
13
+ ]
14
+ }
15
+ }