eleven / messenger Public

ios: implement window.confirm()/alert() in the thread feed webview (#106)

FeedWebCache never set a uiDelegate on the per-thread embedded webview at
all, so WKWebView silently no-ops window.confirm()/alert(). chat.js's
renderApp()/openThread() are shared between embed and full-app mode, so
the "Delete this message" confirm (reachable via long-press in the
embedded feed) was doing nothing on iOS — the same gap the Mac app had
in ChatFeedWebView.swift.

Add WKUIDelegate conformance to WebBridge with async
runJavaScriptConfirmPanelWithMessage/AlertPanelWithMessage, presenting a
UIAlertController from the topmost view controller, and wire it up via
web.uiDelegate = bridge in FeedWebCache.makeEntry.
Co-authored-by: Claude <noreply@anthropic.com>
James Adam 2234fd4122d115f31b5e5da39c22843d5ddb0fda parent 55a33e8
1 file changed, +35 −0
  • ios/LChat/Views/ChatView.swift +35 −0
diff --git a/ios/LChat/Views/ChatView.swift b/ios/LChat/Views/ChatView.swift
index 70817ca..3a2e623 100644
--- a/ios/LChat/Views/ChatView.swift
+++ b/ios/LChat/Views/ChatView.swift
@@ -551,6 +551,41 @@ final class WebBridge: NSObject, ObservableObject, WKScriptMessageHandler, WKNav
"window.lchatNative && window.lchatNative.didAttach && window.lchatNative.didAttach(\(force))")
}
+ // WKUIDelegate shows nothing for window.confirm()/alert() unless we implement
+ // these — chat.js's "Delete this message" confirm (reachable here since embed mode
+ // still shares renderApp()/openThread() with the full app) silently no-ops without
+ // them. The Mac app had the identical gap; see ChatFeedWebView.swift there.
+ func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String,
+ initiatedByFrame frame: WKFrameInfo) async -> Bool {
+ guard let vc = Self.topViewController() else { return false }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
+ return await withCheckedContinuation { continuation in
+ alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in continuation.resume(returning: false) })
+ alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in continuation.resume(returning: true) })
+ vc.present(alert, animated: true)
+ }
+ }
+
+ func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
+ initiatedByFrame frame: WKFrameInfo) async {
+ guard let vc = Self.topViewController() else { return }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
+ await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
+ alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in continuation.resume() })
+ vc.present(alert, animated: true)
+ }
+ }
+
+ private static func topViewController() -> UIViewController? {
+ guard let scene = UIApplication.shared.connectedScenes
+ .compactMap({ $0 as? UIWindowScene })
+ .first(where: { $0.activationState == .foregroundActive }),
+ let root = scene.windows.first(where: { $0.isKeyWindow })?.rootViewController else { return nil }
+ var top = root
+ while let presented = top.presentedViewController { top = presented }
+ return top
+ }
+
// Whether the web composer currently has a photo staged. Drives the native send
// button so an image-only message can be sent. Kept in sync by the "lchat" handler.
@Published var attachmentStaged = false