const jsonHeaders = { "Content-Type": "application/json", Accept: "application/json" };
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function requestWithRetry(path, options = {}, retries = 3) {
let lastError;
for (let attempt = 0; attempt < retries; attempt++) {
try {
const response = await fetch(path, {
credentials: "include",
...options,
headers: {
...jsonHeaders,
...(options.headers || {})
}
});
if (response.status === 204) {
return null;
}
const payload = await response.json();
if (!response.ok) {
throw new Error(payload?.error?.message || "Request failed");
}
return payload;
} catch (err) {
lastError = err;
const isNetworkError = !err.message?.includes("Request failed");
if (!isNetworkError || attempt >= retries - 1) {
throw err;
}
await sleep(300 * Math.pow(2, attempt));
}
}
throw lastError;
}
function request(path, options = {}) {
return requestWithRetry(path, options, 3);
}
export const api = {
health: () => request("/health"),
ready: () => request("/ready"),
me: () => request("/api/v1/me"),
updateMe: (payload) =>
request("/api/v1/me", { method: "PATCH", body: JSON.stringify(payload) }),
logout: () => request("/auth/logout", { method: "POST" }),
listSecrets: (params = {}) => {
const query = new URLSearchParams(params);
return request(`/api/v1/secrets?${query}`);
},
createSecret: (payload) =>
request("/api/v1/secrets", { method: "POST", body: JSON.stringify(payload) }),
getSecret: (id) => request(`/api/v1/secrets/${id}`),
updateSecret: (id, payload) =>
request(`/api/v1/secrets/${id}`, { method: "PATCH", body: JSON.stringify(payload) }),
deleteSecret: (id) => request(`/api/v1/secrets/${id}`, { method: "DELETE" }),
revealSecret: (id) => request(`/api/v1/secrets/${id}/reveal`, { method: "POST" }),
versions: (id) => request(`/api/v1/secrets/${id}/versions`),
revealVersion: (id, versionId) =>
request(`/api/v1/secrets/${id}/versions/${versionId}/reveal`, { method: "POST" }),
audit: (params = {}) => {
const query = new URLSearchParams(params);
return request(`/api/v1/audit-events?${query}`);
},
secretAudit: (id, params = {}) => {
const query = new URLSearchParams(params);
return request(`/api/v1/secrets/${id}/audit-events?${query}`);
},
tokens: () => request("/api/v1/api-tokens"),
createToken: (payload) =>
request("/api/v1/api-tokens", { method: "POST", body: JSON.stringify(payload) }),
revokeToken: (id) =>
request(`/api/v1/api-tokens/${id}/revoke`, { method: "PATCH" }),
exportData: () => request("/api/v1/export", { method: "POST" }),
importData: (payload) =>
request("/api/v1/import", { method: "POST", body: JSON.stringify(payload) }),
deleteAccountData: () => request("/api/v1/account-data", { method: "DELETE" }),
adminUsers: (params = {}) => {
const query = new URLSearchParams(params);
return request(`/api/v1/admin/users?${query}`);
},
stats: () => request("/api/v1/stats"),
createBackup: () => request("/api/v1/admin/backup", { method: "POST" }),
listBackups: () => request("/api/v1/admin/backups"),
downloadBackup: (filename) => `/api/v1/admin/backups/${encodeURIComponent(filename)}`,
restoreBackup: (filename) =>
request("/api/v1/admin/restore", { method: "POST", body: JSON.stringify({ filename }) })
};