| |
| |
| |
| |
|
|
| const redis = require('../models/redis') |
| const logger = require('../utils/logger') |
|
|
| class ClaudeCodeHeadersService { |
| constructor() { |
| this.defaultHeaders = { |
| 'x-stainless-retry-count': '0', |
| 'x-stainless-timeout': '60', |
| 'x-stainless-lang': 'js', |
| 'x-stainless-package-version': '0.55.1', |
| 'x-stainless-os': 'Windows', |
| 'x-stainless-arch': 'x64', |
| 'x-stainless-runtime': 'node', |
| 'x-stainless-runtime-version': 'v20.19.2', |
| 'anthropic-dangerous-direct-browser-access': 'true', |
| 'x-app': 'cli', |
| 'user-agent': 'claude-cli/1.0.57 (external, cli)', |
| 'accept-language': '*', |
| 'sec-fetch-mode': 'cors' |
| } |
|
|
| |
| this.claudeCodeHeaderKeys = [ |
| 'x-stainless-retry-count', |
| 'x-stainless-timeout', |
| 'x-stainless-lang', |
| 'x-stainless-package-version', |
| 'x-stainless-os', |
| 'x-stainless-arch', |
| 'x-stainless-runtime', |
| 'x-stainless-runtime-version', |
| 'anthropic-dangerous-direct-browser-access', |
| 'x-app', |
| 'user-agent', |
| 'accept-language', |
| 'sec-fetch-mode', |
| 'accept-encoding' |
| ] |
| } |
|
|
| |
| |
| |
| extractVersionFromUserAgent(userAgent) { |
| if (!userAgent) { |
| return null |
| } |
| const match = userAgent.match(/claude-cli\/([\d.]+(?:[a-zA-Z0-9-]*)?)/i) |
| return match ? match[1] : null |
| } |
|
|
| |
| |
| |
| |
| compareVersions(v1, v2) { |
| if (!v1 || !v2) { |
| return 0 |
| } |
|
|
| const parts1 = v1.split('.').map(Number) |
| const parts2 = v2.split('.').map(Number) |
|
|
| for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { |
| const p1 = parts1[i] || 0 |
| const p2 = parts2[i] || 0 |
|
|
| if (p1 > p2) { |
| return 1 |
| } |
| if (p1 < p2) { |
| return -1 |
| } |
| } |
|
|
| return 0 |
| } |
|
|
| |
| |
| |
| extractClaudeCodeHeaders(clientHeaders) { |
| const headers = {} |
|
|
| |
| const lowerCaseHeaders = {} |
| Object.keys(clientHeaders || {}).forEach((key) => { |
| lowerCaseHeaders[key.toLowerCase()] = clientHeaders[key] |
| }) |
|
|
| |
| this.claudeCodeHeaderKeys.forEach((key) => { |
| const lowerKey = key.toLowerCase() |
| if (lowerCaseHeaders[lowerKey]) { |
| headers[key] = lowerCaseHeaders[lowerKey] |
| } |
| }) |
|
|
| return headers |
| } |
|
|
| |
| |
| |
| async storeAccountHeaders(accountId, clientHeaders) { |
| try { |
| const extractedHeaders = this.extractClaudeCodeHeaders(clientHeaders) |
|
|
| |
| const userAgent = extractedHeaders['user-agent'] |
| if (!userAgent || !/^claude-cli\/[\d.]+\s+\(/i.test(userAgent)) { |
| |
| return |
| } |
|
|
| const version = this.extractVersionFromUserAgent(userAgent) |
| if (!version) { |
| logger.warn(`⚠️ Failed to extract version from user-agent: ${userAgent}`) |
| return |
| } |
|
|
| |
| const key = `claude_code_headers:${accountId}` |
| const currentData = await redis.getClient().get(key) |
|
|
| if (currentData) { |
| const current = JSON.parse(currentData) |
| const currentVersion = this.extractVersionFromUserAgent(current.headers['user-agent']) |
|
|
| |
| if (this.compareVersions(version, currentVersion) <= 0) { |
| return |
| } |
| } |
|
|
| |
| const data = { |
| headers: extractedHeaders, |
| version, |
| updatedAt: new Date().toISOString() |
| } |
|
|
| await redis.getClient().setex(key, 86400 * 7, JSON.stringify(data)) |
|
|
| logger.info(`✅ Stored Claude Code headers for account ${accountId}, version: ${version}`) |
| } catch (error) { |
| logger.error(`❌ Failed to store Claude Code headers for account ${accountId}:`, error) |
| } |
| } |
|
|
| |
| |
| |
| async getAccountHeaders(accountId) { |
| try { |
| const key = `claude_code_headers:${accountId}` |
| const data = await redis.getClient().get(key) |
|
|
| if (data) { |
| const parsed = JSON.parse(data) |
| logger.debug( |
| `📋 Retrieved Claude Code headers for account ${accountId}, version: ${parsed.version}` |
| ) |
| return parsed.headers |
| } |
|
|
| |
| logger.debug(`📋 Using default Claude Code headers for account ${accountId}`) |
| return this.defaultHeaders |
| } catch (error) { |
| logger.error(`❌ Failed to get Claude Code headers for account ${accountId}:`, error) |
| return this.defaultHeaders |
| } |
| } |
|
|
| |
| |
| |
| async clearAccountHeaders(accountId) { |
| try { |
| const key = `claude_code_headers:${accountId}` |
| await redis.getClient().del(key) |
| logger.info(`🗑️ Cleared Claude Code headers for account ${accountId}`) |
| } catch (error) { |
| logger.error(`❌ Failed to clear Claude Code headers for account ${accountId}:`, error) |
| } |
| } |
|
|
| |
| |
| |
| async getAllAccountHeaders() { |
| try { |
| const pattern = 'claude_code_headers:*' |
| const keys = await redis.getClient().keys(pattern) |
|
|
| const results = {} |
| for (const key of keys) { |
| const accountId = key.replace('claude_code_headers:', '') |
| const data = await redis.getClient().get(key) |
| if (data) { |
| results[accountId] = JSON.parse(data) |
| } |
| } |
|
|
| return results |
| } catch (error) { |
| logger.error('❌ Failed to get all account headers:', error) |
| return {} |
| } |
| } |
| } |
|
|
| module.exports = new ClaudeCodeHeadersService() |
|
|