@samuelspagl/nc-api-client
Version:
A simple API client for Nextcloud Apps.
543 lines (527 loc) • 19.4 kB
JavaScript
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 };