Added ability to add tunnels with a QR code scan. Logic in place to parse conf files as well.
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
parent
c2b591cc44
commit
39ae9db11c
|
@ -46,6 +46,8 @@
|
|||
5FA1D4CD2124A05C00DBA2E6 /* Interface+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA1D4CC2124A05C00DBA2E6 /* Interface+Extension.swift */; };
|
||||
5FA1D5102124D80C00DBA2E6 /* String+Arrays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA1D50F2124D80C00DBA2E6 /* String+Arrays.swift */; };
|
||||
5FA1D5122124DA6400DBA2E6 /* String+Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA1D5112124DA6400DBA2E6 /* String+Base64.swift */; };
|
||||
5FCC4343212B3092009A9C58 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FCC4342212B3092009A9C58 /* QRScanViewController.swift */; };
|
||||
5FCC4347212B3E2C009A9C58 /* Attribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FCC4346212B3E2C009A9C58 /* Attribute.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -126,6 +128,8 @@
|
|||
5FA1D4CC2124A05C00DBA2E6 /* Interface+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Interface+Extension.swift"; sourceTree = "<group>"; };
|
||||
5FA1D50F2124D80C00DBA2E6 /* String+Arrays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Arrays.swift"; sourceTree = "<group>"; };
|
||||
5FA1D5112124DA6400DBA2E6 /* String+Base64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Base64.swift"; sourceTree = "<group>"; };
|
||||
5FCC4342212B3092009A9C58 /* QRScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; };
|
||||
5FCC4346212B3E2C009A9C58 /* Attribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attribute.swift; sourceTree = "<group>"; };
|
||||
861983CAE8FDC13BC83E7E04 /* Pods_WireGuard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WireGuard.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -235,6 +239,7 @@
|
|||
4A8AABD720B6A79100B6D8C1 /* UITableView+WireGuard.swift */,
|
||||
4A4BACE720B5F1BF00F12B28 /* TunnelsTableViewController.swift */,
|
||||
4A4BA6D720B73CBA00223AB8 /* TunnelConfigurationTableViewController.swift */,
|
||||
5FCC4342212B3092009A9C58 /* QRScanViewController.swift */,
|
||||
);
|
||||
path = ViewControllers;
|
||||
sourceTree = "<group>";
|
||||
|
@ -252,6 +257,7 @@
|
|||
4A4BAD1920B5F8FF00F12B28 /* Tunnel+CoreDataProperties.swift */,
|
||||
4AC5462D2116306F00749D21 /* Tunnel+Extension.swift */,
|
||||
4A4BAD1520B5F8DE00F12B28 /* WireGuard.xcdatamodeld */,
|
||||
5FCC4346212B3E2C009A9C58 /* Attribute.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -550,6 +556,7 @@
|
|||
4AEAC32B20F14BA9007B67AB /* Log.swift in Sources */,
|
||||
4A4BAD1320B5F82400F12B28 /* Identifyable.swift in Sources */,
|
||||
4A4BAD1720B5F8DE00F12B28 /* WireGuard.xcdatamodeld in Sources */,
|
||||
5FCC4347212B3E2C009A9C58 /* Attribute.swift in Sources */,
|
||||
4A4BAD1A20B5F8FF00F12B28 /* Tunnel+CoreDataClass.swift in Sources */,
|
||||
4A4BACE820B5F1BF00F12B28 /* TunnelsTableViewController.swift in Sources */,
|
||||
4A4BAD1020B5F6EC00F12B28 /* RootCoordinator.swift in Sources */,
|
||||
|
@ -557,6 +564,7 @@
|
|||
5FA1D4CB21249F7D00DBA2E6 /* Peer+Extension.swift in Sources */,
|
||||
5FA1D5122124DA6400DBA2E6 /* String+Base64.swift in Sources */,
|
||||
4AC5462E2116306F00749D21 /* Tunnel+Extension.swift in Sources */,
|
||||
5FCC4343212B3092009A9C58 /* QRScanViewController.swift in Sources */,
|
||||
4A4BAD0E20B5F6C300F12B28 /* Coordinator.swift in Sources */,
|
||||
4A4BA6D820B73CBA00223AB8 /* TunnelConfigurationTableViewController.swift in Sources */,
|
||||
4A4BAD2020B6026900F12B28 /* Peer+CoreDataProperties.swift in Sources */,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
@ -73,12 +74,12 @@
|
|||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="4uZ-Vv-Fry" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="34" y="154"/>
|
||||
<point key="canvasLocation" x="-670" y="200"/>
|
||||
</scene>
|
||||
<!--Tunnel settings-->
|
||||
<scene sceneID="xV8-BW-4R7">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="TunnelConfigurationTableViewController" id="0VM-73-EPX" customClass="TunnelConfigurationTableViewController" customModule="WireGuard" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController storyboardIdentifier="TunnelConfigurationTableViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0VM-73-EPX" customClass="TunnelConfigurationTableViewController" customModule="WireGuard" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="0Uy-k2-O3i">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
|
@ -472,7 +473,37 @@
|
|||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="j96-PK-ghN" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1128.8" y="321.58920539730138"/>
|
||||
<point key="canvasLocation" x="122" y="-239"/>
|
||||
</scene>
|
||||
<!--Scan Code-->
|
||||
<scene sceneID="gKN-k2-HoW">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="QRScanViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="Efe-yN-iDH" customClass="QRScanViewController" customModule="WireGuard" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="NXo-On-ea8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tip: generate with `qrencode -t ansiutf8 < tunnel.conf`" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XYc-tx-YNF">
|
||||
<rect key="frame" x="16" y="628.5" width="343" height="14.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Soo-c9-MsX" firstAttribute="bottom" secondItem="XYc-tx-YNF" secondAttribute="bottom" constant="24" id="QhS-p5-jbw"/>
|
||||
<constraint firstItem="XYc-tx-YNF" firstAttribute="leading" secondItem="NXo-On-ea8" secondAttribute="leading" constant="16" id="Y3P-Py-ueV"/>
|
||||
<constraint firstAttribute="trailing" secondItem="XYc-tx-YNF" secondAttribute="trailing" constant="16" id="sB1-h9-ueh"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="Soo-c9-MsX"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Scan Code" largeTitleDisplayMode="never" id="WGY-tY-ySz"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="TQ2-zp-o40" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="121" y="461"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
|
|
@ -32,7 +32,6 @@ class AppCoordinator: RootViewCoordinator {
|
|||
return self.tunnelsTableViewController
|
||||
}
|
||||
|
||||
|
||||
var tunnelsTableViewController: TunnelsTableViewController!
|
||||
|
||||
/// Window to manage
|
||||
|
@ -134,10 +133,33 @@ class AppCoordinator: RootViewCoordinator {
|
|||
|
||||
extension AppCoordinator: TunnelsTableViewControllerDelegate {
|
||||
func addProvider(tunnelsTableViewController: TunnelsTableViewController) {
|
||||
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
actionSheet.addAction(UIAlertAction(title: "Add Manually", style: .default) { [unowned self] _ in
|
||||
self.addProviderManually()
|
||||
})
|
||||
actionSheet.addAction(UIAlertAction(title: "Scan QR Code", style: .default) { [unowned self] _ in
|
||||
self.addProviderWithQRScan()
|
||||
})
|
||||
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
|
||||
tunnelsTableViewController.present(actionSheet, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func addProviderManually() {
|
||||
let addContext = persistentContainer.newBackgroundContext()
|
||||
showTunnelConfigurationViewController(tunnel: nil, context: addContext)
|
||||
}
|
||||
|
||||
func addProviderWithQRScan() {
|
||||
let addContext = persistentContainer.newBackgroundContext()
|
||||
|
||||
let qrScanViewController = storyboard.instantiateViewController(type: QRScanViewController.self)
|
||||
|
||||
qrScanViewController.configure(context: addContext, delegate: self)
|
||||
|
||||
self.navigationController.pushViewController(qrScanViewController, animated: true)
|
||||
}
|
||||
|
||||
func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) {
|
||||
let manager = self.providerManager(for: tunnel)!
|
||||
let block = {
|
||||
|
@ -237,10 +259,8 @@ extension AppCoordinator: TunnelsTableViewControllerDelegate {
|
|||
return tunnelIdentifier == tunnel.tunnelIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppCoordinator: TunnelConfigurationTableViewControllerDelegate {
|
||||
func didSave(tunnel: Tunnel, tunnelConfigurationTableViewController: TunnelConfigurationTableViewController) {
|
||||
private func saveTunnel(_ tunnel: Tunnel) {
|
||||
let manager = providerManager(for: tunnel) ?? NETunnelProviderManager()
|
||||
manager.localizedDescription = tunnel.title
|
||||
|
||||
|
@ -265,5 +285,18 @@ extension AppCoordinator: TunnelConfigurationTableViewControllerDelegate {
|
|||
|
||||
navigationController.popToRootViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension AppCoordinator: TunnelConfigurationTableViewControllerDelegate {
|
||||
func didSave(tunnel: Tunnel, tunnelConfigurationTableViewController: TunnelConfigurationTableViewController) {
|
||||
saveTunnel(tunnel)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AppCoordinator: QRScanViewControllerDelegate {
|
||||
func didSave(tunnel: Tunnel, qrScanViewController: QRScanViewController) {
|
||||
showTunnelConfigurationViewController(tunnel: tunnel, context: tunnel.managedObjectContext!)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Camera is used to scan QR codes</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Attribute.swift
|
||||
// WireGuard
|
||||
//
|
||||
// Created by Eric Kuck on 8/20/18.
|
||||
// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Attribute {
|
||||
|
||||
enum Key: String, CaseIterable {
|
||||
case address = "Address"
|
||||
case allowedIPs = "AllowedIPs"
|
||||
case dns = "DNS"
|
||||
case endpoint = "Endpoint"
|
||||
case listenPort = "ListenPort"
|
||||
case mtu = "MTU"
|
||||
case persistentKeepalive = "PersistentKeepalive"
|
||||
case presharedKey = "PresharedKey"
|
||||
case privateKey = "PrivateKey"
|
||||
case publicKey = "PublicKey"
|
||||
}
|
||||
|
||||
private static let separatorPattern = (try? NSRegularExpression(pattern: "\\s|=", options: []))!
|
||||
|
||||
let line: String
|
||||
let key: Key
|
||||
let stringValue: String
|
||||
var arrayValue: [String] {
|
||||
return stringValue.commaSeparatedToArray()
|
||||
}
|
||||
|
||||
static func match(line: String) -> Attribute? {
|
||||
guard let equalsIndex = line.firstIndex(of: "=") else { return nil }
|
||||
let keyString = line[..<equalsIndex].trimmingCharacters(in: .whitespaces)
|
||||
let value = line[line.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespaces)
|
||||
guard let key = Key.allCases.first(where: { $0.rawValue.lowercased() == keyString.lowercased() }) else { return nil }
|
||||
|
||||
return Attribute(line: line, key: key, stringValue: value)
|
||||
}
|
||||
|
||||
}
|
|
@ -36,6 +36,27 @@ extension Interface {
|
|||
}
|
||||
}
|
||||
|
||||
func parse(attribute: Attribute) throws {
|
||||
switch attribute.key {
|
||||
case .address:
|
||||
addresses = attribute.stringValue
|
||||
case .dns:
|
||||
dns = attribute.stringValue
|
||||
case .listenPort:
|
||||
if let port = Int16(attribute.stringValue) {
|
||||
listenPort = port
|
||||
}
|
||||
case .mtu:
|
||||
if let mtu = Int32(attribute.stringValue) {
|
||||
self.mtu = mtu
|
||||
}
|
||||
case .privateKey:
|
||||
privateKey = attribute.stringValue
|
||||
default:
|
||||
throw TunnelParseError.invalidLine(attribute.line)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum InterfaceValidationError: Error {
|
||||
|
|
|
@ -44,6 +44,25 @@ extension Peer {
|
|||
}
|
||||
}
|
||||
|
||||
func parse(attribute: Attribute) throws {
|
||||
switch attribute.key {
|
||||
case .allowedIPs:
|
||||
allowedIPs = attribute.stringValue
|
||||
case .endpoint:
|
||||
endpoint = attribute.stringValue
|
||||
case .persistentKeepalive:
|
||||
if let keepAlive = Int32(attribute.stringValue) {
|
||||
persistentKeepalive = keepAlive
|
||||
}
|
||||
case .presharedKey:
|
||||
presharedKey = attribute.stringValue
|
||||
case .publicKey:
|
||||
publicKey = attribute.stringValue
|
||||
default:
|
||||
throw TunnelParseError.invalidLine(attribute.line)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum PeerValidationError: Error {
|
||||
|
|
|
@ -111,6 +111,54 @@ extension Tunnel {
|
|||
}
|
||||
}
|
||||
|
||||
static func fromConfig(_ text: String, context: NSManagedObjectContext) throws -> Tunnel {
|
||||
let lines = text.split(separator: "\n")
|
||||
|
||||
var currentPeer: Peer?
|
||||
var isInInterfaceSection = false
|
||||
|
||||
var tunnel: Tunnel!
|
||||
context.performAndWait {
|
||||
tunnel = Tunnel(context: context)
|
||||
tunnel.interface = Interface(context: context)
|
||||
}
|
||||
tunnel.tunnelIdentifier = UUID().uuidString
|
||||
|
||||
for line in lines {
|
||||
var trimmedLine: String
|
||||
if let commentRange = line.range(of: "#") {
|
||||
trimmedLine = String(line[..<commentRange.lowerBound])
|
||||
} else {
|
||||
trimmedLine = String(line)
|
||||
}
|
||||
|
||||
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces)
|
||||
|
||||
guard trimmedLine.count > 0 else { continue }
|
||||
|
||||
if "[interface]" == line.lowercased() {
|
||||
currentPeer = nil
|
||||
isInInterfaceSection = true
|
||||
} else if "[peer]" == line.lowercased() {
|
||||
context.performAndWait { currentPeer = Peer(context: context) }
|
||||
tunnel.insertIntoPeers(currentPeer!, at: tunnel.peers?.count ?? 0)
|
||||
isInInterfaceSection = false
|
||||
} else if isInInterfaceSection, let attribute = Attribute.match(line: String(line)) {
|
||||
try tunnel.interface!.parse(attribute: attribute)
|
||||
} else if let currentPeer = currentPeer, let attribute = Attribute.match(line: String(line)) {
|
||||
try currentPeer.parse(attribute: attribute)
|
||||
} else {
|
||||
throw TunnelParseError.invalidLine(String(line))
|
||||
}
|
||||
}
|
||||
|
||||
if !isInInterfaceSection && currentPeer == nil {
|
||||
throw TunnelParseError.noConfigInfo
|
||||
}
|
||||
|
||||
return tunnel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func base64KeyToHex(_ base64: String?) -> String? {
|
||||
|
@ -146,3 +194,8 @@ enum TunnelValidationError: Error {
|
|||
case nilPeers
|
||||
case invalidPeer
|
||||
}
|
||||
|
||||
enum TunnelParseError: Error {
|
||||
case invalidLine(_ line: String)
|
||||
case noConfigInfo
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
//
|
||||
// QRScanViewController.swift
|
||||
// WireGuard
|
||||
//
|
||||
// Created by Eric Kuck on 8/20/18.
|
||||
// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import CoreData
|
||||
import UIKit
|
||||
|
||||
protocol QRScanViewControllerDelegate: class {
|
||||
func didSave(tunnel: Tunnel, qrScanViewController: QRScanViewController)
|
||||
}
|
||||
|
||||
class QRScanViewController: UIViewController {
|
||||
|
||||
private var viewContext: NSManagedObjectContext!
|
||||
private weak var delegate: QRScanViewControllerDelegate?
|
||||
var captureSession: AVCaptureSession? = AVCaptureSession()
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
var previewLayer: AVCaptureVideoPreviewLayer!
|
||||
|
||||
func configure(context: NSManagedObjectContext, delegate: QRScanViewControllerDelegate? = nil) {
|
||||
viewContext = context
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
|
||||
let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice),
|
||||
let captureSession = captureSession,
|
||||
captureSession.canAddInput(videoInput),
|
||||
captureSession.canAddOutput(metadataOutput) else {
|
||||
scanDidEncounterError(title: "Scanning Not Supported", message: "This device does not have the ability to scan QR codes.")
|
||||
return
|
||||
}
|
||||
|
||||
captureSession.addInput(videoInput)
|
||||
captureSession.addOutput(metadataOutput)
|
||||
|
||||
metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
|
||||
metadataOutput.metadataObjectTypes = [.qr]
|
||||
|
||||
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
||||
previewLayer.frame = view.layer.bounds
|
||||
previewLayer.videoGravity = .resizeAspectFill
|
||||
view.layer.addSublayer(previewLayer)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if captureSession?.isRunning == false {
|
||||
captureSession?.startRunning()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
if captureSession?.isRunning == true {
|
||||
captureSession?.stopRunning()
|
||||
}
|
||||
}
|
||||
|
||||
func scanDidComplete(withCode code: String) {
|
||||
do {
|
||||
let tunnel = try Tunnel.fromConfig(code, context: viewContext)
|
||||
delegate?.didSave(tunnel: tunnel, qrScanViewController: self)
|
||||
} catch {
|
||||
scanDidEncounterError(title: "Invalid Code", message: "The scanned code is not a valid WireGuard config file.")
|
||||
}
|
||||
}
|
||||
|
||||
func scanDidEncounterError(title: String, message: String) {
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self] _ in
|
||||
self?.navigationController?.popViewController(animated: true)
|
||||
}))
|
||||
present(alertController, animated: true)
|
||||
captureSession = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension QRScanViewController: AVCaptureMetadataOutputObjectsDelegate {
|
||||
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
||||
captureSession?.stopRunning()
|
||||
|
||||
guard let metadataObject = metadataObjects.first,
|
||||
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
|
||||
let stringValue = readableObject.stringValue else {
|
||||
scanDidEncounterError(title: "Invalid Code", message: "The scanned code could not be read.")
|
||||
return
|
||||
}
|
||||
|
||||
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
|
||||
scanDidComplete(withCode: stringValue)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension QRScanViewController: Identifyable {}
|
|
@ -27,7 +27,6 @@ class TunnelConfigurationTableViewController: UITableViewController {
|
|||
viewContext = context
|
||||
self.delegate = delegate
|
||||
self.tunnel = tunnel ?? generateNewTunnelConfig()
|
||||
|
||||
}
|
||||
|
||||
private func generateNewTunnelConfig() -> Tunnel {
|
||||
|
|
Loading…
Reference in New Issue