23 Commits

Author SHA1 Message Date
relikd
d68e4ec869 Version 1.0.0 (33) 2020-09-14 12:56:30 +02:00
relikd
762263bfbd Default disconnect swdc + pre-check connect message 2020-09-14 12:56:06 +02:00
relikd
b1cddc796e Version 1.0.0 (32) 2020-09-14 11:52:21 +02:00
relikd
77e20f31f5 Persist recording logs in background 2020-09-14 11:51:44 +02:00
relikd
0175f5390e Fix crash trying to access userInfo 2020-09-13 12:13:14 +02:00
relikd
effc305b86 Version 1.0.0 (31) 2020-09-12 22:28:32 +02:00
relikd
c1fe258b0d Force disconnect to prevent domain spamming (optional in advanced settings) 2020-09-12 22:28:11 +02:00
relikd
36a8f0b97b Version 1.0.0 (30) 2020-09-12 11:35:11 +02:00
relikd
33b9cab8a8 Indicate background recording needs more time 2020-09-12 11:32:06 +02:00
relikd
b88874b38b Version 1.0.0 (29) 2020-09-12 10:57:20 +02:00
relikd
f55f3ea32d Disable copy menu on meta cells in 5 min context 2020-09-12 10:42:37 +02:00
relikd
c843bd76a2 Share notes opt-out, assuming notes are created for upload anyway 2020-09-12 10:31:13 +02:00
relikd
4dd2339ed8 Set recording segment color to indicate tap action 2020-09-12 10:27:04 +02:00
relikd
280526bef4 Hide filter button if new recording 2020-09-12 10:04:24 +02:00
relikd
34caffd4a7 Change tutorial text about app recording length 2020-09-12 09:57:53 +02:00
relikd
9e19b457e2 AppStore search: sort local apps case independent 2020-09-11 15:39:23 +02:00
relikd
e6846953b7 Copy upload key to clipboard 2020-09-08 18:35:30 +02:00
relikd
6d78aeac7b Fix header banner display issues 2020-09-08 18:16:21 +02:00
relikd
5d94fe3a0d Version 1.0.0 (28) 2020-09-08 11:56:02 +02:00
relikd
fb680d669b Fix crash on loading App Store search results 2020-09-08 11:52:07 +02:00
relikd
6409e5eaf3 Allow to contribute empty recordings 2020-09-08 04:28:07 +02:00
relikd
39ca9dbdb1 Persist recording logs before save operation (crash-safe) 2020-09-08 03:16:38 +02:00
relikd
27ab2a621a Set recording time as filter 2020-09-08 02:51:25 +02:00
26 changed files with 314 additions and 104 deletions

View File

@@ -1397,7 +1397,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 33;
INFOPLIST_FILE = main/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -1416,7 +1416,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 33;
INFOPLIST_FILE = main/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -1435,7 +1435,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 33;
INFOPLIST_FILE = GlassVPN/Info.plist;
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
@@ -1453,7 +1453,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 27;
CURRENT_PROJECT_VERSION = 33;
INFOPLIST_FILE = GlassVPN/Info.plist;
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";

View File

@@ -1,5 +1,7 @@
import NetworkExtension
let connectMessage: Data = "CONNECT".data(using: .ascii)!
let swcdUserAgent: Data = "User-Agent: swcd".data(using: .ascii)!
fileprivate var hook : GlassVPNHook!
// MARK: ObserverFactory
@@ -15,8 +17,20 @@ class LDObserverFactory: ObserverFactory {
override func signal(_ event: ProxySocketEvent) {
switch event {
case .receivedRequest(let session, let socket):
if socket.isCancelled ||
(hook.forceDisconnectUnresolvable && session.ipAddress.isEmpty) {
hook.silentlyPrevented(session.host)
socket.forceDisconnect()
return
}
let kill = hook.processDNSRequest(session.host)
if kill { socket.forceDisconnect() }
case .readData(let data, on: let socket):
if hook.forceDisconnectSWCD,
data.starts(with: connectMessage),
data.range(of: swcdUserAgent) != nil {
socket.disconnect()
}
default:
break
}

View File

@@ -53,3 +53,5 @@ That means, AppCheck does not have to be active in the foreground all the time.
*information will be added soon™*
For now, go to the results page at [https://appchk.de/](https://appchk.de/).
Btw. we are searching for [help](https://appchk.de/help/) on our ongoing research project.

View File

@@ -30,14 +30,16 @@ struct NotificationBanner {
img.tintColor = fg
view.addSubview(lbl)
view.addSubview(img)
img.anchor([.centerY], to: view.layoutMarginsGuide)
lbl.anchor([.top, .bottom, .trailing], to: view.layoutMarginsGuide)
img.anchor([.centerY], to: lbl)
lbl.anchor([.bottom, .trailing], to: view.layoutMarginsGuide)
img.widthAnchor =&= 25
img.heightAnchor =&= 25
if #available(iOS 11, *) {
img.leadingAnchor =&= view.layoutMarginsGuide.leadingAnchor
lbl.topAnchor =&= view.layoutMarginsGuide.topAnchor
} else {
img.leadingAnchor =&= view.leadingAnchor + 8
lbl.topAnchor =&= view.topAnchor + 8
}
lbl.leadingAnchor =&= img.trailingAnchor + 8
img.bottomAnchor =<= view.bottomAnchor - 8 | .init(rawValue: 999)

View File

@@ -12,6 +12,7 @@ enum PrefsShared {
static func registerDefaults() {
suite.register(defaults: [
"ForceDisconnectSWCD" : true,
"RestartReminderEnabled" : true,
"RestartReminderWithText" : true,
"RestartReminderWithBadge" : true,
@@ -37,6 +38,14 @@ extension PrefsShared {
get { CurrentRecordingState(rawValue: Int("CurrentlyRecording")) ?? .Off }
set { Int("CurrentlyRecording", newValue.rawValue) }
}
static var ForceDisconnectUnresolvableDNS: Bool {
get { PrefsShared.Bool("ForceDisconnectUnresolvableDNS") }
set { PrefsShared.Bool("ForceDisconnectUnresolvableDNS", newValue) }
}
static var ForceDisconnectSWCD: Bool {
get { PrefsShared.Bool("ForceDisconnectSWCD") }
set { PrefsShared.Bool("ForceDisconnectSWCD", newValue) }
}
}

View File

@@ -405,7 +405,7 @@ extension SQLiteDatabase {
}
func appBundleList() -> [AppBundleInfo]? {
try? run(sql: "SELECT appid, title, subtitle FROM rec WHERE appid IS NOT NULL GROUP BY appid ORDER BY title ASC;") {
try? run(sql: "SELECT appid, title, subtitle FROM rec WHERE appid IS NOT NULL GROUP BY appid ORDER BY lower(title) ASC;") {
allRows($0) {
AppBundleInfo(col_text($0, 0)!, col_text($0, 1), col_text($0, 2))
}

View File

@@ -33,11 +33,13 @@ extension FilterOptions {
}
extension Recording {
static let minTimeLongTerm: Timestamp = .hours(1)
var fallbackTitle: String { get {
isLongTerm ? "Background Recording" : "Unnamed Recording #\(id)"
} }
var duration: Timestamp { get { (stop ?? .now()) - start } }
var isLongTerm: Bool { duration > Timestamp.hours(1) }
var isLongTerm: Bool { duration > Recording.minTimeLongTerm }
var isShared: Bool { uploadkey?.count ?? 0 > 0}
}

View File

@@ -87,14 +87,21 @@ struct TimeFormat {
/// Duration string with format `mm:ss` or `mm:ss.SSS`
static func from(_ duration: TimeInterval, millis: Bool = false, hours: Bool = false) -> String {
let t = Int(duration)
let min = t / 60
let sec = t % 60
var t = Int(duration)
var min = t / 60
var sec = t % 60
if millis {
let mil = Int(duration * 1000) % 1000
return String(format: "%02d:%02d.%03d", min, sec, mil)
} else if hours {
return String(format: "%02d:%02d:%02d", min / 60, min % 60, sec)
if t < Recording.minTimeLongTerm {
t = Int(Recording.minTimeLongTerm) - t
min = t / 60
sec = t % 60
return String(format: "-%02d:%02d:%02d", min / 60, min % 60, sec)
} else {
return String(format: "%02d:%02d:%02d", min / 60, min % 60, sec)
}
}
return String(format: "%02d:%02d", min, sec)
}

View File

@@ -40,8 +40,8 @@ extension URL {
return components.url
}
func download(to file: URL, onSuccess: @escaping () -> Void) {
URLSession.shared.downloadTask(with: self) { location, response, error in
@discardableResult func download(to file: URL, onSuccess: @escaping () -> Void) -> URLSessionDownloadTask {
let task = URLSession.shared.downloadTask(with: self) { location, response, error in
if let loc = location {
try? FileManager.default.removeItem(at: file)
do {
@@ -51,6 +51,8 @@ extension URL {
NSLog("[VPN.ERROR] \(error)")
}
}
}.resume()
}
task.resume()
return task
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="hm5-7q-Zfi">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="hm5-7q-Zfi">
<device id="retina4_0" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
@@ -128,6 +128,7 @@
<outlet property="buttonView" destination="La3-9e-6TK" id="UMg-xx-6OV"/>
<outlet property="headerView" destination="ppJ-js-Wwz" id="68u-8M-R2Q"/>
<outlet property="runningView" destination="9Yj-FX-eFd" id="L2C-YR-2HN"/>
<outlet property="startSegment" destination="2MI-6l-YQt" id="Jun-ct-Xag"/>
<outlet property="stopButton" destination="vAq-EZ-Gmx" id="XiW-1H-I9y"/>
<outlet property="timeLabel" destination="rbR-np-cXD" id="EEe-8F-HT6"/>
</connections>
@@ -306,9 +307,6 @@
</rightBarButtonItems>
</navigationItem>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<connections>
<outlet property="shareButton" destination="UkE-Wi-JjW" id="9f1-WG-k0N"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="lan-I9-b0a" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
@@ -416,15 +414,15 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="c0B-OV-ujb">
<rect key="frame" x="16" y="8" width="33.5" height="20.5"/>
<rect key="frame" x="16" y="9" width="33.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="4" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" id="sAD-Ns-DV6">
<rect key="frame" x="16" y="31.5" width="35" height="16"/>
<rect key="frame" x="16" y="32.5" width="33" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
@@ -497,18 +495,18 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<navigationBar contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2yS-xK-Wac">
<rect key="frame" x="0.0" y="0.0" width="320" height="0.0"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<gestureRecognizers/>
<items>
<navigationItem title="Edit" id="JSi-oz-VRx">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="TGg-60-wZW">
<connections>
<action selector="didTapCancel:" destination="VRk-wv-rhk" id="Kff-ed-gdd"/>
<action selector="didTapCancel" destination="VRk-wv-rhk" id="idg-Q7-qLu"/>
</connections>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" enabled="NO" style="done" systemItem="save" id="rWg-hE-Ydl">
<connections>
<action selector="didTapSave:" destination="VRk-wv-rhk" id="Xee-qo-bQx"/>
<action selector="didTapSave" destination="VRk-wv-rhk" id="fPE-i2-I06"/>
</connections>
</barButtonItem>
</navigationItem>
@@ -518,7 +516,7 @@
</connections>
</navigationBar>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="xdn-EU-IMx">
<rect key="frame" x="16" y="12" width="288" height="347.5"/>
<rect key="frame" x="16" y="52" width="288" height="307.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Guy-Ra-fpS" userLabel="Title">
<rect key="frame" x="0.0" y="0.0" width="288" height="40"/>
@@ -562,7 +560,7 @@
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ybL-UG-dwT" userLabel="Notes">
<rect key="frame" x="0.0" y="48" width="288" height="200.5"/>
<rect key="frame" x="0.0" y="48" width="288" height="160.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Notes" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QJp-6C-yoZ">
<rect key="frame" x="0.0" y="0.0" width="288" height="24"/>
@@ -572,7 +570,7 @@
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NXU-yU-eST">
<rect key="frame" x="0.0" y="24" width="288" height="176.5"/>
<rect key="frame" x="0.0" y="24" width="288" height="136.5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<string key="text">1. Line
2. Line
@@ -596,7 +594,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QiY-Mm-Dej" userLabel="Details">
<rect key="frame" x="0.0" y="256.5" width="288" height="91"/>
<rect key="frame" x="0.0" y="216.5" width="288" height="91"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Details" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="FR1-Nt-XuB">
<rect key="frame" x="0.0" y="0.0" width="288" height="24"/>
@@ -605,9 +603,8 @@
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" fixedFrame="YES" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" bouncesZoom="NO" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pql-H5-k6U">
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" bouncesZoom="NO" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pql-H5-k6U">
<rect key="frame" x="0.0" y="24" width="288" height="67"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<string key="text">Start: 1970-01-01 01:00
End: 1970-01-01 02:00
@@ -616,10 +613,23 @@ Duration: 60:00</string>
<fontDescription key="fontDescription" type="system" weight="light" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LOr-e7-foG">
<rect key="frame" x="268" y="46.5" width="20" height="22"/>
<state key="normal" image="filter-clear"/>
<connections>
<action selector="didTapFilter" destination="VRk-wv-rhk" eventType="touchUpInside" id="5NH-qa-yMg"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="pql-H5-k6U" secondAttribute="trailing" id="44x-p3-qWK"/>
<constraint firstItem="LOr-e7-foG" firstAttribute="trailing" secondItem="pql-H5-k6U" secondAttribute="trailing" id="5sr-Cf-h0c"/>
<constraint firstItem="pql-H5-k6U" firstAttribute="top" secondItem="FR1-Nt-XuB" secondAttribute="bottom" id="As8-Px-t6G"/>
<constraint firstItem="pql-H5-k6U" firstAttribute="leading" secondItem="QiY-Mm-Dej" secondAttribute="leading" id="ItB-cO-reV"/>
<constraint firstItem="LOr-e7-foG" firstAttribute="centerY" secondItem="pql-H5-k6U" secondAttribute="centerY" id="OLu-MI-3sa"/>
<constraint firstAttribute="height" priority="250" constant="91" id="or7-9o-FZb"/>
<constraint firstAttribute="bottom" secondItem="pql-H5-k6U" secondAttribute="bottom" id="pbd-43-eN0"/>
</constraints>
</view>
</subviews>
@@ -635,9 +645,9 @@ Duration: 60:00</string>
<constraint firstItem="2yS-xK-Wac" firstAttribute="trailing" secondItem="fMa-Lq-tGz" secondAttribute="trailing" id="1io-bA-4p9"/>
<constraint firstItem="2yS-xK-Wac" firstAttribute="leading" secondItem="fMa-Lq-tGz" secondAttribute="leading" id="Fv1-fO-22V"/>
<constraint firstItem="xdn-EU-IMx" firstAttribute="leading" secondItem="fMa-Lq-tGz" secondAttribute="leading" constant="16" id="JuR-Ro-IPi"/>
<constraint firstItem="xdn-EU-IMx" firstAttribute="top" secondItem="2yS-xK-Wac" secondAttribute="bottom" constant="12" id="Lec-83-aaD"/>
<constraint firstItem="xdn-EU-IMx" firstAttribute="trailing" secondItem="fMa-Lq-tGz" secondAttribute="trailing" constant="-16" id="hhC-bL-G3S"/>
<constraint firstItem="xdn-EU-IMx" firstAttribute="bottom" secondItem="fMa-Lq-tGz" secondAttribute="bottom" constant="-10" id="p7W-sr-Wch"/>
<constraint firstItem="xdn-EU-IMx" firstAttribute="top" secondItem="2yS-xK-Wac" secondAttribute="bottom" constant="8" id="Lec-83-aaD"/>
<constraint firstItem="fMa-Lq-tGz" firstAttribute="trailing" secondItem="xdn-EU-IMx" secondAttribute="trailing" constant="16" id="hhC-bL-G3S"/>
<constraint firstItem="fMa-Lq-tGz" firstAttribute="bottom" secondItem="xdn-EU-IMx" secondAttribute="bottom" constant="10" id="p7W-sr-Wch"/>
<constraint firstItem="2yS-xK-Wac" firstAttribute="top" secondItem="fMa-Lq-tGz" secondAttribute="top" id="yKh-gv-mgg"/>
</constraints>
<viewLayoutGuide key="safeArea" id="fMa-Lq-tGz"/>
@@ -647,6 +657,7 @@ Duration: 60:00</string>
<outlet property="appIcon" destination="rbW-pK-Kct" id="VlO-fG-y1a"/>
<outlet property="appTitle" destination="Et0-8d-CId" id="HgD-oI-0J8"/>
<outlet property="buttonCancel" destination="TGg-60-wZW" id="5Ej-7t-jaD"/>
<outlet property="buttonFilter" destination="LOr-e7-foG" id="qUx-1k-xJK"/>
<outlet property="buttonSave" destination="rWg-hE-Ydl" id="zfM-kx-erX"/>
<outlet property="chooseAppTap" destination="Jab-q2-U9X" id="Tzv-lm-sUm"/>
<outlet property="inputDetails" destination="pql-H5-k6U" id="NXm-8f-5E6"/>
@@ -727,6 +738,7 @@ Duration: 60:00</string>
<resources>
<image name="LaunchIcon.png" width="128" height="128"/>
<image name="detail-help" width="22" height="22"/>
<image name="filter-clear" width="20" height="20"/>
<image name="line-expand" width="20" height="20"/>
</resources>
</document>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="qdB-ZO-LHY">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="qdB-ZO-LHY">
<device id="retina4_0" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
@@ -243,25 +243,93 @@
</tableViewSection>
<tableViewSection headerTitle="Advanced Settings" id="Vlg-nm-VB3">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="VnR-9B-1zl">
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="rio-m6-pXN" detailTextLabel="rr4-sR-VxD" style="IBUITableViewCellStyleSubtitle" id="pQ5-lm-Rco">
<rect key="frame" x="0.0" y="687.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VnR-9B-1zl" id="ZTz-vZ-l5p">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="pQ5-lm-Rco" id="52Y-H3-jvJ">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="twS-Ne-dU0">
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Force disconnect unresolvable" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="rio-m6-pXN">
<rect key="frame" x="16" y="5" width="234" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="in case DNS returns empty IP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="rr4-sR-VxD">
<rect key="frame" x="16" y="25.5" width="166" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TSu-IP-KFG">
<rect key="frame" x="257" y="6" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="togglePreventUnresolvable:" destination="qdB-ZO-LHY" eventType="valueChanged" id="xKv-Hp-Nyq"/>
</connections>
</switch>
</subviews>
</tableViewCellContentView>
<connections>
<outlet property="accessoryView" destination="TSu-IP-KFG" id="VGm-hN-DLK"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="x5u-5T-XTO" detailTextLabel="n7p-Ab-69G" style="IBUITableViewCellStyleSubtitle" id="lxs-NQ-Q7V">
<rect key="frame" x="0.0" y="731.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="lxs-NQ-Q7V" id="B1l-cb-yQg">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Force disconnect swcd" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="x5u-5T-XTO">
<rect key="frame" x="16" y="5" width="177" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="background deamon User-Agent: swcd" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="n7p-Ab-69G">
<rect key="frame" x="16" y="25.5" width="220.5" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="c5N-PN-qWU">
<rect key="frame" x="257" y="6" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="togglePreventSWCD:" destination="qdB-ZO-LHY" eventType="valueChanged" id="MAa-5v-djq"/>
</connections>
</switch>
</subviews>
</tableViewCellContentView>
<connections>
<outlet property="accessoryView" destination="c5N-PN-qWU" id="SAN-Q9-VOn"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Tgc-re-gI7">
<rect key="frame" x="0.0" y="775.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Tgc-re-gI7" id="haV-RB-dEa">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vZa-EO-FAZ">
<rect key="frame" x="125" y="7" width="70" height="30"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<state key="normal" title="Export DB"/>
<connections>
<action selector="exportDB" destination="qdB-ZO-LHY" eventType="touchUpInside" id="FYN-Zz-UK4"/>
<action selector="exportDB" destination="qdB-ZO-LHY" eventType="touchUpInside" id="NPx-9w-ua0"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="twS-Ne-dU0" firstAttribute="centerY" secondItem="ZTz-vZ-l5p" secondAttribute="centerY" id="LgK-8q-r6K"/>
<constraint firstItem="twS-Ne-dU0" firstAttribute="centerX" secondItem="ZTz-vZ-l5p" secondAttribute="centerX" id="ltC-Ba-Bxr"/>
<constraint firstItem="vZa-EO-FAZ" firstAttribute="centerX" secondItem="haV-RB-dEa" secondAttribute="centerX" id="h0M-qz-pHV"/>
<constraint firstItem="vZa-EO-FAZ" firstAttribute="centerY" secondItem="haV-RB-dEa" secondAttribute="centerY" id="xBJ-94-nJh"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
@@ -282,6 +350,8 @@
<outlet property="cellNotificationConnectionAlert" destination="OTC-Kt-LFT" id="XiG-CC-4lC"/>
<outlet property="cellNotificationReminder" destination="jZA-aP-aHG" id="sjo-2s-rqW"/>
<outlet property="cellPrivacyAutoDelete" destination="Qyy-0U-yhd" id="PzN-iv-kFl"/>
<outlet property="swcdToggle" destination="c5N-PN-qWU" id="qDy-BX-O85"/>
<outlet property="unresolvableToggle" destination="TSu-IP-KFG" id="Vdb-cm-Uy2"/>
<outlet property="vpnToggle" destination="ZAz-WT-FDb" id="lGX-J8-WrU"/>
</connections>
</tableViewController>
@@ -364,7 +434,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" id="zaV-mh-eqb">
<rect key="frame" x="252" y="6" width="54" height="31"/>
<rect key="frame" x="250" y="6" width="54" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="toggleAllowRestartReminder:" destination="JYM-cs-i4H" eventType="valueChanged" id="F4e-k2-bni"/>
@@ -391,7 +461,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" id="HaE-En-NH3">
<rect key="frame" x="252" y="6" width="54" height="31"/>
<rect key="frame" x="250" y="6" width="54" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="toggleAllowRestartNotify:" destination="JYM-cs-i4H" eventType="valueChanged" id="12C-h5-mrR"/>
@@ -418,7 +488,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" id="N2Q-cU-pkd">
<rect key="frame" x="252" y="6" width="54" height="31"/>
<rect key="frame" x="250" y="6" width="54" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="toggleAllowRestartBadge:" destination="JYM-cs-i4H" eventType="valueChanged" id="76l-6y-fOu"/>
@@ -476,7 +546,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mTm-Rm-1RQ">
<rect key="frame" x="255" y="6" width="49" height="31"/>
<rect key="frame" x="255" y="6" width="48" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="toggleAllowRecordingReminder:" destination="JYM-cs-i4H" eventType="valueChanged" id="unC-Ur-jPM"/>
@@ -602,7 +672,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="who-8G-voz">
<rect key="frame" x="256" y="6" width="48" height="31"/>
<rect key="frame" x="256" y="6" width="47" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<connections>
<action selector="toggleShowNotifications:" destination="D2a-Po-vDU" eventType="valueChanged" id="Thg-6R-7wM"/>
@@ -832,6 +902,6 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
</scenes>
<inferredMetricsTieBreakers>
<segue reference="tUF-Kv-koO"/>
<segue reference="6wc-d1-VYY"/>
<segue reference="EzT-Xq-wka"/>
</inferredMetricsTieBreakers>
</document>

View File

@@ -161,4 +161,12 @@ struct VPNAppMessage {
static func isRecording(_ state: CurrentRecordingState) -> Self {
.init("recording-now:\(state.rawValue)")
}
/// Triggered whenever user taps on the switch in settings
static func disconnectUnresolvable(_ state: Bool) -> Self {
.init("disconnect-unresolvable:\(state ? 1 : 0)")
}
/// Triggered whenever user taps on the switch in settings
static func disconnectSWCD(_ state: Bool) -> Self {
.init("disconnect-swcd:\(state ? 1 : 0)")
}
}

View File

@@ -10,6 +10,10 @@ class GlassVPNHook {
private var cachedNotify: CachedConnectionAlert!
private var currentlyRecording: Bool = false
public var forceDisconnectUnresolvable: Bool = false
public var forceDisconnectSWCD: Bool = false
init() { reset() }
/// Reload from stored settings and rebuilt binary search tree
@@ -18,6 +22,8 @@ class GlassVPNHook {
setAutoDelete(PrefsShared.AutoDeleteLogsDays)
cachedNotify = CachedConnectionAlert()
currentlyRecording = PrefsShared.CurrentlyRecording != .Off
forceDisconnectUnresolvable = PrefsShared.ForceDisconnectUnresolvableDNS
forceDisconnectSWCD = PrefsShared.ForceDisconnectSWCD
}
/// Invalidate auto-delete timer and release stored properties. You should nullify this instance afterwards.
@@ -28,6 +34,8 @@ class GlassVPNHook {
autoDeleteTimer?.invalidate()
cachedNotify = nil
currentlyRecording = false
forceDisconnectUnresolvable = false
forceDisconnectSWCD = false
}
/// Call this method from `PacketTunnelProvider.handleAppMessage(_:completionHandler:)`
@@ -50,6 +58,12 @@ class GlassVPNHook {
let newState = CurrentRecordingState(rawValue: Int(value) ?? 0)
currentlyRecording = newState != .Off
return
case "disconnect-unresolvable":
forceDisconnectUnresolvable = value == "1"
return
case "disconnect-swcd":
forceDisconnectSWCD = value == "1"
return
default: break
}
}
@@ -79,6 +93,11 @@ class GlassVPNHook {
return blockActive
}
func silentlyPrevented(_ domain: String) {
// TODO: persist in a separate db/table?
NSLog("[VPN.INFO] preventing connection to \(domain)")
}
/// Build binary tree for reverse DNS lookup
private func reloadDomainFilter() {
let tmp = AppDB?.loadFilters()?.map({
@@ -133,13 +152,12 @@ class GlassVPNHook {
@objc private func autoDeleteNow(_ sender: Timer) {
NSLog("[VPN.INFO] Auto-delete old logs")
queue.async {
guard sender.isValid else { return }
do {
try AppDB?.dnsLogsDeleteOlderThan(days: sender.userInfo as! Int)
} catch {
NSLog("[VPN.WARN] Couldn't delete logs, will retry in 5 minutes. \(error)")
if sender.isValid {
sender.fireDate = Date().addingTimeInterval(300) // retry in 5 min
}
sender.fireDate = Date().addingTimeInterval(300) // retry in 5 min
}
}
}

View File

@@ -20,15 +20,15 @@ struct AppStoreSearch {
let developer, imageURL: String?
}
static func search(_ term: String, _ closure: @escaping ([Result]?) -> Void) {
static func search(_ term: String, _ closure: @escaping ([Result]?, Error?) -> Void) {
URLSession.shared.dataTask(with: .init(url: .appStoreSearch(query: term))) { data, response, error in
guard let data = data, error == nil,
let response = response as? HTTPURLResponse,
(200 ..< 300) ~= response.statusCode else {
closure(nil)
closure(nil, error ?? URLError(.badServerResponse))
return
}
closure(jsonSearchToList(data))
closure(jsonSearchToList(data), nil)
}.resume()
}

View File

@@ -98,9 +98,7 @@ struct BundleIcon {
return img
}
static func download(_ bundleId: String, urlStr: String, whenDone: @escaping () -> Void) {
if let url = URL(string: urlStr) {
url.download(to: local(bundleId), onSuccess: whenDone)
}
static func download(_ bundleId: String, url: URL, whenDone: @escaping () -> Void) -> URLSessionDownloadTask {
return url.download(to: local(bundleId), onSuccess: whenDone)
}
}

View File

@@ -12,6 +12,10 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
private var searchActive: Bool = false
var delegate: TVCAppSearchDelegate?
private var searchNo = 0
private var searchError: Bool = false
private var downloadQueue: [URLSessionDownloadTask] = []
@IBOutlet private var searchBar: UISearchBar!
override func viewDidLoad() {
@@ -29,6 +33,18 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
dismiss(animated: true)
}
private func showManualEntryAlert() {
let alert = AskAlert(title: "App Name",
text: "Be as descriptive as possible. Preferably use app bundle id if available. Alternatively use app name or a link to a public repository.",
buttonText: "Set") {
self.delegate?.appSearch(didSelect: "_manually", appName: $0.textFields?.first?.text, developer: nil)
self.closeThis()
}
alert.addTextField { $0.placeholder = "com.apple.notes" }
alert.presentIn(self)
}
// MARK: - Table View Data Source
override func numberOfSections(in _: UITableView) -> Int {
@@ -60,7 +76,13 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
case 0:
guard dataSource.count > 0, indexPath.row < dataSource.count else {
if indexPath.row == 0 {
cell.textLabel?.text = isLoading ? "Loading …" : "no results"
if searchError {
cell.textLabel?.text = "Error loading results"
} else if isLoading {
cell.textLabel?.text = "Loading …"
} else {
cell.textLabel?.text = "No results"
}
cell.isUserInteractionEnabled = false
} else {
cell.textLabel?.text = "Create manually …"
@@ -84,13 +106,16 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
preconditionFailure()
}
let sno = searchNo
cell.imageView?.image = BundleIcon.image(bundleId) {
guard let url = altLoadUrl else { return }
BundleIcon.download(bundleId, urlStr: url) {
guard let u = altLoadUrl, let url = URL(string: u) else { return }
self.downloadQueue.append(BundleIcon.download(bundleId, url: url) {
DispatchQueue.main.async {
// make sure its the same request
guard sno == self.searchNo else { return }
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
})
}
cell.isUserInteractionEnabled = true
cell.imageView?.layer.cornerRadius = 6.75
@@ -103,14 +128,7 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
switch indexPath.section {
case 0:
guard indexPath.row < dataSource.count else {
let alert = AskAlert(title: "App Name",
text: "Be as descriptive as possible. Preferably use app bundle id if available. Alternatively use app name or a link to a public repository.",
buttonText: "Set") {
self.delegate?.appSearch(didSelect: "_manually", appName: $0.textFields?.first?.text, developer: nil)
self.closeThis()
}
alert.addTextField { $0.placeholder = "com.apple.notes" }
alert.presentIn(self)
showManualEntryAlert()
return
}
let src = dataSource[indexPath.row]
@@ -130,6 +148,8 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil)
isLoading = true
tableView.reloadData()
for x in downloadQueue { x.cancel() }
downloadQueue = []
if searchText.count > 0 {
perform(#selector(performSearch), with: nil, afterDelay: 0.4)
} else {
@@ -140,18 +160,22 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
/// Internal callback function for delayed text evaluation.
/// This way we can avoid unnecessary searches while user is typing.
@objc private func performSearch() {
func setSource(_ newSource: [AppStoreSearch.Result], _ err: Bool) {
searchNo += 1
searchError = err
dataSource = searchActive ? newSource : []
tableView.reloadData()
}
isLoading = false
let term = searchBar.text?.lowercased() ?? ""
searchActive = term.count > 0
guard searchActive else {
dataSource = []
tableView.reloadData()
setSource([], false)
return
}
AppStoreSearch.search(term) {
self.dataSource = $0 ?? []
AppStoreSearch.search(term) { source, error in
DispatchQueue.main.async {
self.tableView.reloadData()
setSource(source ?? [], error != nil)
}
}
}

View File

@@ -5,14 +5,11 @@ class TVCRecordingDetails: UITableViewController, EditActionsRemove {
var noResults: Bool = false
private lazy var isLongRecording: Bool = record.isLongTerm
@IBOutlet private var shareButton: UIBarButtonItem!
private var showRaw: Bool = false
/// Sorted by `ts` in ascending order (oldest first)
private lazy var dataSourceRaw: [DomainTsPair] = {
let list = RecordingsDB.details(record)
noResults = list.count == 0
shareButton.isEnabled = !noResults
return list
}()
/// Sorted by `count` (descending), then alphabetically
@@ -115,7 +112,6 @@ class TVCRecordingDetails: UITableViewController, EditActionsRemove {
}
}
noResults = dataSourceRaw.count == 0
shareButton.isEnabled = !noResults
return true
}

View File

@@ -6,7 +6,7 @@ class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextD
// vars
var record: Recording!
private var shareNotes: Bool = false // opt-in
private var shareNotes: Bool = true // green switch is more present
private lazy var hasNotes: Bool = (self.record.notes != nil)
private lazy var editedNotes: String = self.record.notes ?? ""
private lazy var weekInYear: String = {
@@ -86,6 +86,13 @@ class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextD
}
}
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake, let key = record.uploadkey {
UIPasteboard.general.string = key
banner(.ok, "Copied to clipboard", timeout: 1)
}
}
// MARK: - Table Data Source
@@ -145,7 +152,7 @@ class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextD
cell = tableView.dequeueReusableCell(withIdentifier: "shareKeyValueCell")!
let src = dataSourceKeyValue[indexPath.row]
cell.textLabel?.text = src.key
let flag = shareNotes && indexPath.row == 4
let flag = indexPath.row == 4 && shareNotes && hasNotes
cell.detailTextLabel?.text = flag ? editedNotes : src.value
case 3:
cell = tableView.dequeueReusableCell(withIdentifier: "shareLogCell")!
@@ -234,8 +241,8 @@ class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextD
// MARK: - Alerts & Banner
private func banner(_ style: NotificationBanner.Style, _ msg: String) {
NotificationBanner(msg, style: style).present(in: self)
private func banner(_ style: NotificationBanner.Style, _ msg: String, timeout: TimeInterval = 3) {
NotificationBanner(msg, style: style).present(in: navigationController!, hideAfter: timeout)
}
private func showAlertAvailableSoon(_ urlStr: String, when: Int?) {

View File

@@ -16,8 +16,24 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
@IBOutlet private var noteBottom: NSLayoutConstraint!
@IBOutlet private var chooseAppTap: UITapGestureRecognizer!
@IBOutlet private var buttonFilter: UIButton!
override func viewDidLoad() {
if deleteOnCancel { // aka newly created
let r = record!
DispatchQueue.global().async {
RecordingsDB.persist(r)
if Prefs.RecordingReminder.Enabled {
PushNotification.scheduleRecordingReminder(force: true)
}
}
buttonFilter.isHidden = true
// mark as destructive
buttonCancel.tintColor = .systemRed
if #available(iOS 13.0, *) {
isModalInPresentation = true
}
}
if record.isLongTerm {
appId = nil
appIcon.image = nil
@@ -44,12 +60,6 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
Duration: \(TimeFormat.from(record.duration))
"""
validateSaveButton()
if deleteOnCancel { // mark as destructive
buttonCancel.tintColor = .systemRed
if #available(iOS 13.0, *) {
isModalInPresentation = true
}
}
UIResponder.keyboardWillShowNotification.observe(call: #selector(keyboardWillShow), on: self)
UIResponder.keyboardWillHideNotification.observe(call: #selector(keyboardWillHide), on: self)
}
@@ -62,12 +72,9 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
// MARK: Save & Cancel Buttons
@IBAction func didTapSave(_ sender: UIBarButtonItem) {
let newlyCreated = deleteOnCancel
if newlyCreated {
// if remains true, `viewDidDisappear` will delete the record
deleteOnCancel = false
}
@IBAction func didTapSave() {
// if remains true, `viewDidDisappear` will delete the record
deleteOnCancel = false
QLog.Debug("updating record #\(record.id)")
if let id = appId, id != "" {
record.appId = id
@@ -81,16 +88,10 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
record.notes = (inputNotes.text == "") ? nil : inputNotes.text
dismiss(animated: true) {
RecordingsDB.update(self.record)
if newlyCreated {
RecordingsDB.persist(self.record)
if Prefs.RecordingReminder.Enabled {
PushNotification.scheduleRecordingReminder(force: true)
}
}
}
}
@IBAction func didTapCancel(_ sender: UIBarButtonItem) {
@IBAction func didTapCancel() {
QLog.Debug("discard edit of record #\(record.id)")
dismiss(animated: true)
}
@@ -103,6 +104,16 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
}
}
@IBAction func didTapFilter() {
if buttonSave.isEnabled {
NotificationBanner("Filter set", style: .ok).present(in: self, hideAfter: 1)
} else {
(presentingViewController as? TBCMain)?.openTab(0)
didTapCancel()
}
VCDateFilter.setFilter(range: record.start, to: record.stop)
}
// MARK: Handle Keyboard & Notes Frame

View File

@@ -10,8 +10,10 @@ class VCRecordings: UIViewController, UINavigationControllerDelegate {
@IBOutlet private var runningView: UIView!
@IBOutlet private var timeLabel: UILabel!
@IBOutlet private var stopButton: UIButton!
@IBOutlet private var startSegment: UISegmentedControl!
override func viewDidLoad() {
startSegment.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.sysLink], for: .normal)
timeLabel.font = timeLabel.font.monoSpace()
if let ongoing = RecordingsDB.getCurrent() {
currentRecording = ongoing

View File

@@ -70,7 +70,7 @@ class TVCOccurrenceContext: UITableViewController {
private var copyDomain: String? = nil
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if cellMenu.start(tableView, indexPath) {
if !firstOrLast(indexPath.row), cellMenu.start(tableView, indexPath) {
copyDomain = cellMenu.getSelected(dataSource)?.domain
self.becomeFirstResponder()
}

View File

@@ -125,4 +125,11 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate {
NotifyDateFilterChanged.post()
}
}
static func setFilter(range from: Timestamp?, to: Timestamp?) {
Prefs.DateFilter.Kind = .ABRange
Prefs.DateFilter.RangeA = from
Prefs.DateFilter.RangeB = to
NotifyDateFilterChanged.post()
}
}

View File

@@ -3,6 +3,8 @@ import UIKit
class TVCSettings: UITableViewController {
@IBOutlet var vpnToggle: UISwitch!
@IBOutlet var unresolvableToggle: UISwitch!
@IBOutlet var swcdToggle: UISwitch!
@IBOutlet var cellDomainsIgnored: UITableViewCell!
@IBOutlet var cellDomainsBlocked: UITableViewCell!
@IBOutlet var cellPrivacyAutoDelete: UITableViewCell!
@@ -14,6 +16,7 @@ class TVCSettings: UITableViewController {
reloadVPNState()
reloadLoggingFilterUI()
reloadPrivacyUI()
reloadAdvancedUI()
NotifyVPNStateChanged.observe(call: #selector(reloadVPNState), on: self)
NotifyDNSFilterChanged.observe(call: #selector(reloadLoggingFilterUI), on: self)
}
@@ -191,6 +194,21 @@ extension TVCSettings {
// MARK: - Advanced
extension TVCSettings {
private func reloadAdvancedUI() {
unresolvableToggle.isOn = PrefsShared.ForceDisconnectUnresolvableDNS
swcdToggle.isOn = PrefsShared.ForceDisconnectSWCD
}
@IBAction private func togglePreventUnresolvable(_ sender: UISwitch) {
PrefsShared.ForceDisconnectUnresolvableDNS = sender.isOn
GlassVPN.send(.disconnectUnresolvable(sender.isOn))
}
@IBAction private func togglePreventSWCD(_ sender: UISwitch) {
PrefsShared.ForceDisconnectSWCD = sender.isOn
GlassVPN.send(.disconnectSWCD(sender.isOn))
}
@IBAction private func exportDB() {
AppDB?.vacuum()
let sheet = UIActivityViewController(activityItems: [URL.internalDB()], applicationActivities: nil)

View File

@@ -43,6 +43,7 @@ class TBCMain: UITabBarController {
}
extension TBCMain {
/// Open tab and pop to root view controller.
@discardableResult func openTab(_ index: Int) -> UIViewController? {
selectedIndex = index
guard let nav = selectedViewController as? UINavigationController else {

View File

@@ -1,5 +1,5 @@
# What are Recordings?
Similar to the default logging, recordings will intercept every request and log it for later review. App recordings are usually 3  5 minutes long and cover a single application. You can utilize recordings for App analysis or to get a ground truth on background traffic.
Similar to the default logging, recordings will intercept every request and log it for later review. App recordings are usually 1  4 minutes long and cover a single application. You can utilize recordings for App analysis or to get a ground truth on background traffic.
Optionally, you can help us by providing your app specific recordings. Together with your findings we can create a community driven privacy monitor. The research results will help you and others avoid Apps that unnecessarily share data with third-party providers.

View File

@@ -1,6 +1,6 @@
# How to record?
Before you begin, there are two types of recordings: app specific recordings and general background activity. The former are usually 3  5 minutes long, the latter need to be at least an hour long.
Before you begin, there are two types of recordings: app specific recordings and general background activity. The former are usually less than 5 minutes long, the latter must be at least an hour long.
### Important notice