| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { SITE } from "./config.js"; |
|
|
| export class RateLimitError extends Error { |
| constructor(message) { |
| super(message || "You've hit the free hourly limit."); |
| this.name = "RateLimitError"; |
| this.isRateLimit = true; |
| } |
| } |
|
|
| export class ApiError extends Error { |
| constructor(message, status = 0) { |
| super(message || "Something went wrong."); |
| this.name = "ApiError"; |
| this.status = status; |
| } |
| } |
|
|
| const DEFAULT_TIMEOUT = 60_000; |
|
|
| async function doFetch(url, init, timeout) { |
| const ctrl = new AbortController(); |
| const timer = setTimeout(() => ctrl.abort(), timeout ?? DEFAULT_TIMEOUT); |
| try { |
| return await fetch(url, { ...init, signal: ctrl.signal }); |
| } catch (err) { |
| if (err && err.name === "AbortError") { |
| throw new ApiError( |
| "The request timed out. The free demo can be slow under load — try the full tool on mesmer.tools.", |
| 408, |
| ); |
| } |
| throw new ApiError("Network error. Check your connection and try again.", 0); |
| } finally { |
| clearTimeout(timer); |
| } |
| } |
|
|
| async function readJson(res) { |
| try { |
| return await res.json(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| export async function callRest(apiPath, { method = "GET", params, body, timeout } = {}) { |
| let url = SITE.origin + apiPath; |
| const init = { method, headers: {} }; |
| if (params) { |
| const qs = new URLSearchParams(params).toString(); |
| if (qs) url += (url.includes("?") ? "&" : "?") + qs; |
| } |
| if (body !== undefined) { |
| init.headers["Content-Type"] = "application/json"; |
| init.body = JSON.stringify(body); |
| } |
| const res = await doFetch(url, init, timeout); |
| const data = await readJson(res); |
| if (res.status === 429) throw new RateLimitError(data && data.error); |
| if (!res.ok) throw new ApiError((data && data.error) || `Request failed (${res.status}).`, res.status); |
| return data; |
| } |
|
|
| |
| |
| |
| |
| export async function callTrpcMutation(trpcPath, input, { timeout } = {}) { |
| const res = await doFetch( |
| SITE.origin + trpcPath, |
| { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ json: input }), |
| }, |
| timeout, |
| ); |
| const data = await readJson(res); |
| if (res.status === 429) throw new RateLimitError(extractTrpcError(data)); |
| if (!res.ok) throw new ApiError(extractTrpcError(data) || `Request failed (${res.status}).`, res.status); |
| |
| const out = data && data.result && data.result.data; |
| return out && typeof out === "object" && "json" in out ? out.json : out; |
| } |
|
|
| function extractTrpcError(data) { |
| if (!data || !data.error) return null; |
| |
| return (data.error.json && data.error.json.message) || data.error.message || null; |
| } |
|
|
| |
| export async function fetchData(url, { timeout } = {}) { |
| const res = await doFetch(url, { method: "GET" }, timeout ?? 20_000); |
| if (!res.ok) throw new ApiError(`Could not load data (${res.status}).`, res.status); |
| return readJson(res); |
| } |
|
|