1 | /** @namespace Unifile */
|
2 | ;
|
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 |
|
77 | const {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 | */
|
85 | function isAuthentifiedFunction(methodName) {
|
86 | return ['readdir', 'mkdir', 'writeFile', 'createWriteStream',
|
87 | 'readFile', 'createReadStream', 'rename', 'unlink', 'rmdir',
|
88 | 'stat', 'batch'].includes(methodName);
|
89 | }
|
90 |
|
91 | const 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 | */
|
99 | class 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
|
350 | Unifile.GitHubConnector = require('./unifile-github.js');
|
351 | Unifile.DropboxConnector = require('./unifile-dropbox.js');
|
352 | Unifile.FtpConnector = require('./unifile-ftp.js');
|
353 | Unifile.RemoteStorageConnector = require('./unifile-remoteStorage.js');
|
354 | Unifile.FsConnector = require('./unifile-fs.js');
|
355 | Unifile.SftpConnector = require('./unifile-sftp.js');
|
356 |
|
357 | module.exports = Unifile;
|