mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Add Trusted Devices Authentication
This commit is contained in:
1 parent
78df7fe227
commit
48164b8095
14 files changed
+621
-70
No files matched your search
@@ -26,6 +26,8 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
private var cipherCommitment: Data?
|
||||
private var textPayloadID: Int64 = 0
|
||||
private var bytesToBeTransferred: Int64 = 0
|
||||
private var isAuthenticated = false
|
||||
private var peerCertificate: Sharing_Nearby_PublicCertificate? = nil
|
||||
var isPlainTextTransfer = false
|
||||
|
||||
var wasUserRejected = false
|
||||
@@ -371,9 +373,11 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
pairedEncryption.v1 = Sharing_Nearby_V1Frame()
|
||||
pairedEncryption.v1.type = .pairedKeyEncryption
|
||||
pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame()
|
||||
// Presumably used for all the phone number stuff that no one needs anyway
|
||||
|
||||
// For inbound connections, we do not use provide secretIDHash and signedData
|
||||
pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6)
|
||||
pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72)
|
||||
|
||||
try sendTransferSetupFrame(pairedEncryption)
|
||||
currentState = .sentConnectionResponse
|
||||
} else {
|
||||
@@ -381,16 +385,77 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func processPairedKeyEncryptionFrame(_ frame: Sharing_Nearby_Frame) throws {
|
||||
guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.pairedKeyEncryption") }
|
||||
var pairedResult = Sharing_Nearby_Frame()
|
||||
pairedResult.version = .v1
|
||||
pairedResult.v1 = Sharing_Nearby_V1Frame()
|
||||
pairedResult.v1.type = .pairedKeyResult
|
||||
pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame()
|
||||
pairedResult.v1.pairedKeyResult.status = .unable
|
||||
try sendTransferSetupFrame(pairedResult)
|
||||
let pkeFrame = frame.v1.pairedKeyEncryption
|
||||
|
||||
log("[InboundNearbyConnection \(self.id)] Processing paired key encryption frame with secretIDHash: \(pkeFrame.secretIDHash.hex)")
|
||||
|
||||
log("Signed data: " + pkeFrame.signedData.hex)
|
||||
|
||||
// Check if we can accept the connection automatically
|
||||
if !pkeFrame.secretIDHash.isEmpty,
|
||||
let trustedCertData = TrustStore.shared.findTrustedKey(for: pkeFrame.secretIDHash),
|
||||
let trustedCert = try? Sharing_Nearby_PublicCertificate(serializedBytes: trustedCertData),
|
||||
let peerGenericKey = try? Securemessage_GenericPublicKey(serializedBytes: trustedCert.publicKey),
|
||||
let authKeyData = self.authKey?.data() {
|
||||
|
||||
log("[InboundNearbyConnection \(self.id)] Found trusted certificate for secretIDHash: \(pkeFrame.secretIDHash.hex)")
|
||||
|
||||
let domain = Domain.instance(curve: .EC256r1)
|
||||
let ecKey = peerGenericKey.ecP256PublicKey
|
||||
let point = Point(BInt(magnitude: [UInt8](ecKey.x)), BInt(magnitude: [UInt8](ecKey.y)))
|
||||
|
||||
guard let peerPublicKey = try? ECPublicKey(domain: domain, w: point) else {
|
||||
log("[InboundNearbyConnection \(self.id)] Paired key auth failed: Invalid peer public key.")
|
||||
try automaticAuthFailed()
|
||||
return
|
||||
}
|
||||
|
||||
guard pkeFrame.signedData.count == 72 else {
|
||||
log("[InboundNearbyConnection \(self.id)] Paired key auth failed: Invalid signature length.")
|
||||
try automaticAuthFailed()
|
||||
return
|
||||
}
|
||||
|
||||
// Strip header
|
||||
let signedData = pkeFrame.signedData.subdata(in: 8..<72)
|
||||
|
||||
let r = Array(signedData.prefix(32))
|
||||
let s = Array(signedData.suffix(32))
|
||||
|
||||
let signature = ECSignature(domain: domain, r: r, s: s)
|
||||
|
||||
if peerPublicKey.verify(signature: signature, msg: authKeyData) {
|
||||
log("[InboundNearbyConnection \(self.id)] Paired key authentication successful.")
|
||||
try sendPairedKeyResult(status: .success)
|
||||
isAuthenticated = true
|
||||
} else {
|
||||
log("[InboundNearbyConnection \(self.id)] Paired key auth failed: Signature verification failed.")
|
||||
try automaticAuthFailed()
|
||||
}
|
||||
} else {
|
||||
try automaticAuthFailed()
|
||||
}
|
||||
|
||||
func automaticAuthFailed() throws {
|
||||
// Store certificate of peer to let user device later to trust it
|
||||
if let peerCertificate = frame.v1.certificateInfo.publicCertificate.first {
|
||||
log("[InboundNearbyConnection \(self.id)] Storing peer certificate for potential later use")
|
||||
self.peerCertificate = peerCertificate
|
||||
}
|
||||
|
||||
try sendPairedKeyResult(status: .unable)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func sendPairedKeyResult(status: Sharing_Nearby_PairedKeyResultFrame.Status) throws {
|
||||
var resultFrame = Sharing_Nearby_Frame()
|
||||
resultFrame.version = .v1
|
||||
resultFrame.v1.type = .pairedKeyResult
|
||||
resultFrame.v1.pairedKeyResult.status = status
|
||||
try sendTransferSetupFrame(resultFrame)
|
||||
currentState = .sentPairedKeyResult
|
||||
}
|
||||
|
||||
@@ -436,10 +501,8 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
filesToBeReceived[file.payloadID] = info
|
||||
bytesToBeTransferred += file.size
|
||||
}
|
||||
let metadata = TransferMetadata(files: filesToBeReceived.map { $0.value.meta }, id: id, pinCode: pinCode)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.obtainUserConsent(transfer: metadata, device: self.remoteDeviceInfo!, connection: self)
|
||||
}
|
||||
let metadata = TransferMetadata(files: filesToBeReceived.map { $0.value.meta }, id: id, pinCode: pinCode, allowsToBeAddedAsTrustedDevice: self.peerCertificate != nil)
|
||||
checkIfCanProceed(metadata: metadata)
|
||||
}
|
||||
else if let textMetadata = frame.v1.introduction.textMetadata.first {
|
||||
|
||||
@@ -447,16 +510,14 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
|
||||
let isClipboardText = textMetadata.type == .text
|
||||
|
||||
let metadata = TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: textMetadata.textTitle, transferType: isClipboardText ? .text : .url)
|
||||
let metadata = TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: textMetadata.textTitle, transferType: isClipboardText ? .text : .url, allowsToBeAddedAsTrustedDevice: self.peerCertificate != nil)
|
||||
textPayloadID = textMetadata.payloadID
|
||||
|
||||
if isClipboardText {
|
||||
isPlainTextTransfer = true
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.obtainUserConsent(transfer: metadata, device: self.remoteDeviceInfo!, connection: self)
|
||||
}
|
||||
checkIfCanProceed(metadata: metadata)
|
||||
}
|
||||
else {
|
||||
rejectDueToUnsupportedFileType(frame)
|
||||
@@ -464,6 +525,16 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
} else {
|
||||
rejectDueToUnsupportedFileType(frame)
|
||||
}
|
||||
|
||||
|
||||
func checkIfCanProceed(metadata: TransferMetadata) {
|
||||
|
||||
let acceptAutomatically = isAuthenticated || UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.obtainUserConsent(transfer: metadata, device: self.remoteDeviceInfo!, connection: self, acceptAutomatically: acceptAutomatically)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -476,7 +547,12 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
}
|
||||
|
||||
|
||||
func submitUserConsent(accepted: Bool, storeInTemp: Bool = false) {
|
||||
func submitUserConsent(accepted: Bool, trustDevice: Bool, storeInTemp: Bool = false) {
|
||||
|
||||
if trustDevice, let peerCertificate = peerCertificate {
|
||||
TrustStore.shared.addTrusted(certificate: peerCertificate)
|
||||
}
|
||||
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
if accepted {
|
||||
self.acceptTransfer(storeInTemp: storeInTemp)
|
||||
|
||||
@@ -154,8 +154,8 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
}
|
||||
|
||||
|
||||
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, connection _: InboundNearbyConnection) {
|
||||
mainAppDelegate?.obtainUserConsent(transfer: transfer, device: device)
|
||||
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, connection _: InboundNearbyConnection, acceptAutomatically: Bool) {
|
||||
mainAppDelegate?.obtainUserConsent(transfer: transfer, device: device, acceptAutomatically: acceptAutomatically)
|
||||
}
|
||||
|
||||
|
||||
@@ -173,11 +173,11 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
}
|
||||
|
||||
|
||||
public func submitUserConsent(transferID: String, accept: Bool, storeInTemp: Bool = false) {
|
||||
public func submitUserConsent(transferID: String, accept: Bool, trustDevice: Bool, storeInTemp: Bool = false) {
|
||||
guard let conn = incomingConnections[transferID] else { return }
|
||||
|
||||
log("[NearbyConnectionManager] Submitting user consent for transfer \(transferID), accepted: \(accept), store in temp: \(storeInTemp)")
|
||||
conn.submitUserConsent(accepted: accept, storeInTemp: storeInTemp)
|
||||
conn.submitUserConsent(accepted: accept, trustDevice: trustDevice, storeInTemp: storeInTemp)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -271,11 +271,40 @@ class OutboundNearbyConnection: NearbyConnection {
|
||||
|
||||
var pairedEncryption = Sharing_Nearby_Frame()
|
||||
pairedEncryption.version = .v1
|
||||
pairedEncryption.v1 = Sharing_Nearby_V1Frame()
|
||||
pairedEncryption.v1.type = .pairedKeyEncryption
|
||||
pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame()
|
||||
pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6)
|
||||
pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72)
|
||||
|
||||
// add public key
|
||||
var cert = Sharing_Nearby_PublicCertificate()
|
||||
if let signingPrivateKey = IdentityManager.shared.getPrivateKey(),
|
||||
let publicKey = IdentityManager.shared.getPublicKey()?.toGenericPublicKey(),
|
||||
let publicKeyData = IdentityManager.shared.getPublicKey()?.toGenericPublicKeyData(),
|
||||
let publicKeyId = publicKey.id(),
|
||||
let authKeyData = self.authKey?.data() {
|
||||
|
||||
log("[OutboundNearbyConnection \(self.id)] Using private key for signing")
|
||||
|
||||
cert.secretID = publicKeyId
|
||||
cert.publicKey = publicKeyData
|
||||
|
||||
let signatureTuple = signingPrivateKey.sign(msg: authKeyData)
|
||||
|
||||
let rBytes = signatureTuple.r
|
||||
let sBytes = signatureTuple.s
|
||||
|
||||
// Make sure r and s are 32 bytes each by padding with leading zeros if necessary
|
||||
let paddedR = Data(repeating: 0, count: 32 - rBytes.count) + rBytes
|
||||
let paddedS = Data(repeating: 0, count: 32 - sBytes.count) + sBytes
|
||||
|
||||
// Signature: Header (8 bytes) + r (32 bytes) + s (32 bytes)
|
||||
let signatureData = Data(repeating: 0, count: 8) + paddedR + paddedS
|
||||
|
||||
pairedEncryption.v1.certificateInfo.publicCertificate.append(cert)
|
||||
pairedEncryption.v1.pairedKeyEncryption.signedData = signatureData
|
||||
pairedEncryption.v1.pairedKeyEncryption.secretIDHash = cert.secretID
|
||||
|
||||
pairedEncryption.v1.pairedKeyResult.status = .success
|
||||
}
|
||||
|
||||
|
||||
if let qrKey = qrCodePrivateKey, let authKey = authKey {
|
||||
let signature = qrKey.sign(msg: authKey.data())
|
||||
@@ -298,6 +327,7 @@ class OutboundNearbyConnection: NearbyConnection {
|
||||
pairedResult.v1.type = .pairedKeyResult
|
||||
pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame()
|
||||
pairedResult.v1.pairedKeyResult.status = .unable
|
||||
|
||||
try sendTransferSetupFrame(pairedResult)
|
||||
currentState = .sentPairedKeyResult
|
||||
}
|
||||
@@ -375,8 +405,7 @@ class OutboundNearbyConnection: NearbyConnection {
|
||||
|
||||
if let textToSend = textToSend {
|
||||
try sendText(text: textToSend)
|
||||
}
|
||||
if hasURL() {
|
||||
} else if hasURL() {
|
||||
try sendURL()
|
||||
} else {
|
||||
try sendNextFileChunk()
|
||||
|
||||
@@ -12,7 +12,7 @@ import SwiftECC
|
||||
|
||||
|
||||
protocol InboundNearbyConnectionDelegate {
|
||||
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, connection: InboundNearbyConnection)
|
||||
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, connection: InboundNearbyConnection, acceptAutomatically: Bool)
|
||||
func updatedTransferProgress(connection: InboundNearbyConnection, progress: Double)
|
||||
func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ protocol OutboundNearbyConnectionDelegate {
|
||||
|
||||
|
||||
public protocol InboundAppDelegate: AnyObject {
|
||||
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo)
|
||||
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, acceptAutomatically: Bool)
|
||||
func connectionWasTerminated(from device: RemoteDeviceInfo, wasPlainTextTransfer: Bool, error: Error?)
|
||||
func transferProgress(progress: Double)
|
||||
}
|
||||
@@ -107,13 +107,15 @@ public struct TransferMetadata {
|
||||
public let pinCode: String?
|
||||
public let textDescription: String?
|
||||
public let type: TransferType
|
||||
public let allowsToBeAddedAsTrustedDevice: Bool
|
||||
|
||||
init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String? = nil, transferType: TransferType = .file) {
|
||||
init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String? = nil, transferType: TransferType = .file, allowsToBeAddedAsTrustedDevice: Bool) {
|
||||
self.files = files
|
||||
self.id = id
|
||||
self.pinCode = pinCode
|
||||
self.textDescription = textDescription
|
||||
self.type = transferType
|
||||
self.allowsToBeAddedAsTrustedDevice = allowsToBeAddedAsTrustedDevice
|
||||
}
|
||||
|
||||
public enum TransferType {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// AppGroup.swift
|
||||
// QuickDrop
|
||||
//
|
||||
// Created by Leon Böttger on 23.08.25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Contains information about the app group used to share data between main app and widget
|
||||
struct AppGroup {
|
||||
|
||||
/// Identifier of the app group
|
||||
static let appGroupName = "group.com.leonboettger.neardrop"
|
||||
static let appGroupUD = UserDefaults(suiteName: AppGroup.appGroupName)!
|
||||
|
||||
/// Returns the shared App Group directory
|
||||
static var appGroupDirectory: URL? {
|
||||
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// IdentityManager.swift
|
||||
// QuickDrop
|
||||
//
|
||||
// Created by Leon Böttger on 22.08.25.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import SwiftECC
|
||||
import Security
|
||||
import CryptoKit
|
||||
|
||||
class IdentityManager {
|
||||
|
||||
static let shared = IdentityManager()
|
||||
|
||||
private let signingKeyTag = "com.leonboettger.quickdrop.identity.signingkey"
|
||||
private var signingPrivateKey: ECPrivateKey?
|
||||
private var signingPublicKey: ECPublicKey?
|
||||
|
||||
private init() {
|
||||
|
||||
if let (privateKey, publicKey) = loadKeys() {
|
||||
|
||||
log("[IdentityManager] Loaded existing private key.")
|
||||
|
||||
self.signingPrivateKey = privateKey
|
||||
self.signingPublicKey = publicKey
|
||||
}
|
||||
else {
|
||||
log("[IdentityManager] No existing private key found, generating a new one.")
|
||||
let (newPrivate, newPublic) = generateAndStoreKeyPair()
|
||||
self.signingPrivateKey = newPrivate
|
||||
self.signingPublicKey = newPublic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getPrivateKey() -> ECPrivateKey? {
|
||||
return signingPrivateKey
|
||||
}
|
||||
|
||||
|
||||
func getPublicKey() -> ECPublicKey? {
|
||||
return signingPublicKey
|
||||
}
|
||||
|
||||
|
||||
private func generateAndStoreKeyPair() -> (ECPrivateKey, ECPublicKey) {
|
||||
let domain = Domain.instance(curve: .EC256r1)
|
||||
let (pubKey, privKey) = domain.makeKeyPair()
|
||||
|
||||
AppGroup.appGroupUD.set(Data(privKey.s.asSignedBytes()), forKey: signingKeyTag)
|
||||
|
||||
return (privKey, pubKey)
|
||||
}
|
||||
|
||||
|
||||
private func loadKeys() -> (ECPrivateKey, ECPublicKey)? {
|
||||
let keyData = AppGroup.appGroupUD.data(forKey: signingKeyTag)
|
||||
|
||||
guard let keyData = keyData else { return nil }
|
||||
|
||||
let domain = Domain.instance(curve: .EC256r1)
|
||||
guard let privateKey = try? ECPrivateKey(domain: domain, s: .init(signed: Array(keyData))) else {
|
||||
log("[IdentityManager] Failed to reconstruct private key from stored data.")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let pubPoint = try? domain.multiplyPoint(domain.g, privateKey.s) else {
|
||||
log("[IdentityManager] Failed to derive public key from private key.")
|
||||
return nil
|
||||
}
|
||||
guard let publicKey = try? ECPublicKey(domain: domain, w: pubPoint) else {
|
||||
log("[IdentityManager] Failed to reconstruct public key.")
|
||||
return nil
|
||||
}
|
||||
|
||||
return (privateKey, publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ECPublicKey {
|
||||
|
||||
func toGenericPublicKey() -> Securemessage_GenericPublicKey {
|
||||
var genericKey = Securemessage_GenericPublicKey()
|
||||
genericKey.type = .ecP256
|
||||
genericKey.ecP256PublicKey = Securemessage_EcP256PublicKey()
|
||||
genericKey.ecP256PublicKey.x = Data(self.w.x.asSignedBytes())
|
||||
genericKey.ecP256PublicKey.y = Data(self.w.y.asSignedBytes())
|
||||
|
||||
return genericKey
|
||||
}
|
||||
|
||||
|
||||
func toGenericPublicKeyData() -> Data? {
|
||||
let genericKey = self.toGenericPublicKey()
|
||||
return try? genericKey.serializedData()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Securemessage_GenericPublicKey {
|
||||
|
||||
func id() -> Data? {
|
||||
var hasher = SHA256()
|
||||
|
||||
guard let data = try? self.serializedData() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasher.update(data: data)
|
||||
let hash = hasher.finalize()
|
||||
|
||||
return Data(hash).prefix(6)
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ final class LogManager {
|
||||
/// Private initializer
|
||||
private init() {
|
||||
// Use the App Group container
|
||||
let directory = getAppGroupDirectory()
|
||||
let directory = AppGroup.appGroupDirectory
|
||||
|
||||
// Create file name from date
|
||||
let formatter = DateFormatter()
|
||||
@@ -89,7 +89,7 @@ final class LogManager {
|
||||
func deleteLogs() {
|
||||
log("[LUI] Deleting all logs...")
|
||||
|
||||
guard let directory = getAppGroupDirectory() else { return }
|
||||
guard let directory = AppGroup.appGroupDirectory else { return }
|
||||
let fileManager = FileManager.default
|
||||
|
||||
guard let allFiles = try? fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else {
|
||||
@@ -124,10 +124,3 @@ public func log(_ text: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Returns the shared App Group directory
|
||||
func getAppGroupDirectory() -> URL? {
|
||||
let groupIdentifier = "group.com.leonboettger.neardrop"
|
||||
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// TrustStore.swift
|
||||
// QuickDrop
|
||||
//
|
||||
// Created by Leon Böttger on 22.08.25.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
class TrustStore {
|
||||
|
||||
static let shared = TrustStore()
|
||||
private let trustedKeysKey = "com.leonboettger.quickdrop.identity.trustedKeys"
|
||||
|
||||
|
||||
private func getTrustedKeys() -> [String: Data] {
|
||||
return AppGroup.appGroupUD.dictionary(forKey: trustedKeysKey) as? [String: Data] ?? [:]
|
||||
}
|
||||
|
||||
|
||||
func findTrustedKey(for secretIdHash: Data) -> Data? {
|
||||
return getTrustedKeys()[secretIdHash.hex]
|
||||
}
|
||||
|
||||
|
||||
func addTrusted(certificate: Sharing_Nearby_PublicCertificate) {
|
||||
guard let publicKeyData = try? certificate.serializedData() else { return }
|
||||
var keys = getTrustedKeys()
|
||||
keys[certificate.secretID.hex] = publicKeyData
|
||||
AppGroup.appGroupUD.set(keys, forKey: trustedKeysKey)
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,18 @@
|
||||
699DEBAE2AB0573200115D22 /* QuickDropShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; };
|
||||
69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
|
||||
EB16B6122E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
|
||||
EB16B6132E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
|
||||
EB16B6142E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
|
||||
EB16B6152E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
|
||||
EB16B6172E58B4C8007D9792 /* TrustStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6162E58B4C8007D9792 /* TrustStore.swift */; };
|
||||
EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6162E58B4C8007D9792 /* TrustStore.swift */; };
|
||||
EB16B6192E58B4C8007D9792 /* TrustStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6162E58B4C8007D9792 /* TrustStore.swift */; };
|
||||
EB16B61A2E58B4C8007D9792 /* TrustStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6162E58B4C8007D9792 /* TrustStore.swift */; };
|
||||
EB16B7772E59BAA1007D9792 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B7762E59BA9F007D9792 /* AppGroup.swift */; };
|
||||
EB16B7782E59BAA1007D9792 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B7762E59BA9F007D9792 /* AppGroup.swift */; };
|
||||
EB16B7792E59BAA1007D9792 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B7762E59BA9F007D9792 /* AppGroup.swift */; };
|
||||
EB16B77A2E59BAA1007D9792 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B7762E59BA9F007D9792 /* AppGroup.swift */; };
|
||||
EB2F9BFE2DA05F48004010A2 /* BezelNotification in Frameworks */ = {isa = PBXBuildFile; productRef = EB2F9BFD2DA05F48004010A2 /* BezelNotification */; };
|
||||
EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; };
|
||||
EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; };
|
||||
@@ -287,6 +299,9 @@
|
||||
69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; };
|
||||
EB16B6112E58B4B9007D9792 /* IdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManager.swift; sourceTree = "<group>"; };
|
||||
EB16B6162E58B4C8007D9792 /* TrustStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustStore.swift; sourceTree = "<group>"; };
|
||||
EB16B7762E59BA9F007D9792 /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
|
||||
EB2B41E32DC63AC80070FC39 /* SleepManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SleepManager.swift; sourceTree = "<group>"; };
|
||||
EB2F9BFA2DA04B67004010A2 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
||||
EB36435F2D73A29F009CDFF8 /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = "<group>"; };
|
||||
@@ -496,8 +511,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EB96ED8E2D7F8103004B3FF1 /* Localized.swift */,
|
||||
EB16B6112E58B4B9007D9792 /* IdentityManager.swift */,
|
||||
EB2B41E32DC63AC80070FC39 /* SleepManager.swift */,
|
||||
EB16B6162E58B4C8007D9792 /* TrustStore.swift */,
|
||||
EBCC27F82D80C9F70079C478 /* Zip.swift */,
|
||||
EB16B7762E59BA9F007D9792 /* AppGroup.swift */,
|
||||
EB886E512E4E19390052B7D9 /* HKDF.swift */,
|
||||
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */,
|
||||
EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */,
|
||||
@@ -862,6 +880,7 @@
|
||||
files = (
|
||||
EB704A832E4B10D4008E608F /* DistributionDetector.swift in Sources */,
|
||||
EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */,
|
||||
EB16B6172E58B4C8007D9792 /* TrustStore.swift in Sources */,
|
||||
EB886E542E4E19390052B7D9 /* HKDF.swift in Sources */,
|
||||
EBC05C862E33B58300EECC18 /* NetworkFilterIssueView.swift in Sources */,
|
||||
EBC05C872E33B58300EECC18 /* IssueView.swift in Sources */,
|
||||
@@ -877,8 +896,10 @@
|
||||
EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */,
|
||||
EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */,
|
||||
EB950B742E520957002D68FA /* Attachments.swift in Sources */,
|
||||
EB16B7782E59BAA1007D9792 /* AppGroup.swift in Sources */,
|
||||
EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */,
|
||||
EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */,
|
||||
EB16B6122E58B4B9007D9792 /* IdentityManager.swift in Sources */,
|
||||
EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */,
|
||||
EBC05C7B2E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */,
|
||||
EBC05C7C2E33B58100EECC18 /* ApIsolationTester.swift in Sources */,
|
||||
@@ -907,6 +928,7 @@
|
||||
EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */,
|
||||
EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */,
|
||||
EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */,
|
||||
EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */,
|
||||
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */,
|
||||
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */,
|
||||
EB886E5D2E4F13EB0052B7D9 /* Zip.swift in Sources */,
|
||||
@@ -917,6 +939,7 @@
|
||||
EB5C37C12E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */,
|
||||
EB5C37C22E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */,
|
||||
EB5C37C32E377A780052EFD7 /* wire_format.pb.swift in Sources */,
|
||||
EB16B6152E58B4B9007D9792 /* IdentityManager.swift in Sources */,
|
||||
EB5C37C42E377A780052EFD7 /* securegcm.pb.swift in Sources */,
|
||||
EB97A0102E51CD13008B8C5C /* ReceiveModel.swift in Sources */,
|
||||
EBC05C782E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */,
|
||||
@@ -939,6 +962,7 @@
|
||||
EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
|
||||
EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */,
|
||||
EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */,
|
||||
EB16B77A2E59BAA1007D9792 /* AppGroup.swift in Sources */,
|
||||
EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
|
||||
EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */,
|
||||
EBC05C842E33B58300EECC18 /* IssueView.swift in Sources */,
|
||||
@@ -953,6 +977,7 @@
|
||||
EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */,
|
||||
EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */,
|
||||
EB5C37F32E377CE70052EFD7 /* SaveFilesManager.swift in Sources */,
|
||||
EB16B6142E58B4B9007D9792 /* IdentityManager.swift in Sources */,
|
||||
EB886E672E4F2F800052B7D9 /* License.swift in Sources */,
|
||||
EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */,
|
||||
EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */,
|
||||
@@ -979,6 +1004,8 @@
|
||||
EB979F9A2E50CAA6008B8C5C /* PieProgressView.swift in Sources */,
|
||||
EB979F972E50CA23008B8C5C /* SendModel.swift in Sources */,
|
||||
EB979F982E50CA23008B8C5C /* QuickDropiOSApp.swift in Sources */,
|
||||
EB16B61A2E58B4C8007D9792 /* TrustStore.swift in Sources */,
|
||||
EB16B7792E59BAA1007D9792 /* AppGroup.swift in Sources */,
|
||||
EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
|
||||
EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */,
|
||||
EB950B792E521717002D68FA /* AttachmentDetails.swift in Sources */,
|
||||
@@ -999,6 +1026,7 @@
|
||||
EB950B4A2E520436002D68FA /* securegcm.pb.swift in Sources */,
|
||||
EB950B4B2E520436002D68FA /* offline_wire_formats.pb.swift in Sources */,
|
||||
EB950B4C2E520436002D68FA /* InboundNearbyConnection.swift in Sources */,
|
||||
EB16B6132E58B4B9007D9792 /* IdentityManager.swift in Sources */,
|
||||
EB950B4D2E520436002D68FA /* NearbyConnection.swift in Sources */,
|
||||
EB950B4F2E520436002D68FA /* Logger.swift in Sources */,
|
||||
EB950B502E520436002D68FA /* wire_format.pb.swift in Sources */,
|
||||
@@ -1011,6 +1039,8 @@
|
||||
EB950B562E520436002D68FA /* DataModel.swift in Sources */,
|
||||
EB950B3E2E52038A002D68FA /* SendModel.swift in Sources */,
|
||||
EB950B772E521712002D68FA /* AttachmentDetails.swift in Sources */,
|
||||
EB16B6192E58B4C8007D9792 /* TrustStore.swift in Sources */,
|
||||
EB16B7772E59BAA1007D9792 /* AppGroup.swift in Sources */,
|
||||
EB950B3F2E52038A002D68FA /* FilePicker.swift in Sources */,
|
||||
EB950B402E52038A002D68FA /* Configuration.swift in Sources */,
|
||||
EB950B412E52038A002D68FA /* PieProgressView.swift in Sources */,
|
||||
|
||||
@@ -214,6 +214,113 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlwaysAccept" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "قبول دائمًا"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Immer annehmen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Always Accept"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aceptar siempre"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Toujours accepter"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mindig elfogad"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Accetta sempre"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "常に受け入れる"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "항상 수락"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Altijd Accepteren"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sempre Aceitar"
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aceitar Sempre"
|
||||
}
|
||||
},
|
||||
"ro" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Acceptă întotdeauna"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Всегда принимать"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ยอมรับเสมอ"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Завжди приймати"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "始终接受"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AndroidApp" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -856,6 +963,113 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AutoAcceptFromThisDevice" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "قبول تلقائي من هذا الجهاز"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Automatisch von diesem Gerät annehmen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Automatically accept from this device"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aceptar automáticamente desde este dispositivo"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Accepter automatiquement depuis cet appareil"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Automatikusan fogadás erről az eszközről"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Accetta automaticamente da questo dispositivo"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "このデバイスから自動的に受け入れる"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "이 장치로부터 자동으로 수락"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Automatisch accepteren van dit apparaat"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aceitar automaticamente deste dispositivo"
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Aceitar automaticamente deste dispositivo"
|
||||
}
|
||||
},
|
||||
"ro" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Acceptă automat de pe acest dispozitiv"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Автоматически принимать файлы с этого устройства"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ยอมรับอัตโนมัติจากอุปกรณ์นี้"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Автоматично приймати з цього пристрою"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "自动接受来自此设备的文件"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AutomaticallyAcceptFiles" : {
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
|
||||
@@ -14,7 +14,7 @@ let configuration = LUIConfiguration(
|
||||
|
||||
copyrightInformation: licenseText,
|
||||
|
||||
loggingUrl: getAppGroupDirectory(),
|
||||
loggingUrl: AppGroup.appGroupDirectory,
|
||||
|
||||
introductionViewInformation: "QuickDropWelcomeDescription",
|
||||
|
||||
|
||||
@@ -133,7 +133,8 @@ struct DeviceListView: View {
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(Color.primary.opacity(0.6))
|
||||
|
||||
ProgressView()
|
||||
LoadingIndicator()
|
||||
.opacity(0.75)
|
||||
.frame(width: 12, height: 12)
|
||||
}
|
||||
.padding(.vertical, 16)
|
||||
|
||||
@@ -26,15 +26,13 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
|
||||
}
|
||||
|
||||
|
||||
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo) {
|
||||
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, acceptAutomatically: Bool) {
|
||||
|
||||
#if os(macOS)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
AudioManager.playIncomingFileSound()
|
||||
#endif
|
||||
|
||||
let acceptAutomatically = UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue)
|
||||
|
||||
let fileStr: String
|
||||
|
||||
if let textTitle = transfer.textDescription {
|
||||
@@ -64,8 +62,8 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
|
||||
let pinCodeMessage = String(format: "PinCode".localized(), arguments: [transfer.pinCode ?? "?"])
|
||||
let transferID = transfer.id
|
||||
|
||||
if acceptAutomatically {
|
||||
pressAcceptButton(transferID: transfer.id)
|
||||
if acceptAutomatically && isMac() {
|
||||
pressAcceptButton(transferID: transfer.id, trustDevice: false)
|
||||
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, _ in
|
||||
if granted {
|
||||
@@ -89,9 +87,9 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
|
||||
|
||||
let title = "QuickDrop - \(pinCodeMessage)"
|
||||
let primaryButtonTitle = "Accept".localized()
|
||||
let primaryButtonAction = { self.pressAcceptButton(transferID: transferID) }
|
||||
let primaryButtonAction = { (trustDevice: Bool) in self.pressAcceptButton(transferID: transferID, trustDevice: trustDevice) }
|
||||
let secondaryButtonTitle = "Decline".localized()
|
||||
let secondaryButtonAction = { NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: false, storeInTemp: false) }
|
||||
let secondaryButtonAction = { NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: false, trustDevice: false, storeInTemp: false) }
|
||||
|
||||
#if os(macOS)
|
||||
let alert = NSAlert()
|
||||
@@ -101,18 +99,32 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
|
||||
alert.informativeText = mainMessage
|
||||
alert.addButton(withTitle: primaryButtonTitle)
|
||||
alert.addButton(withTitle: secondaryButtonTitle)
|
||||
|
||||
if transfer.allowsToBeAddedAsTrustedDevice {
|
||||
alert.showsSuppressionButton = true
|
||||
alert.suppressionButton?.title = "AutoAcceptFromThisDevice".localized()
|
||||
}
|
||||
|
||||
let result = alert.runModal()
|
||||
|
||||
if result == .alertFirstButtonReturn {
|
||||
primaryButtonAction()
|
||||
primaryButtonAction(alert.suppressionButton?.state == .on)
|
||||
} else if result == .alertSecondButtonReturn {
|
||||
secondaryButtonAction()
|
||||
}
|
||||
#else
|
||||
// iOS
|
||||
ProgressAlert.shared.askForUserPermission(title: title, message: mainMessage, acceptLabel: primaryButtonTitle, rejectLabel: secondaryButtonTitle) { accepted in
|
||||
accepted ? primaryButtonAction() : secondaryButtonAction()
|
||||
let alwaysAcceptLabel = transfer.allowsToBeAddedAsTrustedDevice ? "AlwaysAccept".localized() : nil
|
||||
ProgressAlert.shared.askForUserPermission(title: title, message: mainMessage, acceptLabel: primaryButtonTitle, acceptAlwaysLabel: alwaysAcceptLabel, rejectLabel: secondaryButtonTitle, acceptAutomatically: acceptAutomatically) { accepted in
|
||||
|
||||
switch accepted {
|
||||
case .Accept:
|
||||
primaryButtonAction(false)
|
||||
case .AcceptAlways:
|
||||
primaryButtonAction(true)
|
||||
case .Decline:
|
||||
secondaryButtonAction()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -181,13 +193,13 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
|
||||
}
|
||||
|
||||
|
||||
private func pressAcceptButton(transferID: String) {
|
||||
private func pressAcceptButton(transferID: String, trustDevice: Bool) {
|
||||
if isFileTransferRestricted() {
|
||||
log("[ReceiveModel] Showing plus screen...")
|
||||
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: true, storeInTemp: true)
|
||||
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: true, trustDevice: trustDevice, storeInTemp: true)
|
||||
controlPlusScreen(true)
|
||||
} else {
|
||||
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: true, storeInTemp: false)
|
||||
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: true, trustDevice: trustDevice, storeInTemp: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
-14
@@ -18,23 +18,37 @@ final class ProgressAlert {
|
||||
private weak var progressView: UIProgressView?
|
||||
|
||||
// MARK: - Initial Accept / Decline
|
||||
func askForUserPermission(title: String, message: String, acceptLabel: String, rejectLabel: String, onAccept: @escaping (Bool) -> Void) {
|
||||
func askForUserPermission(title: String, message: String, acceptLabel: String, acceptAlwaysLabel: String?, rejectLabel: String, acceptAutomatically: Bool, onAccept: @escaping (AcceptAction) -> Void) {
|
||||
guard let vc = topMostViewController() else { return }
|
||||
|
||||
let alert = UIAlertController(title: title,
|
||||
message: message,
|
||||
preferredStyle: .alert)
|
||||
|
||||
alert.addAction(UIAlertAction(title: rejectLabel, style: .cancel, handler: { _ in
|
||||
onAccept(false)
|
||||
}))
|
||||
|
||||
alert.addAction(UIAlertAction(title: acceptLabel, style: .default, handler: { _ in
|
||||
onAccept(true)
|
||||
if acceptAutomatically {
|
||||
onAccept(.Accept)
|
||||
self.showProgressAlert(on: vc)
|
||||
}))
|
||||
|
||||
vc.present(alert, animated: true)
|
||||
}
|
||||
else {
|
||||
|
||||
let alert = UIAlertController(title: title,
|
||||
message: message,
|
||||
preferredStyle: .alert)
|
||||
|
||||
alert.addAction(UIAlertAction(title: rejectLabel, style: .cancel, handler: { _ in
|
||||
onAccept(.Decline)
|
||||
}))
|
||||
|
||||
alert.addAction(UIAlertAction(title: acceptLabel, style: .default, handler: { _ in
|
||||
onAccept(.Accept)
|
||||
self.showProgressAlert(on: vc)
|
||||
}))
|
||||
|
||||
if let acceptAlways = acceptAlwaysLabel {
|
||||
alert.addAction(UIAlertAction(title: acceptAlways, style: .default, handler: { _ in
|
||||
onAccept(.AcceptAlways)
|
||||
self.showProgressAlert(on: vc)
|
||||
}))
|
||||
}
|
||||
|
||||
vc.present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Progress Alert
|
||||
@@ -90,4 +104,11 @@ final class ProgressAlert {
|
||||
if let presented = base?.presentedViewController { return topMostViewController(base: presented) }
|
||||
return base
|
||||
}
|
||||
|
||||
|
||||
enum AcceptAction {
|
||||
case Accept
|
||||
case AcceptAlways
|
||||
case Decline
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user