snow-editor

small markdown and org-mode editor
Log | Files | Refs | README

api.js (3629B)


      1 import { STR } from './strings.js';
      2 
      3 const API_BASE = import.meta.env.VITE_API_BASE ?? '';
      4 
      5 function getPublicOrigin() {
      6   const configured = import.meta.env.VITE_PUBLIC_ORIGIN?.trim();
      7   if (configured) {
      8     return configured.replace(/\/$/, '');
      9   }
     10   return window.location.origin;
     11 }
     12 
     13 export class ApiError extends Error {
     14   constructor(status, code, message, payload = {}) {
     15     super(message);
     16     this.name = 'ApiError';
     17     this.status = status;
     18     this.code = code;
     19     this.payload = payload;
     20   }
     21 }
     22 
     23 const ERROR_MESSAGES = {
     24   NOT_FOUND: STR.NOT_FOUND,
     25   EXPIRED: STR.EXPIRED,
     26   DOCUMENT_LOCKED: STR.DOCUMENT_LOCKED,
     27   LOCK_REQUIRED: STR.LOCK_REQUIRED,
     28   CONTENT_TOO_LARGE: STR.CONTENT_TOO_LARGE,
     29   RATE_LIMIT: STR.RATE_LIMIT,
     30   ORIGIN_NOT_ALLOWED: STR.ORIGIN_NOT_ALLOWED,
     31   NETWORK: STR.NETWORK,
     32 };
     33 
     34 export function friendlyErrorMessage(error) {
     35   if (error instanceof ApiError) {
     36     return error.message || ERROR_MESSAGES[error.code] || STR.GENERIC_ERROR;
     37   }
     38   return ERROR_MESSAGES.NETWORK;
     39 }
     40 
     41 async function parseResponse(res) {
     42   let data = null;
     43   const text = await res.text();
     44   if (text) {
     45     try {
     46       data = JSON.parse(text);
     47     } catch {
     48       data = { message: text };
     49     }
     50   }
     51 
     52   if (!res.ok) {
     53     const code = data?.error ?? 'UNKNOWN';
     54     const message =
     55       data?.message ?? ERROR_MESSAGES[code] ?? STR.UNEXPECTED_ERROR;
     56     throw new ApiError(res.status, code, message, data ?? {});
     57   }
     58 
     59   return data;
     60 }
     61 
     62 async function request(path, options = {}) {
     63   let res;
     64   try {
     65     res = await fetch(`${API_BASE}${path}`, {
     66       ...options,
     67       headers: {
     68         'Content-Type': 'application/json',
     69         ...(options.headers ?? {}),
     70       },
     71     });
     72   } catch {
     73     throw new ApiError(0, 'NETWORK', ERROR_MESSAGES.NETWORK);
     74   }
     75   return parseResponse(res);
     76 }
     77 
     78 export function createDocument(body) {
     79   return request('/api/documents', {
     80     method: 'POST',
     81     body: JSON.stringify(body),
     82   });
     83 }
     84 
     85 export function fetchViewDocument(token) {
     86   return request(`/api/documents/view/${encodeURIComponent(token)}`);
     87 }
     88 
     89 export function fetchEditDocument(token) {
     90   return request(`/api/documents/edit/${encodeURIComponent(token)}`);
     91 }
     92 
     93 export function acquireEditLock(token, clientId) {
     94   return request(`/api/documents/edit/${encodeURIComponent(token)}/lock`, {
     95     method: 'POST',
     96     body: JSON.stringify({ clientId }),
     97   });
     98 }
     99 
    100 export function refreshEditLock(token, clientId, lockToken) {
    101   return request(
    102     `/api/documents/edit/${encodeURIComponent(token)}/lock/refresh`,
    103     {
    104       method: 'POST',
    105       body: JSON.stringify({ clientId, lockToken }),
    106     },
    107   );
    108 }
    109 
    110 export function releaseEditLock(token, clientId, lockToken) {
    111   return request(`/api/documents/edit/${encodeURIComponent(token)}/lock`, {
    112     method: 'DELETE',
    113     body: JSON.stringify({ clientId, lockToken }),
    114   });
    115 }
    116 
    117 export function updateDocument(token, body) {
    118   return request(`/api/documents/edit/${encodeURIComponent(token)}`, {
    119     method: 'PUT',
    120     body: JSON.stringify(body),
    121   });
    122 }
    123 
    124 export function fetchDocumentVersions(token, { clientId, lockToken }) {
    125   const params = new URLSearchParams({ clientId, lockToken });
    126   return request(
    127     `/api/documents/edit/${encodeURIComponent(token)}/versions?${params}`,
    128   );
    129 }
    130 
    131 export function restoreDocumentVersion(token, versionId, body) {
    132   return request(
    133     `/api/documents/edit/${encodeURIComponent(token)}/versions/${encodeURIComponent(versionId)}/restore`,
    134     {
    135       method: 'POST',
    136       body: JSON.stringify(body),
    137     },
    138   );
    139 }
    140 
    141 export function toAbsoluteUrl(path) {
    142   if (path.startsWith('http')) return path;
    143   return `${getPublicOrigin()}${path}`;
    144 }