Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ckeditor/ckeditor5-cloud-services

Package Overview
Dependencies
Maintainers
1
Versions
764
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-cloud-services - npm Package Compare versions

Comparing version 36.0.1 to 37.0.0-alpha.0

src/cloudservices.d.ts

2

build/cloud-services.js
/*!
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/(()=>{var e={704:(e,t,r)=>{e.exports=r(79)("./src/core.js")},209:(e,t,r)=>{e.exports=r(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function r(s){var o=t[s];if(void 0!==o)return o.exports;var n=t[s]={exports:{}};return e[s](n,n.exports,r),n.exports}r.d=(e,t)=>{for(var s in t)r.o(t,s)&&!r.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var s={};(()=>{"use strict";r.r(s),r.d(s,{CloudServices:()=>c,CloudServicesCore:()=>d});var e=r(704),t=r(209);const o={autoRefresh:!0},n=36e5;class i{constructor(e,r=o){if(!e)throw new t.CKEditorError("token-missing-token-url",this);r.initValue&&this._validateTokenValue(r.initValue),this.set("value",r.initValue),this._refresh="function"==typeof e?e:()=>{return r=e,new Promise(((e,s)=>{const o=new XMLHttpRequest;o.open("GET",r),o.addEventListener("load",(()=>{const r=o.status,n=o.response;return r<200||r>299?s(new t.CKEditorError("token-cannot-download-new-token",null)):e(n)})),o.addEventListener("error",(()=>s(new Error("Network Error")))),o.addEventListener("abort",(()=>s(new Error("Abort")))),o.send()}));var r},this._options=Object.assign({},o,r)}init(){return new Promise(((e,t)=>{this.value?(this._options.autoRefresh&&this._registerRefreshTokenTimeout(),e(this)):this.refreshToken().then(e).catch(t)}))}refreshToken(){return this._refresh().then((e=>{this._validateTokenValue(e),this.set("value",e),this._options.autoRefresh&&this._registerRefreshTokenTimeout()})).then((()=>this))}destroy(){clearTimeout(this._tokenRefreshTimeout)}_validateTokenValue(e){const r="string"==typeof e,s=!/^".*"$/.test(e),o=r&&3===e.split(".").length;if(!s||!o)throw new t.CKEditorError("token-not-in-jwt-format",this)}_registerRefreshTokenTimeout(){const e=this._getTokenRefreshTimeoutTime();clearTimeout(this._tokenRefreshTimeout),this._tokenRefreshTimeout=setTimeout((()=>{this.refreshToken()}),e)}_getTokenRefreshTimeoutTime(){try{const[,e]=this.value.split("."),{exp:t}=JSON.parse(atob(e));if(!t)return n;return Math.floor((1e3*t-Date.now())/2)}catch(e){return n}}static create(e,t=o){return new i(e,t).init()}}(0,t.mix)(i,t.ObservableMixin);const a=i,u=/^data:(\S*?);base64,/;class h{constructor(e,r,s){if(!e)throw new t.CKEditorError("fileuploader-missing-file",null);if(!r)throw new t.CKEditorError("fileuploader-missing-token",null);if(!s)throw new t.CKEditorError("fileuploader-missing-api-address",null);this.file=function(e){if("string"!=typeof e)return!1;const t=e.match(u);return!(!t||!t.length)}(e)?function(e,r=512){try{const t=e.match(u)[1],s=atob(e.replace(u,"")),o=[];for(let e=0;e<s.length;e+=r){const t=s.slice(e,e+r),n=new Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);o.push(new Uint8Array(n))}return new Blob(o,{type:t})}catch(e){throw new t.CKEditorError("fileuploader-decoding-image-data-error",null)}}(e):e,this._token=r,this._apiAddress=s}onProgress(e){return this.on("progress",((t,r)=>e(r))),this}onError(e){return this.once("error",((t,r)=>e(r))),this}abort(){this.xhr.abort()}send(){return this._prepareRequest(),this._attachXHRListeners(),this._sendRequest()}_prepareRequest(){const e=new XMLHttpRequest;e.open("POST",this._apiAddress),e.setRequestHeader("Authorization",this._token.value),e.responseType="json",this.xhr=e}_attachXHRListeners(){const e=this,t=this.xhr;function r(t){return()=>e.fire("error",t)}t.addEventListener("error",r("Network Error")),t.addEventListener("abort",r("Abort")),t.upload&&t.upload.addEventListener("progress",(e=>{e.lengthComputable&&this.fire("progress",{total:e.total,uploaded:e.loaded})})),t.addEventListener("load",(()=>{const e=t.status,r=t.response;if(e<200||e>299)return this.fire("error",r.message||r.error)}))}_sendRequest(){const e=new FormData,r=this.xhr;return e.append("file",this.file),new Promise(((s,o)=>{r.addEventListener("load",(()=>{const e=r.status,n=r.response;return e<200||e>299?n.message?o(new t.CKEditorError("fileuploader-uploading-data-failed",this,{message:n.message})):o(n.error):s(n)})),r.addEventListener("error",(()=>o(new Error("Network Error")))),r.addEventListener("abort",(()=>o(new Error("Abort")))),r.send(e)}))}}(0,t.mix)(h,t.EmitterMixin);class l{constructor(e,r){if(!e)throw new t.CKEditorError("uploadgateway-missing-token",null);if(!r)throw new t.CKEditorError("uploadgateway-missing-api-address",null);this._token=e,this._apiAddress=r}upload(e){return new h(e,this._token,this._apiAddress)}}class d extends e.ContextPlugin{static get pluginName(){return"CloudServicesCore"}createToken(e,t){return new a(e,t)}createUploadGateway(e,t){return new l(e,t)}}class c extends e.ContextPlugin{static get pluginName(){return"CloudServices"}static get requires(){return[d]}init(){const e=this.context.config.get("cloudServices")||{};for(const t in e)this[t]=e[t];if(this._tokens=new Map,this.tokenUrl)return this.token=this.context.plugins.get("CloudServicesCore").createToken(this.tokenUrl),this._tokens.set(this.tokenUrl,this.token),this.token.init();this.token=null}registerTokenUrl(e){if(this._tokens.has(e))return Promise.resolve(this.getTokenFor(e));const t=this.context.plugins.get("CloudServicesCore").createToken(e);return this._tokens.set(e,t),t.init()}getTokenFor(e){const r=this._tokens.get(e);if(!r)throw new t.CKEditorError("cloudservices-token-not-registered",this);return r}destroy(){super.destroy();for(const e of this._tokens.values())e.destroy()}}})(),(window.CKEditor5=window.CKEditor5||{}).cloudServices=s})();
*/(()=>{var e={704:(e,t,r)=>{e.exports=r(79)("./src/core.js")},209:(e,t,r)=>{e.exports=r(79)("./src/utils.js")},79:e=>{"use strict";e.exports=CKEditor5.dll}},t={};function r(s){var o=t[s];if(void 0!==o)return o.exports;var n=t[s]={exports:{}};return e[s](n,n.exports,r),n.exports}r.d=(e,t)=>{for(var s in t)r.o(t,s)&&!r.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var s={};(()=>{"use strict";r.r(s),r.d(s,{CloudServices:()=>d,CloudServicesCore:()=>h});var e=r(704),t=r(209);const o={autoRefresh:!0},n=36e5;class i extends((0,t.ObservableMixin)()){constructor(e,r={}){if(super(),!e)throw new t.CKEditorError("token-missing-token-url",this);r.initValue&&this._validateTokenValue(r.initValue),this.set("value",r.initValue),this._refresh="function"==typeof e?e:()=>{return r=e,new Promise(((e,s)=>{const o=new XMLHttpRequest;o.open("GET",r),o.addEventListener("load",(()=>{const r=o.status,n=o.response;return r<200||r>299?s(new t.CKEditorError("token-cannot-download-new-token",null)):e(n)})),o.addEventListener("error",(()=>s(new Error("Network Error")))),o.addEventListener("abort",(()=>s(new Error("Abort")))),o.send()}));var r},this._options={...o,...r}}init(){return new Promise(((e,t)=>{this.value?(this._options.autoRefresh&&this._registerRefreshTokenTimeout(),e(this)):this.refreshToken().then(e).catch(t)}))}refreshToken(){return this._refresh().then((e=>(this._validateTokenValue(e),this.set("value",e),this._options.autoRefresh&&this._registerRefreshTokenTimeout(),this)))}destroy(){clearTimeout(this._tokenRefreshTimeout)}_validateTokenValue(e){const r="string"==typeof e,s=!/^".*"$/.test(e),o=r&&3===e.split(".").length;if(!s||!o)throw new t.CKEditorError("token-not-in-jwt-format",this)}_registerRefreshTokenTimeout(){const e=this._getTokenRefreshTimeoutTime();clearTimeout(this._tokenRefreshTimeout),this._tokenRefreshTimeout=setTimeout((()=>{this.refreshToken()}),e)}_getTokenRefreshTimeoutTime(){try{const[,e]=this.value.split("."),{exp:t}=JSON.parse(atob(e));if(!t)return n;return Math.floor((1e3*t-Date.now())/2)}catch(e){return n}}static create(e,t={}){return new i(e,t).init()}}const a=/^data:(\S*?);base64,/;class u extends((0,t.EmitterMixin)()){constructor(e,r,s){if(super(),!e)throw new t.CKEditorError("fileuploader-missing-file",null);if(!r)throw new t.CKEditorError("fileuploader-missing-token",null);if(!s)throw new t.CKEditorError("fileuploader-missing-api-address",null);this.file=function(e){if("string"!=typeof e)return!1;const t=e.match(a);return!(!t||!t.length)}(e)?function(e,r=512){try{const t=e.match(a)[1],s=atob(e.replace(a,"")),o=[];for(let e=0;e<s.length;e+=r){const t=s.slice(e,e+r),n=new Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);o.push(new Uint8Array(n))}return new Blob(o,{type:t})}catch(e){throw new t.CKEditorError("fileuploader-decoding-image-data-error",null)}}(e):e,this._token=r,this._apiAddress=s}onProgress(e){return this.on("progress",((t,r)=>e(r))),this}onError(e){return this.once("error",((t,r)=>e(r))),this}abort(){this.xhr.abort()}send(){return this._prepareRequest(),this._attachXHRListeners(),this._sendRequest()}_prepareRequest(){const e=new XMLHttpRequest;e.open("POST",this._apiAddress),e.setRequestHeader("Authorization",this._token.value),e.responseType="json",this.xhr=e}_attachXHRListeners(){const e=this.xhr,t=e=>()=>this.fire("error",e);e.addEventListener("error",t("Network Error")),e.addEventListener("abort",t("Abort")),e.upload&&e.upload.addEventListener("progress",(e=>{e.lengthComputable&&this.fire("progress",{total:e.total,uploaded:e.loaded})})),e.addEventListener("load",(()=>{const t=e.status,r=e.response;if(t<200||t>299)return this.fire("error",r.message||r.error)}))}_sendRequest(){const e=new FormData,r=this.xhr;return e.append("file",this.file),new Promise(((s,o)=>{r.addEventListener("load",(()=>{const e=r.status,n=r.response;return e<200||e>299?n.message?o(new t.CKEditorError("fileuploader-uploading-data-failed",this,{message:n.message})):o(n.error):s(n)})),r.addEventListener("error",(()=>o(new Error("Network Error")))),r.addEventListener("abort",(()=>o(new Error("Abort")))),r.send(e)}))}}class l{constructor(e,r){if(!e)throw new t.CKEditorError("uploadgateway-missing-token",null);if(!r)throw new t.CKEditorError("uploadgateway-missing-api-address",null);this._token=e,this._apiAddress=r}upload(e){return new u(e,this._token,this._apiAddress)}}class h extends e.ContextPlugin{static get pluginName(){return"CloudServicesCore"}createToken(e,t){return new i(e,t)}createUploadGateway(e,t){return new l(e,t)}}class d extends e.ContextPlugin{constructor(){super(...arguments),this.token=null,this._tokens=new Map}static get pluginName(){return"CloudServices"}static get requires(){return[h]}async init(){const e=this.context.config.get("cloudServices")||{};for(const[t,r]of Object.entries(e))this[t]=r;if(!this.tokenUrl)return void(this.token=null);const t=this.context.plugins.get("CloudServicesCore");this.token=await t.createToken(this.tokenUrl).init(),this._tokens.set(this.tokenUrl,this.token)}async registerTokenUrl(e){if(this._tokens.has(e))return this.getTokenFor(e);const t=this.context.plugins.get("CloudServicesCore"),r=await t.createToken(e).init();return this._tokens.set(e,r),r}getTokenFor(e){const r=this._tokens.get(e);if(!r)throw new t.CKEditorError("cloudservices-token-not-registered",this);return r}destroy(){super.destroy();for(const e of this._tokens.values())e.destroy()}}})(),(window.CKEditor5=window.CKEditor5||{}).cloudServices=s})();
{
"name": "@ckeditor/ckeditor5-cloud-services",
"version": "36.0.1",
"version": "37.0.0-alpha.0",
"description": "CKEditor 5's Cloud Services integration layer.",

@@ -14,10 +14,11 @@ "keywords": [

"dependencies": {
"ckeditor5": "^36.0.1"
"ckeditor5": "^37.0.0-alpha.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-core": "^36.0.1",
"@ckeditor/ckeditor5-dev-utils": "^32.0.0",
"@ckeditor/ckeditor5-editor-classic": "^36.0.1",
"@ckeditor/ckeditor5-theme-lark": "^36.0.1",
"@ckeditor/ckeditor5-utils": "^36.0.1",
"@ckeditor/ckeditor5-core": "^37.0.0-alpha.0",
"@ckeditor/ckeditor5-dev-utils": "^34.0.0",
"@ckeditor/ckeditor5-editor-classic": "^37.0.0-alpha.0",
"@ckeditor/ckeditor5-theme-lark": "^37.0.0-alpha.0",
"@ckeditor/ckeditor5-utils": "^37.0.0-alpha.0",
"typescript": "^4.8.4",
"webpack": "^5.58.1",

@@ -41,3 +42,4 @@ "webpack-cli": "^4.9.0"

"lang",
"src",
"src/**/*.js",
"src/**/*.d.ts",
"theme",

@@ -49,4 +51,7 @@ "build",

"scripts": {
"dll:build": "webpack"
}
"dll:build": "webpack",
"build": "tsc -p ./tsconfig.release.json",
"postversion": "npm run build"
},
"types": "src/index.d.ts"
}

@@ -5,11 +5,8 @@ /**

*/
/**
* @module cloud-services/cloudservices
*/
import { ContextPlugin } from 'ckeditor5/src/core';
import { CKEditorError } from 'ckeditor5/src/utils';
import CloudServicesCore from './cloudservicescore';
/**

@@ -19,247 +16,90 @@ * Plugin introducing the integration between CKEditor 5 and CKEditor Cloud Services .

* It initializes the token provider based on
* the {@link module:cloud-services/cloudservices~CloudServicesConfig `config.cloudService`}.
*
* @extends module:core/contextplugin~ContextPlugin
* the {@link module:cloud-services/cloudservicesconfig~CloudServicesConfig `config.cloudService`}.
*/
export default class CloudServices extends ContextPlugin {
/**
* @inheritdoc
*/
static get pluginName() {
return 'CloudServices';
}
/**
* @inheritDoc
*/
static get requires() {
return [ CloudServicesCore ];
}
/**
* @inheritDoc
*/
init() {
const config = this.context.config;
const options = config.get( 'cloudServices' ) || {};
for ( const optionName in options ) {
this[ optionName ] = options[ optionName ];
}
/**
* A map of token object instances keyed by the token URLs.
*
* @private
* @type {Map.<String, module:cloud-services/token~Token>}
*/
this._tokens = new Map();
/**
* The authentication token URL for CKEditor Cloud Services or a callback to the token value promise. See the
* {@link module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl} for more details.
*
* @readonly
* @member {String|Function|undefined} #tokenUrl
*/
/**
* The URL to which the files should be uploaded.
*
* @readonly
* @member {String} #uploadUrl
*/
/**
* Other plugins use this token for the authorization process. It handles token requesting and refreshing.
* Its value is `null` when {@link module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl} is not provided.
*
* @readonly
* @member {module:cloud-services/token~Token|null} #token
*/
if ( !this.tokenUrl ) {
this.token = null;
return;
}
this.token = this.context.plugins.get( 'CloudServicesCore' ).createToken( this.tokenUrl );
this._tokens.set( this.tokenUrl, this.token );
return this.token.init();
}
/**
* Registers an additional authentication token URL for CKEditor Cloud Services or a callback to the token value promise. See the
* {@link module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl} for more details.
*
* @param {String|Function} tokenUrl The authentication token URL for CKEditor Cloud Services or a callback to the token value promise.
* @returns {Promise.<module:cloud-services/token~Token>}
*/
registerTokenUrl( tokenUrl ) {
// Reuse the token instance in case of multiple features using the same token URL.
if ( this._tokens.has( tokenUrl ) ) {
return Promise.resolve( this.getTokenFor( tokenUrl ) );
}
const token = this.context.plugins.get( 'CloudServicesCore' ).createToken( tokenUrl );
this._tokens.set( tokenUrl, token );
return token.init();
}
/**
* Returns an authentication token provider previously registered by {@link #registerTokenUrl}.
*
* @param {String|Function} tokenUrl The authentication token URL for CKEditor Cloud Services or a callback to the token value promise.
* @returns {module:cloud-services/token~Token}
*/
getTokenFor( tokenUrl ) {
const token = this._tokens.get( tokenUrl );
if ( !token ) {
/**
* The provided `tokenUrl` was not registered by {@link module:cloud-services/cloudservices~CloudServices#registerTokenUrl}.
*
* @error cloudservices-token-not-registered
*/
throw new CKEditorError( 'cloudservices-token-not-registered', this );
}
return token;
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
for ( const token of this._tokens.values() ) {
token.destroy();
}
}
constructor() {
super(...arguments);
/**
* Other plugins use this token for the authorization process. It handles token requesting and refreshing.
* Its value is `null` when {@link module:cloud-services/cloudservicesconfig~CloudServicesConfig#tokenUrl} is not provided.
*
* @readonly
*/
this.token = null;
/**
* A map of token object instances keyed by the token URLs.
*/
this._tokens = new Map();
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'CloudServices';
}
/**
* @inheritDoc
*/
static get requires() {
return [CloudServicesCore];
}
/**
* @inheritDoc
*/
async init() {
const config = this.context.config;
const options = config.get('cloudServices') || {};
for (const [key, value] of Object.entries(options)) {
this[key] = value;
}
if (!this.tokenUrl) {
this.token = null;
return;
}
const cloudServicesCore = this.context.plugins.get('CloudServicesCore');
this.token = await cloudServicesCore.createToken(this.tokenUrl).init();
this._tokens.set(this.tokenUrl, this.token);
}
/**
* Registers an additional authentication token URL for CKEditor Cloud Services or a callback to the token value promise. See the
* {@link module:cloud-services/cloudservicesconfig~CloudServicesConfig#tokenUrl} for more details.
*
* @param tokenUrl The authentication token URL for CKEditor Cloud Services or a callback to the token value promise.
*/
async registerTokenUrl(tokenUrl) {
// Reuse the token instance in case of multiple features using the same token URL.
if (this._tokens.has(tokenUrl)) {
return this.getTokenFor(tokenUrl);
}
const cloudServicesCore = this.context.plugins.get('CloudServicesCore');
const token = await cloudServicesCore.createToken(tokenUrl).init();
this._tokens.set(tokenUrl, token);
return token;
}
/**
* Returns an authentication token provider previously registered by {@link #registerTokenUrl}.
*
* @param tokenUrl The authentication token URL for CKEditor Cloud Services or a callback to the token value promise.
*/
getTokenFor(tokenUrl) {
const token = this._tokens.get(tokenUrl);
if (!token) {
/**
* The provided `tokenUrl` was not registered by {@link module:cloud-services/cloudservices~CloudServices#registerTokenUrl}.
*
* @error cloudservices-token-not-registered
*/
throw new CKEditorError('cloudservices-token-not-registered', this);
}
return token;
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
for (const token of this._tokens.values()) {
token.destroy();
}
}
}
/**
* The configuration of CKEditor Cloud Services. Introduced by the {@link module:cloud-services/cloudservices~CloudServices} plugin.
*
* Read more in {@link module:cloud-services/cloudservices~CloudServicesConfig}.
*
* @member {module:cloud-services/cloudservices~CloudServicesConfig} module:core/editor/editorconfig~EditorConfig#cloudServices
*/
/**
* The configuration for all plugins using CKEditor Cloud Services.
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* cloudServices: {
* tokenUrl: 'https://example.com/cs-token-endpoint',
* uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/'
* }
* } )
* .then( ... )
* .catch( ... );
*
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
*
* @interface CloudServicesConfig
*/
/**
* A token URL or a token request function.
*
* As a string, it should be a URL to the security token endpoint in your application. The role of this endpoint is to securely authorize
* the end users of your application to use [CKEditor Cloud Services](https://ckeditor.com/ckeditor-cloud-services) only
* if they should have access e.g. to upload files with {@glink @cs guides/easy-image/quick-start Easy Image} or to use the
* {@glink @cs guides/collaboration/quick-start Collaboration} service.
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* cloudServices: {
* tokenUrl: 'https://example.com/cs-token-endpoint',
* ...
* }
* } )
* .then( ... )
* .catch( ... );
*
* As a function, it should provide a promise to the token value, so you can highly customize the token and provide your token URL endpoint.
* By using this approach you can set your own headers for the request.
*
* ClassicEditor
* .create( document.querySelector( '#editor' ), {
* cloudServices: {
* tokenUrl: () => new Promise( ( resolve, reject ) => {
* const xhr = new XMLHttpRequest();
*
* xhr.open( 'GET', 'https://example.com/cs-token-endpoint' );
*
* 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.setRequestHeader( customHeader, customValue );
*
* xhr.send();
* } ),
* ...
* }
* } )
*
* You can find more information about token endpoints in the
* {@glink @cs guides/easy-image/quick-start#create-token-endpoint Cloud Services - Quick start}
* and {@glink @cs guides/security/token-endpoint Cloud Services - Token endpoint} documentation.
*
* Without a properly working token endpoint (token URL) CKEditor plugins will not be able to connect to CKEditor Cloud Services.
*
* @member {String|Function} module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl
*/
/**
* The endpoint URL for [CKEditor Cloud Services](https://ckeditor.com/ckeditor-cloud-services) uploads.
* This option must be set for Easy Image to work correctly.
*
* The upload URL is unique for each customer and can be found in the
* [CKEditor Ecosystem customer dashboard](https://dashboard.ckeditor.com) after subscribing to the Easy Image service.
* To learn how to start using Easy Image, check the {@glink @cs guides/easy-image/quick-start Easy Image - Quick start} documentation.
*
* Note: Make sure to also set the {@link module:cloud-services/cloudservices~CloudServicesConfig#tokenUrl} configuration option.
*
* @member {String} module:cloud-services/cloudservices~CloudServicesConfig#uploadUrl
*/
/**
* The URL for web socket communication, used by the `RealTimeCollaborativeEditing` plugin. Every customer (organization in the CKEditor
* Ecosystem dashboard) has their own, unique URLs to communicate with CKEditor Cloud Services. The URL can be found in the
* CKEditor Ecosystem customer dashboard.
*
* Note: Unlike most plugins, `RealTimeCollaborativeEditing` is not included in any CKEditor 5 build and needs to be installed manually.
* Check [Collaboration overview](https://ckeditor.com/docs/ckeditor5/latest/features/collaboration/overview.html) for more details.
*
* @member {String} module:cloud-services/cloudservices~CloudServicesConfig#webSocketUrl
*/
/**
* An optional parameter used for integration with CKEditor Cloud Services when uploading the editor build to cloud services.
*
* Whenever the editor build or the configuration changes, this parameter should be set to a new, unique value to differentiate
* the new bundle (build + configuration) from the old ones.
*
* @member {String} module:cloud-services/cloudservices~CloudServicesConfig#bundleVersion
*/

@@ -5,48 +5,38 @@ /**

*/
/**
* @module cloud-services/cloudservicescore
*/
import { ContextPlugin } from 'ckeditor5/src/core';
import Token from './token/token';
import UploadGateway from './uploadgateway/uploadgateway';
/**
* The `CloudServicesCore` plugin exposes the base API for communication with CKEditor Cloud Services.
*
* @extends module:core/contextplugin~ContextPlugin
*/
export default class CloudServicesCore extends ContextPlugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'CloudServicesCore';
}
/**
* Creates the {@link module:cloud-services/token~Token} instance.
*
* @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 module:cloud-services/token~refreshToken} interface.
* @param {Object} [options]
* @param {String} [options.initValue] Initial value of the token.
* @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
* @returns {module:cloud-services/token~Token}
*/
createToken( tokenUrlOrRefreshToken, options ) {
return new Token( tokenUrlOrRefreshToken, options );
}
/**
* Creates the {@link module:cloud-services/uploadgateway/uploadgateway~UploadGateway} instance.
*
* @param {module:cloud-services/token~Token} token Token used for authentication.
* @param {String} apiAddress API address.
* @returns {module:cloud-services/uploadgateway/uploadgateway~UploadGateway}
*/
createUploadGateway( token, apiAddress ) {
return new UploadGateway( token, apiAddress );
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'CloudServicesCore';
}
/**
* Creates the {@link module:cloud-services/token~Token} instance.
*
* @param 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 module:cloud-services/token~refreshToken} interface.
* @param options.initValue Initial value of the token.
* @param options.autoRefresh Specifies whether to start the refresh automatically.
*/
createToken(tokenUrlOrRefreshToken, options) {
return new Token(tokenUrlOrRefreshToken, options);
}
/**
* Creates the {@link module:cloud-services/uploadgateway/uploadgateway~UploadGateway} instance.
*
* @param token Token used for authentication.
* @param apiAddress API address.
*/
createUploadGateway(token, apiAddress) {
return new UploadGateway(token, apiAddress);
}
}

@@ -5,8 +5,6 @@ /**

*/
/**
* @module cloud-services
*/
export { default as CloudServices } from './cloudservices';
export { default as CloudServicesCore } from './cloudservicescore';

@@ -5,212 +5,142 @@ /**

*/
/**
* @module cloud-services/token
*/
/* globals XMLHttpRequest, setTimeout, clearTimeout, atob */
import { mix, ObservableMixin, CKEditorError } from 'ckeditor5/src/utils';
import { ObservableMixin, CKEditorError } from 'ckeditor5/src/utils';
const DEFAULT_OPTIONS = { autoRefresh: true };
const DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME = 3600000;
/**
* Class representing the token used for communication with CKEditor Cloud Services.
* Value of the token is retrieving from the specified URL and is refreshed every 1 hour by default.
*
* @mixes ObservableMixin
*/
class Token {
/**
* Creates `Token` instance.
* Method `init` should be called after using the constructor or use `create` method instead.
*
* @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 module:cloud-services/token~refreshToken} interface.
* @param {Object} options
* @param {String} [options.initValue] Initial value of the token.
* @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
*/
constructor( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
if ( !tokenUrlOrRefreshToken ) {
/**
* A `tokenUrl` must be provided as the first constructor argument.
*
* @error token-missing-token-url
*/
throw new CKEditorError(
'token-missing-token-url',
this
);
}
if ( options.initValue ) {
this._validateTokenValue( options.initValue );
}
/**
* Value of the token.
* The value of the token is null if `initValue` is not provided or `init` method was not called.
* `create` method creates token with initialized value from url.
*
* @name value
* @member {String} #value
* @observable
* @readonly
*/
this.set( 'value', options.initValue );
/**
* Base refreshing function.
*
* @private
* @member {String|Function} #_refresh
*/
if ( typeof tokenUrlOrRefreshToken === 'function' ) {
this._refresh = tokenUrlOrRefreshToken;
} else {
this._refresh = () => defaultRefreshToken( tokenUrlOrRefreshToken );
}
/**
* @type {Object}
* @private
*/
this._options = Object.assign( {}, DEFAULT_OPTIONS, options );
}
/**
* Initializes the token.
*
* @returns {Promise.<module:cloud-services/token~Token>}
*/
init() {
return new Promise( ( resolve, reject ) => {
if ( !this.value ) {
this.refreshToken()
.then( resolve )
.catch( reject );
return;
}
if ( this._options.autoRefresh ) {
this._registerRefreshTokenTimeout();
}
resolve( this );
} );
}
/**
* Refresh token method. Useful in a method form as it can be override in tests.
* @returns {Promise.<String>}
*/
refreshToken() {
return this._refresh()
.then( value => {
this._validateTokenValue( value );
this.set( 'value', value );
if ( this._options.autoRefresh ) {
this._registerRefreshTokenTimeout();
}
} )
.then( () => this );
}
/**
* Destroys token instance. Stops refreshing.
*/
destroy() {
clearTimeout( this._tokenRefreshTimeout );
}
/**
* Checks whether the provided token follows the JSON Web Tokens (JWT) format.
*
* @protected
* @param {String} tokenValue The token to validate.
*/
_validateTokenValue( tokenValue ) {
// The token must be a string.
const isString = typeof tokenValue === 'string';
// The token must be a plain string without quotes ("").
const isPlainString = !/^".*"$/.test( tokenValue );
// JWT token contains 3 parts: header, payload, and signature.
// Each part is separated by a dot.
const isJWTFormat = isString && tokenValue.split( '.' ).length === 3;
if ( !( isPlainString && isJWTFormat ) ) {
/**
* The provided token must follow the [JSON Web Tokens](https://jwt.io/introduction/) format.
*
* @error token-not-in-jwt-format
*/
throw new CKEditorError( 'token-not-in-jwt-format', this );
}
}
/**
* Registers a refresh token timeout for the time taken from token.
*
* @protected
*/
_registerRefreshTokenTimeout() {
const tokenRefreshTimeoutTime = this._getTokenRefreshTimeoutTime();
clearTimeout( this._tokenRefreshTimeout );
this._tokenRefreshTimeout = setTimeout( () => {
this.refreshToken();
}, tokenRefreshTimeoutTime );
}
/**
* Returns token refresh timeout time calculated from expire time in the token payload.
*
* If the token parse fails or the token payload doesn't contain, the default DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME is returned.
*
* @protected
* @returns {Number}
*/
_getTokenRefreshTimeoutTime() {
try {
const [ , binaryTokenPayload ] = this.value.split( '.' );
const { exp: tokenExpireTime } = JSON.parse( atob( binaryTokenPayload ) );
if ( !tokenExpireTime ) {
return DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME;
}
const tokenRefreshTimeoutTime = Math.floor( ( ( tokenExpireTime * 1000 ) - Date.now() ) / 2 );
return tokenRefreshTimeoutTime;
} catch ( err ) {
return DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME;
}
}
/**
* Creates a initialized {@link module:cloud-services/token~Token} instance.
*
* @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 module:cloud-services/token~refreshToken} interface.
* @param {Object} options
* @param {String} [options.initValue] Initial value of the token.
* @param {Boolean} [options.autoRefresh=true] Specifies whether to start the refresh automatically.
* @returns {Promise.<module:cloud-services/token~Token>}
*/
static create( tokenUrlOrRefreshToken, options = DEFAULT_OPTIONS ) {
const token = new Token( tokenUrlOrRefreshToken, options );
return token.init();
}
export default class Token extends ObservableMixin() {
/**
* Creates `Token` instance.
* Method `init` should be called after using the constructor or use `create` method instead.
*
* @param 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 module:cloud-services/token~refreshToken} interface.
*/
constructor(tokenUrlOrRefreshToken, options = {}) {
super();
if (!tokenUrlOrRefreshToken) {
/**
* A `tokenUrl` must be provided as the first constructor argument.
*
* @error token-missing-token-url
*/
throw new CKEditorError('token-missing-token-url', this);
}
if (options.initValue) {
this._validateTokenValue(options.initValue);
}
this.set('value', options.initValue);
if (typeof tokenUrlOrRefreshToken === 'function') {
this._refresh = tokenUrlOrRefreshToken;
}
else {
this._refresh = () => defaultRefreshToken(tokenUrlOrRefreshToken);
}
this._options = { ...DEFAULT_OPTIONS, ...options };
}
/**
* Initializes the token.
*/
init() {
return new Promise((resolve, reject) => {
if (!this.value) {
this.refreshToken()
.then(resolve)
.catch(reject);
return;
}
if (this._options.autoRefresh) {
this._registerRefreshTokenTimeout();
}
resolve(this);
});
}
/**
* Refresh token method. Useful in a method form as it can be override in tests.
*/
refreshToken() {
return this._refresh()
.then(value => {
this._validateTokenValue(value);
this.set('value', value);
if (this._options.autoRefresh) {
this._registerRefreshTokenTimeout();
}
return this;
});
}
/**
* Destroys token instance. Stops refreshing.
*/
destroy() {
clearTimeout(this._tokenRefreshTimeout);
}
/**
* Checks whether the provided token follows the JSON Web Tokens (JWT) format.
*
* @param tokenValue The token to validate.
*/
_validateTokenValue(tokenValue) {
// The token must be a string.
const isString = typeof tokenValue === 'string';
// The token must be a plain string without quotes ("").
const isPlainString = !/^".*"$/.test(tokenValue);
// JWT token contains 3 parts: header, payload, and signature.
// Each part is separated by a dot.
const isJWTFormat = isString && tokenValue.split('.').length === 3;
if (!(isPlainString && isJWTFormat)) {
/**
* The provided token must follow the [JSON Web Tokens](https://jwt.io/introduction/) format.
*
* @error token-not-in-jwt-format
*/
throw new CKEditorError('token-not-in-jwt-format', this);
}
}
/**
* Registers a refresh token timeout for the time taken from token.
*/
_registerRefreshTokenTimeout() {
const tokenRefreshTimeoutTime = this._getTokenRefreshTimeoutTime();
clearTimeout(this._tokenRefreshTimeout);
this._tokenRefreshTimeout = setTimeout(() => {
this.refreshToken();
}, tokenRefreshTimeoutTime);
}
/**
* Returns token refresh timeout time calculated from expire time in the token payload.
*
* If the token parse fails or the token payload doesn't contain, the default DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME is returned.
*/
_getTokenRefreshTimeoutTime() {
try {
const [, binaryTokenPayload] = this.value.split('.');
const { exp: tokenExpireTime } = JSON.parse(atob(binaryTokenPayload));
if (!tokenExpireTime) {
return DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME;
}
const tokenRefreshTimeoutTime = Math.floor(((tokenExpireTime * 1000) - Date.now()) / 2);
return tokenRefreshTimeoutTime;
}
catch (err) {
return DEFAULT_TOKEN_REFRESH_TIMEOUT_TIME;
}
}
/**
* Creates a initialized {@link module:cloud-services/token~Token} instance.
*
* @param 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 module:cloud-services/token~refreshToken} interface.
*/
static create(tokenUrlOrRefreshToken, options = {}) {
const token = new Token(tokenUrlOrRefreshToken, options);
return token.init();
}
}
mix( Token, ObservableMixin );
/**

@@ -220,42 +150,24 @@ * This function is called in a defined interval by the {@link ~Token} class. It also can be invoked manually.

* 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 ) {
/**
* Cannot download new token from the provided url.
*
* @error token-cannot-download-new-token
*/
return reject(
new CKEditorError( 'token-cannot-download-new-token', null )
);
}
return resolve( xhrResponse );
} );
xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );
xhr.send();
} );
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) {
/**
* Cannot download new token from the provided url.
*
* @error token-cannot-download-new-token
*/
return reject(new CKEditorError('token-cannot-download-new-token', null));
}
return resolve(xhrResponse);
});
xhr.addEventListener('error', () => reject(new Error('Network Error')));
xhr.addEventListener('abort', () => reject(new Error('Abort')));
xhr.send();
});
}
export default Token;

@@ -5,286 +5,180 @@ /**

*/
/**
* @module cloud-services/uploadgateway/fileuploader
*/
/* globals XMLHttpRequest, FormData, Blob, atob */
import { mix, EmitterMixin, CKEditorError } from 'ckeditor5/src/utils';
import { EmitterMixin, CKEditorError } from 'ckeditor5/src/utils';
const BASE64_HEADER_REG_EXP = /^data:(\S*?);base64,/;
/**
* FileUploader class used to upload single file.
*/
export default class FileUploader {
/**
* Creates `FileUploader` instance.
*
* @param {Blob|String} fileOrData A blob object or a data string encoded with Base64.
* @param {module:cloud-services/token~Token} token Token used for authentication.
* @param {String} apiAddress API address.
*/
constructor( fileOrData, token, apiAddress ) {
if ( !fileOrData ) {
/**
* File must be provided as the first argument.
*
* @error fileuploader-missing-file
*/
throw new CKEditorError( 'fileuploader-missing-file', null );
}
if ( !token ) {
/**
* Token must be provided as the second argument.
*
* @error fileuploader-missing-token
*/
throw new CKEditorError( 'fileuploader-missing-token', null );
}
if ( !apiAddress ) {
/**
* Api address must be provided as the third argument.
*
* @error fileuploader-missing-api-address
*/
throw new CKEditorError( 'fileuploader-missing-api-address', null );
}
/**
* A file that is being uploaded.
*
* @type {Blob}
*/
this.file = _isBase64( fileOrData ) ? _base64ToBlob( fileOrData ) : fileOrData;
/**
* CKEditor Cloud Services access token.
*
* @type {module:cloud-services/token~Token}
* @private
*/
this._token = token;
/**
* CKEditor Cloud Services API address.
*
* @type {String}
* @private
*/
this._apiAddress = apiAddress;
}
/**
* Registers callback on `progress` event.
*
* @chainable
* @param {Function} callback
* @returns {module:cloud-services/uploadgateway/fileuploader~FileUploader}
*/
onProgress( callback ) {
this.on( 'progress', ( event, data ) => callback( data ) );
return this;
}
/**
* Registers callback on `error` event. Event is called once when error occurs.
*
* @chainable
* @param {Function} callback
* @returns {module:cloud-services/uploadgateway/fileuploader~FileUploader}
*/
onError( callback ) {
this.once( 'error', ( event, data ) => callback( data ) );
return this;
}
/**
* Aborts upload process.
*/
abort() {
this.xhr.abort();
}
/**
* Sends XHR request to API.
*
* @chainable
* @returns {Promise.<Object>}
*/
send() {
this._prepareRequest();
this._attachXHRListeners();
return this._sendRequest();
}
/**
* Prepares XHR request.
*
* @private
*/
_prepareRequest() {
const xhr = new XMLHttpRequest();
xhr.open( 'POST', this._apiAddress );
xhr.setRequestHeader( 'Authorization', this._token.value );
xhr.responseType = 'json';
this.xhr = xhr;
}
/**
* Attaches listeners to the XHR.
*
* @private
*/
_attachXHRListeners() {
const that = this;
const xhr = this.xhr;
xhr.addEventListener( 'error', onError( 'Network Error' ) );
xhr.addEventListener( 'abort', onError( 'Abort' ) );
/* istanbul ignore else */
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', event => {
if ( event.lengthComputable ) {
this.fire( 'progress', {
total: event.total,
uploaded: event.loaded
} );
}
} );
}
xhr.addEventListener( 'load', () => {
const statusCode = xhr.status;
const xhrResponse = xhr.response;
if ( statusCode < 200 || statusCode > 299 ) {
return this.fire( 'error', xhrResponse.message || xhrResponse.error );
}
} );
function onError( message ) {
return () => that.fire( 'error', message );
}
}
/**
* Sends XHR request.
*
* @private
*/
_sendRequest() {
const formData = new FormData();
const xhr = this.xhr;
formData.append( 'file', this.file );
return new Promise( ( resolve, reject ) => {
xhr.addEventListener( 'load', () => {
const statusCode = xhr.status;
const xhrResponse = xhr.response;
if ( statusCode < 200 || statusCode > 299 ) {
if ( xhrResponse.message ) {
/**
* Uploading file failed.
*
* @error fileuploader-uploading-data-failed
*/
return reject( new CKEditorError(
'fileuploader-uploading-data-failed',
this,
{ message: xhrResponse.message }
) );
}
return reject( xhrResponse.error );
}
return resolve( xhrResponse );
} );
xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );
xhr.send( formData );
} );
}
/**
* Fired when error occurs.
*
* @event error
* @param {String} error Error message
*/
/**
* Fired on upload progress.
*
* @event progress
* @param {Object} status Total and uploaded status
*/
export default class FileUploader extends EmitterMixin() {
/**
* Creates `FileUploader` instance.
*
* @param fileOrData A blob object or a data string encoded with Base64.
* @param token Token used for authentication.
* @param apiAddress API address.
*/
constructor(fileOrData, token, apiAddress) {
super();
if (!fileOrData) {
/**
* File must be provided as the first argument.
*
* @error fileuploader-missing-file
*/
throw new CKEditorError('fileuploader-missing-file', null);
}
if (!token) {
/**
* Token must be provided as the second argument.
*
* @error fileuploader-missing-token
*/
throw new CKEditorError('fileuploader-missing-token', null);
}
if (!apiAddress) {
/**
* Api address must be provided as the third argument.
*
* @error fileuploader-missing-api-address
*/
throw new CKEditorError('fileuploader-missing-api-address', null);
}
this.file = _isBase64(fileOrData) ? _base64ToBlob(fileOrData) : fileOrData;
this._token = token;
this._apiAddress = apiAddress;
}
/**
* Registers callback on `progress` event.
*/
onProgress(callback) {
this.on('progress', (event, data) => callback(data));
return this;
}
/**
* Registers callback on `error` event. Event is called once when error occurs.
*/
onError(callback) {
this.once('error', (event, data) => callback(data));
return this;
}
/**
* Aborts upload process.
*/
abort() {
this.xhr.abort();
}
/**
* Sends XHR request to API.
*/
send() {
this._prepareRequest();
this._attachXHRListeners();
return this._sendRequest();
}
/**
* Prepares XHR request.
*/
_prepareRequest() {
const xhr = new XMLHttpRequest();
xhr.open('POST', this._apiAddress);
xhr.setRequestHeader('Authorization', this._token.value);
xhr.responseType = 'json';
this.xhr = xhr;
}
/**
* Attaches listeners to the XHR.
*/
_attachXHRListeners() {
const xhr = this.xhr;
const onError = (message) => {
return () => this.fire('error', message);
};
xhr.addEventListener('error', onError('Network Error'));
xhr.addEventListener('abort', onError('Abort'));
/* istanbul ignore else */
if (xhr.upload) {
xhr.upload.addEventListener('progress', event => {
if (event.lengthComputable) {
this.fire('progress', {
total: event.total,
uploaded: event.loaded
});
}
});
}
xhr.addEventListener('load', () => {
const statusCode = xhr.status;
const xhrResponse = xhr.response;
if (statusCode < 200 || statusCode > 299) {
return this.fire('error', xhrResponse.message || xhrResponse.error);
}
});
}
/**
* Sends XHR request.
*/
_sendRequest() {
const formData = new FormData();
const xhr = this.xhr;
formData.append('file', this.file);
return new Promise((resolve, reject) => {
xhr.addEventListener('load', () => {
const statusCode = xhr.status;
const xhrResponse = xhr.response;
if (statusCode < 200 || statusCode > 299) {
if (xhrResponse.message) {
/**
* Uploading file failed.
*
* @error fileuploader-uploading-data-failed
*/
return reject(new CKEditorError('fileuploader-uploading-data-failed', this, { message: xhrResponse.message }));
}
return reject(xhrResponse.error);
}
return resolve(xhrResponse);
});
xhr.addEventListener('error', () => reject(new Error('Network Error')));
xhr.addEventListener('abort', () => reject(new Error('Abort')));
xhr.send(formData);
});
}
}
mix( FileUploader, EmitterMixin );
/**
* Transforms Base64 string data into file.
*
* @param {String} base64 String data.
* @param {Number} [sliceSize=512]
* @returns {Blob}
* @private
* @param base64 String data.
*/
function _base64ToBlob( base64, sliceSize = 512 ) {
try {
const contentType = base64.match( BASE64_HEADER_REG_EXP )[ 1 ];
const base64Data = atob( base64.replace( BASE64_HEADER_REG_EXP, '' ) );
const byteArrays = [];
for ( let offset = 0; offset < base64Data.length; offset += sliceSize ) {
const slice = base64Data.slice( offset, offset + sliceSize );
const byteNumbers = new Array( slice.length );
for ( let i = 0; i < slice.length; i++ ) {
byteNumbers[ i ] = slice.charCodeAt( i );
}
byteArrays.push( new Uint8Array( byteNumbers ) );
}
return new Blob( byteArrays, { type: contentType } );
} catch ( error ) {
/**
* Problem with decoding Base64 image data.
*
* @error fileuploader-decoding-image-data-error
*/
throw new CKEditorError( 'fileuploader-decoding-image-data-error', null );
}
function _base64ToBlob(base64, sliceSize = 512) {
try {
const contentType = base64.match(BASE64_HEADER_REG_EXP)[1];
const base64Data = atob(base64.replace(BASE64_HEADER_REG_EXP, ''));
const byteArrays = [];
for (let offset = 0; offset < base64Data.length; offset += sliceSize) {
const slice = base64Data.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
byteArrays.push(new Uint8Array(byteNumbers));
}
return new Blob(byteArrays, { type: contentType });
}
catch (error) {
/**
* Problem with decoding Base64 image data.
*
* @error fileuploader-decoding-image-data-error
*/
throw new CKEditorError('fileuploader-decoding-image-data-error', null);
}
}
/**
* Checks that string is Base64.
*
* @param {String} string
* @returns {Boolean}
* @private
*/
function _isBase64( string ) {
if ( typeof string !== 'string' ) {
return false;
}
const match = string.match( BASE64_HEADER_REG_EXP );
return !!( match && match.length );
function _isBase64(string) {
if (typeof string !== 'string') {
return false;
}
const match = string.match(BASE64_HEADER_REG_EXP);
return !!(match && match.length);
}

@@ -5,10 +5,7 @@ /**

*/
/**
* @module cloud-services/uploadgateway/uploadgateway
*/
import FileUploader from './fileuploader';
import { CKEditorError } from 'ckeditor5/src/utils';
/**

@@ -18,63 +15,48 @@ * UploadGateway abstracts file uploads to CKEditor Cloud Services.

export default class UploadGateway {
/**
* Creates `UploadGateway` instance.
*
* @param {module:cloud-services/token~Token} token Token used for authentication.
* @param {String} apiAddress API address.
*/
constructor( token, apiAddress ) {
if ( !token ) {
/**
* Token must be provided.
*
* @error uploadgateway-missing-token
*/
throw new CKEditorError( 'uploadgateway-missing-token', null );
}
if ( !apiAddress ) {
/**
* Api address must be provided.
*
* @error uploadgateway-missing-api-address
*/
throw new CKEditorError( 'uploadgateway-missing-api-address', null );
}
/**
* CKEditor Cloud Services access token.
*
* @type {module:cloud-services/token~Token}
* @private
*/
this._token = token;
/**
* CKEditor Cloud Services API address.
*
* @type {String}
* @private
*/
this._apiAddress = apiAddress;
}
/**
* Creates a {@link module:cloud-services/uploadgateway/fileuploader~FileUploader} instance that wraps
* file upload process. The file is being sent at a time when the
* {@link module:cloud-services/uploadgateway/fileuploader~FileUploader#send} method is called.
*
* const token = await Token.create( 'https://token-endpoint' );
* new UploadGateway( token, 'https://example.org' )
* .upload( 'FILE' )
* .onProgress( ( data ) => console.log( data ) )
* .send()
* .then( ( response ) => console.log( response ) );
*
* @param {Blob|String} fileOrData A blob object or a data string encoded with Base64.
* @returns {module:cloud-services/uploadgateway/fileuploader~FileUploader} Returns `FileUploader` instance.
*/
upload( fileOrData ) {
return new FileUploader( fileOrData, this._token, this._apiAddress );
}
/**
* Creates `UploadGateway` instance.
*
* @param token Token used for authentication.
* @param apiAddress API address.
*/
constructor(token, apiAddress) {
if (!token) {
/**
* Token must be provided.
*
* @error uploadgateway-missing-token
*/
throw new CKEditorError('uploadgateway-missing-token', null);
}
if (!apiAddress) {
/**
* Api address must be provided.
*
* @error uploadgateway-missing-api-address
*/
throw new CKEditorError('uploadgateway-missing-api-address', null);
}
this._token = token;
this._apiAddress = apiAddress;
}
/**
* Creates a {@link module:cloud-services/uploadgateway/fileuploader~FileUploader} instance that wraps
* file upload process. The file is being sent at a time when the
* {@link module:cloud-services/uploadgateway/fileuploader~FileUploader#send} method is called.
*
* ```ts
* const token = await Token.create( 'https://token-endpoint' );
* new UploadGateway( token, 'https://example.org' )
* .upload( 'FILE' )
* .onProgress( ( data ) => console.log( data ) )
* .send()
* .then( ( response ) => console.log( response ) );
* ```
*
* @param {Blob|String} fileOrData A blob object or a data string encoded with Base64.
* @returns {module:cloud-services/uploadgateway/fileuploader~FileUploader} Returns `FileUploader` instance.
*/
upload(fileOrData) {
return new FileUploader(fileOrData, this._token, this._apiAddress);
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc