/** * Copyright (c) 2006-2017, JGraph Ltd * Copyright (c) 2006-2017, Gaudenz Alder */ OneDriveClient = function(editorUi) { DrawioClient.call(this, editorUi, 'odauth'); this.token = this.token; WL.init({client_id: this.clientId, redirect_uri: this.redirectUri}); }; // Extends DrawioClient mxUtils.extend(OneDriveClient, DrawioClient); /** * Specifies if thumbnails should be enabled. Default is true. * LATER: If thumbnails are disabled, make sure to replace the * existing thumbnail with the placeholder only once. */ OneDriveClient.prototype.clientId = (window.location.hostname == 'test.draw.io') ? '0000000048148130' : ((window.location.hostname == 'drive.draw.io') ? '000000004413EC37' : '0000000040145A19'); /** * OAuth 2.0 scopes for installing Drive Apps. */ OneDriveClient.prototype.scopes = 'wl.skydrive_update wl.signin'; /** * OAuth 2.0 scopes for installing Drive Apps. */ OneDriveClient.prototype.redirectUri = 'https://' + window.location.hostname + '/onedrive.html'; /** * Executes the first step for connecting to Google Drive. */ OneDriveClient.prototype.extension = '.html'; /** * Executes the first step for connecting to Google Drive. */ OneDriveClient.prototype.baseUrl = 'https://api.onedrive.com/v1.0'; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.updateUser = function(success, error, failOnAuth) { var acceptResponse = true; var timeoutThread = window.setTimeout(mxUtils.bind(this, function() { acceptResponse = false; error({code: App.ERROR_TIMEOUT}); }), this.ui.timeout); mxUtils.get(this.baseUrl + '/drive?access_token=' + this.token, mxUtils.bind(this, function(req) { window.clearTimeout(timeoutThread); if (acceptResponse) { if (req.getStatus() < 200 || req.getStatus() >= 300) { if (!failOnAuth) { this.logout(); this.authenticate(mxUtils.bind(this, function() { this.updateUser(success, error, true); }), error); } else { error({message: mxResources.get('accessDenied')}); } } else { var data = JSON.parse(req.getText()); this.setUser(new DrawioUser(data.owner.user.id, null, data.owner.user.displayName)); success(); } } }), error); }; /** * Authorizes the client, gets the userId and calls . */ OneDriveClient.prototype.authenticate = function(success, error) { if (window.onOneDriveCallback == null) { var auth = mxUtils.bind(this, function() { var acceptAuthResponse = true; this.ui.showAuthDialog(this, true, mxUtils.bind(this, function(remember, authSuccess) { var url = 'https://login.live.com/oauth20_authorize.srf?client_id=' + this.clientId + '&scope=' + encodeURIComponent(this.scopes) + '&response_type=token' + '&redirect_uri=' + encodeURIComponent(this.redirectUri); var width = 525, height = 525, screenX = window.screenX, screenY = window.screenY, outerWidth = window.outerWidth, outerHeight = window.outerHeight; var left = screenX + Math.max(outerWidth - width, 0) / 2; var top = screenY + Math.max(outerHeight - height, 0) / 2; var features = ['width=' + width, 'height=' + height, 'top=' + top, 'left=' + left, 'status=no', 'resizable=yes', 'toolbar=no', 'menubar=no', 'scrollbars=yes']; var popup = window.open(url, 'odauth', features.join(',')); if (popup != null) { window.onOneDriveCallback = mxUtils.bind(this, function(token, authWindow) { if (acceptAuthResponse) { window.onOneDriveCallback = null; acceptAuthResponse = false; try { if (token == null) { error({message: mxResources.get('accessDenied'), retry: auth}); } else { if (authSuccess != null) { authSuccess(); } this.setUser(null); this.token = token; if (remember) { this.setPersistentToken(token); } success(); } } catch (e) { error(e); } finally { if (authWindow != null) { authWindow.close(); } } } else if (authWindow != null) { authWindow.close(); } }); popup.focus(); } }), mxUtils.bind(this, function() { if (acceptAuthResponse) { window.onOneDriveCallback = null; acceptAuthResponse = false; error({message: mxResources.get('accessDenied'), retry: auth}); } })); }); auth(); } else { error({code: App.ERROR_BUSY}); } }; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.executeRequest = function(url, success, error) { var doExecute = mxUtils.bind(this, function(failOnAuth) { var acceptResponse = true; var timeoutThread = window.setTimeout(mxUtils.bind(this, function() { acceptResponse = false; error({code: App.ERROR_TIMEOUT, retry: fn}); }), this.ui.timeout); mxUtils.get(url + '?access_token=' + this.token, mxUtils.bind(this, function(req) { window.clearTimeout(timeoutThread); if (acceptResponse) { // 404 (file not found) is a valid response for checkExists if ((req.getStatus() >= 200 && req.getStatus() <= 299) || req.getStatus() == 404) { success(req); } else if (req.getStatus() === 401) { this.clearPersistentToken(); this.setUser(null); this.token = null; if (!failOnAuth) { this.authenticate(function() { doExecute(true); }, error); } else { error({message: mxResources.get('accessDenied'), retry: mxUtils.bind(this, function() { this.authenticate(function() { fn(true); }, error); })}); } } else { error(this.parseRequestText(req)); } } }), error); }); var fn = mxUtils.bind(this, function(failOnAuth) { if (this.user == null) { this.updateUser(function() { fn(true); }, error, failOnAuth); } else { doExecute(failOnAuth); } }); if (this.token == null) { this.authenticate(function() { fn(true); }, error); } else { fn(false); } }; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.getLibrary = function(id, success, error) { this.getFile(id, success, error, false, true); }; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.getFile = function(id, success, error, denyConvert, asLibrary) { asLibrary = (asLibrary != null) ? asLibrary : false; this.executeRequest(this.baseUrl + '/drive/items/' + id, mxUtils.bind(this, function(req) { if (req.getStatus() >= 200 && req.getStatus() <= 299) { var meta = JSON.parse(req.getText()); // Handles .vsdx, Gliffy and PNG+XML files by creating a temporary file if ((/\.vsdx$/i.test(meta.name) || /\.gliffy$/i.test(meta.name) || /\.png$/i.test(meta.name))) { var mimeType = (meta.file != null) ? meta.file.mimeType : null; this.ui.convertFile(meta['@content.downloadUrl'], meta.name, mimeType, this.extension, success, error); } else { var acceptResponse = true; var timeoutThread = window.setTimeout(mxUtils.bind(this, function() { acceptResponse = false; error({code: App.ERROR_TIMEOUT}) }), this.ui.timeout); this.ui.loadUrl(meta['@content.downloadUrl'], mxUtils.bind(this, function(data) { window.clearTimeout(timeoutThread); if (acceptResponse) { if (asLibrary) { success(new OneDriveLibrary(this.ui, data, meta)); } else { success(new OneDriveFile(this.ui, data, meta)); } } }), mxUtils.bind(this, function(req) { window.clearTimeout(timeoutThread); if (acceptResponse) { error(this.parseRequestText(req)); } }), meta.file != null && meta.file.mimeType != null && meta.file.mimeType.substring(0, 6) == 'image/'); } } else { error(this.parseRequestText(req)); } }), error); }; /** * Translates this point by the given vector. * * @param {number} dx X-coordinate of the translation. * @param {number} dy Y-coordinate of the translation. */ OneDriveClient.prototype.renameFile = function(file, filename, success, error) { if (file != null && filename != null) { // TODO: How to force overwrite file with same name? this.checkExists(file.meta.parentReference.id, filename, false, mxUtils.bind(this, function(checked) { if (checked) { var url = this.baseUrl + '/drive/items/' + file.meta.id; this.writeFile(url, JSON.stringify({name: filename}), 'PATCH', 'application/json', success, error); } else { error(); } })); } }; /** * Translates this point by the given vector. * * @param {number} dx X-coordinate of the translation. * @param {number} dy Y-coordinate of the translation. */ OneDriveClient.prototype.moveFile = function(id, folderId, success, error) { var url = this.baseUrl + '/drive/items/' + id; this.writeFile(url, JSON.stringify({parentReference: {id: folderId}}), 'PATCH', 'application/json', success, error); }; /** * Translates this point by the given vector. * * @param {number} dx X-coordinate of the translation. * @param {number} dy Y-coordinate of the translation. */ OneDriveClient.prototype.insertLibrary = function(filename, data, success, error, folderId) { this.insertFile(filename, data, success, error, true, folderId); }; /** * Translates this point by the given vector. * * @param {number} dx X-coordinate of the translation. * @param {number} dy Y-coordinate of the translation. */ OneDriveClient.prototype.insertFile = function(filename, data, success, error, asLibrary, folderId) { asLibrary = (asLibrary != null) ? asLibrary : false; this.checkExists(folderId, filename, true, mxUtils.bind(this, function(checked) { if (checked) { var folder = (folderId != null) ? 'items/' + folderId : 'special/documents'; var url = this.baseUrl + '/drive/' + folder + '/children/' + filename + '/content'; this.writeFile(url, data, 'PUT', null, mxUtils.bind(this, function(meta) { if (asLibrary) { success(new OneDriveLibrary(this.ui, data, meta)); } else { success(new OneDriveFile(this.ui, data, meta)); } }), error); } else { error(); } })) }; /** * Translates this point by the given vector. * * @param {number} dx X-coordinate of the translation. * @param {number} dy Y-coordinate of the translation. */ OneDriveClient.prototype.checkExists = function(parentId, filename, askReplace, fn) { var path = (parentId != null) ? 'items/' + parentId : 'special/documents'; this.executeRequest(this.baseUrl + '/drive/' + path + '/children/' + filename, mxUtils.bind(this, function(req) { if (req.getStatus() == 404) { fn(true); } else { if (askReplace) { this.ui.spinner.stop(); this.ui.confirm(mxResources.get('replaceIt', [filename]), function() { fn(true); }, function() { fn(false); }); } else { this.ui.spinner.stop(); this.ui.showError(mxResources.get('error'), mxResources.get('fileExists'), mxResources.get('ok'), function() { fn(false); }); } } }), function(req) { fn(false); }, true); }; /** * Translates this point by the given vector. * * @param {number} dx X-coordinate of the translation. * @param {number} dy Y-coordinate of the translation. */ OneDriveClient.prototype.saveFile = function(file, success, error) { var url = this.baseUrl + '/drive/items/' + file.meta.id + '/content/'; this.writeFile(url, file.getData(), 'PUT', null, success, error); }; /** * Translates this point by the given vector. * * @param {number} dx X-coordinate of the translation. * @param {number} dy Y-coordinate of the translation. */ OneDriveClient.prototype.writeFile = function(url, data, method, contentType, success, error) { if (url != null && data != null) { var doExecute = mxUtils.bind(this, function(failOnAuth) { var acceptResponse = true; var timeoutThread = window.setTimeout(mxUtils.bind(this, function() { acceptResponse = false; error({code: App.ERROR_TIMEOUT, retry: fn}); }), this.ui.timeout); var req = new mxXmlRequest(url + '?access_token=' + this.token, data, method); req.setRequestHeaders = function(request, params) { // Space deletes content type header. Specification says "text/plain" // should work but returns an 415 Unsupported Media Type error request.setRequestHeader('Content-Type', contentType || ' '); }; req.send(mxUtils.bind(this, function(req) { window.clearTimeout(timeoutThread); if (acceptResponse) { if (req.getStatus() >= 200 && req.getStatus() <= 299) { success(JSON.parse(req.getText())); } else if (req.getStatus() === 401) { this.clearPersistentToken(); this.setUser(null); this.token = null; if (!failOnAuth) { this.authenticate(function() { doExecute(true); }, error); } else { error({message: mxResources.get('accessDenied'), retry: mxUtils.bind(this, function() { this.authenticate(function() { fn(true); }, error); })}); } } else { error(this.parseRequestText(req)); } } }), mxUtils.bind(this, function(req) { window.clearTimeout(timeoutThread); if (acceptResponse) { error(this.parseRequestText(req)); } })); }); var fn = mxUtils.bind(this, function(failOnAuth) { if (this.user == null) { this.updateUser(function() { fn(true); }, error, failOnAuth); } else { doExecute(failOnAuth); } }); if (this.token == null) { this.authenticate(function() { fn(true); }, error); } else { fn(false); } } else { error({message: mxResources.get('unknownError')}); } }; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.parseRequestText = function(req) { var result = {message: mxResources.get('unknownError')}; try { result = JSON.parse(req.getText()); } catch (e) { // ignore } return result; }; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.pickLibrary = function(fn) { this.pickFile(fn); }; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.pickFolder = function(fn) { WL.fileDialog( { mode: 'save' }).then( function (resp) { fn(resp); }, function (responseFailed) { fn(null); } ); }; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.pickFile = function(fn) { fn = (fn != null) ? fn : mxUtils.bind(this, function(id) { this.ui.loadFile('W' + encodeURIComponent(id)); }); WL.fileDialog( { mode: 'open', select: 'multi' }).then( function (resp) { if (resp != null && resp.data != null && resp.data.files != null) { for (var i = 0; i < resp.data.files.length; i++) { var id = resp.data.files[i].id; id = id.substring(id.lastIndexOf('.') + 1); fn(id); } } }, function (responseFailed) {} ); }; /** * Checks if the client is authorized and calls the next step. */ OneDriveClient.prototype.logout = function() { this.clearPersistentToken(); this.setUser(null); this.token = null; // LATER: Check why async callback does not work WL.logout(); };