Files
gentpan c9d722f20d 账号同步、磁盘工具、IP 归属地数据源与纯净度、弹窗与设置调整
- 账号与同步:GitHub / Google / Apple 登录后把菜单栏项目与风格等偏好同步到 getopenstats.com,换一台 Mac 登录即恢复(AccountSync 模块、SyncController、SettingsDocument、账号设置页,openstats:// 回调)
- 磁盘页成为工具页:已用 / 可清除 / 可用分段容量条、空间分析、文件系统检查、本地快照清理(辅助工具协议 v4)、其他磁盘推出
- 公网 IP:归属地数据源可选 CleanIP.io / ipapi.is / DB-IP / ipinfo.io / 本地 GeoLite2,每台 Mac 自己查、本地缓存 7 天;IPv4 / IPv6 双栈各自查询与切换;弹窗显示 ASN、运营方、原生 / 住宅等徽章与纯净度评分色带;接口区块可重置上传 / 下载统计
- 弹窗:右上角按钮改为该指标自己的图标 + 总设置;主窗口页面标题栏的开关点不动已修复;滚动条改为悬浮式,内容不再偏左;网络弹窗卡片被标题行撑宽的问题已修复
- 设置:⌘ 拖动排序说明;每个菜单栏项目单独选风格并带实时预览;网络与磁盘的进程榜单固定 5 行并平滑排序;CPU 持续高负载通知
- 设计系统:分段条、评分色带、标签徽章、胶囊切换、刷新按钮等组件与徽章配色 token
- 更新日志、架构说明、README 与译文同步
2026-09-15 03:37:24 +05:00

75 lines
2.5 KiB
Swift
Raw Permalink 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.
import Foundation
import Security
/// 保存登录令牌的地方:正式环境是钥匙串,测试用内存
public protocol TokenStore: Sendable {
func read() -> String?
func write(_ token: String) throws
func delete()
}
/// 登录钥匙串里的通用密码项。用文件式钥匙串而不是数据保护钥匙串:后者要求应用有 application-identifier,
/// 本地 ad-hoc 签名的开发构建没有
public struct KeychainTokenStore: TokenStore {
public let service: String
public let account: String
public init(service: String = "com.openstats.sync", account: String = "session") {
self.service = service
self.account = account
}
private var query: [String: Any] {
[kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account]
}
public func read() -> String? {
var query = query
query[kSecReturnData as String] = true
query[kSecMatchLimit as String] = kSecMatchLimitOne
var result: AnyObject?
guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,
let data = result as? Data else { return nil }
return String(data: data, encoding: .utf8)
}
public func write(_ token: String) throws {
let data = Data(token.utf8)
let update = [kSecValueData as String: data]
var status = SecItemUpdate(query as CFDictionary, update as CFDictionary)
if status == errSecItemNotFound {
var item = query
item[kSecValueData as String] = data
item[kSecAttrLabel as String] = "OpenStats 同步"
status = SecItemAdd(item as CFDictionary, nil)
}
guard status == errSecSuccess else { throw KeychainError(status: status) }
}
public func delete() {
SecItemDelete(query as CFDictionary)
}
}
public struct KeychainError: Error, LocalizedError {
public let status: OSStatus
public var errorDescription: String? {
(SecCopyErrorMessageString(status, nil) as String?) ?? "Keychain error \(status)"
}
}
/// 测试与截图用的内存实现
public final class MemoryTokenStore: TokenStore, @unchecked Sendable {
private let lock = NSLock()
private var token: String?
public init(token: String? = nil) { self.token = token }
public func read() -> String? { lock.withLock { token } }
public func write(_ token: String) { lock.withLock { self.token = token } }
public func delete() { lock.withLock { token = nil } }
}