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.
- package/CHANGELOG.md +8 -0
- package/android/src/main/assets/edge-core-js/edge-core.js +1 -1
- package/android/src/main/assets/edge-core-js/index.html +1 -1
- package/android/src/main/java/app/edge/reactnative/core/EdgeCoreModule.java +31 -0
- package/android/src/main/java/app/edge/reactnative/core/EdgeCorePackage.java +1 -1
- package/android/src/main/java/app/edge/reactnative/core/EdgeCoreWebView.java +117 -65
- package/android/src/main/java/app/edge/reactnative/core/LocalContentWebViewClient.java +79 -0
- package/edge-core-js.podspec +3 -1
- package/ios/EdgeAssetsSchemeHandler.swift +175 -0
- package/ios/EdgeCoreModule.m +4 -0
- package/ios/EdgeCoreModule.swift +15 -0
- package/ios/EdgeCoreWebView.swift +32 -45
- package/lib/io/react-native/react-native-worker.js +3 -31
- package/lib/libs.d.js +12 -0
- package/lib/node/index.js +1 -1
- package/lib/react-native.js +41 -4
- package/lib/util/nym.js +1 -8
- package/lib/util/promise.js +1 -1
- package/package.json +1 -1
- package/android/src/main/java/app/edge/reactnative/core/BundleHTTPServer.java +0 -318
- package/ios/BundleHTTPServer.swift +0 -394
|
@@ -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
|
|
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.
|
|
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(
|
|
69
|
+
getSettings().setAllowFileAccess(false);
|
|
53
70
|
getSettings().setJavaScriptEnabled(true);
|
|
54
|
-
setWebViewClient(new
|
|
71
|
+
setWebViewClient(new LocalContentWebViewClient(this));
|
|
55
72
|
addJavascriptInterface(new JsMethods(), "edgeCore");
|
|
56
73
|
|
|
57
|
-
//
|
|
58
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
135
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
+
}
|
package/edge-core-js.podspec
CHANGED
|
@@ -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/
|
|
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,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
|
+
}
|