浏览代码

Remove unused Google OAuth and calendar integration code.

The app already schedules and lists meetings via the Zoom API; this deletes dead Google sign-in paths and credentials without changing Zoom auth or meeting flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
huzaifahayat12 3 周之前
父节点
当前提交
7627bdbcc4
共有 3 个文件被更改,包括 38 次插入290 次删除
  1. 1 1
      appconnect/US/description.txt
  2. 0 14
      zoom_app/Utilities/BundledOAuthCredentials.swift
  3. 37 275
      zoom_app/ViewController.swift

+ 1 - 1
appconnect/US/description.txt

@@ -14,7 +14,7 @@ When you choose to record, meeting audio is saved locally on your Mac in the app
 • Generate structured meeting notes (summary, decisions, action items) when you request them
 • Review Zoom AI Companion meeting summaries from your account when available through Zoom’s API
 
-Optional Google sign-in can support scheduling workflows tied to your calendar.
+Sign in with Zoom to sync scheduled meetings, reminders, and AI Companion notes.
 
 PREMIUM
 Unlock the full experience with Premium (weekly, monthly, or yearly subscription, or a one-time lifetime option). Premium includes access to Meetings, Scheduler, meeting reminders, and desktop widgets. Subscriptions renew automatically unless cancelled at least 24 hours before the end of the current period in your Apple ID account settings. A free trial may apply to eligible yearly plans where offered.

+ 0 - 14
zoom_app/Utilities/BundledOAuthCredentials.swift

@@ -14,22 +14,8 @@ enum BundledOAuthCredentials {
         0x29, 0x3F, 0x0B, 0x12, 0x05, 0x18, 0x1D, 0x5E, 0x5A, 0x02, 0x02, 0x0E, 0x5B, 0x4A, 0x2C, 0x24,
         0x2A, 0x0A, 0x70, 0x21, 0x07, 0x34, 0x11, 0x3A, 0x20, 0x08, 0x2D, 0x25, 0x7B, 0x34, 0x2B, 0x03
     ]
-    private static let googleClientIdBytes: [UInt8] = [
-        0x72, 0x5F, 0x58, 0x48, 0x50, 0x57, 0x58, 0x58, 0x5F, 0x75, 0x7D, 0x76, 0x57, 0x5F, 0x33, 0x58,
-        0x0B, 0x01, 0x2A, 0x0C, 0x0C, 0x02, 0x11, 0x1E, 0x03, 0x5F, 0x1C, 0x35, 0x79, 0x21, 0x07, 0x1C,
-        0x63, 0x03, 0x00, 0x02, 0x36, 0x59, 0x5D, 0x46, 0x0D, 0x03, 0x04, 0x5F, 0x0A, 0x33, 0x67, 0x27,
-        0x1F, 0x02, 0x29, 0x41, 0x08, 0x02, 0x2C, 0x08, 0x01, 0x15, 0x14, 0x1D, 0x0C, 0x1D, 0x0D, 0x2E,
-        0x27, 0x32, 0x0A, 0x1C, 0x2E, 0x41, 0x0C, 0x02, 0x2E
-    ]
-    private static let googleClientSecretBytes: [UInt8] = [
-        0x04, 0x20, 0x2E, 0x23, 0x31, 0x36, 0x44, 0x22, 0x36, 0x28, 0x7C, 0x33, 0x37, 0x5F, 0x22, 0x21,
-        0x36, 0x37, 0x75, 0x1E, 0x37, 0x02, 0x2D, 0x26, 0x5A, 0x2D, 0x34, 0x31, 0x23, 0x30, 0x5A, 0x05,
-        0x2C, 0x22, 0x16
-    ]
 
     static var zoomClientId: String { ObfuscatedCredential.reveal(zoomClientIdBytes) }
     static var zoomPublicClientId: String { ObfuscatedCredential.reveal(zoomPublicClientIdBytes) }
     static var zoomClientSecret: String { ObfuscatedCredential.reveal(zoomClientSecretBytes) }
-    static var googleClientId: String { ObfuscatedCredential.reveal(googleClientIdBytes) }
-    static var googleClientSecret: String { ObfuscatedCredential.reveal(googleClientSecretBytes) }
 }

+ 37 - 275
zoom_app/ViewController.swift

@@ -113,7 +113,6 @@ class ViewController: NSViewController {
         case settings = "Settings"
     }
 
-    private let googleOAuth = GoogleOAuthService.shared
     private let zoomOAuth = ZoomOAuthService.shared
     private let loginStateKey = "zoom_app.isLoggedIn"
     private let darkModeDefaultsKey = "settings.darkModeEnabled"
@@ -344,7 +343,6 @@ class ViewController: NSViewController {
 
     private let rootContainer = NSView()
     private var homeView: NSView?
-    private weak var googleButton: NSButton?
     private weak var timeLabel: NSTextField?
     private weak var dateLabel: NSTextField?
     private weak var meetingsDayHeaderLabel: NSTextField?
@@ -486,7 +484,7 @@ class ViewController: NSViewController {
     private weak var settingsNotificationsBlock: NSStackView?
     private weak var settingsPagePrimaryLanguagePopup: NSPopUpButton?
     private weak var settingsPageSecondaryLanguagePopup: NSPopUpButton?
-    private weak var settingsGoogleActionButton: NSButton?
+    private weak var settingsZoomActionButton: NSButton?
     private weak var topBarPremiumButton: NSButton?
     private weak var topBarAuthButton: NSButton?
     private var topBarAuthButtonWidthConstraint: NSLayoutConstraint?
@@ -953,7 +951,7 @@ class ViewController: NSViewController {
         }
     }
 
-    private func showHomeView(profile: GoogleUserProfile?, refreshMeetings: Bool = true) {
+    private func showHomeView(profile: UserProfile?, refreshMeetings: Bool = true) {
         clearMeetingsScrollObserver()
         clearMeetingsPageScrollObserver()
         removeSearchFieldObserver()
@@ -1029,38 +1027,12 @@ class ViewController: NSViewController {
         ])
     }
 
-    @objc private func googleLoginTapped() {
-        guard isSigningIn == false else { return }
-        isSigningIn = true
-        googleButton?.title = "..."
-        googleButton?.isEnabled = false
-
-        Task {
-            do {
-                let token = try await googleOAuth.validAccessToken(presentingWindow: view.window)
-                let profile = try? await googleOAuth.fetchUserProfile(accessToken: token)
-                await MainActor.run {
-                    self.googleButton?.title = "G"
-                    self.resetLoginSigningInState()
-                    self.completeLoginAndRestart(profile: profile)
-                }
-            } catch {
-                await MainActor.run {
-                    self.resetLoginSigningInState()
-                    if case GoogleOAuthError.authenticationCancelled = error { return }
-                    self.showSimpleError("Google login failed", error: error)
-                }
-            }
-        }
-    }
-
     /// Primary Zoom sign-in: browser OAuth, token refresh, then home with scheduled meetings.
     @objc private func zoomPrimarySignInTapped() {
         guard isSigningIn == false else { return }
         isSigningIn = true
         topBarAuthButton?.title = "Signing in..."
         topBarAuthButton?.isEnabled = false
-        googleButton?.isEnabled = false
 
         Task {
             do {
@@ -1072,7 +1044,7 @@ class ViewController: NSViewController {
 
                 let zoomToken = try await zoomOAuth.validAccessToken(presentingWindow: view.window)
                 let zoomUser = try? await fetchZoomUserProfile(accessToken: zoomToken)
-                let profile = zoomUser.map { GoogleUserProfile(name: $0.displayName, email: $0.email, picture: $0.pictureURL) }
+                let profile = zoomUser.map { UserProfile(name: $0.displayName, email: $0.email, picture: $0.pictureURL) }
                 let profileTimeZone = parseZoomTimeZone(zoomUser?.timezone)
 
                 await MainActor.run {
@@ -2930,7 +2902,6 @@ class ViewController: NSViewController {
         isLoadingMeetings = false
         meetingsRateLimitedUntil = nil
         clearMeetingsScrollObserver()
-        googleOAuth.clearSavedTokens()
         zoomOAuth.clearSavedTokens()
         zoomSignedInDisplayName = nil
         embeddedZoomPendingWebAuth = nil
@@ -3456,13 +3427,11 @@ class ViewController: NSViewController {
     @MainActor
     private func resetLoginSigningInState() {
         isSigningIn = false
-        googleButton?.title = "G"
         updateTopBarAuthButton()
-        googleButton?.isEnabled = true
     }
 
     @MainActor
-    private func completeLoginAndRestart(profile: GoogleUserProfile?) {
+    private func completeLoginAndRestart(profile: UserProfile?) {
         persistLoggedInState(true)
         let trimmedName = profile?.name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
         zoomSignedInDisplayName = trimmedName.isEmpty ? nil : trimmedName
@@ -3538,8 +3507,8 @@ class ViewController: NSViewController {
     private func applyAuthenticationUIState(triggerInitialRefresh: Bool) {
         let loggedIn = isUserLoggedIn()
         updateTopBarAuthButton()
-        settingsGoogleActionButton?.title = loggedIn ? "Sign Out" : "Sign in with Zoom"
-        settingsGoogleActionButton?.isEnabled = true
+        settingsZoomActionButton?.title = loggedIn ? "Sign Out" : "Sign in with Zoom"
+        settingsZoomActionButton?.isEnabled = true
         updatePremiumLockedSections()
 
         meetingsRefreshTimer?.invalidate()
@@ -4354,7 +4323,7 @@ class ViewController: NSViewController {
 
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
-            throw GoogleOAuthError.tokenExchangeFailed(String(data: data, encoding: .utf8) ?? "Failed to load Zoom profile")
+            throw ZoomOAuthError.tokenExchangeFailed(String(data: data, encoding: .utf8) ?? "Failed to load Zoom profile")
         }
         return try JSONDecoder().decode(ZoomUserMeResponse.self, from: data)
     }
@@ -4368,7 +4337,7 @@ class ViewController: NSViewController {
         var components = URLComponents(string: "https://api.zoom.us/v2/users/me/token")!
         components.queryItems = [URLQueryItem(name: "type", value: "zak")]
         guard let url = components.url else {
-            throw GoogleOAuthError.tokenExchangeFailed("Invalid ZAK request URL")
+            throw ZoomOAuthError.tokenExchangeFailed("Invalid ZAK request URL")
         }
         var request = URLRequest(url: url)
         request.httpMethod = "GET"
@@ -4376,7 +4345,7 @@ class ViewController: NSViewController {
 
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
-            throw GoogleOAuthError.tokenExchangeFailed("Invalid response from Zoom (ZAK)")
+            throw ZoomOAuthError.tokenExchangeFailed("Invalid response from Zoom (ZAK)")
         }
         if http.statusCode == 429 {
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4388,12 +4357,12 @@ class ViewController: NSViewController {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 throw ZoomOAuthError.missingRequiredScope(raw)
             }
-            throw GoogleOAuthError.tokenExchangeFailed(raw)
+            throw ZoomOAuthError.tokenExchangeFailed(raw)
         }
         let decoded = try JSONDecoder().decode(ZoomZAKTokenResponse.self, from: data)
         let trimmed = decoded.token.trimmingCharacters(in: .whitespacesAndNewlines)
         guard trimmed.isEmpty == false else {
-            throw GoogleOAuthError.tokenExchangeFailed("Empty ZAK from Zoom")
+            throw ZoomOAuthError.tokenExchangeFailed("Empty ZAK from Zoom")
         }
         return trimmed
     }
@@ -4436,7 +4405,7 @@ class ViewController: NSViewController {
 
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
-            throw GoogleOAuthError.tokenExchangeFailed("Invalid response from Zoom")
+            throw ZoomOAuthError.tokenExchangeFailed("Invalid response from Zoom")
         }
         if http.statusCode == 429 {
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4448,7 +4417,7 @@ class ViewController: NSViewController {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 throw ZoomOAuthError.missingRequiredScope(raw)
             }
-            throw GoogleOAuthError.tokenExchangeFailed(raw)
+            throw ZoomOAuthError.tokenExchangeFailed(raw)
         }
         return try JSONDecoder().decode(ZoomCreateMeetingAPIResult.self, from: data)
     }
@@ -4464,7 +4433,7 @@ class ViewController: NSViewController {
 
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
-            throw GoogleOAuthError.tokenExchangeFailed("Invalid response from Zoom")
+            throw ZoomOAuthError.tokenExchangeFailed("Invalid response from Zoom")
         }
         if http.statusCode == 429 {
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4476,7 +4445,7 @@ class ViewController: NSViewController {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 throw ZoomOAuthError.missingRequiredScope(raw)
             }
-            throw GoogleOAuthError.tokenExchangeFailed(raw)
+            throw ZoomOAuthError.tokenExchangeFailed(raw)
         }
         return try JSONDecoder().decode(ZoomCreateMeetingAPIResult.self, from: data)
     }
@@ -4580,7 +4549,7 @@ class ViewController: NSViewController {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                     throw ZoomOAuthError.missingRequiredScope(raw)
                 }
-                throw GoogleOAuthError.tokenExchangeFailed(raw)
+                throw ZoomOAuthError.tokenExchangeFailed(raw)
             }
 
             let decoded = try JSONDecoder().decode(ZoomMeetingsPage.self, from: data)
@@ -4746,7 +4715,7 @@ class ViewController: NSViewController {
 
             let (data, response) = try await URLSession.shared.data(for: request)
             guard let http = response as? HTTPURLResponse else {
-                throw GoogleOAuthError.tokenExchangeFailed("Invalid response from Zoom (meeting_summaries)")
+                throw ZoomOAuthError.tokenExchangeFailed("Invalid response from Zoom (meeting_summaries)")
             }
             if http.statusCode == 429 {
                 let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4761,7 +4730,7 @@ class ViewController: NSViewController {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                     throw ZoomOAuthError.missingRequiredScope(raw)
                 }
-                throw GoogleOAuthError.tokenExchangeFailed(raw)
+                throw ZoomOAuthError.tokenExchangeFailed(raw)
             }
 
             let decoded = try JSONDecoder().decode(Page.self, from: data)
@@ -4838,7 +4807,7 @@ class ViewController: NSViewController {
 
             let (data, response) = try await URLSession.shared.data(for: request)
             guard let http = response as? HTTPURLResponse else {
-                throw GoogleOAuthError.tokenExchangeFailed("Invalid response from Zoom (past meetings)")
+                throw ZoomOAuthError.tokenExchangeFailed("Invalid response from Zoom (past meetings)")
             }
             if http.statusCode == 429 {
                 let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4850,7 +4819,7 @@ class ViewController: NSViewController {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                     throw ZoomOAuthError.missingRequiredScope(raw)
                 }
-                throw GoogleOAuthError.tokenExchangeFailed(raw)
+                throw ZoomOAuthError.tokenExchangeFailed(raw)
             }
 
             let decoded = try JSONDecoder().decode(PastMeetingsPage.self, from: data)
@@ -4900,7 +4869,7 @@ class ViewController: NSViewController {
         }
         let urlString = "https://api.zoom.us/v2/meetings/\(pathSegment)/meeting_summary"
         guard let url = URL(string: urlString) else {
-            throw GoogleOAuthError.tokenExchangeFailed("Invalid Zoom meeting summary URL")
+            throw ZoomOAuthError.tokenExchangeFailed("Invalid Zoom meeting summary URL")
         }
         var request = URLRequest(url: url)
         request.httpMethod = "GET"
@@ -4908,7 +4877,7 @@ class ViewController: NSViewController {
 
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
-            throw GoogleOAuthError.tokenExchangeFailed("Invalid response from Zoom (meeting_summary)")
+            throw ZoomOAuthError.tokenExchangeFailed("Invalid response from Zoom (meeting_summary)")
         }
         if http.statusCode == 404 || http.statusCode == 204 { return nil }
         if http.statusCode == 429 {
@@ -4921,7 +4890,7 @@ class ViewController: NSViewController {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 throw ZoomOAuthError.missingRequiredScope(raw)
             }
-            throw GoogleOAuthError.tokenExchangeFailed(raw)
+            throw ZoomOAuthError.tokenExchangeFailed(raw)
         }
 
         let iso = ISO8601DateFormatter()
@@ -4981,10 +4950,10 @@ class ViewController: NSViewController {
 
         let accountTitle = textLabel("Account", font: .systemFont(ofSize: 16, weight: .semibold), color: primaryText)
         stack.addArrangedSubview(accountTitle)
-        let googleAccountRow = makeSettingsGoogleAccountRow()
-        stack.addArrangedSubview(googleAccountRow)
-        googleAccountRow.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
-        stack.setCustomSpacing(24, after: googleAccountRow)
+        let zoomAccountRow = makeSettingsZoomAccountRow()
+        stack.addArrangedSubview(zoomAccountRow)
+        zoomAccountRow.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
+        stack.setCustomSpacing(24, after: zoomAccountRow)
 
         let appTitle = textLabel("App", font: .systemFont(ofSize: 16, weight: .semibold), color: primaryText)
         stack.addArrangedSubview(appTitle)
@@ -5312,25 +5281,25 @@ class ViewController: NSViewController {
         return row
     }
 
-    private func makeSettingsGoogleAccountRow() -> NSView {
+    private func makeSettingsZoomAccountRow() -> NSView {
         let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
         row.translatesAutoresizingMaskIntoConstraints = false
         styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
 
         let signedIn = isUserLoggedIn()
         let titleText = signedIn ? "Zoom account connected" : "Zoom account not connected"
-        let subtitleText = signedIn ? "Signed in" : "Sign in to sync your meetings and calendar."
+        let subtitleText = signedIn ? "Signed in" : "Sign in to sync your meetings."
 
         let title = textLabel(titleText, font: NSFont.systemFont(ofSize: 15, weight: .semibold), color: primaryText)
         let subtitle = textLabel(subtitleText, font: NSFont.systemFont(ofSize: 13, weight: .regular), color: secondaryText)
         subtitle.maximumNumberOfLines = 2
         subtitle.lineBreakMode = .byTruncatingTail
 
-        let actionButton = NSButton(title: signedIn ? "Sign Out" : "Sign in with Google", target: self, action: #selector(settingsGoogleActionButtonClicked(_:)))
+        let actionButton = NSButton(title: signedIn ? "Sign Out" : "Sign in with Zoom", target: self, action: #selector(settingsZoomActionButtonClicked(_:)))
         actionButton.translatesAutoresizingMaskIntoConstraints = false
         actionButton.bezelStyle = .rounded
         actionButton.controlSize = .regular
-        settingsGoogleActionButton = actionButton
+        settingsZoomActionButton = actionButton
 
         row.addSubview(title)
         row.addSubview(subtitle)
@@ -5393,7 +5362,7 @@ class ViewController: NSViewController {
         }
     }
 
-    @objc private func settingsGoogleActionButtonClicked(_ sender: NSButton) {
+    @objc private func settingsZoomActionButtonClicked(_ sender: NSButton) {
         if isUserLoggedIn() {
             logoutTapped()
             return
@@ -6822,7 +6791,7 @@ class ViewController: NSViewController {
 
     // MARK: - Home UI
 
-    private func makeHomeView(profile: GoogleUserProfile?) -> NSView {
+    private func makeHomeView(profile: UserProfile?) -> NSView {
         let root = NSView()
         let shell = NSView()
         shell.wantsLayer = true
@@ -10727,15 +10696,7 @@ private final class MeetingCardView: NSView {
     }
 }
 
-struct GoogleOAuthTokens: Codable, Equatable {
-    var accessToken: String
-    var refreshToken: String?
-    var expiresAt: Date
-    var scope: String?
-    var tokenType: String?
-}
-
-struct GoogleUserProfile: Codable, Equatable {
+struct UserProfile: Codable, Equatable {
     var name: String?
     var email: String?
     var picture: String?
@@ -11027,10 +10988,6 @@ final class ZoomOAuthService: NSObject {
             }
             await MainActor.run { self.onInteractiveSignInCompleted?() }
             return tokens
-        } catch GoogleOAuthError.authenticationTimedOut {
-            throw ZoomOAuthError.authenticationTimedOut
-        } catch GoogleOAuthError.authenticationCancelled {
-            throw ZoomOAuthError.authenticationCancelled
         } catch {
             throw error
         }
@@ -11216,192 +11173,12 @@ final class ZoomOAuthService: NSObject {
     }
 }
 
-enum GoogleOAuthError: Error {
-    case missingClientId
-    case missingClientSecret
-    case invalidCallbackURL
-    case missingAuthorizationCode
-    case tokenExchangeFailed(String)
-    case unableToOpenBrowser
-    case authenticationTimedOut
-    case authenticationCancelled
-}
-
-final class GoogleOAuthService: NSObject {
-    static let shared = GoogleOAuthService()
-    private var inAppOAuthWindowController: InAppOAuthWindowController?
-    private let clientId = BundledOAuthCredentials.googleClientId
-    private let clientSecret = BundledOAuthCredentials.googleClientSecret
-    private let requiredCalendarScope = "https://www.googleapis.com/auth/calendar.readonly"
-    private let scopes = ["openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly"]
-    private lazy var tokenStore = KeychainTokenStore(account: "googleOAuthTokens.\(clientId)")
-
-    func loadTokens() -> GoogleOAuthTokens? { try? tokenStore.readTokens() }
-
-    func clearSavedTokens() {
-        tokenStore.clearTokens()
-    }
-
-    func fetchUserProfile(accessToken: String) async throws -> GoogleUserProfile {
-        var request = URLRequest(url: URL(string: "https://openidconnect.googleapis.com/v1/userinfo")!)
-        request.httpMethod = "GET"
-        request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
-        let (data, response) = try await URLSession.shared.data(for: request)
-        guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
-            throw GoogleOAuthError.tokenExchangeFailed(String(data: data, encoding: .utf8) ?? "Failed")
-        }
-        return try JSONDecoder().decode(GoogleUserProfile.self, from: data)
-    }
-
-    func validAccessToken(presentingWindow: NSWindow?) async throws -> String {
-        if let tokens = try tokenStore.readTokens(),
-           tokens.expiresAt.timeIntervalSinceNow > 60,
-           tokenHasCalendarScope(tokens.scope) {
-            return tokens.accessToken
-        }
-        let tokens = try await interactiveSignIn(presentingWindow: presentingWindow)
-        try tokenStore.writeTokens(tokens)
-        return tokens.accessToken
-    }
-
-    private func interactiveSignIn(presentingWindow: NSWindow?) async throws -> GoogleOAuthTokens {
-        guard let presentingWindow else { throw GoogleOAuthError.unableToOpenBrowser }
-        let oauthWindow = await MainActor.run { makeOAuthWindowController() }
-        let codeVerifier = Self.randomURLSafeString(length: 64)
-        let codeChallenge = Self.pkceChallenge(for: codeVerifier)
-        let state = Self.randomURLSafeString(length: 32)
-        // Google OAuth app settings often already include this callback from prior loopback setup.
-        let redirectURI = "http://127.0.0.1:8742/oauth2redirect"
-
-        var components = URLComponents(string: "https://accounts.google.com/o/oauth2/v2/auth")!
-        components.queryItems = [
-            URLQueryItem(name: "client_id", value: clientId),
-            URLQueryItem(name: "redirect_uri", value: redirectURI),
-            URLQueryItem(name: "response_type", value: "code"),
-            URLQueryItem(name: "scope", value: scopes.joined(separator: " ")),
-            URLQueryItem(name: "state", value: state),
-            URLQueryItem(name: "code_challenge", value: codeChallenge),
-            URLQueryItem(name: "code_challenge_method", value: "S256"),
-            URLQueryItem(name: "access_type", value: "offline"),
-            URLQueryItem(name: "prompt", value: "consent")
-        ]
-
-        guard let authURL = components.url else { throw GoogleOAuthError.invalidCallbackURL }
-        let callbackURL = try await oauthWindow.authenticate(
-            startURL: authURL,
-            callbackURLPrefix: redirectURI,
-            presentingWindow: presentingWindow
-        )
-        let query = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?.queryItems
-        guard query?.first(where: { $0.name == "state" })?.value == state else { throw GoogleOAuthError.invalidCallbackURL }
-        guard let code = query?.first(where: { $0.name == "code" })?.value, code.isEmpty == false else {
-            throw GoogleOAuthError.missingAuthorizationCode
-        }
-        return try await exchangeCodeForTokens(code: code, codeVerifier: codeVerifier, redirectURI: redirectURI)
-    }
-
-    @MainActor
-    private func makeOAuthWindowController() -> InAppOAuthWindowController {
-        if let existing = inAppOAuthWindowController { return existing }
-        let controller = InAppOAuthWindowController(title: "Google Sign-In")
-        inAppOAuthWindowController = controller
-        return controller
-    }
-
-    private func exchangeCodeForTokens(code: String, codeVerifier: String, redirectURI: String) async throws -> GoogleOAuthTokens {
-        var request = URLRequest(url: URL(string: "https://oauth2.googleapis.com/token")!)
-        request.httpMethod = "POST"
-        request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
-        request.httpBody = Self.formURLEncoded([
-            "client_id": clientId,
-            "client_secret": clientSecret,
-            "code": code,
-            "code_verifier": codeVerifier,
-            "redirect_uri": redirectURI,
-            "grant_type": "authorization_code"
-        ])
-        let (data, response) = try await URLSession.shared.data(for: request)
-        guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
-            throw GoogleOAuthError.tokenExchangeFailed(String(data: data, encoding: .utf8) ?? "Failed")
-        }
-
-        struct TokenResponse: Decodable {
-            let access_token: String
-            let expires_in: Double
-            let refresh_token: String?
-            let scope: String?
-            let token_type: String?
-        }
-
-        let decoded = try JSONDecoder().decode(TokenResponse.self, from: data)
-        return GoogleOAuthTokens(
-            accessToken: decoded.access_token,
-            refreshToken: decoded.refresh_token,
-            expiresAt: Date().addingTimeInterval(decoded.expires_in),
-            scope: decoded.scope,
-            tokenType: decoded.token_type
-        )
-    }
-
-    private static func pkceChallenge(for verifier: String) -> String {
-        let digest = SHA256.hash(data: Data(verifier.utf8))
-        return Data(digest).base64URLEncodedString()
-    }
-
-    private static func randomURLSafeString(length: Int) -> String {
-        var bytes = [UInt8](repeating: 0, count: length)
-        _ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
-        return Data(bytes).base64URLEncodedString()
-    }
-
-    private static func formURLEncoded(_ params: [String: String]) -> Data {
-        let allowed = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
-        let pairs = params.map { key, value in
-            let k = key.addingPercentEncoding(withAllowedCharacters: allowed) ?? key
-            let v = value.addingPercentEncoding(withAllowedCharacters: allowed) ?? value
-            return "\(k)=\(v)"
-        }.joined(separator: "&")
-        return Data(pairs.utf8)
-    }
-
-    private func tokenHasCalendarScope(_ scopeValue: String?) -> Bool {
-        guard let scopeValue else { return false }
-        return scopeValue.split(separator: " ").contains(where: { $0 == Substring(requiredCalendarScope) })
-    }
-}
-
 private extension Data {
     func base64URLEncodedString() -> String {
         base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "")
     }
 }
 
-final class KeychainTokenStore {
-    private let defaultsKey: String
-    private let defaults: UserDefaults
-
-    init(service: String = Bundle.main.bundleIdentifier ?? "zoom_app",
-         account: String = "googleOAuthTokens",
-         defaults: UserDefaults = .standard) {
-        self.defaultsKey = "\(service).\(account)"
-        self.defaults = defaults
-    }
-
-    func readTokens() throws -> GoogleOAuthTokens? {
-        guard let data = defaults.data(forKey: defaultsKey) else { return nil }
-        return try JSONDecoder().decode(GoogleOAuthTokens.self, from: data)
-    }
-
-    func writeTokens(_ tokens: GoogleOAuthTokens) throws {
-        let data = try JSONEncoder().encode(tokens)
-        defaults.set(data, forKey: defaultsKey)
-    }
-
-    func clearTokens() {
-        defaults.removeObject(forKey: defaultsKey)
-    }
-}
-
 // MARK: - OAuth window (previous sign-in behavior)
 
 @MainActor
@@ -11461,7 +11238,7 @@ private final class InAppOAuthWindowController: NSWindowController, WKNavigation
         webView.stopLoading()
         if let continuation = callbackContinuation {
             callbackContinuation = nil
-            continuation.resume(throwing: GoogleOAuthError.authenticationCancelled)
+            continuation.resume(throwing: ZoomOAuthError.authenticationCancelled)
         }
     }
 
@@ -11479,12 +11256,12 @@ private final class InAppOAuthWindowController: NSWindowController, WKNavigation
         do {
             return try await withThrowingTaskGroup(of: URL.self) { group in
                 group.addTask { [weak self] in
-                    guard let self else { throw GoogleOAuthError.invalidCallbackURL }
+                    guard let self else { throw ZoomOAuthError.invalidCallbackURL }
                     return try await self.awaitCallback()
                 }
                 group.addTask {
                     try await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000))
-                    throw GoogleOAuthError.authenticationTimedOut
+                    throw ZoomOAuthError.authenticationTimedOut
                 }
                 let url = try await group.next()!
                 group.cancelAll()
@@ -11550,21 +11327,6 @@ private final class InAppOAuthWindowController: NSWindowController, WKNavigation
     }
 }
 
-extension GoogleOAuthError: LocalizedError {
-    var errorDescription: String? {
-        switch self {
-        case .missingClientId: return "Missing Google OAuth Client ID."
-        case .missingClientSecret: return "Missing Google OAuth Client Secret."
-        case .invalidCallbackURL: return "Invalid OAuth callback URL."
-        case .missingAuthorizationCode: return "Google did not return an authorization code."
-        case .tokenExchangeFailed(let details): return "Token exchange failed: \(details)"
-        case .unableToOpenBrowser: return "Could not open browser for Google sign-in."
-        case .authenticationTimedOut: return "Google sign-in timed out."
-        case .authenticationCancelled: return "Sign-in was cancelled."
-        }
-    }
-}
-
 extension ZoomOAuthError: LocalizedError {
     var errorDescription: String? {
         switch self {