chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// 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<any>}
*/
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<string, string|undefined|null>} [params] - query parameters; an empty one is left out
* @returns {Promise<any>}
*/
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<string, string|undefined|null>} [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<any>}
*/
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;
}