From 9f60c48cbae9780b4c513d3546fc459cddedf049 Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Sat, 16 May 2026 01:01:31 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Google=20Drive=20picks=20fail=20with=204?= =?UTF-8?q?04=20=E2=80=94=20call=20setAppId=20on=20Picker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the drive.file OAuth scope (narrowest, picked-files-only), Google Picker only authorizes the picked file for our OAuth client when PickerBuilder.setAppId() is called. The hook from #2306 didn't set it, so every pick succeeded in the Picker UI but the server's download attempt hit "404 File not found" — surfacing as "Error handling Google Drive files" in the browser. The project number is the numeric prefix of the OAuth client ID (-.apps.googleusercontent.com), so it's derived at runtime from REACT_APP_GOOGLE_CLIENT_ID rather than introducing a third env var. This eliminates the two-vars-can-drift footgun. Test mock updated with setAppId stub; added an assertion that confirms the derived project number is passed to setAppId. Co-Authored-By: Claude Opus 4.7 --- .../UploadForm/hooks/useGooglePicker.test.ts | 12 ++++++++++++ .../components/UploadForm/hooks/useGooglePicker.ts | 5 ++++- web/src/pages/WhatsNewPage/changelog.ts | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/web/src/pages/UploadPage/components/UploadForm/hooks/useGooglePicker.test.ts b/web/src/pages/UploadPage/components/UploadForm/hooks/useGooglePicker.test.ts index bdd22014b..129ac6f7b 100644 --- a/web/src/pages/UploadPage/components/UploadForm/hooks/useGooglePicker.test.ts +++ b/web/src/pages/UploadPage/components/UploadForm/hooks/useGooglePicker.test.ts @@ -25,6 +25,7 @@ function buildBuilder( ) { const built = { setVisible: vi.fn() }; const builder: Record = {}; + builder.setAppId = vi.fn().mockReturnValue(builder); builder.setOAuthToken = vi.fn().mockReturnValue(builder); builder.setDeveloperKey = vi.fn().mockReturnValue(builder); builder.addView = vi.fn().mockReturnValue(builder); @@ -153,5 +154,16 @@ describe('useGooglePicker', () => { /access_denied/ ); }); + + it('calls setAppId with the project number derived from the client id', async () => { + process.env.REACT_APP_GOOGLE_CLIENT_ID = + '806855830059-abc123def.apps.googleusercontent.com'; + const { builder } = stubGoogle((cb) => { + queueMicrotask(() => cb({ action: 'cancel' })); + }); + const { result } = renderHook(() => useGooglePicker()); + await result.current.openPicker(); + expect(builder.setAppId).toHaveBeenCalledWith('806855830059'); + }); }); }); diff --git a/web/src/pages/UploadPage/components/UploadForm/hooks/useGooglePicker.ts b/web/src/pages/UploadPage/components/UploadForm/hooks/useGooglePicker.ts index 176e72033..af013b4f8 100644 --- a/web/src/pages/UploadPage/components/UploadForm/hooks/useGooglePicker.ts +++ b/web/src/pages/UploadPage/components/UploadForm/hooks/useGooglePicker.ts @@ -47,7 +47,7 @@ interface PickerBuilder { setDeveloperKey: (key: string) => PickerBuilder; setCallback: (cb: (data: PickerCallbackData) => void) => PickerBuilder; addView: (view: unknown) => PickerBuilder; - setAppId?: (id: string) => PickerBuilder; + setAppId: (id: string) => PickerBuilder; build: () => { setVisible: (visible: boolean) => void }; } @@ -204,6 +204,8 @@ export function useGooglePicker() { } inFlightRef.current = true; + const projectNumber = id.split('-')[0]; + return Promise.all([loadPicker(), loadIdentityServices()]) .then( ([picker, oauth2]) => @@ -223,6 +225,7 @@ export function useGooglePicker() { const view = new picker.DocsView(); view.setIncludeFolders(false); const built = new picker.PickerBuilder() + .setAppId(projectNumber) .setOAuthToken(resp.access_token) .setDeveloperKey(key) .addView(view) diff --git a/web/src/pages/WhatsNewPage/changelog.ts b/web/src/pages/WhatsNewPage/changelog.ts index 7edd0d8ea..b34a0ee40 100644 --- a/web/src/pages/WhatsNewPage/changelog.ts +++ b/web/src/pages/WhatsNewPage/changelog.ts @@ -5,6 +5,7 @@ export interface ChangelogEntry { } export const changelog: ChangelogEntry[] = [ + { type: 'fix', title: 'Upload form: Google Drive picks convert into decks instead of erroring', date: '2026-05-16' }, { type: 'feature', title: 'Upload form: pick a file from Google Drive in one click', date: '2026-05-16' }, { type: 'feature', title: 'From Google Drive section on Downloads — see the files you picked from Drive and open any of them with one click', date: '2026-05-16' }, { type: 'style', title: 'Upload form has tabs — Your computer and Dropbox sit side by side at the top of the page', date: '2026-05-15' },