This commit is contained in:
Rayyan Syahbani Hermanto
2026-09-07 22:57:18 +07:00
parent fdbfb34842
commit 1bba2b518e
101 changed files with 2503 additions and 8 deletions
+131
View File
@@ -0,0 +1,131 @@
"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