// Every call is same-origin against the PDS serving this page. /** Thrown with the message the server chose, so screens can show it as-is. */ export class ApiError extends Error { /** @param {string} message @param {number} status */ constructor(message, status) { super(message); this.name = 'ApiError'; this.status = status; } } /** * @param {string} path * @param {RequestInit} [init] * @returns {Promise} */ async function request(path, init) { const response = await fetch(`/account/api${path}`, { headers: { 'Content-Type': 'application/json' }, ...init, }); const body = await response.json().catch(() => ({})); if (!response.ok) { throw new ApiError( body.error || `Request failed (${response.status})`, response.status, ); } return body; } /** @param {string} path */ export const get = (path) => request(path); /** * Call one of this server's own XRPC methods, on the account session. A * refusal names its reason in `message`. * @param {string} method - the lexicon method NSID * @param {RequestInit} [init] * @param {Record} [params] - query parameters; an empty one is left out * @returns {Promise} */ async function xrpcRequest(method, init, params) { const query = new URLSearchParams(); for (const [key, value] of Object.entries(params ?? {})) { if (value) query.set(key, value); } const suffix = query.size ? `?${query}` : ''; const response = await fetch(`/xrpc/${method}${suffix}`, { headers: { 'Content-Type': 'application/json' }, ...init, }); const body = await response.json().catch(() => ({})); if (!response.ok) { throw new ApiError( body.message || body.error || `Request failed (${response.status})`, response.status, ); } return body; } /** @param {string} method @param {Record} [params] */ export const xrpcGet = (method, params) => xrpcRequest(method, undefined, params); /** @param {string} method @param {unknown} [body] */ export const xrpcPost = (method, body) => xrpcRequest(method, { method: 'POST', body: JSON.stringify(body ?? {}) }); /** @param {string} path @param {unknown} [body] */ export const post = (path, body) => request(path, { method: 'POST', body: JSON.stringify(body ?? {}) }); /** * Send one file as the request body. The server streams it into blob storage, * so the file is never re-encoded as JSON or a multipart form. * @param {string} path * @param {File} file * @returns {Promise} */ export async function upload(path, file) { const response = await fetch(`/account/api${path}`, { method: 'POST', headers: { 'Content-Type': file.type || 'application/octet-stream' }, body: file, }); const body = await response.json().catch(() => ({})); if (!response.ok) { throw new ApiError( body.error || `Upload failed (${response.status})`, response.status, ); } return body; }