/** @namespace Unifile */
'use strict';
/**
* The built-in Node.js WritableStream class
* @external WritableStream
* @see https://nodejs.org/api/stream.html#stream_writable_streams
*/
/**
* The built-in Node.js ReadableStream class
* @external ReadableStream
* @see https://nodejs.org/api/stream.html#stream_readable_streams
*/
/**
* Bluebird Promise class
* @external Promise
* @see http://bluebirdjs.com/docs/api-reference.html
*/
/**
* State of the connector
* @typedef {Object} ConnectorState
* @property {boolean} isLoggedIn - Flag wether the user is logged in.
* @property {boolean} isOAuth - Flag wether the connector uses OAuth as authentication mechanism.
* @property {string} username - Name used to log in.
*/
/**
* Static infos of the connector
* @typedef {Object} ConnectorStaticInfos
* @property {string} name - ID of the connector. This will be use to select the connector in unifile.
* @property {string} displayName - Name that should be display. Allows characters forbidden in name.
* @property {string} icon - Path to an icon for this connector.
* @property {string} description - Description of the connector.
*/
/**
* Representation of a connector infos
* @typedef {Object} ConnectorInfos
* @todo Use ConnectorState and ConnectorStaticInfos docs
* @property {string} name - ID of the connector. This will be use to select the connector in unifile.
* @property {string} displayName - Name that should be display. Allows characters forbidden in name.
* @property {string} icon - Path to an icon for this connector.
* @property {string} description - Description of the connector.
* @property {boolean} isLoggedIn - Flag wether the user is logged in.
* @property {boolean} isOAuth - Flag wether the connector uses OAuth as authentication mechanism.
* @property {string} username - Name used to log in.
*/
/**
* Credentials of a service
* @typedef {Object} Credentials
*
* For non-OAuth services
* @property {string} [host] - URL to the service
* @property {string} [port] - Port the auth service is listening to
* @property {string} [user] - Username for the service
* @property {string} [password] - Password for the service
*
* For OAuth services
* @property {string} [code] - OAuth code for the service
* @property {string} [state] - OAuth state for the service
*/
/**
* Representation of a file
* @typedef {Object} FileInfos
* @property {string} name - Name of the file
* @property {number} size - Size of the file in bytes
* @property {string} modified - ISO string representation of the date from last modification
* @property {boolean} isDir - Wether this is a directory or not
* @property {string} mime - MIME type of this file
*/
const {UnifileError} = require('./error.js');
/**
* Tells if a method needs authentification
* @param {string} methodName - Name of the method to test
* @return {boolean} true if the method needs to be authenticated
* @private
*/
function isAuthentifiedFunction(methodName) {
return ['readdir', 'mkdir', 'writeFile', 'createWriteStream',
'readFile', 'createReadStream', 'rename', 'unlink', 'rmdir',
'stat', 'batch'].includes(methodName);
}
const connectors = Symbol('connectors');
/**
* Unifile class
* This will use connectors to distant services to manipulate the files.
* An empty instance of Unifile cannot connect to any service. You must first call the use() function
* to register a connector.
*/
class Unifile {
/**
* Create a new instance of Unifile.
* This will regroup all the connectors you decided to use.
* @constructor
*/
constructor() {
this[connectors] = new Map();
}
/**
* Adds a new connector into Unifile.
* Once a connector has been register with this function, it can be used with all the commands.
* @param {Connector} connector - A connector implementing all of Unifile functions
*/
use(connector) {
if(!connector) throw new Error('Connector cannot be undefined');
if(!connector.name) throw new Error('Connector must have a name');
this[connectors].set(connector.name.toLowerCase(), connector);
}
// Infos commands
/**
* Get all the info you need about a connector
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @return {ConnectorInfos} all the infos about this connector
*/
getInfos(session, connectorName) {
return this.callMethod(connectorName, session, 'getInfos');
}
/**
* List all the connectors currently used in this instance of Unifile
* @return {string[]} an array of connectors names
*/
listConnectors() {
return Array.from(this[connectors].keys());
}
// Auth commands
/**
* Log a connector in a distant service.
* This must be called before any access to the service or an error will be thrown.
* The result of a successful login attempt will be saved in the session.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {Credentials|string} credentials - Service credentials (user/password or OAuth code)
* or a authenticated URL to connect to the service.
* @return {external:Promise<string|null>} a promise of OAuth token if the service uses it or null
*/
login(session, connectorName, credentials) {
return this.callMethod(connectorName, session, 'login', credentials);
}
/**
* Log a connector by directly using a OAuth token.
* You don't have to call the method if you use the login() method. This is only in the case
* you got a token from anothe source (CLI, app,...)
* This must be called before any access to the service or an error will be thrown.
* The result of a successful login attempt will be saved in the session.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} token - Service access token generated by OAuth
* @return {external:Promise<string|null>} a promise of OAuth token if the service uses it or null
*/
setAccessToken(session, connectorName, token) {
return this.callMethod(connectorName, session, 'setAccessToken', token);
}
/**
* Log out from a connector.
* After that you won't be able to make any request until you log in again.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @return {external:Promise<null>} an empty promise.
*/
clearAccessToken(session, connectorName) {
return this.callMethod(connectorName, session, 'clearAccessToken');
}
/**
* Get the URL of the authorization endpoint for an OAuth service.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @return {external:Promise<string>} a promise of the authorization URL
*/
getAuthorizeURL(session, connectorName) {
return this.callMethod(connectorName, session, 'getAuthorizeURL');
}
// Filesystem commands
/**
* Reads the content of a directory.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the directory to read. Must be relative to the root of the service.
* @return {external:Promise<FileInfos[]>} a promise of an array of FileInfos
* @see {@link FileInfos} to get the properties of the return objects
*/
readdir(session, connectorName, path) {
return this.callMethod(connectorName, session, 'readdir', path);
}
/**
* Give information about a file or directory.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the object to stat. Must be relative to the root of the service.
* @return {external:Promise<FileInfos>} a promise of FileInfos
* @see {@link FileInfos} to get the properties of the return object
*/
stat(session, connectorName, path) {
return this.callMethod(connectorName, session, 'stat', path);
}
/**
* Create a directory.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the directory to create. Must be relative to the root of the service.
* @return {external:Promise<null>} an empty promise
*/
mkdir(session, connectorName, path) {
return this.callMethod(connectorName, session, 'mkdir', path);
}
/**
* Write content to a file.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the file to write. Must be relative to the root of the service.
* @param {string} content - Content to write into the file
* @return {external:Promise<null>} an empty promise.
*/
writeFile(session, connectorName, path, content) {
return this.callMethod(connectorName, session, 'writeFile', path, content);
}
/**
* Create a write stream to a file.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the file to write. Must be relative to the root of the service.
* @return {external:WritableStream} a writable stream into the file
*/
createWriteStream(session, connectorName, path) {
return this.callMethod(connectorName, session, 'createWriteStream', path);
}
/**
* Read the content of the file.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the file to read. Must be relative to the root of the service.
* @return {external:Promise<string>} a promise of the content of the file
*/
readFile(session, connectorName, path) {
return this.callMethod(connectorName, session, 'readFile', path);
}
/**
* Create a read stream to a file.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the file to read. Must be relative to the root of the service.
* @return {external:ReadableStream} a readable stream from the file
*/
createReadStream(session, connectorName, path) {
return this.callMethod(connectorName, session, 'createReadStream', path);
}
/**
* Rename a file.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} source - Path to the file to rename. Must be relative to the root of the service.
* @param {string} destination - New path to give to the file. Must be relative to the root of the service.
* @return {external:Promise<null>} an empty promise.
*/
rename(session, connectorName, source, destination) {
return this.callMethod(connectorName, session, 'rename', source, destination);
}
/**
* Unlink (delete) a file.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the file to delete. Must be relative to the root of the service.
* @return {external:Promise<null>} an empty promise.
*/
unlink(session, connectorName, path) {
return this.callMethod(connectorName, session, 'unlink', path);
}
/**
* Remove a directory.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {string} path - Path of the directory to delete. Must be relative to the root of the service.
* @return {external:Promise<null>} an empty promise.
*/
rmdir(session, connectorName, path) {
return this.callMethod(connectorName, session, 'rmdir', path);
}
// Batch operation
/**
* Execute batch operation.
* Available actions are UNLINK, RMDIR, RENAME, MKDIR and WRITEFILE.
* @param {Object} session - Object where session data will be stored
* @param {string} connectorName - Name of the connector
* @param {Object[]} actions - Array of actions to execute in this batch.
* @param {string} actions[].name - Name of this action.
* @param {string} actions[].path - Path parameter for this action.
* @param {string} [actions[].destination] - Destination parameter for this action.
* @param {string} [actions[].content] - Content parameter for this action.
* @param {string} [message] - Message to describe this batch
* @return {external:Promise<null>} an empty promise.
*/
batch(session, connectorName, actions, message) {
return this.callMethod(connectorName, session, 'batch', actions, message);
}
// Privates
callMethod(connectorName, session, methodName, ...params) {
// Check connector
if(!connectorName) throw new Error('You should specify a connector name!');
const name = connectorName.toLowerCase();
if(!this[connectors].has(name)) throw new Error(`Unknown connector: ${connectorName}`);
const connector = this[connectors].get(name);
if(!(methodName in connector)) throw new Error(`This connector does not implement ${methodName}()`);
// Check session
if(!session) throw new Error('No session provided');
else if(!(name in session)) session[name] = {};
// Check authentification
if(isAuthentifiedFunction(methodName) && !connector.getInfos(session[name]).isLoggedIn)
return Promise.reject(new UnifileError(UnifileError.EACCES, 'User not logged in.'));
return connector[methodName](session[name], ...params);
}
}
// Register out-of-the-box plugins
Unifile.GitHubConnector = require('./unifile-github.js');
Unifile.DropboxConnector = require('./unifile-dropbox.js');
Unifile.FtpConnector = require('./unifile-ftp.js');
Unifile.RemoteStorageConnector = require('./unifile-remoteStorage.js');
Unifile.FsConnector = require('./unifile-fs.js');
Unifile.SftpConnector = require('./unifile-sftp.js');
module.exports = Unifile;