UNPKG

14.2 kBJavaScriptView Raw
1/** @namespace Unifile */
2'use strict';
3
4/**
5 * The built-in Node.js WritableStream class
6 * @external WritableStream
7 * @see https://nodejs.org/api/stream.html#stream_writable_streams
8 */
9
10/**
11 * The built-in Node.js ReadableStream class
12 * @external ReadableStream
13 * @see https://nodejs.org/api/stream.html#stream_readable_streams
14 */
15
16/**
17 * Bluebird Promise class
18 * @external Promise
19 * @see http://bluebirdjs.com/docs/api-reference.html
20 */
21
22/**
23 * State of the connector
24 * @typedef {Object} ConnectorState
25 * @property {boolean} isLoggedIn - Flag wether the user is logged in.
26 * @property {boolean} isOAuth - Flag wether the connector uses OAuth as authentication mechanism.
27 * @property {string} username - Name used to log in.
28 */
29
30/**
31 * Static infos of the connector
32 * @typedef {Object} ConnectorStaticInfos
33 * @property {string} name - ID of the connector. This will be use to select the connector in unifile.
34 * @property {string} displayName - Name that should be display. Allows characters forbidden in name.
35 * @property {string} icon - Path to an icon for this connector.
36 * @property {string} description - Description of the connector.
37 */
38
39/**
40 * Representation of a connector infos
41 * @typedef {Object} ConnectorInfos
42 * @todo Use ConnectorState and ConnectorStaticInfos docs
43 * @property {string} name - ID of the connector. This will be use to select the connector in unifile.
44 * @property {string} displayName - Name that should be display. Allows characters forbidden in name.
45 * @property {string} icon - Path to an icon for this connector.
46 * @property {string} description - Description of the connector.
47 * @property {boolean} isLoggedIn - Flag wether the user is logged in.
48 * @property {boolean} isOAuth - Flag wether the connector uses OAuth as authentication mechanism.
49 * @property {string} username - Name used to log in.
50 */
51
52/**
53 * Credentials of a service
54 * @typedef {Object} Credentials
55 *
56 * For non-OAuth services
57 * @property {string} [host] - URL to the service
58 * @property {string} [port] - Port the auth service is listening to
59 * @property {string} [user] - Username for the service
60 * @property {string} [password] - Password for the service
61 *
62 * For OAuth services
63 * @property {string} [code] - OAuth code for the service
64 * @property {string} [state] - OAuth state for the service
65 */
66
67/**
68 * Representation of a file
69 * @typedef {Object} FileInfos
70 * @property {string} name - Name of the file
71 * @property {number} size - Size of the file in bytes
72 * @property {string} modified - ISO string representation of the date from last modification
73 * @property {boolean} isDir - Wether this is a directory or not
74 * @property {string} mime - MIME type of this file
75 */
76
77const {UnifileError} = require('./error.js');
78
79/**
80 * Tells if a method needs authentification
81 * @param {string} methodName - Name of the method to test
82 * @return {boolean} true if the method needs to be authenticated
83 * @private
84 */
85function isAuthentifiedFunction(methodName) {
86 return ['readdir', 'mkdir', 'writeFile', 'createWriteStream',
87 'readFile', 'createReadStream', 'rename', 'unlink', 'rmdir',
88 'stat', 'batch'].includes(methodName);
89}
90
91const connectors = Symbol('connectors');
92
93/**
94 * Unifile class
95 * This will use connectors to distant services to manipulate the files.
96 * An empty instance of Unifile cannot connect to any service. You must first call the use() function
97 * to register a connector.
98 */
99class Unifile {
100
101 /**
102 * Create a new instance of Unifile.
103 * This will regroup all the connectors you decided to use.
104 * @constructor
105 */
106 constructor() {
107 this[connectors] = new Map();
108 }
109
110 /**
111 * Adds a new connector into Unifile.
112 * Once a connector has been register with this function, it can be used with all the commands.
113 * @param {Connector} connector - A connector implementing all of Unifile functions
114 */
115 use(connector) {
116 if(!connector) throw new Error('Connector cannot be undefined');
117 if(!connector.name) throw new Error('Connector must have a name');
118 this[connectors].set(connector.name.toLowerCase(), connector);
119 }
120
121 // Infos commands
122
123 /**
124 * Get all the info you need about a connector
125 * @param {Object} session - Object where session data will be stored
126 * @param {string} connectorName - Name of the connector
127 * @return {ConnectorInfos} all the infos about this connector
128 */
129 getInfos(session, connectorName) {
130 return this.callMethod(connectorName, session, 'getInfos');
131 }
132
133 /**
134 * List all the connectors currently used in this instance of Unifile
135 * @return {string[]} an array of connectors names
136 */
137 listConnectors() {
138 return Array.from(this[connectors].keys());
139 }
140
141 // Auth commands
142
143 /**
144 * Log a connector in a distant service.
145 * This must be called before any access to the service or an error will be thrown.
146 * The result of a successful login attempt will be saved in the session.
147 * @param {Object} session - Object where session data will be stored
148 * @param {string} connectorName - Name of the connector
149 * @param {Credentials|string} credentials - Service credentials (user/password or OAuth code)
150 * or a authenticated URL to connect to the service.
151 * @return {external:Promise<string|null>} a promise of OAuth token if the service uses it or null
152 */
153 login(session, connectorName, credentials) {
154 return this.callMethod(connectorName, session, 'login', credentials);
155 }
156
157 /**
158 * Log a connector by directly using a OAuth token.
159 * You don't have to call the method if you use the login() method. This is only in the case
160 * you got a token from anothe source (CLI, app,...)
161 * This must be called before any access to the service or an error will be thrown.
162 * The result of a successful login attempt will be saved in the session.
163 * @param {Object} session - Object where session data will be stored
164 * @param {string} connectorName - Name of the connector
165 * @param {string} token - Service access token generated by OAuth
166 * @return {external:Promise<string|null>} a promise of OAuth token if the service uses it or null
167 */
168 setAccessToken(session, connectorName, token) {
169 return this.callMethod(connectorName, session, 'setAccessToken', token);
170 }
171
172 /**
173 * Log out from a connector.
174 * After that you won't be able to make any request until you log in again.
175 * @param {Object} session - Object where session data will be stored
176 * @param {string} connectorName - Name of the connector
177 * @return {external:Promise<null>} an empty promise.
178 */
179 clearAccessToken(session, connectorName) {
180 return this.callMethod(connectorName, session, 'clearAccessToken');
181 }
182
183 /**
184 * Get the URL of the authorization endpoint for an OAuth service.
185 * @param {Object} session - Object where session data will be stored
186 * @param {string} connectorName - Name of the connector
187 * @return {external:Promise<string>} a promise of the authorization URL
188 */
189 getAuthorizeURL(session, connectorName) {
190 return this.callMethod(connectorName, session, 'getAuthorizeURL');
191 }
192
193 // Filesystem commands
194
195 /**
196 * Reads the content of a directory.
197 * @param {Object} session - Object where session data will be stored
198 * @param {string} connectorName - Name of the connector
199 * @param {string} path - Path of the directory to read. Must be relative to the root of the service.
200 * @return {external:Promise<FileInfos[]>} a promise of an array of FileInfos
201 * @see {@link FileInfos} to get the properties of the return objects
202 */
203 readdir(session, connectorName, path) {
204 return this.callMethod(connectorName, session, 'readdir', path);
205 }
206
207 /**
208 * Give information about a file or directory.
209 * @param {Object} session - Object where session data will be stored
210 * @param {string} connectorName - Name of the connector
211 * @param {string} path - Path of the object to stat. Must be relative to the root of the service.
212 * @return {external:Promise<FileInfos>} a promise of FileInfos
213 * @see {@link FileInfos} to get the properties of the return object
214 */
215 stat(session, connectorName, path) {
216 return this.callMethod(connectorName, session, 'stat', path);
217 }
218
219 /**
220 * Create a directory.
221 * @param {Object} session - Object where session data will be stored
222 * @param {string} connectorName - Name of the connector
223 * @param {string} path - Path of the directory to create. Must be relative to the root of the service.
224 * @return {external:Promise<null>} an empty promise
225 */
226 mkdir(session, connectorName, path) {
227 return this.callMethod(connectorName, session, 'mkdir', path);
228 }
229
230 /**
231 * Write content to a file.
232 * @param {Object} session - Object where session data will be stored
233 * @param {string} connectorName - Name of the connector
234 * @param {string} path - Path of the file to write. Must be relative to the root of the service.
235 * @param {string} content - Content to write into the file
236 * @return {external:Promise<null>} an empty promise.
237 */
238 writeFile(session, connectorName, path, content) {
239 return this.callMethod(connectorName, session, 'writeFile', path, content);
240 }
241
242 /**
243 * Create a write stream to a file.
244 * @param {Object} session - Object where session data will be stored
245 * @param {string} connectorName - Name of the connector
246 * @param {string} path - Path of the file to write. Must be relative to the root of the service.
247 * @return {external:WritableStream} a writable stream into the file
248 */
249 createWriteStream(session, connectorName, path) {
250 return this.callMethod(connectorName, session, 'createWriteStream', path);
251 }
252
253 /**
254 * Read the content of the file.
255 * @param {Object} session - Object where session data will be stored
256 * @param {string} connectorName - Name of the connector
257 * @param {string} path - Path of the file to read. Must be relative to the root of the service.
258 * @return {external:Promise<string>} a promise of the content of the file
259 */
260 readFile(session, connectorName, path) {
261 return this.callMethod(connectorName, session, 'readFile', path);
262 }
263
264 /**
265 * Create a read stream to a file.
266 * @param {Object} session - Object where session data will be stored
267 * @param {string} connectorName - Name of the connector
268 * @param {string} path - Path of the file to read. Must be relative to the root of the service.
269 * @return {external:ReadableStream} a readable stream from the file
270 */
271 createReadStream(session, connectorName, path) {
272 return this.callMethod(connectorName, session, 'createReadStream', path);
273 }
274
275 /**
276 * Rename a file.
277 * @param {Object} session - Object where session data will be stored
278 * @param {string} connectorName - Name of the connector
279 * @param {string} source - Path to the file to rename. Must be relative to the root of the service.
280 * @param {string} destination - New path to give to the file. Must be relative to the root of the service.
281 * @return {external:Promise<null>} an empty promise.
282 */
283 rename(session, connectorName, source, destination) {
284 return this.callMethod(connectorName, session, 'rename', source, destination);
285 }
286
287 /**
288 * Unlink (delete) a file.
289 * @param {Object} session - Object where session data will be stored
290 * @param {string} connectorName - Name of the connector
291 * @param {string} path - Path of the file to delete. Must be relative to the root of the service.
292 * @return {external:Promise<null>} an empty promise.
293 */
294 unlink(session, connectorName, path) {
295 return this.callMethod(connectorName, session, 'unlink', path);
296 }
297
298 /**
299 * Remove a directory.
300 * @param {Object} session - Object where session data will be stored
301 * @param {string} connectorName - Name of the connector
302 * @param {string} path - Path of the directory to delete. Must be relative to the root of the service.
303 * @return {external:Promise<null>} an empty promise.
304 */
305 rmdir(session, connectorName, path) {
306 return this.callMethod(connectorName, session, 'rmdir', path);
307 }
308
309 // Batch operation
310 /**
311 * Execute batch operation.
312 * Available actions are UNLINK, RMDIR, RENAME, MKDIR and WRITEFILE.
313 * @param {Object} session - Object where session data will be stored
314 * @param {string} connectorName - Name of the connector
315 * @param {Object[]} actions - Array of actions to execute in this batch.
316 * @param {string} actions[].name - Name of this action.
317 * @param {string} actions[].path - Path parameter for this action.
318 * @param {string} [actions[].destination] - Destination parameter for this action.
319 * @param {string} [actions[].content] - Content parameter for this action.
320 * @param {string} [message] - Message to describe this batch
321 * @return {external:Promise<null>} an empty promise.
322 */
323 batch(session, connectorName, actions, message) {
324 return this.callMethod(connectorName, session, 'batch', actions, message);
325 }
326
327 // Privates
328
329 callMethod(connectorName, session, methodName, ...params) {
330 // Check connector
331 if(!connectorName) throw new Error('You should specify a connector name!');
332 const name = connectorName.toLowerCase();
333 if(!this[connectors].has(name)) throw new Error(`Unknown connector: ${connectorName}`);
334 const connector = this[connectors].get(name);
335 if(!(methodName in connector)) throw new Error(`This connector does not implement ${methodName}()`);
336
337 // Check session
338 if(!session) throw new Error('No session provided');
339 else if(!(name in session)) session[name] = {};
340
341 // Check authentification
342 if(isAuthentifiedFunction(methodName) && !connector.getInfos(session[name]).isLoggedIn)
343 return Promise.reject(new UnifileError(UnifileError.EACCES, 'User not logged in.'));
344
345 return connector[methodName](session[name], ...params);
346 }
347}
348
349// Register out-of-the-box plugins
350Unifile.GitHubConnector = require('./unifile-github.js');
351Unifile.DropboxConnector = require('./unifile-dropbox.js');
352Unifile.FtpConnector = require('./unifile-ftp.js');
353Unifile.RemoteStorageConnector = require('./unifile-remoteStorage.js');
354Unifile.FsConnector = require('./unifile-fs.js');
355Unifile.SftpConnector = require('./unifile-sftp.js');
356
357module.exports = Unifile;