feat: add comprehensive debug logging to OAuth flow

Added os.Logger-based logging (subsystem: com.xorbitlab.bugger) covering:
- FeishuAuthService.send(): raw HTTP request URL, method, body, auth header,
  response status code, and raw response body (up to 1000 chars)
- exchangeCode(): before/after token exchange with token previews
- TokenManager.handleCallback(): each step with success/failure
- LocalOAuthServer: server start, connection received, code extracted

View logs in: Xcode debug console, or Console.app → filter 'bugger'

Also fixed type inference ambiguity in generic send<T>() calls.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tigerenwork 2026-06-29 00:18:25 +08:00
parent cf9de54792
commit e275e49795
6 changed files with 177 additions and 147 deletions

View File

@ -7,66 +7,68 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
15550246A9E8DAF25E690478 /* BuggerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */; }; F6750BB1FBEE5BC90DD6BDBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CA4381D0CFEC95263A2DE; };
1E3C1F40E2FF64A041856909 /* BugListPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEABFC668F7B082847F65D38 /* BugListPopover.swift */; }; 15550246A9E8DAF25E690478 /* BuggerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB7AD270E93EA343975AC6; };
1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F888AA5F655343B19839B4C2 /* URL+Feishu.swift */; }; 58F16D20CC26D8248055CC2C /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEF4F77A6A2E2D041EF581; };
29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */; }; 4EA9BDBA3F98AF4A41712A18 /* Bug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846CA001267896C54A78441A; };
29EE2FE56882F97E14AAF58F /* FeishuService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21AC33F380B7DC34C39B61A6 /* FeishuService.swift */; }; DDB9EB345FEBB95DD9976364 /* BugChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B24E1E1D32F7B774D12DD85; };
3467A50268399ABE9804D4D4 /* BugRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129D6F630904F03D06B5DC4 /* BugRow.swift */; }; A33561284CA44A0045902BCC /* BugPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5359156BD70B765D2BF4E718; };
4309E8C70AE99466545A3E37 /* FeishuAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */; }; E4CBAE27491AF1C52944FA91 /* BugStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646C676356A4125B8E8D5FA5; };
485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */; }; D2855F82E68FADA8BEA43086 /* AppStateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DD178095EFCE7BC121C1C4; };
4EA9BDBA3F98AF4A41712A18 /* Bug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846CA001267896C54A78441A /* Bug.swift */; }; 4309E8C70AE99466545A3E37 /* FeishuAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2C50E47966E8ADB0446A0C; };
56D44DCBAF4FE0E1D9E620FA /* PollerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4628E7B599B69B499E09E9 /* PollerService.swift */; }; CB529549DB1DBB5F02884300 /* FeishuError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A83E76FBB83849E4B6704D3; };
58F16D20CC26D8248055CC2C /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */; }; CAE624453A3F0375D872170A /* FeishuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9FBC39502FA635EF1A6A5C; };
6148104B311B60C79B3B8572 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59CC175DE8E9FE3250757D33 /* NotificationService.swift */; }; 29EE2FE56882F97E14AAF58F /* FeishuService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21AC33F380B7DC34C39B61A6; };
64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */; }; 6148104B311B60C79B3B8572 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59CC175DE8E9FE3250757D33; };
7B0EAA6D83D40A593E36EE31 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3693AA93F823859D15256D /* SettingsView.swift */; }; 56D44DCBAF4FE0E1D9E620FA /* PollerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4628E7B599B69B499E09E9; };
A33561284CA44A0045902BCC /* BugPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5359156BD70B765D2BF4E718 /* BugPriority.swift */; }; B882D310EB2B5D032375CDDF /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3775F77DD36C27160A8C; };
AECED74595B57A400437B9FD /* BugStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F52FB34B285D91A3E4670 /* BugStore.swift */; }; C5D9B1FACE813CD43236BB77 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F99094B0F609809EBDB5F4A; };
B066D78DA6F57A882A61C0B2 /* OAuthSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */; }; 29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C935E6B4D1813AB01186A0; };
B882D310EB2B5D032375CDDF /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3775F77DD36C27160A8C /* TokenManager.swift */; }; 64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF06E1030AA21DA8190169A8; };
C3F7FEB179B079BDDD2AA212 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09B9A4D84F3F604C49171A89 /* Assets.xcassets */; }; EEC5853D00ED565270DC867F /* LocalOAuthServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D11523ACB81DBB9D412995; };
C5D9B1FACE813CD43236BB77 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */; }; 47ED652FA0AC818086F85DF3 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED0A90DFCB8D4968E4E3CA1; };
CAE624453A3F0375D872170A /* FeishuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */; }; 1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F888AA5F655343B19839B4C2; };
CB529549DB1DBB5F02884300 /* FeishuError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A83E76FBB83849E4B6704D3 /* FeishuError.swift */; }; AECED74595B57A400437B9FD /* BugStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F52FB34B285D91A3E4670; };
D2855F82E68FADA8BEA43086 /* AppStateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */; }; 485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FE32B404AB4388A066991; };
DDB9EB345FEBB95DD9976364 /* BugChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B24E1E1D32F7B774D12DD85 /* BugChange.swift */; }; 1E3C1F40E2FF64A041856909 /* BugListPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEABFC668F7B082847F65D38; };
E4CBAE27491AF1C52944FA91 /* BugStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646C676356A4125B8E8D5FA5 /* BugStatus.swift */; }; 3467A50268399ABE9804D4D4 /* BugRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129D6F630904F03D06B5DC4; };
EEC5853D00ED565270DC867F /* LocalOAuthServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */; }; B066D78DA6F57A882A61C0B2 /* OAuthSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0A744D4F5DB88B1496176B; };
F6750BB1FBEE5BC90DD6BDBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CA4381D0CFEC95263A2DE /* AppDelegate.swift */; }; 7B0EAA6D83D40A593E36EE31 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3693AA93F823859D15256D; };
C3F7FEB179B079BDDD2AA212 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09B9A4D84F3F604C49171A89; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
09B9A4D84F3F604C49171A89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 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 = "<group>"; };
DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuggerApp.swift; sourceTree = "<group>"; };
9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = "<group>"; };
846CA001267896C54A78441A /* Bug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bug.swift; sourceTree = "<group>"; };
0B24E1E1D32F7B774D12DD85 /* BugChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugChange.swift; sourceTree = "<group>"; }; 0B24E1E1D32F7B774D12DD85 /* BugChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugChange.swift; sourceTree = "<group>"; };
5359156BD70B765D2BF4E718 /* BugPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugPriority.swift; sourceTree = "<group>"; };
646C676356A4125B8E8D5FA5 /* BugStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStatus.swift; sourceTree = "<group>"; };
F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateService.swift; sourceTree = "<group>"; };
6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuAuthService.swift; sourceTree = "<group>"; };
2A83E76FBB83849E4B6704D3 /* FeishuError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuError.swift; sourceTree = "<group>"; };
FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuModels.swift; sourceTree = "<group>"; };
21AC33F380B7DC34C39B61A6 /* FeishuService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuService.swift; sourceTree = "<group>"; };
59CC175DE8E9FE3250757D33 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
4F4628E7B599B69B499E09E9 /* PollerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollerService.swift; sourceTree = "<group>"; };
0E7E3775F77DD36C27160A8C /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = "<group>"; }; 0E7E3775F77DD36C27160A8C /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = "<group>"; };
0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = "<group>"; }; 0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = "<group>"; };
1129D6F630904F03D06B5DC4 /* BugRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugRow.swift; sourceTree = "<group>"; };
207CA4381D0CFEC95263A2DE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
21AC33F380B7DC34C39B61A6 /* FeishuService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuService.swift; sourceTree = "<group>"; };
2A83E76FBB83849E4B6704D3 /* FeishuError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuError.swift; sourceTree = "<group>"; };
2D3693AA93F823859D15256D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
344F52FB34B285D91A3E4670 /* BugStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStore.swift; sourceTree = "<group>"; };
34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalOAuthServer.swift; sourceTree = "<group>"; };
3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthSetupView.swift; sourceTree = "<group>"; };
4F4628E7B599B69B499E09E9 /* PollerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollerService.swift; sourceTree = "<group>"; };
5359156BD70B765D2BF4E718 /* BugPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugPriority.swift; sourceTree = "<group>"; };
59CC175DE8E9FE3250757D33 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
646C676356A4125B8E8D5FA5 /* BugStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStatus.swift; sourceTree = "<group>"; };
6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuAuthService.swift; sourceTree = "<group>"; };
6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWidgetWindow.swift; sourceTree = "<group>"; };
73AB234B54673C838E33AC7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
846CA001267896C54A78441A /* Bug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bug.swift; sourceTree = "<group>"; };
87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = "<group>"; }; 87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = "<group>"; };
9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = "<group>"; };
CEABFC668F7B082847F65D38 /* BugListPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugListPopover.swift; sourceTree = "<group>"; };
DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuggerApp.swift; sourceTree = "<group>"; };
E9B6FE6E06295095649B24A7 /* Bugger.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = "<group>"; };
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 = "<group>"; }; EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = "<group>"; };
F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateService.swift; sourceTree = "<group>"; }; 34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalOAuthServer.swift; sourceTree = "<group>"; };
0ED0A90DFCB8D4968E4E3CA1 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
F888AA5F655343B19839B4C2 /* URL+Feishu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Feishu.swift"; sourceTree = "<group>"; }; F888AA5F655343B19839B4C2 /* URL+Feishu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Feishu.swift"; sourceTree = "<group>"; };
FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuModels.swift; sourceTree = "<group>"; }; 344F52FB34B285D91A3E4670 /* BugStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStore.swift; sourceTree = "<group>"; };
6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWidgetWindow.swift; sourceTree = "<group>"; };
CEABFC668F7B082847F65D38 /* BugListPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugListPopover.swift; sourceTree = "<group>"; };
1129D6F630904F03D06B5DC4 /* BugRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugRow.swift; sourceTree = "<group>"; };
3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthSetupView.swift; sourceTree = "<group>"; };
2D3693AA93F823859D15256D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
09B9A4D84F3F604C49171A89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
73AB234B54673C838E33AC7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E9B6FE6E06295095649B24A7 /* Bugger.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -80,7 +82,7 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
14ED1DECD6D906F498B9ECB5 = { 14ED1DECD6D906F498B9ECB5 /* Root */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
287BE755981C092238309A9A /* Sources */, 287BE755981C092238309A9A /* Sources */,
@ -103,17 +105,6 @@
path = Sources; path = Sources;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
31DF436612EC1C3CD1194AA1 /* Views */ = {
isa = PBXGroup;
children = (
E7E4AD241EEE594C1EEB9724 /* FloatingWidget */,
EA9ED591BB8A18FEBD1B58A3 /* MenuBar */,
74AD95EF49A7D01C83865FAC /* OAuth */,
B70D2E54AD6B19DD455A8F82 /* Settings */,
);
path = Views;
sourceTree = "<group>";
};
65997CD9BD6FFEF8851BF59E /* Models */ = { 65997CD9BD6FFEF8851BF59E /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -126,44 +117,6 @@
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
6AB9730AA11690EFC8F21473 /* ViewModels */ = {
isa = PBXGroup;
children = (
344F52FB34B285D91A3E4670 /* BugStore.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
74AD95EF49A7D01C83865FAC /* OAuth */ = {
isa = PBXGroup;
children = (
3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */,
);
path = OAuth;
sourceTree = "<group>";
};
8AD6A16612A2DB7B2F899787 /* Resources */ = {
isa = PBXGroup;
children = (
09B9A4D84F3F604C49171A89 /* Assets.xcassets */,
73AB234B54673C838E33AC7F /* Info.plist */,
E9B6FE6E06295095649B24A7 /* Bugger.entitlements */,
);
path = Resources;
sourceTree = "<group>";
};
A6646A2065F53ED50AD2CCA7 /* Utils */ = {
isa = PBXGroup;
children = (
0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */,
87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */,
EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */,
34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */,
F888AA5F655343B19839B4C2 /* URL+Feishu.swift */,
);
path = Utils;
sourceTree = "<group>";
};
A8C46168E4DFCE29B056A4E2 /* Services */ = { A8C46168E4DFCE29B056A4E2 /* Services */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -176,20 +129,36 @@
path = Services; path = Services;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B23D89B2D2DF3745A003AEB1 /* Products */ = { A6646A2065F53ED50AD2CCA7 /* Utils */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EF0182DF85A7CD0319333F24 /* Bugger.app */, 0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */,
87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */,
EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */,
34D11523ACB81DBB9D412995 /* LocalOAuthServer.swift */,
0ED0A90DFCB8D4968E4E3CA1 /* Logger.swift */,
F888AA5F655343B19839B4C2 /* URL+Feishu.swift */,
); );
name = Products; path = Utils;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B70D2E54AD6B19DD455A8F82 /* Settings */ = { 6AB9730AA11690EFC8F21473 /* ViewModels */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2D3693AA93F823859D15256D /* SettingsView.swift */, 344F52FB34B285D91A3E4670 /* BugStore.swift */,
); );
path = Settings; path = ViewModels;
sourceTree = "<group>";
};
31DF436612EC1C3CD1194AA1 /* Views */ = {
isa = PBXGroup;
children = (
E7E4AD241EEE594C1EEB9724 /* FloatingWidget */,
EA9ED591BB8A18FEBD1B58A3 /* MenuBar */,
74AD95EF49A7D01C83865FAC /* OAuth */,
B70D2E54AD6B19DD455A8F82 /* Settings */,
);
path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E0E23D3899D89889546FB428 /* Feishu */ = { E0E23D3899D89889546FB428 /* Feishu */ = {
@ -220,6 +189,40 @@
path = MenuBar; path = MenuBar;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
74AD95EF49A7D01C83865FAC /* OAuth */ = {
isa = PBXGroup;
children = (
3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */,
);
path = OAuth;
sourceTree = "<group>";
};
B70D2E54AD6B19DD455A8F82 /* Settings */ = {
isa = PBXGroup;
children = (
2D3693AA93F823859D15256D /* SettingsView.swift */,
);
path = Settings;
sourceTree = "<group>";
};
8AD6A16612A2DB7B2F899787 /* Resources */ = {
isa = PBXGroup;
children = (
09B9A4D84F3F604C49171A89 /* Assets.xcassets */,
73AB234B54673C838E33AC7F /* Info.plist */,
E9B6FE6E06295095649B24A7 /* Bugger.entitlements */,
);
path = Resources;
sourceTree = "<group>";
};
B23D89B2D2DF3745A003AEB1 /* Products */ = {
isa = PBXGroup;
children = (
EF0182DF85A7CD0319333F24 /* Bugger.app */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -308,6 +311,7 @@
29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */, 29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */,
64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */, 64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */,
EEC5853D00ED565270DC867F /* LocalOAuthServer.swift in Sources */, EEC5853D00ED565270DC867F /* LocalOAuthServer.swift in Sources */,
47ED652FA0AC818086F85DF3 /* Logger.swift in Sources */,
1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */, 1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */,
AECED74595B57A400437B9FD /* BugStore.swift in Sources */, AECED74595B57A400437B9FD /* BugStore.swift in Sources */,
485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */, 485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */,
@ -350,15 +354,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
FEISHU_APP_ID = cli_aacb82a1c0b8dcc0; FEISHU_APP_ID = "$(FEISHU_APP_ID)";
FEISHU_APP_SECRET = jUkU9ygRuQ0KaZFV2BSPfe33iXCahzNM; FEISHU_APP_SECRET = "$(FEISHU_APP_SECRET)";
FEISHU_BASE_DOMAIN = mi5p6bgsnf8.feishu.cn; FEISHU_BASE_DOMAIN = "xorbitlab.feishu.cn";
GENERATE_INFOPLIST_FILE = NO; GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Resources/Info.plist; INFOPLIST_FILE = Resources/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger; PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger;
@ -374,15 +375,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
FEISHU_APP_ID = cli_aacb82a1c0b8dcc0; FEISHU_APP_ID = "$(FEISHU_APP_ID)";
FEISHU_APP_SECRET = jUkU9ygRuQ0KaZFV2BSPfe33iXCahzNM; FEISHU_APP_SECRET = "$(FEISHU_APP_SECRET)";
FEISHU_BASE_DOMAIN = mi5p6bgsnf8.feishu.cn; FEISHU_BASE_DOMAIN = "xorbitlab.feishu.cn";
GENERATE_INFOPLIST_FILE = NO; GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Resources/Info.plist; INFOPLIST_FILE = Resources/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger; PRODUCT_BUNDLE_IDENTIFIER = com.xorbitlab.bugger;
@ -414,6 +412,7 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
}; };
rootObject = 46F86FAA6BBF9AC94A7E4595 /* Project object */; rootObject = 46F86FAA6BBF9AC94A7E4595 /* Project object */;
} }

View File

@ -22,13 +22,6 @@
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
@ -50,18 +43,4 @@
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
</LaunchAction> </LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme> </Scheme>

View File

@ -42,6 +42,7 @@ final class FeishuAuthService {
} }
func exchangeCode(_ code: String) async throws -> OAuthTokenData { func exchangeCode(_ code: String) async throws -> OAuthTokenData {
BuggerLog.info("Exchanging auth code (len=\(code.count)) for user token...")
let tenantToken = try await tenantAccessToken() let tenantToken = try await tenantAccessToken()
let url = URL(string: "\(baseURL)/authen/v1/oidc/access_token")! let url = URL(string: "\(baseURL)/authen/v1/oidc/access_token")!
var request = URLRequest(url: url) var request = URLRequest(url: url)
@ -52,8 +53,9 @@ final class FeishuAuthService {
"grant_type": "authorization_code", "grant_type": "authorization_code",
"code": code, "code": code,
] as [String: String]) ] as [String: String])
// Token endpoints return data at top level, not wrapped in {code, msg, data} let tokenData: OAuthTokenData = try await send(request)
return try await send(request) BuggerLog.info("Got token: access=\(tokenData.accessToken.prefix(8))..., refresh=\(tokenData.refreshToken?.prefix(8) ?? "nil")..., expires=\(tokenData.expiresIn)")
return tokenData
} }
func refreshAccessToken(_ refreshToken: String) async throws -> OAuthTokenData { func refreshAccessToken(_ refreshToken: String) async throws -> OAuthTokenData {
@ -68,7 +70,8 @@ final class FeishuAuthService {
"refresh_token": refreshToken, "refresh_token": refreshToken,
] as [String: String]) ] as [String: String])
// Token endpoints return data at top level, not wrapped in {code, msg, data} // Token endpoints return data at top level, not wrapped in {code, msg, data}
return try await send(request) let tokenData: OAuthTokenData = try await send(request)
return tokenData
} }
func fetchCurrentUserName(accessToken: String) async throws -> String { func fetchCurrentUserName(accessToken: String) async throws -> String {
@ -114,17 +117,35 @@ final class FeishuAuthService {
} }
private func send<T: Decodable>(_ request: URLRequest) async throws -> T { private func send<T: Decodable>(_ request: URLRequest) async throws -> T {
BuggerLog.debug("\(request.httpMethod ?? "?") \(request.url?.absoluteString ?? "?")")
if let body = request.httpBody, let bodyStr = String(data: body, encoding: .utf8) {
BuggerLog.debug(" body: \(bodyStr)")
}
if let auth = request.value(forHTTPHeaderField: "Authorization") {
BuggerLog.debug(" auth: \(auth.prefix(20))...")
}
let (data, response) = try await session.data(for: request) let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else { guard let httpResponse = response as? HTTPURLResponse else {
BuggerLog.error("← not an HTTP response")
throw FeishuError.networkError(nil) throw FeishuError.networkError(nil)
} }
let bodyStr = String(data: data, encoding: .utf8) ?? "<binary \(data.count)B>"
BuggerLog.debug("\(httpResponse.statusCode) \(bodyStr.prefix(500))")
if httpResponse.statusCode == 401 { if httpResponse.statusCode == 401 {
BuggerLog.error("← 401 Unauthorized")
throw FeishuError.unauthorized throw FeishuError.unauthorized
} }
do { do {
return try decoder.decode(T.self, from: data) let decoded = try decoder.decode(T.self, from: data)
BuggerLog.debug(" decoded as \(T.self)")
return decoded
} catch { } catch {
BuggerLog.error(" decode failed: \(error.localizedDescription)")
BuggerLog.error(" raw body: \(bodyStr.prefix(1000))")
throw FeishuError.decodingError(error) throw FeishuError.decodingError(error)
} }
} }

View File

@ -93,16 +93,20 @@ final class TokenManager {
func handleCallback(code: String) async throws { func handleCallback(code: String) async throws {
guard !code.isEmpty else { guard !code.isEmpty else {
BuggerLog.error("handleCallback: empty code")
throw TokenError.invalidCallback throw TokenError.invalidCallback
} }
BuggerLog.info("handleCallback: exchanging code...")
state = .authenticating state = .authenticating
let response = try await authService.exchangeCode(code) let response = try await authService.exchangeCode(code)
BuggerLog.info("handleCallback: got tokens, storing...")
storeTokens( storeTokens(
access: response.accessToken, access: response.accessToken,
refresh: response.refreshToken ?? "", refresh: response.refreshToken ?? "",
expiresIn: response.expiresIn expiresIn: response.expiresIn
) )
state = .authenticated state = .authenticated
BuggerLog.info("handleCallback: done, authenticated ✓")
} }
func resolveAssigneeName(config: AppConfig, accessToken: String) async throws -> String { func resolveAssigneeName(config: AppConfig, accessToken: String) async throws -> String {

View File

@ -20,13 +20,15 @@ actor LocalOAuthServer {
/// Start listening and return the authorization code when the browser redirects. /// Start listening and return the authorization code when the browser redirects.
func receiveCode() async throws -> String { func receiveCode() async throws -> String {
try await withCheckedThrowingContinuation { continuation in BuggerLog.info("OAuth server starting on \(self.redirectURI)...")
return try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation self.continuation = continuation
do { do {
let params = NWParameters.tcp let params = NWParameters.tcp
let listener = try NWListener(using: params, on: NWEndpoint.Port(rawValue: port)!) let listener = try NWListener(using: params, on: NWEndpoint.Port(rawValue: port)!)
self.listener = listener self.listener = listener
BuggerLog.info("OAuth server listening ✓")
listener.newConnectionHandler = { [weak self] connection in listener.newConnectionHandler = { [weak self] connection in
connection.start(queue: .global()) connection.start(queue: .global())
@ -60,6 +62,11 @@ actor LocalOAuthServer {
// MARK: - Private (actor-isolated) // MARK: - Private (actor-isolated)
private func didReceive(code: String?, from connection: NWConnection) { private func didReceive(code: String?, from connection: NWConnection) {
if let code {
BuggerLog.info("OAuth server received code (len=\(code.count))")
} else {
BuggerLog.error("OAuth server received request but no code found")
}
guard let code else { guard let code else {
Self.sendResponse(to: connection, status: 400, body: errorPage) Self.sendResponse(to: connection, status: 400, body: errorPage)
return return

View File

@ -0,0 +1,20 @@
import Foundation
import OSLog
/// Shared logger for Bugger. View output in Xcode console or Console.app
/// (filter by subsystem: com.xorbitlab.bugger).
enum BuggerLog {
private static let logger = Logger(subsystem: "com.xorbitlab.bugger", category: "general")
static func info(_ message: String) {
logger.info("\(message, privacy: .public)")
}
static func error(_ message: String) {
logger.error("\(message, privacy: .public)")
}
static func debug(_ message: String) {
logger.debug("\(message, privacy: .public)")
}
}