Ver código fonte

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 semanas atrás
pai
commit
7627bdbcc4

+ 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
 • 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
 • 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
 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.
 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,
         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
         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 zoomClientId: String { ObfuscatedCredential.reveal(zoomClientIdBytes) }
     static var zoomPublicClientId: String { ObfuscatedCredential.reveal(zoomPublicClientIdBytes) }
     static var zoomPublicClientId: String { ObfuscatedCredential.reveal(zoomPublicClientIdBytes) }
     static var zoomClientSecret: String { ObfuscatedCredential.reveal(zoomClientSecretBytes) }
     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"
         case settings = "Settings"
     }
     }
 
 
-    private let googleOAuth = GoogleOAuthService.shared
     private let zoomOAuth = ZoomOAuthService.shared
     private let zoomOAuth = ZoomOAuthService.shared
     private let loginStateKey = "zoom_app.isLoggedIn"
     private let loginStateKey = "zoom_app.isLoggedIn"
     private let darkModeDefaultsKey = "settings.darkModeEnabled"
     private let darkModeDefaultsKey = "settings.darkModeEnabled"
@@ -344,7 +343,6 @@ class ViewController: NSViewController {
 
 
     private let rootContainer = NSView()
     private let rootContainer = NSView()
     private var homeView: NSView?
     private var homeView: NSView?
-    private weak var googleButton: NSButton?
     private weak var timeLabel: NSTextField?
     private weak var timeLabel: NSTextField?
     private weak var dateLabel: NSTextField?
     private weak var dateLabel: NSTextField?
     private weak var meetingsDayHeaderLabel: NSTextField?
     private weak var meetingsDayHeaderLabel: NSTextField?
@@ -486,7 +484,7 @@ class ViewController: NSViewController {
     private weak var settingsNotificationsBlock: NSStackView?
     private weak var settingsNotificationsBlock: NSStackView?
     private weak var settingsPagePrimaryLanguagePopup: NSPopUpButton?
     private weak var settingsPagePrimaryLanguagePopup: NSPopUpButton?
     private weak var settingsPageSecondaryLanguagePopup: NSPopUpButton?
     private weak var settingsPageSecondaryLanguagePopup: NSPopUpButton?
-    private weak var settingsGoogleActionButton: NSButton?
+    private weak var settingsZoomActionButton: NSButton?
     private weak var topBarPremiumButton: NSButton?
     private weak var topBarPremiumButton: NSButton?
     private weak var topBarAuthButton: NSButton?
     private weak var topBarAuthButton: NSButton?
     private var topBarAuthButtonWidthConstraint: NSLayoutConstraint?
     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()
         clearMeetingsScrollObserver()
         clearMeetingsPageScrollObserver()
         clearMeetingsPageScrollObserver()
         removeSearchFieldObserver()
         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.
     /// Primary Zoom sign-in: browser OAuth, token refresh, then home with scheduled meetings.
     @objc private func zoomPrimarySignInTapped() {
     @objc private func zoomPrimarySignInTapped() {
         guard isSigningIn == false else { return }
         guard isSigningIn == false else { return }
         isSigningIn = true
         isSigningIn = true
         topBarAuthButton?.title = "Signing in..."
         topBarAuthButton?.title = "Signing in..."
         topBarAuthButton?.isEnabled = false
         topBarAuthButton?.isEnabled = false
-        googleButton?.isEnabled = false
 
 
         Task {
         Task {
             do {
             do {
@@ -1072,7 +1044,7 @@ class ViewController: NSViewController {
 
 
                 let zoomToken = try await zoomOAuth.validAccessToken(presentingWindow: view.window)
                 let zoomToken = try await zoomOAuth.validAccessToken(presentingWindow: view.window)
                 let zoomUser = try? await fetchZoomUserProfile(accessToken: zoomToken)
                 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)
                 let profileTimeZone = parseZoomTimeZone(zoomUser?.timezone)
 
 
                 await MainActor.run {
                 await MainActor.run {
@@ -2930,7 +2902,6 @@ class ViewController: NSViewController {
         isLoadingMeetings = false
         isLoadingMeetings = false
         meetingsRateLimitedUntil = nil
         meetingsRateLimitedUntil = nil
         clearMeetingsScrollObserver()
         clearMeetingsScrollObserver()
-        googleOAuth.clearSavedTokens()
         zoomOAuth.clearSavedTokens()
         zoomOAuth.clearSavedTokens()
         zoomSignedInDisplayName = nil
         zoomSignedInDisplayName = nil
         embeddedZoomPendingWebAuth = nil
         embeddedZoomPendingWebAuth = nil
@@ -3456,13 +3427,11 @@ class ViewController: NSViewController {
     @MainActor
     @MainActor
     private func resetLoginSigningInState() {
     private func resetLoginSigningInState() {
         isSigningIn = false
         isSigningIn = false
-        googleButton?.title = "G"
         updateTopBarAuthButton()
         updateTopBarAuthButton()
-        googleButton?.isEnabled = true
     }
     }
 
 
     @MainActor
     @MainActor
-    private func completeLoginAndRestart(profile: GoogleUserProfile?) {
+    private func completeLoginAndRestart(profile: UserProfile?) {
         persistLoggedInState(true)
         persistLoggedInState(true)
         let trimmedName = profile?.name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
         let trimmedName = profile?.name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
         zoomSignedInDisplayName = trimmedName.isEmpty ? nil : trimmedName
         zoomSignedInDisplayName = trimmedName.isEmpty ? nil : trimmedName
@@ -3538,8 +3507,8 @@ class ViewController: NSViewController {
     private func applyAuthenticationUIState(triggerInitialRefresh: Bool) {
     private func applyAuthenticationUIState(triggerInitialRefresh: Bool) {
         let loggedIn = isUserLoggedIn()
         let loggedIn = isUserLoggedIn()
         updateTopBarAuthButton()
         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()
         updatePremiumLockedSections()
 
 
         meetingsRefreshTimer?.invalidate()
         meetingsRefreshTimer?.invalidate()
@@ -4354,7 +4323,7 @@ class ViewController: NSViewController {
 
 
         let (data, response) = try await URLSession.shared.data(for: request)
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
         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)
         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")!
         var components = URLComponents(string: "https://api.zoom.us/v2/users/me/token")!
         components.queryItems = [URLQueryItem(name: "type", value: "zak")]
         components.queryItems = [URLQueryItem(name: "type", value: "zak")]
         guard let url = components.url else {
         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)
         var request = URLRequest(url: url)
         request.httpMethod = "GET"
         request.httpMethod = "GET"
@@ -4376,7 +4345,7 @@ class ViewController: NSViewController {
 
 
         let (data, response) = try await URLSession.shared.data(for: request)
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
         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 {
         if http.statusCode == 429 {
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4388,12 +4357,12 @@ class ViewController: NSViewController {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 throw ZoomOAuthError.missingRequiredScope(raw)
                 throw ZoomOAuthError.missingRequiredScope(raw)
             }
             }
-            throw GoogleOAuthError.tokenExchangeFailed(raw)
+            throw ZoomOAuthError.tokenExchangeFailed(raw)
         }
         }
         let decoded = try JSONDecoder().decode(ZoomZAKTokenResponse.self, from: data)
         let decoded = try JSONDecoder().decode(ZoomZAKTokenResponse.self, from: data)
         let trimmed = decoded.token.trimmingCharacters(in: .whitespacesAndNewlines)
         let trimmed = decoded.token.trimmingCharacters(in: .whitespacesAndNewlines)
         guard trimmed.isEmpty == false else {
         guard trimmed.isEmpty == false else {
-            throw GoogleOAuthError.tokenExchangeFailed("Empty ZAK from Zoom")
+            throw ZoomOAuthError.tokenExchangeFailed("Empty ZAK from Zoom")
         }
         }
         return trimmed
         return trimmed
     }
     }
@@ -4436,7 +4405,7 @@ class ViewController: NSViewController {
 
 
         let (data, response) = try await URLSession.shared.data(for: request)
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
         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 {
         if http.statusCode == 429 {
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4448,7 +4417,7 @@ class ViewController: NSViewController {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 throw ZoomOAuthError.missingRequiredScope(raw)
                 throw ZoomOAuthError.missingRequiredScope(raw)
             }
             }
-            throw GoogleOAuthError.tokenExchangeFailed(raw)
+            throw ZoomOAuthError.tokenExchangeFailed(raw)
         }
         }
         return try JSONDecoder().decode(ZoomCreateMeetingAPIResult.self, from: data)
         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)
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
         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 {
         if http.statusCode == 429 {
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
             let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4476,7 +4445,7 @@ class ViewController: NSViewController {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 throw ZoomOAuthError.missingRequiredScope(raw)
                 throw ZoomOAuthError.missingRequiredScope(raw)
             }
             }
-            throw GoogleOAuthError.tokenExchangeFailed(raw)
+            throw ZoomOAuthError.tokenExchangeFailed(raw)
         }
         }
         return try JSONDecoder().decode(ZoomCreateMeetingAPIResult.self, from: data)
         return try JSONDecoder().decode(ZoomCreateMeetingAPIResult.self, from: data)
     }
     }
@@ -4580,7 +4549,7 @@ class ViewController: NSViewController {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                     throw ZoomOAuthError.missingRequiredScope(raw)
                     throw ZoomOAuthError.missingRequiredScope(raw)
                 }
                 }
-                throw GoogleOAuthError.tokenExchangeFailed(raw)
+                throw ZoomOAuthError.tokenExchangeFailed(raw)
             }
             }
 
 
             let decoded = try JSONDecoder().decode(ZoomMeetingsPage.self, from: data)
             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)
             let (data, response) = try await URLSession.shared.data(for: request)
             guard let http = response as? HTTPURLResponse else {
             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 {
             if http.statusCode == 429 {
                 let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
                 let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4761,7 +4730,7 @@ class ViewController: NSViewController {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                     throw ZoomOAuthError.missingRequiredScope(raw)
                     throw ZoomOAuthError.missingRequiredScope(raw)
                 }
                 }
-                throw GoogleOAuthError.tokenExchangeFailed(raw)
+                throw ZoomOAuthError.tokenExchangeFailed(raw)
             }
             }
 
 
             let decoded = try JSONDecoder().decode(Page.self, from: data)
             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)
             let (data, response) = try await URLSession.shared.data(for: request)
             guard let http = response as? HTTPURLResponse else {
             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 {
             if http.statusCode == 429 {
                 let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
                 let retryAfterRaw = http.value(forHTTPHeaderField: "Retry-After")
@@ -4850,7 +4819,7 @@ class ViewController: NSViewController {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                     throw ZoomOAuthError.missingRequiredScope(raw)
                     throw ZoomOAuthError.missingRequiredScope(raw)
                 }
                 }
-                throw GoogleOAuthError.tokenExchangeFailed(raw)
+                throw ZoomOAuthError.tokenExchangeFailed(raw)
             }
             }
 
 
             let decoded = try JSONDecoder().decode(PastMeetingsPage.self, from: data)
             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"
         let urlString = "https://api.zoom.us/v2/meetings/\(pathSegment)/meeting_summary"
         guard let url = URL(string: urlString) else {
         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)
         var request = URLRequest(url: url)
         request.httpMethod = "GET"
         request.httpMethod = "GET"
@@ -4908,7 +4877,7 @@ class ViewController: NSViewController {
 
 
         let (data, response) = try await URLSession.shared.data(for: request)
         let (data, response) = try await URLSession.shared.data(for: request)
         guard let http = response as? HTTPURLResponse else {
         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 == 404 || http.statusCode == 204 { return nil }
         if http.statusCode == 429 {
         if http.statusCode == 429 {
@@ -4921,7 +4890,7 @@ class ViewController: NSViewController {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
             if raw.localizedCaseInsensitiveContains("does not contain scopes") {
                 throw ZoomOAuthError.missingRequiredScope(raw)
                 throw ZoomOAuthError.missingRequiredScope(raw)
             }
             }
-            throw GoogleOAuthError.tokenExchangeFailed(raw)
+            throw ZoomOAuthError.tokenExchangeFailed(raw)
         }
         }
 
 
         let iso = ISO8601DateFormatter()
         let iso = ISO8601DateFormatter()
@@ -4981,10 +4950,10 @@ class ViewController: NSViewController {
 
 
         let accountTitle = textLabel("Account", font: .systemFont(ofSize: 16, weight: .semibold), color: primaryText)
         let accountTitle = textLabel("Account", font: .systemFont(ofSize: 16, weight: .semibold), color: primaryText)
         stack.addArrangedSubview(accountTitle)
         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)
         let appTitle = textLabel("App", font: .systemFont(ofSize: 16, weight: .semibold), color: primaryText)
         stack.addArrangedSubview(appTitle)
         stack.addArrangedSubview(appTitle)
@@ -5312,25 +5281,25 @@ class ViewController: NSViewController {
         return row
         return row
     }
     }
 
 
-    private func makeSettingsGoogleAccountRow() -> NSView {
+    private func makeSettingsZoomAccountRow() -> NSView {
         let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
         let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
         row.translatesAutoresizingMaskIntoConstraints = false
         row.translatesAutoresizingMaskIntoConstraints = false
         styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
         styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
 
 
         let signedIn = isUserLoggedIn()
         let signedIn = isUserLoggedIn()
         let titleText = signedIn ? "Zoom account connected" : "Zoom account not connected"
         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 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)
         let subtitle = textLabel(subtitleText, font: NSFont.systemFont(ofSize: 13, weight: .regular), color: secondaryText)
         subtitle.maximumNumberOfLines = 2
         subtitle.maximumNumberOfLines = 2
         subtitle.lineBreakMode = .byTruncatingTail
         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.translatesAutoresizingMaskIntoConstraints = false
         actionButton.bezelStyle = .rounded
         actionButton.bezelStyle = .rounded
         actionButton.controlSize = .regular
         actionButton.controlSize = .regular
-        settingsGoogleActionButton = actionButton
+        settingsZoomActionButton = actionButton
 
 
         row.addSubview(title)
         row.addSubview(title)
         row.addSubview(subtitle)
         row.addSubview(subtitle)
@@ -5393,7 +5362,7 @@ class ViewController: NSViewController {
         }
         }
     }
     }
 
 
-    @objc private func settingsGoogleActionButtonClicked(_ sender: NSButton) {
+    @objc private func settingsZoomActionButtonClicked(_ sender: NSButton) {
         if isUserLoggedIn() {
         if isUserLoggedIn() {
             logoutTapped()
             logoutTapped()
             return
             return
@@ -6822,7 +6791,7 @@ class ViewController: NSViewController {
 
 
     // MARK: - Home UI
     // MARK: - Home UI
 
 
-    private func makeHomeView(profile: GoogleUserProfile?) -> NSView {
+    private func makeHomeView(profile: UserProfile?) -> NSView {
         let root = NSView()
         let root = NSView()
         let shell = NSView()
         let shell = NSView()
         shell.wantsLayer = true
         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 name: String?
     var email: String?
     var email: String?
     var picture: String?
     var picture: String?
@@ -11027,10 +10988,6 @@ final class ZoomOAuthService: NSObject {
             }
             }
             await MainActor.run { self.onInteractiveSignInCompleted?() }
             await MainActor.run { self.onInteractiveSignInCompleted?() }
             return tokens
             return tokens
-        } catch GoogleOAuthError.authenticationTimedOut {
-            throw ZoomOAuthError.authenticationTimedOut
-        } catch GoogleOAuthError.authenticationCancelled {
-            throw ZoomOAuthError.authenticationCancelled
         } catch {
         } catch {
             throw error
             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 {
 private extension Data {
     func base64URLEncodedString() -> String {
     func base64URLEncodedString() -> String {
         base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "")
         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)
 // MARK: - OAuth window (previous sign-in behavior)
 
 
 @MainActor
 @MainActor
@@ -11461,7 +11238,7 @@ private final class InAppOAuthWindowController: NSWindowController, WKNavigation
         webView.stopLoading()
         webView.stopLoading()
         if let continuation = callbackContinuation {
         if let continuation = callbackContinuation {
             callbackContinuation = nil
             callbackContinuation = nil
-            continuation.resume(throwing: GoogleOAuthError.authenticationCancelled)
+            continuation.resume(throwing: ZoomOAuthError.authenticationCancelled)
         }
         }
     }
     }
 
 
@@ -11479,12 +11256,12 @@ private final class InAppOAuthWindowController: NSWindowController, WKNavigation
         do {
         do {
             return try await withThrowingTaskGroup(of: URL.self) { group in
             return try await withThrowingTaskGroup(of: URL.self) { group in
                 group.addTask { [weak self] 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()
                     return try await self.awaitCallback()
                 }
                 }
                 group.addTask {
                 group.addTask {
                     try await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000))
                     try await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000))
-                    throw GoogleOAuthError.authenticationTimedOut
+                    throw ZoomOAuthError.authenticationTimedOut
                 }
                 }
                 let url = try await group.next()!
                 let url = try await group.next()!
                 group.cancelAll()
                 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 {
 extension ZoomOAuthError: LocalizedError {
     var errorDescription: String? {
     var errorDescription: String? {
         switch self {
         switch self {