Compare commits

..
2 Commits
Author SHA1 Message Date
semantic-release-bot 995d565912 chore(release): 0.3.4-beta.1 [skip ci]
## [0.3.4-beta.1](https://github.com/Draculabo/AntigravityManager/compare/v0.3.3...v0.3.4-beta.1) (2026-01-25)

### 🐛 Bug Fixes

* **security:** add safeStorage fallback for production builds ([#38](https://github.com/Draculabo/AntigravityManager/issues/38)) ([92dc2f6](https://github.com/Draculabo/AntigravityManager/commit/92dc2f6f2169eb1a32950694387f2333ea2de682))
2026-01-25 18:04:50 +00:00
Erdogan AbacıandDraculabo 92dc2f6f21 fix(security): add safeStorage fallback for production builds (#38)
* fix(security): add safeStorage fallback for production builds

- Replace hard keytar dependency with multi-strategy key storage:
  1. safeStorage (Electron built-in) - preferred
  2. keytar (system keychain) - fallback
  3. File-based encryption - last resort
- Dynamically import keytar to avoid hard failure in production
- Refactor cloudHandler to use const instead of let (Clean Code)
- Fix 'Failed to load cloud accounts' error in packaged app

The keytar native module fails to load from asar archives in
production builds. This fix ensures the app works even when
system keychain access is unavailable.

* refactor(security): address code review feedback

- Replace sync file ops with async (fs/promises)
- Add atomic file writes to prevent data races
- Implement concurrency lock for key generation
- Add SecurityError class with specific error codes:
  - KEY_STORAGE_FAILED, ENCRYPTION_FAILED, DECRYPTION_FAILED
  - INVALID_FORMAT, AUTH_TAG_MISMATCH, CORRUPTED_DATA
- Enhance error categorization in decrypt() for better diagnostics
- Add security warnings for file-based fallback mode

* fix: align error handling after merge

* feat: migrate legacy encrypted account data

* chore: enable beta prerelease workflow *skip release*

---------

Co-authored-by: Draculabo <[email protected]>
2026-01-26 01:53:25 +08:00
11 changed files with 234 additions and 44 deletions

No files matched your search

-20
View File
@@ -2,26 +2,6 @@
# Changelog
## [0.3.5](https://github.com/Draculabo/AntigravityManager/compare/v0.3.4...v0.3.5) (2026-01-26)
### 🐛 Bug Fixes
* "Check Quota Now" button not refreshing UI after polling ([#42](https://github.com/Draculabo/AntigravityManager/issues/42)) ([e959ee3](https://github.com/Draculabo/AntigravityManager/commit/e959ee346e7c26a8a4c5b7deefa5bd2452153f9d))
### 📝 Documentation
* remove beta download links from README ([5a21680](https://github.com/Draculabo/AntigravityManager/commit/5a2168030eac4ddeffa1c3b002b2de48b6a11a8f))
## [0.3.4](https://github.com/Draculabo/AntigravityManager/compare/v0.3.3...v0.3.4) (2026-01-26)
### 🐛 Bug Fixes
* **security:** add safeStorage fallback for production builds ([#38](https://github.com/Draculabo/AntigravityManager/issues/38)) ([#43](https://github.com/Draculabo/AntigravityManager/issues/43)) ([0208058](https://github.com/Draculabo/AntigravityManager/commit/02080588b764ed88a5831152a3a1249f1d077d29))
### 📝 Documentation
* update beta release link ([d5ee08d](https://github.com/Draculabo/AntigravityManager/commit/d5ee08d5a06a915a8b82f680b38e2f532105498c))
## [0.3.4-beta.1](https://github.com/Draculabo/AntigravityManager/compare/v0.3.3...v0.3.4-beta.1) (2026-01-25)
### 🐛 Bug Fixes
+7
View File
@@ -33,6 +33,13 @@
</a>
</p>
<p align="center">
<strong>🧪 Beta:</strong>
<a href="https://github.com/Draculabo/AntigravityManager/releases/tag/v0.3.3-beta.1">
Download pre-release v0.3.3-beta.1
</a>
</p>
<p align="center">
<img src="https://img.shields.io/badge/Electron-191970?style=for-the-badge&logo=Electron&logoColor=white" alt="Electron" />
<img src="https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB" alt="React" />
+7
View File
@@ -33,6 +33,13 @@
</a>
</p>
<p align="center">
<strong>🧪 内测版:</strong>
<a href="https://github.com/Draculabo/AntigravityManager/releases/tag/v0.3.3-beta.1">
下载 pre-release v0.3.3-beta.1
</a>
</p>
<p align="center">
<img src="https://img.shields.io/badge/Electron-191970?style=for-the-badge&logo=Electron&logoColor=white" alt="Electron" />
<img src="https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB" alt="React" />
+216 -2
View File
@@ -1,12 +1,12 @@
{
"name": "antigravity-manager",
"version": "0.3.5",
"version": "0.3.4-beta.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "antigravity-manager",
"version": "0.3.5",
"version": "0.3.4-beta.1",
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@grpc/grpc-js": "^1.14.3",
@@ -4191,6 +4191,27 @@
}
}
},
"node_modules/@nestjs/platform-socket.io": {
"version": "11.1.11",
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.11.tgz",
"integrity": "sha512-0z6pLg9CuTXtz7q2lRZoPOU94DN28OTa39f4cQrlZysKA6QrKM7w7z6xqb4g32qjF+LQHFNRmMJtE/pLrxBaig==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"socket.io": "4.8.3",
"tslib": "2.8.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nest"
},
"peerDependencies": {
"@nestjs/common": "^11.0.0",
"@nestjs/websockets": "^11.0.0",
"rxjs": "^7.1.0"
}
},
"node_modules/@nestjs/websockets": {
"version": "11.1.11",
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.11.tgz",
@@ -8120,6 +8141,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
@@ -9407,6 +9436,17 @@
"assertion-error": "^2.0.1"
}
},
"node_modules/@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
@@ -10240,6 +10280,32 @@
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==",
"license": "MIT"
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/accepts/node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -10825,6 +10891,17 @@
],
"license": "MIT"
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.32",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz",
@@ -11060,6 +11137,22 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/bufferutil": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz",
"integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -13394,6 +13487,50 @@
"once": "^1.4.0"
}
},
"node_modules/engine.io": {
"version": "6.6.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz",
"integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.18.3"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/enhanced-resolve": {
"version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
@@ -19084,6 +19221,20 @@
"webidl-conversions": "^3.0.0"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -24806,6 +24957,53 @@
"npm": ">= 3.0.0"
}
},
"node_modules/socket.io": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz",
"integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.4.1",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz",
"integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"debug": "~4.4.1",
"ws": "~8.18.3"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
"integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socks": {
"version": "2.8.7",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
@@ -26755,6 +26953,22 @@
"node": ">=8"
}
},
"node_modules/utf-8-validate": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz",
"integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "antigravity-manager",
"productName": "Antigravity Manager",
"version": "0.3.5",
"version": "0.3.4-beta.1",
"description": "Antigravity Manager - Electron App",
"main": ".vite/build/main.js",
"private": true,
-7
View File
@@ -176,15 +176,8 @@ export function CloudAccountList() {
};
const handleForcePoll = () => {
if (forcePollMutation.isPending) return;
forcePollMutation.mutate(undefined, {
onSuccess: () => toast({ title: t('cloud.polling') }),
onError: (err) =>
toast({
title: t('cloud.toast.pollFailed'),
description: getLocalizedErrorMessage(err, t),
variant: 'destructive',
}),
});
};
+2 -6
View File
@@ -47,12 +47,12 @@ export function useRefreshQuota() {
return useMutation({
mutationFn: refreshAccountQuota,
onSuccess: (updatedAccount: CloudAccount) => {
// Optimistically update
// Optimistically update the list or just invalidate?
// Invalidating is safer as sorting might change (last_used)
queryClient.setQueryData(QUERY_KEYS.cloudAccounts, (oldData: CloudAccount[] | undefined) => {
if (!oldData) return [updatedAccount];
return oldData.map((acc) => (acc.id === updatedAccount.id ? updatedAccount : acc));
});
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.cloudAccounts });
},
});
}
@@ -97,12 +97,8 @@ export function useSetAutoSwitchEnabled() {
}
export function useForcePollCloudMonitor() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: forcePollCloudMonitor,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.cloudAccounts });
},
});
}
-4
View File
@@ -123,8 +123,6 @@ export async function refreshAccountQuota(accountId: string): Promise<CloudAccou
const quota = await GoogleAPIService.fetchQuota(account.token.access_token);
account.quota = quota;
await CloudAccountRepo.updateQuota(account.id, quota);
await CloudAccountRepo.updateLastUsed(account.id);
account.last_used = Math.floor(Date.now() / 1000); // Sync memory object
notifyTrayUpdate(account);
return account;
} catch (error: any) {
@@ -145,8 +143,6 @@ export async function refreshAccountQuota(accountId: string): Promise<CloudAccou
const quota = await GoogleAPIService.fetchQuota(account.token.access_token);
account.quota = quota;
await CloudAccountRepo.updateQuota(account.id, quota);
await CloudAccountRepo.updateLastUsed(account.id);
account.last_used = Math.floor(Date.now() / 1000); // Sync memory object
return account;
} catch (refreshError) {
logger.error(
+1 -1
View File
@@ -78,7 +78,7 @@ function createIPCClient(port: MessagePort) {
pendingRequests.delete(id);
reject(new Error(`Request /${methodPath} timed out`));
}
}, 60000);
}, 10000);
});
}
-2
View File
@@ -245,7 +245,6 @@ i18n
},
quotaRefreshed: 'Quota refreshed',
refreshFailed: 'Failed to refresh quota',
pollFailed: 'Failed to poll quota for all accounts',
switched: {
title: 'Account switched!',
description: 'Restarting Antigravity...',
@@ -534,7 +533,6 @@ i18n
},
quotaRefreshed: '配额已刷新',
refreshFailed: '刷新配额失败',
pollFailed: '轮询所有账号配额失败',
switched: {
title: '账号已切换!',
description: '正在重启 Antigravity...',
-1
View File
@@ -53,7 +53,6 @@ export class CloudMonitorService {
// 3. Update DB
await CloudAccountRepo.updateQuota(account.id, quota);
await CloudAccountRepo.updateLastUsed(account.id);
} catch (error) {
logger.error(`Monitor: Failed to update ${account.email}`, error);
// Could mark status as 'error' or 'rate_limited' if 429