Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 3c11508

Browse files
Separate out automation steps into their own methods
1 parent f3ab4cc commit 3c11508

File tree

1 file changed

+138
-121
lines changed

1 file changed

+138
-121
lines changed

DuckDuckGo/AutomationServer.swift

Lines changed: 138 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,6 @@ extension Logger {
2323
static var automationServer = { Logger(subsystem: Bundle.main.bundleIdentifier ?? "DuckDuckGo", category: "Automation Server") }()
2424
}
2525

26-
struct Log: TextOutputStream {
27-
28-
func write(_ string: String) {
29-
let fm = FileManager.default
30-
let log = fm.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("log-automation.txt")
31-
if let handle = try? FileHandle(forWritingTo: log) {
32-
handle.seekToEndOfFile()
33-
handle.write(string.data(using: .utf8)!)
34-
handle.closeFile()
35-
} else {
36-
try? string.data(using: .utf8)?.write(to: log)
37-
}
38-
}
39-
}
4026

4127
class AutomationServer {
4228
let listener: NWListener
@@ -45,9 +31,7 @@ class AutomationServer {
4531
init(main: MainViewController, port: Int?) {
4632
var port = port ?? 8786
4733
self.main = main
48-
var log = Log()
49-
print("Starting automation server on port \(port)", to: &log)
50-
print("Bundle: \(Bundle.main.bundleIdentifier)", to: &log)
34+
Logger.automationServer.info("Starting automation server on port \(port)")
5135
listener = try! NWListener(using: .tcp, on: NWEndpoint.Port(integerLiteral: UInt16(port)))
5236
listener.newConnectionHandler = handleConnection
5337
listener.start(queue: .main)
@@ -72,12 +56,12 @@ class AutomationServer {
7256
return
7357
}
7458
Logger.automationServer.info("Received request! \(String(describing: content)) \(isComplete) \(String(describing: error))")
75-
59+
7660
if let error {
7761
Logger.automationServer.error("Error: \(error)")
7862
return
7963
}
80-
64+
8165
if let content {
8266
Logger.automationServer.info("Handling content")
8367
Task {
@@ -105,6 +89,10 @@ class AutomationServer {
10589
self.handleConnection(connection, content)
10690
}
10791

92+
func getQueryStringParameter(url: URLComponents, param: String) -> String? {
93+
return url.queryItems?.first(where: { $0.name == param })?.value
94+
}
95+
10896
@MainActor
10997
func handleConnection(_ connection: NWConnection, _ content: Data) {
11098
Logger.automationServer.info("Handling request!")
@@ -113,128 +101,157 @@ class AutomationServer {
113101
if let firstLine = stringContent.components(separatedBy: CharacterSet.newlines).first {
114102
Logger.automationServer.info("First line: \(firstLine)")
115103
}
116-
117-
func getQueryStringParameter(url: String, param: String) -> String? {
118-
guard let url = URLComponents(string: url) else { return nil }
119-
return url.queryItems?.first(where: { $0.name == param })?.value
120-
}
104+
121105
// Get url parameter from path
122106
// GET / HTTP/1.1
123107
if #available(iOS 16.0, *) {
124108
let path = /^(GET|POST) (\/[^ ]*) HTTP/
125109
if let match = stringContent.firstMatch(of: path) {
126110
Logger.automationServer.info("Path: \(match.2)")
127111
// Convert the path into a URL object
128-
guard let url = URL(string: String(match.2)) else {
129-
print("Invalid URL: \(match.2)")
112+
guard let url = URLComponents(string: String(match.2)) else {
113+
Logger.automationServer.error("Invalid URL: \(match.2)")
130114
return // Or handle the error appropriately
131115
}
132-
if url.path == "/navigate" {
133-
let navigateUrlString = getQueryStringParameter(url: String(match.2), param: "url") ?? ""
134-
let navigateUrl = URL(string: navigateUrlString)!
135-
self.main.loadUrl(navigateUrl)
136-
self.respond(on: connection, response: "done")
137-
} else if url.path == "/execute" {
138-
let script = getQueryStringParameter(url: String(match.2), param: "script") ?? ""
139-
var args: [String: String] = [:]
140-
// json decode args
141-
if let argsString = getQueryStringParameter(url: String(match.2), param: "args") {
142-
if let argsData = argsString.data(using: .utf8) {
143-
do {
144-
let jsonDecoder = JSONDecoder()
145-
args = try jsonDecoder.decode([String: String].self, from: argsData)
146-
} catch {
147-
self.respond(on: connection, response: "{\"error\": \"\(error.localizedDescription)\", \"args\": \"\(argsString)\"}")
148-
}
149-
} else {
150-
self.respond(on: connection, response: "{\"error\": \"Unable to decode args\"}")
151-
}
152-
}
153-
Task {
154-
await self.executeScript(script, args: args, on: connection)
155-
}
156-
} else if url.path == "/getUrl" {
116+
switch url.path {
117+
case "/navigate":
118+
self.navigate(on: connection, url: url)
119+
case "/execute":
120+
self.execute(on: connection, url: url)
121+
case "/getUrl":
157122
self.respond(on: connection, response: self.main.currentUrl() ?? "")
158-
} else if url.path == "/getWindowHandles" {
159-
// TODO get all tabs
160-
let handle = self.main.tabManager.current(createIfNeeded: true)
161-
guard let handle else {
162-
self.respond(on: connection, response: "no window")
163-
return
164-
}
165-
166-
let handles = self.main.tabManager.model.tabs.map({ tab in
167-
let tabView = self.main.tabManager.controller(for: tab)!
168-
return String(UInt(bitPattern: ObjectIdentifier(tabView)))
169-
})
170-
171-
if let jsonData = try? JSONEncoder().encode(handles),
172-
let jsonString = String(data: jsonData, encoding: .utf8) {
173-
self.respond(on: connection, response: jsonString)
174-
} else {
175-
// Handle JSON encoding failure
176-
self.respond(on: connection, response: "{\"error\":\"Failed to encode response\"}")
177-
}
178-
} else if url.path == "/closeWindow" {
179-
self.main.closeTab(self.main.currentTab!.tabModel)
180-
self.respond(on: connection, response: "{\"success\":true}")
181-
} else if url.path == "/switchToWindow" {
182-
if let handleString = getQueryStringParameter(url: String(match.2), param: "handle") {
183-
Logger.automationServer.info("Switch to window \(handleString)")
184-
let tabToSelect: TabViewController? = nil
185-
if let tabIndex = self.main.tabManager.model.tabs.firstIndex(where: { tab in
186-
guard let tabView = self.main.tabManager.controller(for: tab) else {
187-
return false
188-
}
189-
return String(UInt(bitPattern: ObjectIdentifier(tabView))) == handleString
190-
}) {
191-
Logger.automationServer.info("found tab \(tabIndex)")
192-
self.main.tabManager.select(tabAt: tabIndex)
193-
self.respond(on: connection, response: "{\"success\":true}")
194-
} else {
195-
self.respond(on: connection, response: "{\"error\":\"Invalid window handle\"}")
196-
}
197-
} else {
198-
self.respond(on: connection, response: "{\"error\":\"Invalid window handle\"}")
199-
}
200-
} else if url.path == "/newWindow" {
201-
self.main.newTab()
202-
let handle = self.main.tabManager.current(createIfNeeded: true)
203-
guard let handle else {
204-
self.respond(on: connection, response: "no window")
205-
return
206-
}
207-
// Response {handle: "", type: "tab"}
208-
let response: [String: String] = ["handle": String(UInt(bitPattern: ObjectIdentifier(handle))), "type": "tab"]
209-
if let jsonData = try? JSONEncoder().encode(response),
210-
let jsonString = String(data: jsonData, encoding: .utf8) {
211-
self.respond(on: connection, response: jsonString)
212-
} else {
213-
self.respond(on: connection, response: "{\"error\":\"Failed to encode response\"}")
214-
}
215-
} else if url.path == "/getWindowHandle" {
216-
let handle = self.main.currentTab
217-
guard let handle else {
218-
self.respond(on: connection, response: "no window")
219-
return
220-
}
221-
self.respond(on: connection, response: String(UInt(bitPattern: ObjectIdentifier(handle))))
222-
} else {
223-
self.respond(on: connection, response: "unknown")
123+
case "/getWindowHandles":
124+
self.getWindowHandles(on: connection, url: url)
125+
case "/closeWindow":
126+
self.closeWindow(on: connection, url: url)
127+
case "/switchToWindow":
128+
self.switchToWindow(on: connection, url: url)
129+
case "/newWindow":
130+
self.newWindow(on: connection, url: url)
131+
case "/getWindowHandle":
132+
self.getWindowHandle(on: connection, url: url)
133+
default:
134+
self.respondError(on: connection, error: "unknown")
224135
}
225136
} else {
226-
self.respond(on: connection, response: "unknown method")
137+
self.respondError(on: connection, error: "unknown method")
227138
}
228139
} else {
229-
self.respond(on: connection, response: "unhandled")
140+
self.respondError(on: connection, error: "unhandled")
141+
}
142+
}
143+
144+
@MainActor
145+
func navigate(on connection: NWConnection, url: URLComponents) {
146+
let navigateUrlString = getQueryStringParameter(url: url, param: "url") ?? ""
147+
let navigateUrl = URL(string: navigateUrlString)!
148+
self.main.loadUrl(navigateUrl)
149+
self.respond(on: connection, response: "done")
150+
}
151+
152+
@MainActor
153+
func execute(on connection: NWConnection, url: URLComponents) {
154+
let script = getQueryStringParameter(url: url, param: "script") ?? ""
155+
var args: [String: String] = [:]
156+
// json decode args
157+
if let argsString = getQueryStringParameter(url: url, param: "args") {
158+
if let argsData = argsString.data(using: .utf8) {
159+
do {
160+
let jsonDecoder = JSONDecoder()
161+
args = try jsonDecoder.decode([String: String].self, from: argsData)
162+
} catch {
163+
self.respondError(on: connection, error: error.localizedDescription)
164+
}
165+
} else {
166+
self.respondError(on: connection, error: "Unable to decode args")
167+
}
230168
}
169+
Task {
170+
await self.executeScript(script, args: args, on: connection)
171+
}
172+
}
173+
174+
@MainActor
175+
func getWindowHandle(on connection: NWConnection, url: URLComponents) {
176+
let handle = self.main.currentTab
177+
guard let handle else {
178+
self.respondError(on: connection, error: "no window")
179+
return
180+
}
181+
self.respond(on: connection, response: String(UInt(bitPattern: ObjectIdentifier(handle))))
182+
}
183+
184+
@MainActor
185+
func getWindowHandles(on connection: NWConnection, url: URLComponents) {
186+
let handles = self.main.tabManager.model.tabs.map({ tab in
187+
let tabView = self.main.tabManager.controller(for: tab)!
188+
return String(UInt(bitPattern: ObjectIdentifier(tabView)))
189+
})
190+
191+
if let jsonData = try? JSONEncoder().encode(handles),
192+
let jsonString = String(data: jsonData, encoding: .utf8) {
193+
self.respond(on: connection, response: jsonString)
194+
} else {
195+
// Handle JSON encoding failure
196+
self.respondError(on: connection, error: "Failed to encode response")
197+
}
198+
}
199+
200+
@MainActor
201+
func closeWindow(on connection: NWConnection, url: URLComponents) {
202+
self.main.closeTab(self.main.currentTab!.tabModel)
203+
self.respond(on: connection, response: "{\"success\":true}")
204+
}
205+
206+
@MainActor
207+
func switchToWindow(on connection: NWConnection, url: URLComponents) {
208+
if let handleString = getQueryStringParameter(url: url, param: "handle") {
209+
Logger.automationServer.info("Switch to window \(handleString)")
210+
let tabToSelect: TabViewController? = nil
211+
if let tabIndex = self.main.tabManager.model.tabs.firstIndex(where: { tab in
212+
guard let tabView = self.main.tabManager.controller(for: tab) else {
213+
return false
214+
}
215+
return String(UInt(bitPattern: ObjectIdentifier(tabView))) == handleString
216+
}) {
217+
Logger.automationServer.info("found tab \(tabIndex)")
218+
self.main.tabManager.select(tabAt: tabIndex)
219+
self.respond(on: connection, response: "{\"success\":true}")
220+
} else {
221+
self.respondError(on: connection, error: "Invalid window handle")
222+
}
223+
} else {
224+
self.respondError(on: connection, error: "Invalid window handle")
225+
}
226+
}
227+
228+
@MainActor
229+
func newWindow(on connection: NWConnection, url: URLComponents) {
230+
self.main.newTab()
231+
let handle = self.main.tabManager.current(createIfNeeded: true)
232+
guard let handle else {
233+
self.respondError(on: connection, error: "no window")
234+
return
235+
}
236+
// Response {handle: "", type: "tab"}
237+
let response: [String: String] = ["handle": String(UInt(bitPattern: ObjectIdentifier(handle))), "type": "tab"]
238+
if let jsonData = try? JSONEncoder().encode(response),
239+
let jsonString = String(data: jsonData, encoding: .utf8) {
240+
self.respond(on: connection, response: jsonString)
241+
} else {
242+
self.respondError(on: connection, error: "Failed to encode response")
243+
}
244+
}
245+
246+
func respondError(on connection: NWConnection, error: String) {
247+
self.respond(on: connection, response: "{\"error\": \"\(error)\"}")
231248
}
232249

233250
func executeScript(_ script: String, args: [String: Any], on connection: NWConnection) async {
234251
Logger.automationServer.info("Going to execute script: \(script)")
235-
var result = await main.executeScript(script, args: args)
252+
let result = await main.executeScript(script, args: args)
236253
Logger.automationServer.info("Have result to execute script: \(String(describing: result))")
237-
guard var result else {
254+
guard let result else {
238255
return
239256
}
240257
do {

0 commit comments

Comments
 (0)