8
0
فهرست منبع

Merge branch 'master' into t/ckeditor5/924

Kamil Piechaczek 7 سال پیش
والد
کامیت
c8bbe95cab

+ 1 - 7
packages/ckeditor-cloud-services-core/LICENSE.md

@@ -4,13 +4,7 @@ Software License Agreement
 **CKEditor Cloud Services Core** – https://github.com/ckeditor/ckeditor-cloud-services-core <br>
 Copyright (c) 2003-2017, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
 
-Licensed under the terms of any of the following licenses at your choice:
-
-* [GNU General Public License Version 2 or later (the "GPL")](http://www.gnu.org/licenses/gpl.html)
-* [GNU Lesser General Public License Version 2.1 or later (the "LGPL")](http://www.gnu.org/licenses/lgpl.html)
-* [Mozilla Public License Version 1.1 or later (the "MPL")](http://www.mozilla.org/MPL/MPL-1.1.html)
-
-You are not required to, but if you want to explicitly declare the license you have chosen to be bound to when using, reproducing, modifying and distributing this software, just include a text file titled "legal.txt" in your version of this software, indicating your license choice. In any case, your choice will not restrict any recipient of your version of this software to use, reproduce, modify and distribute this software under any of the above licenses.
+Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
 
 Sources of Intellectual Property Included in CKEditor Cloud Services Core
 -------------------------------------------------------------------------

+ 8 - 2
packages/ckeditor-cloud-services-core/README.md

@@ -1,4 +1,10 @@
-# CKEditor Cloud Services Core
+CKEditor Cloud Services Core
+============================
 
 CKEditor Cloud Services Core API:
-* Upload Gateway - API for file uploads to CKEditor Cloud Services. 
+
+* Upload Gateway - API for file uploads to CKEditor Cloud Services.
+
+## License
+
+Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the LICENSE.md file.

+ 66 - 36
packages/ckeditor-cloud-services-core/src/token/token.js

@@ -23,15 +23,16 @@ class Token {
 	 * Creates `Token` instance.
 	 * Method `init` should be called after using the constructor or use `create` method instead.
 	 *
-	 * @param {String} tokenUrl Endpoint address to download the token.
+	 * @param {String|Function} tokenUrlOrRefreshToken Endpoint address to download the token or a callback that provides the token. If the
+	 * value is a function it has to match the {@link ~refreshToken} interface.
 	 * @param {Object} options
 	 * @param {String} [options.initValue] Initial value of the token.
 	 * @param {Number} [options.refreshInterval=3600000] Delay between refreshes. Default 1 hour.
 	 * @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
 	 */
-	constructor( tokenUrl, options = DEFAULT_OPTIONS ) {
-		if ( !tokenUrl ) {
-			throw new Error( '`tokenUrl` must be provided' );
+	constructor( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
+		if ( !tokenUrlOrRefreshToken ) {
+			throw new Error( 'A `tokenUrl` must be provided as the first constructor argument.' );
 		}
 
 		/**
@@ -40,18 +41,23 @@ class Token {
 		 * `create` method creates token with initialized value from url.
 		 *
 		 * @name value
-		 * @type {String}
+		 * @member {String} #value
 		 * @observable
 		 * @readonly
-		 * @memberOf Token#
 		 */
 		this.set( 'value', options.initValue );
 
 		/**
-		 * @type {String}
+		 * Base refreshing function.
+		 *
 		 * @private
+		 * @member {String|Function} #_refresh
 		 */
-		this._tokenUrl = tokenUrl;
+		if ( typeof tokenUrlOrRefreshToken === 'function' ) {
+			this._refresh = tokenUrlOrRefreshToken;
+		} else {
+			this._refresh = () => defaultRefreshToken( tokenUrlOrRefreshToken );
+		}
 
 		/**
 		 * @type {Object}
@@ -84,35 +90,21 @@ class Token {
 	}
 
 	/**
-	 * Gets the new token.
+	 * Refresh token method. Useful in a method form as it can be override in tests.
 	 *
 	 * @protected
-	 * @returns {Promise.<Token>}
 	 */
 	_refreshToken() {
-		return new Promise( ( resolve, reject ) => {
-			const xhr = new XMLHttpRequest();
-
-			xhr.open( 'GET', this._tokenUrl );
-
-			xhr.addEventListener( 'load', () => {
-				const statusCode = xhr.status;
-				const xhrResponse = xhr.response;
-
-				if ( statusCode < 200 || statusCode > 299 ) {
-					return reject( 'Cannot download new token!' );
-				}
-
-				this.set( 'value', xhrResponse );
-
-				return resolve( this );
-			} );
-
-			xhr.addEventListener( 'error', () => reject( 'Network Error' ) );
-			xhr.addEventListener( 'abort', () => reject( 'Abort' ) );
+		return this._refresh()
+			.then( value => this.set( 'value', value ) )
+			.then( () => this );
+	}
 
-			xhr.send();
-		} );
+	/**
+	 * Destroys token instance. Stops refreshing.
+	 */
+	destroy() {
+		this._stopRefreshing();
 	}
 
 	/**
@@ -121,7 +113,7 @@ class Token {
 	 * @protected
 	 */
 	_startRefreshing() {
-		this._refreshInterval = setInterval( this._refreshToken.bind( this ), this._options.refreshInterval );
+		this._refreshInterval = setInterval( () => this._refreshToken(), this._options.refreshInterval );
 	}
 
 	/**
@@ -136,15 +128,16 @@ class Token {
 	/**
 	 * Creates a initialized {@link Token} instance.
 	 *
-	 * @param {String} tokenUrl Endpoint address to download the token.
+	 * @param {String|Function} tokenUrlOrRefreshToken Endpoint address to download the token or a callback that provides the token. If the
+	 * value is a function it has to match the {@link ~refreshToken} interface.
 	 * @param {Object} options
 	 * @param {String} [options.initValue] Initial value of the token.
 	 * @param {Number} [options.refreshInterval=3600000] Delay between refreshes. Default 1 hour.
 	 * @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
 	 * @returns {Promise.<Token>}
 	 */
-	static create( tokenUrl, options = DEFAULT_OPTIONS ) {
-		const token = new Token( tokenUrl, options );
+	static create( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
+		const token = new Token( tokenUrlOrRefreshToken, options );
 
 		return token.init();
 	}
@@ -152,4 +145,41 @@ class Token {
 
 mix( Token, ObservableMixin );
 
+/**
+ * This function is called in a defined interval by the {@link ~Token} class.
+ * It should return a promise, which resolves with the new token value.
+ * If any error occurs it should return a rejected promise with an error message.
+ *
+ * @function refreshToken
+ * @returns {Promise.<String>}
+ */
+
+/**
+ * @private
+ * @param {String} tokenUrl
+ */
+function defaultRefreshToken( tokenUrl ) {
+	return new Promise( ( resolve, reject ) => {
+		const xhr = new XMLHttpRequest();
+
+		xhr.open( 'GET', tokenUrl );
+
+		xhr.addEventListener( 'load', () => {
+			const statusCode = xhr.status;
+			const xhrResponse = xhr.response;
+
+			if ( statusCode < 200 || statusCode > 299 ) {
+				return reject( new Error( 'Cannot download new token!' ) );
+			}
+
+			return resolve( xhrResponse );
+		} );
+
+		xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
+		xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );
+
+		xhr.send();
+	} );
+};
+
 export default Token;

+ 7 - 3
packages/ckeditor-cloud-services-core/src/uploadgateway/fileuploader.js

@@ -176,14 +176,18 @@ class FileUploader {
 				const xhrResponse = xhr.response;
 
 				if ( statusCode < 200 || statusCode > 299 ) {
-					return reject( xhrResponse.message || xhrResponse.error );
+					if ( xhrResponse.message ) {
+						return reject( new Error( xhrResponse.message ) );
+					}
+
+					return reject( xhrResponse.error );
 				}
 
 				return resolve( xhrResponse );
 			} );
 
-			xhr.addEventListener( 'error', () => reject( 'Network Error' ) );
-			xhr.addEventListener( 'abort', () => reject( 'Abort' ) );
+			xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
+			xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );
 
 			xhr.send( formData );
 		} );

+ 81 - 22
packages/ckeditor-cloud-services-core/tests/token/token.js

@@ -26,7 +26,9 @@ describe( 'Token', () => {
 
 	describe( 'constructor()', () => {
 		it( 'should throw error when no tokenUrl provided', () => {
-			expect( () => new Token() ).to.throw( '`tokenUrl` must be provided' );
+			expect( () => new Token() ).to.throw(
+				'A `tokenUrl` must be provided as the first constructor argument.'
+			);
 		} );
 
 		it( 'should set a init token value', () => {
@@ -48,6 +50,13 @@ describe( 'Token', () => {
 
 			requests[ 0 ].respond( 200, '', 'token-value' );
 		} );
+
+		it( 'should accept the callback in the constructor', () => {
+			expect( () => {
+				// eslint-disable-next-line
+				const token = new Token( () => Promise.resolve( 'token' ) );
+			} ).to.not.throw();
+		} );
 	} );
 
 	describe( 'init()', () => {
@@ -64,6 +73,15 @@ describe( 'Token', () => {
 			requests[ 0 ].respond( 200, '', 'token-value' );
 		} );
 
+		it( 'should get a token from the refreshToken function when is provided', () => {
+			const token = new Token( () => Promise.resolve( 'token-value' ), { autoRefresh: false } );
+
+			return token.init()
+				.then( () => {
+					expect( token.value ).to.equal( 'token-value' );
+				} );
+		} );
+
 		it( 'should start token refresh every 1 hour', done => {
 			const clock = sinon.useFakeTimers( { toFake: [ 'setInterval' ] } );
 
@@ -86,6 +104,29 @@ describe( 'Token', () => {
 		} );
 	} );
 
+	describe( 'destroy', () => {
+		it( 'should stop refreshing the token', () => {
+			const clock = sinon.useFakeTimers( { toFake: [ 'setInterval', 'clearInterval' ] } );
+			const token = new Token( 'http://token-endpoint', { initValue: 'initValue' } );
+
+			return token.init()
+				.then( () => {
+					clock.tick( 3600000 );
+					clock.tick( 3600000 );
+
+					expect( requests.length ).to.equal( 2 );
+
+					token.destroy();
+
+					clock.tick( 3600000 );
+					clock.tick( 3600000 );
+					clock.tick( 3600000 );
+
+					expect( requests.length ).to.equal( 2 );
+				} );
+		} );
+	} );
+
 	describe( '_refreshToken()', () => {
 		it( 'should get a token from the specified address', done => {
 			const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
@@ -100,43 +141,61 @@ describe( 'Token', () => {
 			requests[ 0 ].respond( 200, '', 'token-value' );
 		} );
 
-		it( 'should throw error when cannot download new token ', done => {
-			const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
-
-			token._refreshToken()
-				.catch( error => {
-					expect( error ).to.equal( 'Cannot download new token!' );
+		it( 'should get a token from the specified callback function', () => {
+			const token = new Token( () => Promise.resolve( 'token-value' ), { initValue: 'initValue', autoRefresh: false } );
 
-					done();
+			return token._refreshToken()
+				.then( newToken => {
+					expect( newToken.value ).to.equal( 'token-value' );
 				} );
-
-			requests[ 0 ].respond( 401 );
 		} );
 
-		it( 'should throw error when response is aborted', done => {
+		it( 'should throw an error when cannot download new token', () => {
 			const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
+			const promise = token._refresh();
 
-			token._refreshToken()
-				.catch( error => {
-					expect( error ).to.equal( 'Abort' );
+			requests[ 0 ].respond( 401 );
 
-					done();
-				} );
+			return promise.then( () => {
+				throw new Error( 'Promise should be rejected' );
+			}, error => {
+				expect( error ).to.match( /Cannot download new token!/ );
+			} )
+		} );
+
+		it( 'should throw an error when the response is aborted', () => {
+			const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
+			const promise = token._refresh();
 
 			requests[ 0 ].abort();
+
+			return promise.then( () => {
+				throw new Error( 'Promise should be rejected' );
+			}, error => {
+				expect( error ).to.match( /Abort/ );
+			} )
 		} );
 
-		it( 'should throw error event when network error occurs', done => {
+		it( 'should throw an error when network error occurs', () => {
 			const token = new Token( 'http://token-endpoint', { initValue: 'initValue', autoRefresh: false } );
+			const promise = token._refresh();
+
+			requests[ 0 ].error();
+
+			return promise.then( () => {
+				throw new Error( 'Promise should be rejected' );
+			}, error => {
+				expect( error ).to.match( /Network Error/ );
+			} )
+		} );
+
+		it( 'should throw an error when the callback throws error', () => {
+			const token = new Token( () => Promise.reject( 'Custom error occurred' ), { initValue: 'initValue', autoRefresh: false } );
 
 			token._refreshToken()
 				.catch( error => {
-					expect( error ).to.equal( 'Network Error' );
-
-					done();
+					expect( error ).to.equal( 'Custom error occurred' );
 				} );
-
-			requests[ 0 ].error();
 		} );
 	} );