auth-runtime/dist/client.js

119 lines
4.7 KiB
JavaScript

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 = [
'session not found or expired',
'invalid or expired token',
'unauthorized',
'client key expired',
'client key revoked',
];
function buildConfig(options) {
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: '<dry-run-token>', expiresIn: 900 };
}
return this.fetchSession();
}
/** Fetch client config (metadata with hook info, provider keys, etc.) */
async clientConfig() {
if (this.dryRun) {
return { clientId: '<dry-run>', name: '<dry-run>', 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