Skip to content

Commit f6ff57e

Browse files
authored
Merge pull request #22 from SillyLittleTech/copilot/investigate-flean-ui-redirect-issue
fix: pre-warm wiki index on service worker startup; fix iOS UI navigation delegate (v2.3.0)
2 parents a70cbb7 + 548b3f5 commit f6ff57e

9 files changed

Lines changed: 190 additions & 147 deletions

File tree

ios/Flean.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@
394394
"@executable_path/Frameworks",
395395
"@executable_path/../../Frameworks",
396396
);
397-
MARKETING_VERSION = 2.2.0;
397+
MARKETING_VERSION = 2.3.0;
398398
OTHER_LDFLAGS = (
399399
"-framework",
400400
SafariServices,
@@ -430,7 +430,7 @@
430430
"@executable_path/Frameworks",
431431
"@executable_path/../../Frameworks",
432432
);
433-
MARKETING_VERSION = 2.2.0;
433+
MARKETING_VERSION = 2.3.0;
434434
OTHER_LDFLAGS = (
435435
"-framework",
436436
SafariServices,
@@ -588,7 +588,7 @@
588588
"$(inherited)",
589589
"@executable_path/Frameworks",
590590
);
591-
MARKETING_VERSION = 2.2.0;
591+
MARKETING_VERSION = 2.3.0;
592592
OTHER_LDFLAGS = (
593593
"-framework",
594594
SafariServices,
@@ -624,7 +624,7 @@
624624
"$(inherited)",
625625
"@executable_path/Frameworks",
626626
);
627-
MARKETING_VERSION = 2.2.0;
627+
MARKETING_VERSION = 2.3.0;
628628
OTHER_LDFLAGS = (
629629
"-framework",
630630
SafariServices,
@@ -651,7 +651,7 @@
651651
DEVELOPMENT_TEAM = PWL627GZ4Y;
652652
GENERATE_INFOPLIST_FILE = YES;
653653
MACOSX_DEPLOYMENT_TARGET = 10.14;
654-
MARKETING_VERSION = 2.2.0;
654+
MARKETING_VERSION = 2.3.0;
655655
PRODUCT_BUNDLE_IDENTIFIER = slf.FleanTests;
656656
PRODUCT_NAME = "$(TARGET_NAME)";
657657
SWIFT_EMIT_LOC_STRINGS = NO;
@@ -669,7 +669,7 @@
669669
DEVELOPMENT_TEAM = PWL627GZ4Y;
670670
GENERATE_INFOPLIST_FILE = YES;
671671
MACOSX_DEPLOYMENT_TARGET = 10.14;
672-
MARKETING_VERSION = 2.2.0;
672+
MARKETING_VERSION = 2.3.0;
673673
PRODUCT_BUNDLE_IDENTIFIER = slf.FleanTests;
674674
PRODUCT_NAME = "$(TARGET_NAME)";
675675
SWIFT_EMIT_LOC_STRINGS = NO;
@@ -685,7 +685,7 @@
685685
CURRENT_PROJECT_VERSION = 4;
686686
DEVELOPMENT_TEAM = PWL627GZ4Y;
687687
GENERATE_INFOPLIST_FILE = YES;
688-
MARKETING_VERSION = 2.2.0;
688+
MARKETING_VERSION = 2.3.0;
689689
PRODUCT_BUNDLE_IDENTIFIER = slf.FleanUITests;
690690
PRODUCT_NAME = "$(TARGET_NAME)";
691691
SWIFT_EMIT_LOC_STRINGS = NO;
@@ -701,7 +701,7 @@
701701
CURRENT_PROJECT_VERSION = 4;
702702
DEVELOPMENT_TEAM = PWL627GZ4Y;
703703
GENERATE_INFOPLIST_FILE = YES;
704-
MARKETING_VERSION = 2.2.0;
704+
MARKETING_VERSION = 2.3.0;
705705
PRODUCT_BUNDLE_IDENTIFIER = slf.FleanUITests;
706706
PRODUCT_NAME = "$(TARGET_NAME)";
707707
SWIFT_EMIT_LOC_STRINGS = NO;
Lines changed: 144 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,172 @@
11
// iOS-only web view container. Guard compilation so macOS builds don't attempt to
22
// compile UIKit/SwiftUI iOS-only APIs.
33
#if os(iOS)
4-
import SwiftUI
5-
import WebKit
6-
import UIKit
4+
import SwiftUI
5+
import WebKit
6+
import UIKit
77

8-
struct WebViewContainer: UIViewRepresentable {
8+
struct WebViewContainer: UIViewRepresentable {
99
func makeUIView(context: Context) -> WKWebView {
10-
let webCfg = WKWebViewConfiguration()
11-
let web = WKWebView(frame: .zero, configuration: webCfg)
12-
13-
// Add script message handler compatible with the macOS app's controller name
14-
web.configuration.userContentController.add(context.coordinator, name: "controller")
15-
16-
// Give the coordinator a reference to the web view so it can inject
17-
// settings back into the page when requested.
18-
context.coordinator.webView = web
19-
20-
// Load bundled Main.html from the app bundle resources
21-
if let url = Bundle.main.url(forResource: "Main", withExtension: "html", subdirectory: "Base.lproj") {
22-
web.loadFileURL(url, allowingReadAccessTo: Bundle.main.resourceURL!)
23-
}
24-
25-
return web
10+
let webCfg = WKWebViewConfiguration()
11+
let web = WKWebView(frame: .zero, configuration: webCfg)
12+
13+
// Add script message handler compatible with the macOS app's controller name
14+
web.configuration.userContentController.add(context.coordinator, name: "controller")
15+
web.navigationDelegate = context.coordinator
16+
17+
// Give the coordinator a reference to the web view so it can inject
18+
// settings back into the page when requested.
19+
context.coordinator.webView = web
20+
21+
// Load bundled Main.html from the app bundle resources
22+
if let url = Bundle.main.url(
23+
forResource: "Main", withExtension: "html", subdirectory: "Base.lproj"),
24+
let resourceURL = Bundle.main.resourceURL
25+
{
26+
web.loadFileURL(url, allowingReadAccessTo: resourceURL)
27+
}
28+
29+
return web
2630
}
2731

2832
func updateUIView(_ uiView: WKWebView, context: Context) {}
2933

3034
func makeCoordinator() -> Coordinator { Coordinator() }
3135

32-
class Coordinator: NSObject, WKScriptMessageHandler {
33-
// Weak reference to avoid retain cycles
34-
weak var webView: WKWebView?
35-
36-
override init() {
37-
super.init()
38-
NotificationCenter.default.addObserver(self, selector: #selector(settingsChanged(_:)), name: Notification.Name("FleanSettingsDidChange"), object: nil)
36+
class Coordinator: NSObject, WKScriptMessageHandler, WKNavigationDelegate {
37+
// Weak reference to avoid retain cycles
38+
weak var webView: WKWebView?
39+
40+
override init() {
41+
super.init()
42+
NotificationCenter.default.addObserver(
43+
self, selector: #selector(settingsChanged(_:)),
44+
name: Notification.Name("FleanSettingsDidChange"), object: nil)
45+
}
46+
47+
deinit {
48+
NotificationCenter.default.removeObserver(self)
49+
}
50+
51+
// MARK: - Settings storage in app container
52+
private func settingsFileURL() -> URL? {
53+
let fm = FileManager.default
54+
do {
55+
let appSupport = try fm.url(
56+
for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil,
57+
create: true)
58+
let dir = appSupport.appendingPathComponent("Flean", isDirectory: true)
59+
if !fm.fileExists(atPath: dir.path) {
60+
try fm.createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil)
61+
}
62+
return dir.appendingPathComponent("settings.json")
63+
} catch {
64+
NSLog("Flean: failed to get application support directory: %s", String(describing: error))
65+
return nil
3966
}
67+
}
4068

41-
deinit {
42-
NotificationCenter.default.removeObserver(self)
69+
private func loadSettings() -> [String: Any] {
70+
guard let url = settingsFileURL(), FileManager.default.fileExists(atPath: url.path) else {
71+
return [:]
4372
}
44-
45-
// MARK: - Settings storage in app container
46-
private func settingsFileURL() -> URL? {
47-
let fm = FileManager.default
48-
do {
49-
let appSupport = try fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
50-
let dir = appSupport.appendingPathComponent("Flean", isDirectory: true)
51-
if !fm.fileExists(atPath: dir.path) {
52-
try fm.createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil)
53-
}
54-
return dir.appendingPathComponent("settings.json")
55-
} catch {
56-
NSLog("Flean: failed to get application support directory: %s", String(describing: error))
57-
return nil
58-
}
73+
do {
74+
let data = try Data(contentsOf: url)
75+
if let obj = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
76+
return obj
77+
}
78+
} catch {
79+
NSLog("Flean: failed to load settings: %s", String(describing: error))
5980
}
60-
61-
private func loadSettings() -> [String: Any] {
62-
guard let url = settingsFileURL(), FileManager.default.fileExists(atPath: url.path) else { return [:] }
63-
do {
64-
let data = try Data(contentsOf: url)
65-
if let obj = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
66-
return obj
67-
}
68-
} catch {
69-
NSLog("Flean: failed to load settings: %s", String(describing: error))
70-
}
71-
return [:]
81+
return [:]
82+
}
83+
84+
private func saveSettings(_ dict: [String: Any]) -> Bool {
85+
guard let url = settingsFileURL() else { return false }
86+
do {
87+
let data = try JSONSerialization.data(withJSONObject: dict, options: [.prettyPrinted])
88+
try data.write(to: url, options: [.atomic])
89+
return true
90+
} catch {
91+
NSLog("Flean: failed to save settings: %s", String(describing: error))
92+
return false
7293
}
73-
74-
private func saveSettings(_ dict: [String: Any]) -> Bool {
75-
guard let url = settingsFileURL() else { return false }
76-
do {
77-
let data = try JSONSerialization.data(withJSONObject: dict, options: [.prettyPrinted])
78-
try data.write(to: url, options: [.atomic])
79-
return true
80-
} catch {
81-
NSLog("Flean: failed to save settings: %s", String(describing: error))
82-
return false
83-
}
94+
}
95+
96+
func userContentController(
97+
_ userContentController: WKUserContentController, didReceive message: WKScriptMessage
98+
) {
99+
// Expect either a string command or a dictionary with { action: "getSettings" | "setSettings", data: {...} }
100+
if let bodyStr = message.body as? String {
101+
if bodyStr == "open-preferences" {
102+
NSLog("Flean: open-preferences requested (iOS) — manual enable in Settings required")
103+
return
104+
}
105+
// legacy: handle simple 'get-settings' / 'set-settings' encoded strings
106+
if bodyStr == "get-settings" {
107+
sendSettingsToPage()
108+
return
109+
}
84110
}
85111

86-
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
87-
// Expect either a string command or a dictionary with { action: "getSettings" | "setSettings", data: {...} }
88-
if let bodyStr = message.body as? String {
89-
if bodyStr == "open-preferences" {
90-
NSLog("Flean: open-preferences requested (iOS) — manual enable in Settings required")
91-
return
92-
}
93-
// legacy: handle simple 'get-settings' / 'set-settings' encoded strings
94-
if bodyStr == "get-settings" {
95-
sendSettingsToPage()
96-
return
97-
}
98-
}
99-
100-
if let body = message.body as? [String: Any], let action = body["action"] as? String {
101-
switch action {
102-
case "getSettings":
103-
sendSettingsToPage()
104-
case "setSettings":
105-
if let data = body["data"] as? [String: Any] {
106-
let ok = saveSettings(data)
107-
// Acknowledge back to the page
108-
let ack = ["action": "setSettingsAck", "success": ok] as [String : Any]
109-
sendJSONToPage(ack)
110-
}
111-
default:
112-
break
113-
}
112+
if let body = message.body as? [String: Any], let action = body["action"] as? String {
113+
switch action {
114+
case "getSettings":
115+
sendSettingsToPage()
116+
case "setSettings":
117+
if let data = body["data"] as? [String: Any] {
118+
let ok = saveSettings(data)
119+
// Acknowledge back to the page
120+
let ack = ["action": "setSettingsAck", "success": ok] as [String: Any]
121+
sendJSONToPage(ack)
114122
}
123+
default:
124+
break
125+
}
115126
}
116-
117-
private func sendJSONToPage(_ obj: Any) {
118-
guard let web = webView else { return }
119-
do {
120-
let data = try JSONSerialization.data(withJSONObject: obj, options: [])
121-
if let json = String(data: data, encoding: .utf8) {
122-
// We dispatch a CustomEvent 'flean:message' with the JSON as detail
123-
let safeJSON = json.replacingOccurrences(of: "\\\"", with: "\\\\\"")
124-
let js = "window.dispatchEvent(new CustomEvent('flean:message', { detail: \(json) }));"
125-
web.evaluateJavaScript(js, completionHandler: nil)
126-
}
127-
} catch {
128-
NSLog("Flean: failed to serialize message to page: %s", String(describing: error))
129-
}
127+
}
128+
129+
private func sendJSONToPage(_ obj: Any) {
130+
guard let web = webView else { return }
131+
do {
132+
let data = try JSONSerialization.data(withJSONObject: obj, options: [])
133+
if let json = String(data: data, encoding: .utf8) {
134+
// We dispatch a CustomEvent 'flean:message' with the JSON as detail
135+
let safeJSON = json.replacingOccurrences(of: "\\\"", with: "\\\\\"")
136+
let js = "window.dispatchEvent(new CustomEvent('flean:message', { detail: \(json) }));"
137+
web.evaluateJavaScript(js, completionHandler: nil)
138+
}
139+
} catch {
140+
NSLog("Flean: failed to serialize message to page: %s", String(describing: error))
130141
}
142+
}
143+
144+
private func sendSettingsToPage() {
145+
let settings = loadSettings()
146+
sendJSONToPage(["action": "settings", "data": settings])
147+
}
148+
149+
@objc private func settingsChanged(_ note: Notification) {
150+
// Push updated settings into the web view when the SettingsStore saves.
151+
sendSettingsToPage()
152+
}
153+
154+
// MARK: - WKNavigationDelegate
155+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation?) {
156+
// Update the UI to use iOS-appropriate text. Extension state cannot be
157+
// queried programmatically on iOS (no SFSafariExtensionManager equivalent),
158+
// so we pass null for the enabled state (shows "unknown") and true for
159+
// useSettingsInsteadOfPreferences so the correct iOS wording is displayed.
160+
webView.evaluateJavaScript("show(null, true)", completionHandler: nil)
161+
}
131162

132-
private func sendSettingsToPage() {
133-
let settings = loadSettings()
134-
sendJSONToPage(["action": "settings", "data": settings])
135-
}
136-
137-
@objc private func settingsChanged(_ note: Notification) {
138-
// Push updated settings into the web view when the SettingsStore saves.
139-
sendSettingsToPage()
140-
}
141-
142163
}
143-
}
164+
}
144165

145-
struct WebViewContainer_Previews: PreviewProvider {
166+
struct WebViewContainer_Previews: PreviewProvider {
146167
static var previews: some View {
147-
WebViewContainer()
168+
WebViewContainer()
148169
}
149-
}
170+
}
150171

151172
#endif

ios/extention/Resources/background.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { getWikiData, fetchWikiData, findMatchingWiki, invalidateIndex } from './scripts/wiki-data-manager.js'
1+
import { fetchWikiData, findMatchingWiki, invalidateIndex, warmIndex } from './scripts/wiki-data-manager.js'
22

3-
// Initialise wiki data on extension startup (loads from cache or fetches fresh)
3+
// Initialise wiki data on extension startup and pre-warm the lookup index so the
4+
// first findWiki message from content.js is answered without needing to build the
5+
// index inside the 500ms race window.
46
async function initWikiData () {
57
try {
6-
await getWikiData()
8+
await warmIndex()
79
} catch (err) {
810
if (err) return
911
}

ios/extention/Resources/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
"name": "Flean Extension",
66
"description": "Redirect Fandom wiki pages to independent mirrors.",
7-
"version": "2.2.0",
7+
"version": "2.3.0",
88

99
"icons": {
1010
"48": "images/icon-48.png",

0 commit comments

Comments
 (0)