diff --git a/CHANGELOG.md b/CHANGELOG.md index 90da950a..a8d4543b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Siri Shortcuts in-app manager. [#46](https://github.com/passepartoutvpn/passepartout-ios/pull/46) +- Background data count updates in diagnostics. [#51](https://github.com/passepartoutvpn/passepartout-ios/pull/51) ### Fixed diff --git a/Passepartout-CoreTests/UtilsTests.swift b/Passepartout-CoreTests/UtilsTests.swift new file mode 100644 index 00000000..516517a7 --- /dev/null +++ b/Passepartout-CoreTests/UtilsTests.swift @@ -0,0 +1,51 @@ +// +// UtilsTests.swift +// Passepartout-CoreTests +// +// Created by Davide De Rosa on 3/30/19. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import XCTest +@testable import Passepartout_Core + +class UtilsTests: XCTestCase { + override func setUp() { + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testDataUnitDescription() { + XCTAssertEqual(0.dataUnitDescription, "0B") + XCTAssertEqual(1.dataUnitDescription, "1B") + XCTAssertEqual(1024.dataUnitDescription, "1kB") + XCTAssertEqual(1025.dataUnitDescription, "1kB") + XCTAssertEqual(548575.dataUnitDescription, "0.52MB") + XCTAssertEqual(1048575.dataUnitDescription, "1.00MB") + XCTAssertEqual(1048576.dataUnitDescription, "1.00MB") + XCTAssertEqual(1048577.dataUnitDescription, "1.00MB") + XCTAssertEqual(600000000.dataUnitDescription, "0.56GB") + XCTAssertEqual(1073741823.dataUnitDescription, "1.00GB") + XCTAssertEqual(1073741824.dataUnitDescription, "1.00GB") + XCTAssertEqual(1073741825.dataUnitDescription, "1.00GB") + } +} diff --git a/Passepartout-iOS-Tunnel/PacketTunnelProvider.swift b/Passepartout-iOS-Tunnel/PacketTunnelProvider.swift index 223b1949..f3c423cb 100644 --- a/Passepartout-iOS-Tunnel/PacketTunnelProvider.swift +++ b/Passepartout-iOS-Tunnel/PacketTunnelProvider.swift @@ -30,6 +30,7 @@ class PacketTunnelProvider: TunnelKitProvider { appVersion = "\(GroupConstants.App.name) \(GroupConstants.App.versionString)" dnsTimeout = GroupConstants.VPN.dnsTimeout logSeparator = GroupConstants.VPN.sessionMarker + dataCountInterval = GroupConstants.VPN.dataCountInterval super.startTunnel(options: options, completionHandler: completionHandler) } } diff --git a/Passepartout-iOS/Scenes/ServiceViewController.swift b/Passepartout-iOS/Scenes/ServiceViewController.swift index 50be7a11..083bdfd6 100644 --- a/Passepartout-iOS/Scenes/ServiceViewController.swift +++ b/Passepartout-iOS/Scenes/ServiceViewController.swift @@ -50,6 +50,8 @@ class ServiceViewController: UIViewController, TableModelHost { private var shouldDeleteLogOnDisconnection = false + private var currentDataCount: (Int, Int)? + // MARK: Table var model: TableModel = TableModel() @@ -103,6 +105,7 @@ class ServiceViewController: UIViewController, TableModelHost { nc.addObserver(self, selector: #selector(vpnDidUpdate), name: .VPNDidChangeStatus, object: nil) nc.addObserver(self, selector: #selector(vpnDidUpdate), name: .VPNDidReinstall, object: nil) nc.addObserver(self, selector: #selector(intentDidUpdateService), name: .IntentDidUpdateService, object: nil) + nc.addObserver(self, selector: #selector(serviceDidUpdateDataCount(_:)), name: .ConnectionServiceDidUpdateDataCount, object: nil) // run this no matter what // XXX: convenient here vs AppDelegate for updating table @@ -373,32 +376,32 @@ class ServiceViewController: UIViewController, TableModelHost { } } - private func displayDataCount() { - guard vpn.isEnabled else { - let alert = Macros.alert( - L10n.Service.Cells.DataCount.caption, - L10n.Service.Alerts.DataCount.Messages.notAvailable - ) - alert.addCancelAction(L10n.Global.ok) - present(alert, animated: true, completion: nil) - return - } - - vpn.requestBytesCount { - let message: String - if let count = $0 { - message = L10n.Service.Alerts.DataCount.Messages.current(Int(count.0), Int(count.1)) - } else { - message = L10n.Service.Alerts.DataCount.Messages.notAvailable - } - let alert = Macros.alert( - L10n.Service.Cells.DataCount.caption, - message - ) - alert.addCancelAction(L10n.Global.ok) - self.present(alert, animated: true, completion: nil) - } - } +// private func displayDataCount() { +// guard vpn.isEnabled else { +// let alert = Macros.alert( +// L10n.Service.Cells.DataCount.caption, +// L10n.Service.Alerts.DataCount.Messages.notAvailable +// ) +// alert.addCancelAction(L10n.Global.ok) +// present(alert, animated: true, completion: nil) +// return +// } +// +// vpn.requestBytesCount { +// let message: String +// if let count = $0 { +// message = L10n.Service.Alerts.DataCount.Messages.current(Int(count.0), Int(count.1)) +// } else { +// message = L10n.Service.Alerts.DataCount.Messages.notAvailable +// } +// let alert = Macros.alert( +// L10n.Service.Cells.DataCount.caption, +// message +// ) +// alert.addCancelAction(L10n.Global.ok) +// self.present(alert, animated: true, completion: nil) +// } +// } private func togglePrivateDataMasking(cell: ToggleTableViewCell) { let handler = { @@ -468,6 +471,13 @@ class ServiceViewController: UIViewController, TableModelHost { reloadModel() updateViewsIfNeeded() } + + @objc private func serviceDidUpdateDataCount(_ notification: Notification) { + guard let dataCount = notification.userInfo?[ConnectionService.NotificationKeys.dataCount] as? (Int, Int) else { + return + } + refreshDataCount(dataCount) + } } // MARK: - @@ -551,6 +561,10 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog return model.indexPath(row: .connectionStatus, section: .vpn) } + private var dataCountIndexPath: IndexPath? { + return model.indexPath(row: .dataCount, section: .diagnostics) + } + private var endpointIndexPath: IndexPath { guard let ip = model.indexPath(row: .endpoint, section: .configuration) else { fatalError("Could not locate endpointIndexPath") @@ -743,6 +757,13 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog case .dataCount: let cell = Cells.setting.dequeue(from: tableView, for: indexPath) cell.leftText = L10n.Service.Cells.DataCount.caption + if let count = currentDataCount, vpn.status == .connected { + cell.rightText = L10n.Service.Cells.DataCount.value(count.0.dataUnitDescription, count.1.dataUnitDescription) + } else { + cell.rightText = nil + } + cell.accessoryType = .none + cell.isTappable = false return cell case .debugLog: @@ -859,8 +880,8 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog case .testConnectivity: testInternetConnectivity() - case .dataCount: - displayDataCount() +// case .dataCount: +// displayDataCount() case .debugLog: perform(segue: StoryboardSegue.Main.debugLogSegueIdentifier, sender: cell) @@ -1018,10 +1039,24 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog guard service.isActiveProfile(profile) else { return } + var ips: [IndexPath] = [] guard let statusIndexPath = statusIndexPath else { return } - tableView.reloadRows(at: [statusIndexPath], with: .none) + ips.append(statusIndexPath) + if let dataCountIndexPath = dataCountIndexPath { + currentDataCount = service.vpnDataCount + ips.append(dataCountIndexPath) + } + tableView.reloadRows(at: ips, with: .none) + } + + private func refreshDataCount(_ dataCount: (Int, Int)?) { + currentDataCount = dataCount + guard let dataCountIndexPath = dataCountIndexPath else { + return + } + tableView.reloadRows(at: [dataCountIndexPath], with: .none) } func reloadSelectedRow(andRowAt indexPath: IndexPath? = nil) { diff --git a/Passepartout-iOS/Tables/TableModel.swift b/Passepartout-iOS/Tables/TableModel.swift index d86d6112..aa4c3264 100644 --- a/Passepartout-iOS/Tables/TableModel.swift +++ b/Passepartout-iOS/Tables/TableModel.swift @@ -93,7 +93,7 @@ class TableModel { func indexPath(row rowObject: R, section sectionObject: S) -> IndexPath? { guard let sectionIndex = sections.index(of: sectionObject) else { - fatalError("Missing section: \(sectionObject)") + return nil } guard let row = rowsBySection[sectionObject]?.index(of: rowObject) else { return nil diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index b3c7a7df..527aee9f 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -82,6 +82,8 @@ 0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */; }; 0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */; }; 0EBE3A79213C4E5500BFA2F5 /* OrganizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */; }; + 0ECEB10A224FECEA00E9E551 /* DataUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEB109224FECEA00E9E551 /* DataUnit.swift */; }; + 0ECEB10C224FEF9B00E9E551 /* UtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEB10B224FEF9B00E9E551 /* UtilsTests.swift */; }; 0ECEE44E20E1122200A6BB43 /* TableModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEE44D20E1122200A6BB43 /* TableModel.swift */; }; 0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */; }; 0ED31C2920CF2A340027975F /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2820CF2A340027975F /* AccountViewController.swift */; }; @@ -226,6 +228,8 @@ 0ECEB106224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Organizer.storyboard; sourceTree = ""; }; 0ECEB107224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Shortcuts.storyboard; sourceTree = ""; }; 0ECEB108224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 0ECEB109224FECEA00E9E551 /* DataUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUnit.swift; sourceTree = ""; }; + 0ECEB10B224FEF9B00E9E551 /* UtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilsTests.swift; sourceTree = ""; }; 0ECEE44D20E1122200A6BB43 /* TableModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableModel.swift; sourceTree = ""; }; 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Cells.swift"; sourceTree = ""; }; 0ED31C0F20CF09A30027975F /* Pool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pool.swift; sourceTree = ""; }; @@ -338,6 +342,7 @@ 0EBBE8F121822B4D00106008 /* ConnectionService.json */, 0EBBE8F021822B4D00106008 /* ConnectionServiceTests.swift */, 0ED31C2620CF257C0027975F /* InfrastructureTests.swift */, + 0ECEB10B224FEF9B00E9E551 /* UtilsTests.swift */, 0E3152AC223F9EF500F61841 /* Info.plist */, ); path = "Passepartout-CoreTests"; @@ -456,6 +461,7 @@ 0E2D11B9217DBEDE0096822C /* ConnectionService+Configurations.swift */, 0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */, 0EDE8DE620C93945004C739C /* Credentials.swift */, + 0ECEB109224FECEA00E9E551 /* DataUnit.swift */, 0EC7F20420E24308004EA58E /* DebugLog.swift */, 0ED38AE621404F100004D387 /* EndpointDataSource.swift */, 0E89DFC4213DF7AE00741BA1 /* Preferences.swift */, @@ -942,6 +948,7 @@ buildActionMask = 2147483647; files = ( 0E3152BD223FA03D00F61841 /* GroupConstants.swift in Sources */, + 0ECEB10A224FECEA00E9E551 /* DataUnit.swift in Sources */, 0E3152C2223FA04800F61841 /* MockVPNProvider.swift in Sources */, 0E3152D2223FA05400F61841 /* DebugLog.swift in Sources */, 0E3152C4223FA04800F61841 /* VPN.swift in Sources */, @@ -986,6 +993,7 @@ files = ( 0E3152BA223F9F3D00F61841 /* InfrastructureTests.swift in Sources */, 0E3152B9223F9F3B00F61841 /* ConnectionServiceTests.swift in Sources */, + 0ECEB10C224FEF9B00E9E551 /* UtilsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Passepartout/Resources/en.lproj/Localizable.strings b/Passepartout/Resources/en.lproj/Localizable.strings index 4fba270f..a1100153 100644 --- a/Passepartout/Resources/en.lproj/Localizable.strings +++ b/Passepartout/Resources/en.lproj/Localizable.strings @@ -107,7 +107,8 @@ "service.cells.trusted_add_wifi.caption" = "Add current Wi-Fi"; "service.cells.trusted_policy.caption" = "Trust disables VPN"; "service.cells.test_connectivity.caption" = "Test connectivity"; -"service.cells.data_count.caption" = "Exchanged bytes count"; +"service.cells.data_count.caption" = "Exchanged data count"; +"service.cells.data_count.value" = "%@ / %@"; "service.cells.debug_log.caption" = "Debug log"; "service.cells.masks_private_data.caption" = "Mask network data"; "service.cells.report_issue.caption" = "Report connectivity issue"; diff --git a/Passepartout/Sources/GroupConstants.swift b/Passepartout/Sources/GroupConstants.swift index b79d793e..54cf1574 100644 --- a/Passepartout/Sources/GroupConstants.swift +++ b/Passepartout/Sources/GroupConstants.swift @@ -79,5 +79,7 @@ public class GroupConstants { public static let dnsTimeout = 5000 public static let sessionMarker = "--- EOF ---" + + public static let dataCountInterval = 5000 } } diff --git a/Passepartout/Sources/Model/ConnectionService.swift b/Passepartout/Sources/Model/ConnectionService.swift index a487375f..e4f93e98 100644 --- a/Passepartout/Sources/Model/ConnectionService.swift +++ b/Passepartout/Sources/Model/ConnectionService.swift @@ -42,6 +42,10 @@ public protocol ConnectionServiceDelegate: class { func connectionService(didActivate profile: ConnectionProfile) } +public extension Notification.Name { + static let ConnectionServiceDidUpdateDataCount = Notification.Name("ConnectionServiceDidUpdateDataCount") +} + public class ConnectionService: Codable { public enum CodingKeys: String, CodingKey { case build @@ -54,6 +58,10 @@ public class ConnectionService: Codable { case preferences } + + public struct NotificationKeys { + public static let dataCount = "DataCount" + } public var directory: String? = nil @@ -85,6 +93,8 @@ public class ConnectionService: Codable { private var cache: [ProfileKey: ConnectionProfile] + private var dataCountObserver: Timer? + public private(set) var activeProfileKey: ProfileKey? { willSet { if let oldProfile = activeProfile { @@ -130,6 +140,10 @@ public class ConnectionService: Codable { cache = [:] } + deinit { + dataCountObserver?.invalidate() + } + // MARK: Codable public required init(from decoder: Decoder) throws { @@ -502,14 +516,6 @@ public class ConnectionService: Codable { return baseConfiguration.existingLog(in: appGroup) ?? "" } - public var vpnLastError: TunnelKitProvider.ProviderError? { - return baseConfiguration.lastError(in: appGroup) - } - - public func clearVpnLastError() { - baseConfiguration.clearLastError(in: appGroup) - } - public func eraseVpnLog() { log.info("Erasing VPN log...") guard let url = baseConfiguration.urlForLog(in: appGroup) else { @@ -517,4 +523,26 @@ public class ConnectionService: Codable { } try? FileManager.default.removeItem(at: url) } + + public var vpnLastError: TunnelKitProvider.ProviderError? { + return baseConfiguration.lastError(in: appGroup) + } + + public func clearVpnLastError() { + baseConfiguration.clearLastError(in: appGroup) + } + + public func observeVPNDataCount(interval: TimeInterval) { + dataCountObserver?.invalidate() + dataCountObserver = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { [weak self] (_) in + guard let dataCount = self?.vpnDataCount else { + return + } + NotificationCenter.default.post(name: .ConnectionServiceDidUpdateDataCount, object: nil, userInfo: [NotificationKeys.dataCount: dataCount]) + }) + } + + public var vpnDataCount: (Int, Int)? { + return baseConfiguration.dataCount(in: appGroup) + } } diff --git a/Passepartout/Sources/Model/DataUnit.swift b/Passepartout/Sources/Model/DataUnit.swift new file mode 100644 index 00000000..e93e2215 --- /dev/null +++ b/Passepartout/Sources/Model/DataUnit.swift @@ -0,0 +1,93 @@ +// +// DataUnit.swift +// Passepartout +// +// Created by Davide De Rosa on 3/30/18. +// Copyright (c) 2019 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public enum DataUnit: Int, CustomStringConvertible { + case byte = 1 + + case kilobyte = 1024 + + case megabyte = 1048576 + + case gigabyte = 1073741824 + + fileprivate var showsDecimals: Bool { + switch self { + case .byte, .kilobyte: + return false + + case .megabyte, .gigabyte: + return true + } + } + + fileprivate var boundary: Int { + return Int(0.1 * Double(rawValue)) + } + + // MARK: CustomStringConvertible + + public var description: String { + switch self { + case .byte: + return "B" + + case .kilobyte: + return "kB" + + case .megabyte: + return "MB" + + case .gigabyte: + return "GB" + } + } +} + +public extension Int { + private static let allUnits: [DataUnit] = [ + .gigabyte, + .megabyte, + .kilobyte, + .byte + ] + + var dataUnitDescription: String { + if self == 0 { + return "0B" + } + for u in Int.allUnits { + if self >= u.boundary { + if !u.showsDecimals { + return "\(self / u.rawValue)\(u)" + } + let count = Double(self) / Double(u.rawValue) + return String(format: "%.2f%@", count, u.description) + } + } + fatalError("Number is negative") + } +} diff --git a/Passepartout/Sources/Model/TransientStore.swift b/Passepartout/Sources/Model/TransientStore.swift index df93dad2..bf243697 100644 --- a/Passepartout/Sources/Model/TransientStore.swift +++ b/Passepartout/Sources/Model/TransientStore.swift @@ -107,6 +107,7 @@ public class TransientStore { // _ = service.addProfile(HostConnectionProfile(title: "vps"), credentials: Credentials(username: "foo", password: "bar")) // service.activateProfile(service.profiles.first!) } + service.observeVPNDataCount(interval: TimeInterval(GroupConstants.VPN.dataCountInterval) / 1000.0) } public func serialize(withProfiles: Bool) { diff --git a/Passepartout/Sources/SwiftGen+Strings.swift b/Passepartout/Sources/SwiftGen+Strings.swift index 8e252def..01171a2a 100644 --- a/Passepartout/Sources/SwiftGen+Strings.swift +++ b/Passepartout/Sources/SwiftGen+Strings.swift @@ -541,8 +541,12 @@ public enum L10n { public static let caption = L10n.tr("Localizable", "service.cells.connection_status.caption") } public enum DataCount { - /// Exchanged bytes count + /// Exchanged data count public static let caption = L10n.tr("Localizable", "service.cells.data_count.caption") + /// %@ / %@ + public static func value(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "service.cells.data_count.value", p1, p2) + } } public enum DebugLog { /// Debug log diff --git a/Podfile b/Podfile index dd1dc6c1..adbe781b 100644 --- a/Podfile +++ b/Podfile @@ -5,8 +5,8 @@ use_frameworks! def shared_pods #pod 'TunnelKit', '~> 1.5.0' #pod 'TunnelKit/LZO', '~> 1.5.0' - pod 'TunnelKit', :git => 'https://github.com/keeshux/tunnelkit', :commit => 'd03f1bd' - pod 'TunnelKit/LZO', :git => 'https://github.com/keeshux/tunnelkit', :commit => 'd03f1bd' + pod 'TunnelKit', :git => 'https://github.com/keeshux/tunnelkit', :commit => '44fb5a5' + pod 'TunnelKit/LZO', :git => 'https://github.com/keeshux/tunnelkit', :commit => '44fb5a5' #pod 'TunnelKit', :path => '../../personal/tunnelkit' #pod 'TunnelKit/LZO', :path => '../../personal/tunnelkit' end diff --git a/Podfile.lock b/Podfile.lock index c95590cc..4cc618bf 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,8 +15,8 @@ PODS: DEPENDENCIES: - MBProgressHUD - - TunnelKit (from `https://github.com/keeshux/tunnelkit`, commit `d03f1bd`) - - TunnelKit/LZO (from `https://github.com/keeshux/tunnelkit`, commit `d03f1bd`) + - TunnelKit (from `https://github.com/keeshux/tunnelkit`, commit `44fb5a5`) + - TunnelKit/LZO (from `https://github.com/keeshux/tunnelkit`, commit `44fb5a5`) SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -26,12 +26,12 @@ SPEC REPOS: EXTERNAL SOURCES: TunnelKit: - :commit: d03f1bd + :commit: 44fb5a5 :git: https://github.com/keeshux/tunnelkit CHECKOUT OPTIONS: TunnelKit: - :commit: d03f1bd + :commit: 44fb5a5 :git: https://github.com/keeshux/tunnelkit SPEC CHECKSUMS: @@ -40,6 +40,6 @@ SPEC CHECKSUMS: SwiftyBeaver: 8e67ab3cd94389cbbb7a9c7cc02748d98bfee68e TunnelKit: 75323e2f45e698647ccaebd5a6bcae8c002afea4 -PODFILE CHECKSUM: a2c0a287a69e21e328e35816f2c813616b80f54c +PODFILE CHECKSUM: 871b647058e72fb7ae3c666abb35d2683806455d COCOAPODS: 1.6.1