antigravity2api2 / src /auth /token_manager.js
lin7zhi's picture
Upload folder using huggingface_hub
97ec0e5 verified
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import axios from 'axios';
import { log } from '../utils/logger.js';
import { generateSessionId, generateProjectId } from '../utils/idGenerator.js';
import config from '../config/config.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
const CLIENT_SECRET = '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';
class TokenManager {
constructor(filePath = path.join(__dirname,'..','..','data' ,'accounts.json')) {
this.filePath = filePath;
this.tokens = [];
this.currentIndex = 0;
this.ensureFileExists();
this.initialize();
}
ensureFileExists() {
const dir = path.dirname(this.filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (!fs.existsSync(this.filePath)) {
fs.writeFileSync(this.filePath, '[]', 'utf8');
log.info('✓ 已创建账号配置文件');
}
}
async initialize() {
try {
log.info('正在初始化token管理器...');
const data = fs.readFileSync(this.filePath, 'utf8');
let tokenArray = JSON.parse(data);
this.tokens = tokenArray.filter(token => token.enable !== false).map(token => ({
...token,
sessionId: generateSessionId()
}));
this.currentIndex = 0;
if (this.tokens.length === 0) {
log.warn('⚠ 暂无可用账号,请使用以下方式添加:');
log.warn(' 方式1: 运行 npm run login 命令登录');
log.warn(' 方式2: 访问前端管理页面添加账号');
} else {
log.info(`成功加载 ${this.tokens.length} 个可用token`);
}
} catch (error) {
log.error('初始化token失败:', error.message);
this.tokens = [];
}
}
async fetchProjectId(token) {
const response = await axios({
method: 'POST',
url: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist',
headers: {
'Host': 'daily-cloudcode-pa.sandbox.googleapis.com',
'User-Agent': 'antigravity/1.11.9 windows/amd64',
'Authorization': `Bearer ${token.access_token}`,
'Content-Type': 'application/json',
'Accept-Encoding': 'gzip'
},
data: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),
timeout: config.timeout,
proxy: config.proxy ? (() => {
const proxyUrl = new URL(config.proxy);
return { protocol: proxyUrl.protocol.replace(':', ''), host: proxyUrl.hostname, port: parseInt(proxyUrl.port) };
})() : false
});
return response.data?.cloudaicompanionProject;
}
isExpired(token) {
if (!token.timestamp || !token.expires_in) return true;
const expiresAt = token.timestamp + (token.expires_in * 1000);
return Date.now() >= expiresAt - 300000;
}
async refreshToken(token) {
log.info('正在刷新token...');
const body = new URLSearchParams({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'refresh_token',
refresh_token: token.refresh_token
});
try {
const response = await axios({
method: 'POST',
url: 'https://oauth2.googleapis.com/token',
headers: {
'Host': 'oauth2.googleapis.com',
'User-Agent': 'Go-http-client/1.1',
'Content-Type': 'application/x-www-form-urlencoded',
'Accept-Encoding': 'gzip'
},
data: body.toString(),
timeout: config.timeout,
proxy: config.proxy ? (() => {
const proxyUrl = new URL(config.proxy);
return { protocol: proxyUrl.protocol.replace(':', ''), host: proxyUrl.hostname, port: parseInt(proxyUrl.port) };
})() : false
});
token.access_token = response.data.access_token;
token.expires_in = response.data.expires_in;
token.timestamp = Date.now();
this.saveToFile();
return token;
} catch (error) {
throw { statusCode: error.response?.status, message: error.response?.data || error.message };
}
}
saveToFile() {
try {
const data = fs.readFileSync(this.filePath, 'utf8');
const allTokens = JSON.parse(data);
this.tokens.forEach(memToken => {
const index = allTokens.findIndex(t => t.refresh_token === memToken.refresh_token);
if (index !== -1) {
const { sessionId, ...tokenToSave } = memToken;
allTokens[index] = tokenToSave;
}
});
fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8');
} catch (error) {
log.error('保存文件失败:', error.message);
}
}
disableToken(token) {
log.warn(`禁用token ...${token.access_token.slice(-8)}`)
token.enable = false;
this.saveToFile();
this.tokens = this.tokens.filter(t => t.refresh_token !== token.refresh_token);
this.currentIndex = this.currentIndex % Math.max(this.tokens.length, 1);
}
async getToken() {
if (this.tokens.length === 0) return null;
//const startIndex = this.currentIndex;
const totalTokens = this.tokens.length;
for (let i = 0; i < totalTokens; i++) {
const token = this.tokens[this.currentIndex];
try {
if (this.isExpired(token)) {
await this.refreshToken(token);
}
if (!token.projectId) {
if (config.skipProjectIdFetch) {
token.projectId = generateProjectId();
this.saveToFile();
log.info(`...${token.access_token.slice(-8)}: 使用随机生成的projectId: ${token.projectId}`);
} else {
try {
const projectId = await this.fetchProjectId(token);
if (projectId === undefined) {
log.warn(`...${token.access_token.slice(-8)}: 无资格获取projectId,跳过保存`);
this.disableToken(token);
if (this.tokens.length === 0) return null;
continue;
}
token.projectId = projectId;
this.saveToFile();
} catch (error) {
log.error(`...${token.access_token.slice(-8)}: 获取projectId失败:`, error.message);
this.currentIndex = (this.currentIndex + 1) % this.tokens.length;
continue;
}
}
}
this.currentIndex = (this.currentIndex + 1) % this.tokens.length;
return token;
} catch (error) {
if (error.statusCode === 403 || error.statusCode === 400) {
log.warn(`...${token.access_token.slice(-8)}: Token 已失效或错误,已自动禁用该账号`);
this.disableToken(token);
if (this.tokens.length === 0) return null;
} else {
log.error(`...${token.access_token.slice(-8)} 刷新失败:`, error.message);
this.currentIndex = (this.currentIndex + 1) % this.tokens.length;
}
}
}
return null;
}
disableCurrentToken(token) {
const found = this.tokens.find(t => t.access_token === token.access_token);
if (found) {
this.disableToken(found);
}
}
// API管理方法
async reload() {
await this.initialize();
log.info('Token已热重载');
}
addToken(tokenData) {
try {
this.ensureFileExists();
const data = fs.readFileSync(this.filePath, 'utf8');
const allTokens = JSON.parse(data);
const newToken = {
access_token: tokenData.access_token,
refresh_token: tokenData.refresh_token,
expires_in: tokenData.expires_in || 3599,
timestamp: tokenData.timestamp || Date.now(),
enable: tokenData.enable !== undefined ? tokenData.enable : true
};
if (tokenData.projectId) {
newToken.projectId = tokenData.projectId;
}
allTokens.push(newToken);
fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8');
this.reload();
return { success: true, message: 'Token添加成功' };
} catch (error) {
log.error('添加Token失败:', error.message);
return { success: false, message: error.message };
}
}
updateToken(refreshToken, updates) {
try {
this.ensureFileExists();
const data = fs.readFileSync(this.filePath, 'utf8');
const allTokens = JSON.parse(data);
const index = allTokens.findIndex(t => t.refresh_token === refreshToken);
if (index === -1) {
return { success: false, message: 'Token不存在' };
}
allTokens[index] = { ...allTokens[index], ...updates };
fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8');
this.reload();
return { success: true, message: 'Token更新成功' };
} catch (error) {
log.error('更新Token失败:', error.message);
return { success: false, message: error.message };
}
}
deleteToken(refreshToken) {
try {
this.ensureFileExists();
const data = fs.readFileSync(this.filePath, 'utf8');
const allTokens = JSON.parse(data);
const filteredTokens = allTokens.filter(t => t.refresh_token !== refreshToken);
if (filteredTokens.length === allTokens.length) {
return { success: false, message: 'Token不存在' };
}
fs.writeFileSync(this.filePath, JSON.stringify(filteredTokens, null, 2), 'utf8');
this.reload();
return { success: true, message: 'Token删除成功' };
} catch (error) {
log.error('删除Token失败:', error.message);
return { success: false, message: error.message };
}
}
getTokenList() {
try {
this.ensureFileExists();
const data = fs.readFileSync(this.filePath, 'utf8');
const allTokens = JSON.parse(data);
return allTokens.map(token => ({
refresh_token: token.refresh_token,
access_token_suffix: token.access_token ? `...${token.access_token.slice(-8)}` : 'N/A',
expires_in: token.expires_in,
timestamp: token.timestamp,
enable: token.enable !== false,
projectId: token.projectId || null
}));
} catch (error) {
log.error('获取Token列表失败:', error.message);
return [];
}
}
}
const tokenManager = new TokenManager();
export default tokenManager;