fix(auth): enrich PTY env and invalidate status cache after login
- Use buildEnrichedEnv() in PtyTerminalService so login terminal gets full PATH (Homebrew, nvm, etc.) and USER for Keychain lookup - Add cliInstaller:invalidateStatus IPC to clear cached auth status after successful login, preventing stale "not logged in" responses - Show "Verifying authentication..." spinner instead of flashing the "Not logged in" banner between modal close and status refresh Ref #27
This commit is contained in:
parent
60d80cde70
commit
b8aa2d9f14
9 changed files with 64 additions and 5 deletions
|
|
@ -10,6 +10,7 @@
|
|||
import {
|
||||
CLI_INSTALLER_GET_STATUS,
|
||||
CLI_INSTALLER_INSTALL,
|
||||
CLI_INSTALLER_INVALIDATE_STATUS,
|
||||
// eslint-disable-next-line boundaries/element-types -- IPC channel constants shared between main and preload
|
||||
} from '@preload/constants/ipcChannels';
|
||||
import { getErrorMessage } from '@shared/utils/errorHandling';
|
||||
|
|
@ -39,6 +40,7 @@ export function initializeCliInstallerHandlers(installerService: CliInstallerSer
|
|||
export function registerCliInstallerHandlers(ipcMain: IpcMain): void {
|
||||
ipcMain.handle(CLI_INSTALLER_GET_STATUS, handleGetStatus);
|
||||
ipcMain.handle(CLI_INSTALLER_INSTALL, handleInstall);
|
||||
ipcMain.handle(CLI_INSTALLER_INVALIDATE_STATUS, handleInvalidateStatus);
|
||||
|
||||
logger.info('CLI installer handlers registered');
|
||||
}
|
||||
|
|
@ -49,6 +51,7 @@ export function registerCliInstallerHandlers(ipcMain: IpcMain): void {
|
|||
export function removeCliInstallerHandlers(ipcMain: IpcMain): void {
|
||||
ipcMain.removeHandler(CLI_INSTALLER_GET_STATUS);
|
||||
ipcMain.removeHandler(CLI_INSTALLER_INSTALL);
|
||||
ipcMain.removeHandler(CLI_INSTALLER_INVALIDATE_STATUS);
|
||||
|
||||
logger.info('CLI installer handlers removed');
|
||||
}
|
||||
|
|
@ -105,3 +108,8 @@ async function handleInstall(_event: IpcMainInvokeEvent): Promise<IpcResult<void
|
|||
return { success: false, error: msg };
|
||||
}
|
||||
}
|
||||
|
||||
function handleInvalidateStatus(_event: IpcMainInvokeEvent): IpcResult<void> {
|
||||
cachedStatus = null;
|
||||
return { success: true, data: undefined };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
import { buildEnrichedEnv } from '@main/utils/cliEnv';
|
||||
import { getHomeDir } from '@main/utils/pathDecoder';
|
||||
// eslint-disable-next-line boundaries/element-types -- IPC channel constants shared between main and preload
|
||||
import { TERMINAL_DATA, TERMINAL_EXIT } from '@preload/constants/ipcChannels';
|
||||
|
|
@ -65,9 +66,7 @@ export class PtyTerminalService {
|
|||
rows: options?.rows ?? 24,
|
||||
cwd: options?.cwd ?? home,
|
||||
env: {
|
||||
...process.env,
|
||||
HOME: home,
|
||||
USERPROFILE: home,
|
||||
...buildEnrichedEnv(),
|
||||
...options?.env,
|
||||
} as Record<string, string>,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -405,6 +405,9 @@ export const CLI_INSTALLER_INSTALL = 'cliInstaller:install';
|
|||
/** CLI installer progress events (main -> renderer) */
|
||||
export const CLI_INSTALLER_PROGRESS = 'cliInstaller:progress';
|
||||
|
||||
/** Invalidate cached CLI status (forces fresh check on next getStatus) */
|
||||
export const CLI_INSTALLER_INVALIDATE_STATUS = 'cliInstaller:invalidateStatus';
|
||||
|
||||
// =============================================================================
|
||||
// Terminal API Channels
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
APP_RELAUNCH,
|
||||
CLI_INSTALLER_GET_STATUS,
|
||||
CLI_INSTALLER_INSTALL,
|
||||
CLI_INSTALLER_INVALIDATE_STATUS,
|
||||
CLI_INSTALLER_PROGRESS,
|
||||
CONTEXT_CHANGED,
|
||||
CONTEXT_GET_ACTIVE,
|
||||
|
|
@ -1293,6 +1294,9 @@ const electronAPI: ElectronAPI = {
|
|||
install: async (): Promise<void> => {
|
||||
return invokeIpcWithResult<void>(CLI_INSTALLER_INSTALL);
|
||||
},
|
||||
invalidateStatus: async (): Promise<void> => {
|
||||
return invokeIpcWithResult<void>(CLI_INSTALLER_INVALIDATE_STATUS);
|
||||
},
|
||||
onProgress: (callback: (event: unknown, data: CliInstallerProgress) => void): (() => void) => {
|
||||
ipcRenderer.on(
|
||||
CLI_INSTALLER_PROGRESS,
|
||||
|
|
|
|||
|
|
@ -1045,6 +1045,7 @@ export class HttpAPIClient implements ElectronAPI {
|
|||
install: async (): Promise<void> => {
|
||||
console.warn('[HttpAPIClient] CLI installer not available in browser mode');
|
||||
},
|
||||
invalidateStatus: async (): Promise<void> => {},
|
||||
onProgress: (): (() => void) => {
|
||||
return () => {};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -272,11 +272,13 @@ export const CliStatusBanner = (): React.JSX.Element | null => {
|
|||
installerRawChunks,
|
||||
completedVersion,
|
||||
fetchCliStatus,
|
||||
invalidateCliStatus,
|
||||
installCli,
|
||||
isBusy,
|
||||
} = useCliInstaller();
|
||||
|
||||
const [showLoginTerminal, setShowLoginTerminal] = useState(false);
|
||||
const [isVerifyingAuth, setIsVerifyingAuth] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isElectron) return;
|
||||
|
|
@ -526,6 +528,22 @@ export const CliStatusBanner = (): React.JSX.Element | null => {
|
|||
|
||||
// Installed but not logged in — yellow warning banner
|
||||
if (cliStatus.installed && !cliStatus.authLoggedIn) {
|
||||
if (isVerifyingAuth) {
|
||||
return (
|
||||
<div
|
||||
className="mb-6 flex items-center gap-3 rounded-lg border-l-4 p-4"
|
||||
style={{
|
||||
borderColor: VARIANT_STYLES.info.border,
|
||||
backgroundColor: VARIANT_STYLES.info.bg,
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="size-4 animate-spin" style={{ color: 'var(--color-text-muted)' }} />
|
||||
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
|
||||
Verifying authentication...
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
|
@ -565,10 +583,26 @@ export const CliStatusBanner = (): React.JSX.Element | null => {
|
|||
args={['auth', 'login']}
|
||||
onClose={() => {
|
||||
setShowLoginTerminal(false);
|
||||
void fetchCliStatus();
|
||||
setIsVerifyingAuth(true);
|
||||
void (async () => {
|
||||
try {
|
||||
await invalidateCliStatus();
|
||||
await fetchCliStatus();
|
||||
} finally {
|
||||
setIsVerifyingAuth(false);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
onExit={() => {
|
||||
void fetchCliStatus();
|
||||
setIsVerifyingAuth(true);
|
||||
void (async () => {
|
||||
try {
|
||||
await invalidateCliStatus();
|
||||
await fetchCliStatus();
|
||||
} finally {
|
||||
setIsVerifyingAuth(false);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
autoCloseOnSuccessMs={4000}
|
||||
successMessage="Login complete"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export function useCliInstaller(): {
|
|||
installerRawChunks: string[];
|
||||
completedVersion: string | null;
|
||||
fetchCliStatus: () => Promise<void>;
|
||||
invalidateCliStatus: () => Promise<void>;
|
||||
installCli: () => void;
|
||||
isBusy: boolean;
|
||||
} {
|
||||
|
|
@ -44,6 +45,7 @@ export function useCliInstaller(): {
|
|||
const installerRawChunks = useStore((s) => s.cliInstallerRawChunks);
|
||||
const completedVersion = useStore((s) => s.cliCompletedVersion);
|
||||
const fetchCliStatus = useStore((s) => s.fetchCliStatus);
|
||||
const invalidateCliStatus = useStore((s) => s.invalidateCliStatus);
|
||||
const installCli = useStore((s) => s.installCli);
|
||||
|
||||
const isBusy = installerState !== 'idle' && installerState !== 'error';
|
||||
|
|
@ -61,6 +63,7 @@ export function useCliInstaller(): {
|
|||
installerRawChunks,
|
||||
completedVersion,
|
||||
fetchCliStatus,
|
||||
invalidateCliStatus,
|
||||
installCli,
|
||||
isBusy,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export interface CliInstallerSlice {
|
|||
|
||||
// Actions
|
||||
fetchCliStatus: () => Promise<void>;
|
||||
invalidateCliStatus: () => Promise<void>;
|
||||
installCli: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -90,6 +91,10 @@ export const createCliInstallerSlice: StateCreator<AppState, [], [], CliInstalle
|
|||
return cliStatusInFlight;
|
||||
},
|
||||
|
||||
invalidateCliStatus: async () => {
|
||||
await api.cliInstaller?.invalidateStatus();
|
||||
},
|
||||
|
||||
installCli: () => {
|
||||
set({
|
||||
cliInstallerState: 'checking',
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ export interface CliInstallerAPI {
|
|||
getStatus: () => Promise<CliInstallationStatus>;
|
||||
/** Start install/update flow. Progress sent via onProgress events. */
|
||||
install: () => Promise<void>;
|
||||
/** Invalidate cached status (forces fresh check on next getStatus) */
|
||||
invalidateStatus: () => Promise<void>;
|
||||
/** Subscribe to progress events. Returns cleanup function. */
|
||||
onProgress: (cb: (event: unknown, data: CliInstallerProgress) => void) => () => void;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue