import AppKit import Foundation @Observable final class PollerService { static let shared = PollerService() private let bugStore = BugStore.shared private let feishuService = FeishuService() private let tokenManager = TokenManager.shared private let configService = AppStateService.shared private var timer: Timer? private var isFetching = false private(set) var isRunning = false private init() {} func startIfConfigured() async { guard let config = configService.config, config.isConfigured, tokenManager.isAuthenticated else { return } start(interval: TimeInterval(config.pollIntervalSeconds)) } func start(interval: TimeInterval) { guard !isRunning else { return } isRunning = true Task { await performFetch() } timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in Task { await self?.performFetch() } } timer?.tolerance = interval * 0.1 } func stop() { timer?.invalidate() timer = nil isRunning = false } func restart(interval: TimeInterval) { stop() start(interval: interval) } func fetchNow() async { await performFetch() } private func performFetch() async { guard !isFetching else { return } isFetching = true defer { isFetching = false } await MainActor.run { bugStore.setLoading(true) bugStore.setError(nil) } do { let config = try getConfig() let token = try await tokenManager.getAccessToken() let assignee = try await tokenManager.resolveAssigneeName(config: config, accessToken: token) let bugs = try await feishuService.fetchBugs( config: config, assigneeName: assignee, accessToken: token ) await MainActor.run { bugStore.update(with: bugs) bugStore.setLoading(false) AppDelegate.shared?.updateBadge(count: bugStore.unseenActiveCount) } } catch FeishuError.unauthorized { await MainActor.run { bugStore.setLoading(false) tokenManager.clearTokens() } } catch { await MainActor.run { bugStore.setLoading(false) bugStore.setError(error) } } } private func getConfig() throws -> AppConfig { guard let config = configService.config, config.isConfigured else { throw FeishuError.notConfigured } return config } }