async function request(url, options = {}) { const response = await fetch(url, { ...options, headers: { "content-type": "application/json", ...(options.headers || {}), }, }); const data = await response.json().catch(() => ({})); if (!response.ok || (data && data.success === false)) { throw new Error( data?.error || `Запрос ${url} завершился ошибкой ${response.status}`, ); } return data; } export const api = { state: () => request("/api/state"), config: () => request("/api/config"), rules: { get: () => request("/api/rules"), save: (rules) => request("/api/rules", { method: "PUT", body: JSON.stringify({ rules }) }), conflicts: () => request("/api/rules/conflicts"), }, ruleSets: { get: () => request("/api/rule-sets"), save: (ruleSets) => request("/api/rule-sets", { method: "PUT", body: JSON.stringify({ ruleSets }), }), lookup: (tag, url) => request("/api/rule-sets/lookup", { method: "POST", body: JSON.stringify({ tag, url }), }), sagernetCatalog: () => request("/api/rule-sets/sagernet-catalog"), }, subscription: { fetch: (url) => request("/api/subscription/fetch", { method: "POST", body: JSON.stringify({ url }), }), forget: () => request("/api/subscription", { method: "DELETE" }), }, apply: (selectedTag) => request("/api/apply", { method: "POST", body: JSON.stringify({ selectedTag }), }), rollback: () => request("/api/apply/rollback", { method: "POST" }), singbox: { stop: () => request("/api/singbox/stop", { method: "POST" }), restart: () => request("/api/singbox/restart", { method: "POST" }), clear: () => request("/api/singbox/clear", { method: "POST" }), }, servers: { ping: (host, port) => request("/api/servers/ping", { method: "POST", body: JSON.stringify({ host, port }), }), pingAll: () => request("/api/servers/ping-all", { method: "POST" }), }, route: { check: ({ host, ip, port, network }) => request("/api/route/check", { method: "POST", body: JSON.stringify({ host, ip, port, network }), }), }, configValidate: () => request("/api/config/validate", { method: "POST" }), };