UNPKG

nextcloud-node-client

Version:

Nextcloud client API for node.js TypeScript applications

1,105 lines (1,098 loc) 119 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandStatus = exports.FileSystemFolder = exports.UploadFolderCommand = exports.UploadFilesCommand = exports.GetFilesRecursivelyCommand = exports.UserProperty = exports.UserGroup = exports.User = exports.RequestResponseLogEntry = exports.RequestResponseLog = exports.SharePermission = exports.Share = exports.Server = exports.FakeServer = exports.Tag = exports.FileSystemElement = exports.File = exports.Folder = exports.Environment = exports.ClientError = exports.Client = exports.UserGroupDoesNotExistError = exports.UserGroupDeletionFailedError = exports.UserGroupAlreadyExistsError = exports.UserUpdateError = exports.UserResendWelcomeEmailError = exports.UserCreateError = exports.UserAlreadyExistsError = exports.UserNotFoundError = exports.QueryOffsetError = exports.QueryLimitError = exports.OperationFailedError = exports.InsufficientPrivilegesError = exports.InvalidServiceResponseFormatError = exports.CommandAlreadyExecutedError = void 0; // this must be the first const dotenv_1 = require("dotenv"); dotenv_1.config(); const uploadFilesCommand_1 = __importDefault(require("./uploadFilesCommand")); exports.UploadFilesCommand = uploadFilesCommand_1.default; const uploadFolderCommand_1 = __importDefault(require("./uploadFolderCommand")); exports.UploadFolderCommand = uploadFolderCommand_1.default; const getFilesRecursivelyCommand_1 = __importDefault(require("./getFilesRecursivelyCommand")); exports.GetFilesRecursivelyCommand = getFilesRecursivelyCommand_1.default; const command_1 = require("./command"); Object.defineProperty(exports, "CommandStatus", { enumerable: true, get: function () { return command_1.CommandStatus; } }); const debug_1 = __importDefault(require("debug")); const fast_xml_parser_1 = __importDefault(require("fast-xml-parser")); const node_fetch_1 = require("node-fetch"); const path_1 = __importStar(require("path")); const environment_1 = __importDefault(require("./environment")); exports.Environment = environment_1.default; const environmentVcapServices_1 = __importDefault(require("./environmentVcapServices")); const error_1 = __importStar(require("./error")); exports.ClientError = error_1.default; Object.defineProperty(exports, "CommandAlreadyExecutedError", { enumerable: true, get: function () { return error_1.CommandAlreadyExecutedError; } }); Object.defineProperty(exports, "QueryLimitError", { enumerable: true, get: function () { return error_1.QueryLimitError; } }); Object.defineProperty(exports, "QueryOffsetError", { enumerable: true, get: function () { return error_1.QueryOffsetError; } }); Object.defineProperty(exports, "InsufficientPrivilegesError", { enumerable: true, get: function () { return error_1.InsufficientPrivilegesError; } }); Object.defineProperty(exports, "InvalidServiceResponseFormatError", { enumerable: true, get: function () { return error_1.InvalidServiceResponseFormatError; } }); Object.defineProperty(exports, "OperationFailedError", { enumerable: true, get: function () { return error_1.OperationFailedError; } }); Object.defineProperty(exports, "UserGroupAlreadyExistsError", { enumerable: true, get: function () { return error_1.UserGroupAlreadyExistsError; } }); Object.defineProperty(exports, "UserGroupDeletionFailedError", { enumerable: true, get: function () { return error_1.UserGroupDeletionFailedError; } }); Object.defineProperty(exports, "UserResendWelcomeEmailError", { enumerable: true, get: function () { return error_1.UserResendWelcomeEmailError; } }); Object.defineProperty(exports, "UserGroupDoesNotExistError", { enumerable: true, get: function () { return error_1.UserGroupDoesNotExistError; } }); Object.defineProperty(exports, "UserNotFoundError", { enumerable: true, get: function () { return error_1.UserNotFoundError; } }); Object.defineProperty(exports, "UserAlreadyExistsError", { enumerable: true, get: function () { return error_1.UserAlreadyExistsError; } }); Object.defineProperty(exports, "UserCreateError", { enumerable: true, get: function () { return error_1.UserCreateError; } }); Object.defineProperty(exports, "UserUpdateError", { enumerable: true, get: function () { return error_1.UserUpdateError; } }); const fakeServer_1 = __importDefault(require("./fakeServer")); exports.FakeServer = fakeServer_1.default; const file_1 = __importDefault(require("./file")); exports.File = file_1.default; const fileSystemElement_1 = __importDefault(require("./fileSystemElement")); exports.FileSystemElement = fileSystemElement_1.default; const fileSystemFolder_1 = __importDefault(require("./fileSystemFolder")); exports.FileSystemFolder = fileSystemFolder_1.default; const folder_1 = __importDefault(require("./folder")); exports.Folder = folder_1.default; const httpClient_1 = require("./httpClient"); const requestResponseLog_1 = __importDefault(require("./requestResponseLog")); exports.RequestResponseLog = requestResponseLog_1.default; const requestResponseLogEntry_1 = __importDefault(require("./requestResponseLogEntry")); exports.RequestResponseLogEntry = requestResponseLogEntry_1.default; const server_1 = __importDefault(require("./server")); exports.Server = server_1.default; const share_1 = __importStar(require("./share")); exports.Share = share_1.default; Object.defineProperty(exports, "SharePermission", { enumerable: true, get: function () { return share_1.SharePermission; } }); const tag_1 = __importDefault(require("./tag")); exports.Tag = tag_1.default; const userGroup_1 = __importDefault(require("./userGroup")); exports.UserGroup = userGroup_1.default; const user_1 = __importStar(require("./user")); exports.User = user_1.default; Object.defineProperty(exports, "UserProperty", { enumerable: true, get: function () { return user_1.UserProperty; } }); const debug = debug_1.default("NCClient"); /** * The nextcloud client is the root object to access the remote api of the nextcloud server.<br> */ class Client { /** * Creates a new instance of a nextcloud client.<br/> * Use the server to provide server connectivity information to the client.<br/> * (The FakeServer is only used for testing and code coverage)<br/><br/> * If the server is not provided the client tries to find the connectivity information * in the environment.<br/> * If a <b>VCAP_SERVICES</b> environment variable is available, the client tries to find * a service with the name <b>"nextcloud"</b> in the user-provides-services section.<br/> * If no VCAP_SERVICES are available, the client uses the following variables * from the envirnonment for the connectivity:<br/> * <ul> * <li>NEXTCLOUD_URL - the WebDAV url of the nextcloud server</li> * <li>NEXTCLOUD_USERNAME - the user name</li> * <li>NEXTCLOUD_PASSWORD - the application password</li> * </ul> * @param server optional server information to connection to a nextcloud server * @constructor */ constructor(server) { this.logRequestResponse = false; debug("constructor"); this.nextcloudOrigin = ""; this.nextcloudAuthHeader = ""; this.nextcloudRequestToken = ""; this.webDAVUrl = ""; this.userId = ""; // if no server is provided, try to get a server from VCAP_S environment "nextcloud" instance // If no VCAP_S environment exists try from environment if (!server) { try { const env = new environmentVcapServices_1.default("nextcloud"); server = env.getServer(); } catch (e) { const env = new environment_1.default(); server = env.getServer(); } } if (server instanceof server_1.default) { this.proxy = server.proxy; debug("constructor: webdav url %s", server.url); if (server.url.indexOf(Client.webDavUrlPath) === -1) { // not a valid nextcloud url throw new error_1.default(`The provided nextcloud url "${server.url}" does not comply to the nextcloud url standard, "${Client.webDavUrlPath}" is missing`, "ERR_INVALID_NEXTCLOUD_WEBDAV_URL"); } this.nextcloudOrigin = server.url.substr(0, server.url.indexOf(Client.webDavUrlPath)); debug("constructor: nextcloud url %s", this.nextcloudOrigin); this.userId = server.basicAuth.username; this.nextcloudAuthHeader = "Basic " + Buffer.from(server.basicAuth.username + ":" + server.basicAuth.password).toString("base64"); this.nextcloudRequestToken = ""; if (server.url.slice(-1) === "/") { this.webDAVUrl = server.url.slice(0, -1); } else { this.webDAVUrl = server.url; } this.logRequestResponse = server.logRequestResponse; const options = { authorizationHeader: this.nextcloudAuthHeader, logRequestResponse: this.logRequestResponse, origin: this.nextcloudOrigin, proxy: this.proxy, }; this.httpClient = new httpClient_1.HttpClient(options); } if (server instanceof fakeServer_1.default) { this.fakeServer = server; this.webDAVUrl = "https://fake.server" + Client.webDavUrlPath; } } /** * returns the used and free quota of the nextcloud account */ getQuota() { return __awaiter(this, void 0, void 0, function* () { debug("getQuota"); const requestInit = { method: "PROPFIND", }; const response = yield this.getHttpResponse(this.webDAVUrl + "/", requestInit, [207], { description: "Client get quota" }); const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, Client.webDavUrlPath + "/"); let quota = null; for (const prop of properties) { if (prop["quota-available-bytes"]) { quota = { available: "unlimited", used: prop["quota-used-bytes"], }; if (prop["quota-available-bytes"] > 0) { quota.available = prop["quota-available-bytes"]; } } } if (!quota) { debug("Error, quota not available: %s ", JSON.stringify(properties, null, 4)); throw new error_1.default(`Error, quota not available`, "ERR_QUOTA_NOT_AVAILABLE"); } debug("getQuota = %O", quota); return quota; }); } // *************************************************************************************** // tags // *************************************************************************************** /** * creates a new tag, if not already existing * this function will fail with http 403 if the user does not have admin privileges * @param tagName the name of the tag * @returns tagId */ createTag(tagName) { return __awaiter(this, void 0, void 0, function* () { debug("createTag"); let tag; // is the tag already existing? tag = yield this.getTagByName(tagName); if (tag) { return tag; } // tag does not exist, create tag const requestInit = { body: `{ "name": "${tagName}", "userVisible": true, "userAssignable": true, "canAssign": true }`, headers: new node_fetch_1.Headers({ "Content-Type": "application/json" }), method: "POST", }; const response = yield this.getHttpResponse(this.nextcloudOrigin + "/remote.php/dav/systemtags/", requestInit, [201], { description: "Tag create" }); const tagString = response.headers.get("Content-Location"); debug("createTag new tagId %s, tagName %s", tagString, tagName); if (tagString === "" || tagString === null) { throw new error_1.default(`Error, tag with name '${tagName}' could not be created`, "ERR_TAG_CREATE_FAILED"); } // the number id of the tag is the last element in the id (path) const tagId = this.getTagIdFromHref(tagString); tag = new tag_1.default(this, tagId, tagName, true, true, true); return tag; }); } /** * returns a tag identified by the name or null if not found * @param tagName the name of the tag * @returns tag or null */ getTagByName(tagName) { return __awaiter(this, void 0, void 0, function* () { debug("getTag"); const tags = yield this.getTags(); for (const tag of tags) { if (tag.name === tagName) { return tag; } } return null; }); } /** * returns a tag identified by the id or null if not found * @param tagId the id of the tag * @returns tag or null */ getTagById(tagId) { return __awaiter(this, void 0, void 0, function* () { debug("getTagById"); const tags = yield this.getTags(); for (const tag of tags) { if (tag.id === tagId) { return tag; } } return null; }); } /** * deletes the tag by id * this function will fail with http 403 if the user does not have admin privileges * @param tagId the id of the tag like "/remote.php/dav/systemtags/234" */ deleteTag(tagId) { return __awaiter(this, void 0, void 0, function* () { debug("deleteTag tagId: $s", tagId); const requestInit = { method: "DELETE", }; const response = yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/systemtags/${tagId}`, requestInit, [204, 404], { description: "Tag delete" }); }); } /** * deletes all visible assignable tags * @throws Error */ deleteAllTags() { return __awaiter(this, void 0, void 0, function* () { debug("deleteAllTags"); const tags = yield this.getTags(); for (const tag of tags) { // debug("deleteAllTags tag: %O", tag); yield tag.delete(); } }); } /** * returns a list of tags * @returns array of tags */ getTags() { return __awaiter(this, void 0, void 0, function* () { debug("getTags PROPFIND %s", this.nextcloudOrigin + "/remote.php/dav/systemtags/"); const requestInit = { body: `<?xml version="1.0"?> <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns"> <d:prop> <oc:id /> <oc:display-name /> <oc:user-visible /> <oc:user-assignable /> <oc:can-assign /> </d:prop> </d:propfind>`, method: "PROPFIND", }; const relUrl = `/remote.php/dav/systemtags/`; const response = yield this.getHttpResponse(this.nextcloudOrigin + relUrl, requestInit, [207], { description: "Tags get" }); const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, relUrl + "/*"); const tags = []; for (const prop of properties) { tags.push(new tag_1.default(this, this.getTagIdFromHref(prop._href), prop["display-name"], prop["user-visible"], prop["user-assignable"], prop["can-assign"])); } return tags; }); } /** * returns the list of tag names and the tag ids * @param fileId the id of the file */ getTagsOfFile(fileId) { return __awaiter(this, void 0, void 0, function* () { debug("getTagsOfFile"); const requestInit = { body: `<?xml version="1.0"?> <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns"> <d:prop> <oc:id /> <oc:display-name /> <oc:user-visible /> <oc:user-assignable /> <oc:can-assign /> </d:prop> </d:propfind>`, method: "PROPFIND", }; const relUrl = `/remote.php/dav/systemtags-relations/files/${fileId}`; const response = yield this.getHttpResponse(`${this.nextcloudOrigin}${relUrl}`, requestInit, [207], { description: "File get tags" }); const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, relUrl + "/*"); const tagMap = new Map(); for (const prop of properties) { tagMap.set(prop["display-name"], prop.id); } debug("tags of file %O", tagMap); return tagMap; }); } /** * removes the tag from the file * @param fileId the file id * @param tagId the tag id */ removeTagOfFile(fileId, tagId) { return __awaiter(this, void 0, void 0, function* () { debug("removeTagOfFile tagId: $s fileId:", tagId, fileId); const requestInit = { method: "DELETE", }; const response = yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/systemtags-relations/files/${fileId}/${tagId}`, requestInit, [204, 404], { description: "File remove tag" }); return; }); } /** * returns the id of the file or -1 of not found * @returns id of the file or -1 if not found */ getFileId(fileUrl) { return __awaiter(this, void 0, void 0, function* () { debug("getFileId"); const requestInit = { body: ` <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns"> <d:prop> <oc:fileid /> </d:prop> </d:propfind>`, method: "PROPFIND", }; const response = yield this.getHttpResponse(fileUrl, requestInit, [207], { description: "File get id" }); const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, ""); for (const prop of properties) { if (prop.fileid) { return prop.fileid; } } debug("getFileId no file id found for %s", fileUrl); return -1; }); } getFolderContents(folderName) { return __awaiter(this, void 0, void 0, function* () { debug("getFolderContents"); const requestInit = { body: `<?xml version="1.0"?> <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns" xmlns:ocs="http://open-collaboration-services.org/ns"> <d:prop> <d:getlastmodified /> <d:getetag /> <d:getcontenttype /> <d:resourcetype /> <oc:fileid /> <oc:permissions /> <oc:size /> <d:getcontentlength /> <nc:has-preview /> <nc:mount-type /> <nc:is-encrypted /> <ocs:share-permissions /> <oc:tags /> <oc:favorite /> <oc:comments-unread /> <oc:owner-id /> <oc:owner-display-name /> <oc:share-types /> </d:prop> </d:propfind>`, method: "PROPFIND", }; const url = `${this.webDAVUrl}${folderName}`; const response = yield this.getHttpResponse(url, requestInit, [207], { description: "Folder get contents" }); const folderContents = []; let properties; try { properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, ""); } catch (e) { return folderContents; } // tslint:disable-next-line:no-empty for (const prop of properties) { let fileName = decodeURI(prop._href.substr(prop._href.indexOf(Client.webDavUrlPath) + 18)); if (fileName.endsWith("/")) { fileName = fileName.slice(0, -1); } if ((url + "/").endsWith(decodeURI(prop._href))) { continue; } const folderContentsEntry = {}; folderContentsEntry.lastmod = prop.getlastmodified; folderContentsEntry.fileid = prop.fileid; folderContentsEntry.basename = fileName.split("/").reverse()[0]; folderContentsEntry.filename = fileName; if (prop.getcontenttype) { folderContentsEntry.mime = prop.getcontenttype; folderContentsEntry.size = prop.getcontentlength; folderContentsEntry.type = "file"; } else { folderContentsEntry.type = "directory"; } if (folderContentsEntry.basename !== "") { folderContents.push(folderContentsEntry); } } // debug("folderContentsEntry $s", JSON.stringify(folderContents, null, 4)); return folderContents; }); } /** * creates a folder and all parent folders in the path if they do not exist * @param folderName name of the folder /folder/subfolder/subfolder * @returns a folder object */ createFolder(folderName) { return __awaiter(this, void 0, void 0, function* () { folderName = this.sanitizeFolderName(folderName); debug("createFolder: folderName=%s", folderName); const parts1 = folderName.split("/"); for (const p of parts1) { if ((p) === "." || p === "..") { throw new error_1.default(`Error creating folder, folder name "${folderName}" invalid`, "ERR_CREATE_FOLDER_INVALID_FOLDER_NAME"); } } let folder; folder = yield this.getFolder(folderName); if (folder) { debug("createFolder: folder already available %O", folder.name); return folder; } else { // try to do a simple create with the complete path try { debug("createFolder: folder = %s", folderName); yield this.createFolderInternal(folderName); } catch (e) { // create all folders in the path const parts = folderName.split("/"); parts.shift(); let folderPath = ""; debug("createFolder: parts = %O", parts); for (const part of parts) { debug("createFolder: part = %O", part); folderPath += "/" + part; folder = yield this.getFolder(folderPath); if (folder === null) { debug("createFolder: folder not available"); // folder not available debug("createFolder: folder = %s", folderPath); yield this.createFolderInternal(folderPath); } else { debug("createFolder: folder already available %s", folderPath); } } } } folder = yield this.getFolder(folderName); if (folder) { debug("createFolder: new folder %O", folder.name); return folder; } else { throw new error_1.default(`Error creating folder, folder name "${folderName}" `, "ERR_CREATE_FOLDER_FAILED"); } }); } /** * deletes a file * @param fileName name of folder "/f1/f2/f3/x.txt" */ deleteFile(fileName) { return __awaiter(this, void 0, void 0, function* () { const url = this.webDAVUrl + fileName; debug("deleteFile %s", url); const requestInit = { method: "DELETE", }; try { yield this.getHttpResponse(url, requestInit, [204], { description: "File delete" }); } catch (err) { debug("Error in deleteFile %s %s %s", err.message, requestInit.method, url); throw err; } }); } /** * deletes a folder * @param folderName name of folder "/f1/f2/f3" */ deleteFolder(folderName) { return __awaiter(this, void 0, void 0, function* () { folderName = this.sanitizeFolderName(folderName); debug("deleteFolder:"); const folder = yield this.getFolder(folderName); if (folder) { yield this.deleteFile(folderName); } }); } /** * get the root folder object * @returns {Promise<Folder>} the root folder */ getRootFolder() { return new folder_1.default(this, "/", "", ""); } /** * returns an array of file system objects that have all given tags assigned (AND) * @param {Tag[]} tags array of tags * @async * @returns {Promise<FileSystemElement[]>} returns an array of file system objects */ getFileSystemElementByTags(tags) { return __awaiter(this, void 0, void 0, function* () { debug("getFileSystemElementByTags %s", tags.join(", ")); let filterRule = ""; for (const tag of tags) { filterRule += `<oc:systemtag>${tag.id}</oc:systemtag>`; } const urlSuffix = `/remote.php/dav/files/`; const url = `${this.nextcloudOrigin}${urlSuffix}${this.userId}`; const body = `<?xml version="1.0"?> <oc:filter-files xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\" xmlns:ocs=\"http://open-collaboration-services.org/ns\"> <d:prop> <d:getcontenttype /> <oc:fileid /> </d:prop> <oc:filter-rules> ${filterRule} </oc:filter-rules> </oc:filter-files>`; const requestInit = { body, // headers: new Headers({ Depth: "0" }), method: "REPORT", }; let response; try { response = yield this.getHttpResponse(url, requestInit, [207], { description: "Get FileSystemElements by tags" }); } catch (err) { debug("Error in stat %s %s %s %s", err.message, requestInit.method, url); throw err; } const result = []; let properties = []; try { properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, ""); } catch (e) { return result; } for (const prop of properties) { let fse = null; let name = prop._href; // remove the first two elements from the path name = name.replace(urlSuffix, ""); const a = name.split("/"); a.shift(); name = "/" + a.join("/"); // console.log(name); if (prop.getcontenttype) { fse = yield this.getFile(name); } else { fse = yield this.getFolder(name); } result.push(fse); } return result; }); } /** * get a folder object from a path string * @param {string} folderName Name of the folder like "/company/branches/germany" * @returns {Promise<Folder | null>} null if the folder does not exist or an folder object */ getFolder(folderName) { return __awaiter(this, void 0, void 0, function* () { folderName = this.sanitizeFolderName(folderName); debug("getFolder %s", folderName); // return root folder if (folderName === "/" || folderName === "") { return this.getRootFolder(); } try { const stat = yield this.stat(folderName); debug(": SUCCESS!!"); if (stat.type !== "file") { return new folder_1.default(this, stat.filename.replace(/\\/g, "/"), stat.basename, stat.lastmod, stat.fileid); } else { debug("getFolder: found object is file not a folder"); return null; } } catch (e) { debug("getFolder: exception occurred calling stat %O", e.message); return null; } }); } /** * get a array of folders from a folder path string * @param folderName Name of the folder like "/company/branches/germany" * @returns array of folder objects */ getSubFolders(folderName) { return __awaiter(this, void 0, void 0, function* () { debug("getSubFolders: folder %s", folderName); const folders = []; folderName = this.sanitizeFolderName(folderName); const folderElements = yield this.Contents(folderName, true); for (const folderElement of folderElements) { debug("getSubFolders: adding subfolders %s", folderElement.filename); folders.push(new folder_1.default(this, folderElement.filename.replace(/\\/g, "/"), folderElement.basename, folderElement.lastmod, folderElement.fileid)); } return folders; }); } /** * get files of a folder * @param {string} folderName Name of the folder like "/company/branches/germany" * @param {FolderGetFilesOptions} options options for filtering and paging * @returns array of file objects */ getFiles(folderName, options) { return __awaiter(this, void 0, void 0, function* () { debug("getFiles: folder %s", folderName); const files = []; folderName = this.sanitizeFolderName(folderName); const fileElements = yield this.Contents(folderName, false); for (const folderElement of fileElements) { debug("getFiles: adding file %s", folderElement.filename); // debug("getFiles: adding file %O", folderElement); let file = new file_1.default(this, folderElement.filename.replace(/\\/g, "/"), folderElement.basename, folderElement.lastmod, folderElement.size, folderElement.mime, folderElement.fileid); if (options && options.filterFile) { file = options.filterFile(file); } if (file) { files.push(file); } } return files; }); } /** * create a new file of overwrites an existing file * @param fileName the file name /folder1/folder2/filename.txt * @param data the buffer object */ createFile(fileName, data) { return __awaiter(this, void 0, void 0, function* () { if (fileName.startsWith("./")) { fileName = fileName.replace("./", "/"); } const baseName = path_1.default.basename(fileName); const folderName = path_1.default.dirname(fileName); debug("createFile folder name %s base name %s", folderName, baseName); // ensure that we have a folder yield this.createFolder(folderName); yield this.putFileContents(fileName, data); let file; file = yield this.getFile(fileName); if (!file) { throw new error_1.default(`Error creating file, file name "${fileName}"`, "ERR_CREATE_FILE_FAILED"); } return file; }); } /** * returns a nextcloud file object * @param fileName the full file name /folder1/folder2/file.pdf */ getFile(fileName) { return __awaiter(this, void 0, void 0, function* () { debug("getFile fileName = %s", fileName); try { const stat = yield this.stat(fileName); debug(": SUCCESS!!"); if (stat.type === "file") { return new file_1.default(this, stat.filename.replace(/\\/g, "/"), stat.basename, stat.lastmod, stat.size, stat.mime || "", stat.fileid || -1); } else { debug("getFile: found object is a folder not a file"); return null; } } catch (e) { debug("getFile: exception occurred calling stat %O", e.message); return null; } }); } /** * renames the file or moves it to an other location * @param sourceFileName source file name * @param targetFileName target file name */ moveFile(sourceFileName, targetFileName) { return __awaiter(this, void 0, void 0, function* () { const url = this.webDAVUrl + sourceFileName; const destinationUrl = this.webDAVUrl + targetFileName; debug("moveFile from '%s' to '%s'", url, destinationUrl); const requestInit = { headers: new node_fetch_1.Headers({ Destination: destinationUrl }), method: "MOVE", }; try { yield this.getHttpResponse(url, requestInit, [201], { description: "File move" }); } catch (err) { debug("Error in move file %s %s source: %s destination: %s", err.message, requestInit.method, url, destinationUrl); throw new error_1.default("Error: moving file failed: source=" + sourceFileName + " target=" + targetFileName + " - " + err.message, "ERR_FILE_MOVE_FAILED"); } const targetFile = yield this.getFile(targetFileName); if (!targetFile) { throw new error_1.default("Error: moving file failed: source=" + sourceFileName + " target=" + targetFileName, "ERR_FILE_MOVE_FAILED"); } return targetFile; }); } /** * renames the folder or moves it to an other location * @param sourceFolderName source folder name * @param tarName target folder name */ moveFolder(sourceFolderName, tarName) { return __awaiter(this, void 0, void 0, function* () { const url = this.webDAVUrl + sourceFolderName; const destinationUrl = this.webDAVUrl + tarName; debug("moveFolder from '%s' to '%s'", url, destinationUrl); const requestInit = { headers: new node_fetch_1.Headers({ Destination: destinationUrl }), method: "MOVE", }; try { yield this.getHttpResponse(url, requestInit, [201], { description: "Folder move" }); } catch (err) { debug("Error in move folder %s %s source: %s destination: %s", err.message, requestInit.method, url, destinationUrl); throw new error_1.default("Error: moving folder failed: source=" + sourceFolderName + " target=" + tarName + " - " + err.message, "ERR_FOLDER_MOVE_FAILED"); } const tar = yield this.getFolder(tarName); if (!tar) { throw new error_1.default("Error: moving folder failed: source=" + sourceFolderName + " target=" + tarName, "ERR_FOLDER_MOVE_FAILED"); } return tar; }); } /** * returns the content of a file * @param fileName name of the file /d1/file1.txt * @returns Buffer with file content */ getContent(fileName) { return __awaiter(this, void 0, void 0, function* () { const url = this.webDAVUrl + fileName; debug("getContent GET %s", url); const requestInit = { method: "GET", }; let response; try { response = yield this.getHttpResponse(url, requestInit, [200], { description: "File get content" }); } catch (err) { debug("Error getContent %s - error %s", url, err.message); throw err; } return Buffer.from(yield response.buffer()); }); } /** * returns the content of a file * @param fileName name of the file /d1/file1.txt * @returns Buffer with file content */ pipeContentStream(fileName, destination) { return __awaiter(this, void 0, void 0, function* () { const url = this.webDAVUrl + fileName; debug("getContent GET %s", url); const requestInit = { method: "GET", }; let response; try { response = yield this.getHttpResponse(url, requestInit, [200], { description: "File pipe content stream" }); } catch (err) { debug("Error getContent %s - error %s", url, err.message); throw err; } response.body.pipe(destination); }); } /** * returns the link to a file for downloading * @param fileName name of the file /folder1/folder1.txt * @returns url */ getLink(fileName) { debug("getLink of %s", fileName); return this.webDAVUrl + fileName; } /** * returns the url to the file in the nextcloud UI * @param fileId the id of the file */ getUILink(fileId) { debug("getUILink of %s", fileId); return `${this.nextcloudOrigin}/apps/files/?fileid=${fileId}`; } /** * adds a tag to a file or folder * if the tag does not exist, it is automatically created * if the tag is created, the user must have damin privileges * @param fileId the id of the file * @param tagName the name of the tag * @returns nothing * @throws Error */ addTagToFile(fileId, tagName) { return __awaiter(this, void 0, void 0, function* () { debug("addTagToFile file:%s tag:%s", fileId, tagName); const tag = yield this.createTag(tagName); if (!tag.canAssign) { throw new error_1.default(`Error: No permission to assign tag "${tagName}" to file. Tag is not assignable`, "ERR_TAG_NOT_ASSIGNABLE"); } const addTagBody = { canAssign: tag.canAssign, id: tag.id, name: tag.name, userAssignable: tag.assignable, userVisible: tag.visible, }; const requestInit = { body: JSON.stringify(addTagBody, null, 4), headers: new node_fetch_1.Headers({ "Content-Type": "application/json" }), method: "PUT", }; yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/systemtags-relations/files/${fileId}/${tag.id}`, requestInit, [201, 409], { description: "File add tag" }); // created or conflict }); } // *************************************************************************************** // activity // *************************************************************************************** /* @todo to be refactored to eventing public async getActivities(): Promise<string[]> { const result: string[] = []; const requestInit: RequestInit = { headers: new Headers({ "ocs-apirequest": "true" }), method: "GET", }; const response: Response = await this.getHttpResponse( this.nextcloudOrigin + "/ocs/v2.php/apps/activity/api/v2/activity/files?format=json&previews=false&since=97533", requestInit, [200], { description: "Activities get" }); const responseObject: any = await response.json(); // @todo for (const res of responseObject.ocs.data) { debug(JSON.stringify({ acivityId: res.activity_id, objects: res.objects, type: res.type, }, null, 4)); } // debug("getActivities: responseObject %s", JSON.stringify(responseObject, null, 4)); return result; } */ // *************************************************************************************** // comments // *************************************************************************************** /** * adds a comment to a file * @param fileId the id of the file * @param comment the comment to be added to the file */ addCommentToFile(fileId, comment) { return __awaiter(this, void 0, void 0, function* () { debug("addCommentToFile file:%s comment:%s", fileId, comment); const addCommentBody = { actorType: "users", message: comment, objectType: "files", verb: "comment", }; const requestInit = { body: JSON.stringify(addCommentBody, null, 4), headers: new node_fetch_1.Headers({ "Content-Type": "application/json" }), method: "POST", }; yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/comments/files/${fileId}`, requestInit, [201], { description: "File add comment" }); // created }); } /** * returns comments of a file / folder * @param fileId the id of the file / folder * @param top number of comments to return * @param skip the offset * @returns array of comment strings * @throws Exception */ getFileComments(fileId, top, skip) { return __awaiter(this, void 0, void 0, function* () { debug("getFileComments fileId:%s", fileId); if (!top) { top = 30; } if (!skip) { skip = 0; } const requestInit = { body: `<?xml version="1.0" encoding="utf-8" ?> <oc:filter-comments xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns"> <oc:limit>${top}</oc:limit> <oc:offset>${skip}</oc:offset> </oc:filter-comments>`, method: "REPORT", }; const response = yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/comments/files/${fileId}`, requestInit, [207], { description: "File get comments" }); const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, ""); const comments = []; for (const prop of properties) { comments.push(prop.message); } return comments; }); } /** * returns system information about the nextcloud server and the nextcloud client */ getSystemInfo() { return __awaiter(this, void 0, void 0, function* () { const requestInit = { headers: this.getOcsHeaders(), method: "GET", }; const response = yield this.getHttpResponse(this.nextcloudOrigin + "/ocs/v2.php/apps/serverinfo/api/v1/info", requestInit, [200], { description: "SystemInfo get" }); const rawResult = yield response.json(); // validate the raw result let system = {}; let storage = {}; let shares = {}; let server = {}; let activeUsers = {}; if (rawResult && rawResult.ocs && rawResult.ocs.data) { if (rawResult.ocs.data.nextcloud) { if (rawResult.ocs.data.nextcloud.system) { system = rawResult.ocs.data.nextcloud.system; } else { throw new error_1.default("Fatal Error: nextcloud data.nextcloud.system missing", "ERR_SYSTEM_INFO_MISSING_DATA"); } if (rawResult.ocs.data.nextcloud.storage) { storage = rawResult.ocs.data.nextcloud.storage; } else { throw new error_1.default("Fatal Error: nextcloud data.nextcloud.storage missing", "ERR_SYSTEM_INFO_MISSING_DATA"); } if (rawResult.ocs.data.nextcloud.shares) { shares = rawResult.ocs.data.nextcloud.shares; } else { throw new error_1.default("Fatal Error: nextcloud data.nextcloud.shares missing", "ERR_SYSTEM_INFO_MISSING_DATA"); } } else { throw new error_1.default("Fatal Error: nextcloud data.nextcloud missing", "ERR_SYSTEM_INFO_MISSING_DATA"); } if (rawResult.ocs.data.server) { server = rawResult.ocs.data.server; } else { throw new error_1.default("Fatal Error: nextcloud data.server missing", "ERR_SYSTEM_INFO_MISSING_DATA"); } if (rawResult.ocs.data.activeUsers) { activeUsers = rawResult.ocs.data.activeUsers; } else { throw new error_1.default("Fatal Error: nextcloud data.activeUsers missing", "ERR_SYSTEM_INFO_MISSING_DATA"); } } else { throw new error_1.default("Fatal Error: nextcloud system data missing", "ERR_SYSTEM_INFO_MISSING_DATA"); } const result = { activeUsers, nextcloud: { shares, storage, system, }, nextcloudClient: { version: require("../package.json").version, }, server, }; return result; }); } getSystemBasicData() { return __awaiter(this, void 0, void 0, function* () { const requestInit = { headers: this.getOcsHeaders(), method: "GET", }; const response = yield this.getHttpResponse(this.nextcloudOrigin + "/ocs/v2.php/apps/serverinfo/api/v1/basicdata", requestInit, [200], { description: "System Basic Data get" }); const rawResult = yield response.json(); // console.log("Basic Data\n", JSON.stringify(rawResult)); let result; if (rawResult && rawResult.ocs && rawResult.ocs.data && rawResult.ocs.data.servertime && rawResult.ocs.data.uptime && rawResult.ocs.data.timeservers) { result = { serverTimeString: rawResult.ocs.data.servertime.replace("\n", ""), uptimeString: rawResult.ocs.data.uptime.replace("\n", ""), timeServersString: rawResult.ocs.data.timeservers.trim(), }; } else { throw new error_1.default("Fatal Error: nextcloud basic data missing", "ERR_SYSTEM_INFO_MISSING_DATA"); } return result; }); }