Defer protected app data reads until user action (#1370)
This commit is contained in:
parent
b309f49ee3
commit
ae0fc9c49a
|
|
@ -175,18 +175,12 @@ describe('fetchGeminiRateLimits fallback oauth creds', () => {
|
|||
expect(result.error).toContain('Gemini project ID not found')
|
||||
})
|
||||
|
||||
it('returns unavailable when geminiCliOAuthEnabled=false and no google entry in auth.json', async () => {
|
||||
readFileMock.mockImplementation(async (filePath: string) => {
|
||||
if (filePath.includes('auth.json')) {
|
||||
return JSON.stringify({ 'opencode-go': { type: 'api', key: 'k' } })
|
||||
}
|
||||
throw { code: 'ENOENT' }
|
||||
})
|
||||
|
||||
it('returns unavailable without reading OAuth files when geminiCliOAuthEnabled=false', async () => {
|
||||
const result = await fetchGeminiRateLimits(false)
|
||||
|
||||
expect(result.status).toBe('unavailable')
|
||||
expect(result.error).toContain('disabled')
|
||||
expect(readFileMock).not.toHaveBeenCalled()
|
||||
// No network calls should have been made.
|
||||
expect(netFetchMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -203,22 +203,25 @@ async function fetchViaOauthCreds(
|
|||
export async function fetchGeminiRateLimits(
|
||||
geminiCliOAuthEnabled = false
|
||||
): Promise<ProviderRateLimits> {
|
||||
if (!geminiCliOAuthEnabled) {
|
||||
// Why: the OAuth sources include other apps' data folders on macOS.
|
||||
// Do not touch them during background polling unless the user opts in.
|
||||
return {
|
||||
provider: 'gemini',
|
||||
session: null,
|
||||
weekly: null,
|
||||
updatedAt: Date.now(),
|
||||
error: 'Gemini CLI OAuth is disabled in settings',
|
||||
status: 'unavailable'
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const authJson = await readAuthJson()
|
||||
const result =
|
||||
authJson?.google?.type === 'oauth'
|
||||
? await fetchViaAuthJson(authJson.google, geminiCliOAuthEnabled)
|
||||
: await (async () => {
|
||||
if (!geminiCliOAuthEnabled) {
|
||||
return {
|
||||
provider: 'gemini',
|
||||
session: null,
|
||||
weekly: null,
|
||||
updatedAt: Date.now(),
|
||||
error: 'Gemini CLI OAuth is disabled in settings',
|
||||
status: 'unavailable'
|
||||
} as ProviderRateLimits
|
||||
}
|
||||
const creds = await readGeminiCredentials()
|
||||
return !creds
|
||||
? ({
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ function App(): React.JSX.Element {
|
|||
hydrateEditorSession: s.hydrateEditorSession,
|
||||
hydrateBrowserSession: s.hydrateBrowserSession,
|
||||
fetchBrowserSessionProfiles: s.fetchBrowserSessionProfiles,
|
||||
fetchDetectedBrowsers: s.fetchDetectedBrowsers,
|
||||
reconnectPersistedTerminals: s.reconnectPersistedTerminals,
|
||||
setDeferredSshReconnectTargets: s.setDeferredSshReconnectTargets,
|
||||
setSshConnectionState: s.setSshConnectionState,
|
||||
|
|
@ -233,7 +232,6 @@ function App(): React.JSX.Element {
|
|||
actions.hydrateEditorSession(session)
|
||||
actions.hydrateBrowserSession(session)
|
||||
await actions.fetchBrowserSessionProfiles()
|
||||
await actions.fetchDetectedBrowsers()
|
||||
|
||||
// Why: SSH connections must be re-established BEFORE terminal
|
||||
// reconnect so that reconnectPersistedTerminals can route SSH-backed
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export function BrowserToolbarMenu({
|
|||
const createBrowserSessionProfile = useAppStore((s) => s.createBrowserSessionProfile)
|
||||
const importCookiesFromBrowser = useAppStore((s) => s.importCookiesFromBrowser)
|
||||
const importCookiesToProfile = useAppStore((s) => s.importCookiesToProfile)
|
||||
const fetchDetectedBrowsers = useAppStore((s) => s.fetchDetectedBrowsers)
|
||||
const browserSessionImportState = useAppStore((s) => s.browserSessionImportState)
|
||||
|
||||
const [newProfileDialogOpen, setNewProfileDialogOpen] = useState(false)
|
||||
|
|
@ -171,7 +172,15 @@ export function BrowserToolbarMenu({
|
|||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSub
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
// Why: macOS treats other browsers' profile folders as app
|
||||
// data. Only probe them when the user opens the import menu.
|
||||
void fetchDetectedBrowsers()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuSubTrigger disabled={browserSessionImportState?.status === 'importing'}>
|
||||
<Import className="mr-2 size-3.5" />
|
||||
Import Cookies
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export function BrowserProfileRow({
|
|||
isDefault
|
||||
}: BrowserProfileRowProps): React.JSX.Element {
|
||||
const isImporting = importState?.profileId === profile.id && importState.status === 'importing'
|
||||
const fetchDetectedBrowsers = useAppStore((s) => s.fetchDetectedBrowsers)
|
||||
|
||||
const handleImportFromBrowser = async (
|
||||
browserFamily: string,
|
||||
|
|
@ -115,7 +116,15 @@ export function BrowserProfileRow({
|
|||
)}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1" onClick={(e) => e.stopPropagation()}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenu
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
// Why: macOS treats other browsers' profile folders as app
|
||||
// data. Only probe them when the user opens the import menu.
|
||||
void fetchDetectedBrowsers()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
|
|||
|
|
@ -77,8 +77,7 @@ export function BrowserUseSetup({
|
|||
}
|
||||
void refreshCli()
|
||||
void fetchBrowserSessionProfiles()
|
||||
void fetchDetectedBrowsers()
|
||||
}, [browserUseEnabled, fetchBrowserSessionProfiles, fetchDetectedBrowsers])
|
||||
}, [browserUseEnabled, fetchBrowserSessionProfiles])
|
||||
|
||||
const defaultProfile = browserSessionProfiles.find((p) => p.id === 'default')
|
||||
// Why: this step explicitly imports into the default profile, so completion
|
||||
|
|
@ -325,7 +324,15 @@ export function BrowserUseSetup({
|
|||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenu
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
// Why: macOS treats other browsers' profile folders as app
|
||||
// data. Only probe them when the user opens the import menu.
|
||||
void fetchDetectedBrowsers()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant={cookiesImported ? 'outline' : 'default'}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ export type BrowserSlice = {
|
|||
profiles: { name: string; directory: string }[]
|
||||
selectedProfile: string
|
||||
}[]
|
||||
detectedBrowsersLoaded: boolean
|
||||
fetchDetectedBrowsers: () => Promise<void>
|
||||
importCookiesFromBrowser: (
|
||||
profileId: string,
|
||||
|
|
@ -1283,8 +1284,12 @@ export const createBrowserSlice: StateCreator<AppState, [], [], BrowserSlice> =
|
|||
},
|
||||
|
||||
detectedBrowsers: [],
|
||||
detectedBrowsersLoaded: false,
|
||||
|
||||
fetchDetectedBrowsers: async () => {
|
||||
if (get().detectedBrowsersLoaded) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const browsers = (await window.api.browser.sessionDetectBrowsers()) as {
|
||||
family: string
|
||||
|
|
@ -1292,9 +1297,10 @@ export const createBrowserSlice: StateCreator<AppState, [], [], BrowserSlice> =
|
|||
profiles: { name: string; directory: string }[]
|
||||
selectedProfile: string
|
||||
}[]
|
||||
set({ detectedBrowsers: browsers })
|
||||
set({ detectedBrowsers: browsers, detectedBrowsersLoaded: true })
|
||||
} catch {
|
||||
/* best-effort — empty list is acceptable fallback */
|
||||
set({ detectedBrowsersLoaded: true })
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue