136 lines
4.6 KiB
Swift
136 lines
4.6 KiB
Swift
import Foundation
|
|
import Libbox
|
|
import Network
|
|
|
|
public class ProfileServer {
|
|
private var listener: NWListener
|
|
|
|
@available(iOS 16.0, macOS 13.0, *)
|
|
public init() throws {
|
|
listener = try NWListener(using: .applicationService)
|
|
listener.service = NWListener.Service(applicationService: "sing-box:profile")
|
|
listener.newConnectionHandler = { connection in
|
|
connection.stateUpdateHandler = { state in
|
|
if state == .ready {
|
|
Task.detached {
|
|
try await Task.sleep(nanoseconds: NSEC_PER_MSEC * 100)
|
|
await ProfileConnection(connection).process()
|
|
}
|
|
}
|
|
}
|
|
connection.start(queue: .global())
|
|
}
|
|
}
|
|
|
|
public func start() {
|
|
listener.start(queue: .global())
|
|
}
|
|
|
|
public func cancel() {
|
|
listener.cancel()
|
|
}
|
|
|
|
class ProfileConnection {
|
|
private let connection: NWSocket
|
|
|
|
init(_ connection: NWConnection) {
|
|
self.connection = NWSocket(connection)
|
|
}
|
|
|
|
func process() async {
|
|
do {
|
|
try await writeProfilePreviewList()
|
|
} catch {
|
|
NSLog("profile server: write profile list: \(error.localizedDescription)")
|
|
writeError(error.localizedDescription)
|
|
return
|
|
}
|
|
do {
|
|
while true {
|
|
let message = try connection.read()
|
|
try processMessage(message)
|
|
}
|
|
} catch {
|
|
NSLog("profile server: process connection: \(error.localizedDescription)")
|
|
writeError(error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
private func processMessage(_ data: Data) throws {
|
|
if data.count == 0 {
|
|
return
|
|
}
|
|
let messageType = Int64(data[0])
|
|
switch messageType {
|
|
case LibboxMessageTypeProfileContentRequest:
|
|
Task {
|
|
try await processProfileContentRequest(data)
|
|
}
|
|
default:
|
|
throw NSError(domain: "unexpected message type \(messageType)", code: 0)
|
|
}
|
|
}
|
|
|
|
private func processProfileContentRequest(_ data: Data) async throws {
|
|
var error: NSError?
|
|
let request = LibboxDecodeProfileContentRequest(data, &error)
|
|
if let error {
|
|
throw error
|
|
}
|
|
|
|
let profile = try await ProfileManager.get(request!.profileID)
|
|
guard let profile else {
|
|
throw NSError(domain: "profile not found", code: 0)
|
|
}
|
|
let content = LibboxProfileContent()
|
|
content.name = profile.name
|
|
switch profile.type {
|
|
case .local:
|
|
content.type = LibboxProfileTypeLocal
|
|
case .icloud:
|
|
content.type = LibboxProfileTypeiCloud
|
|
case .remote:
|
|
content.type = LibboxProfileTypeRemote
|
|
}
|
|
content.config = try profile.read()
|
|
if profile.type != .local {
|
|
content.remotePath = profile.remoteURL!
|
|
}
|
|
if profile.type == .remote {
|
|
content.autoUpdate = profile.autoUpdate
|
|
content.autoUpdateInterval = profile.autoUpdateInterval
|
|
if let lastUpdated = profile.lastUpdated {
|
|
content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970)
|
|
}
|
|
}
|
|
try connection.write(content.encode())
|
|
}
|
|
|
|
private func writeProfilePreviewList() async throws {
|
|
let profiles = try await ProfileManager.list()
|
|
let encoder = LibboxProfileEncoder()
|
|
for profile in profiles {
|
|
let preview = LibboxProfilePreview()
|
|
preview.profileID = profile.mustID
|
|
preview.name = profile.name
|
|
switch profile.type {
|
|
case .local:
|
|
preview.type = LibboxProfileTypeLocal
|
|
case .icloud:
|
|
preview.type = LibboxProfileTypeiCloud
|
|
case .remote:
|
|
preview.type = LibboxProfileTypeRemote
|
|
}
|
|
encoder.append(preview)
|
|
}
|
|
try connection.write(encoder.encode())
|
|
}
|
|
|
|
private func writeError(_ message: String) {
|
|
let errorMessage = LibboxErrorMessage()
|
|
errorMessage.message = message
|
|
try? connection.write(errorMessage.encode())
|
|
}
|
|
}
|
|
}
|