2025-08-11 01:11:36 +04:00

521 lines
10 KiB
JavaScript

class API {
constructor(address) {
this.base = '/api';
this.address = address;
this.token = '';
this.cache = new Map();
// Debug logging for API requests/responses (opt-in via REACT_APP_API_DEBUG=true)
this.debugRequests = (String(process.env.REACT_APP_API_DEBUG || '')).toLowerCase() === 'true';
}
_debug(message) {
if (this.debugRequests) {
console.log(`[CoreAPI] ${message}`);
}
}
_error(message) {
console.error(`[CoreAPI] Error: ${message}`);
}
async _GET(path, options) {
const key = path + JSON.stringify(options);
const data = this.cache.get(key);
if (data !== undefined) {
const millis = Date.now() - data.ts;
if (millis < 950) {
return JSON.parse(JSON.stringify(data.payload));
}
}
const ts = Date.now();
const res = await this._call('GET', path, options);
this.cache.set(key, {
payload: JSON.parse(JSON.stringify(res)),
ts: ts,
});
return res;
}
async _HEAD(path, options) {
return await this._call('HEAD', path, options);
}
async _POST(path, options) {
return await this._call('POST', path, options);
}
async _PUT(path, options) {
return await this._call('PUT', path, options);
}
async _DELETE(path, options) {
return await this._call('DELETE', path, options);
}
async _PATCH(path, options) {
return await this._call('PATCH', path, options);
}
async _call(method, path, options = {}) {
options = {
method: method.toUpperCase(),
expect: 'any',
headers: {},
token: '',
...options,
};
path = this.base + path;
if (path !== '/') {
// remove slash at the end of the path
if (path[path.length - 1] === '/') {
path = path.substring(0, path.length - 1);
}
}
let token = '';
if (options.token.length !== 0) {
token = options.token;
} else {
if (typeof this.token === 'function') {
token = await this.token();
} else {
token = this.token;
}
}
if (token.length !== 0) {
options.headers.Authorization = 'Bearer ' + token;
}
this._debug(`calling ${options.method} ${this.address + path}`);
// Debug: print request details with masked Authorization and trimmed body
if (this.debugRequests) {
const dbgHeaders = { ...options.headers };
if (dbgHeaders.Authorization) {
dbgHeaders.Authorization = 'Bearer ***';
}
let bodyPreview = undefined;
if (typeof options.body === 'string') {
try {
const parsed = JSON.parse(options.body);
const str = JSON.stringify(parsed);
bodyPreview = str.length > 2000 ? str.slice(0, 2000) + '…' : str;
} catch (_) {
bodyPreview = options.body.length > 2000 ? options.body.slice(0, 2000) + '…' : options.body;
}
}
console.log('[CoreAPI] Request', {
method: options.method,
url: this.address + path,
headers: dbgHeaders,
body: bodyPreview,
expect: options.expect,
});
}
const res = {
err: null,
val: null,
};
let response = null;
try {
response = await fetch(this.address + path, options);
} catch (err) {
res.err = {
code: -1,
message: err.message,
};
this._error(res.err.message);
return res;
}
const contentType = response.headers.get('Content-Type');
let isJSON = false;
if (contentType != null) {
isJSON = contentType.indexOf('application/json') !== -1;
}
if (response.ok === false) {
res.err = {
code: response.status,
message: response.statusText,
};
if (isJSON === true) {
const body = await response.json();
if ('code' in body && 'message' in body) {
res.err.message = body.message;
} else {
res.err.message = body;
}
} else {
const body = await response.text();
if (body.length > 0) {
res.err.message = body;
}
}
this._error(res.err.message);
if (this.debugRequests) {
console.log('[CoreAPI] Response (error)', {
status: response.status,
statusText: response.statusText,
url: this.address + path,
err: res.err,
});
}
return res;
}
if (isJSON === true) {
res.val = await response.json();
} else {
res.val = await response.text();
}
if (options.expect === 'json') {
if (isJSON === false) {
res.val = null;
res.err = {
code: -2,
message: `The response is not JSON as expected (${contentType})`,
};
this._error(res.err.message);
}
}
return res;
}
SetAddress(address) {
this.address = address;
}
SetToken(token) {
this.token = token;
this.cache = new Map();
}
async Login(username, password) {
return await this._POST('/login', {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username,
password: password,
}),
expect: 'json',
});
}
async LoginWithToken(token) {
return await this._POST('/login', {
headers: {
'Content-Type': 'application/json',
},
expect: 'json',
token: token,
});
}
async RefreshToken(refresh_token) {
return await this._GET('/login/refresh', {
expect: 'json',
token: refresh_token,
});
}
async About() {
return await this._GET('/', {
expect: 'json',
});
}
async Skills() {
return await this._GET('/v3/skills', {
expect: 'json',
});
}
async SkillsReload() {
return await this._GET('/v3/skills/reload', {
expect: 'json',
});
}
async Config(type) {
return await this._GET('/v3/config', {
expect: 'json',
});
}
async ConfigSet(config) {
return await this._PUT('/v3/config', {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config),
expect: 'json',
});
}
async ConfigReload(type) {
return await this._GET('/v3/config/reload');
}
async Log() {
return await this._GET('/v3/log', {
expect: 'json',
});
}
async ActiveSessions(collectors) {
return await this._GET('/v3/session/active?collectors=' + encodeURIComponent(collectors.join(',')), {
expect: 'json',
});
}
async SetMetadata(key, data) {
return await this._PUT('/v3/metadata/' + encodeURIComponent(key), {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
expect: 'json',
});
}
async GetMetadata(key) {
return await this._GET('/v3/metadata/' + encodeURIComponent(key), {
expect: 'json',
});
}
async DataHasFile(path) {
return await this._HEAD('/v3/fs/disk' + path);
}
async DataPutFile(path, data) {
return await this._PUT('/v3/fs/disk' + path, {
headers: {
'Content-Type': 'application/data',
},
body: data,
});
}
async DataGetFile(path) {
return await this._GET('/v3/fs/disk' + path);
}
async DataListFiles(pathPattern) {
return await this._GET('/v3/fs/disk?glob=' + encodeURIComponent(pathPattern), {
expect: 'json',
});
}
async DataDeleteFile(path) {
return await this._DELETE('/v3/fs/disk' + path);
}
async MemFSListFiles(pathPattern) {
return await this._GET('/v3/fs/mem?glob=' + encodeURIComponent(pathPattern), {
expect: 'json',
});
}
async MemFSHasFile(path) {
return await this._HEAD('/v3/fs/mem' + path);
}
async MemFSDeleteFile(path) {
return await this._DELETE('/v3/fs/mem' + path);
}
async MemFSLinkFile(path, linkto) {
return await this._PATCH('/v3/fs/mem/' + path, {
headers: {
'Content-Type': 'application/data',
},
body: linkto,
});
}
async Processes(reference = '', ids = [], filter = []) {
let url = '/v3/process';
let params = [];
if (reference.length !== 0) {
params.push('reference=' + encodeURIComponent(reference));
}
if (ids.length !== 0) {
params.push('id=' + encodeURIComponent(ids.join(',')));
}
if (filter.length !== 0) {
params.push('filter=' + encodeURIComponent(filter.join(',')));
}
if (params.length !== 0) {
url = url + '?' + params.join('&');
}
return await this._GET(url, {
expect: 'json',
});
}
async Process(name, filter = []) {
let url = '/v3/process/' + name;
if (filter.length !== 0) {
url = url + '?filter=' + encodeURIComponent(filter.join(','));
}
return await this._GET(url, {
expect: 'json',
});
}
async ProcessConfig(name) {
return await this._GET('/v3/process/' + encodeURIComponent(name) + '/config', {
expect: 'json',
});
}
async ProcessState(name) {
return await this._GET('/v3/process/' + encodeURIComponent(name) + '/state', {
expect: 'json',
});
}
async ProcessReport(name) {
return await this._GET('/v3/process/' + encodeURIComponent(name) + '/report', {
expect: 'json',
});
}
async ProcessCommand(name, command) {
return await this._PUT('/v3/process/' + encodeURIComponent(name) + '/command', {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
command: command,
}),
expect: 'json',
});
}
async ProcessDelete(name) {
return await this._DELETE('/v3/process/' + encodeURIComponent(name));
}
async ProcessUpdate(name, config) {
return await this._PUT('/v3/process/' + encodeURIComponent(name), {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config),
});
}
async ProcessSetMetadata(name, key, data) {
return await this._PUT('/v3/process/' + encodeURIComponent(name) + '/metadata/' + encodeURIComponent(key), {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
expect: 'json',
});
}
async ProcessGetMetadata(name, key) {
return await this._GET('/v3/process/' + encodeURIComponent(name) + '/metadata/' + encodeURIComponent(key), {
expect: 'json',
});
}
async ProcessAdd(config) {
return await this._POST('/v3/process', {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config),
expect: 'json',
});
}
async ProcessProbe(name) {
return await this._GET('/v3/process/' + encodeURIComponent(name) + '/probe', {
expect: 'json',
});
}
async Metrics(query) {
return await this._POST('/v3/metrics', {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(query),
expect: 'json',
});
}
async RTMPChannels() {
const res = await this._GET('/v3/rtmp', {
expect: 'json',
});
if (res.err !== null) {
return res;
}
res.val = res.val.map((f) => f.name);
return res;
}
async SRTChannels() {
const res = await this._GET('/v3/srt', {
expect: 'json',
});
if (res.err !== null) {
return res;
}
const val = res.val;
res.val = [];
for (let path in val.publisher) {
res.val.push(path);
}
return res;
}
}
export default API;