From 601849be11fe58864f82748993dad13e6391ba21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 21:18:52 +0100 Subject: [PATCH] a file action gets an authenticated client, because it cannot build one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `run` is an event handler outside React, so it cannot call `useClient` — and a bare fetch gets a 401, since this API authenticates with a bearer token rather than a cookie and only the hook holds it. So the browser hands one over in the context. Without it, the only way to call Officer's own API from an action was to reimplement the token handling, which is the kind of thing that works on one page and silently stops when the token rotates. Co-Authored-By: Claude Opus 5 --- .../FileBrowserApp/components/FileGrid.tsx | 5 ++++- .../officerdev/src/apps/FileBrowser/file-actions.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileGrid.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileGrid.tsx index 3ff50552..57a145f1 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileGrid.tsx +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileGrid.tsx @@ -4,6 +4,7 @@ import { ChevronUp, ChevronDown } from 'lucide-react'; import type { DirEntry } from '../../../../hooks/useFilesAPI'; import { getFileType } from '../../../FileViewer'; import type { UseFileBrowserAppType } from '../useFileBrowserApp'; +import { useClient } from 'hooks/useClient'; import { fileActionsFor } from '../../file-actions'; import { FileItem } from './FileItem'; import { useTranslation } from '../../../../locales'; @@ -62,6 +63,8 @@ const useColumnCount = (scrollRef: React.RefObject) => { export const FileGrid = ({ fileBrowserManager }: FileGridProps) => { const { t } = useTranslation(); + // For plugin actions: `run` is outside React and cannot call the hook itself. + const client = useClient(); const { visibleEntries: entries, viewMode, @@ -182,7 +185,7 @@ export const FileGrid = ({ fileBrowserManager }: FileGridProps) => { key: action.key, label: action.label, Icon: action.icon, - onClick: () => action.run(payload, { currentPath, refresh }), + onClick: () => action.run(payload, { currentPath, refresh, client }), })); }; diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/file-actions.ts b/src/workspaces/officerdev/src/apps/FileBrowser/file-actions.ts index 392360bd..85849035 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/file-actions.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/file-actions.ts @@ -34,6 +34,19 @@ export type FileActionContext = { currentPath: string; /** Re-read the listing. For an action that creates or removes something on disk. */ refresh: () => void; + /** + * An authenticated client for Officer's API, rooted at `/api`. + * + * Handed over rather than left to the action, because there is no way for it to build one: `run` is + * called from an event handler outside React, so it cannot use `useClient`, and a bare `fetch` gets a + * 401 — this API authenticates with a bearer token, not a cookie, and only the hook holds it. + */ + client: { + get: (url: string) => Promise; + post: (url: string, payload?: unknown) => Promise; + put: (url: string, payload?: unknown) => Promise; + delete: (url: string, payload?: unknown) => Promise; + }; }; export type FileAction = {