// SpeedTestView.swift // SpeedOf.Me API - macOS/SwiftUI Integration // // This file demonstrates how to integrate the SpeedOf.Me speed test // into a macOS app using WKWebView. import SwiftUI import WebKit // MARK: - Data Models (Same as iOS) struct SpeedTestResult: Codable { let download: Double let upload: Double let latency: Double let jitter: Double let testServer: String? let ip_address: String? let hostname: String? } struct SpeedTestProgress: Codable { let testType: String let currentSpeed: Double let percentComplete: Double let passNumber: Int } struct SpeedTestError: Codable { let code: Int let message: String } struct WebViewMessage: Codable { let type: String let data: AnyCodable } struct AnyCodable: Codable { let value: Any init(_ value: Any) { self.value = value } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let dict = try? container.decode([String: AnyCodable].self) { value = dict.mapValues { $0.value } } else if let array = try? container.decode([AnyCodable].self) { value = array.map { $0.value } } else if let string = try? container.decode(String.self) { value = string } else if let double = try? container.decode(Double.self) { value = double } else if let int = try? container.decode(Int.self) { value = int } else if let bool = try? container.decode(Bool.self) { value = bool } else { value = NSNull() } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encodeNil() } } // MARK: - Observable Model @MainActor class SpeedTestViewModel: ObservableObject { @Published var status: String = "Ready" @Published var result: SpeedTestResult? @Published var progress: SpeedTestProgress? @Published var error: SpeedTestError? @Published var isRunning: Bool = false func handleMessage(_ message: WebViewMessage) { switch message.type { case "ready": status = "Ready to test" case "started": isRunning = true result = nil error = nil status = "Starting test..." case "progress": if let data = message.data.value as? [String: Any], let jsonData = try? JSONSerialization.data(withJSONObject: data), let progress = try? JSONDecoder().decode(SpeedTestProgress.self, from: jsonData) { self.progress = progress let testType = progress.testType == "download" ? "Download" : "Upload" status = "\(testType): \(String(format: "%.1f", progress.currentSpeed)) Mbps" } case "completed": if let data = message.data.value as? [String: Any], let jsonData = try? JSONSerialization.data(withJSONObject: data), let result = try? JSONDecoder().decode(SpeedTestResult.self, from: jsonData) { self.result = result self.progress = nil isRunning = false status = "Test complete" } case "error": if let data = message.data.value as? [String: Any], let jsonData = try? JSONSerialization.data(withJSONObject: data), let error = try? JSONDecoder().decode(SpeedTestError.self, from: jsonData) { self.error = error isRunning = false status = "Error: \(error.message)" } default: break } } } // MARK: - WebView Wrapper (macOS) struct SpeedTestWebView: NSViewRepresentable { @ObservedObject var viewModel: SpeedTestViewModel func makeNSView(context: Context) -> WKWebView { let config = WKWebViewConfiguration() // Add message handler for JavaScript-to-Swift communication config.userContentController.add( context.coordinator, name: "speedTest" ) let webView = WKWebView(frame: .zero, configuration: config) webView.navigationDelegate = context.coordinator // Load the speed test HTML from bundle if let url = Bundle.main.url(forResource: "speedtest", withExtension: "html") { webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent()) } // Or load from remote server // if let url = URL(string: "https://your-domain.com/speedtest.html") { // webView.load(URLRequest(url: url)) // } return webView } func updateNSView(_ nsView: WKWebView, context: Context) { // No updates needed } func makeCoordinator() -> Coordinator { Coordinator(viewModel: viewModel) } class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler { var viewModel: SpeedTestViewModel init(viewModel: SpeedTestViewModel) { self.viewModel = viewModel } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == "speedTest", let body = message.body as? [String: Any], let jsonData = try? JSONSerialization.data(withJSONObject: body), let msg = try? JSONDecoder().decode(WebViewMessage.self, from: jsonData) else { return } Task { @MainActor in viewModel.handleMessage(msg) } } } } // MARK: - Main View struct SpeedTestView: View { @StateObject private var viewModel = SpeedTestViewModel() var body: some View { VStack(spacing: 0) { // Status bar HStack { Text(viewModel.status) .font(.headline) Spacer() if viewModel.isRunning { ProgressView() .scaleEffect(0.7) } } .padding() .background(Color(NSColor.windowBackgroundColor)) Divider() // WebView SpeedTestWebView(viewModel: viewModel) // Native results display (optional) if let result = viewModel.result { Divider() VStack(spacing: 12) { ResultRow(label: "Download", value: "\(result.download) Mbps") ResultRow(label: "Upload", value: "\(result.upload) Mbps") ResultRow(label: "Latency", value: "\(result.latency) ms") ResultRow(label: "Jitter", value: "\(result.jitter) ms") } .padding() .background(Color(NSColor.windowBackgroundColor)) } } } } struct ResultRow: View { let label: String let value: String var body: some View { HStack { Text(label) .foregroundColor(.secondary) Spacer() Text(value) .fontWeight(.semibold) .foregroundColor(.accentColor) } } } // MARK: - App Entry Point @main struct SpeedTestApp: App { var body: some Scene { WindowGroup { SpeedTestView() .frame(minWidth: 400, idealWidth: 450, minHeight: 500, idealHeight: 600) } .windowStyle(.titleBar) .windowResizability(.contentMinSize) } }