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 { 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',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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'`);
|
||||||
|
|
||||||
|
|||||||
@@ -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: '账号',
|
||||||
|
|||||||
@@ -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 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.
|
||||||
|
|||||||
Reference in new issue
Block a user