diff options
author | Sheogorath | 2018-05-16 01:19:44 +0200 |
---|---|---|
committer | Sheogorath | 2018-05-16 01:34:55 +0200 |
commit | ad69c5017b28f333bb5ed9a3ee20d95c92d7c046 (patch) | |
tree | 17b380f2a2a137c10b3248f79415de7d794579a5 /public | |
parent | b8e7c4b97a2a8369c9f1989352dec65867d838ac (diff) |
Removing google drive integration
It's sad but it's not working. For multiple releases this should be
already broken which shows how often it's used.
As there is also a security issue related to that, it's better to
remove the feature completely. Whoever wants to rewrite it, feel free to
go.
This commit removes the Google Drive integration from HackMD's Frontend
editor and this way removes the need to provide any API key and Client
ID in the frontend.
Signed-off-by: Sheogorath <sheogorath@shivering-isles.com>
Diffstat (limited to 'public')
-rw-r--r-- | public/js/google-drive-picker.js | 118 | ||||
-rw-r--r-- | public/js/google-drive-upload.js | 267 | ||||
-rw-r--r-- | public/js/index.js | 104 | ||||
-rw-r--r-- | public/js/lib/common/constant.ejs | 2 | ||||
-rw-r--r-- | public/js/lib/config/index.js | 2 | ||||
-rw-r--r-- | public/js/lib/editor/ui-elements.js | 2 | ||||
-rw-r--r-- | public/views/hackmd/header.ejs | 12 |
7 files changed, 2 insertions, 505 deletions
diff --git a/public/js/google-drive-picker.js b/public/js/google-drive-picker.js deleted file mode 100644 index 5006cd25..00000000 --- a/public/js/google-drive-picker.js +++ /dev/null @@ -1,118 +0,0 @@ -/** ! - * Google Drive File Picker Example - * By Daniel Lo Nigro (http://dan.cx/) - */ -(function () { - /** - * Initialise a Google Driver file picker - */ - var FilePicker = window.FilePicker = function (options) { - // Config - this.apiKey = options.apiKey - this.clientId = options.clientId - - // Elements - this.buttonEl = options.buttonEl - - // Events - this.onSelect = options.onSelect - this.buttonEl.on('click', this.open.bind(this)) - - // Disable the button until the API loads, as it won't work properly until then. - this.buttonEl.prop('disabled', true) - - // Load the drive API - window.gapi.client.setApiKey(this.apiKey) - window.gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this)) - window.google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) }) - } - - FilePicker.prototype = { - /** - * Open the file picker. - */ - open: function () { - // Check if the user has already authenticated - var token = window.gapi.auth.getToken() - if (token) { - this._showPicker() - } else { - // The user has not yet authenticated with Google - // We need to do the authentication before displaying the Drive picker. - this._doAuth(false, function () { this._showPicker() }.bind(this)) - } - }, - - /** - * Show the file picker once authentication has been done. - * @private - */ - _showPicker: function () { - var accessToken = window.gapi.auth.getToken().access_token - var view = new window.google.picker.DocsView() - view.setMimeTypes('text/markdown,text/html') - view.setIncludeFolders(true) - view.setOwnedByMe(true) - this.picker = new window.google.picker.PickerBuilder() - .enableFeature(window.google.picker.Feature.NAV_HIDDEN) - .addView(view) - .setAppId(this.clientId) - .setOAuthToken(accessToken) - .setCallback(this._pickerCallback.bind(this)) - .build() - .setVisible(true) - }, - - /** - * Called when a file has been selected in the Google Drive file picker. - * @private - */ - _pickerCallback: function (data) { - if (data[window.google.picker.Response.ACTION] === window.google.picker.Action.PICKED) { - var file = data[window.google.picker.Response.DOCUMENTS][0] - var id = file[window.google.picker.Document.ID] - var request = window.gapi.client.drive.files.get({ - fileId: id - }) - request.execute(this._fileGetCallback.bind(this)) - } - }, - /** - * Called when file details have been retrieved from Google Drive. - * @private - */ - _fileGetCallback: function (file) { - if (this.onSelect) { - this.onSelect(file) - } - }, - - /** - * Called when the Google Drive file picker API has finished loading. - * @private - */ - _pickerApiLoaded: function () { - this.buttonEl.prop('disabled', false) - }, - - /** - * Called when the Google Drive API has finished loading. - * @private - */ - _driveApiLoaded: function () { - this._doAuth(true) - }, - - /** - * Authenticate with Google Drive via the Google JavaScript API. - * @private - */ - _doAuth: function (immediate, callback) { - window.gapi.auth.authorize({ - client_id: this.clientId, - scope: 'https://www.googleapis.com/auth/drive.readonly', - immediate: immediate - }, callback || function () {}) - } - } -}()) diff --git a/public/js/google-drive-upload.js b/public/js/google-drive-upload.js deleted file mode 100644 index 6c0e8a62..00000000 --- a/public/js/google-drive-upload.js +++ /dev/null @@ -1,267 +0,0 @@ -/* eslint-env browser, jquery */ -/** - * Helper for implementing retries with backoff. Initial retry - * delay is 1 second, increasing by 2x (+jitter) for subsequent retries - * - * @constructor - */ -var RetryHandler = function () { - this.interval = 1000 // Start at one second - this.maxInterval = 60 * 1000 // Don't wait longer than a minute -} - -/** - * Invoke the function after waiting - * - * @param {function} fn Function to invoke - */ -RetryHandler.prototype.retry = function (fn) { - setTimeout(fn, this.interval) - this.interval = this.nextInterval_() -} - -/** - * Reset the counter (e.g. after successful request.) - */ -RetryHandler.prototype.reset = function () { - this.interval = 1000 -} - -/** - * Calculate the next wait time. - * @return {number} Next wait interval, in milliseconds - * - * @private - */ -RetryHandler.prototype.nextInterval_ = function () { - var interval = this.interval * 2 + this.getRandomInt_(0, 1000) - return Math.min(interval, this.maxInterval) -} - -/** - * Get a random int in the range of min to max. Used to add jitter to wait times. - * - * @param {number} min Lower bounds - * @param {number} max Upper bounds - * @private - */ -RetryHandler.prototype.getRandomInt_ = function (min, max) { - return Math.floor(Math.random() * (max - min + 1) + min) -} - -/** - * Helper class for resumable uploads using XHR/CORS. Can upload any Blob-like item, whether - * files or in-memory constructs. - * - * @example - * var content = new Blob(["Hello world"], {"type": "text/plain"}); - * var uploader = new MediaUploader({ - * file: content, - * token: accessToken, - * onComplete: function(data) { ... } - * onError: function(data) { ... } - * }); - * uploader.upload(); - * - * @constructor - * @param {object} options Hash of options - * @param {string} options.token Access token - * @param {blob} options.file Blob-like item to upload - * @param {string} [options.fileId] ID of file if replacing - * @param {object} [options.params] Additional query parameters - * @param {string} [options.contentType] Content-type, if overriding the type of the blob. - * @param {object} [options.metadata] File metadata - * @param {function} [options.onComplete] Callback for when upload is complete - * @param {function} [options.onProgress] Callback for status for the in-progress upload - * @param {function} [options.onError] Callback if upload fails - */ -var MediaUploader = function (options) { - var noop = function () {} - this.file = options.file - this.contentType = options.contentType || this.file.type || 'application/octet-stream' - this.metadata = options.metadata || { - 'title': this.file.name, - 'mimeType': this.contentType - } - this.token = options.token - this.onComplete = options.onComplete || noop - this.onProgress = options.onProgress || noop - this.onError = options.onError || noop - this.offset = options.offset || 0 - this.chunkSize = options.chunkSize || 0 - this.retryHandler = new RetryHandler() - - this.url = options.url - if (!this.url) { - var params = options.params || {} - params.uploadType = 'resumable' - this.url = this.buildUrl_(options.fileId, params, options.baseUrl) - } - this.httpMethod = options.fileId ? 'PUT' : 'POST' -} - -/** - * Initiate the upload. - */ -MediaUploader.prototype.upload = function () { - var xhr = new XMLHttpRequest() - - xhr.open(this.httpMethod, this.url, true) - xhr.setRequestHeader('Authorization', 'Bearer ' + this.token) - xhr.setRequestHeader('Content-Type', 'application/json') - xhr.setRequestHeader('X-Upload-Content-Length', this.file.size) - xhr.setRequestHeader('X-Upload-Content-Type', this.contentType) - - xhr.onload = function (e) { - if (e.target.status < 400) { - var location = e.target.getResponseHeader('Location') - this.url = location - this.sendFile_() - } else { - this.onUploadError_(e) - } - }.bind(this) - xhr.onerror = this.onUploadError_.bind(this) - xhr.send(JSON.stringify(this.metadata)) -} - -/** - * Send the actual file content. - * - * @private - */ -MediaUploader.prototype.sendFile_ = function () { - var content = this.file - var end = this.file.size - - if (this.offset || this.chunkSize) { - // Only bother to slice the file if we're either resuming or uploading in chunks - if (this.chunkSize) { - end = Math.min(this.offset + this.chunkSize, this.file.size) - } - content = content.slice(this.offset, end) - } - - var xhr = new XMLHttpRequest() - xhr.open('PUT', this.url, true) - xhr.setRequestHeader('Content-Type', this.contentType) - xhr.setRequestHeader('Content-Range', 'bytes ' + this.offset + '-' + (end - 1) + '/' + this.file.size) - xhr.setRequestHeader('X-Upload-Content-Type', this.file.type) - if (xhr.upload) { - xhr.upload.addEventListener('progress', this.onProgress) - } - xhr.onload = this.onContentUploadSuccess_.bind(this) - xhr.onerror = this.onContentUploadError_.bind(this) - xhr.send(content) -} - -/** - * Query for the state of the file for resumption. - * - * @private - */ -MediaUploader.prototype.resume_ = function () { - var xhr = new XMLHttpRequest() - xhr.open('PUT', this.url, true) - xhr.setRequestHeader('Content-Range', 'bytes */' + this.file.size) - xhr.setRequestHeader('X-Upload-Content-Type', this.file.type) - if (xhr.upload) { - xhr.upload.addEventListener('progress', this.onProgress) - } - xhr.onload = this.onContentUploadSuccess_.bind(this) - xhr.onerror = this.onContentUploadError_.bind(this) - xhr.send() -} - -/** - * Extract the last saved range if available in the request. - * - * @param {XMLHttpRequest} xhr Request object - */ -MediaUploader.prototype.extractRange_ = function (xhr) { - var range = xhr.getResponseHeader('Range') - if (range) { - this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1 - } -} - -/** - * Handle successful responses for uploads. Depending on the context, - * may continue with uploading the next chunk of the file or, if complete, - * invokes the caller's callback. - * - * @private - * @param {object} e XHR event - */ -MediaUploader.prototype.onContentUploadSuccess_ = function (e) { - if (e.target.status === 200 || e.target.status === 201) { - this.onComplete(e.target.response) - } else if (e.target.status === 308) { - this.extractRange_(e.target) - this.retryHandler.reset() - this.sendFile_() - } else { - this.onContentUploadError_(e) - } -} - -/** - * Handles errors for uploads. Either retries or aborts depending - * on the error. - * - * @private - * @param {object} e XHR event - */ -MediaUploader.prototype.onContentUploadError_ = function (e) { - if (e.target.status && e.target.status < 500) { - this.onError(e.target.response) - } else { - this.retryHandler.retry(this.resume_.bind(this)) - } -} - -/** - * Handles errors for the initial request. - * - * @private - * @param {object} e XHR event - */ -MediaUploader.prototype.onUploadError_ = function (e) { - this.onError(e.target.response) // TODO - Retries for initial upload -} - -/** - * Construct a query string from a hash/object - * - * @private - * @param {object} [params] Key/value pairs for query string - * @return {string} query string - */ -MediaUploader.prototype.buildQuery_ = function (params) { - params = params || {} - return Object.keys(params).map(function (key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]) - }).join('&') -} - -/** - * Build the drive upload URL - * - * @private - * @param {string} [id] File ID if replacing - * @param {object} [params] Query parameters - * @return {string} URL - */ -MediaUploader.prototype.buildUrl_ = function (id, params, baseUrl) { - var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/' - if (id) { - url += id - } - var query = this.buildQuery_(params) - if (query) { - url += '?' + query - } - return url -} - -window.MediaUploader = MediaUploader diff --git a/public/js/index.js b/public/js/index.js index d76a37fe..c6a4f770 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -30,8 +30,6 @@ import { import { debug, DROPBOX_APP_KEY, - GOOGLE_API_KEY, - GOOGLE_CLIENT_ID, noteid, noteurl, urlpath, @@ -908,29 +906,6 @@ if (DROPBOX_APP_KEY) { ui.toolbar.export.dropbox.hide() } -// check if google api key and client id are set and load scripts -if (GOOGLE_API_KEY && GOOGLE_CLIENT_ID) { - $('<script>') - .attr('type', 'text/javascript') - .attr('src', 'https://www.google.com/jsapi?callback=onGoogleAPILoaded') - .prop('async', true) - .prop('defer', true) - .appendTo('body') -} else { - ui.toolbar.import.googleDrive.hide() - ui.toolbar.export.googleDrive.hide() -} - -function onGoogleAPILoaded () { - $('<script>') - .attr('type', 'text/javascript') - .attr('src', 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded') - .prop('async', true) - .prop('defer', true) - .appendTo('body') -} -window.onGoogleAPILoaded = onGoogleAPILoaded - // button actions // share ui.toolbar.publish.attr('href', noteurl + '/publish') @@ -979,53 +954,6 @@ ui.toolbar.export.dropbox.click(function () { } Dropbox.save(options) }) -function uploadToGoogleDrive (accessToken) { - ui.spinner.show() - var filename = renderFilename(ui.area.markdown) + '.md' - var markdown = editor.getValue() - var blob = new Blob([markdown], { - type: 'text/markdown;charset=utf-8' - }) - blob.name = filename - var uploader = new MediaUploader({ - file: blob, - token: accessToken, - onComplete: function (data) { - data = JSON.parse(data) - showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true) - ui.spinner.hide() - }, - onError: function (data) { - showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false) - ui.spinner.hide() - } - }) - uploader.upload() -} -function googleApiAuth (immediate, callback) { - gapi.auth.authorize( - { - 'client_id': GOOGLE_CLIENT_ID, - 'scope': 'https://www.googleapis.com/auth/drive.file', - 'immediate': immediate - }, callback || function () { }) -} -function onGoogleClientLoaded () { - googleApiAuth(true) - buildImportFromGoogleDrive() -} -window.onGoogleClientLoaded = onGoogleClientLoaded -// export to google drive -ui.toolbar.export.googleDrive.click(function (e) { - var token = gapi.auth.getToken() - if (token) { - uploadToGoogleDrive(token.access_token) - } else { - googleApiAuth(false, function (result) { - uploadToGoogleDrive(result.access_token) - }) - } -}) // export to gist ui.toolbar.export.gist.attr('href', noteurl + '/gist') // export to snippet @@ -1075,38 +1003,6 @@ ui.toolbar.import.dropbox.click(function () { } Dropbox.choose(options) }) -// import from google drive -function buildImportFromGoogleDrive () { - /* eslint-disable no-unused-vars */ - let picker = new FilePicker({ - apiKey: GOOGLE_API_KEY, - clientId: GOOGLE_CLIENT_ID, - buttonEl: ui.toolbar.import.googleDrive, - onSelect: function (file) { - if (file.downloadUrl) { - ui.spinner.show() - var accessToken = gapi.auth.getToken().access_token - $.ajax({ - type: 'GET', - beforeSend: function (request) { - request.setRequestHeader('Authorization', 'Bearer ' + accessToken) - }, - url: file.downloadUrl, - success: function (data) { - if (file.fileExtension === 'html') { parseToEditor(data) } else { replaceAll(data) } - }, - error: function (data) { - showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false) - }, - complete: function () { - ui.spinner.hide() - } - }) - } - } - }) - /* eslint-enable no-unused-vars */ -} // import from gist ui.toolbar.import.gist.click(function () { // na diff --git a/public/js/lib/common/constant.ejs b/public/js/lib/common/constant.ejs index c0963635..a94b815e 100644 --- a/public/js/lib/common/constant.ejs +++ b/public/js/lib/common/constant.ejs @@ -5,6 +5,4 @@ window.version = '<%- version %>' window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %> -window.GOOGLE_API_KEY = '<%- GOOGLE_API_KEY %>' -window.GOOGLE_CLIENT_ID = '<%- GOOGLE_CLIENT_ID %>' window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>' diff --git a/public/js/lib/config/index.js b/public/js/lib/config/index.js index 11e4389f..4758ffe7 100644 --- a/public/js/lib/config/index.js +++ b/public/js/lib/config/index.js @@ -1,5 +1,3 @@ -export const GOOGLE_API_KEY = window.GOOGLE_API_KEY || '' -export const GOOGLE_CLIENT_ID = window.GOOGLE_CLIENT_ID || '' export const DROPBOX_APP_KEY = window.DROPBOX_APP_KEY || '' export const domain = window.domain || '' // domain name diff --git a/public/js/lib/editor/ui-elements.js b/public/js/lib/editor/ui-elements.js index 88a1e3ca..ca06d30c 100644 --- a/public/js/lib/editor/ui-elements.js +++ b/public/js/lib/editor/ui-elements.js @@ -22,13 +22,11 @@ export const getUIElements = () => ({ }, export: { dropbox: $('.ui-save-dropbox'), - googleDrive: $('.ui-save-google-drive'), gist: $('.ui-save-gist'), snippet: $('.ui-save-snippet') }, import: { dropbox: $('.ui-import-dropbox'), - googleDrive: $('.ui-import-google-drive'), gist: $('.ui-import-gist'), snippet: $('.ui-import-snippet'), clipboard: $('.ui-import-clipboard') diff --git a/public/views/hackmd/header.ejs b/public/views/hackmd/header.ejs index e179f171..21b632ce 100644 --- a/public/views/hackmd/header.ejs +++ b/public/views/hackmd/header.ejs @@ -32,13 +32,11 @@ </li> <li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a> </li> - <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof google !== 'undefined' && google) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %> + <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %> <li class="divider"></li> <li class="dropdown-header"><%= __('Export') %></li> <li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a> </li> - <li role="presentation"><a role="menuitem" class="ui-save-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-upload fa-fw"></i> Google Drive</a> - </li> <% if(typeof github !== 'undefined' && github) { %> <li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank"><i class="fa fa-github fa-fw"></i> Gist</a> </li> @@ -52,8 +50,6 @@ <li class="dropdown-header"><%= __('Import') %></li> <li role="presentation"><a role="menuitem" class="ui-import-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a> </li> - <li role="presentation"><a role="menuitem" class="ui-import-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-download fa-fw"></i> Google Drive</a> - </li> <li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a> </li> <% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %> @@ -138,13 +134,11 @@ </li> <li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a> </li> - <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof google !== 'undefined' && google) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %> + <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %> <li class="divider"></li> <li class="dropdown-header"><%= __('Export') %></li> <li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a> </li> - <li role="presentation"><a role="menuitem" class="ui-save-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-upload fa-fw"></i> Google Drive</a> - </li> <% if(typeof github !== 'undefined' && github) { %> <li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank"><i class="fa fa-github fa-fw"></i> Gist</a> </li> @@ -158,8 +152,6 @@ <li class="dropdown-header"><%= __('Import') %></li> <li role="presentation"><a role="menuitem" class="ui-import-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a> </li> - <li role="presentation"><a role="menuitem" class="ui-import-google-drive" tabindex="-1" href="#" target="_self"><i class="fa fa-cloud-download fa-fw"></i> Google Drive</a> - </li> <li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a> </li> <% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %> |