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]); const SESSION_RETRYABLE_BODY_MARKERS = [ 'session not found or expired', 'invalid or expired token', 'unauthorized', 'client key expired', 'client key revoked', ]; function buildConfig(options) { loadGlobalEnv(); 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), }; } function isRetryable(response) { if (!SESSION_RETRYABLE_STATUS.has(response.status)) return false; const body = (response.body || '').toLowerCase(); if (!body) return true; return SESSION_RETRYABLE_BODY_MARKERS.some((m) => body.includes(m)); } export class SkillClient { config; apiBase; dryRun; constructor(options = {}) { this.config = buildConfig(options); this.apiBase = (options.apiBase || process.env.ECOM_BASE || this.config.authBase).replace(/\/$/, ''); this.dryRun = options.dryRun ?? false; if (!this.dryRun && !this.config.clientKey) { throw new Error('CLIENT_KEY is required. Set via env or pass clientKey option.'); } } /** Fetch raw session info (token + expiresIn) */ async session() { if (this.dryRun) { return { accessToken: '', expiresIn: 900 }; } return this.fetchSession(); } /** Fetch client config (metadata with hook info, provider keys, etc.) */ async clientConfig() { if (this.dryRun) { return { clientId: '', name: '', status: 'active', metadata: {} }; } const result = await requestApi('GET', `${this.config.authBase}/auth/skill-credit/client-config`, undefined, undefined, { 'X-Client-Key': this.config.clientKey }); if (result.status < 200 || result.status >= 300) { throw new Error(`Client config failed: HTTP ${result.status} — ${result.body}`); } return JSON.parse(result.body); } async get(path) { return this.request('GET', path); } async post(path, body) { return this.request('POST', path, body); } async put(path, body) { return this.request('PUT', path, body); } async patch(path, body) { return this.request('PATCH', path, body); } async delete(path, body) { return this.request('DELETE', path, body); } // ---- internal ---- async request(method, path, body) { if (this.dryRun) { return { status: 200, body: JSON.stringify({ dryRun: true, method, path }) }; } const url = `${this.apiBase}${path}`; const bodyStr = body != null ? JSON.stringify(body) : undefined; const token = await this.getToken(); const first = await requestApi(method, url, token, bodyStr); if (!isRetryable(first)) { return first; } // Token expired — refresh and retry once const freshToken = await this.refreshToken(); return requestApi(method, url, freshToken, bodyStr); } async getToken() { 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; } async refreshToken() { 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; } async fetchSession() { const result = await requestApi('POST', `${this.config.authBase}/auth/skill-credit/session`, undefined, JSON.stringify({ clientKey: this.config.clientKey })); if (result.status < 200 || result.status >= 300) { throw new Error(`Auth session failed: HTTP ${result.status} — ${result.body}`); } const session = JSON.parse(result.body); if (!session.accessToken) { throw new Error(`Missing accessToken in session response: ${result.body}`); } return session; } } export function createSkillClient(options) { return new SkillClient(options); } //# sourceMappingURL=client.js.map