token.js 4.9 KB

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