Compare commits

...

2 Commits

Author SHA1 Message Date
Christoph Hagen
e9d870bd12 Add connection check in settings 2023-12-27 21:57:32 +01:00
Christoph Hagen
9086c6a916 Fix model, delete if necessary 2023-12-27 21:57:17 +01:00
8 changed files with 168 additions and 25 deletions

View File

@ -1,8 +1,14 @@
import SwiftUI import SwiftUI
import SwiftData import SwiftData
private enum MainScreenSelection: Int {
case unlock = 0
case settings = 1
case history = 2
}
@main @main
struct Sesame_Watch_Watch_AppApp: App { struct SesameWatchApp: App {
@State @State
var modelContainer: ModelContainer var modelContainer: ModelContainer
@ -13,36 +19,32 @@ struct Sesame_Watch_Watch_AppApp: App {
let keyManagement = KeyManagement() let keyManagement = KeyManagement()
@State @State
var selected: Int = 0 private var selectedScreen: MainScreenSelection = .unlock
@State @State
var didLaunchFromComplication = false var didLaunchFromComplication = false
init() { init() {
do { let modelContainer = SesameWatchApp.loadModelContainer()
let modelContainer = try ModelContainer(for: HistoryItem.self) self.modelContainer = modelContainer
self.modelContainer = modelContainer self.coordinator = .init(modelContext: modelContainer.mainContext)
self.coordinator = .init(modelContext: modelContainer.mainContext)
} catch {
fatalError("Failed to create model container: \(error)")
}
} }
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
TabView(selection: $selected) { TabView(selection: $selectedScreen) {
ContentView(coordinator: coordinator, didLaunchFromComplication: $didLaunchFromComplication) ContentView(coordinator: coordinator, didLaunchFromComplication: $didLaunchFromComplication)
.tag(1) .tag(MainScreenSelection.unlock)
SettingsView() SettingsView()
.environmentObject(keyManagement) .environmentObject(keyManagement)
.tag(2) .tag(MainScreenSelection.settings)
HistoryView() HistoryView()
.tag(3) .tag(MainScreenSelection.history)
} }
.tabViewStyle(PageTabViewStyle()) .tabViewStyle(PageTabViewStyle())
.onOpenURL { url in .onOpenURL { url in
selected = 0
didLaunchFromComplication = true didLaunchFromComplication = true
selectedScreen = .unlock
} }
} }
.modelContainer(modelContainer) .modelContainer(modelContainer)

View File

@ -67,6 +67,8 @@
88E197D429EDCE7600BF1D19 /* UInt32+Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DC281B3AC400769EF6 /* UInt32+Coding.swift */; }; 88E197D429EDCE7600BF1D19 /* UInt32+Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2C5C1DC281B3AC400769EF6 /* UInt32+Coding.swift */; };
88E197D729EDCFE800BF1D19 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */; }; 88E197D729EDCFE800BF1D19 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */; };
88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */; }; 88E197D829EDD13B00BF1D19 /* SymmetricKey+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */; };
88E35EF52B3B0A9800485A66 /* App+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E35EF42B3B0A9800485A66 /* App+Extensions.swift */; };
88E35EF62B3B0A9800485A66 /* App+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E35EF42B3B0A9800485A66 /* App+Extensions.swift */; };
E240654B2A8153C6009C1AD8 /* SettingsListTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */; }; E240654B2A8153C6009C1AD8 /* SettingsListTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */; };
E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */; }; E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */; };
E24065512A819066009C1AD8 /* SettingsTextItemLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24065502A819066009C1AD8 /* SettingsTextItemLink.swift */; }; E24065512A819066009C1AD8 /* SettingsTextItemLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24065502A819066009C1AD8 /* SettingsTextItemLink.swift */; };
@ -160,6 +162,7 @@
88E197B329EDC9BC00BF1D19 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; 88E197B329EDC9BC00BF1D19 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
88E197B529EDC9BD00BF1D19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 88E197B529EDC9BD00BF1D19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; }; 88E197D629EDCFE800BF1D19 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
88E35EF42B3B0A9800485A66 /* App+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+Extensions.swift"; sourceTree = "<group>"; };
E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsListTextItem.swift; sourceTree = "<group>"; }; E240654A2A8153C6009C1AD8 /* SettingsListTextItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsListTextItem.swift; sourceTree = "<group>"; };
E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextInputView.swift; sourceTree = "<group>"; }; E240654E2A8159B7009C1AD8 /* SettingsTextInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextInputView.swift; sourceTree = "<group>"; };
E24065502A819066009C1AD8 /* SettingsTextItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextItemLink.swift; sourceTree = "<group>"; }; E24065502A819066009C1AD8 /* SettingsTextItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextItemLink.swift; sourceTree = "<group>"; };
@ -283,6 +286,7 @@
884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */, 884A45CA27A464C000D6E650 /* SymmetricKey+Extensions.swift */,
88AEE3802B22327F0034EDA9 /* UInt32+Random.swift */, 88AEE3802B22327F0034EDA9 /* UInt32+Random.swift */,
8860D76D2B246FC400849FAC /* Text+Extensions.swift */, 8860D76D2B246FC400849FAC /* Text+Extensions.swift */,
88E35EF42B3B0A9800485A66 /* App+Extensions.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -554,6 +558,7 @@
E24EE77227FDCCC00011CFD2 /* Data+Hex.swift in Sources */, E24EE77227FDCCC00011CFD2 /* Data+Hex.swift in Sources */,
8860D7482B23294600849FAC /* SignedMessage+Crypto.swift in Sources */, 8860D7482B23294600849FAC /* SignedMessage+Crypto.swift in Sources */,
8860D74C2B232A7700849FAC /* SesameHeader.swift in Sources */, 8860D74C2B232A7700849FAC /* SesameHeader.swift in Sources */,
88E35EF52B3B0A9800485A66 /* App+Extensions.swift in Sources */,
8860D7622B23803E00849FAC /* ServerChallenge.swift in Sources */, 8860D7622B23803E00849FAC /* ServerChallenge.swift in Sources */,
8860D7432B22858600849FAC /* Date+Timestamp.swift in Sources */, 8860D7432B22858600849FAC /* Date+Timestamp.swift in Sources */,
88AEE3862B22376D0034EDA9 /* Message+Crypto.swift in Sources */, 88AEE3862B22376D0034EDA9 /* Message+Crypto.swift in Sources */,
@ -595,6 +600,7 @@
888362362A80F4420032BBB2 /* HistoryView.swift in Sources */, 888362362A80F4420032BBB2 /* HistoryView.swift in Sources */,
8860D75B2B237FB600849FAC /* SignedMessage+Crypto.swift in Sources */, 8860D75B2B237FB600849FAC /* SignedMessage+Crypto.swift in Sources */,
E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */, E240654F2A8159B7009C1AD8 /* SettingsTextInputView.swift in Sources */,
88E35EF62B3B0A9800485A66 /* App+Extensions.swift in Sources */,
88E197D329EDCE6E00BF1D19 /* MessageResult.swift in Sources */, 88E197D329EDCE6E00BF1D19 /* MessageResult.swift in Sources */,
88E197D129EDCE5F00BF1D19 /* Data+Hex.swift in Sources */, 88E197D129EDCE5F00BF1D19 /* Data+Hex.swift in Sources */,
8860D76C2B246F5E00849FAC /* UInt32+Random.swift in Sources */, 8860D76C2B246F5E00849FAC /* UInt32+Random.swift in Sources */,

View File

@ -0,0 +1,37 @@
import SwiftUI
import SwiftData
extension App {
static func loadModelContainer() -> ModelContainer {
do {
return try ModelContainer(for: HistoryItem.self)
} catch {
print("[WARNING] Removing default SwiftData storage")
removeDefaultModelContainer()
}
// Try again to load an empty container
do {
return try ModelContainer(for: HistoryItem.self)
} catch {
fatalError("Failed to create empty model container: \(error)")
}
}
private static func removeDefaultModelContainer() {
guard let appSupportDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).last else {
fatalError("Failed to get application support directory")
}
do {
try FileManager.default.contentsOfDirectory(at: appSupportDir, includingPropertiesForKeys: nil)
.filter { $0.lastPathComponent.hasPrefix("default") }
.forEach {
try FileManager.default.removeItem(at: $0)
}
} catch {
print(error)
fatalError("Failed to remove default SwiftData database files")
}
}
}

View File

@ -41,6 +41,23 @@ final class RequestCoordinator: ObservableObject {
} }
} }
func checkConnection(using route: TransmissionType? = nil) {
guard !isPerformingRequest else {
return
}
isPerformingRequest = true
Task {
let route = route ?? connectionType.transmissionTypes.first!
let (finalResult, _) = await performChallenge(route: route)
DispatchQueue.main.async {
self.state = finalResult.result
self.isPerformingRequest = false
}
print("Finished connection test: \(finalResult)")
scheduleReturnToReadyState()
}
}
func startUnlock() { func startUnlock() {
guard !isPerformingRequest else { guard !isPerformingRequest else {
return return

View File

@ -73,7 +73,8 @@ struct ContentView: View {
.animation(.easeInOut, value: coordinator.state.color) .animation(.easeInOut, value: coordinator.state.color)
.sheet(isPresented: $showSettingsSheet) { .sheet(isPresented: $showSettingsSheet) {
SettingsView( SettingsView(
keyManager: coordinator.keyManager, keyManager: coordinator.keyManager,
coordinator: coordinator,
serverAddress: $coordinator.serverPath, serverAddress: $coordinator.serverPath,
localAddress: $coordinator.localAddress) localAddress: $coordinator.localAddress)
} }

View File

@ -6,7 +6,7 @@ final class HistoryItem {
let startDate: Date let startDate: Date
let message: Message let message: HistoryMessage
let route: TransmissionType let route: TransmissionType
@ -14,7 +14,11 @@ final class HistoryItem {
init(message: Message, startDate: Date, route: TransmissionType, finishDate: Date) { init(message: Message, startDate: Date, route: TransmissionType, finishDate: Date) {
self.startDate = startDate self.startDate = startDate
self.message = message self.message = .init(
messageType: message.messageType,
clientChallenge: Int(message.clientChallenge),
serverChallenge: Int(message.serverChallenge),
result: message.result)
self.finishDate = finishDate self.finishDate = finishDate
self.route = route self.route = route
} }
@ -28,6 +32,59 @@ final class HistoryItem {
} }
} }
struct HistoryMessage {
/// The type of message being sent.
let messageType: MessageType
/**
* The random nonce created by the remote
*
* This nonce is a random number created by the remote, different for each unlock request.
* It is set for all message types.
*/
let clientChallenge: Int
/**
* A random number to sign by the remote
*
* This nonce is set by the server after receiving an initial message.
* It is set for the message types `challenge`, `request`, and `response`.
*/
let serverChallenge: Int
/**
* The response status for the previous message.
*
* It is set only for messages from the server, e.g. the `challenge` and `response` message types.
* Must be set to `MessageAccepted` for other messages.
*/
let result: MessageResult
}
extension HistoryMessage: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(messageType)
try container.encode(clientChallenge)
try container.encode(serverChallenge)
try container.encode(result)
}
}
extension HistoryMessage: Decodable {
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
self.messageType = try container.decode(MessageType.self)
self.clientChallenge = try container.decode(Int.self)
self.serverChallenge = try container.decode(Int.self)
self.result = try container.decode(MessageResult.self)
}
}
extension HistoryItem: Identifiable { extension HistoryItem: Identifiable {
var id: Double { var id: Double {

View File

@ -8,11 +8,7 @@ struct SesameApp: App {
var modelContainer: ModelContainer var modelContainer: ModelContainer
init() { init() {
do { self.modelContainer = SesameApp.loadModelContainer()
self.modelContainer = try ModelContainer(for: HistoryItem.self)
} catch {
fatalError("Failed to create model container: \(error)")
}
} }
var body: some Scene { var body: some Scene {

View File

@ -1,9 +1,14 @@
import SwiftUI import SwiftUI
import SwiftData
import SFSafeSymbols
struct SettingsView: View { struct SettingsView: View {
let keyManager: KeyManagement let keyManager: KeyManagement
@ObservedObject
var coordinator: RequestCoordinator
@Binding @Binding
var serverAddress: String var serverAddress: String
@ -20,6 +25,19 @@ struct SettingsView: View {
TextField("Server address", text: $serverAddress) TextField("Server address", text: $serverAddress)
.foregroundColor(.secondary) .foregroundColor(.secondary)
.padding(.leading, 8) .padding(.leading, 8)
HStack {
Button("Test") {
coordinator.checkConnection(using: .throughServer)
}.padding(8)
if coordinator.state == .deviceAvailable {
Image(systemSymbol: .checkmarkCircle)
.foregroundColor(.green)
} else if coordinator.state != .notChecked {
Text(coordinator.state.description)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}.padding(.vertical, 8) }.padding(.vertical, 8)
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("Local address") Text("Local address")
@ -44,11 +62,20 @@ struct SettingsView: View {
} }
} }
struct SettingsView_Previews: PreviewProvider { #Preview {
static var previews: some View { do {
SettingsView( let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: HistoryItem.self, configurations: config)
let item = HistoryItem.mock
container.mainContext.insert(item)
try container.mainContext.save()
return SettingsView(
keyManager: KeyManagement(), keyManager: KeyManagement(),
coordinator: .init(modelContext: container.mainContext),
serverAddress: .constant("https://example.com"), serverAddress: .constant("https://example.com"),
localAddress: .constant("192.168.178.42")) localAddress: .constant("192.168.178.42"))
} catch {
fatalError("Failed to create model container.")
} }
} }