Refine app structure for persisted sessions and test coverage
CI / test (push) Has been cancelled

This commit is contained in:
2026-04-18 12:29:32 +02:00
parent 243029c798
commit d534964601
18 changed files with 1850 additions and 1162 deletions
+46
View File
@@ -0,0 +1,46 @@
import Foundation
struct PersistedAppState: Codable, Equatable {
let session: AuthSession
let profile: MemberProfile
let requests: [ApprovalRequest]
let notifications: [AppNotification]
}
protocol AppStateStoring {
func load() -> PersistedAppState?
func save(_ state: PersistedAppState)
func clear()
}
final class UserDefaultsAppStateStore: AppStateStoring {
private let defaults: UserDefaults
private let storageKey: String
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
init(defaults: UserDefaults = .standard, storageKey: String = "persisted-app-state") {
self.defaults = defaults
self.storageKey = storageKey
}
func load() -> PersistedAppState? {
guard let data = defaults.data(forKey: storageKey) else {
return nil
}
return try? decoder.decode(PersistedAppState.self, from: data)
}
func save(_ state: PersistedAppState) {
guard let data = try? encoder.encode(state) else {
return
}
defaults.set(data, forKey: storageKey)
}
func clear() {
defaults.removeObject(forKey: storageKey)
}
}
+3 -36
View File
@@ -61,7 +61,7 @@ actor MockIDPService: IDPServicing {
try await Task.sleep(for: .milliseconds(180))
try validateSignedGPSPosition(in: request)
let context = try parsePayloadContext(from: request.pairingPayload)
let context = try PairingPayloadParser.parse(request.pairingPayload)
notifications.insert(
AppNotification(
title: "Identity proof completed",
@@ -186,7 +186,7 @@ actor MockIDPService: IDPServicing {
}
private func parseSession(from request: PairingAuthenticationRequest) throws -> AuthSession {
let context = try parsePayloadContext(from: request.pairingPayload)
let context = try PairingPayloadParser.parse(request.pairingPayload)
return AuthSession(
deviceName: context.deviceName,
@@ -199,33 +199,6 @@ actor MockIDPService: IDPServicing {
)
}
private func parsePayloadContext(from payload: String) throws -> PayloadContext {
if let components = URLComponents(string: payload),
components.scheme == "idp.global",
components.host == "pair" {
let queryItems = components.queryItems ?? []
let token = queryItems.first(where: { $0.name == "token" })?.value ?? "demo-token"
let origin = queryItems.first(where: { $0.name == "origin" })?.value ?? "code.foss.global"
let device = queryItems.first(where: { $0.name == "device" })?.value ?? "Web Session"
return PayloadContext(
deviceName: device,
originHost: origin,
tokenPreview: String(token.suffix(6))
)
}
if payload.contains("token") || payload.contains("pair") {
return PayloadContext(
deviceName: "Manual Session",
originHost: "code.foss.global",
tokenPreview: String(payload.suffix(6))
)
}
throw AppError.invalidPairingPayload
}
private func pairingMessage(for session: AuthSession) -> String {
let transportSummary: String
switch session.pairingTransport {
@@ -246,7 +219,7 @@ actor MockIDPService: IDPServicing {
return "\(session.deviceName) is now acting as a passport, \(transportSummary) against \(session.originHost)."
}
private func identificationMessage(for context: PayloadContext, signedGPSPosition: SignedGPSPosition?) -> String {
private func identificationMessage(for context: PairingPayloadContext, signedGPSPosition: SignedGPSPosition?) -> String {
if let signedGPSPosition {
return "A signed GPS proof was sent for \(context.deviceName) on \(context.originHost) from \(signedGPSPosition.coordinateSummary) \(signedGPSPosition.accuracySummary)."
}
@@ -254,12 +227,6 @@ actor MockIDPService: IDPServicing {
return "An identity proof was completed for \(context.deviceName) on \(context.originHost)."
}
private struct PayloadContext {
let deviceName: String
let originHost: String
let tokenPreview: String
}
private static func seedRequests() -> [ApprovalRequest] {
[
ApprovalRequest(
@@ -0,0 +1,19 @@
import CryptoKit
import Foundation
enum OneTimePasscodeGenerator {
static func code(for pairingCode: String, at date: Date) -> String {
let timeSlot = Int(date.timeIntervalSince1970 / 30)
let digest = SHA256.hash(data: Data("\(pairingCode)|\(timeSlot)".utf8))
let value = digest.prefix(4).reduce(UInt32(0)) { partialResult, byte in
(partialResult << 8) | UInt32(byte)
}
return String(format: "%06d", locale: Locale(identifier: "en_US_POSIX"), Int(value % 1_000_000))
}
static func renewalCountdown(at date: Date) -> Int {
let elapsed = Int(date.timeIntervalSince1970) % 30
return elapsed == 0 ? 30 : 30 - elapsed
}
}
@@ -0,0 +1,38 @@
import Foundation
struct PairingPayloadContext: Equatable {
let deviceName: String
let originHost: String
let tokenPreview: String
}
enum PairingPayloadParser {
static func parse(_ payload: String) throws -> PairingPayloadContext {
let trimmedPayload = payload.trimmingCharacters(in: .whitespacesAndNewlines)
if let components = URLComponents(string: trimmedPayload),
components.scheme == "idp.global",
components.host == "pair" {
let queryItems = components.queryItems ?? []
let token = queryItems.first(where: { $0.name == "token" })?.value ?? "demo-token"
let origin = queryItems.first(where: { $0.name == "origin" })?.value ?? "code.foss.global"
let device = queryItems.first(where: { $0.name == "device" })?.value ?? "Web Session"
return PairingPayloadContext(
deviceName: device,
originHost: origin,
tokenPreview: String(token.suffix(6))
)
}
if trimmedPayload.contains("token") || trimmedPayload.contains("pair") {
return PairingPayloadContext(
deviceName: "Manual Session",
originHost: "code.foss.global",
tokenPreview: String(trimmedPayload.suffix(6))
)
}
throw AppError.invalidPairingPayload
}
}