UNPKG

@samuelspagl/nc-api-client

Version:

A simple API client for Nextcloud Apps.

543 lines (527 loc) 19.4 kB
import { ofetch } from 'ofetch'; class ApiError extends Error { statusCode; errorDetails; constructor(errorDetails, name = "ApiError") { super(errorDetails.message); this.name = `${name}`; this.statusCode = errorDetails.code; this.errorDetails = errorDetails; this.message = this.toString(errorDetails); } toString(errorDetails) { return `Request returned an ${errorDetails.code} at ${errorDetails.url} Message: ${errorDetails.message} Additional Info: ${JSON.stringify(errorDetails.body)}`; } } async function request(url, options) { const { basicAuth, mapError, ...fetchOptions } = options; const headersInit = { "Content-Type": "application/json", ...fetchOptions.headers }; const headers = new Headers(headersInit); if (basicAuth) { const authHeader = "Basic " + Buffer.from(`${basicAuth.username}:${basicAuth.password}`).toString("base64"); headers.set("Authorization", authHeader); } const response = await ofetch(url, { ...fetchOptions, headers, timeout: 3e4, // 5 seconds timeout (you can adjust as needed), async onResponseError({ request: request2, response: response2, options: options2 }) { if (mapError) { throw await mapError(request2, response2, options2); } else { throw new ApiError({ code: response2.status, message: response2.statusText, url: request2.toString(), timestamp: (/* @__PURE__ */ new Date()).toISOString(), details: response2._data }); } } }); return response; } class BaseApiClient { baseUrl; username; password; constructor(baseUrl, username, password) { this.username = username; this.password = password; this.baseUrl = baseUrl; } async request(url, options) { return await request(`${this.baseUrl}${url}`, { basicAuth: { username: this.username, password: this.password }, mapError: this.buildErrorObject, ...options }); } async ocsRequest(url, options) { return await request(`${this.baseUrl}${url}`, { basicAuth: { username: this.username, password: this.password }, headers: { "Content-Type": "application/json;charset=utf-8", "Accept": "application/json", "OCS-APIRequest": "true" }, mapError: this.buildOcsErrorObject, ...options }); } async get(url, query) { return await this.request(url, { method: "GET", query }); } async getBlob(url) { return await this.request(url, { method: "GET", responseType: "blob" }); } async post(url, body) { return await this.request(url, { method: "POST", body: JSON.stringify(body) }); } async postBlob(url, body) { return await this.request(url, { method: "POST", body: JSON.stringify(body) }); } async put(url, body) { return await this.request(url, { method: "PUT", body: JSON.stringify(body) }); } async patch(url, body) { return await this.request(url, { method: "PATCH", body: JSON.stringify(body) }); } async delete(url) { return await this.request(url, { method: "DELETE" }); } async ocsGet(url, query) { return await this.ocsRequest(url, { method: "GET", query }); } async ocsPost(url, body) { return await this.ocsRequest(url, { method: "POST", body: JSON.stringify(body) }); } async ocsPut(url, body) { return await this.ocsRequest(url, { method: "PUT", body: JSON.stringify(body) }); } async ocsDelete(url) { return await this.ocsRequest(url, { method: "DELETE" }); } async buildErrorObject(request2, response, options) { return new ApiError({ code: response.status, message: response.statusText, url: request2.toString(), timestamp: (/* @__PURE__ */ new Date()).toISOString(), details: response._data }); } async buildOcsErrorObject(request2, response, options) { return new ApiError({ code: response.status, message: response.statusText, url: request2.toString(), timestamp: (/* @__PURE__ */ new Date()).toISOString(), details: response._data }); } } const apiPrefix = "/index.php/apps/deck/api/v1.0"; const pathBoards = `${apiPrefix}/boards`; const pathBoardById = (boardId) => `${apiPrefix}/boards/${boardId}`; const pathBoardUndoDelete = (boardId) => `${apiPrefix}/boards/${boardId}/undo_delete`; const pathBoardAcl = (boardId) => `${apiPrefix}/boards/${boardId}/acl`; const pathBoardAclById = (boardId, aclId) => `${apiPrefix}/boards/${boardId}/acl/${aclId}`; const pathStacks = (boardId) => `${apiPrefix}/boards/${boardId}/stacks`; const pathStacksArchived = (boardId) => `${apiPrefix}/boards/${boardId}/stacks/archived`; const pathStacksById = (boardId, stackId) => `${apiPrefix}/boards/${boardId}/stacks/${stackId}`; const pathCards = (boardId, stackId) => `${apiPrefix}/boards/${boardId}/stacks/${stackId}/cards`; const pathCardById = (boardId, stackId, cardId) => `${apiPrefix}/boards/${boardId}/stacks/${stackId}/cards/${cardId}`; const pathCardAssignLabel = (boardId, stackId, cardId) => `${apiPrefix}/boards/${boardId}/stacks/${stackId}/cards/${cardId}/assignLabel`; const pathCardAttachements = (boardId, stackId, cardId) => `${apiPrefix}/boards/${boardId}/stacks/${stackId}/cards/${cardId}/attachments`; const pathCardAttachementsById = (boardId, stackId, cardId, attachementId) => `${apiPrefix}/boards/${boardId}/stacks/${stackId}/cards/${cardId}/attachments/${attachementId}`; const pathCardRestoreAttachementsById = (boardId, stackId, cardId, attachementId) => `${apiPrefix}/boards/${boardId}/stacks/${stackId}/cards/${cardId}/attachments/${attachementId}/restore`; const pathLabels = (boardId) => `${apiPrefix}/boards/${boardId}/labels`; const pathLabelById = (boardId, labelId) => `${apiPrefix}/boards/${boardId}/labels/${labelId}`; class DeckError extends ApiError { constructor(errorDetails) { super(errorDetails, "DeckError"); } } class DeckClient extends BaseApiClient { // ------------------------------ // Boards // ------------------------------ async getBoards(details) { return await this.get(pathBoards, { details }); } async createBoard(payload) { return await this.post(pathBoards, payload); } async getBoard(boardId) { return await this.get(pathBoardById(boardId)); } async updateBoard(boardId, payload) { return await this.put(pathBoardById(boardId), payload); } async deleteBoard(boardId) { return await this.delete(pathBoardById(boardId)); } async undoDeleteBoard(boardId) { return await this.post(pathBoardUndoDelete(boardId), null); } async createShare(boardId, payload) { return await this.post(pathBoardAcl(boardId), payload); } async updateShare(boardId, aclId, payload) { return await this.put(pathBoardAclById(boardId, aclId), payload); } async deleteShare(boardId, aclId) { return await this.delete(pathBoardAclById(boardId, aclId)); } // ------------------------------ // Stacks // ------------------------------ async getStacks(boardId) { return await this.get(pathStacks(boardId)); } async getArchivedStacks(boardId) { return await this.get(pathStacksArchived(boardId)); } async createStack(boardId, payload) { return await this.post(pathStacks(boardId), payload); } async getStack(boardId, stackId) { return await this.get(pathStacksById(boardId, stackId)); } async updateStack(boardId, stackId, payload) { return await this.put(pathStacksById(boardId, stackId), payload); } async deleteStack(boardId, stackId) { return await this.delete(pathStacksById(boardId, stackId)); } // ------------------------------ // Cards // ------------------------------ async createCard(boardId, stackId, payload) { return await this.post(pathCards(boardId, stackId), payload); } async getCard(boardId, stackId, cardId) { return await this.get(pathCardById(boardId, stackId, cardId)); } async updateCard(boardId, stackId, cardId, payload) { return await this.put(pathCardById(boardId, stackId, cardId), payload); } async deleteCard(boardId, stackId, cardId) { return await this.delete(pathCardById(boardId, stackId, cardId)); } async assignLabelToCard(boardId, stackId, cardId, labelId) { return await this.put(pathCardAssignLabel(boardId, stackId, cardId), { labelId }); } async removeLabelFromCard(boardId, stackId, cardId, labelId) { return await this.put(pathCardAssignLabel(boardId, stackId, cardId), { labelId }); } async assignUserToCard(boardId, stackId, cardId, userId) { return await this.put(pathCardAssignLabel(boardId, stackId, cardId), { userId }); } async removeUserFromCard(boardId, stackId, cardId, userId) { return await this.put(pathCardAssignLabel(boardId, stackId, cardId), { userId }); } async reorderCard(boardId, stackId, cardId, payload) { return await this.put(pathCardAssignLabel(boardId, stackId, cardId), payload); } // ------------------------------ // Labels // ------------------------------ async getLabel(boardId, labelId) { return await this.get(pathLabelById(boardId, labelId)); } async createLabel(boardId, payload) { return await this.post(pathLabels(boardId), payload); } async updateLabel(boardId, labelId, payload) { return await this.put(pathLabelById(boardId, labelId), payload); } async deleteLabel(boardId, labelId) { return await this.delete(pathLabelById(boardId, labelId)); } // ------------------------------ // Attachements // ------------------------------ async getCardAttachements(boardId, stackId, cardId) { return await this.get(pathCardAttachements(boardId, stackId, cardId)); } async getCardAttachement(boardId, stackId, cardId, attachementId) { return await this.get(pathCardAttachementsById(boardId, stackId, cardId, attachementId)); } async uploadCardAttachement(boardId, stackId, cardId, file) { return await this.post(pathCardAttachements(boardId, stackId, cardId), { type: "file", file }); } async updateCardAttachement(boardId, stackId, cardId, attachementId, file) { return await this.put(pathCardAttachementsById(boardId, stackId, cardId, attachementId), { type: "file", file }); } async deleteCardAttachement(boardId, stackId, cardId, attachementId) { return await this.delete(pathCardAttachementsById(boardId, stackId, cardId, attachementId)); } async restoreCardAttachement(boardId, stackId, cardId, attachementId) { return await this.put(pathCardRestoreAttachementsById(boardId, stackId, cardId, attachementId), null); } // ------------------------------ // OCS Config // ------------------------------ async getConfig() { return await this.ocsGet("/api/v1.0/config"); } async updateConfigKey(configId, key, value) { throw Error("Not implemented"); } async buildOcsErrorObject(request, response, options) { console.log("DeckClient.buildOcsErrorObject", request, response, options); console.log(await response.text()); return new DeckError({ code: response.status, message: response._data.message, url: request.toString(), timestamp: (/* @__PURE__ */ new Date()).toISOString(), details: response._data }); } } const bookmarkPrefix = "/index.php/apps/bookmarks"; const pathBookmark = `${bookmarkPrefix}/public/rest/v2/bookmark `; const pathBookmarkById = (bookmarkId) => `${bookmarkPrefix}/public/rest/v2/bookmark/${bookmarkId}`; const pathBookmarkImage = (bookmarkId) => `${bookmarkPrefix}/public/rest/v2/bookmark/${bookmarkId}/image`; const pathBookmarkFavicon = (bookmarkId) => `${bookmarkPrefix}/public/rest/v2/bookmark/${bookmarkId}/favicon`; const pathBookmarkClicked = `${bookmarkPrefix}/public/rest/v2/bookmark/click`; const pathTag = `${bookmarkPrefix}/public/rest/v2/tag`; const pathTagByName = (tagName) => `${bookmarkPrefix}/public/rest/v2/tag/${tagName}`; const pathFolder = `${bookmarkPrefix}/public/rest/v2/folder`; const pathFolderById = (folderId) => `${bookmarkPrefix}/public/rest/v2/folder/${folderId}`; const pathFolderByIdHash = (folderId) => `${bookmarkPrefix}/public/rest/v2/folder/${folderId}/hash`; const pathFolderBookmark = (folderId, bookmarkId) => `${bookmarkPrefix}/public/rest/v2/folder/${folderId}/bookmarks/${bookmarkId}`; const pathFolderChildOrder = (folderId) => `${bookmarkPrefix}/public/rest/v2/folder/${folderId}/childorder`; const pathFolderContent = (folderId) => `${bookmarkPrefix}/public/rest/v2/folder/${folderId}/children`; const pathFolderContentCount = (folderId) => `${bookmarkPrefix}/public/rest/v2/folder/${folderId}/count`; const pathFolderShare = (folderId) => `${bookmarkPrefix}/public/rest/v2/folder/${folderId}/shares`; const pathShareById = (shareId) => `${bookmarkPrefix}/public/rest/v2/share/${shareId}`; const pathClientLock = `${bookmarkPrefix}/public/rest/v2/lock`; class BookmarkClient extends BaseApiClient { // ------------------------------ // Bookmarks // ------------------------------ async queryBookmarks(query) { return (await this.get(pathBookmark, query)).data; } async createBookmark(body) { return (await this.post(pathBookmark, body)).item; } async updateBookmark(bookmarkId, body) { return (await this.put(pathBookmarkById(bookmarkId), body)).item; } async getBookmark(bookmarkId) { return (await this.get(pathBookmarkById(bookmarkId))).item; } async deleteBookmark(bookmarkId) { return (await this.delete(pathBookmarkById(bookmarkId))).status; } async getBookmarkImage(bookmarkId) { return await this.getBlob(pathBookmarkImage(bookmarkId)); } async getBookmarkFavicon(bookmarkId) { return await this.getBlob(pathBookmarkFavicon(bookmarkId)); } async clickBookmark(bookmarkUrl) { return (await this.post(pathBookmarkClicked, { url: bookmarkUrl })).status; } // ------------------------------ // Tags // ------------------------------ async getTags() { return await this.get(pathTag); } async renameTag(tagName, newTagName) { return (await this.put(pathTagByName(tagName), { name: newTagName })).status; } async deleteTag(tagName) { return (await this.delete(pathTagByName(tagName))).status; } // ------------------------------ // Folder // ------------------------------ async getFolderTree(query) { return (await this.get(pathFolder, query)).data; } async createFolder(body) { return (await this.post(pathFolder, body)).item; } async getFolder(folderId) { return (await this.get(pathFolderById(folderId))).item; } async updateFolder(folderId, body) { return (await this.put(pathFolderById(folderId), body)).item; } async hashFolder(folderId, fieldsToHash = []) { return (await this.get(pathFolderByIdHash(folderId), { fields: fieldsToHash })).data; } async deleteFolder(folderId) { return (await this.delete(pathFolderById(folderId))).status; } async addBookmarkToFolder(folderId, bookmarkId) { return (await this.post(pathFolderBookmark(folderId, bookmarkId), null)).status; } async deleteBookmarkFromFolder(folderId, bookmarkId) { return (await this.delete(pathFolderBookmark(folderId, bookmarkId))).status; } async getFolderContentOrder(folderId) { return (await this.get(pathFolderChildOrder(folderId))).data; } async setFolderContentOrder(folderId, order) { return (await this.patch(pathFolderChildOrder(folderId), order)).status; } async getFolderContent(folderId, layers = 1) { return (await this.get(pathFolderContent(folderId), { layers })).data; } async getFolderContentCount(folderId) { return (await this.get(pathFolderContentCount(folderId))).item; } // ------------------------------ // Folder // ------------------------------ async createFolderShare(folderId, shareBody) { return (await this.post(pathFolderShare(folderId), shareBody)).item; } async getShare(shareId) { return (await this.get(pathShareById(shareId))).item; } async getFolderShares(folderId) { return (await this.get(pathFolderShare(folderId))).data; } async updateFolderShare(shareId, shareBody) { return (await this.put(pathShareById(shareId), shareBody)).item; } async deleteFolderShare(shareId) { return (await this.delete(pathShareById(shareId))).status; } // ------------------------------ // Client Lock // ------------------------------ async aquireClientLock() { return (await this.post(pathClientLock, null)).status; } async deleteClientLock() { return (await this.delete(pathClientLock)).status; } } const notesPrefix = "/index.php/apps/notes/api/v1"; const pathNotes = `${notesPrefix}/notes`; const pathNotesById = (noteId) => `${notesPrefix}/notes/${noteId}`; const pathNotesSettings = `${notesPrefix}/settings`; class NotesClient extends BaseApiClient { async queryNotes(query) { return await this.get(pathNotes, query); } async getNote(noteId) { return await this.get(pathNotesById(noteId)); } async createNote(payload) { return await this.post(pathNotes, payload); } async updateNote(noteId, payload) { return await this.put(pathNotesById(noteId), payload); } async deleteNote(noteId) { return await this.delete(pathNotesById(noteId)); } async getSettings() { return await this.get(pathNotesSettings); } async updateSettings(payload) { return await this.put(pathNotesSettings, payload); } } const pathCapabilities = "/ocs/v2.php/cloud/capabilities"; const pathUserInfo = (userId) => `/ocs/v2.php/cloud/users/${userId}`; const pathAutocompleteUsers = "/ocs/v2.php/core/autocomplete/get"; class GeneralClient extends BaseApiClient { async getCapabilities() { return (await this.ocsGet(pathCapabilities)).ocs.data; } async getUserInfo(userId) { return (await this.ocsGet(pathUserInfo(userId))).ocs.data; } async queryUsernames(query) { return (await this.ocsGet(pathAutocompleteUsers, { search: query })).ocs.data; } } const pathAnonymousLoginV2 = "/index.php/login/v2"; const pathAnonymousPolling = "/index.php/login/v2/poll"; class AuthClient extends BaseApiClient { async initiateAnonymousLogin() { return this.post(pathAnonymousLoginV2, null); } async startPolling(token, maxCounter = 240, waitTimeMs = 300) { let counter = 0; let response = null; while (counter < maxCounter) { await new Promise((r) => setTimeout(r, waitTimeMs)); try { response = await this.post(pathAnonymousPolling, { token }); } catch (e) { if (e instanceof ApiError) { counter += 1; } else { throw Error(`Polling error, received ${e}`); } } } if (!response) { throw new ApiError({ code: 404, message: "Invalid polling result", url: pathAnonymousPolling, timestamp: (/* @__PURE__ */ new Date()).toDateString() }, "AuthError"); } return response; } } export { AuthClient as A, BookmarkClient as B, DeckClient as D, GeneralClient as G, NotesClient as N };