"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CoreApiClient = exports.CoreApiClientError = void 0; class CoreApiClientError extends Error { status; constructor(message, status) { super(message); this.name = 'CoreApiClientError'; this.status = status; } } exports.CoreApiClientError = CoreApiClientError; class CoreApiClient { apiUrl; clientId; clientSecret; timeoutMs; cachedToken = null; tokenExpiry = 0; // epoch ms tokenMarginSec = 60; // safety margin in seconds constructor(options) { this.apiUrl = options?.apiUrl ?? process.env.RAYLAB_CORE_API_URL ?? 'http://localhost:3000'; this.clientId = options?.clientId ?? process.env.RAYLAB_BOT_CLIENT_ID ?? ''; this.clientSecret = options?.clientSecret ?? process.env.RAYLAB_BOT_CLIENT_SECRET ?? ''; this.timeoutMs = options?.timeoutMs ?? 5000; if (!this.clientId || !this.clientSecret) { // keep construction allowed but operations will fail with clear errors } } async fetchWithTimeout(url, init) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), this.timeoutMs); try { const res = await fetch(url, { ...init, signal: controller.signal }); return res; } finally { clearTimeout(id); } } isTokenValid() { return !!this.cachedToken && Date.now() < this.tokenExpiry; } async requestAccessToken() { if (!this.clientId || !this.clientSecret) { throw new CoreApiClientError('Missing client credentials'); } const url = `${this.apiUrl.replace(/\/$/, '')}/api/v1/auth/token`; const body = JSON.stringify({ client_id: this.clientId, client_secret: this.clientSecret, grant_type: 'client_credentials' }); let res; try { res = await this.fetchWithTimeout(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body, }); } catch (err) { if (err.name === 'AbortError') { throw new CoreApiClientError('Request timeout'); } throw new CoreApiClientError('Network error'); } if (res.status === 401) { throw new CoreApiClientError('Invalid client credentials', 401); } if (res.status >= 400) { throw new CoreApiClientError(`Token request failed with status ${res.status}`, res.status); } const payload = await res.json(); // Expecting { success: true, data: { access_token, token_type, expires_in }, ... } const data = payload?.data; if (!data || !data.access_token) { throw new CoreApiClientError('Invalid token response'); } // Cache token this.cachedToken = data.access_token; const expiresIn = typeof data.expires_in === 'number' ? data.expires_in : parseInt(data.expires_in, 10) || 3600; const effective = Math.max(0, expiresIn - this.tokenMarginSec); this.tokenExpiry = Date.now() + effective * 1000; return { access_token: data.access_token, token_type: data.token_type ?? 'Bearer', expires_in: expiresIn }; } async getToken() { if (this.isTokenValid()) { return this.cachedToken; } const tokenResp = await this.requestAccessToken(); return tokenResp.access_token; } async authenticatedRequest(input, init, retry = true) { const token = await this.getToken(); const headers = new Headers(init?.headers); headers.set('Authorization', `Bearer ${token}`); headers.set('Accept', 'application/json'); let res; try { res = await this.fetchWithTimeout(typeof input === 'string' ? input : input.url, { ...init, headers, }); } catch (err) { if (err.name === 'AbortError') { throw new CoreApiClientError('Request timeout'); } throw new CoreApiClientError('Network error'); } if (res.status === 401 && retry) { // invalidate token and retry once this.cachedToken = null; this.tokenExpiry = 0; try { const newToken = await this.getToken(); const headers2 = new Headers(init?.headers); headers2.set('Authorization', `Bearer ${newToken}`); headers2.set('Accept', 'application/json'); const res2 = await this.fetchWithTimeout(typeof input === 'string' ? input : input.url, { ...init, headers: headers2, }); return res2; } catch (e) { throw e; } } return res; } } exports.CoreApiClient = CoreApiClient; //# sourceMappingURL=core-api-client.js.map