| const redis = require('../models/redis') |
| const logger = require('../utils/logger') |
| const { v4: uuidv4 } = require('uuid') |
|
|
| |
| |
| |
| |
| class TokenRefreshService { |
| constructor() { |
| this.lockTTL = 60 |
| this.lockValue = new Map() |
| } |
|
|
| |
| |
| |
| |
| async acquireLock(lockKey) { |
| try { |
| const client = redis.getClientSafe() |
| const lockId = uuidv4() |
| const result = await client.set(lockKey, lockId, 'NX', 'EX', this.lockTTL) |
|
|
| if (result === 'OK') { |
| this.lockValue.set(lockKey, lockId) |
| logger.debug(`🔒 Acquired lock ${lockKey} with ID ${lockId}, TTL: ${this.lockTTL}s`) |
| return true |
| } |
| return false |
| } catch (error) { |
| logger.error(`Failed to acquire lock ${lockKey}:`, error) |
| return false |
| } |
| } |
|
|
| |
| |
| |
| |
| async releaseLock(lockKey) { |
| try { |
| const client = redis.getClientSafe() |
| const lockId = this.lockValue.get(lockKey) |
|
|
| if (!lockId) { |
| logger.warn(`⚠️ No lock ID found for ${lockKey}, skipping release`) |
| return |
| } |
|
|
| |
| const luaScript = ` |
| if redis.call("get", KEYS[1]) == ARGV[1] then |
| return redis.call("del", KEYS[1]) |
| else |
| return 0 |
| end |
| ` |
|
|
| const result = await client.eval(luaScript, 1, lockKey, lockId) |
|
|
| if (result === 1) { |
| this.lockValue.delete(lockKey) |
| logger.debug(`🔓 Released lock ${lockKey} with ID ${lockId}`) |
| } else { |
| logger.warn(`⚠️ Lock ${lockKey} was not released - value mismatch or already expired`) |
| } |
| } catch (error) { |
| logger.error(`Failed to release lock ${lockKey}:`, error) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async acquireRefreshLock(accountId, platform = 'claude') { |
| const lockKey = `token_refresh_lock:${platform}:${accountId}` |
| return await this.acquireLock(lockKey) |
| } |
|
|
| |
| |
| |
| |
| |
| async releaseRefreshLock(accountId, platform = 'claude') { |
| const lockKey = `token_refresh_lock:${platform}:${accountId}` |
| await this.releaseLock(lockKey) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async isRefreshLocked(accountId, platform = 'claude') { |
| const lockKey = `token_refresh_lock:${platform}:${accountId}` |
| try { |
| const client = redis.getClientSafe() |
| const exists = await client.exists(lockKey) |
| return exists === 1 |
| } catch (error) { |
| logger.error(`Failed to check lock status ${lockKey}:`, error) |
| return false |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async getLockTTL(accountId, platform = 'claude') { |
| const lockKey = `token_refresh_lock:${platform}:${accountId}` |
| try { |
| const client = redis.getClientSafe() |
| const ttl = await client.ttl(lockKey) |
| return ttl |
| } catch (error) { |
| logger.error(`Failed to get lock TTL ${lockKey}:`, error) |
| return -1 |
| } |
| } |
|
|
| |
| |
| |
| |
| cleanup() { |
| this.lockValue.clear() |
| logger.info('🧹 Cleaned up local lock records') |
| } |
| } |
|
|
| |
| const tokenRefreshService = new TokenRefreshService() |
|
|
| module.exports = tokenRefreshService |
|
|