PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Account.js

https://gitlab.com/alexphillips/node-clouddrive
JavaScript | 527 lines | 441 code | 86 blank | 0 comment | 57 complexity | bde9ba5fdae6b84764c324edc9d0b62d MD5 | raw file
  1. 'use strict';
  2. let got = require('got'),
  3. Url = require('url'),
  4. Node = require('./Node'),
  5. async = require('async'),
  6. fs = require('fs');
  7. class Account {
  8. constructor(email, clientId, clientSecret, cache) {
  9. this.email = email;
  10. this.clientId = clientId;
  11. this.clientSecret = clientSecret;
  12. this.cache = cache;
  13. this.token = {};
  14. this.checkpoint = null;
  15. this.metadataUrl = null;
  16. this.contentUrl = null;
  17. this.scope = [
  18. Account.SCOPE_READ_ALL,
  19. Account.SCOPE_WRITE
  20. ];
  21. }
  22. authorize(authCredentials, options, callback) {
  23. let retval = {
  24. success: false,
  25. data: {}
  26. };
  27. if (!options) {
  28. options = {};
  29. }
  30. if (authCredentials === undefined) {
  31. authCredentials = null;
  32. }
  33. let scope = encodeURIComponent(this.scope.join(' '));
  34. if (this.token.access_token === undefined || !this.token.access_token) {
  35. if (authCredentials === null) {
  36. retval.data.message = 'Initial authorization is required';
  37. if (!this.clientId || !this.clientSecret) {
  38. retval.data.auth_url = 'https://data-mind-687.appspot.com/clouddrive';
  39. } else {
  40. retval.data.auth_url = `https://www.amazon.com/ap/oa?client_id=${this.clientId}&scope=${scope}&response_type=code&redirect_uri=http://localhost`;
  41. }
  42. return callback(null, retval);
  43. }
  44. switch (typeof authCredentials) {
  45. case 'string':
  46. return this.requestAuthorization(authCredentials, (err, data) => {
  47. if (err) {
  48. return callback(err);
  49. }
  50. if (data.success === false) {
  51. return callback(null, data);
  52. }
  53. retval.success = true;
  54. return async.forEachOf(data.data, (value, key, callback) => {
  55. this.token[key] = value;
  56. callback();
  57. }, err => {
  58. if (err) {
  59. return callback(err);
  60. }
  61. return this.getEndpoints((err, data) => {
  62. if (err) {
  63. return callback(err);
  64. }
  65. if (data.success === true) {
  66. this.metadataUrl = data.data.metadataUrl;
  67. this.contentUrl = data.data.contentUrl;
  68. return this.save(() => {
  69. return callback(null, retval);
  70. });
  71. }
  72. return this.save(() => {
  73. return callback(null, data);
  74. });
  75. });
  76. });
  77. });
  78. case 'object':
  79. retval.success = true;
  80. return async.forEachOf(authCredentials, (value, key, callback) => {
  81. this.token[key] = value;
  82. callback();
  83. }, err => {
  84. if (err) {
  85. return callback(err);
  86. }
  87. return this.getEndpoints((err, data) => {
  88. if (err) {
  89. return callback(err);
  90. }
  91. if (data.success === true) {
  92. this.metadataUrl = data.data.metadataUrl;
  93. this.contentUrl = data.data.contentUrl;
  94. return this.save(() => {
  95. return callback(null, retval);
  96. });
  97. }
  98. return this.save(() => {
  99. return callback(null, data);
  100. });
  101. });
  102. });
  103. default:
  104. return callback(Error('Auth credentials must either be a token object or a redirect URL'));
  105. }
  106. }
  107. retval.success = true;
  108. if ((Date.now() - (this.token.expires_in * 1000) > (this.token.last_authorized)) || options.force === true) {
  109. return this.renewAuthorization((err, data) => {
  110. if (err) {
  111. return callback(err);
  112. }
  113. if (data.success === true) {
  114. return async.forEachOf(data.data, (value, key, callback) => {
  115. this.token[key] = value;
  116. callback();
  117. }, err => {
  118. if (err) {
  119. return callback(err);
  120. }
  121. return this.getEndpoints((err, data) => {
  122. if (err) {
  123. return callback(err);
  124. }
  125. if (data.success === true) {
  126. this.metadataUrl = data.data.metadataUrl;
  127. this.contentUrl = data.data.contentUrl;
  128. return this.save(() => {
  129. return callback(null, retval);
  130. });
  131. }
  132. return this.save(() => {
  133. return callback(null, data);
  134. });
  135. });
  136. });
  137. }
  138. return callback(null, data);
  139. });
  140. }
  141. return this.getEndpoints((err, data) => {
  142. if (err) {
  143. return callback(err);
  144. }
  145. if (data.success === true) {
  146. this.metadataUrl = data.data.metadataUrl;
  147. this.contentUrl = data.data.contentUrl;
  148. return this.save(() => {
  149. return callback(null, retval);
  150. });
  151. }
  152. return callback(null, data);
  153. });
  154. }
  155. getEndpoints(callback) {
  156. let retval = {
  157. success: false,
  158. data: {
  159. metadataUrl: this.metadataUrl,
  160. contentUrl: this.contentUrl
  161. }
  162. };
  163. got.get('https://cdws.us-east-1.amazonaws.com/drive/v1/account/endpoint', {
  164. headers: {
  165. Authorization: 'Bearer ' + this.token.access_token
  166. }
  167. })
  168. .then(response => {
  169. retval.data = JSON.parse(response.body);
  170. if (response.statusCode === 200) {
  171. retval.success = true;
  172. }
  173. return callback(null, retval);
  174. })
  175. .catch(err => {
  176. return callback(err);
  177. });
  178. }
  179. getInfo(callback) {
  180. let retval = {
  181. success: false,
  182. data: {}
  183. };
  184. got.get(`${this.metadataUrl}account/info`, {
  185. headers: {
  186. Authorization: 'Bearer ' + this.token.access_token
  187. }
  188. })
  189. .then(response => {
  190. retval.data = JSON.parse(response.body);
  191. if (response.statusCode === 200) {
  192. retval.success = true;
  193. }
  194. return callback(null, retval);
  195. })
  196. .catch(err => {
  197. return callback(err);
  198. });
  199. }
  200. getQuota(callback) {
  201. let retval = {
  202. success: false,
  203. data: {}
  204. };
  205. got.get(`${this.metadataUrl}account/quota`, {
  206. headers: {
  207. Authorization: 'Bearer ' + this.token.access_token
  208. }
  209. })
  210. .then(response => {
  211. retval.data = JSON.parse(response.body);
  212. if (retval.statusCode === 200) {
  213. retval.success = true;
  214. }
  215. return callback(null, retval);
  216. })
  217. .catch(err => {
  218. return callback(err);
  219. });
  220. }
  221. getUsage(callback) {
  222. let retval = {
  223. success: false,
  224. data: {}
  225. };
  226. got.get(`${this.metadataUrl}account/usage`, {
  227. headers: {
  228. Authorization: 'Bearer ' + this.token.access_token
  229. },
  230. })
  231. .then(response => {
  232. retval.data = JSON.parse(response.body);
  233. if (response.statusCode === 200) {
  234. retval.success = true;
  235. }
  236. return callback(null, retval);
  237. })
  238. .catch(err => {
  239. return callback(err);
  240. });
  241. }
  242. load(callback) {
  243. this.cache.loadConfigByEmail(this.email, (err, data) => {
  244. if (err) {
  245. return callback(err);
  246. }
  247. let retval = {
  248. success: true,
  249. data: {}
  250. };
  251. let config = data.length === 0 ? {} : data[0];
  252. return async.forEachOf(config, (value, key, callback) => {
  253. this.token[key] = value;
  254. callback();
  255. }, err => {
  256. if (err) {
  257. return callback(err);
  258. }
  259. if (config.checkpoint !== undefined) {
  260. this.checkpoint = config.checkpoint;
  261. }
  262. this.metadataUrl = this.token.metadata_url;
  263. this.contentUrl = this.token.content_url;
  264. return callback(null, retval);
  265. });
  266. });
  267. }
  268. renewAuthorization(callback) {
  269. let retval = {
  270. success: false,
  271. data: {}
  272. };
  273. if (!this.clientId || !this.clientSecret) {
  274. got.get(`https://data-mind-687.appspot.com/clouddrive?refresh_token=${this.token.refresh_token}`)
  275. .then(response => {
  276. retval.success = true;
  277. retval.data = JSON.parse(response.body);
  278. retval.data.last_authorized = Date.now();
  279. return callback(null, retval);
  280. })
  281. .catch(err => {
  282. return callback(err);
  283. });
  284. } else {
  285. got.post('https://api.amazon.com/auth/o2/token', {
  286. body: {
  287. grant_type: 'refresh_token',
  288. refresh_token: this.token.refresh_token,
  289. client_id: this.clientId,
  290. client_secret: this.clientSecret,
  291. redirect_uri: 'http://localhost'
  292. }
  293. })
  294. .then(response => {
  295. retval.data = JSON.parse(response.body);
  296. if (response.statusCode === 200) {
  297. retval.success = true;
  298. retval.data.last_authorized = Date.now();
  299. }
  300. return callback(null, retval);
  301. })
  302. .catch(err => {
  303. return callback(err);
  304. });
  305. }
  306. }
  307. requestAuthorization(redirectUrl, callback) {
  308. let retval = {
  309. success: false,
  310. data: {}
  311. };
  312. let url = Url.parse(redirectUrl, true);
  313. if (url.query.code === undefined) {
  314. return callback(Error(`No authorization code found in callback URL: ${redirectUrl}`));
  315. }
  316. got.post('https://api.amazon.com/auth/o2/token', {
  317. body: {
  318. grant_type: 'authorization_code',
  319. code: url.query.code,
  320. client_id: this.clientId,
  321. client_secret: this.clientSecret,
  322. redirect_uri: 'http://localhost',
  323. }
  324. })
  325. .then(response => {
  326. retval.data = JSON.parse(response.body);
  327. if (response.statusCode === 200) {
  328. retval.success = true;
  329. retval.data.last_authorized = Date.now();
  330. }
  331. return callback(null, retval);
  332. })
  333. .catch(err => {
  334. return callback(err);
  335. });
  336. }
  337. save(callback) {
  338. return this.cache.saveAccount(this, callback);
  339. }
  340. setScope(scopes) {
  341. this.scope = scopes;
  342. }
  343. sync(params, callback) {
  344. params = params ? params : {};
  345. if (this.checkpoint) {
  346. params.includePurged = 'true';
  347. }
  348. return this.getChanges(params, callback);
  349. }
  350. getChanges(params, callback) {
  351. let loop = true;
  352. if (this.checkpoint !== null) {
  353. params.checkpoint = this.checkpoint;
  354. }
  355. let self = this;
  356. function processData(part, callback) {
  357. if (part.nodes !== undefined) {
  358. if (part.nodes.length === 0) {
  359. loop = false;
  360. return callback();
  361. } else {
  362. self.cache.transaction(() => {
  363. async.forEach(part.nodes, (node, callback) => {
  364. node = new Node(node);
  365. if (node.getStatus() === Node.STATUS_PURGED) {
  366. return node.del(callback);
  367. }
  368. return node.save(callback);
  369. }, err => {
  370. if (err) {
  371. return callback(err);
  372. }
  373. if (part.checkpoint !== undefined) {
  374. self.checkpoint = part.checkpoint;
  375. }
  376. return self.save(err => {
  377. if (err) {
  378. self.cache.rollback();
  379. return callback(err);
  380. }
  381. self.cache.commit();
  382. return callback();
  383. });
  384. });
  385. });
  386. }
  387. }
  388. }
  389. got.post(`${this.metadataUrl}changes`, {
  390. headers: {
  391. Authorization: 'Bearer ' + this.token.access_token
  392. },
  393. body: JSON.stringify(params),
  394. gzip: true,
  395. })
  396. .then(response => {
  397. if (response.statusCode === 401) {
  398. return this.authorize(null, {force: true}, (err, data) => {
  399. if (err) {
  400. return callback(err);
  401. }
  402. return this.getChanges(params, callback);
  403. });
  404. }
  405. if (!response.body) {
  406. return callback(Error(`Invalid data received: ${response.body}`));
  407. }
  408. let data = response.body.split('\n');
  409. async.forEachSeries(data, (part, callback) => {
  410. part = JSON.parse(part);
  411. if (part.end !== undefined && part.end === true) {
  412. return callback();
  413. }
  414. if (part.reset !== undefined && part.reset === true) {
  415. return this.cache.deleteAllNodes(() => {
  416. return processData(part, callback);
  417. });
  418. }
  419. return processData(part, callback);
  420. }, err => {
  421. if (err) {
  422. return callback(err);
  423. }
  424. if (loop === true) {
  425. return this.getChanges(params, callback);
  426. }
  427. return callback();
  428. });
  429. })
  430. .catch(err => {
  431. return callback(err);
  432. });
  433. }
  434. }
  435. Account.SCOPE_READ_IMAGE = 'clouddrive:read_image';
  436. Account.SCOPE_READ_VIDEO = 'clouddrive:read_video';
  437. Account.SCOPE_READ_DOCUMENT = 'clouddrive:read_document';
  438. Account.SCOPE_READ_OTHER = 'clouddrive:read_other';
  439. Account.SCOPE_READ_ALL = 'clouddrive:read_all';
  440. Account.SCOPE_WRITE = 'clouddrive:write';
  441. module.exports = Account;