drawio-offline
Version:
diagrams.net desktop
1,522 lines (1,354 loc) • 37.8 kB
JavaScript
/**
* Copyright (c) 2006-2020, JGraph Ltd
* Copyright (c) 2006-2020, draw.io AG
*/
//Add a closure to hide the class private variables without changing the code a lot
(function ()
{
var _token = null;
window.OneDriveClient = function(editorUi, isExtAuth, inlinePicker, noLogout)
{
if (isExtAuth == null && window.urlParams != null && window.urlParams['extAuth'] == '1')
{
isExtAuth = true;
}
if (inlinePicker == null &&
((window.urlParams != null && window.urlParams['inlinePicker'] == '1') ||
mxClient.IS_ANDROID || mxClient.IS_IOS //Mobile devices doesn't work with OneDrive picker, so, use the inline picker
))
{
inlinePicker = true;
}
if (noLogout == null && window.urlParams != null && window.urlParams['noLogoutOD'] == '1')
{
noLogout = true;
}
DrawioClient.call(this, editorUi, isExtAuth? 'oneDriveExtAuthInfo' : 'oneDriveAuthInfo');
this.isExtAuth = isExtAuth;
this.inlinePicker = inlinePicker;
this.noLogout = noLogout;
var authInfo = JSON.parse(this.token);
if (authInfo != null)
{
this.endpointHint = authInfo.endpointHint != null ? authInfo.endpointHint.replace('/Documents', '/_layouts/15/onedrive.aspx') : authInfo.endpointHint;
}
};
// 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.DRAWIO_MSGRAPH_CLIENT_ID || ((window.location.hostname == 'test.draw.io') ?
'2e598409-107f-4b59-89ca-d7723c8e00a4' : '45c10911-200f-4e27-a666-9e9fca147395');
OneDriveClient.prototype.clientId = window.location.hostname == 'app.diagrams.net' ?
'b5ff67d6-3155-4fca-965a-59a3655c4476' : OneDriveClient.prototype.clientId;
OneDriveClient.prototype.clientId = window.location.hostname == 'viewer.diagrams.net' ?
'417a451a-a343-4788-b6c1-901e63182565' : OneDriveClient.prototype.clientId;
/**
* OAuth 2.0 scopes for installing Drive Apps.
*/
OneDriveClient.prototype.scopes = 'user.read files.readwrite.all sites.read.all';
/**
* OAuth 2.0 scopes for installing Drive Apps.
*/
OneDriveClient.prototype.redirectUri = window.location.protocol + '//' + window.location.host + '/microsoft';
OneDriveClient.prototype.pickerRedirectUri = window.location.protocol + '//' + window.location.host + '/onedrive3.html';
/**
* This is the default endpoint for personal accounts
*/
OneDriveClient.prototype.defEndpointHint = 'api.onedrive.com';
OneDriveClient.prototype.endpointHint = OneDriveClient.prototype.defEndpointHint;
/**
* Executes the first step for connecting to Google Drive.
*/
OneDriveClient.prototype.extension = '.drawio';
/**
* Executes the first step for connecting to Google Drive.
*/
OneDriveClient.prototype.baseUrl = 'https://graph.microsoft.com/v1.0';
/**
* Empty function used when no callback is needed
*/
OneDriveClient.prototype.emptyFn = function(){};
OneDriveClient.prototype.invalidFilenameRegExs = [
/[~"#%\*:<>\?\/\\{\|}]/,
/^\.lock$/i,
/^CON$/i,
/^PRN$/i,
/^AUX$/i,
/^NUL$/i,
/^COM\d$/i,
/^LPT\d$/i,
/^desktop\.ini$/i,
/_vti_/i
];
/**
* Check if the file/folder name is valid
*/
OneDriveClient.prototype.isValidFilename = function(filename)
{
if (filename == null || filename === '') return false;
for (var i = 0; i < this.invalidFilenameRegExs.length; i++)
{
if (this.invalidFilenameRegExs[i].test(filename)) return false;
}
return true;
};
/**
* Checks if the client is authorized and calls the next step.
*/
OneDriveClient.prototype.get = function(url, onload, onerror)
{
var req = new mxXmlRequest(url, null, 'GET');
req.setRequestHeaders = mxUtils.bind(this, function(request, params)
{
request.setRequestHeader('Authorization', 'Bearer ' + _token);
});
req.send(onload, onerror);
return req;
};
/**
* 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);
this.get(this.baseUrl + '/me', 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.id, null, data.displayName));
success();
}
}
}), mxUtils.bind(this, function(err)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
error(err);
}
}));
};
OneDriveClient.prototype.resetTokenRefresh = function(expires_in)
{
if (this.tokenRefreshThread != null)
{
window.clearTimeout(this.tokenRefreshThread);
this.tokenRefreshThread = null;
}
// Starts timer to refresh token before it expires
if (expires_in > 0)
{
this.tokenRefreshInterval = expires_in * 1000;
this.tokenRefreshThread = window.setTimeout(mxUtils.bind(this, function()
{
//Get a new fresh accessToken
this.authenticate(this.emptyFn, this.emptyFn, true);
}), expires_in * 900);
}
};
/**
* Authorizes the client, gets the userId and calls <open>.
*/
OneDriveClient.prototype.authenticate = function(success, error, failOnAuth)
{
if (this.isExtAuth)
{
window.parent.oneDriveAuth(mxUtils.bind(this, function(newAuthInfo)
{
this.updateAuthInfo(newAuthInfo, true, this.endpointHint == null, success, error);
}), error, window.urlParams != null && urlParams['odAuthCancellable'] == '1');
return;
}
var req = new mxXmlRequest(this.redirectUri + '?getState=1', null, 'GET');
req.send(mxUtils.bind(this, function(req)
{
if (req.getStatus() >= 200 && req.getStatus() <= 299)
{
this.authenticateStep2(req.getText(), success, error, failOnAuth);
}
else if (error != null)
{
error(req);
}
}), error);
};
OneDriveClient.prototype.updateAuthInfo = function(newAuthInfo, remember, forceUserUpdate, success, error)
{
if (forceUserUpdate)
{
this.setUser(null);
}
_token = newAuthInfo.access_token;
delete newAuthInfo.access_token; //Don't store access token
newAuthInfo.expiresOn = Date.now() + newAuthInfo.expires_in * 1000;
this.tokenExpiresOn = newAuthInfo.expiresOn;
newAuthInfo.remember = remember;
this.setPersistentToken(JSON.stringify(newAuthInfo), !remember);
this.resetTokenRefresh(newAuthInfo.expires_in);
if (forceUserUpdate)
{
//Find out the type of the account + endpoint
this.getAccountTypeAndEndpoint(mxUtils.bind(this, function()
{
success();
}), error);
}
else
{
success();
}
};
OneDriveClient.prototype.authenticateStep2 = function(state, success, error, failOnAuth)
{
if (window.onOneDriveCallback == null)
{
var auth = mxUtils.bind(this, function()
{
var acceptAuthResponse = true;
//Retry request with refreshed token
var authInfo = JSON.parse(this.getPersistentToken(true));
if (authInfo != null)
{
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
req.send(mxUtils.bind(this, function(req)
{
if (req.getStatus() >= 200 && req.getStatus() <= 299)
{
this.updateAuthInfo(JSON.parse(req.getText()), authInfo.remember, false, success, error);
}
else
{
this.clearPersistentToken();
this.setUser(null);
_token = null;
if (req.getStatus() == 401 && !failOnAuth) // (Unauthorized) [e.g, invalid refresh token]
{
auth();
}
else
{
error({message: mxResources.get('accessDenied'), retry: auth});
}
}
}), error);
}
else
{
this.ui.showAuthDialog(this, true, mxUtils.bind(this, function(remember, authSuccess)
{
var url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize' +
'?client_id=' + this.clientId + '&response_type=code' +
'&redirect_uri=' + encodeURIComponent(this.redirectUri) +
'&scope=' + encodeURIComponent(this.scopes + (remember? ' offline_access' : '')) +
'&state=' + encodeURIComponent('cId=' + this.clientId + '&domain=' + window.location.hostname + '&token=' + state); //To identify which app/domain is used
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(authInfo, authWindow)
{
if (acceptAuthResponse)
{
window.onOneDriveCallback = null;
acceptAuthResponse = false;
try
{
if (authInfo == null)
{
error({message: mxResources.get('accessDenied'), retry: auth});
}
else
{
if (authSuccess != null)
{
authSuccess();
}
this.updateAuthInfo(authInfo, remember, true, success, error);
}
}
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});
}
};
OneDriveClient.prototype.getAccountTypeAndEndpoint = function(success, error)
{
this.get(this.baseUrl + '/me/drive/root', mxUtils.bind(this, function(req)
{
try
{
if (req.getStatus() >= 200 && req.getStatus() <= 299)
{
var resp = JSON.parse(req.getText());
if (resp.webUrl.indexOf('.sharepoint.com') > 0)
{
//TODO Confirm this works with all sharepoint sites
this.endpointHint = resp.webUrl.replace('/Documents', '/_layouts/15/onedrive.aspx');
}
else
{
this.endpointHint = this.defEndpointHint;
}
//Update authInfo with endpointHint
var authInfo = JSON.parse(this.getPersistentToken(true));
if (authInfo != null)
{
authInfo.endpointHint = this.endpointHint;
this.setPersistentToken(JSON.stringify(authInfo), !authInfo.remember);
}
success();
return;
}
}
catch(e) {}
//It is expected to work as this call immediately follows getting a fresh access token
error({message: mxResources.get('unknownError') + ' (Code: ' + req.getStatus() + ')'});
}), error);
};
/**
* 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: doExecute});
}), this.ui.timeout);
this.get(url, 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)
{
if (this.user == null)
{
this.updateUser(this.emptyFn, this.emptyFn, true);
}
success(req);
}
// 400 is returns if wrong user for this file
else if (!failOnAuth && (req.getStatus() === 401 || req.getStatus() === 400))
{
//Authorize again using the refresh token
this.authenticate(function()
{
doExecute(true);
}, error, failOnAuth);
}
else
{
error(this.parseRequestText(req));
}
}
}), mxUtils.bind(this, function(err)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
error(err);
}
}));
});
if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window
{
this.authenticate(function()
{
doExecute(true);
}, error);
}
else
{
doExecute(false);
}
};
/**
* Checks if the client is authorized and calls the next step.
*/
OneDriveClient.prototype.checkToken = function(fn)
{
if (_token == null || this.tokenRefreshThread == null || this.tokenExpiresOn - Date.now() < 60000)
{
this.authenticate(fn, this.emptyFn);
}
else
{
fn();
}
};
OneDriveClient.prototype.getItemRef = function(id)
{
var idParts = id.split('/');
if (idParts.length > 1)
{
return {driveId: idParts[0], id: idParts[1]};
}
else
{
return {id: id};
}
};
OneDriveClient.prototype.getItemURL = function(id, relative)
{
var idParts = id.split('/');
if (idParts.length > 1)
{
var driveId = idParts[0];
var itemId = idParts[1];
return (relative? '' : this.baseUrl) + '/drives/' + driveId + (itemId == 'root' ? '/root' : '/items/' + itemId);
}
else
{
return (relative? '' : this.baseUrl) + '/me/drive/items/' + id;
}
};
/**
* 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);
};
/**
* Workaround for added content to HTML files in Sharepoint.
*/
OneDriveClient.prototype.removeExtraHtmlContent = function(data)
{
var idx = data.lastIndexOf('<html><head><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8"><meta name="Robots" ');
if (idx > 0)
{
data = data.substring(0, idx);
}
return data;
};
/**
* 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.getItemURL(id), mxUtils.bind(this, function(req)
{
if (req.getStatus() >= 200 && req.getStatus() <= 299)
{
var meta = JSON.parse(req.getText());
var binary = /\.png$/i.test(meta.name);
// Handles .vsdx, Gliffy and PNG+XML files by creating a temporary file
if (/\.v(dx|sdx?)$/i.test(meta.name) || /\.gliffy$/i.test(meta.name) ||
/\.pdf$/i.test(meta.name) || (!this.ui.useCanvasForExport && binary))
{
var mimeType = (meta.file != null) ? meta.file.mimeType : null;
this.ui.convertFile(meta['@microsoft.graph.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.editor.loadUrl(meta['@microsoft.graph.downloadUrl'], mxUtils.bind(this, function(data)
{
try
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
if (/\.html$/i.test(meta.name))
{
data = this.removeExtraHtmlContent(data);
}
var index = (binary) ? data.lastIndexOf(',') : -1;
var file = null;
if (index > 0)
{
var xml = this.ui.extractGraphModelFromPng(data);
if (xml != null && xml.length > 0)
{
data = xml;
}
else
{
// Imports as PNG image
file = new LocalFile(this.ui, data, meta.name, true);
}
}
// Checks for base64 encoded mxfile
else if (data.substring(0, 32) == 'data:image/png;base64,PG14ZmlsZS')
{
var temp = data.substring(22);
data = (window.atob && !mxClient.IS_SF) ? atob(temp) : Base64.decode(temp);
}
if (Graph.fileSupport && new XMLHttpRequest().upload && this.ui.isRemoteFileFormat(data, meta['@microsoft.graph.downloadUrl']))
{
this.ui.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
{
try
{
if (xhr.readyState == 4)
{
if (xhr.status >= 200 && xhr.status <= 299)
{
success(new LocalFile(this.ui, xhr.responseText, meta.name + this.extension, true));
}
else if (error != null)
{
error({message: mxResources.get('errorLoadingFile')});
}
}
}
catch (e)
{
if (error != null)
{
error(e);
}
else
{
throw e;
}
}
}), meta.name);
}
else
{
if (file != null)
{
success(file);
}
else if (asLibrary)
{
success(new OneDriveLibrary(this.ui, data, meta));
}
else
{
success(new OneDriveFile(this.ui, data, meta));
}
}
}
}
catch (e)
{
if (error != null)
{
error(e);
}
else
{
throw e;
}
}
}), mxUtils.bind(this, function(req)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
error(this.parseRequestText(req));
}
}), binary || (meta.file != null && meta.file.mimeType != null &&
(meta.file.mimeType.substring(0, 6) == 'image/' ||
meta.file.mimeType == 'application/pdf')));
}
}
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)
{
if (!this.isValidFilename(filename))
{
error({message: this.invalidFilenameRegExs[0].test(filename) ?
mxResources.get('oneDriveCharsNotAllowed') : mxResources.get('oneDriveInvalidDeviceName')});
return;
}
// TODO: How to force overwrite file with same name?
this.checkExists(file.getParentId(), filename, false, mxUtils.bind(this, function(checked)
{
if (checked)
{
this.writeFile(this.getItemURL(file.getId()), 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)
{
//check that the source and destination are on the same drive
var folderInfo = this.getItemRef(folderId);
var fileInfo = this.getItemRef(id);
if (folderInfo.driveId != fileInfo.driveId)
{
error({message: mxResources.get('cannotMoveOneDrive', null, 'Moving a file between accounts is not supported yet.')});
}
else
{
this.writeFile(this.getItemURL(id), JSON.stringify({parentReference: folderInfo}), '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)
{
if (!this.isValidFilename(filename))
{
error({message: this.invalidFilenameRegExs[0].test(filename) ?
mxResources.get('oneDriveCharsNotAllowed') : mxResources.get('oneDriveInvalidDeviceName')});
return;
}
asLibrary = (asLibrary != null) ? asLibrary : false;
this.checkExists(folderId, filename, true, mxUtils.bind(this, function(checked)
{
if (checked)
{
var folder = '/me/drive/root';
if (folderId != null)
{
folder = this.getItemURL(folderId, true);
}
var insertSuccess = mxUtils.bind(this, function(meta)
{
if (asLibrary)
{
success(new OneDriveLibrary(this.ui, data, meta));
}
else
{
success(new OneDriveFile(this.ui, data, meta));
}
});
var url = this.baseUrl + folder + '/children/' + encodeURIComponent(filename) + '/content';
//OneDrive has a limit on PUT API of 4MB, larger files needs to use the upload session method
if (data.length >= 4000000 /*4MB*/)
{
//Create empty file first then upload. TODO Can we get an upload session for non-existing files?
this.writeFile(url, '', 'PUT', null, mxUtils.bind(this, function(meta)
{
this.writeLargeFile(this.getItemURL(meta.id), data, insertSuccess, error);
}), error);
}
else
{
this.writeFile(url, data, 'PUT', null, insertSuccess, 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 folder = '/me/drive/root';
if (parentId != null)
{
folder = this.getItemURL(parentId, true);
}
this.executeRequest(this.baseUrl + folder + '/children/' + encodeURIComponent(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, etag)
{
try
{
var savedData = file.getData();
var fn = mxUtils.bind(this, function(data)
{
var saveSuccess = mxUtils.bind(this, function(resp)
{
success(resp, savedData);
});
var url = this.getItemURL(file.getId());
//OneDrive has a limit on PUT API of 4MB, larger files needs to use the upload session method
if (data.length >= 4000000 /*4MB*/)
{
this.writeLargeFile(url, data, saveSuccess, error, etag);
}
else
{
this.writeFile(url + '/content/', data, 'PUT', null, saveSuccess, error, etag);
}
});
if (this.ui.useCanvasForExport && /(\.png)$/i.test(file.meta.name))
{
var p = this.ui.getPngFileProperties(this.ui.fileNode);
this.ui.getEmbeddedPng(mxUtils.bind(this, function(data)
{
fn(this.ui.base64ToBlob(data, 'image/png'));
}), error, (this.ui.getCurrentFile() != file) ?
savedData : null, p.scale, p.border);
}
else
{
fn(savedData);
}
}
catch (e)
{
error(e);
}
};
OneDriveClient.prototype.writeLargeFile = function(url, data, success, error, etag)
{
try
{
var chunkSize = 4 * 1024 * 1024; //4MB chunk;
if (data != null)
{
var uploadPart = mxUtils.bind(this, function(uploadUrl, index, retryCount)
{
try
{
retryCount = retryCount || 0;
var acceptResponse = true;
var timeoutThread = null;
timeoutThread = window.setTimeout(mxUtils.bind(this, function()
{
acceptResponse = false;
error({code: App.ERROR_TIMEOUT});
}), this.ui.timeout);
var part = data.substr(index, chunkSize);
var req = new mxXmlRequest(uploadUrl, part, 'PUT');
req.setRequestHeaders = mxUtils.bind(this, function(request, params)
{
request.setRequestHeader('Content-Length', part.length);
request.setRequestHeader('Content-Range', 'bytes ' + index + '-' + (index + part.length - 1) + '/' + data.length);
});
req.send(mxUtils.bind(this, function(req)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
var status = req.getStatus();
if (status >= 200 && status <= 299)
{
var nextByte = index + part.length;
if (nextByte == data.length)
{
success(JSON.parse(req.getText()));
}
else
{
uploadPart(uploadUrl, nextByte, retryCount);
}
}
else if (status >= 500 && status <= 599 && retryCount < 2) //Retry on server errors
{
retryCount++;
uploadPart(uploadUrl, index, retryCount);
}
else
{
error(this.parseRequestText(req), req);
}
}
}), mxUtils.bind(this, function(req)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
error(this.parseRequestText(req));
}
}));
}
catch (e)
{
error(e);
}
});
var doExecute = mxUtils.bind(this, function(failOnAuth)
{
try
{
var acceptResponse = true;
var timeoutThread = null;
try
{
timeoutThread = window.setTimeout(mxUtils.bind(this, function()
{
acceptResponse = false;
error({code: App.ERROR_TIMEOUT});
}), this.ui.timeout);
}
catch (e)
{
// Ignore window closed
}
var req = new mxXmlRequest(url + '/createUploadSession', '{}', 'POST');
req.setRequestHeaders = mxUtils.bind(this, function(request, params)
{
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Authorization', 'Bearer ' + _token);
if (etag != null)
{
request.setRequestHeader('If-Match', etag);
}
});
req.send(mxUtils.bind(this, function(req)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
if (req.getStatus() >= 200 && req.getStatus() <= 299)
{
var resp = JSON.parse(req.getText());
uploadPart(resp.uploadUrl, 0);
}
else if (!failOnAuth && req.getStatus() === 401)
{
this.authenticate(function()
{
doExecute(true);
}, error, failOnAuth);
}
else
{
error(this.parseRequestText(req), req);
}
}
}), mxUtils.bind(this, function(req)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
error(this.parseRequestText(req));
}
}));
}
catch (e)
{
error(e);
}
});
if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window
{
this.authenticate(function()
{
doExecute(true);
}, error);
}
else
{
doExecute(false);
}
}
else
{
error({message: mxResources.get('unknownError')});
}
}
catch (e)
{
error(e);
}
};
/**
* 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, etag)
{
try
{
if (url != null && data != null)
{
var doExecute = mxUtils.bind(this, function(failOnAuth)
{
try
{
var acceptResponse = true;
var timeoutThread = null;
try
{
timeoutThread = window.setTimeout(mxUtils.bind(this, function()
{
acceptResponse = false;
error({code: App.ERROR_TIMEOUT});
}), this.ui.timeout);
}
catch (e)
{
// Ignore window closed
}
var req = new mxXmlRequest(url, data, method);
req.setRequestHeaders = mxUtils.bind(this, 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 || ' ');
//TODO This header is needed for moving a file between two different drives.
// 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)
//request.setRequestHeader('Prefer', 'respond-async');
request.setRequestHeader('Authorization', 'Bearer ' + _token);
if (etag != null)
{
request.setRequestHeader('If-Match', etag);
}
});
req.send(mxUtils.bind(this, function(req)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
if (req.getStatus() >= 200 && req.getStatus() <= 299)
{
if (this.user == null)
{
this.updateUser(this.emptyFn, this.emptyFn, true);
}
success(JSON.parse(req.getText()));
}
else if (!failOnAuth && req.getStatus() === 401)
{
this.authenticate(function()
{
doExecute(true);
}, error, failOnAuth);
}
else
{
error(this.parseRequestText(req), req);
}
}
}), mxUtils.bind(this, function(req)
{
window.clearTimeout(timeoutThread);
if (acceptResponse)
{
error(this.parseRequestText(req));
}
}));
}
catch (e)
{
error(e);
}
});
if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window
{
this.authenticate(function()
{
doExecute(true);
}, error);
}
else
{
doExecute(false);
}
}
else
{
error({message: mxResources.get('unknownError')});
}
}
catch (e)
{
error(e);
}
};
/**
* 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(function(id)
{
// Ignores second argument
fn(id);
});
};
OneDriveClient.prototype.createInlinePicker = function(fn, foldersOnly)
{
return mxUtils.bind(this, function()
{
var odPicker = null;
var div = document.createElement('div');
div.style.width = '550px';
div.style.height = '435px';
div.style.position = 'relative';
var dlg = new CustomDialog(this.ui, div, mxUtils.bind(this, function()
{
var item = odPicker.getSelectedItem();
if (item != null)
{
if (foldersOnly && typeof item.folder == 'object')
{
fn({
value: [item]
});
return;
}
else if (!item.folder)
{
fn(OneDriveFile.prototype.getIdOf(item));
return;
}
}
return mxResources.get('invalidSel', null, 'Invalid selection');
}), null, mxResources.get(foldersOnly? 'save' :'open'), null, null, null, null, true);
this.ui.showDialog(dlg.container, 550, 485, true, true);
odPicker = new mxODPicker(div, null, mxUtils.bind(this, function(url, success, error)
{
this.executeRequest(this.baseUrl + url, function(req)
{
success(JSON.parse(req.getText()));
}, error);
}), mxUtils.bind(this, function(id, driveId, success, error)
{
this.executeRequest(this.baseUrl + '/drives/' + driveId + '/items/' + id, function(req)
{
success(JSON.parse(req.getText()));
}, error);
}), null, null, function(item)
{
if (foldersOnly) //Currently this is not called when in foldersOnly mode
{
fn({
value: [item]
});
}
else
{
fn(OneDriveFile.prototype.getIdOf(item));
}
},
mxUtils.bind(this, function(err)
{
this.ui.showError(mxResources.get('error'), err);
}), foldersOnly);
});
};
/**
* Checks if the client is authorized and calls the next step.
*/
OneDriveClient.prototype.pickFolder = function(fn, direct)
{
var errorFn = mxUtils.bind(this, function(e)
{
this.ui.showError(mxResources.get('error'), e && e.message? e.message : e);
});
var odSaveDlg = mxUtils.bind(this, function(direct)
{
var openSaveDlg = this.inlinePicker? this.createInlinePicker(fn, true) :
mxUtils.bind(this, function()
{
OneDrive.save(
{
clientId: this.clientId,
action: 'query',
openInNewWindow: true,
advanced:
{
'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!
'redirectUri': this.pickerRedirectUri,
'queryParameters': 'select=id,name,parentReference',
'accessToken': _token,
isConsumerAccount: false
},
success: mxUtils.bind(this, function(files)
{
fn(files);
//Update the token in case a login with a different user
if (mxClient.IS_IE11)
{
_token = files.accessToken;
}
}),
cancel: mxUtils.bind(this, function()
{
// do nothing
}),
error: errorFn
});
});
if (direct)
{
openSaveDlg();
}
else
{
this.ui.confirm(mxResources.get('useRootFolder'), mxUtils.bind(this, function()
{
fn({value: [{id: 'root', name: 'root', parentReference: {driveId: 'me'}}]});
}), openSaveDlg, mxResources.get('yes'), mxResources.get('noPickFolder') + '...', true);
}
if (this.user == null)
{
this.updateUser(this.emptyFn, this.emptyFn, true);
}
});
if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window
{
this.authenticate(mxUtils.bind(this, function()
{
odSaveDlg(false);
}), errorFn);
}
else
{
odSaveDlg(direct);
}
};
/**
* 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));
});
var errorFn = mxUtils.bind(this, function(e)
{
this.ui.showError(mxResources.get('error'), e && e.message? e.message : e);
});
var odOpenDlg = this.inlinePicker? this.createInlinePicker(fn) :
mxUtils.bind(this, function()
{
OneDrive.open(
{
clientId: this.clientId,
action: 'query',
multiSelect: false,
advanced:
{
'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!
'redirectUri': this.pickerRedirectUri,
'queryParameters': 'select=id,name,parentReference', //We can also get @microsoft.graph.downloadUrl within this request but it will break the normal process
'accessToken': _token,
isConsumerAccount: false
},
success: mxUtils.bind(this, function(files)
{
if (files != null && files.value != null && files.value.length > 0)
{
//Update the token in case a login with a different user
if (mxClient.IS_IE11)
{
_token = files.accessToken;
}
fn(OneDriveFile.prototype.getIdOf(files.value[0]), files);
}
}),
cancel: mxUtils.bind(this, function()
{
// do nothing
}),
error: errorFn
});
if (this.user == null)
{
this.updateUser(this.emptyFn, this.emptyFn, true);
}
});
if (_token == null || this.tokenExpiresOn - Date.now() < 60000) //60 sec tolerance window
{
this.authenticate(mxUtils.bind(this, function()
{
this.ui.showDialog(new BtnDialog(this.ui, this, mxResources.get('open'), mxUtils.bind(this, function()
{
this.ui.hideDialog();
odOpenDlg();
})).container, 300, 140, true, true);
}), errorFn);
}
else
{
odOpenDlg();
}
};
/**
* Checks if the client is authorized and calls the next step.
*/
OneDriveClient.prototype.logout = function()
{
if (isLocalStorage)
{
var check = localStorage.getItem('odpickerv7cache');
if (check != null && check.substring(0, 19) == '{"odsdkLoginHint":{')
{
localStorage.removeItem('odpickerv7cache');
}
}
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');
//Send to server to clear refresh token cookie
this.ui.editor.loadUrl(this.redirectUri + '?doLogout=1&state=' + encodeURIComponent('cId=' + this.clientId + '&domain=' + window.location.hostname));
this.clearPersistentToken();
this.setUser(null);
_token = null;
};
})();