Files
Fluxup_PAP/node_modules/@expo/log-box/ios/ExpoLogBoxWebViewWrapper.swift
2026-03-10 16:18:05 +00:00

207 lines
7.3 KiB
Swift

// 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