OneDriveClient.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. /**
  2. * Copyright (c) 2006-2016, JGraph Ltd
  3. * Copyright (c) 2006-2016, Gaudenz Alder
  4. */
  5. /**
  6. * Constructs a new point for the optional x and y coordinates. If no
  7. * coordinates are given, then the default values for <x> and <y> are used.
  8. * @constructor
  9. * @class Implements a basic 2D point. Known subclassers = {@link mxRectangle}.
  10. * @param {number} x X-coordinate of the point.
  11. * @param {number} y Y-coordinate of the point.
  12. */
  13. OneDriveClient = function(editorUi)
  14. {
  15. mxEventSource.call(this);
  16. /**
  17. * Holds a reference to the UI. Needed for the sharing client.
  18. */
  19. this.ui = editorUi;
  20. };
  21. // Extends mxEventSource
  22. mxUtils.extend(OneDriveClient, mxEventSource);
  23. /**
  24. * Specifies if thumbnails should be enabled. Default is true.
  25. * LATER: If thumbnails are disabled, make sure to replace the
  26. * existing thumbnail with the placeholder only once.
  27. */
  28. OneDriveClient.prototype.clientId = (window.location.hostname == 'test.draw.io') ? '0000000048148130' :
  29. ((window.location.hostname == 'drive.draw.io') ? '000000004413EC37' : '0000000040145A19');
  30. /**
  31. * OAuth 2.0 scopes for installing Drive Apps.
  32. */
  33. OneDriveClient.prototype.scopes = 'wl.skydrive_update wl.signin';
  34. /**
  35. * OAuth 2.0 scopes for installing Drive Apps.
  36. */
  37. OneDriveClient.prototype.redirectUri = 'https://' + window.location.hostname + '/onedrive.html';
  38. /**
  39. * Executes the first step for connecting to Google Drive.
  40. */
  41. OneDriveClient.prototype.extension = '.html';
  42. /**
  43. * Executes the first step for connecting to Google Drive.
  44. */
  45. OneDriveClient.prototype.baseUrl = 'https://api.onedrive.com/v1.0';
  46. /**
  47. * Authorizes the client, gets the userId and calls <open>.
  48. */
  49. OneDriveClient.prototype.setUser = function(user)
  50. {
  51. this.user = user;
  52. this.fireEvent(new mxEventObject('userChanged'));
  53. };
  54. /**
  55. *
  56. */
  57. OneDriveClient.prototype.clearCookie = function()
  58. {
  59. var expiration = new Date();
  60. expiration.setYear(expiration.getFullYear() - 1);
  61. document.cookie = 'odauth=; expires=' + expiration.toUTCString();
  62. };
  63. /**
  64. * Authorizes the client, gets the userId and calls <open>.
  65. */
  66. OneDriveClient.prototype.getUser = function()
  67. {
  68. return this.user;
  69. };
  70. /**
  71. * Authorizes the client, gets the userId and calls <open>.
  72. */
  73. OneDriveClient.prototype.getTokenFromCookie = function()
  74. {
  75. var cookies = document.cookie;
  76. var name = 'odauth=';
  77. var start = cookies.indexOf(name);
  78. if (start >= 0)
  79. {
  80. start += name.length;
  81. var end = cookies.indexOf(';', start);
  82. if (end < 0)
  83. {
  84. end = cookies.length;
  85. }
  86. else
  87. {
  88. postCookie = cookies.substring(end);
  89. }
  90. var value = cookies.substring(start, end);
  91. return value;
  92. }
  93. return '';
  94. };
  95. /**
  96. * Authorizes the client, gets the userId and calls <open>.
  97. */
  98. OneDriveClient.prototype.execute = function(fn, userEvent)
  99. {
  100. userEvent = (userEvent != null) ? userEvent : false;
  101. var token = this.getTokenFromCookie();
  102. if (this.getUser() != null && token != null && token.length > 0)
  103. {
  104. fn(token);
  105. }
  106. else
  107. {
  108. var next = mxUtils.bind(this, function(newToken)
  109. {
  110. if (newToken != null && newToken.length > 0)
  111. {
  112. try
  113. {
  114. // Initializes the Windows live API (needed for the picker)
  115. WL.init({client_id: this.clientId, redirect_uri: this.redirectUri});
  116. // Gets the user data to display a logout button in the UI
  117. mxUtils.get(this.baseUrl + '/drive?access_token=' + newToken, mxUtils.bind(this, function(req)
  118. {
  119. if (req.getStatus() == 200)
  120. {
  121. var data = JSON.parse(req.getText());
  122. this.setUser(new DrawioUser(data.owner.user.id, null, data.owner.user.displayName));
  123. fn(newToken);
  124. }
  125. else
  126. {
  127. // TODO: Reauth
  128. fn(null);
  129. }
  130. }));
  131. }
  132. catch (e)
  133. {
  134. // Happens if popups have been blocked in certain cases
  135. fn(null);
  136. }
  137. }
  138. else
  139. {
  140. fn(null);
  141. }
  142. });
  143. if (token != null && token.length > 0)
  144. {
  145. next(token);
  146. }
  147. else
  148. {
  149. var auth = mxUtils.bind(this, function()
  150. {
  151. var url = 'https://login.live.com/oauth20_authorize.srf?client_id=' + this.clientId +
  152. '&scope=' + encodeURIComponent(this.scopes) + '&response_type=token' +
  153. '&redirect_uri=' + encodeURIComponent(this.redirectUri);
  154. var width = 525,
  155. height = 525,
  156. screenX = window.screenX,
  157. screenY = window.screenY,
  158. outerWidth = window.outerWidth,
  159. outerHeight = window.outerHeight;
  160. var left = screenX + Math.max(outerWidth - width, 0) / 2;
  161. var top = screenY + Math.max(outerHeight - height, 0) / 2;
  162. var features = ['width=' + width, 'height=' + height,
  163. 'top=' + top, 'left=' + left,
  164. 'status=no', 'resizable=yes',
  165. 'toolbar=no', 'menubar=no',
  166. 'scrollbars=yes'];
  167. var popup = window.open(url, 'oauth', features.join(','));
  168. if (popup != null)
  169. {
  170. // Sets temporary global callback for auth popup
  171. window.onAuthenticated = mxUtils.bind(this, function(newToken, authWindow)
  172. {
  173. window.onAuthenticated = null;
  174. if (authWindow != null)
  175. {
  176. authWindow.close();
  177. }
  178. next(newToken);
  179. });
  180. popup.focus();
  181. }
  182. });
  183. if (userEvent)
  184. {
  185. auth();
  186. }
  187. else
  188. {
  189. // Requires a user event to about popups being blocked
  190. this.ui.showAuthDialog(this, false, mxUtils.bind(this, function(remember, success)
  191. {
  192. if (success != null)
  193. {
  194. success();
  195. }
  196. auth();
  197. }));
  198. }
  199. }
  200. }
  201. };
  202. /**
  203. * Checks if the client is authorized and calls the next step.
  204. */
  205. OneDriveClient.prototype.getLibrary = function(id, success, error)
  206. {
  207. this.getFile(id, success, error, false, true);
  208. };
  209. /**
  210. * Checks if the client is authorized and calls the next step.
  211. */
  212. OneDriveClient.prototype.getFile = function(id, success, error, denyConvert, asLibrary)
  213. {
  214. asLibrary = (asLibrary != null) ? asLibrary : false;
  215. var fn = mxUtils.bind(this, function()
  216. {
  217. this.execute(mxUtils.bind(this, function(token)
  218. {
  219. if (token != null)
  220. {
  221. var acceptResponse = true;
  222. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  223. {
  224. acceptResponse = false;
  225. error({code: App.ERROR_TIMEOUT, retry: fn});
  226. }), this.ui.timeout);
  227. var err = mxUtils.bind(this, function(req)
  228. {
  229. window.clearTimeout(timeoutThread);
  230. if (acceptResponse)
  231. {
  232. if (error != null)
  233. {
  234. error(this.parseRequestText(req));
  235. }
  236. }
  237. });
  238. mxUtils.get(this.baseUrl + '/drive/items/' + id + '?access_token=' + token, mxUtils.bind(this, function(req)
  239. {
  240. window.clearTimeout(timeoutThread);
  241. if (acceptResponse)
  242. {
  243. if (req.getStatus() == 200)
  244. {
  245. var meta = JSON.parse(req.getText());
  246. if (!denyConvert && Graph.fileSupport && new XMLHttpRequest().upload &&
  247. (/(\.png)$/i.test(meta.name) || /(\.vs?dx)$/i.test(meta.name) ||
  248. /(\.gliffy)$/i.test(meta.name)))
  249. {
  250. this.convertFile(meta, success, error);
  251. }
  252. else
  253. {
  254. this.ui.loadUrl(meta['@content.downloadUrl'], mxUtils.bind(this, function(data)
  255. {
  256. if (asLibrary)
  257. {
  258. success(new OneDriveLibrary(this.ui, data, meta));
  259. }
  260. else
  261. {
  262. success(new OneDriveFile(this.ui, data, meta));
  263. }
  264. }), err, meta.file.mimeType == 'image/png');
  265. }
  266. }
  267. else if (error != null)
  268. {
  269. error(this.parseRequestText(req));
  270. }
  271. }
  272. }), err);
  273. }
  274. else
  275. {
  276. error();
  277. }
  278. }));
  279. });
  280. fn();
  281. };
  282. /**
  283. * Checks if the client is authorized and calls the next step. The optional
  284. * readXml argument is used for import. Default is false. The optional
  285. * readLibrary argument is used for reading libraries. Default is false.
  286. */
  287. OneDriveClient.prototype.convertFile = function(meta, success, error)
  288. {
  289. var name = meta.name;
  290. name = name.substring(0, name.lastIndexOf('.')) + this.extension;
  291. // Gets file data
  292. var url = meta['@content.downloadUrl'];
  293. this.ui.loadUrl(url, mxUtils.bind(this, function(data)
  294. {
  295. // Handles PNG+XML files
  296. if (meta.file.mimeType == 'image/png')
  297. {
  298. data = this.ui.extractGraphModelFromPng(data);
  299. if (data != null)
  300. {
  301. this.insertFile(name, data, success, error);
  302. }
  303. else if (error != null)
  304. {
  305. error({message: mxResources.get('errorLoadingFile')});
  306. }
  307. }
  308. else
  309. {
  310. this.ui.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  311. {
  312. if (xhr.readyState == 4)
  313. {
  314. if (xhr.status == 200 && xhr.responseText.substring(0, 13) == '<mxGraphModel')
  315. {
  316. this.insertFile(name, xhr.responseText, success, error);
  317. }
  318. else if (error != null)
  319. {
  320. error({message: mxResources.get('errorLoadingFile')});
  321. }
  322. }
  323. }), meta.name);
  324. }
  325. }), error, meta.file.mimeType == 'image/png');
  326. };
  327. /**
  328. * Translates this point by the given vector.
  329. *
  330. * @param {number} dx X-coordinate of the translation.
  331. * @param {number} dy Y-coordinate of the translation.
  332. */
  333. OneDriveClient.prototype.renameFile = function(file, filename, success, error)
  334. {
  335. if (file != null && filename != null)
  336. {
  337. // TODO: How to force overwrite file with same name?
  338. this.checkExists(file.meta.parentReference.id, filename, false, mxUtils.bind(this, function(checked, token)
  339. {
  340. if (checked)
  341. {
  342. var url = this.baseUrl + '/drive/items/' + file.meta.id + '?access_token=' + token;
  343. this.writeFile(url, JSON.stringify({name: filename}), 'PATCH', 'application/json', success, error);
  344. }
  345. else
  346. {
  347. error();
  348. }
  349. }));
  350. }
  351. };
  352. /**
  353. * Translates this point by the given vector.
  354. *
  355. * @param {number} dx X-coordinate of the translation.
  356. * @param {number} dy Y-coordinate of the translation.
  357. */
  358. OneDriveClient.prototype.moveFile = function(id, folderId, success, error)
  359. {
  360. this.execute(mxUtils.bind(this, function(token)
  361. {
  362. var url = this.baseUrl + '/drive/items/' + id + '?access_token=' + token;
  363. this.writeFile(url, JSON.stringify({parentReference: {id: folderId}}), 'PATCH', 'application/json', success, error);
  364. }));
  365. };
  366. /**
  367. * Translates this point by the given vector.
  368. *
  369. * @param {number} dx X-coordinate of the translation.
  370. * @param {number} dy Y-coordinate of the translation.
  371. */
  372. OneDriveClient.prototype.insertLibrary = function(filename, data, success, error, folderId)
  373. {
  374. this.insertFile(filename, data, success, error, true, folderId);
  375. };
  376. /**
  377. * Translates this point by the given vector.
  378. *
  379. * @param {number} dx X-coordinate of the translation.
  380. * @param {number} dy Y-coordinate of the translation.
  381. */
  382. OneDriveClient.prototype.insertFile = function(filename, data, success, error, asLibrary, folderId)
  383. {
  384. asLibrary = (asLibrary != null) ? asLibrary : false;
  385. this.checkExists(folderId, filename, true, mxUtils.bind(this, function(checked, token)
  386. {
  387. if (checked)
  388. {
  389. var folder = (folderId != null) ? 'items/' + folderId : 'special/documents';
  390. var url = this.baseUrl + '/drive/' + folder + '/children/' + filename + '/content?access_token=' + token;
  391. this.writeFile(url, data, 'PUT', null, mxUtils.bind(this, function(meta)
  392. {
  393. if (asLibrary)
  394. {
  395. success(new OneDriveLibrary(this.ui, data, meta));
  396. }
  397. else
  398. {
  399. success(new OneDriveFile(this.ui, data, meta));
  400. }
  401. }), error);
  402. }
  403. else if (error != null)
  404. {
  405. error();
  406. }
  407. }))
  408. };
  409. /**
  410. * Translates this point by the given vector.
  411. *
  412. * @param {number} dx X-coordinate of the translation.
  413. * @param {number} dy Y-coordinate of the translation.
  414. */
  415. OneDriveClient.prototype.checkExists = function(parentId, filename, askReplace, fn)
  416. {
  417. this.execute(mxUtils.bind(this, function(token)
  418. {
  419. var path = (parentId != null) ? 'items/' + parentId : 'special/documents';
  420. mxUtils.get(this.baseUrl + '/drive/' + path + '/children/' + filename + '?access_token=' + token, mxUtils.bind(this, function(req)
  421. {
  422. if (req.getStatus() == 404)
  423. {
  424. fn(true, token);
  425. }
  426. else
  427. {
  428. if (askReplace)
  429. {
  430. this.ui.spinner.stop();
  431. this.ui.confirm(mxResources.get('replaceIt', [filename]), function()
  432. {
  433. fn(true, token);
  434. }, function()
  435. {
  436. fn(false, token);
  437. });
  438. }
  439. else
  440. {
  441. this.ui.spinner.stop();
  442. this.ui.showError(mxResources.get('error'), mxResources.get('fileExists'), mxResources.get('ok'), function()
  443. {
  444. fn(false, token);
  445. });
  446. }
  447. }
  448. }), function(req)
  449. {
  450. fn(false, token);
  451. });
  452. }));
  453. };
  454. /**
  455. * Translates this point by the given vector.
  456. *
  457. * @param {number} dx X-coordinate of the translation.
  458. * @param {number} dy Y-coordinate of the translation.
  459. */
  460. OneDriveClient.prototype.saveFile = function(file, success, error)
  461. {
  462. this.execute(mxUtils.bind(this, function(token)
  463. {
  464. var url = this.baseUrl + '/drive/items/' + file.meta.id + '/content/?access_token=' + token;
  465. this.writeFile(url, file.getData(), 'PUT', null, success, error);
  466. }));
  467. };
  468. /**
  469. * Translates this point by the given vector.
  470. *
  471. * @param {number} dx X-coordinate of the translation.
  472. * @param {number} dy Y-coordinate of the translation.
  473. */
  474. OneDriveClient.prototype.writeFile = function(url, data, method, contentType, success, error)
  475. {
  476. if (!this.writingFile)
  477. {
  478. if (url != null && data != null)
  479. {
  480. var acceptResponse = true;
  481. var timeoutThread = null;
  482. this.writingFile = true;
  483. // Cancels any pending requests
  484. if (this.requestThread != null)
  485. {
  486. window.clearTimeout(this.requestThread);
  487. }
  488. var fn = mxUtils.bind(this, function()
  489. {
  490. if (timeoutThread != null)
  491. {
  492. window.clearTimeout(timeoutThread);
  493. }
  494. timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  495. {
  496. this.writingFile = false;
  497. acceptResponse = false;
  498. if (error != null)
  499. {
  500. error({code: App.ERROR_TIMEOUT, retry: fn});
  501. }
  502. }), this.ui.timeout);
  503. var req = new mxXmlRequest(url, data, method);
  504. req.setRequestHeaders = function(request, params)
  505. {
  506. // Space deletes content type header. Specification says "text/plain"
  507. // should work but returns an 415 Unsupported Media Type error
  508. request.setRequestHeader('Content-Type', contentType || ' ');
  509. };
  510. req.send(mxUtils.bind(this, function(req)
  511. {
  512. window.clearTimeout(timeoutThread);
  513. if (acceptResponse)
  514. {
  515. this.writingFile = false;
  516. // Returns 201 (created) for new resources
  517. if (req.getStatus() == 200 || req.getStatus() == 201)
  518. {
  519. if (success != null)
  520. {
  521. success(JSON.parse(req.getText()));
  522. }
  523. }
  524. else if (error != null)
  525. {
  526. error(this.parseRequestText(req));
  527. }
  528. }
  529. }), mxUtils.bind(this, function(req)
  530. {
  531. window.clearTimeout(timeoutThread);
  532. if (acceptResponse)
  533. {
  534. this.writingFile = false;
  535. if (error != null)
  536. {
  537. error(this.parseRequestText(req));
  538. }
  539. }
  540. }));
  541. });
  542. fn();
  543. }
  544. else if (error != null)
  545. {
  546. error();
  547. }
  548. }
  549. else if (error != null)
  550. {
  551. error({code: App.ERROR_BUSY});
  552. }
  553. };
  554. /**
  555. * Checks if the client is authorized and calls the next step.
  556. */
  557. OneDriveClient.prototype.parseRequestText = function(req)
  558. {
  559. var result = {message: mxResources.get('unknownError')};
  560. try
  561. {
  562. result = JSON.parse(req.getText());
  563. }
  564. catch (e)
  565. {
  566. // ignore
  567. }
  568. return result;
  569. };
  570. /**
  571. * Checks if the client is authorized and calls the next step.
  572. */
  573. OneDriveClient.prototype.pickLibrary = function(fn)
  574. {
  575. this.pickFile(fn);
  576. };
  577. /**
  578. * Checks if the client is authorized and calls the next step.
  579. */
  580. OneDriveClient.prototype.pickFolder = function(fn)
  581. {
  582. // Default folder not supported in OneDrive
  583. if (this.ui.spinner.spin(document.body, mxResources.get('loading')))
  584. {
  585. this.execute(mxUtils.bind(this, function(token)
  586. {
  587. this.ui.spinner.stop();
  588. if (token != null)
  589. {
  590. WL.fileDialog(
  591. {
  592. mode: 'save'
  593. }).then(
  594. function (resp)
  595. {
  596. fn(resp);
  597. },
  598. function (responseFailed)
  599. {
  600. fn(null);
  601. }
  602. );
  603. }
  604. }));
  605. }
  606. };
  607. /**
  608. * Checks if the client is authorized and calls the next step.
  609. */
  610. OneDriveClient.prototype.pickFile = function(fn)
  611. {
  612. fn = (fn != null) ? fn : mxUtils.bind(this, function(id)
  613. {
  614. this.ui.loadFile('W' + encodeURIComponent(id));
  615. });
  616. // First time loading is slow so show spinner
  617. if (this.ui.spinner.spin(document.body, mxResources.get('loading')))
  618. {
  619. this.execute(mxUtils.bind(this, function(token)
  620. {
  621. this.ui.spinner.stop();
  622. if (token != null)
  623. {
  624. WL.fileDialog(
  625. {
  626. mode: 'open',
  627. select: 'multi'
  628. }).then(
  629. function (resp)
  630. {
  631. if (resp != null && resp.data != null && resp.data.files != null)
  632. {
  633. for (var i = 0; i < resp.data.files.length; i++)
  634. {
  635. var id = resp.data.files[i].id;
  636. id = id.substring(id.lastIndexOf('.') + 1);
  637. fn(id);
  638. }
  639. }
  640. },
  641. function (responseFailed) {}
  642. );
  643. }
  644. }));
  645. }
  646. };
  647. /**
  648. * Checks if the client is authorized and calls the next step.
  649. */
  650. OneDriveClient.prototype.logout = function()
  651. {
  652. this.setUser(null);
  653. this.clearCookie();
  654. // LATER: Check why async callback does not work
  655. WL.logout();
  656. };