fix: handle keychain hint and suppress pgrep spam

This commit is contained in:
Draculabo committed 2026-01-25 17:46:32 +08:00
1 parent d2945a6e8a
commit bd3d41aed1
5 files changed
+112 -7

No files matched your search

+4 -3
View File
@@ -41,6 +41,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { getLocalizedErrorMessage } from '@/utils/errorMessages';
// ... (existing code: imports and comments) // ... (existing code: imports and comments)
@@ -78,7 +79,7 @@ export function CloudAccountList() {
onError: (err) => { onError: (err) => {
toast({ toast({
title: t('cloud.toast.addFailed.title'), title: t('cloud.toast.addFailed.title'),
description: err.message, description: getLocalizedErrorMessage(err, t),
variant: 'destructive', variant: 'destructive',
}); });
}, },
@@ -133,7 +134,7 @@ export function CloudAccountList() {
onError: (err) => onError: (err) =>
toast({ toast({
title: t('cloud.toast.switchFailed'), title: t('cloud.toast.switchFailed'),
description: err.message, description: getLocalizedErrorMessage(err, t),
variant: 'destructive', variant: 'destructive',
}), }),
}, },
@@ -199,7 +200,7 @@ export function CloudAccountList() {
onError: (err) => { onError: (err) => {
toast({ toast({
title: t('cloud.toast.syncFailed.title'), title: t('cloud.toast.syncFailed.title'),
description: err.message, description: getLocalizedErrorMessage(err, t),
variant: 'destructive', variant: 'destructive',
}); });
}, },
+22 -2
View File
@@ -1,6 +1,6 @@
import { exec, execSync } from 'child_process'; import { exec, execSync } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import findProcess from 'find-process'; import findProcess, { ProcessInfo } from 'find-process';
import { getAntigravityExecutablePath, isWsl } from '../../utils/paths'; import { getAntigravityExecutablePath, isWsl } from '../../utils/paths';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';
@@ -51,6 +51,17 @@ function isHelperProcess(name: string, cmd: string): boolean {
return false; return false;
} }
function isPgrepNoMatchError(error: unknown): boolean {
if (!(error instanceof Error)) {
return false;
}
const message = error.message;
const hasPgrep = message.includes('pgrep -xi Antigravity');
const code = (error as { code?: number }).code;
return hasPgrep && code === 1;
}
/** /**
* Checks if the Antigravity process is running. * Checks if the Antigravity process is running.
* Uses find-process package for robust cross-platform process detection. * Uses find-process package for robust cross-platform process detection.
@@ -63,7 +74,16 @@ export async function isProcessRunning(): Promise<boolean> {
// Use find-process to search for Antigravity processes // Use find-process to search for Antigravity processes
// 'name' search type matches process name // 'name' search type matches process name
const processes = await findProcess('name', 'Antigravity', true); let processes: ProcessInfo[] = [];
try {
processes = await findProcess('name', 'Antigravity', true);
} catch (error) {
if (isPgrepNoMatchError(error)) {
logger.debug('No Antigravity process found (pgrep returned 1)');
return false;
}
throw error;
}
logger.debug(`Found ${processes.length} processes matching 'Antigravity'`); logger.debug(`Found ${processes.length} processes matching 'Antigravity'`);
+14
View File
@@ -34,6 +34,14 @@ i18n
}, },
error: { error: {
generic: 'An unexpected error occurred.', generic: 'An unexpected error occurred.',
keychainUnavailable: 'Keychain is unavailable.',
keychainHint: {
translocation:
'Detected macOS App Translocation. Move the app to /Applications and reopen.',
keychainDenied:
'Keychain access denied. The app may be unsigned; see README for the self-signing workaround.',
signNotarize: 'Please use a signed and notarized build when available.',
},
}, },
nav: { nav: {
accounts: 'Accounts', accounts: 'Accounts',
@@ -310,6 +318,12 @@ i18n
}, },
error: { error: {
generic: '发生未知错误。', generic: '发生未知错误。',
keychainUnavailable: 'Keychain 不可用。',
keychainHint: {
translocation: '检测到 macOS App Translocation。请将应用移动到 /Applications 后重试。',
keychainDenied: 'Keychain 访问被拒绝。应用可能未签名,可参考 README 的“macOS 自签”说明。',
signNotarize: '请在可用时使用已签名并公证的版本。',
},
}, },
nav: { nav: {
accounts: '账号', accounts: '账号',
+39
View File
@@ -0,0 +1,39 @@
import type { TFunction } from 'i18next';
const KEYCHAIN_ERROR_CODE = 'ERR_KEYCHAIN_UNAVAILABLE';
const KEYCHAIN_HINT_TRANSLOCATION = 'HINT_APP_TRANSLOCATION';
const KEYCHAIN_HINT_KEYCHAIN_DENIED = 'HINT_KEYCHAIN_DENIED';
const KEYCHAIN_HINT_SIGN_NOTARIZE = 'HINT_SIGN_NOTARIZE';
const KEYCHAIN_HINT_I18N_MAP: Record<string, string> = {
[KEYCHAIN_HINT_TRANSLOCATION]: 'error.keychainHint.translocation',
[KEYCHAIN_HINT_KEYCHAIN_DENIED]: 'error.keychainHint.keychainDenied',
[KEYCHAIN_HINT_SIGN_NOTARIZE]: 'error.keychainHint.signNotarize',
};
function resolveKeychainMessage(hintCode: string | undefined, t: TFunction): string {
const base = t('error.keychainUnavailable');
if (!hintCode) {
return base;
}
const hintKey = KEYCHAIN_HINT_I18N_MAP[hintCode];
if (!hintKey) {
return base;
}
return `${base} ${t(hintKey)}`;
}
export function getLocalizedErrorMessage(error: unknown, t: TFunction): string {
if (error instanceof Error) {
const rawMessage = error.message;
const [code, hint] = rawMessage.split('|');
if (code === KEYCHAIN_ERROR_CODE) {
return resolveKeychainMessage(hint, t);
}
return rawMessage;
}
return String(error);
}
+33 -2
View File
@@ -1,13 +1,43 @@
import keytar from 'keytar'; import keytar from 'keytar';
import crypto from 'crypto'; import crypto from 'crypto';
import { app } from 'electron';
import { logger } from './logger'; import { logger } from './logger';
const SERVICE_NAME = 'AntigravityManager'; const SERVICE_NAME = 'AntigravityManager';
const ACCOUNT_NAME = 'MasterKey'; const ACCOUNT_NAME = 'MasterKey';
const KEYCHAIN_ERROR_CODE = 'ERR_KEYCHAIN_UNAVAILABLE';
const KEYCHAIN_HINT_TRANSLOCATION = 'HINT_APP_TRANSLOCATION';
const KEYCHAIN_HINT_KEYCHAIN_DENIED = 'HINT_KEYCHAIN_DENIED';
const KEYCHAIN_HINT_SIGN_NOTARIZE = 'HINT_SIGN_NOTARIZE';
// Cache the key in memory to avoid frequent system calls // Cache the key in memory to avoid frequent system calls
let cachedMasterKey: Buffer | null = null; let cachedMasterKey: Buffer | null = null;
function buildKeychainAccessHint(error: unknown): string | null {
if (process.platform !== 'darwin') {
return null;
}
let appPath = '';
try {
appPath = app.getAppPath();
} catch {
appPath = '';
}
const isTranslocated = appPath.includes('/AppTranslocation/');
if (isTranslocated) {
return KEYCHAIN_HINT_TRANSLOCATION;
}
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.toLowerCase().includes('keychain')) {
return KEYCHAIN_HINT_KEYCHAIN_DENIED;
}
return KEYCHAIN_HINT_SIGN_NOTARIZE;
}
async function getOrGenerateMasterKey(): Promise<Buffer> { async function getOrGenerateMasterKey(): Promise<Buffer> {
if (cachedMasterKey) return cachedMasterKey; if (cachedMasterKey) return cachedMasterKey;
@@ -25,16 +55,17 @@ async function getOrGenerateMasterKey(): Promise<Buffer> {
cachedMasterKey = Buffer.from(hexKey, 'hex'); cachedMasterKey = Buffer.from(hexKey, 'hex');
return cachedMasterKey; return cachedMasterKey;
} catch (error) { } catch (error) {
const hint = buildKeychainAccessHint(error);
logger.error('Security: Failed to access keychain/credential manager', error); logger.error('Security: Failed to access keychain/credential manager', error);
// Fallback? If we can't store the key, we can't persistently encrypt. // Fallback? If we can't store the key, we can't persistently encrypt.
// For now, throw to prevent data loss (better not to write than to write something we can't decrypt later or write plain text when promised encrypted) // For now, throw to prevent data loss (better not to write than to write something we can't decrypt later or write plain text when promised encrypted)
throw new Error('Key management system unavailable'); const message = hint ? `${KEYCHAIN_ERROR_CODE}|${hint}` : KEYCHAIN_ERROR_CODE;
throw new Error(message);
} }
} }
const ALGORITHM = 'aes-256-gcm'; const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16; const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
/** /**
* Encrypts a string using AES-256-GCM. * Encrypts a string using AES-256-GCM.