1/** 2 * Copyright (c) 2006-2020, JGraph Ltd 3 * Copyright (c) 2006-2020, draw.io AG 4 */ 5 6//Add a closure to hide the class private variables without changing the code a lot 7(function () 8{ 9 10var _token = null; 11 12window.OneDriveClient = function(editorUi, isExtAuth, inlinePicker, noLogout) 13{ 14 if (isExtAuth == null && window.urlParams != null && window.urlParams['extAuth'] == '1') 15 { 16 isExtAuth = true; 17 } 18 19 if (inlinePicker == null) //Use inline picker as default 20 { 21 inlinePicker = window.Editor != null? Editor.oneDriveInlinePicker : true; 22 } 23 24 if (noLogout == null && window.urlParams != null && window.urlParams['noLogoutOD'] == '1') 25 { 26 noLogout = true; 27 } 28 29 DrawioClient.call(this, editorUi, isExtAuth? 'oneDriveExtAuthInfo' : 'oneDriveAuthInfo'); 30 31 this.isExtAuth = isExtAuth; 32 this.inlinePicker = inlinePicker; 33 this.noLogout = noLogout; 34 var authInfo = JSON.parse(this.token); 35 36 if (authInfo != null) 37 { 38 this.endpointHint = authInfo.endpointHint != null ? authInfo.endpointHint.replace('/Documents', '/_layouts/15/onedrive.aspx') : authInfo.endpointHint; 39 } 40}; 41 42// Extends DrawioClient 43mxUtils.extend(OneDriveClient, DrawioClient); 44 45/** 46 * Specifies if thumbnails should be enabled. Default is true. 47 * LATER: If thumbnails are disabled, make sure to replace the 48 * existing thumbnail with the placeholder only once. 49 */ 50OneDriveClient.prototype.clientId = window.DRAWIO_MSGRAPH_CLIENT_ID || ((window.location.hostname == 'test.draw.io') ? 51 '2e598409-107f-4b59-89ca-d7723c8e00a4' : '45c10911-200f-4e27-a666-9e9fca147395'); 52 53OneDriveClient.prototype.clientId = window.location.hostname == 'app.diagrams.net' ? 54 'b5ff67d6-3155-4fca-965a-59a3655c4476' : OneDriveClient.prototype.clientId; 55 56OneDriveClient.prototype.clientId = window.location.hostname == 'viewer.diagrams.net' ? 57 '417a451a-a343-4788-b6c1-901e63182565' : OneDriveClient.prototype.clientId; 58/** 59 * OAuth 2.0 scopes for installing Drive Apps. 60 */ 61OneDriveClient.prototype.scopes = 'user.read files.readwrite.all sites.read.all'; 62 63/** 64 * OAuth 2.0 scopes for installing Drive Apps. 65 */ 66OneDriveClient.prototype.redirectUri = window.location.protocol + '//' + window.location.host + '/microsoft'; 67OneDriveClient.prototype.pickerRedirectUri = window.location.protocol + '//' + window.location.host + '/onedrive3.html'; 68 69/** 70 * This is the default endpoint for personal accounts 71 */ 72OneDriveClient.prototype.defEndpointHint = 'api.onedrive.com'; 73OneDriveClient.prototype.endpointHint = OneDriveClient.prototype.defEndpointHint; 74 75/** 76 * Executes the first step for connecting to Google Drive. 77 */ 78OneDriveClient.prototype.extension = '.drawio'; 79 80/** 81 * Executes the first step for connecting to Google Drive. 82 */ 83OneDriveClient.prototype.baseUrl = 'https://graph.microsoft.com/v1.0'; 84 85/** 86 * Empty function used when no callback is needed 87 */ 88OneDriveClient.prototype.emptyFn = function(){}; 89 90OneDriveClient.prototype.invalidFilenameRegExs = [ 91 /[~"#%\*:<>\?\/\\{\|}]/, 92 /^\.lock$/i, 93 /^CON$/i, 94 /^PRN$/i, 95 /^AUX$/i, 96 /^NUL$/i, 97 /^COM\d$/i, 98 /^LPT\d$/i, 99 /^desktop\.ini$/i, 100 /_vti_/i 101]; 102 103/** 104 * Check if the file/folder name is valid 105 */ 106OneDriveClient.prototype.isValidFilename = function(filename) 107{ 108 if (filename == null || filename === '') return false; 109 110 for (var i = 0; i < this.invalidFilenameRegExs.length; i++) 111 { 112 if (this.invalidFilenameRegExs[i].test(filename)) return false; 113 } 114 115 return true; 116}; 117 118 119/** 120 * Checks if the client is authorized and calls the next step. 121 */ 122OneDriveClient.prototype.get = function(url, onload, onerror) 123{ 124 var req = new mxXmlRequest(url, null, 'GET'); 125 126 req.setRequestHeaders = mxUtils.bind(this, function(request, params) 127 { 128 request.setRequestHeader('Authorization', 'Bearer ' + _token); 129 }); 130 131 req.send(onload, onerror); 132 133 return req; 134}; 135 136/** 137 * Checks if the client is authorized and calls the next step. 138 */ 139OneDriveClient.prototype.updateUser = function(success, error, failOnAuth) 140{ 141 var acceptResponse = true; 142 143 var timeoutThread = window.setTimeout(mxUtils.bind(this, function() 144 { 145 acceptResponse = false; 146 error({code: App.ERROR_TIMEOUT}); 147 }), this.ui.timeout); 148 149 this.get(this.baseUrl + '/me', mxUtils.bind(this, function(req) 150 { 151 window.clearTimeout(timeoutThread); 152 153 if (acceptResponse) 154 { 155 if (req.getStatus() < 200 || req.getStatus() >= 300) 156 { 157 if (!failOnAuth) 158 { 159 this.logout(); 160 161 this.authenticate(mxUtils.bind(this, function() 162 { 163 this.updateUser(success, error, true); 164 }), error); 165 } 166 else 167 { 168 error({message: mxResources.get('accessDenied')}); 169 } 170 } 171 else 172 { 173 var data = JSON.parse(req.getText()); 174 this.setUser(new DrawioUser(data.id, data.mail, data.displayName)); 175 success(); 176 } 177 } 178 }), mxUtils.bind(this, function(err) 179 { 180 window.clearTimeout(timeoutThread); 181 182 if (acceptResponse) 183 { 184 error(err); 185 } 186 })); 187}; 188 189OneDriveClient.prototype.resetTokenRefresh = function(expires_in) 190{ 191 if (this.tokenRefreshThread != null) 192 { 193 window.clearTimeout(this.tokenRefreshThread); 194 this.tokenRefreshThread = null; 195 } 196 197 // Starts timer to refresh token before it expires 198 if (expires_in > 0) 199 { 200 this.tokenRefreshInterval = expires_in * 1000; 201 202 this.tokenRefreshThread = window.setTimeout(mxUtils.bind(this, function() 203 { 204 //Get a new fresh accessToken 205 this.authenticate(this.emptyFn, this.emptyFn, true); 206 }), expires_in * 900); 207 } 208}; 209 210 211/** 212 * Authorizes the client, gets the userId and calls <open>. 213 */ 214OneDriveClient.prototype.authenticate = function(success, error, failOnAuth) 215{ 216 if (this.isExtAuth) 217 { 218 window.parent.oneDriveAuth(mxUtils.bind(this, function(newAuthInfo) 219 { 220 this.updateAuthInfo(newAuthInfo, true, this.endpointHint == null, success, error); 221 }), error, window.urlParams != null && urlParams['odAuthCancellable'] == '1'); 222 return; 223 } 224 225 var req = new mxXmlRequest(this.redirectUri + '?getState=1', null, 'GET'); 226 227 req.send(mxUtils.bind(this, function(req) 228 { 229 if (req.getStatus() >= 200 && req.getStatus() <= 299) 230 { 231 this.authenticateStep2(req.getText(), success, error, failOnAuth); 232 } 233 else if (error != null) 234 { 235 error(req); 236 } 237 }), error); 238}; 239 240OneDriveClient.prototype.updateAuthInfo = function(newAuthInfo, remember, forceUserUpdate, success, error) 241{ 242 if (forceUserUpdate) 243 { 244 this.setUser(null); 245 } 246 247 _token = newAuthInfo.access_token; 248 delete newAuthInfo.access_token; //Don't store access token 249 newAuthInfo.expiresOn = Date.now() + newAuthInfo.expires_in * 1000; 250 this.tokenExpiresOn = newAuthInfo.expiresOn; 251 252 newAuthInfo.remember = remember; 253 this.setPersistentToken(JSON.stringify(newAuthInfo), !remember); 254 this.resetTokenRefresh(newAuthInfo.expires_in); 255 256 if (forceUserUpdate) 257 { 258 //Find out the type of the account + endpoint 259 this.getAccountTypeAndEndpoint(mxUtils.bind(this, function() 260 { 261 success(); 262 }), error); 263 } 264 else 265 { 266 success(); 267 } 268}; 269 270OneDriveClient.prototype.authenticateStep2 = function(state, success, error, failOnAuth) 271{ 272 if (window.onOneDriveCallback == null) 273 { 274 var auth = mxUtils.bind(this, function() 275 { 276 var acceptAuthResponse = true; 277 278 //Retry request with refreshed token 279 var authInfo = JSON.parse(this.getPersistentToken(true)); 280 281 if (authInfo != null) 282 { 283 var req = new mxXmlRequest(this.redirectUri + '?state=' + encodeURIComponent('cId=' + this.clientId + '&domain=' + window.location.hostname + '&token=' + state), null, 'GET'); //To identify which app/domain is used 284 285 req.send(mxUtils.bind(this, function(req) 286 { 287 if (req.getStatus() >= 200 && req.getStatus() <= 299) 288 { 289 this.updateAuthInfo(JSON.parse(req.getText()), authInfo.remember, false, success, error); 290 } 291 else 292 { 293 this.clearPersistentToken(); 294 this.setUser(null); 295 _token = null; 296 297 if (req.getStatus() == 401 && !failOnAuth) // (Unauthorized) [e.g, invalid refresh token] 298 { 299 auth(); 300 } 301 else 302 { 303 error({message: mxResources.get('accessDenied'), retry: auth}); 304 } 305 } 306 }), error); 307 } 308 else 309 { 310 this.ui.showAuthDialog(this, true, mxUtils.bind(this, function(remember, authSuccess) 311 { 312 var url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize' + 313 '?client_id=' + this.clientId + '&response_type=code' + 314 '&redirect_uri=' + encodeURIComponent(this.redirectUri) + 315 '&scope=' + encodeURIComponent(this.scopes + (remember? ' offline_access' : '')) + 316 '&state=' + encodeURIComponent('cId=' + this.clientId + '&domain=' + window.location.hostname + '&token=' + state); //To identify which app/domain is used 317 318 var width = 525, 319 height = 525, 320 screenX = window.screenX, 321 screenY = window.screenY, 322 outerWidth = window.outerWidth, 323 outerHeight = window.outerHeight; 324 325 var left = screenX + Math.max(outerWidth - width, 0) / 2; 326 var top = screenY + Math.max(outerHeight - height, 0) / 2; 327 328 var features = ['width=' + width, 'height=' + height, 329 'top=' + top, 'left=' + left, 330 'status=no', 'resizable=yes', 331 'toolbar=no', 'menubar=no', 332 'scrollbars=yes']; 333 var popup = window.open(url, 'odauth', features.join(',')); 334 335 if (popup != null) 336 { 337 window.onOneDriveCallback = mxUtils.bind(this, function(authInfo, authWindow) 338 { 339 if (acceptAuthResponse) 340 { 341 window.onOneDriveCallback = null; 342 acceptAuthResponse = false; 343 344 try 345 { 346 if (authInfo == null) 347 { 348 error({message: mxResources.get('accessDenied'), retry: auth}); 349 } 350 else 351 { 352 if (authSuccess != null) 353 { 354 authSuccess(); 355 } 356 357 this.updateAuthInfo(authInfo, remember, true, success, error); 358 } 359 } 360 catch (e) 361 { 362 error(e); 363 } 364 finally 365 { 366 if (authWindow != null) 367 { 368 authWindow.close(); 369 } 370 } 371 } 372 else if (authWindow != null) 373 { 374 authWindow.close(); 375 } 376 }); 377 378 popup.focus(); 379 } 380 }), mxUtils.bind(this, function() 381 { 382 if (acceptAuthResponse) 383 { 384 window.onOneDriveCallback = null; 385 acceptAuthResponse = false; 386 error({message: mxResources.get('accessDenied'), retry: auth}); 387 } 388 })); 389 } 390 }); 391 392 auth(); 393 } 394 else 395 { 396 error({code: App.ERROR_BUSY}); 397 } 398}; 399 400 401OneDriveClient.prototype.getAccountTypeAndEndpoint = function(success, error) 402{ 403 this.get(this.baseUrl + '/me/drive/root', mxUtils.bind(this, function(req) 404 { 405 try 406 { 407 if (req.getStatus() >= 200 && req.getStatus() <= 299) 408 { 409 var resp = JSON.parse(req.getText()); 410 411 if (resp.webUrl.indexOf('.sharepoint.com') > 0) 412 { 413 //TODO Confirm this works with all sharepoint sites 414 this.endpointHint = resp.webUrl.replace('/Documents', '/_layouts/15/onedrive.aspx'); 415 } 416 else 417 { 418 this.endpointHint = this.defEndpointHint; 419 } 420 421 //Update authInfo with endpointHint 422 var authInfo = JSON.parse(this.getPersistentToken(true)); 423 424 if (authInfo != null) 425 { 426 authInfo.endpointHint = this.endpointHint; 427 this.setPersistentToken(JSON.stringify(authInfo), !authInfo.remember); 428 } 429 430 success(); 431 return; 432 } 433 } 434 catch(e) {} 435 //It is expected to work as this call immediately follows getting a fresh access token 436 error({message: mxResources.get('unknownError') + ' (Code: ' + req.getStatus() + ')'}); 437 438 }), error); 439}; 440 441/** 442 * Checks if the client is authorized and calls the next step. 443 */ 444OneDriveClient.prototype.executeRequest = function(url, success, error) 445{ 446 var doExecute = mxUtils.bind(this, function(failOnAuth) 447 { 448 var acceptResponse = true; 449 450 var timeoutThread = window.setTimeout(mxUtils.bind(this, function() 451 { 452 acceptResponse = false; 453 error({code: App.ERROR_TIMEOUT, retry: doExecute}); 454 }), this.ui.timeout); 455 456 this.get(url, mxUtils.bind(this, function(req) 457 { 458 window.clearTimeout(timeoutThread); 459 460 if (acceptResponse) 461 { 462 // 404 (file not found) is a valid response for checkExists 463 if ((req.getStatus() >= 200 && req.getStatus() <= 299) || req.getStatus() == 404) 464 { 465 if (this.user == null) 466 { 467 this.updateUser(this.emptyFn, this.emptyFn, true); 468 } 469 470 success(req); 471 } 472 // 400 is returns if wrong user for this file 473 else if (!failOnAuth && (req.getStatus() === 401 || req.getStatus() === 400)) 474 { 475 //Authorize again using the refresh token 476 this.authenticate(function() 477 { 478 doExecute(true); 479 }, error, failOnAuth); 480 } 481 else 482 { 483 error(this.parseRequestText(req)); 484 } 485 } 486 }), mxUtils.bind(this, function(err) 487 { 488 window.clearTimeout(timeoutThread); 489 490 if (acceptResponse) 491 { 492 error(err); 493 } 494 })); 495 }); 496 497 if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window 498 { 499 this.authenticate(function() 500 { 501 doExecute(true); 502 }, error); 503 } 504 else 505 { 506 doExecute(false); 507 } 508}; 509 510/** 511 * Checks if the client is authorized and calls the next step. 512 */ 513OneDriveClient.prototype.checkToken = function(fn) 514{ 515 if (_token == null || this.tokenRefreshThread == null || this.tokenExpiresOn - Date.now() < 60000) 516 { 517 this.authenticate(fn, this.emptyFn); 518 } 519 else 520 { 521 fn(); 522 } 523}; 524 525OneDriveClient.prototype.getItemRef = function(id) 526{ 527 var idParts = id.split('/'); 528 529 if (idParts.length > 1) 530 { 531 return {driveId: idParts[0], id: idParts[1]}; 532 } 533 else 534 { 535 return {id: id}; 536 } 537}; 538 539OneDriveClient.prototype.getItemURL = function(id, relative) 540{ 541 var idParts = id.split('/'); 542 543 if (idParts.length > 1) 544 { 545 var driveId = idParts[0]; 546 var itemId = idParts[1]; 547 return (relative? '' : this.baseUrl) + '/drives/' + driveId + (itemId == 'root' ? '/root' : '/items/' + itemId); 548 } 549 else 550 { 551 return (relative? '' : this.baseUrl) + '/me/drive/items/' + id; 552 } 553}; 554 555/** 556 * Checks if the client is authorized and calls the next step. 557 */ 558OneDriveClient.prototype.getLibrary = function(id, success, error) 559{ 560 this.getFile(id, success, error, false, true); 561}; 562 563/** 564 * Workaround for added content to HTML files in Sharepoint. 565 */ 566OneDriveClient.prototype.removeExtraHtmlContent = function(data) 567{ 568 var idx = data.lastIndexOf('<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8"><meta name="Robots" '); 569 570 if (idx > 0) 571 { 572 data = data.substring(0, idx); 573 } 574 575 return data; 576}; 577 578/** 579 * Checks if the client is authorized and calls the next step. 580 */ 581OneDriveClient.prototype.getFile = function(id, success, error, denyConvert, asLibrary) 582{ 583 asLibrary = (asLibrary != null) ? asLibrary : false; 584 585 this.executeRequest(this.getItemURL(id), mxUtils.bind(this, function(req) 586 { 587 if (req.getStatus() >= 200 && req.getStatus() <= 299) 588 { 589 var meta = JSON.parse(req.getText()); 590 var binary = /\.png$/i.test(meta.name); 591 592 // Handles .vsdx, Gliffy and PNG+XML files by creating a temporary file 593 if (/\.v(dx|sdx?)$/i.test(meta.name) || /\.gliffy$/i.test(meta.name) || 594 /\.pdf$/i.test(meta.name) || (!this.ui.useCanvasForExport && binary)) 595 { 596 var mimeType = (meta.file != null) ? meta.file.mimeType : null; 597 this.ui.convertFile(meta['@microsoft.graph.downloadUrl'], meta.name, mimeType, 598 this.extension, success, error); 599 } 600 else 601 { 602 var acceptResponse = true; 603 604 var timeoutThread = window.setTimeout(mxUtils.bind(this, function() 605 { 606 acceptResponse = false; 607 error({code: App.ERROR_TIMEOUT}) 608 }), this.ui.timeout); 609 610 this.ui.editor.loadUrl(meta['@microsoft.graph.downloadUrl'], mxUtils.bind(this, function(data) 611 { 612 try 613 { 614 window.clearTimeout(timeoutThread); 615 616 if (acceptResponse) 617 { 618 if (/\.html$/i.test(meta.name)) 619 { 620 data = this.removeExtraHtmlContent(data); 621 } 622 623 var index = (binary) ? data.lastIndexOf(',') : -1; 624 var file = null; 625 626 if (index > 0) 627 { 628 var xml = this.ui.extractGraphModelFromPng(data); 629 630 if (xml != null && xml.length > 0) 631 { 632 data = xml; 633 } 634 else 635 { 636 // Imports as PNG image 637 file = new LocalFile(this.ui, data, meta.name, true); 638 } 639 } 640 // Checks for base64 encoded mxfile 641 else if (data.substring(0, 32) == 'data:image/png;base64,PG14ZmlsZS') 642 { 643 var temp = data.substring(22); 644 data = (window.atob && !mxClient.IS_SF) ? atob(temp) : Base64.decode(temp); 645 } 646 647 if (Graph.fileSupport && new XMLHttpRequest().upload && this.ui.isRemoteFileFormat(data, meta['@microsoft.graph.downloadUrl'])) 648 { 649 this.ui.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr) 650 { 651 try 652 { 653 if (xhr.readyState == 4) 654 { 655 if (xhr.status >= 200 && xhr.status <= 299) 656 { 657 success(new LocalFile(this.ui, xhr.responseText, meta.name + this.extension, true)); 658 } 659 else if (error != null) 660 { 661 error({message: mxResources.get('errorLoadingFile')}); 662 } 663 } 664 } 665 catch (e) 666 { 667 if (error != null) 668 { 669 error(e); 670 } 671 else 672 { 673 throw e; 674 } 675 } 676 }), meta.name); 677 } 678 else 679 { 680 if (file != null) 681 { 682 success(file); 683 } 684 else if (asLibrary) 685 { 686 success(new OneDriveLibrary(this.ui, data, meta)); 687 } 688 else 689 { 690 success(new OneDriveFile(this.ui, data, meta)); 691 } 692 } 693 } 694 } 695 catch (e) 696 { 697 if (error != null) 698 { 699 error(e); 700 } 701 else 702 { 703 throw e; 704 } 705 } 706 }), mxUtils.bind(this, function(req) 707 { 708 window.clearTimeout(timeoutThread); 709 710 if (acceptResponse) 711 { 712 error(this.parseRequestText(req)); 713 } 714 }), binary || (meta.file != null && meta.file.mimeType != null && 715 ((meta.file.mimeType.substring(0, 6) == 'image/' && 716 meta.file.mimeType.substring(0, 9) != 'image/svg') || 717 meta.file.mimeType == 'application/pdf'))); 718 } 719 } 720 else 721 { 722 if (this.isExtAuth) 723 { 724 error({message: mxResources.get('fileNotFoundOrDenied'), 725 ownerEmail: window.urlParams != null? urlParams['ownerEml'] : null}); 726 } 727 else 728 { 729 error(this.parseRequestText(req)); 730 } 731 } 732 }), error); 733}; 734 735/** 736 * Translates this point by the given vector. 737 * 738 * @param {number} dx X-coordinate of the translation. 739 * @param {number} dy Y-coordinate of the translation. 740 */ 741OneDriveClient.prototype.renameFile = function(file, filename, success, error) 742{ 743 if (file != null && filename != null) 744 { 745 if (!this.isValidFilename(filename)) 746 { 747 error({message: this.invalidFilenameRegExs[0].test(filename) ? 748 mxResources.get('oneDriveCharsNotAllowed') : mxResources.get('oneDriveInvalidDeviceName')}); 749 return; 750 } 751 752 // TODO: How to force overwrite file with same name? 753 this.checkExists(file.getParentId(), filename, false, mxUtils.bind(this, function(checked) 754 { 755 if (checked) 756 { 757 this.writeFile(this.getItemURL(file.getId()), JSON.stringify({name: filename}), 'PATCH', 'application/json', success, error); 758 } 759 else 760 { 761 error(); 762 } 763 })); 764 } 765}; 766 767/** 768 * Translates this point by the given vector. 769 * 770 * @param {number} dx X-coordinate of the translation. 771 * @param {number} dy Y-coordinate of the translation. 772 */ 773OneDriveClient.prototype.moveFile = function(id, folderId, success, error) 774{ 775 //check that the source and destination are on the same drive 776 var folderInfo = this.getItemRef(folderId); 777 var fileInfo = this.getItemRef(id); 778 779 if (folderInfo.driveId != fileInfo.driveId) 780 { 781 error({message: mxResources.get('cannotMoveOneDrive', null, 'Moving a file between accounts is not supported yet.')}); 782 } 783 else 784 { 785 this.writeFile(this.getItemURL(id), JSON.stringify({parentReference: folderInfo}), 'PATCH', 'application/json', success, error); 786 } 787}; 788 789/** 790 * Translates this point by the given vector. 791 * 792 * @param {number} dx X-coordinate of the translation. 793 * @param {number} dy Y-coordinate of the translation. 794 */ 795OneDriveClient.prototype.insertLibrary = function(filename, data, success, error, folderId) 796{ 797 this.insertFile(filename, data, success, error, true, folderId); 798}; 799 800/** 801 * Translates this point by the given vector. 802 * 803 * @param {number} dx X-coordinate of the translation. 804 * @param {number} dy Y-coordinate of the translation. 805 */ 806OneDriveClient.prototype.insertFile = function(filename, data, success, error, asLibrary, folderId) 807{ 808 if (!this.isValidFilename(filename)) 809 { 810 error({message: this.invalidFilenameRegExs[0].test(filename) ? 811 mxResources.get('oneDriveCharsNotAllowed') : mxResources.get('oneDriveInvalidDeviceName')}); 812 return; 813 } 814 815 asLibrary = (asLibrary != null) ? asLibrary : false; 816 817 this.checkExists(folderId, filename, true, mxUtils.bind(this, function(checked) 818 { 819 if (checked) 820 { 821 var folder = '/me/drive/root'; 822 823 if (folderId != null) 824 { 825 folder = this.getItemURL(folderId, true); 826 } 827 828 var insertSuccess = mxUtils.bind(this, function(meta) 829 { 830 if (asLibrary) 831 { 832 success(new OneDriveLibrary(this.ui, data, meta)); 833 } 834 else 835 { 836 success(new OneDriveFile(this.ui, data, meta)); 837 } 838 }); 839 840 var url = this.baseUrl + folder + '/children/' + encodeURIComponent(filename) + '/content'; 841 842 //OneDrive has a limit on PUT API of 4MB, larger files needs to use the upload session method 843 if (data.length >= 4000000 /*4MB*/) 844 { 845 //Create empty file first then upload. TODO Can we get an upload session for non-existing files? 846 this.writeFile(url, '', 'PUT', null, mxUtils.bind(this, function(meta) 847 { 848 this.writeLargeFile(this.getItemURL(meta.id), data, insertSuccess, error); 849 }), error); 850 } 851 else 852 { 853 this.writeFile(url, data, 'PUT', null, insertSuccess, error); 854 } 855 } 856 else 857 { 858 error(); 859 } 860 })) 861}; 862 863/** 864 * Translates this point by the given vector. 865 * 866 * @param {number} dx X-coordinate of the translation. 867 * @param {number} dy Y-coordinate of the translation. 868 */ 869OneDriveClient.prototype.checkExists = function(parentId, filename, askReplace, fn) 870{ 871 var folder = '/me/drive/root'; 872 873 if (parentId != null) 874 { 875 folder = this.getItemURL(parentId, true); 876 } 877 878 this.executeRequest(this.baseUrl + folder + '/children/' + encodeURIComponent(filename), mxUtils.bind(this, function(req) 879 { 880 if (req.getStatus() == 404) 881 { 882 fn(true); 883 } 884 else 885 { 886 if (askReplace) 887 { 888 this.ui.spinner.stop(); 889 890 this.ui.confirm(mxResources.get('replaceIt', [filename]), function() 891 { 892 fn(true); 893 }, function() 894 { 895 fn(false); 896 }); 897 } 898 else 899 { 900 this.ui.spinner.stop(); 901 902 this.ui.showError(mxResources.get('error'), mxResources.get('fileExists'), mxResources.get('ok'), function() 903 { 904 fn(false); 905 }); 906 } 907 } 908 }), function(req) 909 { 910 fn(false); 911 }, true); 912}; 913 914/** 915 * Translates this point by the given vector. 916 * 917 * @param {number} dx X-coordinate of the translation. 918 * @param {number} dy Y-coordinate of the translation. 919 */ 920OneDriveClient.prototype.saveFile = function(file, success, error, etag) 921{ 922 try 923 { 924 var savedData = file.getData(); 925 926 var fn = mxUtils.bind(this, function(data) 927 { 928 var saveSuccess = mxUtils.bind(this, function(resp) 929 { 930 success(resp, savedData); 931 }); 932 933 var url = this.getItemURL(file.getId()); 934 935 //OneDrive has a limit on PUT API of 4MB, larger files needs to use the upload session method 936 if (data.length >= 4000000 /*4MB*/) 937 { 938 this.writeLargeFile(url, data, saveSuccess, error, etag); 939 } 940 else 941 { 942 this.writeFile(url + '/content/', data, 'PUT', null, saveSuccess, error, etag); 943 } 944 }); 945 946 if (this.ui.useCanvasForExport && /(\.png)$/i.test(file.meta.name)) 947 { 948 var p = this.ui.getPngFileProperties(this.ui.fileNode); 949 950 this.ui.getEmbeddedPng(mxUtils.bind(this, function(data) 951 { 952 fn(this.ui.base64ToBlob(data, 'image/png')); 953 }), error, (this.ui.getCurrentFile() != file) ? 954 savedData : null, p.scale, p.border); 955 } 956 else 957 { 958 fn(savedData); 959 } 960 } 961 catch (e) 962 { 963 error(e); 964 } 965}; 966 967OneDriveClient.prototype.writeLargeFile = function(url, data, success, error, etag) 968{ 969 try 970 { 971 var chunkSize = 4 * 1024 * 1024; //4MB chunk; 972 973 if (data != null) 974 { 975 var uploadPart = mxUtils.bind(this, function(uploadUrl, index, retryCount) 976 { 977 try 978 { 979 retryCount = retryCount || 0; 980 var acceptResponse = true; 981 var timeoutThread = null; 982 983 timeoutThread = window.setTimeout(mxUtils.bind(this, function() 984 { 985 acceptResponse = false; 986 error({code: App.ERROR_TIMEOUT}); 987 }), this.ui.timeout); 988 989 var part = data.substr(index, chunkSize); 990 var req = new mxXmlRequest(uploadUrl, part, 'PUT'); 991 992 req.setRequestHeaders = mxUtils.bind(this, function(request, params) 993 { 994 request.setRequestHeader('Content-Length', part.length); 995 request.setRequestHeader('Content-Range', 'bytes ' + index + '-' + (index + part.length - 1) + '/' + data.length); 996 }); 997 998 req.send(mxUtils.bind(this, function(req) 999 { 1000 window.clearTimeout(timeoutThread); 1001 1002 if (acceptResponse) 1003 { 1004 var status = req.getStatus(); 1005 if (status >= 200 && status <= 299) 1006 { 1007 var nextByte = index + part.length; 1008 1009 if (nextByte == data.length) 1010 { 1011 success(JSON.parse(req.getText())); 1012 } 1013 else 1014 { 1015 uploadPart(uploadUrl, nextByte, retryCount); 1016 } 1017 } 1018 else if (status >= 500 && status <= 599 && retryCount < 2) //Retry on server errors 1019 { 1020 retryCount++; 1021 uploadPart(uploadUrl, index, retryCount); 1022 } 1023 else 1024 { 1025 error(this.parseRequestText(req), req); 1026 } 1027 } 1028 }), mxUtils.bind(this, function(req) 1029 { 1030 window.clearTimeout(timeoutThread); 1031 1032 if (acceptResponse) 1033 { 1034 error(this.parseRequestText(req)); 1035 } 1036 })); 1037 } 1038 catch (e) 1039 { 1040 error(e); 1041 } 1042 }); 1043 1044 var doExecute = mxUtils.bind(this, function(failOnAuth) 1045 { 1046 try 1047 { 1048 var acceptResponse = true; 1049 var timeoutThread = null; 1050 1051 try 1052 { 1053 timeoutThread = window.setTimeout(mxUtils.bind(this, function() 1054 { 1055 acceptResponse = false; 1056 error({code: App.ERROR_TIMEOUT}); 1057 }), this.ui.timeout); 1058 } 1059 catch (e) 1060 { 1061 // Ignore window closed 1062 } 1063 1064 var req = new mxXmlRequest(url + '/createUploadSession', '{}', 'POST'); 1065 1066 req.setRequestHeaders = mxUtils.bind(this, function(request, params) 1067 { 1068 request.setRequestHeader('Content-Type', 'application/json'); 1069 request.setRequestHeader('Authorization', 'Bearer ' + _token); 1070 1071 if (etag != null) 1072 { 1073 request.setRequestHeader('If-Match', etag); 1074 } 1075 }); 1076 1077 req.send(mxUtils.bind(this, function(req) 1078 { 1079 window.clearTimeout(timeoutThread); 1080 1081 if (acceptResponse) 1082 { 1083 if (req.getStatus() >= 200 && req.getStatus() <= 299) 1084 { 1085 var resp = JSON.parse(req.getText()); 1086 uploadPart(resp.uploadUrl, 0); 1087 } 1088 else if (!failOnAuth && req.getStatus() === 401) 1089 { 1090 this.authenticate(function() 1091 { 1092 doExecute(true); 1093 }, error, failOnAuth); 1094 } 1095 else 1096 { 1097 error(this.parseRequestText(req), req); 1098 } 1099 } 1100 }), mxUtils.bind(this, function(req) 1101 { 1102 window.clearTimeout(timeoutThread); 1103 1104 if (acceptResponse) 1105 { 1106 error(this.parseRequestText(req)); 1107 } 1108 })); 1109 } 1110 catch (e) 1111 { 1112 error(e); 1113 } 1114 }); 1115 1116 if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window 1117 { 1118 this.authenticate(function() 1119 { 1120 doExecute(true); 1121 }, error); 1122 } 1123 else 1124 { 1125 doExecute(false); 1126 } 1127 } 1128 else 1129 { 1130 error({message: mxResources.get('unknownError')}); 1131 } 1132 } 1133 catch (e) 1134 { 1135 error(e); 1136 } 1137}; 1138 1139/** 1140 * Translates this point by the given vector. 1141 * 1142 * @param {number} dx X-coordinate of the translation. 1143 * @param {number} dy Y-coordinate of the translation. 1144 */ 1145OneDriveClient.prototype.writeFile = function(url, data, method, contentType, success, error, etag) 1146{ 1147 try 1148 { 1149 if (url != null && data != null) 1150 { 1151 var doExecute = mxUtils.bind(this, function(failOnAuth) 1152 { 1153 try 1154 { 1155 var acceptResponse = true; 1156 var timeoutThread = null; 1157 1158 try 1159 { 1160 timeoutThread = window.setTimeout(mxUtils.bind(this, function() 1161 { 1162 acceptResponse = false; 1163 error({code: App.ERROR_TIMEOUT}); 1164 }), this.ui.timeout); 1165 } 1166 catch (e) 1167 { 1168 // Ignore window closed 1169 } 1170 1171 var req = new mxXmlRequest(url, data, method); 1172 1173 req.setRequestHeaders = mxUtils.bind(this, function(request, params) 1174 { 1175 // Space deletes content type header. Specification says "text/plain" 1176 // should work but returns an 415 Unsupported Media Type error 1177 request.setRequestHeader('Content-Type', contentType || ' '); 1178 //TODO This header is needed for moving a file between two different drives. 1179 // Note: the response is empty when this header is used, also the server may take some time to really execute the request (i.e. async) 1180 //request.setRequestHeader('Prefer', 'respond-async'); 1181 request.setRequestHeader('Authorization', 'Bearer ' + _token); 1182 1183 if (etag != null) 1184 { 1185 request.setRequestHeader('If-Match', etag); 1186 } 1187 }); 1188 1189 req.send(mxUtils.bind(this, function(req) 1190 { 1191 window.clearTimeout(timeoutThread); 1192 1193 if (acceptResponse) 1194 { 1195 if (req.getStatus() >= 200 && req.getStatus() <= 299) 1196 { 1197 if (this.user == null) 1198 { 1199 this.updateUser(this.emptyFn, this.emptyFn, true); 1200 } 1201 1202 success(JSON.parse(req.getText())); 1203 } 1204 else if (!failOnAuth && req.getStatus() === 401) 1205 { 1206 this.authenticate(function() 1207 { 1208 doExecute(true); 1209 }, error, failOnAuth); 1210 } 1211 else 1212 { 1213 error(this.parseRequestText(req), req); 1214 } 1215 } 1216 }), mxUtils.bind(this, function(req) 1217 { 1218 window.clearTimeout(timeoutThread); 1219 1220 if (acceptResponse) 1221 { 1222 error(this.parseRequestText(req)); 1223 } 1224 })); 1225 } 1226 catch (e) 1227 { 1228 error(e); 1229 } 1230 }); 1231 1232 if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window 1233 { 1234 this.authenticate(function() 1235 { 1236 doExecute(true); 1237 }, error); 1238 } 1239 else 1240 { 1241 doExecute(false); 1242 } 1243 } 1244 else 1245 { 1246 error({message: mxResources.get('unknownError')}); 1247 } 1248 } 1249 catch (e) 1250 { 1251 error(e); 1252 } 1253}; 1254 1255/** 1256 * Checks if the client is authorized and calls the next step. 1257 */ 1258OneDriveClient.prototype.parseRequestText = function(req) 1259{ 1260 var result = {message: mxResources.get('unknownError')}; 1261 1262 try 1263 { 1264 result = JSON.parse(req.getText()); 1265 result.status = req.getStatus(); 1266 1267 if (result.error) 1268 { 1269 result.error.status = result.status; 1270 result.error.code = result.status; 1271 } 1272 } 1273 catch (e) 1274 { 1275 // ignore 1276 } 1277 1278 return result; 1279}; 1280 1281/** 1282 * Checks if the client is authorized and calls the next step. 1283 */ 1284OneDriveClient.prototype.pickLibrary = function(fn) 1285{ 1286 this.pickFile(function(id) 1287 { 1288 // Ignores second argument 1289 fn(id); 1290 }); 1291}; 1292 1293OneDriveClient.prototype.createInlinePicker = function(fn, foldersOnly) 1294{ 1295 return mxUtils.bind(this, function() 1296 { 1297 var odPicker = null; 1298 var div = document.createElement('div'); 1299 div.style.position = 'relative'; 1300 1301 var dlg = new CustomDialog(this.ui, div, mxUtils.bind(this, function() 1302 { 1303 var item = odPicker.getSelectedItem(); 1304 1305 if (item != null) 1306 { 1307 if (foldersOnly && typeof item.folder == 'object') 1308 { 1309 fn({ 1310 value: [item] 1311 }); 1312 return; 1313 } 1314 else if (!item.folder) 1315 { 1316 fn(OneDriveFile.prototype.getIdOf(item)); 1317 return; 1318 } 1319 } 1320 1321 return mxResources.get('invalidSel', null, 'Invalid selection'); 1322 }), null, mxResources.get(foldersOnly? 'save' :'open'), null, null, null, null, true); 1323 1324 this.ui.showDialog(dlg.container, 550, 500, true, true); 1325 //Set width/height of the picker container 1326 div.style.width = dlg.container.parentNode.style.width; 1327 div.style.height = (parseInt(dlg.container.parentNode.style.height) - 60) + 'px'; 1328 1329 odPicker = new mxODPicker(div, null, mxUtils.bind(this, function(url, success, error) 1330 { 1331 this.executeRequest(this.baseUrl + url, function(req) 1332 { 1333 success(JSON.parse(req.getText())); 1334 }, error); 1335 }), mxUtils.bind(this, function(id, driveId, success, error) 1336 { 1337 this.executeRequest(this.baseUrl + '/drives/' + driveId + '/items/' + id, function(req) 1338 { 1339 success(JSON.parse(req.getText())); 1340 }, error); 1341 }), null, null, function(item) 1342 { 1343 if (foldersOnly) //Currently this is not called when in foldersOnly mode 1344 { 1345 fn({ 1346 value: [item] 1347 }); 1348 } 1349 else 1350 { 1351 fn(OneDriveFile.prototype.getIdOf(item)); 1352 } 1353 }, 1354 mxUtils.bind(this, function(err) 1355 { 1356 this.ui.showError(mxResources.get('error'), err); 1357 }), foldersOnly); 1358 }); 1359}; 1360 1361/** 1362 * Checks if the client is authorized and calls the next step. 1363 */ 1364OneDriveClient.prototype.pickFolder = function(fn, direct) 1365{ 1366 var errorFn = mxUtils.bind(this, function(e) 1367 { 1368 this.ui.showError(mxResources.get('error'), e && e.message? e.message : e); 1369 }); 1370 1371 var odSaveDlg = mxUtils.bind(this, function(direct) 1372 { 1373 var openSaveDlg = this.inlinePicker? this.createInlinePicker(fn, true) : 1374 mxUtils.bind(this, function() 1375 { 1376 OneDrive.save( 1377 { 1378 clientId: this.clientId, 1379 action: 'query', 1380 openInNewWindow: true, 1381 advanced: 1382 { 1383 'endpointHint': mxClient.IS_IE11? null : this.endpointHint, //IE11 doen't work with our modified version, so, setting endpointHint disable using our token BUT will force relogin! 1384 'redirectUri': this.pickerRedirectUri, 1385 'queryParameters': 'select=id,name,parentReference', 1386 'accessToken': _token, 1387 isConsumerAccount: false 1388 }, 1389 success: mxUtils.bind(this, function(files) 1390 { 1391 fn(files); 1392 1393 //Update the token in case a login with a different user 1394 if (mxClient.IS_IE11) 1395 { 1396 _token = files.accessToken; 1397 } 1398 }), 1399 cancel: mxUtils.bind(this, function() 1400 { 1401 // do nothing 1402 }), 1403 error: errorFn 1404 }); 1405 }); 1406 1407 if (direct) 1408 { 1409 openSaveDlg(); 1410 } 1411 else 1412 { 1413 this.ui.confirm(mxResources.get('useRootFolder'), mxUtils.bind(this, function() 1414 { 1415 fn({value: [{id: 'root', name: 'root', parentReference: {driveId: 'me'}}]}); 1416 1417 }), openSaveDlg, mxResources.get('yes'), mxResources.get('noPickFolder') + '...', true); 1418 } 1419 1420 if (this.user == null) 1421 { 1422 this.updateUser(this.emptyFn, this.emptyFn, true); 1423 } 1424 }); 1425 1426 if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window 1427 { 1428 this.authenticate(mxUtils.bind(this, function() 1429 { 1430 odSaveDlg(false); 1431 }), errorFn); 1432 } 1433 else 1434 { 1435 odSaveDlg(direct); 1436 } 1437}; 1438 1439/** 1440 * Checks if the client is authorized and calls the next step. 1441 */ 1442OneDriveClient.prototype.pickFile = function(fn) 1443{ 1444 fn = (fn != null) ? fn : mxUtils.bind(this, function(id) 1445 { 1446 this.ui.loadFile('W' + encodeURIComponent(id)); 1447 }); 1448 1449 var errorFn = mxUtils.bind(this, function(e) 1450 { 1451 this.ui.showError(mxResources.get('error'), e && e.message? e.message : e); 1452 }); 1453 1454 var odOpenDlg = this.inlinePicker? this.createInlinePicker(fn) : 1455 mxUtils.bind(this, function() 1456 { 1457 OneDrive.open( 1458 { 1459 clientId: this.clientId, 1460 action: 'query', 1461 multiSelect: false, 1462 advanced: 1463 { 1464 'endpointHint': mxClient.IS_IE11? null : this.endpointHint, //IE11 doen't work with our modified version, so, setting endpointHint disable using our token BUT will force relogin! 1465 'redirectUri': this.pickerRedirectUri, 1466 'queryParameters': 'select=id,name,parentReference', //We can also get @microsoft.graph.downloadUrl within this request but it will break the normal process 1467 'accessToken': _token, 1468 isConsumerAccount: false 1469 }, 1470 success: mxUtils.bind(this, function(files) 1471 { 1472 if (files != null && files.value != null && files.value.length > 0) 1473 { 1474 //Update the token in case a login with a different user 1475 if (mxClient.IS_IE11) 1476 { 1477 _token = files.accessToken; 1478 } 1479 1480 fn(OneDriveFile.prototype.getIdOf(files.value[0]), files); 1481 } 1482 }), 1483 cancel: mxUtils.bind(this, function() 1484 { 1485 // do nothing 1486 }), 1487 error: errorFn 1488 }); 1489 1490 if (this.user == null) 1491 { 1492 this.updateUser(this.emptyFn, this.emptyFn, true); 1493 } 1494 }); 1495 1496 if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window 1497 { 1498 this.authenticate(mxUtils.bind(this, function() 1499 { 1500 if (this.inlinePicker) 1501 { 1502 this.ui.hideDialog(); 1503 odOpenDlg(); 1504 } 1505 else 1506 { 1507 this.ui.showDialog(new BtnDialog(this.ui, this, mxResources.get('open'), mxUtils.bind(this, function() 1508 { 1509 this.ui.hideDialog(); 1510 odOpenDlg(); 1511 })).container, 300, 140, true, true); 1512 } 1513 }), errorFn); 1514 } 1515 else 1516 { 1517 odOpenDlg(); 1518 } 1519}; 1520 1521/** 1522 * Checks if the client is authorized and calls the next step. 1523 */ 1524OneDriveClient.prototype.logout = function() 1525{ 1526 if (isLocalStorage) 1527 { 1528 var check = localStorage.getItem('odpickerv7cache'); 1529 1530 if (check != null && check.substring(0, 19) == '{"odsdkLoginHint":{') 1531 { 1532 localStorage.removeItem('odpickerv7cache'); 1533 } 1534 } 1535 1536 window.open('https://login.microsoftonline.com/common/oauth2/v2.0/logout', 'logout', 'width=525,height=525,status=no,resizable=yes,toolbar=no,menubar=no,scrollbars=yes'); 1537 //Send to server to clear refresh token cookie 1538 this.ui.editor.loadUrl(this.redirectUri + '?doLogout=1&state=' + encodeURIComponent('cId=' + this.clientId + '&domain=' + window.location.hostname)); 1539 this.clearPersistentToken(); 1540 this.setUser(null); 1541 _token = null; 1542}; 1543 1544})();