2023-08-07 15:57:09 +02:00
|
|
|
import SwiftUI
|
2023-08-09 16:29:18 +02:00
|
|
|
import SFSafeSymbols
|
2023-08-07 15:57:09 +02:00
|
|
|
import CryptoKit
|
|
|
|
|
|
|
|
struct ContentView: View {
|
2023-08-10 18:20:16 +02:00
|
|
|
|
|
|
|
@Binding
|
|
|
|
var didLaunchFromComplication: Bool
|
2023-08-14 10:39:29 +02:00
|
|
|
|
|
|
|
@AppStorage("connectionType")
|
|
|
|
var connectionType: ConnectionStrategy = .remoteFirst
|
2023-08-07 15:57:09 +02:00
|
|
|
|
|
|
|
@AppStorage("server")
|
|
|
|
var serverPath: String = "https://christophhagen.de/sesame/"
|
|
|
|
|
|
|
|
@AppStorage("localIP")
|
|
|
|
var localAddress: String = "192.168.178.104/"
|
|
|
|
|
|
|
|
@AppStorage("counter")
|
|
|
|
var nextMessageCounter: Int = 0
|
|
|
|
|
|
|
|
@AppStorage("compensate")
|
|
|
|
var isCompensatingDaylightTime: Bool = false
|
|
|
|
|
|
|
|
@AppStorage("deviceId")
|
|
|
|
private var deviceId: Int = 0
|
|
|
|
|
|
|
|
@EnvironmentObject
|
|
|
|
var keyManager: KeyManagement
|
2023-08-14 10:39:29 +02:00
|
|
|
|
|
|
|
@EnvironmentObject
|
|
|
|
var history: HistoryManager
|
2023-08-07 15:57:09 +02:00
|
|
|
|
|
|
|
@State
|
|
|
|
var state: ClientState = .noKeyAvailable
|
2023-08-10 18:20:16 +02:00
|
|
|
|
|
|
|
@State
|
|
|
|
var stateResetTimer: Timer?
|
2023-08-07 15:57:09 +02:00
|
|
|
|
|
|
|
let server = Client()
|
|
|
|
|
2023-08-14 10:39:29 +02:00
|
|
|
private var firstTryIsLocalConnection: Bool {
|
|
|
|
switch connectionType {
|
|
|
|
case .local, .localFirst:
|
|
|
|
return true
|
|
|
|
case .remote, .remoteFirst:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private var hasSecondTry: Bool {
|
|
|
|
switch connectionType {
|
|
|
|
case .localFirst, .remoteFirst:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private var secondTryIsLocalConnection: Bool {
|
|
|
|
switch connectionType {
|
|
|
|
case .local, .localFirst:
|
|
|
|
return false
|
|
|
|
case .remote, .remoteFirst:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2023-08-09 16:29:18 +02:00
|
|
|
|
2023-08-07 15:57:09 +02:00
|
|
|
var buttonBackground: Color {
|
|
|
|
state.allowsAction ?
|
|
|
|
.white.opacity(0.2) :
|
|
|
|
.black.opacity(0.2)
|
|
|
|
}
|
|
|
|
|
|
|
|
var buttonColor: Color {
|
|
|
|
state.allowsAction ? .white : .gray
|
|
|
|
}
|
|
|
|
|
|
|
|
var body: some View {
|
2023-08-09 16:29:18 +02:00
|
|
|
HStack {
|
2023-08-07 15:57:09 +02:00
|
|
|
Spacer()
|
2023-08-09 16:29:18 +02:00
|
|
|
VStack(alignment: .center) {
|
|
|
|
Image(systemSymbol: .lock)
|
|
|
|
.resizable()
|
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
|
.fontWeight(.ultraLight)
|
|
|
|
.padding()
|
|
|
|
.onTapGesture(perform: mainButtonPressed)
|
|
|
|
.disabled(!state.allowsAction)
|
2023-08-10 18:20:16 +02:00
|
|
|
if state == .waitingForResponse {
|
|
|
|
ProgressView()
|
|
|
|
.progressViewStyle(CircularProgressViewStyle())
|
|
|
|
.frame(width: 20, height: 20)
|
|
|
|
} else {
|
|
|
|
Text(state.actionText)
|
|
|
|
.font(.subheadline)
|
|
|
|
}
|
2023-08-07 15:57:09 +02:00
|
|
|
}
|
|
|
|
Spacer()
|
|
|
|
}
|
|
|
|
.background(state.color)
|
|
|
|
.animation(.easeInOut, value: state.color)
|
2023-08-09 16:29:18 +02:00
|
|
|
.onAppear {
|
2023-08-10 18:20:16 +02:00
|
|
|
if state == .noKeyAvailable, keyManager.hasAllKeys {
|
2023-08-09 16:29:18 +02:00
|
|
|
state = .ready
|
|
|
|
}
|
|
|
|
}
|
2023-08-10 18:20:16 +02:00
|
|
|
.onChange(of: didLaunchFromComplication) { launched in
|
|
|
|
guard launched else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
didLaunchFromComplication = false
|
|
|
|
mainButtonPressed()
|
|
|
|
}
|
2023-08-07 15:57:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func mainButtonPressed() {
|
2023-08-14 10:39:29 +02:00
|
|
|
guard let keys = keyManager.getAllKeys(),
|
2023-08-07 15:57:09 +02:00
|
|
|
let deviceId = UInt8(exactly: deviceId) else {
|
|
|
|
return
|
|
|
|
}
|
2023-08-14 10:39:29 +02:00
|
|
|
sendMessage(from: deviceId, using: keys, isFirstTry: true)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func sendMessage(from deviceId: UInt8, using keys: KeySet, isFirstTry: Bool) {
|
2023-08-10 18:20:16 +02:00
|
|
|
preventStateReset()
|
2023-08-07 15:57:09 +02:00
|
|
|
state = .waitingForResponse
|
2023-08-14 10:39:29 +02:00
|
|
|
let localConnection = isFirstTry ? firstTryIsLocalConnection : secondTryIsLocalConnection
|
2023-08-07 15:57:09 +02:00
|
|
|
Task {
|
2023-08-14 10:39:29 +02:00
|
|
|
let response = await send(
|
|
|
|
count: UInt32(nextMessageCounter),
|
|
|
|
from: deviceId,
|
|
|
|
using: keys,
|
|
|
|
to: server,
|
|
|
|
over: localConnection,
|
|
|
|
while: isCompensatingDaylightTime,
|
|
|
|
localAddress: localAddress,
|
|
|
|
remoteAddress: serverPath)
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
state = response.response
|
|
|
|
scheduleStateReset()
|
|
|
|
if let counter = response.responseMessage?.id {
|
|
|
|
nextMessageCounter = Int(counter)
|
|
|
|
}
|
2023-08-07 15:57:09 +02:00
|
|
|
}
|
2023-08-14 10:39:29 +02:00
|
|
|
save(historyItem: response)
|
|
|
|
guard isFirstTry, hasSecondTry else {
|
2023-08-07 15:57:09 +02:00
|
|
|
return
|
|
|
|
}
|
2023-08-14 10:39:29 +02:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
sendMessage(from: deviceId, using: keys, isFirstTry: false)
|
2023-08-07 15:57:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-10 18:20:16 +02:00
|
|
|
|
|
|
|
private func preventStateReset() {
|
|
|
|
stateResetTimer?.invalidate()
|
|
|
|
stateResetTimer = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
private func scheduleStateReset() {
|
|
|
|
stateResetTimer?.invalidate()
|
|
|
|
stateResetTimer = Timer.scheduledTimer(withTimeInterval: 8.0, repeats: false) { _ in
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
resetState()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func resetState() {
|
|
|
|
state = keyManager.hasAllKeys ? .ready : .noKeyAvailable
|
|
|
|
preventStateReset()
|
|
|
|
}
|
2023-08-07 15:57:09 +02:00
|
|
|
|
|
|
|
private func save(historyItem: HistoryItem) {
|
2023-08-09 16:29:18 +02:00
|
|
|
do {
|
|
|
|
try history.save(item: historyItem)
|
|
|
|
} catch {
|
|
|
|
print("Failed to save item: \(error)")
|
|
|
|
}
|
2023-08-07 15:57:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-14 10:39:29 +02:00
|
|
|
private func send(count: UInt32, from deviceId: UInt8, using keys: KeySet, to server: Client, over localConnection: Bool, while compensatingTime: Bool, localAddress: String, remoteAddress: String) async -> HistoryItem {
|
|
|
|
let sentTime = Date()
|
|
|
|
// Add time to compensate that the device is using daylight savings time
|
|
|
|
let timeCompensation: UInt32 = compensatingTime ? 3600 : 0
|
|
|
|
let content = Message.Content(
|
|
|
|
time: sentTime.timestamp + timeCompensation,
|
|
|
|
id: count,
|
|
|
|
device: deviceId)
|
|
|
|
let message = content.authenticate(using: keys.remote)
|
|
|
|
print("Sending message \(count)")
|
|
|
|
let address = localConnection ? localAddress : remoteAddress
|
|
|
|
let (newState, responseMessage) = await send(message, to: server, using: keys.server, local: localConnection, address: address)
|
|
|
|
var historyItem = HistoryItem(
|
|
|
|
sent: message.content,
|
|
|
|
sentDate: sentTime,
|
|
|
|
local: localConnection,
|
|
|
|
response: newState,
|
|
|
|
responseDate: .now,
|
|
|
|
responseMessage: responseMessage?.content)
|
|
|
|
|
|
|
|
guard let responseMessage else {
|
|
|
|
return historyItem
|
|
|
|
}
|
|
|
|
guard responseMessage.isValid(using: keys.device) else {
|
|
|
|
historyItem.response = .responseRejected(.invalidAuthentication)
|
|
|
|
return historyItem
|
|
|
|
}
|
|
|
|
return historyItem
|
|
|
|
}
|
|
|
|
|
|
|
|
private func send(_ message: Message, to server: Client, using authToken: Data, local: Bool, address: String) async -> (state: ClientState, response: Message?) {
|
|
|
|
if local {
|
|
|
|
return await server.sendMessageOverLocalNetwork(message, server: address)
|
|
|
|
} else {
|
|
|
|
return await server.send(message, server: address, authToken: authToken)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-07 15:57:09 +02:00
|
|
|
struct ContentView_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
2023-08-10 18:20:16 +02:00
|
|
|
ContentView(didLaunchFromComplication: .constant(false))
|
2023-08-07 15:57:09 +02:00
|
|
|
.environmentObject(KeyManagement())
|
2023-08-14 10:39:29 +02:00
|
|
|
.environmentObject(HistoryManager())
|
2023-08-07 15:57:09 +02:00
|
|
|
}
|
|
|
|
}
|