token.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* eslint-env browser */
  6. 'use strict';
  7. import mix from '@ckeditor/ckeditor5-utils/src/mix';
  8. import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
  9. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  10. const DEFAULT_OPTIONS = { refreshInterval: 3600000, autoRefresh: true };
  11. /**
  12. * Class representing the token used for communication with CKEditor Cloud Services.
  13. * Value of the token is retrieving from the specified URL and is refreshed every 1 hour by default.
  14. *
  15. * @mixes ObservableMixin
  16. */
  17. class Token {
  18. /**
  19. * Creates `Token` instance.
  20. * Method `init` should be called after using the constructor or use `create` method instead.
  21. *
  22. * @param {String|Function} tokenUrlOrRefreshToken Endpoint address to download the token or a callback that provides the token. If the
  23. * value is a function it has to match the {@link ~refreshToken} interface.
  24. * @param {Object} options
  25. * @param {String} [options.initValue] Initial value of the token.
  26. * @param {Number} [options.refreshInterval=3600000] Delay between refreshes. Default 1 hour.
  27. * @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
  28. */
  29. constructor( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
  30. if ( !tokenUrlOrRefreshToken ) {
  31. /**
  32. * A `tokenUrl` must be provided as the first constructor argument.
  33. *
  34. * @error token-missing-token-url
  35. */
  36. throw new CKEditorError( 'token-missing-token-url: A `tokenUrl` must be provided as the first constructor argument.' );
  37. }
  38. /**
  39. * Value of the token.
  40. * The value of the token is null if `initValue` is not provided or `init` method was not called.
  41. * `create` method creates token with initialized value from url.
  42. *
  43. * @name value
  44. * @member {String} #value
  45. * @observable
  46. * @readonly
  47. */
  48. this.set( 'value', options.initValue );
  49. /**
  50. * Base refreshing function.
  51. *
  52. * @private
  53. * @member {String|Function} #_refresh
  54. */
  55. if ( typeof tokenUrlOrRefreshToken === 'function' ) {
  56. this._refresh = tokenUrlOrRefreshToken;
  57. } else {
  58. this._refresh = () => defaultRefreshToken( tokenUrlOrRefreshToken );
  59. }
  60. /**
  61. * @type {Object}
  62. * @private
  63. */
  64. this._options = Object.assign( {}, DEFAULT_OPTIONS, options );
  65. }
  66. /**
  67. * Initializes the token.
  68. *
  69. * @returns {Promise.<Token>}
  70. */
  71. init() {
  72. return new Promise( ( resolve, reject ) => {
  73. if ( this._options.autoRefresh ) {
  74. this._startRefreshing();
  75. }
  76. if ( !this.value ) {
  77. this._refreshToken()
  78. .then( resolve )
  79. .catch( reject );
  80. return;
  81. }
  82. resolve( this );
  83. } );
  84. }
  85. /**
  86. * Refresh token method. Useful in a method form as it can be override in tests.
  87. *
  88. * @protected
  89. */
  90. _refreshToken() {
  91. return this._refresh()
  92. .then( value => this.set( 'value', value ) )
  93. .then( () => this );
  94. }
  95. /**
  96. * Destroys token instance. Stops refreshing.
  97. */
  98. destroy() {
  99. this._stopRefreshing();
  100. }
  101. /**
  102. * Starts value refreshing every `refreshInterval` time.
  103. *
  104. * @protected
  105. */
  106. _startRefreshing() {
  107. this._refreshInterval = setInterval( () => this._refreshToken(), this._options.refreshInterval );
  108. }
  109. /**
  110. * Stops value refreshing.
  111. *
  112. * @protected
  113. */
  114. _stopRefreshing() {
  115. clearInterval( this._refreshInterval );
  116. }
  117. /**
  118. * Creates a initialized {@link Token} instance.
  119. *
  120. * @param {String|Function} tokenUrlOrRefreshToken Endpoint address to download the token or a callback that provides the token. If the
  121. * value is a function it has to match the {@link ~refreshToken} interface.
  122. * @param {Object} options
  123. * @param {String} [options.initValue] Initial value of the token.
  124. * @param {Number} [options.refreshInterval=3600000] Delay between refreshes. Default 1 hour.
  125. * @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
  126. * @returns {Promise.<Token>}
  127. */
  128. static create( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
  129. const token = new Token( tokenUrlOrRefreshToken, options );
  130. return token.init();
  131. }
  132. }
  133. mix( Token, ObservableMixin );
  134. /**
  135. * This function is called in a defined interval by the {@link ~Token} class.
  136. * It should return a promise, which resolves with the new token value.
  137. * If any error occurs it should return a rejected promise with an error message.
  138. *
  139. * @function refreshToken
  140. * @returns {Promise.<String>}
  141. */
  142. /**
  143. * @private
  144. * @param {String} tokenUrl
  145. */
  146. function defaultRefreshToken( tokenUrl ) {
  147. return new Promise( ( resolve, reject ) => {
  148. const xhr = new XMLHttpRequest();
  149. xhr.open( 'GET', tokenUrl );
  150. xhr.addEventListener( 'load', () => {
  151. const statusCode = xhr.status;
  152. const xhrResponse = xhr.response;
  153. if ( statusCode < 200 || statusCode > 299 ) {
  154. /**
  155. * Cannot download new token from the provided url.
  156. *
  157. * @error token-cannot-download-new-token
  158. */
  159. return reject( new CKEditorError( 'token-cannot-download-new-token: Cannot download new token from the provided url.' ) );
  160. }
  161. return resolve( xhrResponse );
  162. } );
  163. xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
  164. xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );
  165. xhr.send();
  166. } );
  167. }
  168. export default Token;