refactor: remove all token caching — always fetch fresh session
Every call now hits /auth/skill-credit/session directly. Deletes cache.ts, removes CachedTokenData type, strips cacheDir/minTtlSec options. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f7f385321f
commit
153b05414e
37
src/auth.ts
37
src/auth.ts
|
|
@ -1,6 +1,5 @@
|
|||
import type { ApiResponse, EnvConfig, HttpMethod, SessionResponse } from './types.js';
|
||||
import { requestApi } from './http.js';
|
||||
import { getCacheFile, readCachedToken, writeCache, deleteCache } from './cache.js';
|
||||
|
||||
const SESSION_RETRYABLE_STATUS = new Set([401, 403]);
|
||||
const SESSION_RETRYABLE_BODY_MARKERS = [
|
||||
|
|
@ -18,8 +17,6 @@ export function createEnvConfig(): EnvConfig {
|
|||
return {
|
||||
authBase: (process.env.AUTH_BASE || 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''),
|
||||
clientKey: process.env.CLIENT_KEY || '',
|
||||
authCacheDir: process.env.AUTH_CACHE_DIR || '/tmp/skill-auth-cache',
|
||||
authMinTtlSec: parseInt(process.env.AUTH_MIN_TTL_SEC || '60', 10),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -65,53 +62,23 @@ export async function fetchSessionJson(
|
|||
}
|
||||
|
||||
/**
|
||||
* Get access token with caching
|
||||
* Get access token (always fetches fresh)
|
||||
*/
|
||||
export async function getAccessToken(
|
||||
dryRun: boolean,
|
||||
config: EnvConfig
|
||||
): Promise<string> {
|
||||
if (dryRun) {
|
||||
return '<dry-run-token>';
|
||||
}
|
||||
|
||||
if (!config.clientKey) {
|
||||
throw new Error('CLIENT_KEY is required');
|
||||
}
|
||||
|
||||
const cacheFile = getCacheFile(config.authBase, config.clientKey, config.authCacheDir);
|
||||
const cachedToken = readCachedToken(cacheFile, config.authMinTtlSec);
|
||||
|
||||
if (cachedToken) {
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
const session = await fetchSessionJson(dryRun, config);
|
||||
writeCache(cacheFile, session);
|
||||
|
||||
return session.accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh access token (bypass cache)
|
||||
* Refresh access token (same as getAccessToken — no cache to clear)
|
||||
*/
|
||||
export async function refreshAccessToken(
|
||||
dryRun: boolean,
|
||||
config: EnvConfig
|
||||
): Promise<string> {
|
||||
if (dryRun) {
|
||||
return '<dry-run-token>';
|
||||
}
|
||||
|
||||
if (!config.clientKey) {
|
||||
throw new Error('CLIENT_KEY is required');
|
||||
}
|
||||
|
||||
const cacheFile = getCacheFile(config.authBase, config.clientKey, config.authCacheDir);
|
||||
|
||||
// Remove cache file if exists
|
||||
deleteCache(cacheFile);
|
||||
|
||||
return getAccessToken(dryRun, config);
|
||||
}
|
||||
|
||||
|
|
|
|||
83
src/cache.ts
83
src/cache.ts
|
|
@ -1,83 +0,0 @@
|
|||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import type { CachedTokenData } from './types.js';
|
||||
|
||||
/**
|
||||
* Generate SHA256 hash for cache key
|
||||
*/
|
||||
export function sha256(input: string): string {
|
||||
return crypto.createHash('sha256').update(input).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache file path for current AUTH_BASE and CLIENT_KEY
|
||||
*/
|
||||
export function getCacheFile(authBase: string, clientKey: string, cacheDir: string): string {
|
||||
const key = sha256(`${authBase}|${clientKey}`);
|
||||
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir, { recursive: true });
|
||||
}
|
||||
|
||||
return path.join(cacheDir, `session_${key}.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read cached token if valid
|
||||
*/
|
||||
export function readCachedToken(
|
||||
cacheFile: string,
|
||||
minTtlSec: number
|
||||
): string | null {
|
||||
if (!fs.existsSync(cacheFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(cacheFile, 'utf-8')) as CachedTokenData;
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
|
||||
if (!data.accessToken || data.expiresAtEpoch <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if token is still valid (with min TTL buffer)
|
||||
if (now + minTtlSec >= data.expiresAtEpoch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.accessToken;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write token to cache
|
||||
*/
|
||||
export function writeCache(
|
||||
cacheFile: string,
|
||||
sessionJson: { accessToken: string; expiresIn: number }
|
||||
): void {
|
||||
const nowEpoch = Math.floor(Date.now() / 1000);
|
||||
const expiresIn = sessionJson.expiresIn > 0 ? sessionJson.expiresIn : 900;
|
||||
const expiresAtEpoch = nowEpoch + expiresIn;
|
||||
|
||||
const cacheData: CachedTokenData = {
|
||||
accessToken: sessionJson.accessToken,
|
||||
expiresAtEpoch,
|
||||
createdAtEpoch: nowEpoch,
|
||||
};
|
||||
|
||||
fs.writeFileSync(cacheFile, JSON.stringify(cacheData, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache file if exists
|
||||
*/
|
||||
export function deleteCache(cacheFile: string): void {
|
||||
if (fs.existsSync(cacheFile)) {
|
||||
fs.unlinkSync(cacheFile);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import type { ApiResponse, ClientConfig, EnvConfig, HttpMethod, SessionResponse } from './types.js';
|
||||
import { requestApi } from './http.js';
|
||||
import { getCacheFile, readCachedToken, writeCache, deleteCache } from './cache.js';
|
||||
import { loadGlobalEnv } from './env.js';
|
||||
|
||||
const SESSION_RETRYABLE_STATUS = new Set([401, 403]);
|
||||
|
|
@ -21,10 +20,6 @@ export interface SkillClientOptions {
|
|||
apiBase?: string;
|
||||
/** Dry run mode — no real HTTP calls */
|
||||
dryRun?: boolean;
|
||||
/** Cache directory (default: /tmp/skill-auth-cache) */
|
||||
cacheDir?: string;
|
||||
/** Min TTL before token refresh, seconds (default: 60) */
|
||||
minTtlSec?: number;
|
||||
}
|
||||
|
||||
function buildConfig(options: SkillClientOptions): EnvConfig {
|
||||
|
|
@ -32,8 +27,6 @@ function buildConfig(options: SkillClientOptions): EnvConfig {
|
|||
return {
|
||||
authBase: (options.authBase || process.env.AUTH_BASE || 'https://api-gw-test.yuanwei-lnc.com').replace(/\/$/, ''),
|
||||
clientKey: options.clientKey || process.env.CLIENT_KEY || '',
|
||||
authCacheDir: options.cacheDir || process.env.AUTH_CACHE_DIR || '/tmp/skill-auth-cache',
|
||||
authMinTtlSec: options.minTtlSec ?? parseInt(process.env.AUTH_MIN_TTL_SEC || '60', 10),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -118,37 +111,18 @@ export class SkillClient {
|
|||
const url = `${this.apiBase}${path}`;
|
||||
const bodyStr = body != null ? JSON.stringify(body) : undefined;
|
||||
|
||||
const token = await this.getToken();
|
||||
const token = (await this.fetchSession()).accessToken;
|
||||
const first = await requestApi(method, url, token, bodyStr);
|
||||
|
||||
if (!isRetryable(first)) {
|
||||
return first;
|
||||
}
|
||||
|
||||
// Token expired — refresh and retry once
|
||||
const freshToken = await this.refreshToken();
|
||||
// Token rejected — fetch fresh and retry once
|
||||
const freshToken = (await this.fetchSession()).accessToken;
|
||||
return requestApi(method, url, freshToken, bodyStr);
|
||||
}
|
||||
|
||||
private async getToken(): Promise<string> {
|
||||
const cacheFile = getCacheFile(this.config.authBase, this.config.clientKey, this.config.authCacheDir);
|
||||
const cached = readCachedToken(cacheFile, this.config.authMinTtlSec);
|
||||
if (cached) return cached;
|
||||
|
||||
const session = await this.fetchSession();
|
||||
writeCache(cacheFile, session);
|
||||
return session.accessToken;
|
||||
}
|
||||
|
||||
private async refreshToken(): Promise<string> {
|
||||
const cacheFile = getCacheFile(this.config.authBase, this.config.clientKey, this.config.authCacheDir);
|
||||
deleteCache(cacheFile);
|
||||
|
||||
const session = await this.fetchSession();
|
||||
writeCache(cacheFile, session);
|
||||
return session.accessToken;
|
||||
}
|
||||
|
||||
private async fetchSession(): Promise<SessionResponse> {
|
||||
const result = await requestApi(
|
||||
'POST',
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ export type {
|
|||
EnvConfig,
|
||||
SessionResponse,
|
||||
ClientConfig,
|
||||
CachedTokenData,
|
||||
ApiResponse,
|
||||
HttpMethod,
|
||||
} from './types.js';
|
||||
|
|
|
|||
13
src/types.ts
13
src/types.ts
|
|
@ -6,10 +6,6 @@ export interface EnvConfig {
|
|||
authBase: string;
|
||||
/** Client key for authentication */
|
||||
clientKey: string;
|
||||
/** Directory for storing auth cache files */
|
||||
authCacheDir: string;
|
||||
/** Minimum TTL for cached tokens in seconds */
|
||||
authMinTtlSec: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -33,15 +29,6 @@ export interface ClientConfig {
|
|||
metadata: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached token data stored on disk
|
||||
*/
|
||||
export interface CachedTokenData {
|
||||
accessToken: string;
|
||||
expiresAtEpoch: number;
|
||||
createdAtEpoch: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP method used by requestApi
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue