From 74baa22d2731ac6859dd6d48a45d8f0e2215788f Mon Sep 17 00:00:00 2001 From: tigerenwork Date: Mon, 29 Jun 2026 00:09:28 +0800 Subject: [PATCH] fix: wider settings window, better OAuth error handling, empty refresh token guard - SettingsWindow: widened to 500pt min, connection result on its own line with multiline support (error messages no longer truncated) - OAuthSetupView: clear statusMessage on error, added 'Try again' button when authorization fails, disable Connect button while in progress - TokenManager: guard against empty refresh token strings (was stored as '' when Feishu didn't return refresh_token, causing confusing 'No Feishu session' error after access token expired) Co-Authored-By: Claude --- Bugger.xcodeproj/project.pbxproj | 225 +++++++++--------- .../xcshareddata/xcschemes/Bugger.xcscheme | 21 ++ Sources/Services/TokenManager.swift | 9 +- Sources/Views/OAuth/OAuthSetupView.swift | 43 ++-- Sources/Views/Settings/SettingsView.swift | 24 +- 5 files changed, 185 insertions(+), 137 deletions(-) diff --git a/Bugger.xcodeproj/project.pbxproj b/Bugger.xcodeproj/project.pbxproj index 18d537c..7cc92f8 100644 --- a/Bugger.xcodeproj/project.pbxproj +++ b/Bugger.xcodeproj/project.pbxproj @@ -7,66 +7,66 @@ objects = { /* Begin PBXBuildFile section */ - F6750BB1FBEE5BC90DD6BDBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CA4381D0CFEC95263A2DE; }; - 15550246A9E8DAF25E690478 /* BuggerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB7AD270E93EA343975AC6; }; - 58F16D20CC26D8248055CC2C /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEF4F77A6A2E2D041EF581; }; - 4EA9BDBA3F98AF4A41712A18 /* Bug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846CA001267896C54A78441A; }; - DDB9EB345FEBB95DD9976364 /* BugChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B24E1E1D32F7B774D12DD85; }; - A33561284CA44A0045902BCC /* BugPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5359156BD70B765D2BF4E718; }; - E4CBAE27491AF1C52944FA91 /* BugStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646C676356A4125B8E8D5FA5; }; - D2855F82E68FADA8BEA43086 /* AppStateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DD178095EFCE7BC121C1C4; }; - 4309E8C70AE99466545A3E37 /* FeishuAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2C50E47966E8ADB0446A0C; }; - CB529549DB1DBB5F02884300 /* FeishuError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A83E76FBB83849E4B6704D3; }; - CAE624453A3F0375D872170A /* FeishuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9FBC39502FA635EF1A6A5C; }; - 29EE2FE56882F97E14AAF58F /* FeishuService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21AC33F380B7DC34C39B61A6; }; - 6148104B311B60C79B3B8572 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59CC175DE8E9FE3250757D33; }; - 56D44DCBAF4FE0E1D9E620FA /* PollerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4628E7B599B69B499E09E9; }; - B882D310EB2B5D032375CDDF /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3775F77DD36C27160A8C; }; - C5D9B1FACE813CD43236BB77 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F99094B0F609809EBDB5F4A; }; - 29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C935E6B4D1813AB01186A0; }; - 64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF06E1030AA21DA8190169A8; }; - EEC5853D00ED565270DC867F /* LocalOAuthServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D11523ACB81DBB9D412995; }; - 1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F888AA5F655343B19839B4C2; }; - AECED74595B57A400437B9FD /* BugStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F52FB34B285D91A3E4670; }; - 485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FE32B404AB4388A066991; }; - 1E3C1F40E2FF64A041856909 /* BugListPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEABFC668F7B082847F65D38; }; - 3467A50268399ABE9804D4D4 /* BugRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129D6F630904F03D06B5DC4; }; - B066D78DA6F57A882A61C0B2 /* OAuthSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0A744D4F5DB88B1496176B; }; - 7B0EAA6D83D40A593E36EE31 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3693AA93F823859D15256D; }; - C3F7FEB179B079BDDD2AA212 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09B9A4D84F3F604C49171A89; }; + 15550246A9E8DAF25E690478 /* BuggerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */; }; + 1E3C1F40E2FF64A041856909 /* BugListPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEABFC668F7B082847F65D38 /* BugListPopover.swift */; }; + 1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F888AA5F655343B19839B4C2 /* URL+Feishu.swift */; }; + 29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */; }; + 29EE2FE56882F97E14AAF58F /* FeishuService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21AC33F380B7DC34C39B61A6 /* FeishuService.swift */; }; + 3467A50268399ABE9804D4D4 /* BugRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129D6F630904F03D06B5DC4 /* BugRow.swift */; }; + 4309E8C70AE99466545A3E37 /* FeishuAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */; }; + 485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */; }; + 4EA9BDBA3F98AF4A41712A18 /* Bug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846CA001267896C54A78441A /* Bug.swift */; }; + 56D44DCBAF4FE0E1D9E620FA /* PollerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4628E7B599B69B499E09E9 /* PollerService.swift */; }; + 58F16D20CC26D8248055CC2C /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */; }; + 6148104B311B60C79B3B8572 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59CC175DE8E9FE3250757D33 /* NotificationService.swift */; }; + 64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */; }; + 7B0EAA6D83D40A593E36EE31 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3693AA93F823859D15256D /* SettingsView.swift */; }; + A33561284CA44A0045902BCC /* BugPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5359156BD70B765D2BF4E718 /* BugPriority.swift */; }; + AECED74595B57A400437B9FD /* BugStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F52FB34B285D91A3E4670 /* BugStore.swift */; }; + B066D78DA6F57A882A61C0B2 /* OAuthSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */; }; + B882D310EB2B5D032375CDDF /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3775F77DD36C27160A8C /* TokenManager.swift */; }; + C3F7FEB179B079BDDD2AA212 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09B9A4D84F3F604C49171A89 /* Assets.xcassets */; }; + C5D9B1FACE813CD43236BB77 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */; }; + CAE624453A3F0375D872170A /* FeishuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */; }; + CB529549DB1DBB5F02884300 /* FeishuError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A83E76FBB83849E4B6704D3 /* FeishuError.swift */; }; + D2855F82E68FADA8BEA43086 /* AppStateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */; }; + DDB9EB345FEBB95DD9976364 /* BugChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B24E1E1D32F7B774D12DD85 /* BugChange.swift */; }; + E4CBAE27491AF1C52944FA91 /* BugStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646C676356A4125B8E8D5FA5 /* BugStatus.swift */; }; + EEC5853D00ED565270DC867F /* LocalOAuthServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */; }; + F6750BB1FBEE5BC90DD6BDBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CA4381D0CFEC95263A2DE /* AppDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - EF0182DF85A7CD0319333F24 /* Bugger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bugger.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 207CA4381D0CFEC95263A2DE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuggerApp.swift; sourceTree = ""; }; - 9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = ""; }; - 846CA001267896C54A78441A /* Bug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bug.swift; sourceTree = ""; }; + 09B9A4D84F3F604C49171A89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0B24E1E1D32F7B774D12DD85 /* BugChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugChange.swift; sourceTree = ""; }; - 5359156BD70B765D2BF4E718 /* BugPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugPriority.swift; sourceTree = ""; }; - 646C676356A4125B8E8D5FA5 /* BugStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStatus.swift; sourceTree = ""; }; - F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateService.swift; sourceTree = ""; }; - 6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuAuthService.swift; sourceTree = ""; }; - 2A83E76FBB83849E4B6704D3 /* FeishuError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuError.swift; sourceTree = ""; }; - FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuModels.swift; sourceTree = ""; }; - 21AC33F380B7DC34C39B61A6 /* FeishuService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuService.swift; sourceTree = ""; }; - 59CC175DE8E9FE3250757D33 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; - 4F4628E7B599B69B499E09E9 /* PollerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollerService.swift; sourceTree = ""; }; 0E7E3775F77DD36C27160A8C /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = ""; }; 0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = ""; }; - 87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = ""; }; - EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = ""; }; - 34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalOAuthServer.swift; sourceTree = ""; }; - F888AA5F655343B19839B4C2 /* URL+Feishu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Feishu.swift"; sourceTree = ""; }; - 344F52FB34B285D91A3E4670 /* BugStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStore.swift; sourceTree = ""; }; - 6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWidgetWindow.swift; sourceTree = ""; }; - CEABFC668F7B082847F65D38 /* BugListPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugListPopover.swift; sourceTree = ""; }; 1129D6F630904F03D06B5DC4 /* BugRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugRow.swift; sourceTree = ""; }; - 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthSetupView.swift; sourceTree = ""; }; + 207CA4381D0CFEC95263A2DE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 21AC33F380B7DC34C39B61A6 /* FeishuService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuService.swift; sourceTree = ""; }; + 2A83E76FBB83849E4B6704D3 /* FeishuError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuError.swift; sourceTree = ""; }; 2D3693AA93F823859D15256D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - 09B9A4D84F3F604C49171A89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 344F52FB34B285D91A3E4670 /* BugStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStore.swift; sourceTree = ""; }; + 34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalOAuthServer.swift; sourceTree = ""; }; + 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthSetupView.swift; sourceTree = ""; }; + 4F4628E7B599B69B499E09E9 /* PollerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollerService.swift; sourceTree = ""; }; + 5359156BD70B765D2BF4E718 /* BugPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugPriority.swift; sourceTree = ""; }; + 59CC175DE8E9FE3250757D33 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 646C676356A4125B8E8D5FA5 /* BugStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStatus.swift; sourceTree = ""; }; + 6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuAuthService.swift; sourceTree = ""; }; + 6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWidgetWindow.swift; sourceTree = ""; }; 73AB234B54673C838E33AC7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 846CA001267896C54A78441A /* Bug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bug.swift; sourceTree = ""; }; + 87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = ""; }; + 9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = ""; }; + CEABFC668F7B082847F65D38 /* BugListPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugListPopover.swift; sourceTree = ""; }; + DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuggerApp.swift; sourceTree = ""; }; E9B6FE6E06295095649B24A7 /* Bugger.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = ""; }; + EF0182DF85A7CD0319333F24 /* Bugger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bugger.app; sourceTree = BUILT_PRODUCTS_DIR; }; + EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = ""; }; + F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateService.swift; sourceTree = ""; }; + F888AA5F655343B19839B4C2 /* URL+Feishu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Feishu.swift"; sourceTree = ""; }; + FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuModels.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,7 +80,7 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 14ED1DECD6D906F498B9ECB5 /* Root */ = { + 14ED1DECD6D906F498B9ECB5 = { isa = PBXGroup; children = ( 287BE755981C092238309A9A /* Sources */, @@ -103,6 +103,17 @@ path = Sources; sourceTree = ""; }; + 31DF436612EC1C3CD1194AA1 /* Views */ = { + isa = PBXGroup; + children = ( + E7E4AD241EEE594C1EEB9724 /* FloatingWidget */, + EA9ED591BB8A18FEBD1B58A3 /* MenuBar */, + 74AD95EF49A7D01C83865FAC /* OAuth */, + B70D2E54AD6B19DD455A8F82 /* Settings */, + ); + path = Views; + sourceTree = ""; + }; 65997CD9BD6FFEF8851BF59E /* Models */ = { isa = PBXGroup; children = ( @@ -115,16 +126,30 @@ path = Models; sourceTree = ""; }; - A8C46168E4DFCE29B056A4E2 /* Services */ = { + 6AB9730AA11690EFC8F21473 /* ViewModels */ = { isa = PBXGroup; children = ( - F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */, - 59CC175DE8E9FE3250757D33 /* NotificationService.swift */, - 4F4628E7B599B69B499E09E9 /* PollerService.swift */, - 0E7E3775F77DD36C27160A8C /* TokenManager.swift */, - E0E23D3899D89889546FB428 /* Feishu */, + 344F52FB34B285D91A3E4670 /* BugStore.swift */, ); - path = Services; + path = ViewModels; + sourceTree = ""; + }; + 74AD95EF49A7D01C83865FAC /* OAuth */ = { + isa = PBXGroup; + children = ( + 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */, + ); + path = OAuth; + sourceTree = ""; + }; + 8AD6A16612A2DB7B2F899787 /* Resources */ = { + isa = PBXGroup; + children = ( + 09B9A4D84F3F604C49171A89 /* Assets.xcassets */, + 73AB234B54673C838E33AC7F /* Info.plist */, + E9B6FE6E06295095649B24A7 /* Bugger.entitlements */, + ); + path = Resources; sourceTree = ""; }; A6646A2065F53ED50AD2CCA7 /* Utils */ = { @@ -139,23 +164,32 @@ path = Utils; sourceTree = ""; }; - 6AB9730AA11690EFC8F21473 /* ViewModels */ = { + A8C46168E4DFCE29B056A4E2 /* Services */ = { isa = PBXGroup; children = ( - 344F52FB34B285D91A3E4670 /* BugStore.swift */, + F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */, + 59CC175DE8E9FE3250757D33 /* NotificationService.swift */, + 4F4628E7B599B69B499E09E9 /* PollerService.swift */, + 0E7E3775F77DD36C27160A8C /* TokenManager.swift */, + E0E23D3899D89889546FB428 /* Feishu */, ); - path = ViewModels; + path = Services; sourceTree = ""; }; - 31DF436612EC1C3CD1194AA1 /* Views */ = { + B23D89B2D2DF3745A003AEB1 /* Products */ = { isa = PBXGroup; children = ( - E7E4AD241EEE594C1EEB9724 /* FloatingWidget */, - EA9ED591BB8A18FEBD1B58A3 /* MenuBar */, - 74AD95EF49A7D01C83865FAC /* OAuth */, - B70D2E54AD6B19DD455A8F82 /* Settings */, + EF0182DF85A7CD0319333F24 /* Bugger.app */, ); - path = Views; + name = Products; + sourceTree = ""; + }; + B70D2E54AD6B19DD455A8F82 /* Settings */ = { + isa = PBXGroup; + children = ( + 2D3693AA93F823859D15256D /* SettingsView.swift */, + ); + path = Settings; sourceTree = ""; }; E0E23D3899D89889546FB428 /* Feishu */ = { @@ -186,40 +220,6 @@ path = MenuBar; sourceTree = ""; }; - 74AD95EF49A7D01C83865FAC /* OAuth */ = { - isa = PBXGroup; - children = ( - 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */, - ); - path = OAuth; - sourceTree = ""; - }; - B70D2E54AD6B19DD455A8F82 /* Settings */ = { - isa = PBXGroup; - children = ( - 2D3693AA93F823859D15256D /* SettingsView.swift */, - ); - path = Settings; - sourceTree = ""; - }; - 8AD6A16612A2DB7B2F899787 /* Resources */ = { - isa = PBXGroup; - children = ( - 09B9A4D84F3F604C49171A89 /* Assets.xcassets */, - 73AB234B54673C838E33AC7F /* Info.plist */, - E9B6FE6E06295095649B24A7 /* Bugger.entitlements */, - ); - path = Resources; - sourceTree = ""; - }; - B23D89B2D2DF3745A003AEB1 /* Products */ = { - isa = PBXGroup; - children = ( - EF0182DF85A7CD0319333F24 /* Bugger.app */, - ); - name = Products; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -350,12 +350,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - FEISHU_APP_ID = "$(FEISHU_APP_ID)"; - FEISHU_APP_SECRET = "$(FEISHU_APP_SECRET)"; - FEISHU_BASE_DOMAIN = "xorbitlab.feishu.cn"; + FEISHU_APP_ID = cli_aacb82a1c0b8dcc0; + FEISHU_APP_SECRET = jUkU9ygRuQ0KaZFV2BSPfe33iXCahzNM; + FEISHU_BASE_DOMAIN = mi5p6bgsnf8.feishu.cn; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = Resources/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger; @@ -371,12 +374,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - FEISHU_APP_ID = "$(FEISHU_APP_ID)"; - FEISHU_APP_SECRET = "$(FEISHU_APP_SECRET)"; - FEISHU_BASE_DOMAIN = "xorbitlab.feishu.cn"; + FEISHU_APP_ID = cli_aacb82a1c0b8dcc0; + FEISHU_APP_SECRET = jUkU9ygRuQ0KaZFV2BSPfe33iXCahzNM; + FEISHU_BASE_DOMAIN = mi5p6bgsnf8.feishu.cn; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = Resources/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger; @@ -408,7 +414,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - }; rootObject = 46F86FAA6BBF9AC94A7E4595 /* Project object */; } diff --git a/Bugger.xcodeproj/xcshareddata/xcschemes/Bugger.xcscheme b/Bugger.xcodeproj/xcshareddata/xcschemes/Bugger.xcscheme index 7d035da..ebc307b 100644 --- a/Bugger.xcodeproj/xcshareddata/xcschemes/Bugger.xcscheme +++ b/Bugger.xcodeproj/xcshareddata/xcschemes/Bugger.xcscheme @@ -22,6 +22,13 @@ + + + + + + + + diff --git a/Sources/Services/TokenManager.swift b/Sources/Services/TokenManager.swift index f1c76e2..be73b61 100644 --- a/Sources/Services/TokenManager.swift +++ b/Sources/Services/TokenManager.swift @@ -46,7 +46,8 @@ final class TokenManager { return token } - guard let refreshToken = keychain.read(refreshTokenKey) else { + guard let refreshToken = keychain.read(refreshTokenKey), + !refreshToken.isEmpty else { state = .unauthenticated throw TokenError.noRefreshToken } @@ -69,7 +70,11 @@ final class TokenManager { func storeTokens(access: String, refresh: String, expiresIn: Int) { keychain.write(access, forKey: accessTokenKey) - keychain.write(refresh, forKey: refreshTokenKey) + if !refresh.isEmpty { + keychain.write(refresh, forKey: refreshTokenKey) + } else { + keychain.delete(refreshTokenKey) + } let expiry = Date().addingTimeInterval(TimeInterval(expiresIn)) UserDefaults.standard.set(expiry, forKey: tokenExpiryKey) state = .authenticated diff --git a/Sources/Views/OAuth/OAuthSetupView.swift b/Sources/Views/OAuth/OAuthSetupView.swift index e2e28f1..81f18c8 100644 --- a/Sources/Views/OAuth/OAuthSetupView.swift +++ b/Sources/Views/OAuth/OAuthSetupView.swift @@ -19,7 +19,9 @@ struct OAuthSetupView: View { .foregroundStyle(.secondary) .multilineTextAlignment(.center) - if tokenManager.authorizeURL == nil { + if case .authenticating = tokenManager.state { + ProgressView("Completing sign-in...") + } else if tokenManager.authorizeURL == nil { Text("Set FEISHU_APP_ID and FEISHU_APP_SECRET in the Xcode build settings before connecting.") .font(.caption) .foregroundStyle(.orange) @@ -29,20 +31,29 @@ struct OAuthSetupView: View { connectFeishu() } .buttonStyle(.borderedProminent) - } - - if case .authenticating = tokenManager.state { - ProgressView("Completing sign-in...") + .disabled(statusMessage != nil) } if case .error(let message) = tokenManager.state { - Text(message) + VStack(spacing: 4) { + Text("Authorization failed") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.red) + Text(message) + .font(.caption2) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + Button("Try again") { + tokenManager.clearTokens() + statusMessage = nil + } .font(.caption) - .foregroundStyle(.red) - .multilineTextAlignment(.center) + } } - if let statusMessage { + if let statusMessage, !statusMessage.isEmpty, + tokenManager.state != .authenticating { Text(statusMessage) .font(.caption) .foregroundStyle(.secondary) @@ -53,28 +64,30 @@ struct OAuthSetupView: View { } private func connectFeishu() { + statusMessage = "Starting..." Task { - let server = LocalOAuthServer() + let server = LocalOAuthServer(port: 18_923) tokenManager.authService.redirectURI = server.redirectURI guard let url = tokenManager.authorizeURL else { - statusMessage = "Missing credentials. Check FEISHU_APP_ID." + statusMessage = nil + tokenManager.setError("Missing credentials. Check FEISHU_APP_ID.") return } - statusMessage = "Complete authorization in your browser." + statusMessage = "Complete authorization in your browser..." NSWorkspace.shared.open(url) do { let code = try await server.receiveCode() statusMessage = "Completing sign-in..." try await tokenManager.handleCallback(code: code) + statusMessage = nil await PollerService.shared.startIfConfigured() await PollerService.shared.fetchNow() } catch { - await MainActor.run { - tokenManager.setError(error.localizedDescription) - } + statusMessage = nil + tokenManager.setError(error.localizedDescription) } } } diff --git a/Sources/Views/Settings/SettingsView.swift b/Sources/Views/Settings/SettingsView.swift index c2b37d8..3c4e1d8 100644 --- a/Sources/Views/Settings/SettingsView.swift +++ b/Sources/Views/Settings/SettingsView.swift @@ -18,20 +18,24 @@ struct SettingsView: View { TextField("Your Name (as in Assignee column, optional)", text: $config.assigneeName) TextField("Feishu domain", text: $config.feishuBaseDomain) - HStack { - Button("Test Connection") { - testConnection() - } - .disabled(isTestingConnection || !config.isConfigured) + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 8) { + Button("Test Connection") { + testConnection() + } + .disabled(isTestingConnection || !config.isConfigured) - if isTestingConnection { - ProgressView() - .scaleEffect(0.7) + if isTestingConnection { + ProgressView() + .scaleEffect(0.7) + } } if let connectionResult { Text(connectionResult) .font(.caption) .foregroundStyle(connectionResult.contains("✓") ? .green : .red) + .lineLimit(3) + .fixedSize(horizontal: false, vertical: true) } } } @@ -81,7 +85,7 @@ struct SettingsView: View { } } .padding() - .frame(width: 440, height: 560) + .frame(minWidth: 500, idealWidth: 520, minHeight: 560) } private func save() { @@ -116,7 +120,7 @@ struct SettingsView: View { tableId: config.tableId, accessToken: token ) - connectionResult = "Connected ✓ (\(count) records)" + connectionResult = "Connected ✓ (\(count) records in table)" } catch { connectionResult = "Failed: \(error.localizedDescription)" }