92 lines
2.7 KiB
JavaScript
92 lines
2.7 KiB
JavaScript
/**
|
|
* Goosed HTTP client with dual-node support.
|
|
*
|
|
* 103 production: two goosed nodes on :18006 and :18007 (HTTPS + self-signed TLS).
|
|
* Local dev: one node (GOOSED_URLS=https://127.0.0.1:18006 or falls back to TKMIND_API_TARGET).
|
|
*
|
|
* Skill calls are stateless one-shot requests — no session affinity needed.
|
|
* We round-robin across available nodes for basic load distribution.
|
|
*/
|
|
|
|
import https from 'node:https';
|
|
|
|
// Self-signed cert agent — mirrors what Memind server.mjs does for tkmind-proxy
|
|
const tlsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
|
|
function parseGoosedUrls() {
|
|
const raw =
|
|
process.env.GOOSED_URLS ??
|
|
[
|
|
process.env.TKMIND_API_TARGET,
|
|
process.env.TKMIND_API_TARGET_1,
|
|
]
|
|
.filter(Boolean)
|
|
.join(',');
|
|
|
|
if (!raw) return ['https://127.0.0.1:18006'];
|
|
return raw
|
|
.split(',')
|
|
.map((u) => u.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
let _roundRobinIdx = 0;
|
|
|
|
export function pickGoosedUrl() {
|
|
const urls = parseGoosedUrls();
|
|
const url = urls[_roundRobinIdx % urls.length];
|
|
_roundRobinIdx = (_roundRobinIdx + 1) % urls.length;
|
|
return url;
|
|
}
|
|
|
|
export function goosedUrls() {
|
|
return parseGoosedUrls();
|
|
}
|
|
|
|
/**
|
|
* Make a JSON request to a goosed node.
|
|
* Returns { ok, status, body } — never throws on HTTP errors, only on network failure.
|
|
* Uses https.request directly so rejectUnauthorized: false works for self-signed certs.
|
|
*/
|
|
export function goosedFetch(path, { method = 'GET', body, node } = {}) {
|
|
const base = node ?? pickGoosedUrl();
|
|
const secret =
|
|
process.env.GOOSE_SERVER__SECRET_KEY ??
|
|
process.env.TKMIND_SERVER__SECRET_KEY ??
|
|
'local-dev-secret';
|
|
|
|
const url = new URL(`${base}${path}`);
|
|
const payload = body !== undefined ? JSON.stringify(body) : null;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const options = {
|
|
hostname: url.hostname,
|
|
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
path: url.pathname + url.search,
|
|
method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Secret-Key': secret,
|
|
...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
|
|
},
|
|
agent: tlsAgent,
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let text = '';
|
|
res.setEncoding('utf8');
|
|
res.on('data', (chunk) => { text += chunk; });
|
|
res.on('end', () => {
|
|
let json = null;
|
|
try { json = JSON.parse(text); } catch { json = { raw: text }; }
|
|
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, body: json });
|
|
});
|
|
res.on('error', reject);
|
|
});
|
|
|
|
req.on('error', reject);
|
|
if (payload) req.write(payload);
|
|
req.end();
|
|
});
|
|
}
|