From 9983cfbaeafacfbcdce80375985fbd251aeac239 Mon Sep 17 00:00:00 2001
From: Ryan ZHAO <>
Date: Wed, 19 Feb 2025 10:49:39 +1100
Subject: [PATCH] make local network access permission check wokr
---
Session/Meta/Session-Info.plist | 4 +
Session/Utilities/Permissions.swift | 139 +++++++++++++++++++++++++---
2 files changed, 128 insertions(+), 15 deletions(-)
diff --git a/Session/Meta/Session-Info.plist b/Session/Meta/Session-Info.plist
index bd9e0d8cd..ef0b99b51 100644
--- a/Session/Meta/Session-Info.plist
+++ b/Session/Meta/Session-Info.plist
@@ -152,5 +152,9 @@
UIViewControllerBasedStatusBarAppearance
+ NSBonjourServices
+
+ _preflight_check._tcp
+
diff --git a/Session/Utilities/Permissions.swift b/Session/Utilities/Permissions.swift
index 2078c5f8b..d237f986b 100644
--- a/Session/Utilities/Permissions.swift
+++ b/Session/Utilities/Permissions.swift
@@ -172,25 +172,134 @@ extension Permissions {
}
public static func checkLocalNetworkPermission() {
- let connection = NWConnection(host: "192.168.1.1", port: 80, using: .tcp)
- connection.stateUpdateHandler = { newState in
- switch newState {
- case .ready:
+ Task {
+ do {
+ if try await requestLocalNetworkAuthorization() {
+ // Permission is granted, continue to next onboarding step
UserDefaults.sharedLokiProject?[.lastSeenHasLocalNetworkPermission] = true
- connection.cancel() // Stop connection since we only need permission status
- case .failed(let error):
- switch error {
- case .posix(let code):
- if code.rawValue == 13 {
- UserDefaults.sharedLokiProject?[.lastSeenHasLocalNetworkPermission] = false
+ } else {
+ // Permission denied, explain why we need it and show button to open Settings
+ UserDefaults.sharedLokiProject?[.lastSeenHasLocalNetworkPermission] = false
+ }
+ } catch {
+ // Networking failure, handle error
+ }
+ }
+ }
+
+ public static func requestLocalNetworkAuthorization() async throws -> Bool {
+ let type = "_preflight_check._tcp"
+ let queue = DispatchQueue(label: "com.nonstrict.localNetworkAuthCheck")
+
+ let listener = try NWListener(using: NWParameters(tls: .none, tcp: NWProtocolTCP.Options()))
+ listener.service = NWListener.Service(name: UUID().uuidString, type: type)
+ listener.newConnectionHandler = { _ in } // Must be set or else the listener will error with POSIX error 22
+
+ let parameters = NWParameters()
+ parameters.includePeerToPeer = true
+ let browser = NWBrowser(for: .bonjour(type: type, domain: nil), using: parameters)
+
+ return try await withTaskCancellationHandler {
+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in
+ class LocalState {
+ var didResume = false
+ }
+ let local = LocalState()
+ @Sendable func resume(with result: Result) {
+ if local.didResume {
+ print("Already resumed, ignoring subsequent result.")
+ return
+ }
+ local.didResume = true
+
+ // Teardown listener and browser
+ listener.stateUpdateHandler = { _ in }
+ browser.stateUpdateHandler = { _ in }
+ browser.browseResultsChangedHandler = { _, _ in }
+ listener.cancel()
+ browser.cancel()
+
+ continuation.resume(with: result)
+ }
+
+ // Do not setup listener/browser is we're already cancelled, it does work but logs a lot of very ugly errors
+ if Task.isCancelled {
+ resume(with: .failure(CancellationError()))
+ return
+ }
+
+ listener.stateUpdateHandler = { newState in
+ switch newState {
+ case .setup:
+ print("Listener performing setup.")
+ case .ready:
+ print("Listener ready to be discovered.")
+ case .cancelled:
+ print("Listener cancelled.")
+ resume(with: .failure(CancellationError()))
+ case .failed(let error):
+ print("Listener failed, stopping. \(error)")
+ resume(with: .failure(error))
+ case .waiting(let error):
+ print("Listener waiting, stopping. \(error)")
+ resume(with: .failure(error))
+ @unknown default:
+ print("Ignoring unknown listener state: \(String(describing: newState))")
+ }
+ }
+ listener.start(queue: queue)
+
+ browser.stateUpdateHandler = { newState in
+ switch newState {
+ case .setup:
+ print("Browser performing setup.")
+ return
+ case .ready:
+ print("Browser ready to discover listeners.")
+ return
+ case .cancelled:
+ print("Browser cancelled.")
+ resume(with: .failure(CancellationError()))
+ case .failed(let error):
+ print("Browser failed, stopping. \(error)")
+ resume(with: .failure(error))
+ case let .waiting(error):
+ switch error {
+ case .dns(DNSServiceErrorType(kDNSServiceErr_PolicyDenied)):
+ print("Browser permission denied, reporting failure.")
+ resume(with: .success(false))
+ default:
+ print("Browser waiting, stopping. \(error)")
+ resume(with: .failure(error))
}
- default:
- break
+ @unknown default:
+ print("Ignoring unknown browser state: \(String(describing: newState))")
+ return
}
- default:
- break
+ }
+
+ browser.browseResultsChangedHandler = { results, changes in
+ if results.isEmpty {
+ print("Got empty result set from browser, ignoring.")
+ return
+ }
+
+ print("Discovered \(results.count) listeners, reporting success.")
+ resume(with: .success(true))
+ }
+ browser.start(queue: queue)
+
+ // Task cancelled while setting up listener & browser, tear down immediatly
+ if Task.isCancelled {
+ print("Task cancelled during listener & browser start. (Some warnings might be logged by the listener or browser.)")
+ resume(with: .failure(CancellationError()))
+ return
+ }
}
+ } onCancel: {
+ listener.cancel()
+ browser.cancel()
}
- connection.start(queue: .main)
}
}
+