Compare commits
11
Commits
v0.3.0-rc.3
...
v0.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad39a735b5 | ||
|
|
6a07bc0a10 | ||
|
|
e91457eceb | ||
|
|
01e3f8f8fd | ||
|
|
bd3d41aed1 | ||
|
|
d2945a6e8a | ||
|
|
89f39710c7 | ||
|
|
1bce5d331d | ||
|
|
47be175e27 | ||
|
|
f0718db374 | ||
|
|
4b12f1220d |
No files matched your search
@@ -1,9 +1,8 @@
|
||||
name: Publish Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Semantic Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Required for semantic-release to analyze commits
|
||||
persist-credentials: false # We use a token in the next steps
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} # Use GH_TOKEN to trigger other workflows
|
||||
run: npx semantic-release
|
||||
+49
-16
@@ -1,3 +1,36 @@
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
## [0.3.2](https://github.com/Draculabo/AntigravityManager/compare/v0.3.1...v0.3.2) (2026-01-25)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* handle keychain hint and suppress pgrep spam ([bd3d41a](https://github.com/Draculabo/AntigravityManager/commit/bd3d41aed17bafe9d684c5c421bad8b90afa19a8))
|
||||
|
||||
### 📝 Documentation
|
||||
|
||||
* add macOS self-signing workaround for Keychain issues ([01e3f8f](https://github.com/Draculabo/AntigravityManager/commit/01e3f8f8fd6dacc5eed214ed4b505d6d85f4bcff))
|
||||
|
||||
### 🔧 Continuous Integration
|
||||
|
||||
* setup semantic release configuration and github actions workflow ([d2945a6](https://github.com/Draculabo/AntigravityManager/commit/d2945a6e8a14d75f577716183cdff093443d9636))
|
||||
* trigger publish on release published event ([6a07bc0](https://github.com/Draculabo/AntigravityManager/commit/6a07bc0a10a5ad802777e007cfd7390852119b15))
|
||||
|
||||
## [0.3.2](https://github.com/Draculabo/AntigravityManager/compare/v0.3.1...v0.3.2) (2026-01-25)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* handle keychain hint and suppress pgrep spam ([bd3d41a](https://github.com/Draculabo/AntigravityManager/commit/bd3d41aed17bafe9d684c5c421bad8b90afa19a8))
|
||||
|
||||
### 📝 Documentation
|
||||
|
||||
* add macOS self-signing workaround for Keychain issues ([01e3f8f](https://github.com/Draculabo/AntigravityManager/commit/01e3f8f8fd6dacc5eed214ed4b505d6d85f4bcff))
|
||||
|
||||
### 🔧 Continuous Integration
|
||||
|
||||
* setup semantic release configuration and github actions workflow ([d2945a6](https://github.com/Draculabo/AntigravityManager/commit/d2945a6e8a14d75f577716183cdff093443d9636))
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
@@ -5,30 +38,30 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.3.0-rc.3] - 2026-01-21
|
||||
## [0.3.1] - 2026-01-25
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed startup race condition causing cloud accounts verify failure ([f0718db]
|
||||
- Enabled WAL mode and force initialization on startup to resolve process resource contention ([1bce5d3])
|
||||
|
||||
## [0.3.0] - 2026-01-23
|
||||
|
||||
### New Features
|
||||
|
||||
- Verify Google OAuth code automatically after receipt
|
||||
- Add button to open logs folder
|
||||
- Add expiration warning for Google OAuth authentication
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed `state.vscdb` path on Linux to include `User/globalStorage` subdirectory (Fixed [#26](https://github.com/Draculabo/AntigravityManager/issues/26))
|
||||
- Improved process detection on macOS/Linux using `find-process` to reliably identify the main application and exclude helper processes(Fixed [#27](https://github.com/Draculabo/AntigravityManager/issues/27))
|
||||
|
||||
## [0.3.0-rc.2] - 2026-01-21
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Improved process detection on macOS/Linux using `find-process` to reliably identify the main application and exclude helper processes (Fixed [#27](https://github.com/Draculabo/AntigravityManager/issues/27))
|
||||
- Fixed keychain access error on macOS Apple Silicon (M1/M2/M3) by adding arm64 build to CI
|
||||
|
||||
## [0.3.0-rc.1] - 2026-01-21
|
||||
|
||||
### New Features
|
||||
|
||||
- Verify Google OAuth code automatically after receipt ([e927ac9]
|
||||
- Add button to open logs folder ([a438070]
|
||||
- Add expiration warning for Google OAuth authentication ([f224fea]
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Add VS Code settings for auto-formatting and ESLint ([013f329])
|
||||
- Add VS Code settings for auto-formatting and ESLint
|
||||
|
||||
## [0.2.2] - 2026-01-19
|
||||
|
||||
|
||||
@@ -280,6 +280,24 @@ Please check:
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: macOS shows Keychain/Credential error and OAuth cannot be saved?</b></summary>
|
||||
|
||||
This is a common macOS security behavior, usually when the app is unsigned or run directly from Downloads.
|
||||
This is a **temporary workaround** for personal use:
|
||||
|
||||
1. Move the app to `/Applications`
|
||||
2. Run the following commands in Terminal (repeat after every update)
|
||||
|
||||
```plaintext
|
||||
sudo xattr -dr com.apple.quarantine "/Applications/Antigravity Manager 2.app"
|
||||
codesign --force --deep --sign - "/Applications/Antigravity Manager 2.app"
|
||||
```
|
||||
|
||||
Reopen the app and allow Keychain access if prompted.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: How to report issues or suggestions?</b></summary>
|
||||
|
||||
|
||||
@@ -280,6 +280,24 @@ AntigravityManager/
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: macOS 上提示 Keychain/凭据管理不可用,OAuth 无法保存?</b></summary>
|
||||
|
||||
这是 macOS 安全机制导致的常见问题,通常出现在未签名的应用或从 Downloads 直接运行的场景。
|
||||
这是**临时方案**,仅适合个人设备:
|
||||
|
||||
1. 将应用移动到 `/Applications`
|
||||
2. 打开终端执行以下命令(每次更新后都需要重新执行)
|
||||
|
||||
```plaintext
|
||||
sudo xattr -dr com.apple.quarantine "/Applications/Antigravity Manager 2.app"
|
||||
codesign --force --deep --sign - "/Applications/Antigravity Manager 2.app"
|
||||
```
|
||||
|
||||
重新打开应用后,Keychain 应会提示授权(可选“始终允许”)。
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: 如何反馈问题或建议?</b></summary>
|
||||
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ const config: ForgeConfig = {
|
||||
name: 'AntigravityManager',
|
||||
},
|
||||
draft: true,
|
||||
prerelease: true,
|
||||
prerelease: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Generated
+5903
-2
File diff suppressed because it is too large.
Load diff
+7
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "antigravity-manager",
|
||||
"productName": "Antigravity Manager",
|
||||
"version": "0.3.0-rc.3",
|
||||
"version": "0.3.2",
|
||||
"description": "Antigravity Manager - Electron App",
|
||||
"main": ".vite/build/main.js",
|
||||
"private": true,
|
||||
@@ -38,6 +38,9 @@
|
||||
"@eslint/compat": "^1.4.1",
|
||||
"@eslint/js": "^9.39.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/github": "^12.0.2",
|
||||
"@swc/core": "^1.15.8",
|
||||
"@tanstack/router-plugin": "^1.135.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
@@ -53,6 +56,7 @@
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"code-inspector-plugin": "^1.3.3",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
"electron": "^37.3.1",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"electron-playwright-helpers": "^1.8.2",
|
||||
@@ -67,6 +71,8 @@
|
||||
"node-addon-api": "^8.3.1",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||
"semantic-release": "^25.0.2",
|
||||
"semantic-release-config-gitmoji": "^1.5.3",
|
||||
"sharp": "^0.34.5",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Semantic Release Configuration
|
||||
*
|
||||
* This configuration uses the specific release rules requested by the user,
|
||||
* but adapts the plugin configuration to work with standard Conventional Commits
|
||||
* instead of Gitmoji (since the project history doesn't use emojis).
|
||||
*/
|
||||
|
||||
const releaseRules = [
|
||||
{
|
||||
release: 'minor',
|
||||
type: 'feat',
|
||||
},
|
||||
{
|
||||
release: 'patch',
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
release: 'patch',
|
||||
type: 'perf',
|
||||
},
|
||||
{
|
||||
release: 'patch',
|
||||
type: 'style',
|
||||
},
|
||||
{
|
||||
release: 'patch',
|
||||
type: 'refactor',
|
||||
},
|
||||
{
|
||||
release: 'patch',
|
||||
type: 'build',
|
||||
},
|
||||
{ release: 'patch', scope: 'README', type: 'docs' },
|
||||
{ release: 'patch', scope: 'README.md', type: 'docs' },
|
||||
{ release: false, type: 'docs' },
|
||||
{
|
||||
release: false,
|
||||
type: 'test',
|
||||
},
|
||||
{
|
||||
release: false,
|
||||
type: 'ci',
|
||||
},
|
||||
{
|
||||
release: false,
|
||||
type: 'chore',
|
||||
},
|
||||
{
|
||||
release: false,
|
||||
type: 'wip',
|
||||
},
|
||||
{
|
||||
release: 'major',
|
||||
type: 'BREAKING CHANGE',
|
||||
},
|
||||
{
|
||||
release: 'major',
|
||||
scope: 'BREAKING CHANGE',
|
||||
},
|
||||
{
|
||||
release: 'major',
|
||||
subject: '*BREAKING CHANGE*',
|
||||
},
|
||||
{ release: 'patch', subject: '*force release*' },
|
||||
{ release: 'patch', subject: '*force patch*' },
|
||||
{ release: 'minor', subject: '*force minor*' },
|
||||
{ release: 'major', subject: '*force major*' },
|
||||
{ release: false, subject: '*skip release*' },
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
branches: ['main', 'master'],
|
||||
plugins: [
|
||||
[
|
||||
'@semantic-release/commit-analyzer',
|
||||
{
|
||||
preset: 'conventionalcommits',
|
||||
releaseRules: releaseRules,
|
||||
},
|
||||
],
|
||||
[
|
||||
'@semantic-release/release-notes-generator',
|
||||
{
|
||||
preset: 'conventionalcommits',
|
||||
presetConfig: {
|
||||
types: [
|
||||
{ type: 'feat', section: '✨ Features' },
|
||||
{ type: 'fix', section: '🐛 Bug Fixes' },
|
||||
{ type: 'perf', section: '⚡ Performance Improvements' },
|
||||
{ type: 'revert', section: '⏪ Reverts' },
|
||||
{ type: 'docs', section: '📝 Documentation' },
|
||||
{ type: 'style', section: '💄 Styles' },
|
||||
{ type: 'refactor', section: '♻️ Code Refactoring' },
|
||||
{ type: 'test', section: '✅ Tests' },
|
||||
{ type: 'build', section: '👷 Build System' },
|
||||
{ type: 'ci', section: '🔧 Continuous Integration' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'@semantic-release/changelog',
|
||||
{
|
||||
changelogFile: 'CHANGELOG.md',
|
||||
changelogTitle: '<a name="readme-top"></a>\n\n# Changelog',
|
||||
},
|
||||
],
|
||||
'@semantic-release/npm', // Updates package.json and npm-shrinkwrap.json
|
||||
[
|
||||
'@semantic-release/github',
|
||||
{
|
||||
successComment: false,
|
||||
failComment: false,
|
||||
labels: false,
|
||||
releaseName: 'v${nextRelease.version}',
|
||||
},
|
||||
],
|
||||
[
|
||||
'@semantic-release/git',
|
||||
{
|
||||
assets: ['CHANGELOG.md', 'package.json', 'package-lock.json', 'npm-shrinkwrap.json'],
|
||||
message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
@@ -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',
|
||||
});
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ function ensureDatabaseInitialized(dbPath: string): void {
|
||||
let db: Database.Database | null = null;
|
||||
try {
|
||||
db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
|
||||
// Create accounts table
|
||||
// Storing complex objects (token, quota) as JSON strings for simplicity
|
||||
@@ -73,7 +74,9 @@ function ensureDatabaseInitialized(dbPath: string): void {
|
||||
function getDb(): Database.Database {
|
||||
const dbPath = getCloudAccountsDbPath();
|
||||
ensureDatabaseInitialized(dbPath);
|
||||
return new Database(dbPath);
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
return db;
|
||||
}
|
||||
|
||||
export class CloudAccountRepo {
|
||||
@@ -307,6 +310,7 @@ export class CloudAccountRepo {
|
||||
}
|
||||
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
try {
|
||||
const row = db
|
||||
.prepare('SELECT value FROM ItemTable WHERE key = ?')
|
||||
@@ -454,6 +458,7 @@ export class CloudAccountRepo {
|
||||
|
||||
logger.info(`SyncLocal: Using Antigravity database at: ${dbPath}`);
|
||||
const ideDb = new Database(dbPath, { readonly: true });
|
||||
ideDb.pragma('journal_mode = WAL');
|
||||
try {
|
||||
// 1. Read Raw Token Data
|
||||
const row = ideDb
|
||||
|
||||
@@ -8,6 +8,31 @@ import path from 'path';
|
||||
|
||||
const KEYS_TO_BACKUP = ['antigravityAuthStatus', 'jetskiStateSync.agentManagerInitState'];
|
||||
|
||||
function getDbWithWal(dbPath: string, options: Database.Options = {}): Database.Database {
|
||||
const db = new Database(dbPath, options);
|
||||
db.pragma('journal_mode = WAL');
|
||||
return db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the database and ensures WAL mode is enabled.
|
||||
* Should be called on application startup.
|
||||
*/
|
||||
export function initDatabase(): void {
|
||||
try {
|
||||
const dbPaths = getAntigravityDbPaths();
|
||||
if (dbPaths.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const db = getDatabaseConnection();
|
||||
db.close();
|
||||
logger.info('Database initialized and verified (WAL mode)');
|
||||
} catch (error) {
|
||||
logger.error('Failed to initialize database on startup', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the database file exists.
|
||||
* @param dbPath {string} The path to the database file.
|
||||
@@ -59,7 +84,7 @@ export function getDatabaseConnection(dbPath?: string): Database.Database {
|
||||
ensureDatabaseExists(targetPath);
|
||||
|
||||
try {
|
||||
return new Database(targetPath, { readonly: false, fileMustExist: false });
|
||||
return getDbWithWal(targetPath, { readonly: false, fileMustExist: false });
|
||||
} catch (error: unknown) {
|
||||
const err = error as { code?: string };
|
||||
if (err.code === 'SQLITE_BUSY' || err.code === 'SQLITE_LOCKED') {
|
||||
@@ -69,6 +94,12 @@ export function getDatabaseConnection(dbPath?: string): Database.Database {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a database connection and enables WAL mode.
|
||||
* @param dbPath {string} The path to the database file.
|
||||
* @returns {Database.Database} The database connection.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the current account info.
|
||||
* @returns {AccountInfo} The current account info.
|
||||
|
||||
@@ -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: '账号',
|
||||
|
||||
+15
-4
@@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, Tray, Menu, nativeImage } from 'electron';
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { IPC_CHANNELS } from './constants';
|
||||
import { updateElectronApp, UpdateSourceType } from 'update-electron-app';
|
||||
import { logger } from './utils/logger';
|
||||
import { CloudAccountRepo } from './ipc/database/cloudHandler';
|
||||
import { initDatabase } from './ipc/database/handler';
|
||||
import { CloudMonitorService } from './services/CloudMonitorService';
|
||||
|
||||
// Static Imports to fix Bundle Resolution Errors
|
||||
@@ -223,6 +224,19 @@ process.on('unhandledRejection', (reason) => {
|
||||
|
||||
app
|
||||
.whenReady()
|
||||
.then(async () => {
|
||||
logger.info('Step: Initialize CloudAccountRepo');
|
||||
try {
|
||||
await CloudAccountRepo.init();
|
||||
} catch (e) {
|
||||
logger.error('Startup: Failed to initialize CloudAccountRepo', e);
|
||||
// We might want to exit here or show a dialog, but for now we proceed
|
||||
// though functionality will be broken.
|
||||
}
|
||||
|
||||
logger.info('Step: Initialize Antigravity DB (WAL Mode)');
|
||||
initDatabase();
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('Step: setupORPC');
|
||||
return setupORPC();
|
||||
@@ -242,9 +256,6 @@ app
|
||||
.then(async () => {
|
||||
// Initialize Cloud Monitor if enabled
|
||||
try {
|
||||
// Initialize DB & Migrate to Encrypted Storage
|
||||
await CloudAccountRepo.init();
|
||||
|
||||
// Start OAuth Server
|
||||
AuthServer.start();
|
||||
|
||||
|
||||
@@ -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