refactor: standardize code formatting and linting configuration

- Replace .eslintrc.js with .eslintrc.cjs for better ES module compatibility
- Add .prettierrc configuration for consistent code formatting
- Update package.json with new lint and format scripts
- Add nodemon.json for development hot reloading configuration
- Standardize code formatting across all JavaScript and Vue files
- Update web admin SPA with improved linting rules and formatting
- Add prettier configuration to web admin SPA

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
千羽
2025-08-07 18:19:31 +09:00
parent 4a0eba117c
commit 8a74bf5afe
124 changed files with 20878 additions and 18757 deletions

View File

@@ -3,27 +3,27 @@
* 基于claude-code-login.js中的OAuth流程实现
*/
const crypto = require('crypto');
const { SocksProxyAgent } = require('socks-proxy-agent');
const { HttpsProxyAgent } = require('https-proxy-agent');
const axios = require('axios');
const logger = require('./logger');
const crypto = require('crypto')
const { SocksProxyAgent } = require('socks-proxy-agent')
const { HttpsProxyAgent } = require('https-proxy-agent')
const axios = require('axios')
const logger = require('./logger')
// OAuth 配置常量 - 从claude-code-login.js提取
const OAUTH_CONFIG = {
AUTHORIZE_URL: 'https://claude.ai/oauth/authorize',
TOKEN_URL: 'https://console.anthropic.com/v1/oauth/token',
CLIENT_ID: '9d1c250a-e61b-44d9-88ed-5944d1962f5e',
REDIRECT_URI: 'https://console.anthropic.com/oauth/code/callback',
SCOPES: 'org:create_api_key user:profile user:inference'
};
AUTHORIZE_URL: 'https://claude.ai/oauth/authorize',
TOKEN_URL: 'https://console.anthropic.com/v1/oauth/token',
CLIENT_ID: '9d1c250a-e61b-44d9-88ed-5944d1962f5e',
REDIRECT_URI: 'https://console.anthropic.com/oauth/code/callback',
SCOPES: 'org:create_api_key user:profile user:inference'
}
/**
* 生成随机的 state 参数
* @returns {string} 随机生成的 state (64字符hex)
*/
function generateState() {
return crypto.randomBytes(32).toString('hex');
return crypto.randomBytes(32).toString('hex')
}
/**
@@ -31,7 +31,7 @@ function generateState() {
* @returns {string} base64url 编码的随机字符串
*/
function generateCodeVerifier() {
return crypto.randomBytes(32).toString('base64url');
return crypto.randomBytes(32).toString('base64url')
}
/**
@@ -40,9 +40,7 @@ function generateCodeVerifier() {
* @returns {string} SHA256 哈希后的 base64url 编码字符串
*/
function generateCodeChallenge(codeVerifier) {
return crypto.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
return crypto.createHash('sha256').update(codeVerifier).digest('base64url')
}
/**
@@ -52,18 +50,18 @@ function generateCodeChallenge(codeVerifier) {
* @returns {string} 完整的授权 URL
*/
function generateAuthUrl(codeChallenge, state) {
const params = new URLSearchParams({
code: 'true',
client_id: OAUTH_CONFIG.CLIENT_ID,
response_type: 'code',
redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
scope: OAUTH_CONFIG.SCOPES,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
state: state
});
const params = new URLSearchParams({
code: 'true',
client_id: OAUTH_CONFIG.CLIENT_ID,
response_type: 'code',
redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
scope: OAUTH_CONFIG.SCOPES,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
state
})
return `${OAUTH_CONFIG.AUTHORIZE_URL}?${params.toString()}`;
return `${OAUTH_CONFIG.AUTHORIZE_URL}?${params.toString()}`
}
/**
@@ -71,18 +69,18 @@ function generateAuthUrl(codeChallenge, state) {
* @returns {{authUrl: string, codeVerifier: string, state: string, codeChallenge: string}}
*/
function generateOAuthParams() {
const state = generateState();
const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);
const authUrl = generateAuthUrl(codeChallenge, state);
return {
authUrl,
codeVerifier,
state,
codeChallenge
};
const state = generateState()
const codeVerifier = generateCodeVerifier()
const codeChallenge = generateCodeChallenge(codeVerifier)
const authUrl = generateAuthUrl(codeChallenge, state)
return {
authUrl,
codeVerifier,
state,
codeChallenge
}
}
/**
@@ -91,25 +89,31 @@ function generateOAuthParams() {
* @returns {object|null} 代理agent或null
*/
function createProxyAgent(proxyConfig) {
if (!proxyConfig) {
return null;
}
if (!proxyConfig) {
return null
}
try {
if (proxyConfig.type === 'socks5') {
const auth = proxyConfig.username && proxyConfig.password ? `${proxyConfig.username}:${proxyConfig.password}@` : '';
const socksUrl = `socks5://${auth}${proxyConfig.host}:${proxyConfig.port}`;
return new SocksProxyAgent(socksUrl);
} else if (proxyConfig.type === 'http' || proxyConfig.type === 'https') {
const auth = proxyConfig.username && proxyConfig.password ? `${proxyConfig.username}:${proxyConfig.password}@` : '';
const httpUrl = `${proxyConfig.type}://${auth}${proxyConfig.host}:${proxyConfig.port}`;
return new HttpsProxyAgent(httpUrl);
}
} catch (error) {
console.warn('⚠️ Invalid proxy configuration:', error);
try {
if (proxyConfig.type === 'socks5') {
const auth =
proxyConfig.username && proxyConfig.password
? `${proxyConfig.username}:${proxyConfig.password}@`
: ''
const socksUrl = `socks5://${auth}${proxyConfig.host}:${proxyConfig.port}`
return new SocksProxyAgent(socksUrl)
} else if (proxyConfig.type === 'http' || proxyConfig.type === 'https') {
const auth =
proxyConfig.username && proxyConfig.password
? `${proxyConfig.username}:${proxyConfig.password}@`
: ''
const httpUrl = `${proxyConfig.type}://${auth}${proxyConfig.host}:${proxyConfig.port}`
return new HttpsProxyAgent(httpUrl)
}
} catch (error) {
console.warn('⚠️ Invalid proxy configuration:', error)
}
return null;
return null
}
/**
@@ -121,110 +125,110 @@ function createProxyAgent(proxyConfig) {
* @returns {Promise<object>} Claude格式的token响应
*/
async function exchangeCodeForTokens(authorizationCode, codeVerifier, state, proxyConfig = null) {
// 清理授权码移除URL片段
const cleanedCode = authorizationCode.split('#')[0]?.split('&')[0] ?? authorizationCode;
const params = {
grant_type: 'authorization_code',
client_id: OAUTH_CONFIG.CLIENT_ID,
code: cleanedCode,
redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
code_verifier: codeVerifier,
state: state
};
// 清理授权码移除URL片段
const cleanedCode = authorizationCode.split('#')[0]?.split('&')[0] ?? authorizationCode
// 创建代理agent
const agent = createProxyAgent(proxyConfig);
const params = {
grant_type: 'authorization_code',
client_id: OAUTH_CONFIG.CLIENT_ID,
code: cleanedCode,
redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
code_verifier: codeVerifier,
state
}
try {
logger.debug('🔄 Attempting OAuth token exchange', {
url: OAUTH_CONFIG.TOKEN_URL,
codeLength: cleanedCode.length,
codePrefix: cleanedCode.substring(0, 10) + '...',
hasProxy: !!proxyConfig,
proxyType: proxyConfig?.type || 'none'
});
// 创建代理agent
const agent = createProxyAgent(proxyConfig)
const response = await axios.post(OAUTH_CONFIG.TOKEN_URL, params, {
headers: {
'Content-Type': 'application/json',
'User-Agent': 'claude-cli/1.0.56 (external, cli)',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://claude.ai/',
'Origin': 'https://claude.ai'
},
httpsAgent: agent,
timeout: 30000
});
try {
logger.debug('🔄 Attempting OAuth token exchange', {
url: OAUTH_CONFIG.TOKEN_URL,
codeLength: cleanedCode.length,
codePrefix: `${cleanedCode.substring(0, 10)}...`,
hasProxy: !!proxyConfig,
proxyType: proxyConfig?.type || 'none'
})
logger.success('✅ OAuth token exchange successful', {
status: response.status,
hasAccessToken: !!response.data?.access_token,
hasRefreshToken: !!response.data?.refresh_token,
scopes: response.data?.scope
});
const response = await axios.post(OAUTH_CONFIG.TOKEN_URL, params, {
headers: {
'Content-Type': 'application/json',
'User-Agent': 'claude-cli/1.0.56 (external, cli)',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.9',
Referer: 'https://claude.ai/',
Origin: 'https://claude.ai'
},
httpsAgent: agent,
timeout: 30000
})
const data = response.data;
// 返回Claude格式的token数据
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresAt: (Math.floor(Date.now() / 1000) + data.expires_in) * 1000,
scopes: data.scope ? data.scope.split(' ') : ['user:inference', 'user:profile'],
isMax: true
};
} catch (error) {
// 处理axios错误响应
if (error.response) {
// 服务器返回了错误状态码
const status = error.response.status;
const errorData = error.response.data;
logger.error('❌ OAuth token exchange failed with server error', {
status: status,
statusText: error.response.statusText,
headers: error.response.headers,
data: errorData,
codeLength: cleanedCode.length,
codePrefix: cleanedCode.substring(0, 10) + '...'
});
// 尝试从错误响应中提取有用信息
let errorMessage = `HTTP ${status}`;
if (errorData) {
if (typeof errorData === 'string') {
errorMessage += `: ${errorData}`;
} else if (errorData.error) {
errorMessage += `: ${errorData.error}`;
if (errorData.error_description) {
errorMessage += ` - ${errorData.error_description}`;
}
} else {
errorMessage += `: ${JSON.stringify(errorData)}`;
}
}
throw new Error(`Token exchange failed: ${errorMessage}`);
} else if (error.request) {
// 请求被发送但没有收到响应
logger.error('❌ OAuth token exchange failed with network error', {
message: error.message,
code: error.code,
hasProxy: !!proxyConfig
});
throw new Error('Token exchange failed: No response from server (network error or timeout)');
} else {
// 其他错误
logger.error('❌ OAuth token exchange failed with unknown error', {
message: error.message,
stack: error.stack
});
throw new Error(`Token exchange failed: ${error.message}`);
}
logger.success('✅ OAuth token exchange successful', {
status: response.status,
hasAccessToken: !!response.data?.access_token,
hasRefreshToken: !!response.data?.refresh_token,
scopes: response.data?.scope
})
const { data } = response
// 返回Claude格式的token数据
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresAt: (Math.floor(Date.now() / 1000) + data.expires_in) * 1000,
scopes: data.scope ? data.scope.split(' ') : ['user:inference', 'user:profile'],
isMax: true
}
} catch (error) {
// 处理axios错误响应
if (error.response) {
// 服务器返回了错误状态码
const { status } = error.response
const errorData = error.response.data
logger.error('❌ OAuth token exchange failed with server error', {
status,
statusText: error.response.statusText,
headers: error.response.headers,
data: errorData,
codeLength: cleanedCode.length,
codePrefix: `${cleanedCode.substring(0, 10)}...`
})
// 尝试从错误响应中提取有用信息
let errorMessage = `HTTP ${status}`
if (errorData) {
if (typeof errorData === 'string') {
errorMessage += `: ${errorData}`
} else if (errorData.error) {
errorMessage += `: ${errorData.error}`
if (errorData.error_description) {
errorMessage += ` - ${errorData.error_description}`
}
} else {
errorMessage += `: ${JSON.stringify(errorData)}`
}
}
throw new Error(`Token exchange failed: ${errorMessage}`)
} else if (error.request) {
// 请求被发送但没有收到响应
logger.error('❌ OAuth token exchange failed with network error', {
message: error.message,
code: error.code,
hasProxy: !!proxyConfig
})
throw new Error('Token exchange failed: No response from server (network error or timeout)')
} else {
// 其他错误
logger.error('❌ OAuth token exchange failed with unknown error', {
message: error.message,
stack: error.stack
})
throw new Error(`Token exchange failed: ${error.message}`)
}
}
}
/**
@@ -233,47 +237,47 @@ async function exchangeCodeForTokens(authorizationCode, codeVerifier, state, pro
* @returns {string} 授权码
*/
function parseCallbackUrl(input) {
if (!input || typeof input !== 'string') {
throw new Error('请提供有效的授权码或回调 URL');
}
if (!input || typeof input !== 'string') {
throw new Error('请提供有效的授权码或回调 URL')
}
const trimmedInput = input.trim();
// 情况1: 尝试作为完整URL解析
if (trimmedInput.startsWith('http://') || trimmedInput.startsWith('https://')) {
try {
const urlObj = new URL(trimmedInput);
const authorizationCode = urlObj.searchParams.get('code');
const trimmedInput = input.trim()
if (!authorizationCode) {
throw new Error('回调 URL 中未找到授权码 (code 参数)');
}
// 情况1: 尝试作为完整URL解析
if (trimmedInput.startsWith('http://') || trimmedInput.startsWith('https://')) {
try {
const urlObj = new URL(trimmedInput)
const authorizationCode = urlObj.searchParams.get('code')
return authorizationCode;
} catch (error) {
if (error.message.includes('回调 URL 中未找到授权码')) {
throw error;
}
throw new Error('无效的 URL 格式,请检查回调 URL 是否正确');
}
if (!authorizationCode) {
throw new Error('回调 URL 中未找到授权码 (code 参数)')
}
return authorizationCode
} catch (error) {
if (error.message.includes('回调 URL 中未找到授权码')) {
throw error
}
throw new Error('无效的 URL 格式,请检查回调 URL 是否正确')
}
// 情况2: 直接的授权码可能包含URL fragments
// 参考claude-code-login.js的处理方式移除URL fragments和参数
const cleanedCode = trimmedInput.split('#')[0]?.split('&')[0] ?? trimmedInput;
// 验证授权码格式Claude的授权码通常是base64url格式
if (!cleanedCode || cleanedCode.length < 10) {
throw new Error('授权码格式无效,请确保复制了完整的 Authorization Code');
}
// 基本格式验证:授权码应该只包含字母、数字、下划线、连字符
const validCodePattern = /^[A-Za-z0-9_-]+$/;
if (!validCodePattern.test(cleanedCode)) {
throw new Error('授权码包含无效字符,请检查是否复制了正确的 Authorization Code');
}
return cleanedCode;
}
// 情况2: 直接的授权码(可能包含URL fragments
// 参考claude-code-login.js的处理方式移除URL fragments和参数
const cleanedCode = trimmedInput.split('#')[0]?.split('&')[0] ?? trimmedInput
// 验证授权码格式Claude的授权码通常是base64url格式
if (!cleanedCode || cleanedCode.length < 10) {
throw new Error('授权码格式无效,请确保复制了完整的 Authorization Code')
}
// 基本格式验证:授权码应该只包含字母、数字、下划线、连字符
const validCodePattern = /^[A-Za-z0-9_-]+$/
if (!validCodePattern.test(cleanedCode)) {
throw new Error('授权码包含无效字符,请检查是否复制了正确的 Authorization Code')
}
return cleanedCode
}
/**
@@ -282,26 +286,26 @@ function parseCallbackUrl(input) {
* @returns {object} claudeAiOauth格式的数据
*/
function formatClaudeCredentials(tokenData) {
return {
claudeAiOauth: {
accessToken: tokenData.accessToken,
refreshToken: tokenData.refreshToken,
expiresAt: tokenData.expiresAt,
scopes: tokenData.scopes,
isMax: tokenData.isMax
}
};
return {
claudeAiOauth: {
accessToken: tokenData.accessToken,
refreshToken: tokenData.refreshToken,
expiresAt: tokenData.expiresAt,
scopes: tokenData.scopes,
isMax: tokenData.isMax
}
}
}
module.exports = {
OAUTH_CONFIG,
generateOAuthParams,
exchangeCodeForTokens,
parseCallbackUrl,
formatClaudeCredentials,
generateState,
generateCodeVerifier,
generateCodeChallenge,
generateAuthUrl,
createProxyAgent
};
OAUTH_CONFIG,
generateOAuthParams,
exchangeCodeForTokens,
parseCallbackUrl,
formatClaudeCredentials,
generateState,
generateCodeVerifier,
generateCodeChallenge,
generateAuthUrl,
createProxyAgent
}