token.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /**
  2. * @license Copyright (c) 2003-2017, 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. * Starts value refreshing every `refreshInterval` time.
  91. *
  92. * @protected
  93. */
  94. _startRefreshing() {
  95. this._refreshInterval = setInterval( () => this._refreshToken(), this._options.refreshInterval );
  96. }
  97. /**
  98. * Stops value refreshing.
  99. *
  100. * @protected
  101. */
  102. _stopRefreshing() {
  103. clearInterval( this._refreshInterval );
  104. }
  105. /**
  106. * Creates a initialized {@link Token} instance.
  107. *
  108. * @param {String|Function} tokenUrlOrRefreshToken Endpoint address to download the token or a callback that provides the token. If the
  109. * value is a function it has to match the {@link ~refreshToken} interface.
  110. * @param {Object} options
  111. * @param {String} [options.initValue] Initial value of the token.
  112. * @param {Number} [options.refreshInterval=3600000] Delay between refreshes. Default 1 hour.
  113. * @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
  114. * @returns {Promise.<Token>}
  115. */
  116. static create( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
  117. const token = new Token( tokenUrlOrRefreshToken, options );
  118. return token.init();
  119. }
  120. }
  121. mix( Token, ObservableMixin );
  122. /**
  123. * This function is called in a defined interval by the {@link ~Token} class.
  124. * It should return a promise, which resolves with the new token value.
  125. * If any error occurs it should return a rejected promise with an error message.
  126. *
  127. * @function refreshToken
  128. * @returns {Promise.<String>}
  129. */
  130. /**
  131. * @private
  132. * @param {String} tokenUrl
  133. */
  134. function defaultRefreshToken( tokenUrl ) {
  135. return new Promise( ( resolve, reject ) => {
  136. const xhr = new XMLHttpRequest();
  137. xhr.open( 'GET', tokenUrl );
  138. xhr.addEventListener( 'load', () => {
  139. const statusCode = xhr.status;
  140. const xhrResponse = xhr.response;
  141. if ( statusCode < 200 || statusCode > 299 ) {
  142. return reject( 'Cannot download new token!' );
  143. }
  144. return resolve( xhrResponse );
  145. } );
  146. xhr.addEventListener( 'error', () => reject( 'Network Error' ) );
  147. xhr.addEventListener( 'abort', () => reject( 'Abort' ) );
  148. xhr.send();
  149. } );
  150. };
  151. export default Token;