first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
#if canImport(UIKit) && EXPO_UNSTABLE_LOG_BOX
import UIKit
import WebKit
import React
@objc public class ExpoLogBoxScreenProvider: NSObject {
@objc public static func makeHostingController(message: String?, stack: [RCTJSStackFrame]?) -> UIViewController {
return ExpoLogBoxController(message: message, stack:stack)
}
}
struct Colors {
static let background = UIColor(red: 17/255.0,green: 17/255.0,blue: 19/255.0,alpha: 1.0)
}
class ExpoLogBoxController: UIViewController, ExpoLogBoxNativeActionsProtocol {
private var message: String
private var stack: [Dictionary<String, Any>]
init(message: String?, stack: [RCTJSStackFrame]?) {
self.message = message ?? "Error without message."
self.stack = stack?.map { frame in
return [
"file": frame.file ?? "unknown",
"methodName": frame.methodName ?? "unknown",
"arguments": [],
"lineNumber": frame.lineNumber,
"column": frame.column,
"collapse": frame.collapse,
]
} ?? []
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
self.message = "If you see this message this is an issue in ExpoLogBox."
self.stack = []
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Colors.background
isModalInPresentation = true
let webViewWrapper = ExpoLogBoxWebViewWrapper(nativeActions: self, props: [
"platform": "ios",
"nativeLogs": [
[
"message": self.message,
"stack": self.stack,
],
]
])
let webView = webViewWrapper.prepareWebView()
view.addSubview(webView)
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
#if EXPO_DEVELOP_LOG_BOX
// TODO: In the @expo/log-box add `yarn dev` which will return the same as
// http://localhost:8081/_expo/@dom/logbox-polyfill-dom.tsx?file=file:///user/repos/expo/expo/packages/@expo/log-box/src/logbox-polyfill-dom.tsx
// let myURL = URL(string:"http://localhost:8082/_expo/@dom/logbox-polyfill-dom.tsx?file=file:///Users/krystofwoldrich/repos/expo/expo/packages/@expo/log-box/src/logbox-polyfill-dom.tsx")
let myURL = URL(string:"http://localhost:8090")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
#else
let bundleURL = Bundle.main.url(forResource: "ExpoLogBox", withExtension: "bundle")
let bundle = Bundle(url: bundleURL!)
let url = bundle!.url(forResource: "index", withExtension: "html")
webView.loadFileURL(url!, allowingReadAccessTo: url!.deletingLastPathComponent())
#endif
}
func onReload() {
DispatchQueue.main.async {
RCTTriggerReloadCommandListeners("ExpoRedBoxSwap:Reload")
}
dismiss(animated: true)
}
func fetchTextAsync(url: String, method: String?, body: String?) async -> String {
let finalMethod = method ?? "GET"
let finalBody = body ?? ""
guard let url = URL(string: url) else {
print("Invalid URL: \(url)")
return "{}" // Return empty JSON object for invalid URL
}
var request = URLRequest(url: url)
request.httpMethod = finalMethod.uppercased()
// Set Content-Type for POST/PUT requests with body
if !finalBody.isEmpty && (finalMethod.uppercased() == "POST" || finalMethod.uppercased() == "PUT") {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = body!.data(using: .utf8)
}
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Invalid response status: \(String(describing: response))")
return "{}"
}
guard let jsonString = String(data: data, encoding: .utf8) else {
print("Failed to convert data to UTF-8 string")
return "{}"
}
return jsonString
} catch {
print("Request failed: \(error.localizedDescription)")
return "{}"
}
}
}
#endif

View File

@@ -0,0 +1,206 @@
// Keep in sync with webview-wrapper.tsx
// https://github.com/expo/expo/blob/main/packages/expo/src/dom/webview-wrapper.tsx
#if canImport(UIKit) && EXPO_UNSTABLE_LOG_BOX
import UIKit
import WebKit
import React
protocol ExpoLogBoxNativeActionsProtocol {
func onReload() -> Void
func fetchTextAsync(url: String, method: String?, body: String?) async -> String
}
private class ExpoLogBoxNativeActions: ExpoLogBoxNativeActionsProtocol {
func onReload() -> Void {
fatalError()
}
func fetchTextAsync(url: String, method: String?, body: String?) async -> String {
fatalError()
}
static let onReloadName = "onReload"
static let fetchTextAsyncName = "fetchTextAsync"
static let names = [
onReloadName,
fetchTextAsyncName,
]
}
private struct Constants {
static let DOM_EVENT = "$$dom_event"
static let NATIVE_ACTION_RESULT = "$$native_action_result"
static let NATIVE_ACTION = "$$native_action"
}
class ExpoLogBoxWebViewWrapper: NSObject, WKScriptMessageHandler {
private let nativeMessageHandlerName = "nativeHandler"
private let nativeActions: ExpoLogBoxNativeActionsProtocol
private let props: [String: Any]
private let webView: WKWebView
init(nativeActions: ExpoLogBoxNativeActionsProtocol, props: [String: Any]) {
self.nativeActions = nativeActions
self.props = props
self.webView = WKWebView(frame: .zero)
}
func prepareWebView() -> WKWebView {
let initProps: [String: Any] = [
"names": ExpoLogBoxNativeActions.names,
"props": props
]
guard let initPropsObject = try? JSONSerialization.data(withJSONObject: initProps, options: []),
let initPropsStringified = String(data: initPropsObject, encoding: .utf8) else {
fatalError("Failed to serialize initProps. This is an issue in ExpoLogBox. Please report it.")
}
let devServerOrigin: String? = {
guard let bundleUrl = RCTBundleURLProvider.sharedSettings()
.jsBundleURL(forBundleRoot: "unused.name"),
let scheme = bundleUrl.scheme,
let host = bundleUrl.host,
let port = bundleUrl.port
else {
return nil
}
return "\(scheme)://\(host):\(port)"
}()
let devServerOriginJsValue: String = devServerOrigin.map { "'\($0)'" } ?? "undefined"
let injectJavascript = """
var process = globalThis.process || {};
process.env = process.env || {};
process.env.EXPO_DEV_SERVER_ORIGIN = \(devServerOriginJsValue);
window.$$EXPO_DOM_HOST_OS = 'ios';
window.$$EXPO_INITIAL_PROPS = \(initPropsStringified);
window.ReactNativeWebView = {};
window.ReactNativeWebView.postMessage = (message) => {
window.webkit.messageHandlers.\(nativeMessageHandlerName).postMessage(
JSON.parse(message)
);
};
"""
webView.configuration.userContentController.addUserScript(WKUserScript(
source: injectJavascript,
injectionTime: .atDocumentStart,
forMainFrameOnly: true
))
if #available(iOS 16.4, *) {
#if EXPO_DEBUG_LOG_BOX || EXPO_DEVELOP_LOG_BOX
webView.isInspectable = true
#else
webView.isInspectable = false
#endif
}
webView.configuration.userContentController.add(self, name: nativeMessageHandlerName)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.isOpaque = false
return webView
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
Task.detached {
await self.handleWebViewMessageAsync(message: message)
}
}
func handleWebViewMessageAsync(message: WKScriptMessage) async {
guard message.name == nativeMessageHandlerName,
let messageBody = message.body as? [String: Any],
let messageType = messageBody["type"] as? String else {
return
}
let data = messageBody["data"] as? [String: Any] ?? [:]
if (messageType == Constants.NATIVE_ACTION) {
guard let actionId = data["actionId"] as? String,
let uid = data["uid"] as? String,
let args = data["args"] as? [Any] else {
print("ExpoLogBoxDomRuntimeError native actions is missing actionId or uid.")
return
}
do {
switch actionId {
case ExpoLogBoxNativeActions.onReloadName:
nativeActions.onReload()
case ExpoLogBoxNativeActions.fetchTextAsyncName:
guard let url = args[0] as? String,
let options = args[1] as? [String: Any] else {
print("ExpoLogBox fetchTextAsync action is missing url or options.")
return
}
let method = options["method"] as? String
let body = options["body"] as? String
let result = await nativeActions.fetchTextAsync(url: url, method: method, body: body)
sendReturn(result: result, uid: uid, actionId: actionId)
default:
print("Unknown native action: \(actionId)")
}
} catch {
sendReturn(error: error, uid: uid, actionId: actionId)
}
} else {
print("Unknown message type: \(messageType)")
}
}
func sendReturn(result: Any, uid: String, actionId: String) {
sendReturn(data: [
"type": Constants.NATIVE_ACTION_RESULT,
"data": [
"uid": uid,
"actionId": actionId,
"result": result,
],
])
}
func sendReturn(error: Any, uid: String, actionId: String) {
sendReturn(data: [
"type": Constants.NATIVE_ACTION_RESULT,
"data": [
"uid": uid,
"actionId": actionId,
"error": [
"message": "\(error)",
],
],
])
}
func sendReturn(data: [String: Any]) {
guard let jsonData = try? JSONSerialization.data(
withJSONObject: [ "detail": data ],
options: []
), let jsonDataStringified = String(data: jsonData, encoding: .utf8) else {
print("ExpoLogBox failed to stringify native action result.")
return
}
sendReturn(value: jsonDataStringified)
}
func sendReturn(value: String) {
let injectedJavascript = """
;
(function() {
try {
console.log("received", \(value));
window.dispatchEvent(new CustomEvent("\(Constants.DOM_EVENT)", \(value)));
} catch (e) {}
})();
true;
"""
webView.evaluateJavaScript(injectedJavascript) { _, error in
if let error = error {
print("ExpoLogBox NativeActions return value injection error: \(error.localizedDescription)")
}
}
}
}
#endif

48
node_modules/@expo/log-box/ios/ExpoRedBoxSwap.mm generated vendored Normal file
View File

@@ -0,0 +1,48 @@
#if !TARGET_OS_MACCATALYST && EXPO_UNSTABLE_LOG_BOX
#import <objc/runtime.h>
#import <React/RCTRedBox.h>
#import <React/RCTUtils.h>
#import "ExpoLogBox-Swift.h"
@implementation RCTRedBox (WithExpoLogBox)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class classs = [self class];
SEL originalSelector = @selector(showErrorMessage:withParsedStack:isUpdate:errorCookie:);
SEL swizzledSelector = @selector(showErrorMessageWithExpoLogBox:withParsedStack:isUpdate:errorCookie:);
Method originalMethod = class_getInstanceMethod(classs, originalSelector);
Method swizzledMethod = class_getInstanceMethod(classs, swizzledSelector);
BOOL didAddMethod =
class_addMethod(classs,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(classs,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)showErrorMessageWithExpoLogBox:(NSString *)message
withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
isUpdate: (BOOL) isUpdate
errorCookie:(int)errorCookie {
UIViewController *expoRedBox = [ExpoLogBoxScreenProvider makeHostingControllerWithMessage:message stack:stack];
[RCTKeyWindow().rootViewController presentViewController:expoRedBox animated:YES completion:nil];
}
@end
#endif