Commit Graph

14 Commits

Author SHA1 Message Date
tigerenwork 0f8565f65d fix: OAuth token endpoint wraps in {code,message,data} — not flat
The /authen/v1/oidc/access_token response uses the standard Feishu envelope
with 'message' (not 'msg') field. Updated FeishuAPIResponse to handle both
'msg' and 'message' via custom init(from:). exchangeCode and
refreshAccessToken now decode via FeishuAPIResponse<OAuthTokenData>.

Tenant token endpoint (/auth/v3/tenant_access_token/internal) returns flat
response — TenantAccessTokenData decoded directly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 00:33:53 +08:00
tigerenwork ae918a379c fix: remove redundant CodingKeys conflicting with .convertFromSnakeCase
JSONDecoder.keyDecodingStrategy = .convertFromSnakeCase converts JSON keys
like 'tenant_access_token' to Swift names like 'tenantAccessToken'. Having
explicit CodingKeys that map to the ORIGINAL snake_case keys (e.g.,
case tenantAccessToken = "tenant_access_token") causes a collision:
the decoder first converts the JSON key, then looks for a CodingKey
matching the original name — but the converted name doesn't match.

Removed redundant CodingKeys from OAuthTokenData, TenantAccessTokenData,
UserInfoData, and RecordItem. .convertFromSnakeCase handles all these
mappings automatically.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 00:30:51 +08:00
tigerenwork f1ef89ef55 fix: tenant_access_token response is flat, not wrapped in data key
The /auth/v3/tenant_access_token/internal endpoint, like the OAuth token
endpoints, returns code, msg, tenant_access_token, and expire at the top
level of the JSON response — NOT nested under a 'data' key.

FeishuAPIResponse<TenantAccessTokenData> decoded successfully but with
data=nil (extra fields silently ignored by JSONDecoder). The guard then
threw FeishuError.apiError(code: 0, message: 'ok') — appearing to succeed
but always failing. This blocked the subsequent OAuth token exchange call.

- Added code/msg fields to TenantAccessTokenData
- tenantAccessToken() now decodes TenantAccessTokenData directly and
  checks data.code != 0 instead of going through FeishuAPIResponse

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 00:28:39 +08:00
tigerenwork ba56b2385e debug: switch to print() for critical path, manual JSON body
print() goes to stdout which is always visible in Xcode console regardless
of log level filtering. Also replaced JSONEncoder with manual JSON string
construction to rule out any Encodable issues with [String: String].

All critical send() calls now use print() instead of os.Logger.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 00:26:15 +08:00
tigerenwork f61f8332b8 fix: add step-by-step logs in exchangeCode to pinpoint crash location
The log stops after tenant_token fetch but before the OAuth token exchange
HTTP request. Added per-line logging to find exactly where it dies:
URL construction, body encoding, or the send() call.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 00:23:39 +08:00
tigerenwork 12e22da9b3 fix: change HTTP request/response logging from .debug to .info level
Debug-level os.Logger messages are filtered out in Console.app by default,
hiding the actual OAuth token exchange request/response. Changed all
FeishuAuthService.send() log calls to .info level so they appear without
changing Console filter settings.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 00:23:07 +08:00
tigerenwork e275e49795 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>
2026-06-29 00:18:25 +08:00
tigerenwork cf9de54792 fix: decode OAuth token response directly, not via FeishuAPIResponse wrapper
The /authen/v1/oidc/access_token and refresh_access_token endpoints return
token fields (access_token, refresh_token, expires_in) at the top level
of the JSON response, not nested under a 'data' key like standard Feishu
API responses. This caused 'Feishu API error (0): ok' — code 0 with no
wrapped data.

Changed exchangeCode and refreshAccessToken to decode OAuthTokenData
directly from the response. Removed unused decodeAPIResponse helper.

Also removed redirect_uri from token exchange body (not required by
Feishu's token endpoint).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 00:13:48 +08:00
tigerenwork 74baa22d27 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 <noreply@anthropic.com>
2026-06-29 00:09:28 +08:00
tigerenwork 9f8fb52726 feat: switch OAuth redirect to localhost loopback (RFC 8252)
Feishu rejected the 'bugger://' custom URL scheme redirect with 参数不合法.
Switched to the standard desktop OAuth flow: a one-shot local HTTP server
on 127.0.0.1:18923 that receives the authorization code from the browser.

Changes:
- Added LocalOAuthServer (Network.framework, one-shot HTTP listener)
- FeishuAuthService now uses dynamic http://127.0.0.1:PORT/callback redirect
- Removed CFBundleURLTypes from Info.plist (no custom scheme needed)
- Removed NSAppleEventManager handler from AppDelegate
- TokenManager.handleCallback changed from URL to code string
- OAuthSetupView starts server before opening browser
- Added offline_access scope for refresh token support
- generate_xcode_project.py: removed dev cert requirements,
  auto-quotes paths with special chars

Builds successfully with xcodebuild.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 23:48:49 +08:00
tigerenwork bda106dcad fix: pbxproj now parses with Xcode 26
Root cause: filenames containing '+' (Color+Extensions.swift,
DateFormatter+Extensions.swift, URL+Feishu.swift) had their path values
unquoted in PBXFileReference entries. In NeXTSTEP plist format, '+'
is an operator character — the parser interpreted 'path = Utils/Color'
followed by '+Extensions.swift' as an addition expression instead of
a file path, causing a parse error.

Fixes:
- Added quote_path() to escape paths with special characters
- Regenerated pbxproj with all 25 source files, subdirectory groups
- Fixed workspace contents.xcworkspacedata (was empty)
- Cleaned up generator script (removed dead code, added scheme gen)
- Proper SWIFT_OPTIMIZATION_LEVEL quoting

Verified: xcodebuild -list parses the project successfully.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 21:33:02 +08:00
tigerenwork c57ab859eb fix: rewrite pbxproj to fix duplicate groups and broken hierarchy
- Removed duplicate PBXGroup definitions (Models, Services, Views, etc.)
- Removed orphan groups (.claude, docs, scripts, etc.)
- Added proper Products group for productRefGroup reference
- Fixed main group to reference Sources, Resources, Products
- Clean single-definition group hierarchy

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 20:59:51 +08:00
tigerenwork d9d8ce3e46 feat: full macOS menu bar app implementation
SwiftUI macOS menu bar app for tracking Feishu Bitable bugs:
- Menu bar icon with active bug count badge
- Popover with bug list sorted by priority, click to open in Feishu
- Floating always-on-top widget (NSPanel, optional)
- OAuth 2.0 flow with Feishu Open API (user_access_token + refresh)
- PollerService with configurable interval (default 5 min)
- Diff engine detecting new bugs, status/priority/assignee changes
- Native macOS notifications (UserNotifications framework)
- Keychain-backed token storage
- Settings window with field mapping, polling interval, widget toggle
- Support for custom Bitable column name mappings
- Zero third-party dependencies

17 source files across Models/Services/Views/ViewModels/Utils layers.
Xcode project generated via scripts/generate_xcode_project.py.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 20:56:44 +08:00
tigerenwork a9c90f64b9 first commit: project README, high-level design and detailed implementation docs
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-28 20:54:51 +08:00