Files
suixinkan_ios_new/suixinkan/App/State/SessionBootstrapper.swift
汉秋 26f4d0e671 Migrate from iOS 17 Observation to iOS 16-compatible Combine architecture.
Lower deployment target to iOS 16, replace @Observable with ObservableObject, add navigation and UI compatibility shims, and include login smoke UI tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 10:16:35 +08:00

113 lines
4.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// SessionBootstrapper.swift
// suixinkan
//
// Created by Codex on 2026/6/20.
//
import Foundation
import Combine
@MainActor
/// token
final class SessionBootstrapper {
private let tokenStore: SessionTokenStore
private let snapshotStore: AccountSnapshotStore
private var didAttemptRestore = false
/// token
init(
tokenStore: SessionTokenStore,
snapshotStore: AccountSnapshotStore
) {
self.tokenStore = tokenStore
self.snapshotStore = snapshotStore
}
/// 使
convenience init() {
self.init(
tokenStore: SessionTokenStore(),
snapshotStore: AccountSnapshotStore()
)
}
///
func restore(
appSession: AppSession,
accountContext: AccountContext,
permissionContext: PermissionContext,
profileAPI: UserProfileServing,
accountContextAPI: AccountContextServing,
toastCenter: ToastCenter
) async {
guard !didAttemptRestore, !appSession.isLoggedIn else { return }
didAttemptRestore = true
guard let token = tokenStore.load() else {
appSession.logout()
return
}
appSession.beginRestoring(token: token)
let cachedSnapshot = restoreCachedSnapshot(to: accountContext)
do {
try await AccountContextLoader().refresh(
accountContext: accountContext,
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContextAPI: accountContextAPI,
cachedCurrentRoleId: cachedSnapshot?.currentRoleId,
cachedCurrentScenicId: cachedSnapshot?.currentScenicId,
cachedCurrentStoreId: cachedSnapshot?.currentStoreId
)
saveLatestSnapshot(from: accountContext, permissionContext: permissionContext)
appSession.markLoggedIn(token: token)
} catch {
if APIError.isAuthenticationExpired(error) {
try? tokenStore.clear()
snapshotStore.clear()
accountContext.reset()
permissionContext.reset()
appSession.logout()
toastCenter.show("登录状态已失效,请重新登录")
} else {
appSession.markLoggedIn(token: token)
toastCenter.show("网络异常,已使用本地登录状态")
}
}
}
///
private func restoreCachedSnapshot(to accountContext: AccountContext) -> AccountSnapshot? {
guard let snapshot = snapshotStore.load() else { return nil }
accountContext.applyLogin(profile: snapshot.profile)
accountContext.replaceScopes(
scenic: snapshot.scenicScopes,
stores: snapshot.storeScopes,
currentScenicId: snapshot.currentScenicId,
currentStoreId: snapshot.currentStoreId
)
return snapshot
}
///
private func saveLatestSnapshot(from accountContext: AccountContext, permissionContext: PermissionContext) {
let existing = snapshotStore.load()
snapshotStore.save(
AccountSnapshot(
profile: accountContext.profile,
accountType: existing?.accountType,
businessUserId: existing?.businessUserId,
currentRoleId: permissionContext.currentRole?.id ?? existing?.currentRoleId,
scenicScopes: accountContext.scenicScopes,
storeScopes: accountContext.storeScopes,
currentScenicId: accountContext.currentScenic?.id,
currentStoreId: accountContext.currentStore?.id
)
)
}
}