fix: handle keychain hint and suppress pgrep spam
This commit is contained in:
1 parent
d2945a6e8a
commit
bd3d41aed1
5 files changed
+112
-7
No files matched your search
@@ -41,6 +41,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getLocalizedErrorMessage } from '@/utils/errorMessages';
|
||||
|
||||
// ... (existing code: imports and comments)
|
||||
|
||||
@@ -78,7 +79,7 @@ export function CloudAccountList() {
|
||||
onError: (err) => {
|
||||
toast({
|
||||
title: t('cloud.toast.addFailed.title'),
|
||||
description: err.message,
|
||||
description: getLocalizedErrorMessage(err, t),
|
||||
variant: 'destructive',
|
||||
});
|
||||
},
|
||||
@@ -133,7 +134,7 @@ export function CloudAccountList() {
|
||||
onError: (err) =>
|
||||
toast({
|
||||
title: t('cloud.toast.switchFailed'),
|
||||
description: err.message,
|
||||
description: getLocalizedErrorMessage(err, t),
|
||||
variant: 'destructive',
|
||||
}),
|
||||
},
|
||||
@@ -199,7 +200,7 @@ export function CloudAccountList() {
|
||||
onError: (err) => {
|
||||
toast({
|
||||
title: t('cloud.toast.syncFailed.title'),
|
||||
description: err.message,
|
||||
description: getLocalizedErrorMessage(err, t),
|
||||
variant: 'destructive',
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { exec, execSync } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import findProcess from 'find-process';
|
||||
import findProcess, { ProcessInfo } from 'find-process';
|
||||
import { getAntigravityExecutablePath, isWsl } from '../../utils/paths';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
@@ -51,6 +51,17 @@ function isHelperProcess(name: string, cmd: string): boolean {
|
||||
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.
|
||||
* 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
|
||||
// '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'`);
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@ i18n
|
||||
},
|
||||
error: {
|
||||
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: {
|
||||
accounts: 'Accounts',
|
||||
@@ -310,6 +318,12 @@ i18n
|
||||
},
|
||||
error: {
|
||||
generic: '发生未知错误。',
|
||||
keychainUnavailable: 'Keychain 不可用。',
|
||||
keychainHint: {
|
||||
translocation: '检测到 macOS App Translocation。请将应用移动到 /Applications 后重试。',
|
||||
keychainDenied: 'Keychain 访问被拒绝。应用可能未签名,可参考 README 的“macOS 自签”说明。',
|
||||
signNotarize: '请在可用时使用已签名并公证的版本。',
|
||||
},
|
||||
},
|
||||
nav: {
|
||||
accounts: '账号',
|
||||
|
||||
@@ -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
@@ -1,13 +1,43 @@
|
||||
import keytar from 'keytar';
|
||||
import crypto from 'crypto';
|
||||
import { app } from 'electron';
|
||||
import { logger } from './logger';
|
||||
|
||||
const SERVICE_NAME = 'AntigravityManager';
|
||||
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
|
||||
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> {
|
||||
if (cachedMasterKey) return cachedMasterKey;
|
||||
|
||||
@@ -25,16 +55,17 @@ async function getOrGenerateMasterKey(): Promise<Buffer> {
|
||||
cachedMasterKey = Buffer.from(hexKey, 'hex');
|
||||
return cachedMasterKey;
|
||||
} catch (error) {
|
||||
const hint = buildKeychainAccessHint(error);
|
||||
logger.error('Security: Failed to access keychain/credential manager', error);
|
||||
// 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)
|
||||
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 IV_LENGTH = 16;
|
||||
const AUTH_TAG_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* Encrypts a string using AES-256-GCM.
|
||||
|
||||
Reference in new issue
Block a user